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> + 'static>( + expr: &Expr, + ctx: &'ctx Context<'ctx>, + version: u8, + ) -> Result { + 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::() + .map_err(EvalError::TryExtractFrom) + }) + } + pub fn try_eval_out_wo_ctx> + 'static>( expr: &Expr, ) -> Result { 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 = BoundedVec; /// 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, + pub error: Box, /// 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::(&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::(); + assert!(try_eval_out::(&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, 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::(); - assert_eq!( - eval_out::(&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, 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::()?; 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::()?; 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::()?; 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::()?; 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::()?; 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::()?; 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::()?; 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::()?; 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::()?; 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::()?; 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::()?; 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::()?; 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::()?; 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::()?; 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::()?; 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::>()?.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::()?; + .try_extract_into::()? + .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::>()? - .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::>()? + .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::>()? .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::(); + (0..ErgoTreeVersion::V6_SOFT_FORK_VERSION).for_each(|version| { + assert!(try_eval_out_with_version::(&expr, &ctx, version).is_err()) + }); + (ErgoTreeVersion::V6_SOFT_FORK_VERSION..=ErgoTreeVersion::MAX_SCRIPT_VERSION).for_each( + |version| { + assert_eq!( + try_eval_out_with_version::>(&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::(); + (0..ErgoTreeVersion::V6_SOFT_FORK_VERSION).for_each(|version| { + let res = try_eval_out_with_version::>(&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::>(&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 = 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 = 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::
()?; 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::
()?; Ok(Into::>::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::
()?; Ok(Into::>::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::
()?; Ok(Into::>::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::
()?; Ok(Into::>::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::
()?; Ok(Into::>::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::
()?; Ok(Into::>::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::
()?; 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::
()?; 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::
()?; 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::
()?; 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::
()?; 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::
()?; 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::
()?; 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::
()?; Ok(Into::>::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::()?; 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::()?; Ok(Into::>::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::()?; 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::()?; 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::()?; 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::()?; 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::()?; Ok(Into::>::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: &mut R) -> Result { + 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 { - let wrap_in_ergotree = |r: Result| -> 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 = 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 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 { - 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 { - 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 { - 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, + /// Arguments that cannot be inferred from function signature, such as Box.getReg[T]() + pub explicit_type_args: HashMap, } impl MethodCall { - /// Create new object, returns an error if any of the requirements failed - pub fn new(obj: Expr, method: SMethod, args: Vec) -> Result { + fn new_inner( + method: SMethod, + args: Vec, + obj: Expr, + explicit_type_args: HashMap, + ) -> Result { 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 = vec![obj.tpe()]; let arg_types: Vec = 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) -> Result { + 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, + type_args: HashMap, + ) -> Result { + 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::::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::, _>>()?; + 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 { @@ -70,6 +71,20 @@ impl Read for SigmaByteReader { } } +impl Seek for SigmaByteReader { + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { + self.inner.seek(pos) + } + + fn rewind(&mut self) -> std::io::Result<()> { + self.inner.rewind() + } + + fn stream_position(&mut self) -> std::io::Result { + self.inner.stream_position() + } +} + impl SigmaByteRead for SigmaByteReader { 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, } 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::scorex_parse(&mut bytes) + fn scorex_parse_bytes(bytes: &[u8]) -> Result { + Self::scorex_parse(&mut Cursor::new(bytes)) } } @@ -182,7 +182,7 @@ impl ScorexSerializable for Option> { pub fn scorex_serialize_roundtrip(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 WriteSigmaVlqExt for W {} /// Read and decode values using VLQ (+ ZigZag for signed values) encoded and written with [`WriteSigmaVlqExt`] /// for VLQ see (GLE) /// for ZigZag see -pub trait ReadSigmaVlqExt: io::Read { +pub trait ReadSigmaVlqExt: io::Read + io::Seek { /// Read i8 without decoding fn get_i8(&mut self) -> Result { 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 ReadSigmaVlqExt for R {} +impl ReadSigmaVlqExt for R {} #[allow(clippy::unwrap_used)] #[cfg(test)]