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

Issue:
Box.getReg
Add v6.0 getReg support:
```
{
   val x = SELF
   x.getReg[Long](0).get == SELF.value &&
   x.getReg[Coll[(Coll[Byte], Long)]](2).get == SELF.tokens &&
   x.getReg[Int](9).isEmpty
}
```

sigma-rust already has support for non-constant register indices in getReg, but does not allow all numeric types as input (only Byte). Add support for numeric indices in  getReg which should only be enabled on v6.0 activation

Also fix getReg/ExtractRegister not doing type-checking. For example the following should fail if R4 is a type other than Int, but it does not currently:

```
{
    SELF.R4[Int].isDefined
}
```


Discussion / hints:

Fix patch:
diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs
index 3bc851326..80eb1d597 100644
--- a/ergotree-interpreter/src/eval.rs
+++ b/ergotree-interpreter/src/eval.rs
@@ -197,6 +197,7 @@ pub(crate) trait Evaluable {
 }
 
 type EvalFn = for<'ctx> fn(
+    mc: &SMethod,
     env: &mut Env<'ctx>,
     ctx: &Context<'ctx>,
     Value<'ctx>,
@@ -402,6 +403,22 @@ pub(crate) mod tests {
         })
     }
 
+    // Evaluate with activated version (set block version to version + 1)
+    pub fn try_eval_out_with_version<'ctx, T: TryExtractFrom<Value<'static>> + 'static>(
+        expr: &Expr,
+        ctx: &'ctx Context<'ctx>,
+        version: u8,
+    ) -> Result<T, EvalError> {
+        let mut ctx = ctx.clone();
+        ctx.pre_header.version = version + 1;
+        let mut env = Env::empty();
+        expr.eval(&mut env, &ctx).and_then(|v| {
+            v.to_static()
+                .try_extract_into::<T>()
+                .map_err(EvalError::TryExtractFrom)
+        })
+    }
+
     pub fn try_eval_out_wo_ctx<T: TryExtractFrom<Value<'static>> + 'static>(
         expr: &Expr,
     ) -> Result<T, EvalError> {
diff --git a/ergotree-interpreter/src/eval/context.rs b/ergotree-interpreter/src/eval/context.rs
index 6b86e5052..5858f4cb2 100644
--- a/ergotree-interpreter/src/eval/context.rs
+++ b/ergotree-interpreter/src/eval/context.rs
@@ -7,7 +7,7 @@ use ergotree_ir::chain::ergo_box::ErgoBox;
 pub type TxIoVec<T> = BoundedVec<T, 1, { i16::MAX as usize }>;
 
 /// Interpreter's context (blockchain state)
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct Context<'ctx> {
     /// Current height
     pub height: u32,
@@ -35,6 +35,10 @@ impl<'ctx> Context<'ctx> {
             ..self
         }
     }
+    /// Activated script version corresponds to block version - 1
+    pub fn activated_script_version(&self) -> u8 {
+        self.pre_header.version.saturating_sub(1)
+    }
 }
 
 #[cfg(feature = "arbitrary")]
diff --git a/ergotree-interpreter/src/eval/error.rs b/ergotree-interpreter/src/eval/error.rs
index 61cc2e474..224d90336 100644
--- a/ergotree-interpreter/src/eval/error.rs
+++ b/ergotree-interpreter/src/eval/error.rs
@@ -74,17 +74,25 @@ pub enum EvalError {
     /// Wrapped error with source span
     #[error("eval error: {0:?}")]
     Spanned(SpannedEvalError),
+    /// Script version error
+    #[error("Method requires at least version {required_version}, but activated version is {activated_version}")]
+    ScriptVersionError {
+        /// Opcode/method call requires this version
+        required_version: u8,
+        /// Currently activated script version on network
+        activated_version: u8,
+    },
 }
 
 /// Wrapped error with source span
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct SpannedEvalError {
     /// eval error
-    error: Box<EvalError>,
+    pub error: Box<EvalError>,
     /// source span for the expression where error occurred
-    source_span: SourceSpan,
+    pub source_span: SourceSpan,
     /// environment at the time when error occurred
-    env: Env<'static>,
+    pub env: Env<'static>,
 }
 
 /// Wrapped error with source span and source code
diff --git a/ergotree-interpreter/src/eval/extract_reg_as.rs b/ergotree-interpreter/src/eval/extract_reg_as.rs
index 3d68fa738..404c0c894 100644
--- a/ergotree-interpreter/src/eval/extract_reg_as.rs
+++ b/ergotree-interpreter/src/eval/extract_reg_as.rs
@@ -32,7 +32,16 @@ impl Evaluable for ExtractRegisterAs {
                 "Error getting the register id {id} with error {e:?}"
             ))
         })?;
-        Ok(Value::Opt(Box::new(reg_val_opt.map(|c| Value::from(c.v)))))
+        match reg_val_opt {
+            Some(constant) if constant.tpe == *self.elem_tpe => {
+                Ok(Value::Opt(Box::new(Some(constant.v.into()))))
+            }
+            Some(constant) => Err(EvalError::UnexpectedValue(format!(
+                "Expected register {id} to be of type {}, got {}",
+                self.elem_tpe, constant.tpe
+            ))),
+            None => Ok(Value::Opt(Box::new(None))),
+        }
     }
 }
 
@@ -41,7 +50,7 @@ impl Evaluable for ExtractRegisterAs {
 mod tests {
     use super::*;
     use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
+    use crate::eval::tests::{eval_out, try_eval_out};
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::mir::global_vars::GlobalVars;
     use ergotree_ir::mir::option_get::OptionGet;
@@ -63,4 +72,18 @@ mod tests {
         let v = eval_out::<i64>(&option_get_expr, &ctx);
         assert_eq!(v, ctx.self_box.value.as_i64());
     }
+
+    #[test]
+    fn eval_box_get_reg_r0_wrong_type() {
+        let get_reg_expr: Expr = ExtractRegisterAs::new(
+            GlobalVars::SelfBox.into(),
+            0,
+            SType::SOption(SType::SInt.into()), // R0 (value) is long, but we're expecting int
+        )
+        .unwrap()
+        .into();
+        let option_get_expr: Expr = OptionGet::try_build(get_reg_expr).unwrap().into();
+        let ctx = force_any_val::<Context>();
+        assert!(try_eval_out::<Value>(&option_get_expr, &ctx).is_err());
+    }
 }
diff --git a/ergotree-interpreter/src/eval/method_call.rs b/ergotree-interpreter/src/eval/method_call.rs
index c1b4ec5cc..4902b527a 100644
--- a/ergotree-interpreter/src/eval/method_call.rs
+++ b/ergotree-interpreter/src/eval/method_call.rs
@@ -16,41 +16,6 @@ impl Evaluable for MethodCall {
         let ov = self.obj.eval(env, ectx)?;
         let argsv: Result<Vec<Value>, EvalError> =
             self.args.iter().map(|arg| arg.eval(env, ectx)).collect();
-        smethod_eval_fn(&self.method)?(env, ectx, ov, argsv?)
-    }
-}
-
-#[allow(clippy::unwrap_used)]
-#[cfg(test)]
-#[cfg(feature = "arbitrary")]
-mod tests {
-    use ergotree_ir::mir::constant::Constant;
-    use ergotree_ir::mir::expr::Expr;
-    use ergotree_ir::mir::global_vars::GlobalVars;
-    use ergotree_ir::mir::option_get::OptionGet;
-    use ergotree_ir::mir::unary_op::OneArgOpTryBuild;
-    use ergotree_ir::types::sbox;
-    use sigma_test_util::force_any_val;
-
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-
-    use super::*;
-
-    #[test]
-    fn eval_box_get_reg() {
-        let mc: Expr = MethodCall::new(
-            GlobalVars::SelfBox.into(),
-            sbox::GET_REG_METHOD.clone(),
-            vec![Constant::from(0i8).into()],
-        )
-        .unwrap()
-        .into();
-        let option_get_expr: Expr = OptionGet::try_build(mc).unwrap().into();
-        let ctx = force_any_val::<Context>();
-        assert_eq!(
-            eval_out::<i64>(&option_get_expr, &ctx),
-            ctx.self_box.value.as_i64()
-        );
+        smethod_eval_fn(&self.method)?(&self.method, env, ectx, ov, argsv?)
     }
 }
diff --git a/ergotree-interpreter/src/eval/property_call.rs b/ergotree-interpreter/src/eval/property_call.rs
index 6f1554014..9d60eb320 100644
--- a/ergotree-interpreter/src/eval/property_call.rs
+++ b/ergotree-interpreter/src/eval/property_call.rs
@@ -14,7 +14,7 @@ impl Evaluable for PropertyCall {
         ectx: &Context<'ctx>,
     ) -> Result<Value<'ctx>, EvalError> {
         let ov = self.obj.eval(env, ectx)?;
-        smethod_eval_fn(&self.method)?(env, ectx, ov, vec![])
+        smethod_eval_fn(&self.method)?(&self.method, env, ectx, ov, vec![])
     }
 }
 
diff --git a/ergotree-interpreter/src/eval/savltree.rs b/ergotree-interpreter/src/eval/savltree.rs
index 01fc11d3b..769c2ca32 100644
--- a/ergotree-interpreter/src/eval/savltree.rs
+++ b/ergotree-interpreter/src/eval/savltree.rs
@@ -20,24 +20,24 @@ use super::EvalError;
 use super::EvalFn;
 use ergotree_ir::types::stype::SType;
 
-pub(crate) static DIGEST_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static DIGEST_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
     Ok(Value::Coll(CollKind::NativeColl(NativeColl::CollByte(
         avl_tree_data.digest.0.iter().map(|&b| b as i8).collect(),
     ))))
 };
 
-pub(crate) static ENABLED_OPERATIONS_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static ENABLED_OPERATIONS_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
     Ok(Value::Byte(avl_tree_data.tree_flags.serialize() as i8))
 };
 
-pub(crate) static KEY_LENGTH_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static KEY_LENGTH_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
     Ok(Value::Int(avl_tree_data.key_length as i32))
 };
 
-pub(crate) static VALUE_LENGTH_OPT_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static VALUE_LENGTH_OPT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
     Ok(Value::Opt(Box::new(
         avl_tree_data
@@ -46,22 +46,22 @@ pub(crate) static VALUE_LENGTH_OPT_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
     )))
 };
 
-pub(crate) static IS_INSERT_ALLOWED_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static IS_INSERT_ALLOWED_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
     Ok(Value::Boolean(avl_tree_data.tree_flags.insert_allowed()))
 };
 
-pub(crate) static IS_UPDATE_ALLOWED_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static IS_UPDATE_ALLOWED_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
     Ok(Value::Boolean(avl_tree_data.tree_flags.update_allowed()))
 };
 
-pub(crate) static IS_REMOVE_ALLOWED_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static IS_REMOVE_ALLOWED_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
     Ok(Value::Boolean(avl_tree_data.tree_flags.remove_allowed()))
 };
 
-pub(crate) static UPDATE_OPERATIONS_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
+pub(crate) static UPDATE_OPERATIONS_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, args| {
     let mut avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
     let new_operations = {
         let v = args.first().cloned().ok_or_else(|| {
@@ -73,7 +73,7 @@ pub(crate) static UPDATE_OPERATIONS_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
     Ok(Value::AvlTree(Box::new(avl_tree_data)))
 };
 
-pub(crate) static UPDATE_DIGEST_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
+pub(crate) static UPDATE_DIGEST_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, args| {
     let mut avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
     let new_digest = {
         let v = args.first().cloned().ok_or_else(|| {
@@ -86,7 +86,7 @@ pub(crate) static UPDATE_DIGEST_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
     Ok(Value::AvlTree(Box::new(avl_tree_data)))
 };
 
-pub(crate) static GET_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
+pub(crate) static GET_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, args| {
     let avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
     let key = {
         let v = args
@@ -135,7 +135,7 @@ pub(crate) static GET_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
 };
 
 pub(crate) static GET_MANY_EVAL_FN: EvalFn =
-    |_env, _ctx, obj, args| {
+    |_mc, _env, _ctx, obj, args| {
         let avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
 
         let keys = {
@@ -197,7 +197,7 @@ pub(crate) static GET_MANY_EVAL_FN: EvalFn =
     };
 
 pub(crate) static INSERT_EVAL_FN: EvalFn =
-    |_env, _ctx, obj, args| {
+    |_mc, _env, _ctx, obj, args| {
         let mut avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
 
         if !avl_tree_data.tree_flags.insert_allowed() {
@@ -260,7 +260,7 @@ pub(crate) static INSERT_EVAL_FN: EvalFn =
     };
 
 pub(crate) static REMOVE_EVAL_FN: EvalFn =
-    |_env, _ctx, obj, args| {
+    |_mc, _env, _ctx, obj, args| {
         let mut avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
 
         if !avl_tree_data.tree_flags.remove_allowed() {
@@ -319,7 +319,7 @@ pub(crate) static REMOVE_EVAL_FN: EvalFn =
         }
     };
 
-pub(crate) static CONTAINS_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
+pub(crate) static CONTAINS_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, args| {
     let avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
     let key = {
         let v = args
@@ -367,7 +367,7 @@ pub(crate) static CONTAINS_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
 };
 
 pub(crate) static UPDATE_EVAL_FN: EvalFn =
-    |_env, _ctx, obj, args| {
+    |_mc, _env, _ctx, obj, args| {
         let mut avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
 
         if !avl_tree_data.tree_flags.update_allowed() {
diff --git a/ergotree-interpreter/src/eval/sbox.rs b/ergotree-interpreter/src/eval/sbox.rs
index a14c4bab5..ea7e857a2 100644
--- a/ergotree-interpreter/src/eval/sbox.rs
+++ b/ergotree-interpreter/src/eval/sbox.rs
@@ -3,24 +3,37 @@ use std::convert::TryInto;
 use crate::eval::EvalError;
 
 use ergotree_ir::chain::ergo_box::ErgoBox;
+use ergotree_ir::ergo_tree::ErgoTreeVersion;
 use ergotree_ir::mir::constant::TryExtractInto;
 use ergotree_ir::mir::value::Value;
 use ergotree_ir::reference::Ref;
+use ergotree_ir::types::stype::SType;
 
 use super::EvalFn;
 
-pub(crate) static VALUE_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static VALUE_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     Ok(Value::Long(
         obj.try_extract_into::<Ref<'_, ErgoBox>>()?.value.as_i64(),
     ))
 };
 
-pub(crate) static GET_REG_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
-    let reg_id = args
+pub(crate) static GET_REG_EVAL_FN: EvalFn = |mc, _env, ctx, obj, args| {
+    if ctx.activated_script_version() < ErgoTreeVersion::V6_SOFT_FORK_VERSION {
+        return Err(EvalError::ScriptVersionError {
+            required_version: ErgoTreeVersion::V6_SOFT_FORK_VERSION,
+            activated_version: ctx.activated_script_version(),
+        });
+    }
+    #[allow(clippy::unwrap_used)]
+    let reg_id: i8 = args
         .first()
         .cloned()
         .ok_or_else(|| EvalError::NotFound("register index is missing".to_string()))?
-        .try_extract_into::<i8>()?;
+        .try_extract_into::<i32>()?
+        .try_into()
+        .map_err(|e| {
+            EvalError::RegisterIdOutOfBounds(format!("register index is out of bounds: {:?} ", e))
+        })?;
     let reg_id = reg_id.try_into().map_err(|e| {
         EvalError::RegisterIdOutOfBounds(format!(
             "register index {reg_id} is out of bounds: {:?} ",
@@ -28,19 +41,33 @@ pub(crate) static GET_REG_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
         ))
     })?;
 
-    Ok(Value::Opt(Box::new(
-        obj.try_extract_into::<Ref<'_, ErgoBox>>()?
-            .get_register(reg_id)
-            .map_err(|e| {
-                EvalError::NotFound(format!(
-                    "Error getting the register id {reg_id} with error {e:?}"
-                ))
-            })?
-            .map(|c| Value::from(c.v)),
-    )))
+    let reg_val_opt = obj
+        .try_extract_into::<Ref<'_, ErgoBox>>()?
+        .get_register(reg_id)
+        .map_err(|e| {
+            EvalError::NotFound(format!(
+                "Error getting the register id {reg_id} with error {e:?}"
+            ))
+        })?;
+    // Return type of getReg[T] is always Option[T]
+    #[allow(clippy::unreachable)]
+    let SType::SOption(expected_type) = &*mc.tpe().t_range
+    else {
+        unreachable!()
+    };
+    match reg_val_opt {
+        Some(constant) if constant.tpe == **expected_type => {
+            Ok(Value::Opt(Box::new(Some(constant.v.into()))))
+        }
+        Some(constant) => Err(EvalError::UnexpectedValue(format!(
+            "Expected register {reg_id} to be of type {}, got {}",
+            expected_type, constant.tpe
+        ))),
+        None => Ok(Value::Opt(Box::new(None))),
+    }
 };
 
-pub(crate) static TOKENS_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static TOKENS_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let res: Value = obj
         .try_extract_into::<Ref<'_, ErgoBox>>()?
         .tokens_raw()
@@ -49,17 +76,24 @@ pub(crate) static TOKENS_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
 };
 
 #[allow(clippy::unwrap_used)]
+#[allow(clippy::panic)]
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 mod tests {
+    use ergotree_ir::ergo_tree::ErgoTreeVersion;
+    use ergotree_ir::mir::constant::Constant;
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::mir::global_vars::GlobalVars;
+    use ergotree_ir::mir::method_call::MethodCall;
     use ergotree_ir::mir::property_call::PropertyCall;
     use ergotree_ir::types::sbox;
+    use ergotree_ir::types::stype::SType;
+    use ergotree_ir::types::stype_param::STypeVar;
     use sigma_test_util::force_any_val;
 
     use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
+    use crate::eval::tests::{eval_out, try_eval_out_with_version};
+    use crate::eval::EvalError;
 
     #[test]
     fn eval_box_value() {
@@ -81,4 +115,65 @@ mod tests {
             ctx.self_box.tokens_raw()
         );
     }
+
+    #[test]
+    fn eval_reg_out() {
+        let type_args = std::iter::once((STypeVar::t(), SType::SLong)).collect();
+        let expr: Expr = MethodCall::with_type_args(
+            GlobalVars::SelfBox.into(),
+            sbox::GET_REG_METHOD.clone().with_concrete_types(&type_args),
+            vec![Constant::from(0i32).into()],
+            type_args,
+        )
+        .unwrap()
+        .into();
+        let ctx = force_any_val::<Context>();
+        (0..ErgoTreeVersion::V6_SOFT_FORK_VERSION).for_each(|version| {
+            assert!(try_eval_out_with_version::<i64>(&expr, &ctx, version).is_err())
+        });
+        (ErgoTreeVersion::V6_SOFT_FORK_VERSION..=ErgoTreeVersion::MAX_SCRIPT_VERSION).for_each(
+            |version| {
+                assert_eq!(
+                    try_eval_out_with_version::<Option<i64>>(&expr, &ctx, version)
+                        .unwrap()
+                        .unwrap(),
+                    ctx.self_box.value.as_i64()
+                )
+            },
+        );
+    }
+
+    // Attempt to extract SigmaProp from register of type SLong
+    #[test]
+    fn eval_reg_out_wrong_type() {
+        let type_args = std::iter::once((STypeVar::t(), SType::SSigmaProp)).collect();
+        let expr: Expr = MethodCall::with_type_args(
+            GlobalVars::SelfBox.into(),
+            sbox::GET_REG_METHOD.clone().with_concrete_types(&type_args),
+            vec![Constant::from(0i32).into()],
+            type_args,
+        )
+        .unwrap()
+        .into();
+        let ctx = force_any_val::<Context>();
+        (0..ErgoTreeVersion::V6_SOFT_FORK_VERSION).for_each(|version| {
+            let res = try_eval_out_with_version::<Option<i64>>(&expr, &ctx, version);
+            match res {
+                Err(EvalError::Spanned(err))
+                    if matches!(
+                        *err.error,
+                        EvalError::ScriptVersionError {
+                            required_version: ErgoTreeVersion::V6_SOFT_FORK_VERSION,
+                            activated_version: _
+                        }
+                    ) => {}
+                _ => panic!("Expected script version error"),
+            }
+        });
+        (ErgoTreeVersion::V6_SOFT_FORK_VERSION..=ErgoTreeVersion::MAX_SCRIPT_VERSION).for_each(
+            |version| {
+                assert!(try_eval_out_with_version::<Option<i64>>(&expr, &ctx, version).is_err())
+            },
+        );
+    }
 }
diff --git a/ergotree-interpreter/src/eval/scoll.rs b/ergotree-interpreter/src/eval/scoll.rs
index c0d54a517..d22e9784d 100644
--- a/ergotree-interpreter/src/eval/scoll.rs
+++ b/ergotree-interpreter/src/eval/scoll.rs
@@ -5,6 +5,7 @@ use ergotree_ir::mir::constant::TryExtractInto;
 use ergotree_ir::mir::expr::Expr;
 use ergotree_ir::mir::value::CollKind;
 use ergotree_ir::mir::value::Value;
+use ergotree_ir::types::smethod::SMethod;
 use ergotree_ir::types::stuple::STuple;
 use ergotree_ir::types::stype::SType::SInt;
 
@@ -14,7 +15,7 @@ use super::EvalFn;
 use std::convert::TryFrom;
 use std::sync::Arc;
 
-pub(crate) static INDEX_OF_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
+pub(crate) static INDEX_OF_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, args| {
     Ok(Value::Int({
         let normalized_input_vals: Vec<Value> = match obj {
             Value::Coll(coll) => Ok(coll.as_vec()),
@@ -44,6 +45,7 @@ pub(crate) static INDEX_OF_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
 };
 
 pub(crate) fn flatmap_eval<'ctx>(
+    _mc: &SMethod,
     env: &mut Env<'ctx>,
     ctx: &Context<'ctx>,
     obj: Value<'ctx>,
@@ -126,7 +128,7 @@ pub(crate) fn flatmap_eval<'ctx>(
         .map(Value::Coll)
 }
 
-pub(crate) static ZIP_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
+pub(crate) static ZIP_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, args| {
     let (type_1, coll_1) = match obj {
         Value::Coll(coll) => Ok((coll.elem_tpe().clone(), coll.as_vec())),
         _ => Err(EvalError::UnexpectedValue(format!(
@@ -157,7 +159,7 @@ pub(crate) static ZIP_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
     }
 };
 
-pub(crate) static INDICES_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static INDICES_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let input_len = match obj {
         Value::Coll(coll) => Ok(coll.len()),
         _ => Err(EvalError::UnexpectedValue(format!(
@@ -180,7 +182,7 @@ pub(crate) static INDICES_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
     }
 };
 
-pub(crate) static PATCH_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
+pub(crate) static PATCH_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, args| {
     let (input_tpe, normalized_input_vals) = match obj {
         Value::Coll(coll) => Ok((coll.elem_tpe().clone(), coll.as_vec())),
         _ => Err(EvalError::UnexpectedValue(format!(
@@ -221,7 +223,7 @@ pub(crate) static PATCH_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
     Ok(Value::Coll(CollKind::from_collection(input_tpe, res)?))
 };
 
-pub(crate) static UPDATED_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
+pub(crate) static UPDATED_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, args| {
     let (input_tpe, normalized_input_vals) = match obj {
         Value::Coll(coll) => Ok((coll.elem_tpe().clone(), coll.as_vec())),
         _ => Err(EvalError::UnexpectedValue(format!(
@@ -255,7 +257,7 @@ pub(crate) static UPDATED_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
 };
 
 pub(crate) static UPDATE_MANY_EVAL_FN: EvalFn =
-    |_env, _ctx, obj, args| {
+    |_mc, _env, _ctx, obj, args| {
         let (input_tpe, normalized_input_vals) = match obj {
             Value::Coll(coll) => Ok((coll.elem_tpe().clone(), coll.as_vec())),
             _ => Err(EvalError::UnexpectedValue(format!(
diff --git a/ergotree-interpreter/src/eval/scontext.rs b/ergotree-interpreter/src/eval/scontext.rs
index dab58da19..a74f434a7 100644
--- a/ergotree-interpreter/src/eval/scontext.rs
+++ b/ergotree-interpreter/src/eval/scontext.rs
@@ -11,7 +11,7 @@ use ergotree_ir::types::stype::SType;
 use super::EvalError;
 use super::EvalFn;
 
-pub(crate) static DATA_INPUTS_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
+pub(crate) static DATA_INPUTS_EVAL_FN: EvalFn = |_mc, _env, ctx, obj, _args| {
     if obj != Value::Context {
         return Err(EvalError::UnexpectedValue(format!(
             "Context.dataInputs: expected object of Value::Context, got {:?}",
@@ -26,7 +26,7 @@ pub(crate) static DATA_INPUTS_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
     }))
 };
 
-pub(crate) static SELF_BOX_INDEX_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
+pub(crate) static SELF_BOX_INDEX_EVAL_FN: EvalFn = |_mc, _env, ctx, obj, _args| {
     if obj != Value::Context {
         return Err(EvalError::UnexpectedValue(format!(
             "Context.selfBoxIndex: expected object of Value::Context, got {:?}",
@@ -41,7 +41,7 @@ pub(crate) static SELF_BOX_INDEX_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
     Ok(Value::Int(box_index as i32))
 };
 
-pub(crate) static HEADERS_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
+pub(crate) static HEADERS_EVAL_FN: EvalFn = |_mc, _env, ctx, obj, _args| {
     if obj != Value::Context {
         return Err(EvalError::UnexpectedValue(format!(
             "Context.headers: expected object of Value::Context, got {:?}",
@@ -54,7 +54,7 @@ pub(crate) static HEADERS_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
     }))
 };
 
-pub(crate) static PRE_HEADER_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
+pub(crate) static PRE_HEADER_EVAL_FN: EvalFn = |_mc, _env, ctx, obj, _args| {
     if obj != Value::Context {
         return Err(EvalError::UnexpectedValue(format!(
             "Context.preHeader: expected object of Value::Context, got {:?}",
@@ -64,7 +64,7 @@ pub(crate) static PRE_HEADER_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
     Ok(Box::from(ctx.pre_header.clone()).into())
 };
 
-pub(crate) static LAST_BLOCK_UTXO_ROOT_HASH_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
+pub(crate) static LAST_BLOCK_UTXO_ROOT_HASH_EVAL_FN: EvalFn = |_mc, _env, ctx, obj, _args| {
     if obj != Value::Context {
         return Err(EvalError::UnexpectedValue(format!(
             "Context.LastBlockUtxoRootHash: expected object of Value::Context, got {:?}",
@@ -81,7 +81,7 @@ pub(crate) static LAST_BLOCK_UTXO_ROOT_HASH_EVAL_FN: EvalFn = |_env, ctx, obj, _
     })))
 };
 
-pub(crate) static MINER_PUBKEY_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
+pub(crate) static MINER_PUBKEY_EVAL_FN: EvalFn = |_mc, _env, ctx, obj, _args| {
     if obj != Value::Context {
         return Err(EvalError::UnexpectedValue(format!(
             "Context.preHeader: expected object of Value::Context, got {:?}",
diff --git a/ergotree-interpreter/src/eval/sglobal.rs b/ergotree-interpreter/src/eval/sglobal.rs
index 70ae6563c..ceb248550 100644
--- a/ergotree-interpreter/src/eval/sglobal.rs
+++ b/ergotree-interpreter/src/eval/sglobal.rs
@@ -12,7 +12,7 @@ fn helper_xor(x: &[i8], y: &[i8]) -> Arc<[i8]> {
     x.iter().zip(y.iter()).map(|(x1, x2)| *x1 ^ *x2).collect()
 }
 
-pub(crate) static GROUP_GENERATOR_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static GROUP_GENERATOR_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     if obj != Value::Global {
         return Err(EvalError::UnexpectedValue(format!(
             "sglobal.groupGenerator expected obj to be Value::Global, got {:?}",
@@ -22,7 +22,7 @@ pub(crate) static GROUP_GENERATOR_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
     Ok(Value::from(generator()))
 };
 
-pub(crate) static XOR_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
+pub(crate) static XOR_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, args| {
     if obj != Value::Global {
         return Err(EvalError::UnexpectedValue(format!(
             "sglobal.xor expected obj to be Value::Global, got {:?}",
diff --git a/ergotree-interpreter/src/eval/sgroup_elem.rs b/ergotree-interpreter/src/eval/sgroup_elem.rs
index 9b45addcd..e3181fb96 100644
--- a/ergotree-interpreter/src/eval/sgroup_elem.rs
+++ b/ergotree-interpreter/src/eval/sgroup_elem.rs
@@ -7,7 +7,7 @@ use ergotree_ir::serialization::SigmaSerializable;
 
 use super::EvalFn;
 
-pub(crate) static GET_ENCODED_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static GET_ENCODED_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let encoded: Vec<u8> = match obj {
         Value::GroupElement(ec_point) => Ok(ec_point.sigma_serialize_bytes()?),
         _ => Err(EvalError::UnexpectedValue(format!(
@@ -19,7 +19,7 @@ pub(crate) static GET_ENCODED_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
     Ok(Value::from(encoded))
 };
 
-pub(crate) static NEGATE_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static NEGATE_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let negated: EcPoint = match obj {
         Value::GroupElement(ec_point) => Ok(-(*ec_point).clone()),
         _ => Err(EvalError::UnexpectedValue(format!(
diff --git a/ergotree-interpreter/src/eval/sheader.rs b/ergotree-interpreter/src/eval/sheader.rs
index e98e1e317..9fa363912 100644
--- a/ergotree-interpreter/src/eval/sheader.rs
+++ b/ergotree-interpreter/src/eval/sheader.rs
@@ -7,72 +7,72 @@ use ergotree_ir::{bigint256::BigInt256, mir::constant::TryExtractInto};
 
 use super::{EvalError, EvalFn};
 
-pub(crate) static VERSION_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static VERSION_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let header = obj.try_extract_into::<Header>()?;
     Ok((header.version as i8).into())
 };
 
-pub(crate) static ID_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static ID_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let header = obj.try_extract_into::<Header>()?;
     Ok(Into::<Vec<i8>>::into(header.id).into())
 };
 
-pub(crate) static PARENT_ID_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static PARENT_ID_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let header = obj.try_extract_into::<Header>()?;
     Ok(Into::<Vec<i8>>::into(header.parent_id).into())
 };
 
-pub(crate) static AD_PROOFS_ROOT_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static AD_PROOFS_ROOT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let header = obj.try_extract_into::<Header>()?;
     Ok(Into::<Vec<i8>>::into(header.ad_proofs_root).into())
 };
 
-pub(crate) static STATE_ROOT_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static STATE_ROOT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let header = obj.try_extract_into::<Header>()?;
     Ok(Into::<Vec<i8>>::into(header.state_root).into())
 };
 
-pub(crate) static TRANSACTION_ROOT_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static TRANSACTION_ROOT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let header = obj.try_extract_into::<Header>()?;
     Ok(Into::<Vec<i8>>::into(header.transaction_root).into())
 };
 
-pub(crate) static EXTENSION_ROOT_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static EXTENSION_ROOT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let header = obj.try_extract_into::<Header>()?;
     Ok(Into::<Vec<i8>>::into(header.extension_root).into())
 };
 
-pub(crate) static TIMESTAMP_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static TIMESTAMP_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let header = obj.try_extract_into::<Header>()?;
     Ok((header.timestamp as i64).into())
 };
 
-pub(crate) static N_BITS_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static N_BITS_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let header = obj.try_extract_into::<Header>()?;
     Ok((header.n_bits as i64).into())
 };
 
-pub(crate) static HEIGHT_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static HEIGHT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let header = obj.try_extract_into::<Header>()?;
     Ok((header.height as i32).into())
 };
 
-pub(crate) static MINER_PK_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static MINER_PK_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let header = obj.try_extract_into::<Header>()?;
     Ok(Arc::new(*header.autolykos_solution.miner_pk).into())
 };
 
-pub(crate) static POW_ONETIME_PK_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static POW_ONETIME_PK_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let header = obj.try_extract_into::<Header>()?;
     Ok((*header.autolykos_solution.pow_onetime_pk.unwrap_or_default()).into())
 };
 
-pub(crate) static POW_NONCE_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static POW_NONCE_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let header = obj.try_extract_into::<Header>()?;
     Ok(header.autolykos_solution.nonce.into())
 };
 
-pub(crate) static POW_DISTANCE_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static POW_DISTANCE_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let header = obj.try_extract_into::<Header>()?;
     let pow_distance: BigInt256 = header
         .autolykos_solution
@@ -83,7 +83,7 @@ pub(crate) static POW_DISTANCE_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
     Ok(pow_distance.into())
 };
 
-pub(crate) static VOTES_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static VOTES_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let header = obj.try_extract_into::<Header>()?;
     Ok(Into::<Vec<u8>>::into(header.votes).into())
 };
diff --git a/ergotree-interpreter/src/eval/soption.rs b/ergotree-interpreter/src/eval/soption.rs
index 4105bc08f..7ac133c12 100644
--- a/ergotree-interpreter/src/eval/soption.rs
+++ b/ergotree-interpreter/src/eval/soption.rs
@@ -2,11 +2,13 @@ use crate::eval::EvalError;
 use crate::eval::Evaluable;
 
 use ergotree_ir::mir::value::Value;
+use ergotree_ir::types::smethod::SMethod;
 
 use super::env::Env;
 use super::Context;
 
 pub fn map_eval<'ctx>(
+    _mc: &SMethod,
     env: &mut Env<'ctx>,
     ctx: &Context<'ctx>,
     obj: Value<'ctx>,
@@ -54,6 +56,7 @@ pub fn map_eval<'ctx>(
 }
 
 pub fn filter_eval<'ctx>(
+    _mc: &SMethod,
     env: &mut Env<'ctx>,
     ctx: &Context<'ctx>,
     obj: Value<'ctx>,
diff --git a/ergotree-interpreter/src/eval/spreheader.rs b/ergotree-interpreter/src/eval/spreheader.rs
index d09a64514..e38467cf5 100644
--- a/ergotree-interpreter/src/eval/spreheader.rs
+++ b/ergotree-interpreter/src/eval/spreheader.rs
@@ -5,37 +5,37 @@ use ergotree_ir::mir::constant::TryExtractInto;
 
 use super::EvalFn;
 
-pub(crate) static VERSION_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static VERSION_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let preheader = obj.try_extract_into::<PreHeader>()?;
     Ok((preheader.version as i8).into())
 };
 
-pub(crate) static PARENT_ID_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static PARENT_ID_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let preheader = obj.try_extract_into::<PreHeader>()?;
     Ok(Into::<Vec<i8>>::into(preheader.parent_id).into())
 };
 
-pub(crate) static TIMESTAMP_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static TIMESTAMP_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let preheader = obj.try_extract_into::<PreHeader>()?;
     Ok((preheader.timestamp as i64).into())
 };
 
-pub(crate) static N_BITS_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static N_BITS_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let preheader = obj.try_extract_into::<PreHeader>()?;
     Ok((preheader.n_bits as i64).into())
 };
 
-pub(crate) static HEIGHT_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static HEIGHT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let preheader = obj.try_extract_into::<PreHeader>()?;
     Ok((preheader.height as i32).into())
 };
 
-pub(crate) static MINER_PK_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static MINER_PK_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let preheader = obj.try_extract_into::<PreHeader>()?;
     Ok(Arc::new(*preheader.miner_pk).into())
 };
 
-pub(crate) static VOTES_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+pub(crate) static VOTES_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
     let preheader = obj.try_extract_into::<PreHeader>()?;
     Ok(Into::<Vec<u8>>::into(preheader.votes).into())
 };
diff --git a/ergotree-ir/src/ergo_tree.rs b/ergotree-ir/src/ergo_tree.rs
index eba6d2cfd..d7fcaa086 100644
--- a/ergotree-ir/src/ergo_tree.rs
+++ b/ergotree-ir/src/ergo_tree.rs
@@ -12,14 +12,12 @@ use crate::serialization::{
 use crate::sigma_protocol::sigma_boolean::ProveDlog;
 use crate::types::stype::SType;
 use io::Cursor;
-use sigma_ser::vlq_encode::ReadSigmaVlqExt;
 use sigma_ser::vlq_encode::WriteSigmaVlqExt;
 
 use crate::serialization::constant_store::ConstantStore;
 use derive_more::From;
 use std::convert::TryFrom;
 use std::io;
-use std::io::Read;
 use thiserror::Error;
 
 mod tree_header;
@@ -108,6 +106,9 @@ pub enum ErgoTreeError {
     /// IO error
     #[error("IO error: {0:?}")]
     IoError(String),
+    /// ErgoTree root error. ErgoTree root TPE should be SigmaProp
+    #[error("Root Tpe error: expected SigmaProp, got {0}")]
+    RootTpeError(SType),
 }
 
 /// The root of ErgoScript IR. Serialized instances of this class are self sufficient and can be passed around.
@@ -146,6 +147,9 @@ impl ErgoTree {
         };
         r.set_constant_store(ConstantStore::new(constants.clone()));
         let root = Expr::sigma_parse(r)?;
+        if root.tpe() != SType::SSigmaProp {
+            return Err(ErgoTreeError::RootTpeError(root.tpe()));
+        }
         Ok(ParsedErgoTree {
             header,
             constants,
@@ -346,21 +350,22 @@ impl SigmaSerializable for ErgoTree {
     }
 
     fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
+        let start_pos = r.stream_position()?;
         let header = ErgoTreeHeader::sigma_parse(r)?;
         if header.has_size() {
             let tree_size_bytes = r.get_u32()?;
+            let body_pos = r.stream_position()?;
             let mut buf = vec![0u8; tree_size_bytes as usize];
             r.read_exact(buf.as_mut_slice())?;
-            let buf_copy = buf.clone();
             let mut inner_r =
                 SigmaByteReader::new(Cursor::new(&mut buf[..]), ConstantStore::empty());
             match ErgoTree::sigma_parse_sized(&mut inner_r, header.clone()) {
                 Ok(parsed_tree) => Ok(parsed_tree.into()),
                 Err(error) => {
-                    let mut bytes = vec![header.serialized()];
-                    #[allow(clippy::unwrap_used)]
-                    bytes.put_u32(tree_size_bytes).unwrap();
-                    bytes.extend_from_slice(&buf_copy);
+                    let num_bytes = (body_pos - start_pos) + tree_size_bytes as u64;
+                    r.seek(io::SeekFrom::Start(start_pos))?;
+                    let mut bytes = vec![0; num_bytes as usize];
+                    r.read_exact(&mut bytes)?;
                     Ok(ErgoTree::Unparsed {
                         tree_bytes: bytes,
                         error,
@@ -382,51 +387,6 @@ impl SigmaSerializable for ErgoTree {
             }))
         }
     }
-
-    fn sigma_parse_bytes(bytes: &[u8]) -> Result<Self, SigmaParsingError> {
-        let wrap_in_ergotree = |r: Result<ParsedErgoTree, ErgoTreeError>| -> Self {
-            match r {
-                Ok(parsed_tree) => ErgoTree::Parsed(parsed_tree),
-                Err(error) => ErgoTree::Unparsed {
-                    tree_bytes: bytes.to_vec(),
-                    error,
-                },
-            }
-        };
-        let mut r = SigmaByteReader::new(Cursor::new(bytes), ConstantStore::empty());
-        let tree: Result<ErgoTree, SigmaParsingError> = match ErgoTreeHeader::sigma_parse(&mut r) {
-            Ok(header) => {
-                if header.has_size() {
-                    let tree_size_bytes = r.get_u32()?;
-                    let mut buf = vec![0u8; tree_size_bytes as usize];
-                    r.read_exact(buf.as_mut_slice())?;
-                    let mut inner_r =
-                        SigmaByteReader::new(Cursor::new(&mut buf[..]), ConstantStore::empty());
-                    Ok(wrap_in_ergotree(ErgoTree::sigma_parse_sized(
-                        &mut inner_r,
-                        header,
-                    )))
-                } else {
-                    Ok(wrap_in_ergotree(ErgoTree::sigma_parse_sized(
-                        &mut r, header,
-                    )))
-                }
-            }
-            Err(e) => Ok(ErgoTree::Unparsed {
-                tree_bytes: bytes.to_vec(),
-                error: e.into(),
-            }),
-        };
-        let mut buffer = Vec::new();
-        if let Ok(0) = r.read_to_end(&mut buffer) {
-            tree
-        } else {
-            Ok(ErgoTree::Unparsed {
-                tree_bytes: bytes.to_vec(),
-                error: ErgoTreeRootParsingError::NonConsumedBytes.into(),
-            })
-        }
-    }
 }
 
 impl TryFrom<ErgoTree> for ProveDlog {
@@ -507,6 +467,7 @@ mod tests {
     use crate::chain::address::AddressEncoder;
     use crate::chain::address::NetworkPrefix;
     use crate::mir::constant::Literal;
+    use crate::sigma_protocol::sigma_boolean::SigmaProp;
     use proptest::prelude::*;
 
     proptest! {
@@ -539,16 +500,9 @@ mod tests {
             99,
             99,
         ];
-        let tree = ErgoTree::sigma_parse_bytes(&bytes).unwrap();
-        assert!(tree.parsed_tree().is_err(), "parsing constants should fail");
         assert_eq!(
-            tree.sigma_serialize_bytes().unwrap(),
-            bytes,
-            "serialization should return original bytes"
-        );
-        assert!(
-            tree.template_bytes().is_err(),
-            "template bytes should not be parsed"
+            ErgoTree::sigma_parse_bytes(&bytes),
+            Err(SigmaParsingError::InvalidTypeCode(0))
         );
     }
 
@@ -580,17 +534,7 @@ mod tests {
     fn deserialization_non_parseable_root_v0() {
         // no constant segregation, Expr is invalid
         let bytes = [ErgoTreeHeader::v0(false).serialized(), 0, 1];
-        let tree = ErgoTree::sigma_parse_bytes(&bytes).unwrap();
-        assert!(tree.parsed_tree().is_err(), "parsing root should fail");
-        assert_eq!(
-            tree.sigma_serialize_bytes().unwrap(),
-            bytes,
-            "serialization should return original bytes"
-        );
-        assert!(
-            tree.template_bytes().is_err(),
-            "template bytes should not be parsed"
-        );
+        assert!(ErgoTree::sigma_parse_bytes(&bytes).is_err());
     }
 
     #[test]
@@ -641,8 +585,8 @@ mod tests {
     #[test]
     fn test_constant_segregation() {
         let expr = Expr::Const(Constant {
-            tpe: SType::SBoolean,
-            v: Literal::Boolean(true),
+            tpe: SType::SSigmaProp,
+            v: Literal::SigmaProp(SigmaProp::new(true.into()).into()),
         });
         let ergo_tree = ErgoTree::new(ErgoTreeHeader::v0(false), &expr).unwrap();
         let bytes = ergo_tree.sigma_serialize_bytes().unwrap();
@@ -717,28 +661,21 @@ mod tests {
             tree,
             ErgoTree::Unparsed {
                 tree_bytes,
-                error: ErgoTreeRootParsingError::NonConsumedBytes.into()
+                error: ErgoTreeError::RootTpeError(SType::SByte)
             }
         );
     }
 
     #[test]
-    fn parse_invalid_tree_extra_bytes() {
+    fn parse_tree_extra_bytes() {
         let valid_ergo_tree_hex =
             "0008cd02a706374307f3038cb2f16e7ae9d3e29ca03ea5333681ca06a9bd87baab1164bc";
+        let valid_ergo_tree_bytes = base16::decode(valid_ergo_tree_hex).unwrap();
         // extra bytes at the end will be left unparsed
         let invalid_ergo_tree_with_extra_bytes = format!("{}aaaa", valid_ergo_tree_hex);
         let bytes = base16::decode(invalid_ergo_tree_with_extra_bytes.as_bytes()).unwrap();
         let tree = ErgoTree::sigma_parse_bytes(&bytes).unwrap();
-        //dbg!(&tree);
-        assert_eq!(tree.sigma_serialize_bytes().unwrap(), bytes);
-        assert_eq!(
-            tree,
-            ErgoTree::Unparsed {
-                tree_bytes: bytes,
-                error: ErgoTreeRootParsingError::NonConsumedBytes.into()
-            }
-        );
+        assert_eq!(tree.sigma_serialize_bytes().unwrap(), valid_ergo_tree_bytes);
     }
 
     #[test]
@@ -753,20 +690,20 @@ mod tests {
             tree,
             ErgoTree::Unparsed {
                 tree_bytes: bytes,
-                error: ErgoTreeRootParsingError::NonConsumedBytes.into()
+                error: ErgoTreeError::RootTpeError(SType::SShort)
             }
         );
     }
 
     #[test]
-    fn parse_invalid_tree_707() {
+    fn parse_tree_707() {
         // see https://github.com/ergoplatform/sigma-rust/issues/707
         let ergo_tree_hex =
             "100208cd03553448c194fdd843c87d080f5e8ed983f5bb2807b13b45a9683bba8c7bfb5ae808cd0354c06b1af711e51986d787ff1df2883fcaf8d34865fea720f549e382063a08ebd1eb0273007301";
         let bytes = base16::decode(ergo_tree_hex.as_bytes()).unwrap();
         let tree = ErgoTree::sigma_parse_bytes(&bytes).unwrap();
         //dbg!(&tree);
-        assert!(tree.parsed_tree().is_err(), "the tree is BoolToSigmaProp(SigmaOr(pk1, pk2)) is invalid (BoolToSigmaProp expects bool");
+        assert!(tree.parsed_tree().is_ok());
     }
 
     // Test Ergotree.proposition() for contract with some constants segregated already and some not. See: https://github.com/ergoplatform/sigma-rust/issues/757
diff --git a/ergotree-ir/src/ergo_tree/tree_header.rs b/ergotree-ir/src/ergo_tree/tree_header.rs
index 3baf0dd71..e7d5108c2 100644
--- a/ergotree-ir/src/ergo_tree/tree_header.rs
+++ b/ergotree-ir/src/ergo_tree/tree_header.rs
@@ -51,7 +51,7 @@ impl ErgoTreeHeader {
 
     /// Parse from byte
     pub fn new(header_byte: u8) -> Result<Self, ErgoTreeHeaderError> {
-        let version = ErgoTreeVersion::parse_version(header_byte)?;
+        let version = ErgoTreeVersion::parse_version(header_byte);
         let has_size = header_byte & Self::HAS_SIZE_FLAG != 0;
         let is_constant_segregation = header_byte & Self::CONSTANT_SEGREGATION_FLAG != 0;
         Ok(ErgoTreeHeader {
@@ -125,19 +125,23 @@ pub struct ErgoTreeVersion(u8);
 impl ErgoTreeVersion {
     /// Header mask to extract version bits.
     pub const VERSION_MASK: u8 = 0x07;
+
+    /// Max version of ErgoTree supported by interpreter
+    pub const MAX_SCRIPT_VERSION: u8 = 3;
+    /// Version for v6.0 (Evolution)
+    pub const V6_SOFT_FORK_VERSION: u8 = 3;
     /// Version 0
     pub const V0: Self = ErgoTreeVersion(0);
     /// Version 1 (size flag is mandatory)
     pub const V1: Self = ErgoTreeVersion(1);
+    /// Version 2 (JIT)
+    pub const V2: Self = ErgoTreeVersion(2);
+    /// Version 3 (v6.0/Evolution)
+    pub const V3: Self = ErgoTreeVersion(Self::V6_SOFT_FORK_VERSION);
 
     /// Returns a value of the version bits from the given header byte.
-    pub fn parse_version(header_byte: u8) -> Result<Self, ErgoTreeVersionError> {
-        let version = header_byte & ErgoTreeVersion::VERSION_MASK;
-        if version <= 1 {
-            Ok(ErgoTreeVersion(version))
-        } else {
-            Err(ErgoTreeVersionError::InvalidVersion(version))
-        }
+    pub fn parse_version(header_byte: u8) -> Self {
+        ErgoTreeVersion(header_byte & ErgoTreeVersion::VERSION_MASK)
     }
 }
 
diff --git a/ergotree-ir/src/mir/bool_to_sigma.rs b/ergotree-ir/src/mir/bool_to_sigma.rs
index 851e85008..01e035d56 100644
--- a/ergotree-ir/src/mir/bool_to_sigma.rs
+++ b/ergotree-ir/src/mir/bool_to_sigma.rs
@@ -37,7 +37,7 @@ impl OneArgOp for BoolToSigmaProp {
 
 impl OneArgOpTryBuild for BoolToSigmaProp {
     fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
-        input.check_post_eval_tpe(&SType::SBoolean)?;
+        // Input TPE is not checked here as old versions of interpreter (v4.0) accepted SigmaProp as argument to BoolToSigmaProp
         Ok(Self {
             input: input.into(),
         })
diff --git a/ergotree-ir/src/mir/method_call.rs b/ergotree-ir/src/mir/method_call.rs
index 5c136971f..7d644ae8f 100644
--- a/ergotree-ir/src/mir/method_call.rs
+++ b/ergotree-ir/src/mir/method_call.rs
@@ -1,6 +1,9 @@
+use std::collections::HashMap;
+
 use crate::serialization::op_code::OpCode;
 use crate::types::smethod::SMethod;
 use crate::types::stype::SType;
+use crate::types::stype_param::STypeVar;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
@@ -24,16 +27,36 @@ pub struct MethodCall {
     pub method: SMethod,
     /// Arguments passed to the method on invocation
     pub args: Vec<Expr>,
+    /// Arguments that cannot be inferred from function signature, such as Box.getReg[T]()
+    pub explicit_type_args: HashMap<STypeVar, SType>,
 }
 
 impl MethodCall {
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(obj: Expr, method: SMethod, args: Vec<Expr>) -> Result<Self, InvalidArgumentError> {
+    fn new_inner(
+        method: SMethod,
+        args: Vec<Expr>,
+        obj: Expr,
+        explicit_type_args: HashMap<STypeVar, SType>,
+    ) -> Result<MethodCall, InvalidArgumentError> {
         if method.tpe().t_dom.len() != args.len() + 1 {
             return Err(InvalidArgumentError(format!(
                 "MethodCall: expected arguments count {} does not match provided arguments count {}",
                 method.tpe().t_dom.len(), args.len() + 1)));
         }
+        if method.method_raw.explicit_type_args.len() != explicit_type_args.len() {
+            return Err(InvalidArgumentError(format!("MethodCall: expected explicit type args count {} does not match provided type args count {}",
+                method.method_raw.explicit_type_args.len(), explicit_type_args.len())));
+        }
+        if let Some(missing_tpe) = method
+            .method_raw
+            .explicit_type_args
+            .iter()
+            .find(|tpe| !explicit_type_args.contains_key(tpe))
+        {
+            return Err(InvalidArgumentError(format!(
+                "MethodCall: explicit_type_args does not include substitution for STypeVar {missing_tpe:?}",
+            )));
+        }
         let mut expected_types: Vec<SType> = vec![obj.tpe()];
         let arg_types: Vec<SType> = args.clone().into_iter().map(|a| a.tpe()).collect();
         expected_types.extend(arg_types);
@@ -53,9 +76,25 @@ impl MethodCall {
             obj: obj.into(),
             method,
             args,
+            explicit_type_args,
         })
     }
 
+    /// Create new object, returns an error if any of the requirements failed
+    pub fn new(obj: Expr, method: SMethod, args: Vec<Expr>) -> Result<Self, InvalidArgumentError> {
+        MethodCall::new_inner(method, args, obj, Default::default())
+    }
+
+    /// Create new object with explicit type args
+    pub fn with_type_args(
+        obj: Expr,
+        method: SMethod,
+        args: Vec<Expr>,
+        type_args: HashMap<STypeVar, SType>,
+    ) -> Result<Self, InvalidArgumentError> {
+        MethodCall::new_inner(method, args, obj, type_args)
+    }
+
     /// Type
     pub fn tpe(&self) -> SType {
         *self.method.tpe().t_range.clone()
diff --git a/ergotree-ir/src/pretty_printer/print.rs b/ergotree-ir/src/pretty_printer/print.rs
index 6e64d5cbc..1b30ffe39 100644
--- a/ergotree-ir/src/pretty_printer/print.rs
+++ b/ergotree-ir/src/pretty_printer/print.rs
@@ -518,6 +518,7 @@ impl Print for MethodCall {
                 obj: Box::new(obj),
                 method: self.method.clone(),
                 args,
+                explicit_type_args: self.explicit_type_args.clone(),
             },
         }
         .into())
diff --git a/ergotree-ir/src/serialization/method_call.rs b/ergotree-ir/src/serialization/method_call.rs
index a0cbd6576..b1fc31282 100644
--- a/ergotree-ir/src/serialization/method_call.rs
+++ b/ergotree-ir/src/serialization/method_call.rs
@@ -1,7 +1,11 @@
+use std::collections::HashMap;
+
 use crate::mir::expr::Expr;
 use crate::mir::method_call::MethodCall;
 use crate::types::smethod::MethodId;
 use crate::types::smethod::SMethod;
+use crate::types::stype::SType;
+use crate::types::stype_param::STypeVar;
 
 use super::sigma_byte_reader::SigmaByteRead;
 use super::sigma_byte_writer::SigmaByteWrite;
@@ -16,6 +20,11 @@ impl SigmaSerializable for MethodCall {
         self.method.method_id().sigma_serialize(w)?;
         self.obj.sigma_serialize(w)?;
         self.args.sigma_serialize(w)?;
+        for type_arg in &self.method.method_raw.explicit_type_args {
+            // Should not fail as existence of explicit type args is checked in constructor
+            let tpe = &self.explicit_type_args[type_arg];
+            tpe.sigma_serialize(w)?;
+        }
         Ok(())
     }
 
@@ -26,7 +35,20 @@ impl SigmaSerializable for MethodCall {
         let args = Vec::<Expr>::sigma_parse(r)?;
         let arg_types = args.iter().map(|arg| arg.tpe()).collect();
         let method = SMethod::from_ids(type_id, method_id)?.specialize_for(obj.tpe(), arg_types)?;
-        Ok(MethodCall::new(obj, method, args)?)
+        let explicit_type_args = method
+            .method_raw
+            .explicit_type_args
+            .iter()
+            .cloned()
+            .zip(std::iter::from_fn(|| Some(SType::sigma_parse(r))))
+            .map(|(tpe, res)| -> Result<(STypeVar, SType), SigmaParsingError> { Ok((tpe, res?)) })
+            .collect::<Result<HashMap<STypeVar, SType>, _>>()?;
+        Ok(MethodCall::with_type_args(
+            obj,
+            method.with_concrete_types(&explicit_type_args),
+            args,
+            explicit_type_args,
+        )?)
     }
 }
 
diff --git a/ergotree-ir/src/serialization/sigma_byte_reader.rs b/ergotree-ir/src/serialization/sigma_byte_reader.rs
index b1cf531ef..e31e96402 100644
--- a/ergotree-ir/src/serialization/sigma_byte_reader.rs
+++ b/ergotree-ir/src/serialization/sigma_byte_reader.rs
@@ -4,6 +4,7 @@ use super::val_def_type_store::ValDefTypeStore;
 use sigma_ser::vlq_encode::ReadSigmaVlqExt;
 use std::io::Cursor;
 use std::io::Read;
+use std::io::Seek;
 
 /// Implementation of SigmaByteRead
 pub struct SigmaByteReader<R> {
@@ -70,6 +71,20 @@ impl<R: Read> Read for SigmaByteReader<R> {
     }
 }
 
+impl<R: Seek> Seek for SigmaByteReader<R> {
+    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
+        self.inner.seek(pos)
+    }
+
+    fn rewind(&mut self) -> std::io::Result<()> {
+        self.inner.rewind()
+    }
+
+    fn stream_position(&mut self) -> std::io::Result<u64> {
+        self.inner.stream_position()
+    }
+}
+
 impl<R: ReadSigmaVlqExt> SigmaByteRead for SigmaByteReader<R> {
     fn constant_store(&mut self) -> &mut ConstantStore {
         &mut self.constant_store
diff --git a/ergotree-ir/src/types/savltree.rs b/ergotree-ir/src/types/savltree.rs
index 017d0833a..216779748 100644
--- a/ergotree-ir/src/types/savltree.rs
+++ b/ergotree-ir/src/types/savltree.rs
@@ -78,6 +78,7 @@ lazy_static! {
             t_range: SType::SColl(Arc::new(SType::SByte)).into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
     /// AvlTree.digest
     pub static ref DIGEST_METHOD: SMethod =
@@ -93,6 +94,7 @@ lazy_static! {
             t_range: SType::SByte.into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
     /// AvlTree.enabledOperations
     pub static ref ENABLED_OPERATIONS_METHOD: SMethod =
@@ -108,6 +110,7 @@ lazy_static! {
             t_range: SType::SInt.into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
     /// AvlTree.keyLength
     pub static ref KEY_LENGTH_METHOD: SMethod =
@@ -123,6 +126,7 @@ lazy_static! {
             t_range: SType::SOption(Arc::new(SType::SInt)).into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
     /// AvlTree.valueLengthOpt
     pub static ref VALUE_LENGTH_OPT_METHOD: SMethod =
@@ -138,6 +142,7 @@ lazy_static! {
             t_range: SType::SBoolean.into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
     /// AvlTree.isInsertAllowed
     pub static ref IS_INSERT_ALLOWED_METHOD: SMethod =
@@ -153,6 +158,7 @@ lazy_static! {
             t_range: SType::SBoolean.into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
     /// AvlTree.isUpdateAllowed
     pub static ref IS_UPDATE_ALLOWED_METHOD: SMethod =
@@ -168,6 +174,7 @@ lazy_static! {
             t_range: SType::SBoolean.into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
     /// AvlTree.isRemoveAllowed
     pub static ref IS_REMOVE_ALLOWED_METHOD: SMethod =
@@ -183,6 +190,7 @@ lazy_static! {
             t_range: SType::SAvlTree.into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
     /// AvlTree.updateOperations
     pub static ref UPDATE_OPERATIONS_METHOD: SMethod =
@@ -201,6 +209,7 @@ lazy_static! {
             t_range: SType::SOption(SType::SColl(SType::SByte.into()).into()).into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
 
     /// AvlTree.get
@@ -220,6 +229,7 @@ lazy_static! {
             t_range: SType::SColl(SType::SOption(SType::SColl(SType::SByte.into()).into()).into()).into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
 
     /// AvlTree.getMany
@@ -248,6 +258,7 @@ lazy_static! {
             t_range: SType::SOption(Arc::new(SType::SAvlTree)).into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
     /// AvlTree.insert
     pub static ref INSERT_METHOD: SMethod =
@@ -270,6 +281,7 @@ lazy_static! {
             t_range: SType::SOption(Arc::new(SType::SAvlTree)).into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
     /// AvlTree.remove
     pub static ref REMOVE_METHOD: SMethod =
@@ -289,6 +301,7 @@ lazy_static! {
             t_range: SType::SBoolean.into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
     /// AvlTree.contains
     pub static ref CONTAINS_METHOD: SMethod =
@@ -316,6 +329,7 @@ lazy_static! {
             t_range: SType::SOption(Arc::new(SType::SAvlTree)).into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
     /// AvlTree.update
     pub static ref UPDATE_METHOD: SMethod =
@@ -331,6 +345,7 @@ lazy_static! {
             t_range: SType::SAvlTree.into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
     /// AvlTree.updateDigest
     pub static ref UPDATE_DIGEST_METHOD: SMethod =
diff --git a/ergotree-ir/src/types/sbox.rs b/ergotree-ir/src/types/sbox.rs
index 07f007108..b898604b0 100644
--- a/ergotree-ir/src/types/sbox.rs
+++ b/ergotree-ir/src/types/sbox.rs
@@ -42,6 +42,7 @@ lazy_static! {
             t_range: Box::new(SType::SLong),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![],
     };
     /// Box.value
     pub static ref VALUE_METHOD: SMethod = SMethod::new(STypeCompanion::Box, VALUE_METHOD_DESC.clone(),);
@@ -52,10 +53,11 @@ lazy_static! {
         method_id: GET_REG_METHOD_ID,
         name: "getReg",
         tpe: SFunc {
-            t_dom: vec![SType::SBox, SType::SByte],
+            t_dom: vec![SType::SBox, SType::SInt],
             t_range: SType::SOption(Arc::new(STypeVar::t().into())).into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![STypeVar::t()]
     };
     /// Box.getReg
     pub static ref GET_REG_METHOD: SMethod =
@@ -75,14 +77,21 @@ lazy_static! {
                     ).into())).into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![],
     };
     /// Box.tokens
     pub static ref TOKENS_METHOD: SMethod =
         SMethod::new( STypeCompanion::Box,TOKENS_METHOD_DESC.clone(),);
 }
 
+#[allow(clippy::unwrap_used)]
 #[cfg(test)]
 mod tests {
+    use crate::{
+        mir::{constant::Constant, global_vars::GlobalVars, method_call::MethodCall},
+        serialization::SigmaSerializable,
+    };
+
     use super::*;
 
     #[test]
@@ -91,4 +100,20 @@ mod tests {
         assert!(SMethod::from_ids(TYPE_CODE, GET_REG_METHOD_ID).map(|e| e.name()) == Ok("getReg"));
         assert!(SMethod::from_ids(TYPE_CODE, TOKENS_METHOD_ID).map(|e| e.name()) == Ok("tokens"));
     }
+
+    #[test]
+    fn test_getreg_serialization_roundtrip() {
+        let type_args = std::iter::once((STypeVar::t(), SType::SInt)).collect();
+        let mc = MethodCall::with_type_args(
+            GlobalVars::SelfBox.into(),
+            GET_REG_METHOD.clone().with_concrete_types(&type_args),
+            vec![Constant::from(4i32).into()],
+            type_args,
+        )
+        .unwrap();
+        assert_eq!(
+            MethodCall::sigma_parse_bytes(&mc.sigma_serialize_bytes().unwrap()).unwrap(),
+            mc
+        );
+    }
 }
diff --git a/ergotree-ir/src/types/scoll.rs b/ergotree-ir/src/types/scoll.rs
index 513b15dd3..11400aea6 100644
--- a/ergotree-ir/src/types/scoll.rs
+++ b/ergotree-ir/src/types/scoll.rs
@@ -57,6 +57,7 @@ lazy_static! {
             t_range: SType::SInt.into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
     /// Coll.indexOf
     pub static ref INDEX_OF_METHOD: SMethod = SMethod::new(STypeCompanion::Coll, INDEX_OF_METHOD_DESC.clone());
@@ -76,6 +77,7 @@ lazy_static! {
                 ],
             SType::SColl(SType::STypeVar(STypeVar::ov()).into()),
         ),
+        explicit_type_args: vec![]
     };
     /// Coll.flatMap
     pub static ref FLATMAP_METHOD: SMethod = SMethod::new(STypeCompanion::Coll, FLATMAP_METHOD_DESC.clone());
@@ -93,7 +95,8 @@ lazy_static! {
             SType::SColl(SType::STuple(STuple::pair(
                 STypeVar::t().into(), STypeVar::iv().into()
             )).into())
-        )
+        ),
+        explicit_type_args: vec![]
     };
     /// Coll.zip
     pub static ref ZIP_METHOD: SMethod = SMethod::new(STypeCompanion::Coll, ZIP_METHOD_DESC.clone());
@@ -108,7 +111,8 @@ lazy_static! {
                 SType::SColl(SType::STypeVar(STypeVar::t()).into()),
             ],
             SType::SColl(SType::SInt.into())
-        )
+        ),
+        explicit_type_args: vec![]
     };
     /// Coll.indices
     pub static ref INDICES_METHOD: SMethod = SMethod::new(STypeCompanion::Coll, INDICES_METHOD_DESC.clone());
@@ -126,7 +130,8 @@ lazy_static! {
                 SType::SInt,
             ],
             SType::SColl(SType::STypeVar(STypeVar::t()).into())
-        )
+        ),
+        explicit_type_args: vec![]
     };
     /// Coll.patch
     pub static ref PATCH_METHOD: SMethod = SMethod::new(STypeCompanion::Coll, PATCH_METHOD_DESC.clone());
@@ -144,7 +149,8 @@ lazy_static! {
 
             ],
             SType::SColl(SType::STypeVar(STypeVar::t()).into())
-        )
+        ),
+        explicit_type_args: vec![]
     };
     /// Coll.updated
     pub static ref UPDATED_METHOD: SMethod = SMethod::new(STypeCompanion::Coll, UPDATED_METHOD_DESC.clone());
@@ -162,7 +168,8 @@ lazy_static! {
 
             ],
             SType::SColl(SType::STypeVar(STypeVar::t()).into())
-        )
+        ),
+        explicit_type_args: vec![]
     };
     /// Coll.updateMany
     pub static ref UPDATE_MANY_METHOD: SMethod = SMethod::new(STypeCompanion::Coll, UPDATE_MANY_METHOD_DESC.clone());
diff --git a/ergotree-ir/src/types/sglobal.rs b/ergotree-ir/src/types/sglobal.rs
index 143d0bc14..9e90c2e75 100644
--- a/ergotree-ir/src/types/sglobal.rs
+++ b/ergotree-ir/src/types/sglobal.rs
@@ -33,6 +33,7 @@ lazy_static! {
             t_range: SType::SGroupElement.into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
      /// GLOBAL.GroupGenerator
     pub static ref GROUP_GENERATOR_METHOD: SMethod = SMethod::new(STypeCompanion::Global, GROUP_GENERATOR_METHOD_DESC.clone(),);
@@ -52,6 +53,7 @@ lazy_static! {
             t_range: SType::SColl(SType::SByte.into()).into(),
             tpe_params: vec![],
         },
+        explicit_type_args: vec![]
     };
      /// GLOBAL.xor
     pub static ref XOR_METHOD: SMethod = SMethod::new(STypeCompanion::Global, XOR_METHOD_DESC.clone(),);
diff --git a/ergotree-ir/src/types/sgroup_elem.rs b/ergotree-ir/src/types/sgroup_elem.rs
index 15c8d3b16..f389f49ee 100644
--- a/ergotree-ir/src/types/sgroup_elem.rs
+++ b/ergotree-ir/src/types/sgroup_elem.rs
@@ -35,7 +35,8 @@ lazy_static! {
         tpe: SFunc::new(
             vec![SType::SGroupElement],
             SType::SColl(Arc::new(SType::SByte)),
-        )
+        ),
+        explicit_type_args: vec![]
     };
     /// GroupElement.geEncoded
     pub static ref GET_ENCODED_METHOD: SMethod = SMethod::new(STypeCompanion::GroupElem, GET_ENCODED_METHOD_DESC.clone(),);
@@ -48,7 +49,8 @@ lazy_static! {
         tpe: SFunc::new(
             vec![SType::SGroupElement],
             SType::SGroupElement,
-        )
+        ),
+        explicit_type_args: vec![]
     };
     /// GroupElement.negate
     pub static ref NEGATE_METHOD: SMethod = SMethod::new(STypeCompanion::GroupElem, NEGATE_METHOD_DESC.clone(),);
diff --git a/ergotree-ir/src/types/smethod.rs b/ergotree-ir/src/types/smethod.rs
index 7e050fc1f..f369904a3 100644
--- a/ergotree-ir/src/types/smethod.rs
+++ b/ergotree-ir/src/types/smethod.rs
@@ -32,7 +32,7 @@ impl MethodId {
 pub struct SMethod {
     /// Object type companion
     pub obj_type: STypeCompanion,
-    method_raw: SMethodDesc,
+    pub(crate) method_raw: SMethodDesc,
 }
 
 impl SMethod {
@@ -100,6 +100,8 @@ pub struct SMethodDesc {
     pub(crate) name: &'static str,
     pub(crate) method_id: MethodId,
     pub(crate) tpe: SFunc,
+    // Typevars that cannot be inferred from arguments. For example Box.getReg[T](4), T can not be inferred thus must be explicitly serialized
+    pub(crate) explicit_type_args: Vec<STypeVar>,
 }
 
 impl SMethodDesc {
@@ -118,6 +120,7 @@ impl SMethodDesc {
                 t_range: res_tpe.into(),
                 tpe_params: vec![],
             },
+            explicit_type_args: vec![], // TODO: check if PropertyCalls need explicit type args as well
         }
     }
     pub(crate) fn as_method(&self, obj_type: STypeCompanion) -> SMethod {
diff --git a/ergotree-ir/src/types/soption.rs b/ergotree-ir/src/types/soption.rs
index 1d8464c06..2a3dccb08 100644
--- a/ergotree-ir/src/types/soption.rs
+++ b/ergotree-ir/src/types/soption.rs
@@ -42,6 +42,7 @@ lazy_static! {
                 ],
             SType::SOption(SType::STypeVar(STypeVar::ov()).into()),
         ),
+        explicit_type_args: vec![]
     };
     /// Option.map
     pub static ref MAP_METHOD: SMethod = SMethod::new(
@@ -63,6 +64,7 @@ lazy_static! {
                 ],
             SType::SOption(SType::STypeVar(STypeVar::iv()).into()),
         ),
+        explicit_type_args: vec![]
     };
     /// Option.map
     pub static ref FILTER_METHOD: SMethod = SMethod::new(
diff --git a/sigma-ser/src/scorex_serialize.rs b/sigma-ser/src/scorex_serialize.rs
index f43ed2874..fc43fc2b0 100644
--- a/sigma-ser/src/scorex_serialize.rs
+++ b/sigma-ser/src/scorex_serialize.rs
@@ -1,5 +1,5 @@
-use std::convert::TryInto;
 use std::io;
+use std::{convert::TryInto, io::Cursor};
 
 use crate::vlq_encode;
 use crate::vlq_encode::*;
@@ -111,8 +111,8 @@ pub trait ScorexSerializable: Sized {
         Ok(w)
     }
     /// Parse `self` from the bytes
-    fn scorex_parse_bytes(mut bytes: &[u8]) -> Result<Self, ScorexParsingError> {
-        Self::scorex_parse(&mut bytes)
+    fn scorex_parse_bytes(bytes: &[u8]) -> Result<Self, ScorexParsingError> {
+        Self::scorex_parse(&mut Cursor::new(bytes))
     }
 }
 
@@ -182,7 +182,7 @@ impl<T: ScorexSerializable> ScorexSerializable for Option<Box<T>> {
 pub fn scorex_serialize_roundtrip<T: ScorexSerializable>(v: &T) -> T {
     let mut data = Vec::new();
     v.scorex_serialize(&mut data).expect("serialization failed");
-    let reader = &mut &data[..];
+    let reader = &mut Cursor::new(&data[..]);
     T::scorex_parse(reader).expect("parse failed")
 }
 
diff --git a/sigma-ser/src/vlq_encode.rs b/sigma-ser/src/vlq_encode.rs
index ad14abfad..c8f22dff2 100644
--- a/sigma-ser/src/vlq_encode.rs
+++ b/sigma-ser/src/vlq_encode.rs
@@ -159,7 +159,7 @@ impl<W: io::Write + ?Sized> WriteSigmaVlqExt for W {}
 /// Read and decode values using VLQ (+ ZigZag for signed values) encoded and written with [`WriteSigmaVlqExt`]
 /// for VLQ see <https://en.wikipedia.org/wiki/Variable-length_quantity> (GLE)
 /// for ZigZag see <https://developers.google.com/protocol-buffers/docs/encoding#types>
-pub trait ReadSigmaVlqExt: io::Read {
+pub trait ReadSigmaVlqExt: io::Read + io::Seek {
     /// Read i8 without decoding
     fn get_i8(&mut self) -> Result<i8, io::Error> {
         Self::get_u8(self).map(|v| v as i8)
@@ -256,7 +256,7 @@ pub trait ReadSigmaVlqExt: io::Read {
 }
 
 /// Mark all types implementing `Read` as implementing the extension.
-impl<R: io::Read + ?Sized> ReadSigmaVlqExt for R {}
+impl<R: io::Read + io::Seek + ?Sized> ReadSigmaVlqExt for R {}
 
 #[allow(clippy::unwrap_used)]
 #[cfg(test)]


Test patch: