diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs index dcd87ea25..72e484c49 100644 --- a/ergotree-interpreter/src/eval.rs +++ b/ergotree-interpreter/src/eval.rs @@ -12,6 +12,7 @@ use ergotree_ir::mir::value::Value; use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean; use ergotree_ir::types::smethod::SMethod; +use ergotree_ir::types::stype::SType; use self::env::Env; use ergotree_ir::chain::context::Context; @@ -149,6 +150,28 @@ fn validate_self_extension_key_domain(ctx: &Context) -> Result<(), EvalError> { Ok(()) } +/// Mirror of sigma-state `SType.isValueOfType`'s reachable rejections +/// (`SType.scala:200-205`), invoked by the JVM via `Value.checkType` at the +/// Tuple-eval items (`values.scala:801/804`) and `ConstantPlaceholder.eval` +/// (`values.scala:412`): a tuple type must be a pair (arity 2) and a function +/// type must be unary, otherwise the JVM throws "Unsupported tuple/function +/// type". sigma-rust models flat N-ary tuples (`TupleItems` = `BoundedVec<2,255>`) +/// so an arity≠2 tuple constant/value is representable and would otherwise +/// evaluate where the JVM rejects (a consensus accept/reject divergence). The +/// remaining `isValueOfType` arms only assert a value matches its type, which is +/// an invariant of sigma-rust's typed `Value`s, so they need no run-time check. +pub(crate) fn check_value_type(tpe: &SType) -> Result<(), EvalError> { + match tpe { + SType::STuple(t) if t.items.len() != 2 => { + Err(EvalError::Misc(format!("Unsupported tuple type {tpe:?}"))) + } + SType::SFunc(f) if f.t_dom.len() != 1 => Err(EvalError::Misc(format!( + "Unsupported function type {tpe:?}" + ))), + _ => Ok(()), + } +} + /// Evaluate the given expression by reducing it to SigmaBoolean value. pub fn reduce_to_crypto(tree: &ErgoTree, ctx: &Context) -> Result { // The JVM rejects a `>= 0x80` self-extension key at context construction, diff --git a/ergotree-interpreter/src/eval/tuple.rs b/ergotree-interpreter/src/eval/tuple.rs index 194f5a100..9add3c3ca 100644 --- a/ergotree-interpreter/src/eval/tuple.rs +++ b/ergotree-interpreter/src/eval/tuple.rs @@ -27,7 +27,18 @@ impl Evaluable for Tuple { self.items.len() ))); } - let items_v = self.items.try_mapped_ref(|i| i.eval(env, ctx)); + let items_v = self + .items + .try_mapped_ref(|i| -> Result, EvalError> { + // Mirror the JVM Tuple eval, which `checkType`s each item BEFORE + // evaluating it (values.scala:801/804): an item whose type is an + // unsupported tuple (arity != 2) is rejected ("Unsupported tuple + // type", SType.scala:200). Catches an arity-3 tuple constant carried + // as a pair item, which the JVM refuses but sigma-rust would + // otherwise evaluate. + crate::eval::check_value_type(&i.tpe())?; + i.eval(env, ctx) + }); Ok(Value::Tup(items_v?)) } } @@ -69,4 +80,40 @@ mod tests { "arity-3 Tuple must be rejected at eval, got {res:?}" ); } + + #[test] + fn eval_rejects_arity3_tuple_item_constant() { + use crate::eval::test_util::try_eval_out_wo_ctx; + use ergotree_ir::mir::constant::{Constant, Literal}; + use ergotree_ir::types::stuple::{STuple, TupleItems}; + use ergotree_ir::types::stype::SType; + + // SANTA Tuple.checkType_unsupported (inline): a pair Tuple whose item0 is + // an arity-3 (Bool,Bool,Bool) tuple CONSTANT. The JVM `checkType`s each + // Tuple item (values.scala:801/804) → arity != 2 → "Unsupported tuple + // type" (SType.scala:200-202). sigma-rust models flat N-ary tuples, so the + // value is representable; it must be rejected at eval to match the JVM. + let triple = Constant { + tpe: SType::STuple(STuple::triple( + SType::SBoolean, + SType::SBoolean, + SType::SBoolean, + )), + v: Literal::Tup( + TupleItems::try_from(vec![ + Literal::Boolean(true), + Literal::Boolean(true), + Literal::Boolean(true), + ]) + .unwrap(), + ), + }; + let tuple: Expr = Tuple::new(vec![Expr::Const(triple), 1i32.into()]) + .unwrap() + .into(); + assert!( + try_eval_out_wo_ctx::(&tuple).is_err(), + "arity-3 tuple item constant must be rejected at eval (Unsupported tuple type)" + ); + } }