From 06cab26817c3f294dc0e1caedd15ecce62d5c5aa Mon Sep 17 00:00:00 2001 From: Kamal Ahmad Date: Wed, 25 Sep 2024 21:59:09 +0500 Subject: [PATCH] Add versioning for getReg, change max script version to 3 --- ergotree-interpreter/src/eval.rs | 16 +++++++ ergotree-interpreter/src/eval/context.rs | 6 ++- ergotree-interpreter/src/eval/error.rs | 14 ++++-- ergotree-interpreter/src/eval/method_call.rs | 35 -------------- ergotree-interpreter/src/eval/sbox.rs | 50 +++++++++++++++++--- ergotree-ir/src/ergo_tree/tree_header.rs | 20 ++++---- ergotree-ir/src/types/sbox.rs | 1 + 7 files changed, 89 insertions(+), 53 deletions(-) diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs index a042320d5..80eb1d597 100644 --- a/ergotree-interpreter/src/eval.rs +++ b/ergotree-interpreter/src/eval.rs @@ -403,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/method_call.rs b/ergotree-interpreter/src/eval/method_call.rs index 9c5d875ba..4902b527a 100644 --- a/ergotree-interpreter/src/eval/method_call.rs +++ b/ergotree-interpreter/src/eval/method_call.rs @@ -19,38 +19,3 @@ impl Evaluable for MethodCall { smethod_eval_fn(&self.method)?(&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(0i32).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() - ); - } -} diff --git a/ergotree-interpreter/src/eval/sbox.rs b/ergotree-interpreter/src/eval/sbox.rs index e09b457fc..ea7e857a2 100644 --- a/ergotree-interpreter/src/eval/sbox.rs +++ b/ergotree-interpreter/src/eval/sbox.rs @@ -3,6 +3,7 @@ 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; @@ -16,7 +17,13 @@ pub(crate) static VALUE_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| { )) }; -pub(crate) static GET_REG_EVAL_FN: EvalFn = |mc, _env, _ctx, obj, 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() @@ -69,22 +76,24 @@ pub(crate) static TOKENS_EVAL_FN: EvalFn = |_mc, _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::mir::value::Value; 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, try_eval_out}; + use crate::eval::tests::{eval_out, try_eval_out_with_version}; + use crate::eval::EvalError; #[test] fn eval_box_value() { @@ -107,7 +116,6 @@ mod tests { ); } - // Attempt to extract SigmaProp from register of type SByte #[test] fn eval_reg_out() { let type_args = std::iter::once((STypeVar::t(), SType::SLong)).collect(); @@ -120,7 +128,19 @@ mod tests { .unwrap() .into(); let ctx = force_any_val::(); - try_eval_out::(&expr, &ctx).unwrap(); + (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 @@ -136,6 +156,24 @@ mod tests { .unwrap() .into(); let ctx = force_any_val::(); - assert!(try_eval_out::(&expr, &ctx).is_err()); + (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-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/types/sbox.rs b/ergotree-ir/src/types/sbox.rs index 8720547d5..b898604b0 100644 --- a/ergotree-ir/src/types/sbox.rs +++ b/ergotree-ir/src/types/sbox.rs @@ -84,6 +84,7 @@ lazy_static! { SMethod::new( STypeCompanion::Box,TOKENS_METHOD_DESC.clone(),); } +#[allow(clippy::unwrap_used)] #[cfg(test)] mod tests { use crate::{