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

Issue:
[150 SigmaUSD] `Box.bytesWithoutRef` method's IR node (`ExtractBytesWithNoRef`)
Create a new struct in a separate module(file) in `mir` module and add it to `Expr` enum. 
Similar to the existing `ExtractBytes` IR node but without the transaction id and the index in the output.

### Serialization
- Implement `Arbitrary` trait and add serialization roundtrip PBT test for this node;

### Evaluation
- Implement `Evaluable` trait in `ergotree-interpreter` crate;
- Add new method in `IrErgoBox::bytesWithoutRef`
- Add evaluation tests;

Check out the existing IR node implementation (`ExtractBytes`) for details.

Check out the detailed implementation instructions for `Append` op in  #301

Reference Scala version:
https://github.com/ScorexFoundation/sigmastate-interpreter/blob/c863a9b1a82589e47b15f76f3affdb30a475e740/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala#L437-L456



Discussion / hints:
I'm making a start on this one

Fix patch:
diff --git a/ergo-lib/src/chain/ergo_box.rs b/ergo-lib/src/chain/ergo_box.rs
index d3b75f019..1f5247c52 100644
--- a/ergo-lib/src/chain/ergo_box.rs
+++ b/ergo-lib/src/chain/ergo_box.rs
@@ -214,6 +214,11 @@ impl IrErgoBox for ErgoBox {
     fn bytes(&self) -> Result<Vec<i8>, SigmaSerializationError> {
         Ok(self.sigma_serialize_bytes()?.as_vec_i8())
     }
+
+    fn bytes_without_ref(&self) -> Result<Vec<i8>, SigmaSerializationError> {
+        let candidate: ErgoBoxCandidate = self.clone().into();
+        Ok(candidate.sigma_serialize_bytes()?.as_vec_i8())
+    }
 }
 
 /// Assets that ErgoBox holds
diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs
index 0d4db7c1a..adedadafb 100644
--- a/ergotree-interpreter/src/eval.rs
+++ b/ergotree-interpreter/src/eval.rs
@@ -54,6 +54,7 @@ pub(crate) mod exponentiate;
 pub(crate) mod expr;
 pub(crate) mod extract_amount;
 pub(crate) mod extract_bytes;
+pub(crate) mod extract_bytes_with_no_ref;
 pub(crate) mod extract_creation_info;
 pub(crate) mod extract_id;
 pub(crate) mod extract_reg_as;
diff --git a/ergotree-interpreter/src/eval/context/ir_ergo_box_dummy.rs b/ergotree-interpreter/src/eval/context/ir_ergo_box_dummy.rs
index 82659c753..4704a9f8c 100644
--- a/ergotree-interpreter/src/eval/context/ir_ergo_box_dummy.rs
+++ b/ergotree-interpreter/src/eval/context/ir_ergo_box_dummy.rs
@@ -75,6 +75,10 @@ impl IrErgoBox for IrErgoBoxDummy {
     fn bytes(&self) -> Result<Vec<i8>, SigmaSerializationError> {
         Ok(self.sigma_serialize_bytes.clone())
     }
+
+    fn bytes_without_ref(&self) -> Result<Vec<i8>, SigmaSerializationError> {
+        self.bytes()
+    }
 }
 
 #[cfg(feature = "arbitrary")]
diff --git a/ergotree-interpreter/src/eval/expr.rs b/ergotree-interpreter/src/eval/expr.rs
index 9ec795f6a..e057c1183 100644
--- a/ergotree-interpreter/src/eval/expr.rs
+++ b/ergotree-interpreter/src/eval/expr.rs
@@ -76,6 +76,7 @@ impl Evaluable for Expr {
             Expr::Exponentiate(op) => op.eval(env, ctx),
             Expr::XorOf(op) => op.eval(env, ctx),
             Expr::ExtractBytes(op) => op.eval(env, ctx),
+            Expr::ExtractBytesWithNoRef(op) => op.eval(env, ctx),
         }
     }
 }
diff --git a/ergotree-interpreter/src/eval/extract_bytes_with_no_ref.rs b/ergotree-interpreter/src/eval/extract_bytes_with_no_ref.rs
new file mode 100644
index 000000000..914bbfa80
--- /dev/null
+++ b/ergotree-interpreter/src/eval/extract_bytes_with_no_ref.rs
@@ -0,0 +1,49 @@
+use ergotree_ir::mir::extract_bytes_with_no_ref::ExtractBytesWithNoRef;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for ExtractBytesWithNoRef {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        match input_v {
+            Value::CBox(b) => Ok(ctx.ctx.box_arena.get(&b)?.bytes_without_ref()?.into()),
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "Expected ExtractBytesWithNoRef input to be Value::CBox, got {0:?}",
+                input_v
+            ))),
+        }
+    }
+}
+
+#[allow(clippy::unwrap_used)]
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::global_vars::GlobalVars;
+    use sigma_test_util::force_any_val;
+    use std::rc::Rc;
+
+    #[test]
+    fn eval() {
+        let e: Expr = ExtractBytesWithNoRef {
+            input: Box::new(GlobalVars::SelfBox.into()),
+        }
+        .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        assert_eq!(
+            eval_out::<Vec<i8>>(&e, ctx.clone()),
+            ctx.self_box
+                .get_box(&ctx.box_arena)
+                .unwrap()
+                .bytes_without_ref()
+                .unwrap()
+        );
+    }
+}
diff --git a/ergotree-ir/README.md b/ergotree-ir/README.md
index 1d4bef65e..af2ec96ff 100644
--- a/ergotree-ir/README.md
+++ b/ergotree-ir/README.md
@@ -27,10 +27,6 @@ Descriptions for the operations can be found in [ErgoTree Specification](https:/
 
 - isProven
 
-#### Box
-
-- bytesWithoutRef
-
 #### AvlTree 
 
 - digest
@@ -83,7 +79,6 @@ Descriptions for the operations can be found in [ErgoTree Specification](https:/
 
 - headers
 - preHeader
-- selfBoxIndex
 - LastBlockUtxoRootHash
 
 
@@ -162,6 +157,7 @@ Descriptions for the operations can be found in [ErgoTree Specification](https:/
 - tokens
 - R0 .. R9
 - bytes
+- bytesWithoutRef
 
 #### Context
 
@@ -172,6 +168,7 @@ Descriptions for the operations can be found in [ErgoTree Specification](https:/
 - SELF
 - minerPubKey
 - getVar
+- selfBoxIndex
 
 #### Collection
 
diff --git a/ergotree-ir/src/ir_ergo_box.rs b/ergotree-ir/src/ir_ergo_box.rs
index e347cde93..07987bfe3 100644
--- a/ergotree-ir/src/ir_ergo_box.rs
+++ b/ergotree-ir/src/ir_ergo_box.rs
@@ -62,4 +62,6 @@ pub trait IrErgoBox: Debug {
     fn creation_info(&self) -> (i32, Vec<i8>);
     /// Box serialized bytes
     fn bytes(&self) -> Result<Vec<i8>, SigmaSerializationError>;
+    /// Box serialized bytes excluding txId & index
+    fn bytes_without_ref(&self) -> Result<Vec<i8>, SigmaSerializationError>;
 }
diff --git a/ergotree-ir/src/mir.rs b/ergotree-ir/src/mir.rs
index aace95690..674a8372f 100644
--- a/ergotree-ir/src/mir.rs
+++ b/ergotree-ir/src/mir.rs
@@ -49,6 +49,8 @@ pub mod expr;
 pub mod extract_amount;
 /// Box.bytes (serialized box bytes)
 pub mod extract_bytes;
+/// Box.bytesWithoutRef
+pub mod extract_bytes_with_no_ref;
 /// Box.creationInfo (height, tx id + box index)
 pub mod extract_creation_info;
 /// Box id, Blake2b256 hash of this box's content, basically equals to `blake2b256(bytes)`
diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs
index ed6bdbef1..47889deee 100644
--- a/ergotree-ir/src/mir/expr.rs
+++ b/ergotree-ir/src/mir/expr.rs
@@ -35,6 +35,7 @@ use super::decode_point::DecodePoint;
 use super::exponentiate::Exponentiate;
 use super::extract_amount::ExtractAmount;
 use super::extract_bytes::ExtractBytes;
+use super::extract_bytes_with_no_ref::ExtractBytesWithNoRef;
 use super::extract_creation_info::ExtractCreationInfo;
 use super::extract_id::ExtractId;
 use super::extract_reg_as::ExtractRegisterAs;
@@ -152,6 +153,8 @@ pub enum Expr {
     ExtractRegisterAs(ExtractRegisterAs),
     /// Extract serialized box bytes
     ExtractBytes(ExtractBytes),
+    /// Extract serialized box bytes excluding transaction_id & index
+    ExtractBytesWithNoRef(ExtractBytesWithNoRef),
     /// Extract box's guarding script serialized to bytes
     ExtractScriptBytes(ExtractScriptBytes),
     /// Tuple of height when block got included into the blockchain and transaction identifier with
@@ -277,6 +280,7 @@ impl Expr {
             Expr::Exponentiate(v) => v.tpe(),
             Expr::XorOf(v) => v.tpe(),
             Expr::ExtractBytes(v) => v.tpe(),
+            Expr::ExtractBytesWithNoRef(v) => v.tpe(),
         }
     }
 
diff --git a/ergotree-ir/src/mir/extract_bytes_with_no_ref.rs b/ergotree-ir/src/mir/extract_bytes_with_no_ref.rs
new file mode 100644
index 000000000..18aa8793b
--- /dev/null
+++ b/ergotree-ir/src/mir/extract_bytes_with_no_ref.rs
@@ -0,0 +1,81 @@
+use crate::serialization::op_code::OpCode;
+use crate::types::stype::SType;
+
+use super::expr::Expr;
+use super::expr::InvalidArgumentError;
+use super::unary_op::OneArgOp;
+use super::unary_op::OneArgOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
+
+/// Serialized box bytes without transaction_id and index
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ExtractBytesWithNoRef {
+    /// Box, type of SBox
+    pub input: Box<Expr>,
+}
+
+impl ExtractBytesWithNoRef {
+    /// Type
+    pub fn tpe(&self) -> SType {
+        SType::SColl(SType::SByte.into())
+    }
+}
+
+impl HasStaticOpCode for ExtractBytesWithNoRef {
+    const OP_CODE: OpCode = OpCode::EXTRACT_BYTES_WITH_NO_REF;
+}
+
+impl OneArgOp for ExtractBytesWithNoRef {
+    fn input(&self) -> &Expr {
+        &self.input
+    }
+}
+
+impl OneArgOpTryBuild for ExtractBytesWithNoRef {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        input.check_post_eval_tpe(&SType::SBox)?;
+        Ok(ExtractBytesWithNoRef {
+            input: input.into(),
+        })
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+mod arbitrary {
+    use super::*;
+    use crate::mir::expr::arbitrary::ArbExprParams;
+    use proptest::prelude::*;
+
+    impl Arbitrary for ExtractBytesWithNoRef {
+        type Parameters = usize;
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            any_with::<Expr>(ArbExprParams {
+                tpe: SType::SColl(SType::SByte.into()),
+                depth: 0,
+            })
+            .prop_map(|input| Self {
+                input: input.into(),
+            })
+            .boxed()
+        }
+    }
+}
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+    use super::*;
+    use crate::mir::global_vars::GlobalVars;
+    use crate::serialization::sigma_serialize_roundtrip;
+
+    #[test]
+    fn ser_roundtrip() {
+        let e: Expr = ExtractBytesWithNoRef {
+            input: Box::new(GlobalVars::SelfBox.into()),
+        }
+        .into();
+        assert_eq![sigma_serialize_roundtrip(&e), e];
+    }
+}
diff --git a/ergotree-ir/src/serialization/expr.rs b/ergotree-ir/src/serialization/expr.rs
index 86c77bfa8..660693f3a 100644
--- a/ergotree-ir/src/serialization/expr.rs
+++ b/ergotree-ir/src/serialization/expr.rs
@@ -40,6 +40,7 @@ use crate::mir::exponentiate::Exponentiate;
 use crate::mir::expr::Expr;
 use crate::mir::extract_amount::ExtractAmount;
 use crate::mir::extract_bytes::ExtractBytes;
+use crate::mir::extract_bytes_with_no_ref::ExtractBytesWithNoRef;
 use crate::mir::extract_creation_info::ExtractCreationInfo;
 use crate::mir::extract_id::ExtractId;
 use crate::mir::extract_reg_as::ExtractRegisterAs;
@@ -116,6 +117,7 @@ impl Expr {
                 ExtractRegisterAs::OP_CODE => Ok(ExtractRegisterAs::sigma_parse(r)?.into()),
                 ExtractScriptBytes::OP_CODE => Ok(ExtractScriptBytes::sigma_parse(r)?.into()),
                 ExtractBytes::OP_CODE => Ok(ExtractBytes::sigma_parse(r)?.into()),
+                ExtractBytesWithNoRef::OP_CODE => Ok(ExtractBytesWithNoRef::sigma_parse(r)?.into()),
                 ExtractCreationInfo::OP_CODE => Ok(ExtractCreationInfo::sigma_parse(r)?.into()),
                 ExtractId::OP_CODE => Ok(ExtractId::sigma_parse(r)?.into()),
                 OpCode::EQ => Ok(bin_op_sigma_parse(RelationOp::Eq.into(), r)?),
@@ -279,6 +281,7 @@ impl SigmaSerializable for Expr {
             Expr::Exponentiate(op) => op.sigma_serialize_w_opcode(w),
             Expr::XorOf(op) => op.sigma_serialize_w_opcode(w),
             Expr::ExtractBytes(op) => op.sigma_serialize_w_opcode(w),
+            Expr::ExtractBytesWithNoRef(op) => op.sigma_serialize_w_opcode(w),
         }
     }
 


Test patch: