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

Issue:
DeserializeContext and DeserializeRegister evaluation
Extracted from #239 


Discussion / hints:

Fix patch:
diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs
index e6e285dfc..bf5b8bcf4 100644
--- a/ergotree-interpreter/src/eval.rs
+++ b/ergotree-interpreter/src/eval.rs
@@ -6,6 +6,7 @@ use ergotree_ir::ir_ergo_box::IrErgoBoxArenaError;
 use ergotree_ir::mir::constant::TryExtractFromError;
 use ergotree_ir::mir::expr::Expr;
 use ergotree_ir::mir::value::Value;
+use ergotree_ir::serialization::SigmaParsingError;
 use ergotree_ir::serialization::SigmaSerializationError;
 use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
 
@@ -118,6 +119,9 @@ pub enum EvalError {
     /// Sigma serialization error
     #[error("Serialization error: {0}")]
     SigmaSerializationError(#[from] SigmaSerializationError),
+    /// Sigma serialization parsing error
+    #[error("Serialization parsing error: {0}")]
+    SigmaParsingError(#[from] SigmaParsingError),
     /// ErgoTree error
     #[error("ErgoTree error: {0}")]
     ErgoTreeError(#[from] ErgoTreeError),
diff --git a/ergotree-interpreter/src/eval/context.rs b/ergotree-interpreter/src/eval/context.rs
index cfb44cf22..c571390d2 100644
--- a/ergotree-interpreter/src/eval/context.rs
+++ b/ergotree-interpreter/src/eval/context.rs
@@ -1,4 +1,4 @@
-mod ir_ergo_box_dummy;
+pub(crate) mod ir_ergo_box_dummy;
 
 use std::rc::Rc;
 
@@ -28,6 +28,16 @@ pub struct Context {
     pub extension: ContextExtension,
 }
 
+impl Context {
+    /// Return a new Context with given context extension
+    pub fn with_extension(self, ext: ContextExtension) -> Self {
+        Context {
+            extension: ext,
+            ..self
+        }
+    }
+}
+
 #[cfg(feature = "arbitrary")]
 mod arbitrary {
     use std::collections::HashMap;
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 336ad281d..bbc6ba2c0 100644
--- a/ergotree-interpreter/src/eval/context/ir_ergo_box_dummy.rs
+++ b/ergotree-interpreter/src/eval/context/ir_ergo_box_dummy.rs
@@ -23,13 +23,13 @@ impl IrErgoBoxArena for IrErgoBoxDummyArena {
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub(crate) struct IrErgoBoxDummy {
-    id: IrBoxId,
-    value: i64,
-    tokens: Vec<(Vec<i8>, i64)>,
-    additional_registers: Vec<Constant>,
-    creation_height: i32,
-    script_bytes: Vec<i8>,
-    creation_info: (i32, Vec<i8>),
+    pub(crate) id: IrBoxId,
+    pub(crate) value: i64,
+    pub(crate) tokens: Vec<(Vec<i8>, i64)>,
+    pub(crate) additional_registers: Vec<Constant>,
+    pub(crate) creation_height: i32,
+    pub(crate) script_bytes: Vec<i8>,
+    pub(crate) creation_info: (i32, Vec<i8>),
 }
 
 impl IrErgoBox for IrErgoBoxDummy {
@@ -55,7 +55,7 @@ impl IrErgoBox for IrErgoBoxDummy {
             1 => Some(self.script_bytes.clone().into()),
             2 => Some(self.tokens.clone().into()),
             3 => Some(self.creation_info.clone().into()),
-            _ => self.additional_registers.get(id as usize).cloned(),
+            _ => self.additional_registers.get(id as usize - 4).cloned(),
         }
     }
 
diff --git a/ergotree-interpreter/src/eval/deserialize_context.rs b/ergotree-interpreter/src/eval/deserialize_context.rs
index 448fa9eac..4fa22e2fe 100644
--- a/ergotree-interpreter/src/eval/deserialize_context.rs
+++ b/ergotree-interpreter/src/eval/deserialize_context.rs
@@ -1,5 +1,9 @@
+use ergotree_ir::mir::constant::TryExtractInto;
 use ergotree_ir::mir::deserialize_context::DeserializeContext;
+use ergotree_ir::mir::expr::Expr;
 use ergotree_ir::mir::value::Value;
+use ergotree_ir::serialization::SigmaSerializable;
+use ergotree_ir::types::stype::SType;
 
 use crate::eval::env::Env;
 use crate::eval::EvalContext;
@@ -7,7 +11,104 @@ use crate::eval::EvalError;
 use crate::eval::Evaluable;
 
 impl Evaluable for DeserializeContext {
-    fn eval(&self, _env: &Env, _ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        Err(EvalError::NotImplementedYet("DeserializeContext"))
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        match ctx.ctx.extension.values.get(&self.id) {
+            Some(c) => {
+                if c.tpe != SType::SColl(SType::SByte.into()) {
+                    Err(EvalError::UnexpectedExpr(format!("DeserializeContext: expected extension value to have type SColl(SByte), got {:?}", c.tpe)))
+                } else {
+                    let bytes = c.v.clone().try_extract_into::<Vec<u8>>()?;
+                    let expr = Expr::sigma_parse_bytes(bytes.as_slice())?;
+                    if expr.tpe() != self.tpe {
+                        return Err(EvalError::UnexpectedExpr(format!("DeserializeContext: expected deserialized expr to have type {:?}, got {:?}", self.tpe, expr.tpe())));
+                    }
+                    expr.eval(env, ctx)
+                }
+            }
+            None => Err(EvalError::NotFound(format!(
+                "DeserializeContext: no value with id {} in context extension",
+                self.id
+            ))),
+        }
+    }
+}
+
+#[allow(clippy::unwrap_used)]
+#[cfg(test)]
+mod tests {
+    use std::rc::Rc;
+
+    use ergotree_ir::mir::constant::Constant;
+    use ergotree_ir::mir::global_vars::GlobalVars;
+    use sigma_test_util::force_any_val;
+
+    use crate::eval::context::Context;
+    use crate::eval::tests::try_eval_out;
+    use crate::sigma_protocol::prover::ContextExtension;
+
+    use super::*;
+
+    #[test]
+    fn eval() {
+        let expr: Expr = DeserializeContext {
+            tpe: SType::SBoolean,
+            id: 1,
+        }
+        .into();
+        let inner_expr: Expr = true.into();
+        let ctx_ext = ContextExtension {
+            values: [(1u8, inner_expr.sigma_serialize_bytes().unwrap().into())]
+                .iter()
+                .cloned()
+                .collect(),
+        };
+        let ctx = force_any_val::<Context>().with_extension(ctx_ext);
+        assert!(try_eval_out::<bool>(&expr, Rc::new(ctx)).unwrap());
+    }
+
+    #[test]
+    fn eval_id_not_found() {
+        let expr: Expr = DeserializeContext {
+            tpe: SType::SBoolean,
+            id: 1,
+        }
+        .into();
+        let ctx = force_any_val::<Context>().with_extension(ContextExtension::empty());
+        assert!(try_eval_out::<bool>(&expr, Rc::new(ctx)).is_err());
+    }
+
+    #[test]
+    fn eval_context_extension_wrong_type() {
+        let expr: Expr = DeserializeContext {
+            tpe: SType::SBoolean,
+            id: 1,
+        }
+        .into();
+        // should be byte array
+        let ctx_ext_val: Constant = 1i32.into();
+        let ctx_ext = ContextExtension {
+            values: [(1u8, ctx_ext_val)].iter().cloned().collect(),
+        };
+        let ctx = force_any_val::<Context>().with_extension(ctx_ext);
+        assert!(try_eval_out::<bool>(&expr, Rc::new(ctx)).is_err());
+    }
+
+    #[test]
+    fn evaluated_expr_wrong_type() {
+        let expr: Expr = DeserializeContext {
+            tpe: SType::SBoolean,
+            id: 1,
+        }
+        .into();
+        // should be SBoolean
+        let inner_expr: Expr = GlobalVars::Height.into();
+        let ctx_ext = ContextExtension {
+            values: [(1u8, inner_expr.sigma_serialize_bytes().unwrap().into())]
+                .iter()
+                .cloned()
+                .collect(),
+        };
+        let ctx = force_any_val::<Context>().with_extension(ctx_ext);
+        assert!(try_eval_out::<Value>(&expr, Rc::new(ctx)).is_err());
     }
 }
diff --git a/ergotree-interpreter/src/eval/deserialize_register.rs b/ergotree-interpreter/src/eval/deserialize_register.rs
index 47150f2f6..6050b730b 100644
--- a/ergotree-interpreter/src/eval/deserialize_register.rs
+++ b/ergotree-interpreter/src/eval/deserialize_register.rs
@@ -1,5 +1,9 @@
+use ergotree_ir::mir::constant::TryExtractInto;
 use ergotree_ir::mir::deserialize_register::DeserializeRegister;
+use ergotree_ir::mir::expr::Expr;
 use ergotree_ir::mir::value::Value;
+use ergotree_ir::serialization::SigmaSerializable;
+use ergotree_ir::types::stype::SType;
 
 use crate::eval::env::Env;
 use crate::eval::EvalContext;
@@ -7,7 +11,179 @@ use crate::eval::EvalError;
 use crate::eval::Evaluable;
 
 impl Evaluable for DeserializeRegister {
-    fn eval(&self, _env: &Env, _ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        Err(EvalError::NotImplementedYet("DeserializeRegister"))
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        match ctx
+            .ctx
+            .box_arena
+            .get(&ctx.ctx.self_box)?
+            .get_register(self.reg as i8)
+        {
+            Some(c) => {
+                if c.tpe != SType::SColl(SType::SByte.into()) {
+                    Err(EvalError::UnexpectedExpr(format!(
+                        "DeserializeRegister: expected value to have type SColl(SByte), got {:?}",
+                        c.tpe
+                    )))
+                } else {
+                    let bytes = c.v.try_extract_into::<Vec<u8>>()?;
+                    let expr = Expr::sigma_parse_bytes(bytes.as_slice())?;
+                    if expr.tpe() != self.tpe {
+                        Err(EvalError::UnexpectedExpr(format!("DeserializeRegister: expected deserialized expr to have type {:?}, got {:?}", self.tpe, expr.tpe())))
+                    } else {
+                        expr.eval(env, ctx)
+                    }
+                }
+            }
+            None => match &self.default {
+                Some(default_expr) => {
+                    if default_expr.tpe() != self.tpe {
+                        Err(EvalError::UnexpectedExpr(format!("DeserializeRegister: expected default expr to have type {:?}, got {:?}", self.tpe, default_expr.tpe())))
+                    } else {
+                        default_expr.eval(env, ctx)
+                    }
+                }
+                None => Err(EvalError::NotFound(format!(
+                    "DeserializeRegister: register with id {} is empty",
+                    self.reg
+                ))),
+            },
+        }
+    }
+}
+
+#[allow(clippy::unwrap_used)]
+#[cfg(feature = "arbitrary")]
+#[cfg(test)]
+mod tests {
+
+    use std::rc::Rc;
+
+    use ergotree_ir::mir::bin_op::BinOp;
+    use ergotree_ir::mir::bin_op::RelationOp;
+    use ergotree_ir::mir::constant::Constant;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::global_vars::GlobalVars;
+    use ergotree_ir::serialization::SigmaSerializable;
+    use ergotree_ir::types::stype::SType;
+    use sigma_test_util::force_any_val;
+
+    use crate::eval::context::ir_ergo_box_dummy::IrErgoBoxDummy;
+    use crate::eval::context::ir_ergo_box_dummy::IrErgoBoxDummyArena;
+    use crate::eval::context::Context;
+    use crate::eval::tests::try_eval_out;
+
+    use super::*;
+
+    fn make_ctx_with_self_box(self_box: IrErgoBoxDummy) -> Context {
+        let ctx = force_any_val::<Context>();
+        let mut m = std::collections::HashMap::new();
+        let self_box_id = self_box.id.clone();
+        m.insert(self_box_id.clone(), self_box);
+        Context {
+            height: 0u32,
+            self_box: self_box_id,
+            box_arena: Rc::new(IrErgoBoxDummyArena(m)),
+            ..ctx
+        }
+    }
+
+    #[test]
+    fn eval() {
+        // SInt
+        let inner_expr: Expr = BinOp {
+            kind: RelationOp::NEq.into(),
+            left: Box::new(GlobalVars::Height.into()),
+            right: Box::new(1i32.into()),
+        }
+        .into();
+        let reg_value: Constant = inner_expr.sigma_serialize_bytes().unwrap().into();
+        let b = IrErgoBoxDummy {
+            additional_registers: vec![reg_value],
+            ..force_any_val::<IrErgoBoxDummy>()
+        };
+        // expected SBoolean
+        let expr: Expr = DeserializeRegister {
+            reg: 4,
+            tpe: SType::SBoolean,
+            default: None,
+        }
+        .into();
+        let ctx = make_ctx_with_self_box(b);
+        assert!(try_eval_out::<bool>(&expr, Rc::new(ctx)).unwrap());
+    }
+
+    #[test]
+    fn eval_reg_is_empty() {
+        let b = IrErgoBoxDummy {
+            additional_registers: vec![],
+            ..force_any_val::<IrErgoBoxDummy>()
+        };
+        // no default provided
+        let expr: Expr = DeserializeRegister {
+            reg: 5,
+            tpe: SType::SBoolean,
+            default: None,
+        }
+        .into();
+        let ctx = make_ctx_with_self_box(b.clone());
+        assert!(try_eval_out::<Value>(&expr, Rc::new(ctx)).is_err());
+
+        // default with wrong type provided
+        let expr: Expr = DeserializeRegister {
+            reg: 5,
+            tpe: SType::SInt,
+            default: Some(Box::new(true.into())),
+        }
+        .into();
+        let ctx = make_ctx_with_self_box(b.clone());
+        assert!(try_eval_out::<i32>(&expr, Rc::new(ctx)).is_err());
+
+        // default provided
+        let expr: Expr = DeserializeRegister {
+            reg: 5,
+            tpe: SType::SInt,
+            default: Some(Box::new(1i32.into())),
+        }
+        .into();
+        let ctx = make_ctx_with_self_box(b);
+        assert_eq!(try_eval_out::<i32>(&expr, Rc::new(ctx)).unwrap(), 1i32);
+    }
+
+    #[test]
+    fn eval_reg_wrong_type() {
+        // SInt, expected SColl(SByte)
+        let reg_value: Constant = 1i32.into();
+        let b = IrErgoBoxDummy {
+            additional_registers: vec![reg_value],
+            ..force_any_val::<IrErgoBoxDummy>()
+        };
+        let expr: Expr = DeserializeRegister {
+            reg: 4,
+            tpe: SType::SBoolean,
+            default: None,
+        }
+        .into();
+        let ctx = make_ctx_with_self_box(b);
+        assert!(try_eval_out::<Value>(&expr, Rc::new(ctx)).is_err());
+    }
+
+    #[test]
+    fn evaluated_expr_wrong_type() {
+        // SInt
+        let inner_expr: Expr = 1i32.into();
+        let reg_value: Constant = inner_expr.sigma_serialize_bytes().unwrap().into();
+        let b = IrErgoBoxDummy {
+            additional_registers: vec![reg_value],
+            ..force_any_val::<IrErgoBoxDummy>()
+        };
+        // expected SBoolean
+        let expr: Expr = DeserializeRegister {
+            reg: 4,
+            tpe: SType::SBoolean,
+            default: None,
+        }
+        .into();
+        let ctx = make_ctx_with_self_box(b);
+        assert!(try_eval_out::<bool>(&expr, Rc::new(ctx)).is_err());
     }
 }
diff --git a/ergotree-ir/src/mir/deserialize_register.rs b/ergotree-ir/src/mir/deserialize_register.rs
index d4daab506..a751d4286 100644
--- a/ergotree-ir/src/mir/deserialize_register.rs
+++ b/ergotree-ir/src/mir/deserialize_register.rs
@@ -10,15 +10,14 @@ use crate::serialization::SigmaSerializable;
 use crate::serialization::SigmaSerializeResult;
 use crate::types::stype::SType;
 
-/// Extract register of SELF box as `Coll[Byte]`, deserialize it into
-/// Value and inline into executing script.
+/// Extract register of SELF box as `Coll[Byte]`, deserialize and execute Expr out of it
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct DeserializeRegister {
-    /// Register number
+    /// Register number (0 .. =9 for R0-R9 registers)
     pub reg: u8,
-    /// Type of value in register
+    /// Type of expression serialized in register
     pub tpe: SType,
-    /// Default value
+    /// Default value (expression that would be executed if register is empty)
     pub default: Option<Box<Expr>>,
 }
 impl DeserializeRegister {


Test patch: