Repository: ergoplatform/sigma-rust (language: rust)

Issue:
Bug with slicing an empty array in a transaction builder (fleet)
This bug is strange. Using a node, a tx can be formed and passed to chain, but using fleet-SDK the same tx gives the following error below. When i posted this as an issue in fleet-sdk github, it was mentioned that this bug was likely sigma-rust and not in the scope of fleet.

The scope of the contract is that the user is withdrawing their token from the contract, which is allowed.

The contract has been proven working through a node/swagger, but does not work with fleet / nautilus

`{
    "code": 1,
    "info": "Transaction signing error: Prover error (tx input index 0): Evaluation error: error: Slice: indices 0..1 out of bounds for collection size 0"
}`

here is the json of the tx:
```
{
    "inputs": [
        {
            "boxId": "c00efdbc4dbb0d568f9fdbbfaaaf7b17a42612cc0c69cbea314d0e2e27e349db",
            "value": "1000000",
            "ergoTree": "101904000500040004000e0301b97b040208cd03704333b53273fd0cbec619124f04ba6019241756745273b3eff792e4d8ffc7c9040404020400040204020400010004000400040204fe01040604040580dac4090406040404000100d813d601b2a5730000d602c27201d603937202c2a7d6049299c17201c1a77301d605db6308a7d606b17205d607b4720573027206d608db63087201d609b4720873037206d60ae4c6a7040c410ed60be4c6a7051ad60ce4c6a70605d60de4c672010605d60eeded93720ae4c67201040c410e93720be4c67201051a93720c720dd60f7304d610b1a5d611b2a5730500d612c27211d613d07306d1ececededed720372049372077209720eededed93cb7202720faf720bd901140eae7208d901164d0eed938c7216017214928c721602720d720e95eded917210730791b1db6308b2a57308007309e6c6b2a5730a00070c410ed803d614b2a5730b00d615b2db63087214730c00d616e4c67214070c410eededed93cbc27214720fed938c721501c5a7938c7215027eb1721605937216720a93e4c67201074d0e7215730dededededed95937eb27212730e0004730f9372127213d801d614b1721293b472137310b17213b4721295917214731173127313721492c172117314959172107315d804d614b2a5731600d615e4c672140404d616b2db63087214731700d617b2720b721500eded93c272148cb2720a72150002ed938c7216017217928c721602720c93b57207d901184d0e948c7218017217b57209d901184d0e948c72180172177318720e72037204",
            "creationHeight": 1097773,
            "assets": [
                {
                    "tokenId": "0fdb7ff8b37479b6eb7aab38d45af2cfeefabbefdc7eebc0348d25dd65bc2c91",
                    "amount": "1"
                }
            ],
            "additionalRegisters": {
                "R4": "0c410e0180897a240008cd03f68e2d2a05f62d2dcb3db017c62fcdd92c554ff6f31cc5acccb2157c63b45238",
                "R5": "1a01200fdb7ff8b37479b6eb7aab38d45af2cfeefabbefdc7eebc0348d25dd65bc2c91",
                "R6": "0502"
            },
            "transactionId": "bfa53fe3af8233e5d6023ca719d47c3197857ed31fd28be3e5dfc8a30fcba9e0",
            "index": 0,
            "extension": {}
        },
        {
            "boxId": "a673323e1e91c8b63fde742fe326209f1d1ec710dcc36c3e90c39ee439f8f424",
            "value": "3944900000",
            "ergoTree": "0008cd026708de5aea2e9a2db870607e403202b753a2cf6e0cb24ac6e6b127215c4bd014",
            "creationHeight": 1101200,
            "assets": [],
            "additionalRegisters": {},
            "transactionId": "a9308015385803c3d9115ce770055f3c4eb262856b7fa4ab3cdbeb1d085c6fa1",
            "index": 8,
            "extension": {}
        }
    ],
    "dataInputs": [],
    "outputs": [
        {
            "value": "1000000",
            "ergoTree": "101904000500040004000e0301b97b040208cd03704333b53273fd0cbec619124f04ba6019241756745273b3eff792e4d8ffc7c9040404020400040204020400010004000400040204fe01040604040580dac4090406040404000100d813d601b2a5730000d602c27201d603937202c2a7d6049299c17201c1a77301d605db6308a7d606b17205d607b4720573027206d608db63087201d609b4720873037206d60ae4c6a7040c410ed60be4c6a7051ad60ce4c6a70605d60de4c672010605d60eeded93720ae4c67201040c410e93720be4c67201051a93720c720dd60f7304d610b1a5d611b2a5730500d612c27211d613d07306d1ececededed720372049372077209720eededed93cb7202720faf720bd901140eae7208d901164d0eed938c7216017214928c721602720d720e95eded917210730791b1db6308b2a57308007309e6c6b2a5730a00070c410ed803d614b2a5730b00d615b2db63087214730c00d616e4c67214070c410eededed93cbc27214720fed938c721501c5a7938c7215027eb1721605937216720a93e4c67201074d0e7215730dededededed95937eb27212730e0004730f9372127213d801d614b1721293b472137310b17213b4721295917214731173127313721492c172117314959172107315d804d614b2a5731600d615e4c672140404d616b2db63087214731700d617b2720b721500eded93c272148cb2720a72150002ed938c7216017217928c721602720c93b57207d901184d0e948c7218017217b57209d901184d0e948c72180172177318720e72037204",
            "creationHeight": 1104768,
            "assets": [],
            "additionalRegisters": {
                "R4": "0c410e0180897a240008cd03f68e2d2a05f62d2dcb3db017c62fcdd92c554ff6f31cc5acccb2157c63b45238",
                "R5": "1a01200fdb7ff8b37479b6eb7aab38d45af2cfeefabbefdc7eebc0348d25dd65bc2c91",
                "R6": "0502"
            }
        },
        {
            "value": "10000000",
            "ergoTree": "0008cd03704333b53273fd0cbec619124f04ba6019241756745273b3eff792e4d8ffc7c9",
            "creationHeight": 1104768,
            "assets": [],
            "additionalRegisters": {}
        },
        {
            "value": "1000000",
            "ergoTree": "0008cd03f68e2d2a05f62d2dcb3db017c62fcdd92c554ff6f31cc5acccb2157c63b45238",
            "creationHeight": 1104768,
            "assets": [
                {
                    "tokenId": "0fdb7ff8b37479b6eb7aab38d45af2cfeefabbefdc7eebc0348d25dd65bc2c91",
                    "amount": "1"
                }
            ],
            "additionalRegisters": {
                "R4": "0400"
            }
        },
        {
            "value": "1100000",
            "ergoTree": "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304",
            "creationHeight": 1104768,
            "assets": [],
            "additionalRegisters": {}
        },
        {
            "value": "3932800000",
            "ergoTree": "0008cd03f68e2d2a05f62d2dcb3db017c62fcdd92c554ff6f31cc5acccb2157c63b45238",
            "creationHeight": 1104768,
            "assets": [],
            "additionalRegisters": {}
        }
    ]
}
```


Discussion / hints:
The ErgoTree in the input box decompiles to the following code(excerpt):
```scala
val v1 = OUTPUTS(0)
val v5 = SELF.tokens
val v6 = v5.size
...
val v8 = v1.tokens
val v9 = v8.slice(0, v6)
```
Since there are no tokens in the first output box(v8 has an empty collection), the slice fails.
I suspect that in the case of you running this operation on the node, the transaction is constructed so that the first output box has some tokens in it. 
> Since there are no tokens in the first output box(v8 has an empty collection), the slice fails. I suspect that in the case of you running this operation on the node, the transaction is constructed so that the first output box has some tokens in it.


Unfortunately, this is not the case. Here is a formation of a successful tx made to the same contract, done by Krasavice through a node:
https://explorer.ergoplatform.com/en/transactions/effa18f650754950cb0387ef69ca64ca0569c1eb8d81802aafbbc91f8031ac66

As you can see, output(0) contains no token array (please note that this was a test different from my own, so the inputBox and contract address have changed slightly in my example due to arbitrary uniqueness conditions in the contract

You are right! I suspect the Scala version returns an empty collection when a slice is called with an out-of-bounds range. I'll look into it ASAP.

Fix patch:
diff --git a/ergotree-interpreter/src/eval/coll_slice.rs b/ergotree-interpreter/src/eval/coll_slice.rs
index c39de608a..6094eacd7 100644
--- a/ergotree-interpreter/src/eval/coll_slice.rs
+++ b/ergotree-interpreter/src/eval/coll_slice.rs
@@ -22,14 +22,14 @@ impl Evaluable for Slice {
         }?;
         let from = from_v.try_extract_into::<i32>()?;
         let until = until_v.try_extract_into::<i32>()?;
-        match input_vec.get(from as usize..until as usize) {
+        // intersection of the range with collection bounds
+        // to preserve the Scala version semantics of slice op
+        // see https://github.com/ergoplatform/sigma-rust/issues/724
+        let range = from.max(0) as usize..until.min(input_vec.len() as i32) as usize;
+        match input_vec.get(range) {
             Some(slice) => Ok(Value::Coll(CollKind::from_vec(elem_tpe, slice.to_vec())?)),
-            None => Err(EvalError::Misc(format!(
-                "Slice: indices {0:?}..{1:?} out of bounds for collection size {2:?}",
-                from,
-                until,
-                input_vec.len()
-            ))),
+            // Scala version returns empty collection if the range is out of bounds
+            None => Ok(Value::Coll(CollKind::from_vec(elem_tpe, vec![])?)),
         }
     }
 }
@@ -42,7 +42,7 @@ mod tests {
     use ergotree_ir::types::stype::SType;
 
     use super::*;
-    use crate::eval::tests::{eval_out_wo_ctx, try_eval_out_wo_ctx};
+    use crate::eval::tests::eval_out_wo_ctx;
 
     #[test]
     fn slice() {
@@ -74,6 +74,9 @@ mod tests {
 
     #[test]
     fn slice_empty_coll() {
+        // In Scala version the slice with indices out of bounds does not throw
+        // but returns an intersection or an empty array.
+        // see https://github.com/ergoplatform/sigma-rust/issues/724
         let expr: Expr = Slice::new(
             Expr::Const(Vec::<i64>::new().into()),
             Expr::Const(1i32.into()),
@@ -81,7 +84,7 @@ mod tests {
         )
         .unwrap()
         .into();
-        assert!(try_eval_out_wo_ctx::<Vec<i64>>(&expr).is_err());
+        assert_eq!(eval_out_wo_ctx::<Vec<i64>>(&expr), Vec::<i64>::new());
     }
 
     #[test]
@@ -98,6 +101,9 @@ mod tests {
 
     #[test]
     fn slice_start_index_greater_than_end_index() {
+        // In Scala version the slice with indices out of bounds does not throw
+        // but returns an intersection or an empty array.
+        // see https://github.com/ergoplatform/sigma-rust/issues/724
         let expr: Expr = Slice::new(
             Expr::Const(vec![1i64, 2i64, 3i64, 4i64].into()),
             Expr::Const(3i32.into()),
@@ -105,11 +111,14 @@ mod tests {
         )
         .unwrap()
         .into();
-        assert!(try_eval_out_wo_ctx::<Vec<i64>>(&expr).is_err());
+        assert_eq!(eval_out_wo_ctx::<Vec<i64>>(&expr), Vec::<i64>::new());
     }
 
     #[test]
     fn slice_index_out_of_bounds() {
+        // In Scala version the slice with indices out of bounds does not throw
+        // but returns an intersection or an empty array.
+        // see https://github.com/ergoplatform/sigma-rust/issues/724
         let expr: Expr = Slice::new(
             Expr::Const(vec![1i64, 2i64, 3i64, 4i64].into()),
             Expr::Const((-1i32).into()),
@@ -117,7 +126,7 @@ mod tests {
         )
         .unwrap()
         .into();
-        assert!(try_eval_out_wo_ctx::<Vec<i64>>(&expr).is_err());
+        assert_eq!(eval_out_wo_ctx::<Vec<i64>>(&expr), vec![1i64]);
 
         let expr: Expr = Slice::new(
             Expr::Const(vec![1i64, 2i64, 3i64, 4i64].into()),
@@ -126,6 +135,18 @@ mod tests {
         )
         .unwrap()
         .into();
-        assert!(try_eval_out_wo_ctx::<Vec<i64>>(&expr).is_err());
+        assert_eq!(
+            eval_out_wo_ctx::<Vec<i64>>(&expr),
+            vec![1i64, 2i64, 3i64, 4i64]
+        );
+
+        let expr: Expr = Slice::new(
+            Expr::Const(vec![1i64, 2i64, 3i64, 4i64].into()),
+            Expr::Const(9i32.into()),
+            Expr::Const(10i32.into()),
+        )
+        .unwrap()
+        .into();
+        assert_eq!(eval_out_wo_ctx::<Vec<i64>>(&expr), Vec::<i64>::new());
     }
 }


Test patch: