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

Issue:
ExtractRegisterAs (box.getReg) eval and serialization



Discussion / hints:

Fix patch:
diff --git a/ergo-lib/CHANGELOG.md b/ergo-lib/CHANGELOG.md
index 50581f376..3acd9f8b3 100644
--- a/ergo-lib/CHANGELOG.md
+++ b/ergo-lib/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ### Added 
 
+- Interpreter: Box.Rx properties (get register value), OptionGet [#163](https://github.com/ergoplatform/sigma-rust/pull/163);
 - Interpreter: added global vars (`INPUTS`, `OUTPUTS`, `SELF`, `HEIGHT`), `Context` properties (`dataInputs`) [#155](https://github.com/ergoplatform/sigma-rust/pull/155);
 - Explorer API v1 format parsing for box.additionalRegisters [#161](https://github.com/ergoplatform/sigma-rust/pull/161);
 
diff --git a/ergo-lib/src/ast.rs b/ergo-lib/src/ast.rs
index 1de30a74d..c79fab4fa 100644
--- a/ergo-lib/src/ast.rs
+++ b/ergo-lib/src/ast.rs
@@ -1,11 +1,12 @@
 //! AST for ErgoTree
 
-pub(crate) mod box_methods;
 pub(crate) mod coll_methods;
 pub(crate) mod expr;
+pub(crate) mod extract_reg_as;
 pub(crate) mod global_vars;
 pub(crate) mod method_call;
 pub(crate) mod ops;
+pub(crate) mod option_get;
 pub(crate) mod predef_func;
 pub(crate) mod property_call;
 
diff --git a/ergo-lib/src/ast/box_methods.rs b/ergo-lib/src/ast/box_methods.rs
deleted file mode 100644
index 3ff9e99e6..000000000
--- a/ergo-lib/src/ast/box_methods.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-use crate::serialization::op_code::OpCode;
-
-use super::expr::Expr;
-
-#[derive(PartialEq, Eq, Debug, Clone)]
-/// newtype for box register id
-pub struct RegisterId(u8); // should be a sum of NonMandatoryRegisterId and MandatoryRegisterId
-
-#[derive(PartialEq, Eq, Debug, Clone)]
-/// Methods for Box type instance
-pub enum BoxM {
-    /// Box.RX methods
-    ExtractRegisterAs {
-        /// Box
-        input: Box<Expr>,
-        /// Register id to extract value from
-        register_id: RegisterId,
-    },
-}
-
-impl BoxM {
-    /// Code (serialization)
-    pub fn op_code(&self) -> OpCode {
-        todo!()
-    }
-}
diff --git a/ergo-lib/src/ast/constant.rs b/ergo-lib/src/ast/constant.rs
index a02616e66..d7c644737 100644
--- a/ergo-lib/src/ast/constant.rs
+++ b/ergo-lib/src/ast/constant.rs
@@ -1,5 +1,6 @@
 //! Constant(Literal) IR node
 
+use crate::chain::ergo_box::BoxValue;
 use crate::chain::ergo_box::ErgoBox;
 use crate::chain::{Base16DecodedBytes, Base16EncodedBytes};
 use crate::types::stype::LiftIntoSType;
@@ -130,13 +131,19 @@ impl From<ErgoBox> for Constant {
     }
 }
 
+impl From<BoxValue> for Constant {
+    fn from(v: BoxValue) -> Self {
+        v.as_i64().into()
+    }
+}
+
 impl From<Vec<u8>> for Constant {
     fn from(v: Vec<u8>) -> Self {
         Constant {
             tpe: SType::SColl(Box::new(SType::SByte)),
-            v: Value::Coll(Coll::Primitive(CollPrim::CollByte(
+            v: Value::Coll(Box::new(Coll::Primitive(CollPrim::CollByte(
                 v.into_iter().map(|b| b as i8).collect(),
-            ))),
+            )))),
         }
     }
 }
@@ -145,7 +152,7 @@ impl From<Vec<i8>> for Constant {
     fn from(v: Vec<i8>) -> Constant {
         Constant {
             tpe: SType::SColl(Box::new(SType::SByte)),
-            v: Value::Coll(Coll::Primitive(CollPrim::CollByte(v))),
+            v: Value::Coll(Box::new(Coll::Primitive(CollPrim::CollByte(v)))),
         }
     }
 }
@@ -192,7 +199,14 @@ impl<T: TryExtractFrom<Value>> TryExtractFrom<Constant> for T {
 impl TryExtractFrom<Constant> for Vec<i8> {
     fn try_extract_from(c: Constant) -> Result<Self, TryExtractFromError> {
         match c.v {
-            Value::Coll(Coll::Primitive(CollPrim::CollByte(bs))) => Ok(bs),
+            Value::Coll(v) => match *v {
+                Coll::Primitive(CollPrim::CollByte(bs)) => Ok(bs),
+                _ => Err(TryExtractFromError(format!(
+                    "expected {:?}, found {:?}",
+                    std::any::type_name::<Self>(),
+                    v
+                ))),
+            },
             _ => Err(TryExtractFromError(format!(
                 "expected {:?}, found {:?}",
                 std::any::type_name::<Self>(),
diff --git a/ergo-lib/src/ast/expr.rs b/ergo-lib/src/ast/expr.rs
index a73912c36..5faa62327 100644
--- a/ergo-lib/src/ast/expr.rs
+++ b/ergo-lib/src/ast/expr.rs
@@ -3,13 +3,14 @@ use core::fmt;
 use crate::serialization::op_code::OpCode;
 use crate::types::stype::SType;
 
-use super::box_methods::BoxM;
 use super::coll_methods::CollM;
 use super::constant::Constant;
 use super::constant::ConstantPlaceholder;
+use super::extract_reg_as::ExtractRegisterAs;
 use super::global_vars::GlobalVars;
 use super::method_call::MethodCall;
 use super::ops;
+use super::option_get::OptionGet;
 use super::predef_func::PredefFunc;
 use super::property_call::PropertyCall;
 
@@ -27,8 +28,6 @@ pub enum Expr {
     PredefFunc(PredefFunc),
     /// Collection type methods
     CollM(CollM),
-    /// Box methods
-    BoxM(BoxM),
     Context,
     // Global(Global),
     /// Predefined global variables
@@ -39,6 +38,10 @@ pub enum Expr {
     ProperyCall(PropertyCall),
     /// Binary operation
     BinOp(ops::BinOp, Box<Expr>, Box<Expr>),
+    /// Option get method
+    OptionGet(Box<OptionGet>),
+    /// Extract register's value (box.RX properties)
+    ExtractRegisterAs(Box<ExtractRegisterAs>),
 }
 
 impl Expr {
@@ -51,6 +54,8 @@ impl Expr {
             Expr::MethodCall(v) => v.op_code(),
             Expr::ProperyCall(v) => v.op_code(),
             Expr::Context => OpCode::CONTEXT,
+            Expr::OptionGet(v) => v.op_code(),
+            Expr::ExtractRegisterAs(v) => v.op_code(),
             _ => todo!("{0:?}", self),
         }
     }
diff --git a/ergo-lib/src/ast/extract_reg_as.rs b/ergo-lib/src/ast/extract_reg_as.rs
new file mode 100644
index 000000000..afe9913a0
--- /dev/null
+++ b/ergo-lib/src/ast/extract_reg_as.rs
@@ -0,0 +1,105 @@
+use crate::chain::ergo_box::ErgoBox;
+use crate::chain::ergo_box::RegisterId;
+use crate::eval::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+use crate::serialization::op_code::OpCode;
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::SerializationError;
+use crate::serialization::SigmaSerializable;
+use crate::types::stype::SType;
+
+use super::constant::TryExtractInto;
+use super::expr::Expr;
+use super::value::Value;
+
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ExtractRegisterAs {
+    /// Box
+    pub input: Box<Expr>,
+    /// Register id to extract value from
+    pub register_id: RegisterId,
+    /// Type
+    pub tpe: SType,
+}
+
+impl ExtractRegisterAs {
+    pub fn op_code(&self) -> OpCode {
+        OpCode::EXTRACT_REGISTER_AS
+    }
+}
+
+impl Evaluable for ExtractRegisterAs {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        Ok(Value::Opt(Box::new(
+            self.input
+                .eval(env, ctx)?
+                .try_extract_into::<ErgoBox>()?
+                .get_register(self.register_id)
+                .map(|c| c.v),
+        )))
+    }
+}
+
+impl SigmaSerializable for ExtractRegisterAs {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
+        self.input.sigma_serialize(w)?;
+        self.register_id.sigma_serialize(w)?;
+        self.tpe.sigma_serialize(w)
+    }
+
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+        let input = Expr::sigma_parse(r)?.into();
+        let register_id = RegisterId::sigma_parse(r)?;
+        let tpe = SType::sigma_parse(r)?;
+        Ok(ExtractRegisterAs {
+            input,
+            register_id,
+            tpe,
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::rc::Rc;
+
+    use crate::ast::global_vars::GlobalVars;
+    use crate::ast::option_get::OptionGet;
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use crate::test_util::force_any_val;
+
+    use super::*;
+
+    #[test]
+    fn eval_box_get_reg() {
+        let get_reg_expr: Expr = Box::new(ExtractRegisterAs {
+            input: Box::new(GlobalVars::SelfBox.into()),
+            register_id: RegisterId::R0,
+            tpe: SType::SOption(SType::SLong.into()),
+        })
+        .into();
+        let option_get_expr: Expr = Box::new(OptionGet {
+            input: get_reg_expr,
+        })
+        .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        let v = eval_out::<i64>(&option_get_expr, ctx.clone());
+        assert_eq!(v, ctx.self_box.value.as_i64());
+    }
+
+    #[test]
+    fn ser_roundtrip() {
+        let e: Expr = Box::new(ExtractRegisterAs {
+            input: Box::new(GlobalVars::SelfBox.into()),
+            register_id: RegisterId::R0,
+            tpe: SType::SOption(SType::SLong.into()),
+        })
+        .into();
+        assert_eq![sigma_serialize_roundtrip(&e), e];
+    }
+}
diff --git a/ergo-lib/src/ast/option_get.rs b/ergo-lib/src/ast/option_get.rs
new file mode 100644
index 000000000..3066307e9
--- /dev/null
+++ b/ergo-lib/src/ast/option_get.rs
@@ -0,0 +1,92 @@
+use super::expr::Expr;
+use crate::ast::value::Value;
+use crate::eval::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+use crate::serialization::op_code::OpCode;
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::SerializationError;
+use crate::serialization::SigmaSerializable;
+
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct OptionGet {
+    pub input: Expr,
+}
+
+impl OptionGet {
+    pub fn op_code(&self) -> OpCode {
+        OpCode::OPTION_GET
+    }
+}
+
+impl Evaluable for OptionGet {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let v = self.input.eval(env, ctx)?;
+        match v {
+            Value::Opt(opt_v) => {
+                opt_v.ok_or_else(|| EvalError::NotFound("calling Option.get on None".to_string()))
+            }
+            _ => Err(EvalError::UnexpectedExpr(format!(
+                "Don't know how to eval OptM: {0:?}",
+                self
+            ))),
+        }
+    }
+}
+
+impl SigmaSerializable for OptionGet {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
+        self.input.sigma_serialize(w)
+    }
+
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+        Ok(OptionGet {
+            input: Expr::sigma_parse(r)?,
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::rc::Rc;
+
+    use crate::ast::expr::Expr;
+    use crate::ast::extract_reg_as::ExtractRegisterAs;
+    use crate::ast::global_vars::GlobalVars;
+    use crate::chain::ergo_box::RegisterId;
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use crate::test_util::force_any_val;
+    use crate::types::stype::SType;
+
+    use super::OptionGet;
+
+    #[test]
+    fn eval_get() {
+        let get_reg_expr: Expr = Box::new(ExtractRegisterAs {
+            input: Box::new(GlobalVars::SelfBox.into()),
+            register_id: RegisterId::R0,
+            tpe: SType::SOption(SType::SLong.into()),
+        })
+        .into();
+        let option_get_expr: Expr = Box::new(OptionGet {
+            input: get_reg_expr,
+        })
+        .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        let v = eval_out::<i64>(&option_get_expr, ctx.clone());
+        assert_eq!(v, ctx.self_box.value.as_i64());
+    }
+
+    #[test]
+    fn ser_roundtrip() {
+        let e: Expr = Box::new(OptionGet {
+            input: Expr::Context,
+        })
+        .into();
+        assert_eq![sigma_serialize_roundtrip(&e), e];
+    }
+}
diff --git a/ergo-lib/src/ast/value.rs b/ergo-lib/src/ast/value.rs
index 72f429085..8c8fa95aa 100644
--- a/ergo-lib/src/ast/value.rs
+++ b/ergo-lib/src/ast/value.rs
@@ -4,7 +4,6 @@ use std::convert::TryFrom;
 use std::rc::Rc;
 
 use crate::chain::ergo_box::ErgoBox;
-// use crate::eval::context::Context;
 use crate::eval::context::Context;
 use crate::sigma_protocol::dlog_group::EcPoint;
 use crate::sigma_protocol::sigma_boolean::ProveDlog;
@@ -81,11 +80,13 @@ pub enum Value {
     /// AVL tree
     AvlTree,
     /// Collection of values of the same type
-    Coll(Coll),
+    Coll(Box<Coll>),
     /// Tuple (arbitrary type values)
     Tup(Vec<Value>),
     /// Transaction(and blockchain) context info
     Context(Rc<Context>),
+    /// Optional value
+    Opt(Box<Option<Value>>),
 }
 
 impl Value {
@@ -154,10 +155,10 @@ impl StoredNonPrimitive for ErgoBox {}
 
 impl<T: LiftIntoSType + StoredNonPrimitive + Into<Value>> Into<Value> for Vec<T> {
     fn into(self) -> Value {
-        Value::Coll(Coll::NonPrimitive {
+        Value::Coll(Box::new(Coll::NonPrimitive {
             elem_tpe: T::stype(),
             v: self.into_iter().map(|i| i.into()).collect(),
-        })
+        }))
     }
 }
 
@@ -248,9 +249,16 @@ impl TryExtractFrom<Value> for ErgoBox {
 impl<T: TryExtractFrom<Value> + StoredNonPrimitive> TryExtractFrom<Value> for Vec<T> {
     fn try_extract_from(c: Value) -> Result<Self, TryExtractFromError> {
         match c {
-            Value::Coll(Coll::NonPrimitive { elem_tpe: _, v }) => {
-                v.into_iter().map(T::try_extract_from).collect()
-            }
+            Value::Coll(coll) => match *coll {
+                Coll::NonPrimitive { elem_tpe: _, v } => {
+                    v.into_iter().map(T::try_extract_from).collect()
+                }
+                _ => Err(TryExtractFromError(format!(
+                    "expected {:?}, found {:?}",
+                    std::any::type_name::<Self>(),
+                    coll
+                ))),
+            },
             _ => Err(TryExtractFromError(format!(
                 "expected {:?}, found {:?}",
                 std::any::type_name::<Self>(),
diff --git a/ergo-lib/src/chain/ergo_box.rs b/ergo-lib/src/chain/ergo_box.rs
index 66038da09..e03c8c16d 100644
--- a/ergo-lib/src/chain/ergo_box.rs
+++ b/ergo-lib/src/chain/ergo_box.rs
@@ -18,6 +18,7 @@ use super::{
     transaction::TxId,
 };
 
+use crate::ast::constant::Constant;
 use crate::{
     ergo_tree::ErgoTree,
     serialization::{
@@ -149,6 +150,17 @@ impl ErgoBox {
         let bytes = self.sigma_serialize_bytes();
         BoxId(blake2b256_hash(&bytes))
     }
+
+    /// Get register value
+    pub fn get_register(&self, id: RegisterId) -> Option<Constant> {
+        match id {
+            RegisterId::MandatoryRegisterId(id) => match id {
+                MandatoryRegisterId::R0 => Some(self.value.clone().into()),
+                _ => todo!(),
+            },
+            RegisterId::NonMandatoryRegisterId(id) => self.additional_registers.get(id).cloned(),
+        }
+    }
 }
 
 /// Assets that ErgoBox holds
diff --git a/ergo-lib/src/chain/ergo_box/register.rs b/ergo-lib/src/chain/ergo_box/register.rs
index 525f003e4..1c17dc531 100644
--- a/ergo-lib/src/chain/ergo_box/register.rs
+++ b/ergo-lib/src/chain/ergo_box/register.rs
@@ -2,12 +2,70 @@
 
 #[cfg(feature = "json")]
 use crate::chain::json::ergo_box::ConstantHolder;
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::SigmaSerializable;
 use crate::{ast::constant::Constant, serialization::SerializationError};
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
+use std::convert::TryInto;
 use std::{collections::HashMap, convert::TryFrom};
 use thiserror::Error;
 
+/// Box register id (0-9)
+#[derive(PartialEq, Eq, Debug, Clone, Copy)]
+pub enum RegisterId {
+    /// Id for mandatory registers (0-3)
+    MandatoryRegisterId(MandatoryRegisterId),
+    /// Id for non-mandotory registers (4-9)
+    NonMandatoryRegisterId(NonMandatoryRegisterId),
+}
+
+impl RegisterId {
+    /// R0 register id (box.value)
+    pub const R0: RegisterId = RegisterId::MandatoryRegisterId(MandatoryRegisterId::R0);
+}
+
+/// Register id out of bounds error (not in 0-9 range)
+#[derive(Error, PartialEq, Eq, Debug, Clone)]
+#[error("register id {0} is out of bounds (0 - 9)")]
+pub struct RegisterIdOutOfBounds(pub i8);
+
+impl TryFrom<i8> for RegisterId {
+    type Error = RegisterIdOutOfBounds;
+
+    fn try_from(value: i8) -> Result<Self, Self::Error> {
+        if value < 0 {
+            return Err(RegisterIdOutOfBounds(value));
+        }
+        let v = value as usize;
+        if v < NonMandatoryRegisterId::START_INDEX {
+            Ok(RegisterId::MandatoryRegisterId(value.try_into()?))
+        } else if v <= NonMandatoryRegisterId::END_INDEX {
+            Ok(RegisterId::NonMandatoryRegisterId(value.try_into()?))
+        } else {
+            Err(RegisterIdOutOfBounds(value))
+        }
+    }
+}
+
+impl SigmaSerializable for RegisterId {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
+        let byte = match self {
+            RegisterId::MandatoryRegisterId(id) => *id as i8,
+            RegisterId::NonMandatoryRegisterId(id) => *id as i8,
+        };
+        w.put_i8(byte)
+    }
+
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+        let reg_id = r.get_i8()?;
+        RegisterId::try_from(reg_id).map_err(|_| {
+            SerializationError::ValueOutOfBounds(format!("Register id out of bounds: {}", reg_id))
+        })
+    }
+}
+
 /// newtype for additional registers R4 - R9
 #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
 #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
@@ -83,7 +141,24 @@ impl TryFrom<String> for NonMandatoryRegisterId {
     }
 }
 
-#[derive(Error, Debug)]
+impl TryFrom<i8> for NonMandatoryRegisterId {
+    type Error = RegisterIdOutOfBounds;
+
+    fn try_from(value: i8) -> Result<Self, Self::Error> {
+        let v_usize = value as usize;
+        if v_usize >= NonMandatoryRegisterId::START_INDEX
+            && v_usize <= NonMandatoryRegisterId::END_INDEX
+        {
+            Ok(NonMandatoryRegisterId::get_by_zero_index(
+                v_usize - NonMandatoryRegisterId::START_INDEX,
+            ))
+        } else {
+            Err(RegisterIdOutOfBounds(value))
+        }
+    }
+}
+
+#[derive(Error, PartialEq, Eq, Debug, Clone)]
 #[error("failed to parse register id")]
 /// Error for failed parsing of the register id from string
 pub struct NonMandatoryRegisterIdParsingError();
@@ -208,6 +283,33 @@ impl From<NonMandatoryRegistersError> for SerializationError {
     }
 }
 
+/// Register ids that every box have (box properties exposed as registers)
+#[derive(PartialEq, Eq, Debug, Clone, Copy)]
+pub enum MandatoryRegisterId {
+    /// Monetary value, in Ergo tokens
+    R0 = 0,
+    /// Guarding script
+    R1 = 1,
+    /// Secondary tokens
+    R2 = 2,
+    /// Reference to transaction and output id where the box was created
+    R3 = 3,
+}
+
+impl TryFrom<i8> for MandatoryRegisterId {
+    type Error = RegisterIdOutOfBounds;
+
+    fn try_from(value: i8) -> Result<Self, Self::Error> {
+        match value {
+            v if v == MandatoryRegisterId::R0 as i8 => Ok(MandatoryRegisterId::R0),
+            v if v == MandatoryRegisterId::R1 as i8 => Ok(MandatoryRegisterId::R1),
+            v if v == MandatoryRegisterId::R2 as i8 => Ok(MandatoryRegisterId::R2),
+            v if v == MandatoryRegisterId::R3 as i8 => Ok(MandatoryRegisterId::R3),
+            _ => Err(RegisterIdOutOfBounds(value)),
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/ergo-lib/src/eval.rs b/ergo-lib/src/eval.rs
index 8675caffd..73c45d029 100644
--- a/ergo-lib/src/eval.rs
+++ b/ergo-lib/src/eval.rs
@@ -4,6 +4,7 @@ use std::rc::Rc;
 use crate::ast::constant::TryExtractFromError;
 use crate::ast::expr::Expr;
 use crate::ast::value::Value;
+use crate::chain::ergo_box::RegisterIdOutOfBounds;
 use crate::sigma_protocol::sigma_boolean::SigmaBoolean;
 
 use cost_accum::CostAccumulator;
@@ -37,16 +38,21 @@ pub enum EvalError {
     /// Only boolean or SigmaBoolean is a valid result expr type
     #[error("Only boolean or SigmaBoolean is a valid result expr type")]
     InvalidResultType,
-    /// Unsupported Expr encountered during the evaluation
-    #[error("Unsupported Expr encountered during the evaluation")]
-    // TODO: store unexpected expr
-    UnexpectedExpr,
+    /// Unexpected Expr encountered during the evaluation
+    #[error("unexpected Expr: {0:?}")]
+    UnexpectedExpr(String),
     /// Error on cost calculation
     #[error("Error on cost calculation: {0:?}")]
     CostError(#[from] CostError),
     /// Unexpected value type
     #[error("Unexpected value type: {0:?}")]
     TryExtractFrom(#[from] TryExtractFromError),
+    /// Not found (missing value, argument, etc.)
+    #[error("Not found: {0}")]
+    NotFound(String),
+    /// Register id out of bounds
+    #[error("{0:?}")]
+    RegisterIdOutOfBounds(#[from] RegisterIdOutOfBounds),
 }
 
 /// Result of expression reduction procedure (see `reduce_to_crypto`).
diff --git a/ergo-lib/src/eval/expr.rs b/ergo-lib/src/eval/expr.rs
index 4400024a4..276fb8b29 100644
--- a/ergo-lib/src/eval/expr.rs
+++ b/ergo-lib/src/eval/expr.rs
@@ -13,7 +13,7 @@ impl Evaluable for Expr {
             Expr::Const(c) => Ok(c.v.clone()),
             Expr::PredefFunc(_) => todo!(),
             Expr::CollM(_) => todo!(),
-            Expr::BoxM(_) => todo!(),
+            Expr::ExtractRegisterAs(v) => v.eval(env, ectx),
             Expr::GlobalVars(v) => v.eval(env, ectx),
             Expr::MethodCall(v) => v.eval(env, ectx),
             Expr::ProperyCall(v) => v.eval(env, ectx),
@@ -29,7 +29,11 @@ impl Evaluable for Expr {
                 // })
             }
             Expr::Context => Ok(Value::Context(ectx.ctx.clone())),
-            _ => Err(EvalError::UnexpectedExpr),
+            Expr::OptionGet(v) => v.eval(env, ectx),
+            _ => Err(EvalError::UnexpectedExpr(format!(
+                "Don't know how to eval Expr: {}",
+                self
+            ))),
         }
     }
 }
diff --git a/ergo-lib/src/eval/global_vars.rs b/ergo-lib/src/eval/global_vars.rs
index cb4edfe23..4e01d7dec 100644
--- a/ergo-lib/src/eval/global_vars.rs
+++ b/ergo-lib/src/eval/global_vars.rs
@@ -12,7 +12,10 @@ impl Evaluable for GlobalVars {
             GlobalVars::Height => Ok(ectx.ctx.height.clone().into()),
             GlobalVars::SelfBox => Ok(ectx.ctx.self_box.clone().into()),
             GlobalVars::Outputs => Ok(ectx.ctx.outputs.clone().into()),
-            _ => Err(EvalError::UnexpectedExpr),
+            _ => Err(EvalError::UnexpectedExpr(format!(
+                "Don't know how to eval GlobalVars: {0:?}",
+                self
+            ))),
         }
     }
 }
diff --git a/ergo-lib/src/eval/method_call.rs b/ergo-lib/src/eval/method_call.rs
index f1f650a89..f3d43e891 100644
--- a/ergo-lib/src/eval/method_call.rs
+++ b/ergo-lib/src/eval/method_call.rs
@@ -17,29 +17,32 @@ impl Evaluable for MethodCall {
 
 #[cfg(test)]
 mod tests {
-    // use std::rc::Rc;
+    use std::rc::Rc;
 
-    // use crate::ast::expr::Expr;
-    // use crate::chain::ergo_box::ErgoBox;
-    // use crate::eval::context::Context;
-    // use crate::eval::tests::eval_out;
-    // use crate::test_util::force_any_val;
-    // use crate::types::scontext;
+    use crate::ast::constant::Constant;
+    use crate::ast::expr::Expr;
+    use crate::ast::global_vars::GlobalVars;
+    use crate::ast::option_get::OptionGet;
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use crate::test_util::force_any_val;
+    use crate::types::sbox;
 
-    // use super::*;
+    use super::*;
 
-    // #[test]
-    // fn eval_context_data_inputs() {
-    //     let mc = MethodCall {
-    //         tpe: scontext::DATA_INPUTS_METHOD.tpe().clone(),
-    //         obj: Box::new(Expr::Context),
-    //         method: scontext::DATA_INPUTS_METHOD.clone(),
-    //         args: vec![],
-    //     };
-    //     let ctx = Rc::new(force_any_val::<Context>());
-    //     assert_eq!(
-    //         eval_out::<Vec<ErgoBox>>(&mc.into(), ctx.clone()),
-    //         ctx.data_inputs
-    //     );
-    // }
+    #[test]
+    fn eval_box_get_reg() {
+        let mc: Expr = MethodCall {
+            obj: Box::new(GlobalVars::SelfBox.into()),
+            method: sbox::GET_REG_METHOD.clone(),
+            args: vec![Constant::from(0i8).into()],
+        }
+        .into();
+        let option_get_expr: Expr = Box::new(OptionGet { input: mc }).into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        assert_eq!(
+            eval_out::<i64>(&option_get_expr, ctx.clone()),
+            ctx.self_box.value.as_i64()
+        );
+    }
 }
diff --git a/ergo-lib/src/serialization/data.rs b/ergo-lib/src/serialization/data.rs
index d5fe841b3..948320b15 100644
--- a/ergo-lib/src/serialization/data.rs
+++ b/ergo-lib/src/serialization/data.rs
@@ -31,7 +31,7 @@ impl DataSerializer {
             Value::CBox(_) => todo!(),
             // Value::TBox(_) => todo!(),
             Value::AvlTree => todo!(),
-            Value::Coll(ct) => match ct {
+            Value::Coll(ct) => match &**ct {
                 Coll::Primitive(CollPrim::CollByte(b)) => {
                     w.put_usize_as_u16(b.len())?;
                     w.write_all(b.clone().as_vec_u8().as_slice())
@@ -43,6 +43,7 @@ impl DataSerializer {
                 }
             },
             Value::Tup(_) => todo!(),
+            Value::Opt(_) => todo!(), // unsupported, see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/659
             Value::Context(_) => todo!(), // TODO: throw error? it should not be here
         }
     }
@@ -65,9 +66,9 @@ impl DataSerializer {
                 let len = r.get_u16()? as usize;
                 let mut buf = vec![0u8; len];
                 r.read_exact(&mut buf)?;
-                Value::Coll(Coll::Primitive(CollPrim::CollByte(
+                Value::Coll(Box::new(Coll::Primitive(CollPrim::CollByte(
                     buf.into_iter().map(|v| v as i8).collect(),
-                )))
+                ))))
             }
             SColl(elem_type) => {
                 let len = r.get_u16()? as usize;
@@ -75,10 +76,10 @@ impl DataSerializer {
                 for _ in 0..len {
                     elems.push(DataSerializer::sigma_parse(elem_type, r)?);
                 }
-                Value::Coll(Coll::NonPrimitive {
+                Value::Coll(Box::new(Coll::NonPrimitive {
                     elem_tpe: *elem_type.clone(),
                     v: elems,
-                })
+                }))
             }
             STup(types) => {
                 let mut items = Vec::new();
diff --git a/ergo-lib/src/serialization/expr.rs b/ergo-lib/src/serialization/expr.rs
index bb4eb02da..cda205243 100644
--- a/ergo-lib/src/serialization/expr.rs
+++ b/ergo-lib/src/serialization/expr.rs
@@ -3,8 +3,10 @@ use crate::ast::coll_methods::CollM;
 use crate::ast::constant::Constant;
 use crate::ast::constant::ConstantPlaceholder;
 use crate::ast::expr::Expr;
+use crate::ast::extract_reg_as::ExtractRegisterAs;
 use crate::ast::global_vars::GlobalVars;
 use crate::ast::method_call::MethodCall;
+use crate::ast::option_get::OptionGet;
 use crate::ast::property_call::PropertyCall;
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, SerializationError, SigmaSerializable,
@@ -35,6 +37,8 @@ impl SigmaSerializable for Expr {
                     Expr::MethodCall(mc) => mc.sigma_serialize(w),
                     Expr::ProperyCall(pc) => pc.sigma_serialize(w),
                     Expr::Context => Ok(()),
+                    Expr::OptionGet(v) => v.sigma_serialize(w),
+                    Expr::ExtractRegisterAs(v) => v.sigma_serialize(w),
                     _ => panic!(format!("don't know how to serialize {:?}", expr)),
                 }
             }
@@ -75,6 +79,10 @@ impl SigmaSerializable for Expr {
                 OpCode::PROPERTY_CALL => Ok(Expr::ProperyCall(PropertyCall::sigma_parse(r)?)),
                 OpCode::METHOD_CALL => Ok(Expr::MethodCall(MethodCall::sigma_parse(r)?)),
                 OpCode::CONTEXT => Ok(Expr::Context),
+                OpCode::OPTION_GET => Ok(Box::new(OptionGet::sigma_parse(r)?).into()),
+                OpCode::EXTRACT_REGISTER_AS => {
+                    Ok(Box::new(ExtractRegisterAs::sigma_parse(r)?).into())
+                }
                 o => Err(SerializationError::NotImplementedOpCode(o.value())),
             }
         }
diff --git a/ergo-lib/src/serialization/op_code.rs b/ergo-lib/src/serialization/op_code.rs
index c610c0720..b51da013f 100644
--- a/ergo-lib/src/serialization/op_code.rs
+++ b/ergo-lib/src/serialization/op_code.rs
@@ -27,11 +27,16 @@ impl OpCode {
     pub const SELF_BOX: OpCode = Self::new_op_code(55);
 
     pub const FOLD: OpCode = Self::new_op_code(64);
+
+    pub const EXTRACT_REGISTER_AS: OpCode = Self::new_op_code(86);
+
     pub const PROVE_DLOG: OpCode = Self::new_op_code(93);
 
     pub const PROPERTY_CALL: OpCode = Self::new_op_code(107);
     pub const METHOD_CALL: OpCode = Self::new_op_code(108);
 
+    pub const OPTION_GET: OpCode = Self::new_op_code(116);
+
     pub const CONTEXT: OpCode = Self::new_op_code(142);
 
     const fn new_op_code(shift: u8) -> OpCode {
diff --git a/ergo-lib/src/serialization/types.rs b/ergo-lib/src/serialization/types.rs
index 0c9bcf44b..ba8d9ff07 100644
--- a/ergo-lib/src/serialization/types.rs
+++ b/ergo-lib/src/serialization/types.rs
@@ -34,6 +34,13 @@ impl TypeCode {
     pub const COLLECTION_TYPE_CODE: TypeCode =
         Self::new((TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::COLLECTION_TYPE_CONSTR_ID);
 
+    pub const OPTION_TYPE_CONSTR_ID: u8 = 3;
+    pub const OPTION_COLLECTION_TYPE_CONSTR_ID: u8 = 4;
+    pub const OPTION_TYPE_CODE: TypeCode =
+        Self::new((TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::OPTION_TYPE_CONSTR_ID);
+    pub const OPTION_COLLECTION_TYPE_CODE: TypeCode =
+        Self::new((TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::OPTION_COLLECTION_TYPE_CONSTR_ID);
+
     const fn new(c: u8) -> TypeCode {
         TypeCode(c)
     }
@@ -120,7 +127,20 @@ impl SigmaSerializable for SType {
 
             SType::SBox => todo!(),
             SType::SAvlTree => todo!(),
-            SType::SOption(_) => todo!(),
+            SType::SOption(elem_type) if is_stype_embeddable(elem_type) => {
+                let code = TypeCode::OPTION_TYPE_CODE + elem_type.type_code();
+                code.sigma_serialize(w)
+            }
+            SType::SOption(elem_type) => match &**elem_type {
+                SType::SColl(elem_type) if is_stype_embeddable(elem_type.as_ref()) => {
+                    let code = TypeCode::OPTION_COLLECTION_TYPE_CODE + elem_type.type_code();
+                    code.sigma_serialize(w)
+                }
+                _ => {
+                    TypeCode::OPTION_TYPE_CODE.sigma_serialize(w)?;
+                    elem_type.sigma_serialize(w)
+                }
+            },
             SType::SColl(elem_type) if is_stype_embeddable(elem_type) => {
                 let code = TypeCode::COLLECTION_TYPE_CODE + elem_type.type_code();
                 code.sigma_serialize(w)
@@ -129,6 +149,7 @@ impl SigmaSerializable for SType {
             SType::STup(_) => todo!(),
             SType::SFunc(_) => todo!(),
             SType::SContext(_) => todo!(),
+            SType::STypeVar(_) => todo!(),
         }
     }
 
@@ -145,6 +166,16 @@ impl SigmaSerializable for SType {
                 let t_elem = get_embeddable_type(prim_id)?;
                 SType::SColl(Box::new(t_elem))
             }
+            // Option[_]
+            3 => {
+                let t_elem = get_embeddable_type(prim_id)?;
+                SType::SOption(Box::new(t_elem))
+            }
+            // Option[Coll[_]]
+            4 => {
+                let t_elem = get_embeddable_type(prim_id)?;
+                SType::SOption(SType::SColl(t_elem.into()).into())
+            }
             _ => {
                 return Err(SerializationError::NotImplementedYet(
                     "parsing type is not yet implemented".to_string(),
diff --git a/ergo-lib/src/types.rs b/ergo-lib/src/types.rs
index 977e880f5..7390ace22 100644
--- a/ergo-lib/src/types.rs
+++ b/ergo-lib/src/types.rs
@@ -2,6 +2,7 @@
 
 pub mod stype;
 
+pub(crate) mod sbox;
 pub(crate) mod scontext;
 pub(crate) mod sfunc;
 pub(crate) mod smethod;
diff --git a/ergo-lib/src/types/sbox.rs b/ergo-lib/src/types/sbox.rs
new file mode 100644
index 000000000..a7e95f139
--- /dev/null
+++ b/ergo-lib/src/types/sbox.rs
@@ -0,0 +1,60 @@
+use std::convert::TryInto;
+
+use crate::ast::constant::TryExtractInto;
+use crate::ast::value::Value;
+use crate::chain::ergo_box::ErgoBox;
+use crate::eval::EvalError;
+
+use super::sfunc::SFunc;
+use super::smethod::EvalFn;
+use super::smethod::MethodId;
+use super::smethod::SMethod;
+use super::smethod::SMethodDesc;
+use super::stype::SType;
+use super::stype_companion::STypeCompanion;
+use super::stype_companion::STypeCompanionHead;
+use super::stype_companion::TypeId;
+use super::stype_param::STypeVar;
+use lazy_static::lazy_static;
+
+static S_BOX_TYPE_COMPANION_HEAD: STypeCompanionHead = STypeCompanionHead {
+    type_id: TypeId(99),
+    type_name: "Box",
+};
+
+static GET_REG_EVAL_FN: EvalFn = |obj, args| {
+    Ok(Value::Opt(Box::new(
+        obj.try_extract_into::<ErgoBox>()?
+            .get_register(
+                args.get(0)
+                    .cloned()
+                    .ok_or_else(|| EvalError::NotFound("register index is missing".to_string()))?
+                    .try_extract_into::<i8>()?
+                    .try_into()?,
+            )
+            .map(|c| c.v),
+    )))
+};
+
+lazy_static! {
+    static ref GET_REG_METHOD_DESC: SMethodDesc = SMethodDesc {
+        method_id: MethodId(7),
+        name: "getReg",
+        tpe: SType::SFunc(Box::new(SFunc {
+            t_dom: vec![SType::SBox, SType::SByte],
+            t_range: SType::SOption(Box::new(SType::STypeVar(STypeVar::T))),
+            tpe_params: vec![],
+        })),
+        eval_fn: GET_REG_EVAL_FN,
+    };
+}
+
+lazy_static! {
+    pub static ref S_BOX_TYPE_COMPANION: STypeCompanion =
+        STypeCompanion::new(&S_BOX_TYPE_COMPANION_HEAD, vec![&GET_REG_METHOD_DESC]);
+}
+
+lazy_static! {
+    pub static ref GET_REG_METHOD: SMethod =
+        SMethod::new(&S_BOX_TYPE_COMPANION, &GET_REG_METHOD_DESC,);
+}
diff --git a/ergo-lib/src/types/scontext.rs b/ergo-lib/src/types/scontext.rs
index 040f1c00b..a6512e59d 100644
--- a/ergo-lib/src/types/scontext.rs
+++ b/ergo-lib/src/types/scontext.rs
@@ -25,7 +25,7 @@ static S_CONTEXT_TYPE_COMPANION_HEAD: STypeCompanionHead = STypeCompanionHead {
 };
 
 static DATA_INPUTS_EVAL_FN: EvalFn = |obj, _args| {
-    Ok(Value::Coll(Coll::NonPrimitive {
+    Ok(Value::Coll(Box::new(Coll::NonPrimitive {
         v: obj
             .try_extract_into::<Rc<Context>>()?
             .data_inputs
@@ -34,11 +34,11 @@ static DATA_INPUTS_EVAL_FN: EvalFn = |obj, _args| {
             .map(|b| Value::CBox(Box::new(b)))
             .collect(),
         elem_tpe: SType::SBox,
-    }))
+    })))
 };
 
 lazy_static! {
-    static ref DATA_INPUTS_PROPERTY_RAW: SMethodDesc = SMethodDesc {
+    static ref DATA_INPUTS_PROPERTY_METHOD_DESC: SMethodDesc = SMethodDesc {
         method_id: MethodId(1),
         name: "dataInputs",
         tpe: SType::SFunc(Box::new(SFunc {
@@ -53,11 +53,11 @@ lazy_static! {
 lazy_static! {
     pub static ref S_CONTEXT_TYPE_COMPANION: STypeCompanion = STypeCompanion::new(
         &S_CONTEXT_TYPE_COMPANION_HEAD,
-        vec![&DATA_INPUTS_PROPERTY_RAW]
+        vec![&DATA_INPUTS_PROPERTY_METHOD_DESC]
     );
 }
 
 lazy_static! {
     pub static ref DATA_INPUTS_PROPERTY: SMethod =
-        SMethod::new(&S_CONTEXT_TYPE_COMPANION, &DATA_INPUTS_PROPERTY_RAW,);
+        SMethod::new(&S_CONTEXT_TYPE_COMPANION, &DATA_INPUTS_PROPERTY_METHOD_DESC,);
 }
diff --git a/ergo-lib/src/types/stype.rs b/ergo-lib/src/types/stype.rs
index f8f2ae4ce..eb761daff 100644
--- a/ergo-lib/src/types/stype.rs
+++ b/ergo-lib/src/types/stype.rs
@@ -11,10 +11,13 @@ use crate::sigma_protocol::sigma_boolean::SigmaProp;
 use super::scontext::SContext;
 use super::sfunc::SFunc;
 use super::stype_companion::STypeCompanion;
+use super::stype_param::STypeVar;
 
 /// Every type descriptor is a tree represented by nodes in SType hierarchy.
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub enum SType {
+    /// Type variable (generic)
+    STypeVar(STypeVar),
     /// TBD
     SAny,
     /// Boolean
@@ -69,6 +72,7 @@ impl SType {
             SType::STup(_) => todo!(),
             SType::SFunc(_) => todo!(),
             SType::SContext(_) => todo!(),
+            SType::STypeVar(_) => todo!(),
         }
     }
 
diff --git a/ergo-lib/src/types/stype_param.rs b/ergo-lib/src/types/stype_param.rs
index a32477cef..52197e48b 100644
--- a/ergo-lib/src/types/stype_param.rs
+++ b/ergo-lib/src/types/stype_param.rs
@@ -2,7 +2,11 @@ use super::stype::SType;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct STypeVar {
-    name: String,
+    pub name: &'static str,
+}
+
+impl STypeVar {
+    pub const T: STypeVar = STypeVar { name: "T" };
 }
 
 #[derive(PartialEq, Eq, Debug, Clone)]


Test patch: