diff --git a/Cargo.toml b/Cargo.toml index 202b75360..4c49a8063 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ members = [ "bindings/ergo-lib-wasm", "bindings/ergo-lib-c-core", "bindings/ergo-lib-c", - "bindings/ergo-lib-jni" + "bindings/ergo-lib-jni", ] [workspace.package] @@ -37,7 +37,7 @@ ergo-merkle-tree = { version = "^0.11.1", path = "./ergo-merkle-tree" } ergo-rest = { version = "^0.9.1", path = "./ergo-rest" } ergo-lib = { version = "^0.24.1", path = "./ergo-lib"} k256 = { version = "0.13.1", features = ["arithmetic", "ecdsa"] } -elliptic-curve = {version = "0.12", features = [ "ff"]} +elliptic-curve = { version = "0.12", features = ["ff"] } thiserror = "1" bounded-vec = { version = "^0.7.0" } bitvec = { version = "1.0.1" } @@ -52,9 +52,12 @@ lazy_static = "1.4" bs58 = "0.4.0" base16 = "0.2.1" base64 = "0.13.0" -indexmap = {version ="1.3.2", features = ["serde"]} +indexmap = { version = "1.3.2", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } -serde_json = { version = "1.0", features = ["arbitrary_precision", "preserve_order"] } +serde_json = { version = "1.0", features = [ + "arbitrary_precision", + "preserve_order", +] } serde_with = { version = "1.9.1", features = ["json"] } rand = "0.8.5" bytes = "1.1" @@ -67,13 +70,14 @@ bounded-integer = { version = "^0.5", features = ["types"] } url = "2.2" getrandom = { version = "0.2.7" } itertools = "0.10.3" +miette = { version = "5", features = ["fancy"] } # dev-dependencies proptest = { version = "=1.0", default-features = false, features = ["std"] } proptest-derive = "0.3" pretty_assertions = "1.3" wasm-bindgen-test = "0.3.37" -expect-test = "1.0.1" +expect-test = "1.4.1" [profile.release] # Tell `rustc` to optimize for small code size. diff --git a/ergo-chain-types/src/ec_point.rs b/ergo-chain-types/src/ec_point.rs index 97b89c45d..f71f4e4d6 100644 --- a/ergo-chain-types/src/ec_point.rs +++ b/ergo-chain-types/src/ec_point.rs @@ -28,6 +28,15 @@ impl std::fmt::Debug for EcPoint { } } +#[allow(clippy::unwrap_used)] +impl std::fmt::Display for EcPoint { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str(&base16::encode_lower( + &self.scorex_serialize_bytes().unwrap(), + )) + } +} + impl EcPoint { /// Number of bytes to represent any group element as byte array pub const GROUP_SIZE: usize = 33; diff --git a/ergo-lib/src/chain/transaction/reduced.rs b/ergo-lib/src/chain/transaction/reduced.rs index 4e2c542b9..c3a19a98c 100644 --- a/ergo-lib/src/chain/transaction/reduced.rs +++ b/ergo-lib/src/chain/transaction/reduced.rs @@ -123,7 +123,11 @@ impl SigmaSerializable for ReducedTransaction { let cost = r.get_u64()?; let extension = input.spending_proof.extension; let reduced_input = ReducedInput { - reduction_result: ReductionResult { sigma_prop, cost }, + reduction_result: ReductionResult { + sigma_prop, + cost, + env: Env::empty(), + }, extension: extension.clone(), }; let unsigned_input = UnsignedInput { @@ -167,6 +171,7 @@ pub mod arbitrary { reduction_result: ReductionResult { sigma_prop: sb.clone(), cost: 0, + env: Env::empty(), }, extension: unsigned_input.extension, }), diff --git a/ergoscript-compiler/src/mir/lower.rs b/ergoscript-compiler/src/mir/lower.rs index c4540679d..5ddfc8bd4 100644 --- a/ergoscript-compiler/src/mir/lower.rs +++ b/ergoscript-compiler/src/mir/lower.rs @@ -110,19 +110,25 @@ mod tests { check( "HEIGHT + HEIGHT", expect![[r#" - BinOp( - BinOp { - kind: Arith( - Plus, - ), - left: GlobalVars( - Height, - ), - right: GlobalVars( - Height, - ), - }, - )"#]], + BinOp( + Spanned { + source_span: SourceSpan { + offset: 0, + length: 0, + }, + expr: BinOp { + kind: Arith( + Plus, + ), + left: GlobalVars( + Height, + ), + right: GlobalVars( + Height, + ), + }, + }, + )"#]], ) } @@ -154,16 +160,22 @@ mod tests { "4+2", expect![[r#" BinOp( - BinOp { - kind: Arith( - Plus, - ), - left: Const( - "4: SInt", - ), - right: Const( - "2: SInt", - ), + Spanned { + source_span: SourceSpan { + offset: 0, + length: 0, + }, + expr: BinOp { + kind: Arith( + Plus, + ), + left: Const( + "4: SInt", + ), + right: Const( + "2: SInt", + ), + }, }, )"#]], ); @@ -174,19 +186,25 @@ mod tests { check( "4L+2L", expect![[r#" - BinOp( - BinOp { - kind: Arith( - Plus, - ), - left: Const( - "4: SLong", - ), - right: Const( - "2: SLong", - ), - }, - )"#]], + BinOp( + Spanned { + source_span: SourceSpan { + offset: 0, + length: 0, + }, + expr: BinOp { + kind: Arith( + Plus, + ), + left: Const( + "4: SLong", + ), + right: Const( + "2: SLong", + ), + }, + }, + )"#]], ); } } diff --git a/ergotree-interpreter/Cargo.toml b/ergotree-interpreter/Cargo.toml index ce6934ac2..8009b1ee3 100644 --- a/ergotree-interpreter/Cargo.toml +++ b/ergotree-interpreter/Cargo.toml @@ -6,9 +6,7 @@ authors = ["Denys Zadorozhnyi "] repository.workspace = true edition.workspace = true description = "ErgoTree interpreter" -exclude = [ - "proptest-regressions/*" -] +exclude = ["proptest-regressions/*"] [lib] crate-type = ["cdylib", "rlib"] @@ -31,22 +29,29 @@ base16 = { workspace = true } proptest-derive = { workspace = true, optional = true } bytes = { workspace = true } num-bigint = { workspace = true } -bounded-vec = { workspace = true, features=["serde"] } +bounded-vec = { workspace = true, features = ["serde"] } serde = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } serde_with = { workspace = true, optional = true } -proptest = { workspace = true , optional = true } +proptest = { workspace = true, optional = true } scorex_crypto_avltree = "0.1.0" gf2_192 = { version = "^0.24.1", path = "../gf2_192" } +miette = { workspace = true } [features] default = ["json"] json = ["serde", "serde_json", "serde_with", "bounded-vec/serde"] -arbitrary = ["proptest", "proptest-derive", "ergotree-ir/arbitrary", "ergo-chain-types/arbitrary", "gf2_192/arbitrary"] +arbitrary = [ + "proptest", + "proptest-derive", + "ergotree-ir/arbitrary", + "ergo-chain-types/arbitrary", + "gf2_192/arbitrary", +] [dev-dependencies] ergotree-ir = { workspace = true, features = ["arbitrary"] } ergoscript-compiler = { workspace = true } proptest = { workspace = true } sigma-test-util = { workspace = true } - +expect-test = { workspace = true } diff --git a/ergotree-interpreter/src/contracts.rs b/ergotree-interpreter/src/contracts.rs index a1c27154f..b3e47cc07 100644 --- a/ergotree-interpreter/src/contracts.rs +++ b/ergotree-interpreter/src/contracts.rs @@ -213,11 +213,9 @@ mod tests { let encoder = AddressEncoder::new(NetworkPrefix::Mainnet); let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap(); assert!(addr.script().unwrap().proposition().is_ok()); - let _script = addr.script().unwrap().proposition().unwrap(); + // let script = addr.script().unwrap().proposition().unwrap(); // dbg!(&script); - // let res: bool = eval_out_wo_ctx::(script.as_ref()) - // .try_into() - // .unwrap(); + // let res: bool = eval_out_wo_ctx::(&script).try_into().unwrap(); // assert!(!res); } @@ -227,12 +225,9 @@ mod tests { let p2s_addr_str = "cLPHJ3MHuKAHoCUwGhcEFw5sWJqvPwFyKxTRj1aUoMwgAz78Fg3zLXRhBup9Te1WLau1gZXNmXvUmeXGCd7QLeqB7ArrT3v5cg26piEtqymM6j2SkgYVCobgoAGKeTf6nMLxv1uVrLdjt1GnPxG1MuWj7Es7Dfumotbx9YEaxwqtTUC5SKsJc9LCpAmNWRAQbU6tVVEvmfwWivrGoZ3L5C4DMisxN3U"; let encoder = AddressEncoder::new(NetworkPrefix::Mainnet); let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap(); - let _script = addr.script().unwrap().proposition().unwrap(); - // dbg!(&script); - // let res: bool = eval_out_wo_ctx::(script.as_ref()) - // .try_into() - // .unwrap(); - // assert!(!res); + let _ = addr.script().unwrap().proposition().unwrap(); + // let ctx = Rc::new(force_any_val::()); + // let _ = reduce_to_crypto(&script, &Env::empty(), ctx).unwrap(); } #[test] @@ -284,6 +279,15 @@ mod tests { let encoder = AddressEncoder::new(NetworkPrefix::Mainnet); let addr = encoder.parse_address_from_str(p2s_str).unwrap(); - let _script = addr.script().unwrap().proposition().unwrap(); + let _ = addr.script().unwrap().proposition().unwrap(); + // let ctx = Rc::new(force_any_val::()); + // let res = reduce_to_crypto(&script, &Env::empty(), ctx).unwrap(); + // match res.sigma_prop { + // SigmaBoolean::TrivialProp(b) => assert!(b), + // SigmaBoolean::ProofOfKnowledge(_) => { + // todo!() + // } + // SigmaBoolean::SigmaConjecture(_) => todo!(), + // } } } diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs index aefeda105..84c82474f 100644 --- a/ergotree-interpreter/src/eval.rs +++ b/ergotree-interpreter/src/eval.rs @@ -1,25 +1,18 @@ //! Interpreter -use bounded_vec::BoundedVecOutOfBounds; use ergotree_ir::mir::constant::TryExtractInto; +use ergotree_ir::pretty_printer::PosTrackingWriter; +use ergotree_ir::pretty_printer::Print; use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProp; -use sigma_ser::ScorexParsingError; -use sigma_ser::ScorexSerializationError; use std::rc::Rc; -use ergotree_ir::ergo_tree::ErgoTreeError; -use ergotree_ir::mir::constant::TryExtractFromError; use ergotree_ir::mir::expr::Expr; use ergotree_ir::mir::value::Value; -use ergotree_ir::serialization::SigmaParsingError; -use ergotree_ir::serialization::SigmaSerializationError; use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean; use cost_accum::CostAccumulator; use ergotree_ir::types::smethod::SMethod; -use thiserror::Error; use self::context::Context; -use self::cost_accum::CostError; use self::env::Env; /// Context(blockchain) for the interpreter @@ -57,6 +50,7 @@ pub(crate) mod decode_point; mod deserialize_context; mod deserialize_register; pub(crate) mod downcast; +mod error; pub(crate) mod exponentiate; pub(crate) mod expr; pub(crate) mod extract_amount; @@ -101,61 +95,7 @@ pub(crate) mod val_use; pub(crate) mod xor; pub(crate) mod xor_of; -/// Interpreter errors -#[derive(Error, PartialEq, Eq, Debug, Clone)] -pub enum EvalError { - /// AVL tree errors - #[error("AvlTree: {0}")] - AvlTree(String), - /// Only boolean or SigmaBoolean is a valid result expr type - #[error("Only boolean or SigmaBoolean is a valid result expr type")] - InvalidResultType, - /// Unexpected Expr encountered during the evaluation - #[error("Unexpected Expr: {0}")] - UnexpectedExpr(String), - /// Error on cost calculation - #[error("Error on cost calculation: {0:?}")] - CostError(#[from] CostError), - /// Unexpected value type - #[error("Unexpected value type: {0:?}")] - TryExtractFrom(#[from] TryExtractFromError), - /// Not found (missing value, argument, etc.) - #[error("Not found: {0}")] - NotFound(String), - /// Register id out of bounds - #[error("{0}")] - RegisterIdOutOfBounds(String), - /// Unexpected value - #[error("Unexpected value: {0}")] - UnexpectedValue(String), - /// Arithmetic exception error - #[error("Arithmetic exception: {0}")] - ArithmeticException(String), - /// Misc error - #[error("error: {0}")] - Misc(String), - /// Sigma serialization error - #[error("Serialization error: {0}")] - SigmaSerializationError(#[from] SigmaSerializationError), - /// Sigma serialization parsing error - #[error("Serialization parsing error: {0}")] - SigmaParsingError(#[from] SigmaParsingError), - /// ErgoTree error - #[error("ErgoTree error: {0}")] - ErgoTreeError(#[from] ErgoTreeError), - /// Not yet implemented - #[error("evaluation is not yet implemented: {0}")] - NotImplementedYet(&'static str), - /// Invalid item quantity for BoundedVec - #[error("Invalid item quantity for BoundedVec: {0}")] - BoundedVecError(#[from] BoundedVecOutOfBounds), - /// Scorex serialization error - #[error("Serialization error: {0}")] - ScorexSerializationError(#[from] ScorexSerializationError), - /// Scorex serialization parsing error - #[error("Serialization parsing error: {0}")] - ScorexParsingError(#[from] ScorexParsingError), -} +pub use error::EvalError; /// Result of expression reduction procedure (see `reduce_to_crypto`). #[derive(PartialEq, Eq, Debug, Clone)] @@ -164,6 +104,8 @@ pub struct ReductionResult { pub sigma_prop: SigmaBoolean, /// estimated cost of expression evaluation pub cost: u64, + /// environment after the evaluation + pub env: Env, } /// Evaluate the given expression by reducing it to SigmaBoolean value. @@ -172,22 +114,40 @@ pub fn reduce_to_crypto( env: &Env, ctx: Rc, ) -> Result { - let cost_accum = CostAccumulator::new(0, None); - let mut ectx = EvalContext::new(ctx, cost_accum); - expr.eval(env, &mut ectx) - .and_then(|v| -> Result { - match v { - Value::Boolean(b) => Ok(ReductionResult { - sigma_prop: SigmaBoolean::TrivialProp(b), - cost: 0, - }), - Value::SigmaProp(sp) => Ok(ReductionResult { - sigma_prop: sp.value().clone(), - cost: 0, - }), - _ => Err(EvalError::InvalidResultType), - } - }) + let ctx_clone = ctx.clone(); + fn inner(expr: &Expr, env: &Env, ctx: Rc) -> Result { + let cost_accum = CostAccumulator::new(0, None); + let mut ectx = EvalContext::new(ctx, cost_accum); + let mut env_mut = env.clone(); + expr.eval(&mut env_mut, &mut ectx) + .and_then(|v| -> Result { + match v { + Value::Boolean(b) => Ok(ReductionResult { + sigma_prop: SigmaBoolean::TrivialProp(b), + cost: 0, + env: env_mut.clone(), + }), + Value::SigmaProp(sp) => Ok(ReductionResult { + sigma_prop: sp.value().clone(), + cost: 0, + env: env_mut.clone(), + }), + _ => Err(EvalError::InvalidResultType), + } + }) + } + + let res = inner(expr, env, ctx); + if res.is_ok() { + return res; + } + let mut printer = PosTrackingWriter::new(); + let spanned_expr = expr + .print(&mut printer) + .map_err(|e| EvalError::Misc(format!("printer error: {}", e)))?; + let printed_expr_str = printer.get_buf(); + inner(&spanned_expr, env, ctx_clone) + .map_err(|e| e.wrap_spanned_with_src(printed_expr_str.to_string())) } /// Expects SigmaProp constant value and returns it's value. Otherwise, returns an error. @@ -214,10 +174,11 @@ impl EvalContext { /// Should be implemented by every node that can be evaluated. pub(crate) trait Evaluable { /// Evaluation routine to be implement by each node - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result; + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result; } -type EvalFn = fn(env: &Env, ctx: &mut EvalContext, Value, Vec) -> Result; +type EvalFn = + fn(env: &mut Env, ctx: &mut EvalContext, Value, Vec) -> Result; fn smethod_eval_fn(method: &SMethod) -> Result { use ergotree_ir::types::*; @@ -387,7 +348,8 @@ pub(crate) mod tests { pub fn eval_out>(expr: &Expr, ctx: Rc) -> T { let cost_accum = CostAccumulator::new(0, None); let mut ectx = EvalContext::new(ctx, cost_accum); - expr.eval(&Env::empty(), &mut ectx) + let mut env = Env::empty(); + expr.eval(&mut env, &mut ectx) .unwrap() .try_extract_into::() .unwrap() @@ -399,7 +361,8 @@ pub(crate) mod tests { ) -> Result { let cost_accum = CostAccumulator::new(0, None); let mut ectx = EvalContext::new(ctx, cost_accum); - expr.eval(&Env::empty(), &mut ectx) + let mut env = Env::empty(); + expr.eval(&mut env, &mut ectx) .and_then(|v| v.try_extract_into::().map_err(EvalError::TryExtractFrom)) } diff --git a/ergotree-interpreter/src/eval/and.rs b/ergotree-interpreter/src/eval/and.rs index 668520bb5..882227600 100644 --- a/ergotree-interpreter/src/eval/and.rs +++ b/ergotree-interpreter/src/eval/and.rs @@ -8,7 +8,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for And { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; let input_v_bools = input_v.try_extract_into::>()?; Ok(input_v_bools.iter().all(|b| *b).into()) diff --git a/ergotree-interpreter/src/eval/apply.rs b/ergotree-interpreter/src/eval/apply.rs index ceefbe8ea..9a3c93352 100644 --- a/ergotree-interpreter/src/eval/apply.rs +++ b/ergotree-interpreter/src/eval/apply.rs @@ -8,7 +8,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for Apply { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let func_v = self.func.eval(env, ctx)?; let args_v_res: Result, EvalError> = self.args.iter().map(|arg| arg.eval(env, ctx)).collect(); @@ -16,11 +16,10 @@ impl Evaluable for Apply { match func_v { Value::Lambda(fv) => { let arg_ids: Vec = fv.args.iter().map(|a| a.idx).collect(); - let mut cur_env = env.clone(); arg_ids.iter().zip(args_v).for_each(|(idx, arg_v)| { - cur_env.insert(*idx, arg_v); + env.insert(*idx, arg_v); }); - fv.body.eval(&cur_env, ctx) + fv.body.eval(env, ctx) } _ => Err(EvalError::UnexpectedValue(format!( "expected func_v to be Value::FuncValue got: {0:?}", @@ -54,31 +53,37 @@ mod tests { #[test] fn eval_user_defined_func_call() { let arg = Expr::Const(1i32.into()); - let bin_op = Expr::BinOp(BinOp { - kind: RelationOp::Eq.into(), - left: Box::new( - ValUse { - val_id: 1.into(), - tpe: SType::SInt, - } - .into(), - ), - right: Box::new( - ValUse { - val_id: 2.into(), - tpe: SType::SInt, + let bin_op = Expr::BinOp( + BinOp { + kind: RelationOp::Eq.into(), + left: Box::new( + ValUse { + val_id: 1.into(), + tpe: SType::SInt, + } + .into(), + ), + right: Box::new( + ValUse { + val_id: 2.into(), + tpe: SType::SInt, + } + .into(), + ), + } + .into(), + ); + let body = Expr::BlockValue( + BlockValue { + items: vec![ValDef { + id: 2.into(), + rhs: Box::new(Expr::Const(1i32.into())), } - .into(), - ), - }); - let body = Expr::BlockValue(BlockValue { - items: vec![ValDef { - id: 2.into(), - rhs: Box::new(Expr::Const(1i32.into())), + .into()], + result: Box::new(bin_op), } - .into()], - result: Box::new(bin_op), - }); + .into(), + ); let apply: Expr = Apply::new( FuncValue::new( vec![FuncArg { diff --git a/ergotree-interpreter/src/eval/atleast.rs b/ergotree-interpreter/src/eval/atleast.rs index e15f31442..93b56ff36 100644 --- a/ergotree-interpreter/src/eval/atleast.rs +++ b/ergotree-interpreter/src/eval/atleast.rs @@ -14,7 +14,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for Atleast { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let bound_v = self.bound.eval(env, ctx)?; let input_v = self.input.eval(env, ctx)?; diff --git a/ergotree-interpreter/src/eval/bin_op.rs b/ergotree-interpreter/src/eval/bin_op.rs index 146c0387d..2a47f9ceb 100644 --- a/ergotree-interpreter/src/eval/bin_op.rs +++ b/ergotree-interpreter/src/eval/bin_op.rs @@ -171,7 +171,7 @@ where } impl Evaluable for BinOp { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { ctx.cost_accum.add(Costs::DEFAULT.eq_const_size)?; let lv = self.left.eval(env, ctx)?; // using closure to keep right value from evaluation (for lazy AND, OR, XOR) diff --git a/ergotree-interpreter/src/eval/bit_inversion.rs b/ergotree-interpreter/src/eval/bit_inversion.rs index 61bd51653..18541f474 100644 --- a/ergotree-interpreter/src/eval/bit_inversion.rs +++ b/ergotree-interpreter/src/eval/bit_inversion.rs @@ -7,7 +7,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for BitInversion { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; match input_v { Value::Byte(v) => Ok(Value::Byte(!v)), diff --git a/ergotree-interpreter/src/eval/block.rs b/ergotree-interpreter/src/eval/block.rs index 2562d66d3..dfe5b3c21 100644 --- a/ergotree-interpreter/src/eval/block.rs +++ b/ergotree-interpreter/src/eval/block.rs @@ -2,6 +2,7 @@ use ergotree_ir::mir::block::BlockValue; use ergotree_ir::mir::constant::TryExtractInto; use ergotree_ir::mir::val_def::ValDef; use ergotree_ir::mir::value::Value; +use ergotree_ir::source_span::Spanned; use crate::eval::env::Env; use crate::eval::EvalContext; @@ -9,14 +10,15 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for BlockValue { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { - let mut cur_env = env.clone(); + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { for i in &self.items { - let val_def = i.clone().try_extract_into::()?; - let v: Value = val_def.rhs.eval(&cur_env, ctx)?; - cur_env.insert(val_def.id, v); + // TODO: new try_extract_spanned_into? + let spanned_val_def = &i.clone().try_extract_into::>()?; + let val_def = spanned_val_def.expr(); + let v: Value = val_def.rhs.eval(env, ctx)?; + env.insert(val_def.id, v); } - self.result.eval(&cur_env, ctx) + self.result.eval(env, ctx) } } @@ -32,7 +34,7 @@ mod tests { #[test] fn ser_roundtrip(block in any::()) { - let e = Expr::BlockValue(block); + let e = Expr::BlockValue(block.into()); prop_assert_eq![sigma_serialize_roundtrip(&e), e]; } } diff --git a/ergotree-interpreter/src/eval/bool_to_sigma.rs b/ergotree-interpreter/src/eval/bool_to_sigma.rs index 591ce4760..3f249c052 100644 --- a/ergotree-interpreter/src/eval/bool_to_sigma.rs +++ b/ergotree-interpreter/src/eval/bool_to_sigma.rs @@ -10,7 +10,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for BoolToSigmaProp { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; let input_v_bool = input_v.try_extract_into::()?; Ok((SigmaProp::new(SigmaBoolean::TrivialProp(input_v_bool))).into()) diff --git a/ergotree-interpreter/src/eval/byte_array_to_bigint.rs b/ergotree-interpreter/src/eval/byte_array_to_bigint.rs index a31d3a751..695607cc4 100644 --- a/ergotree-interpreter/src/eval/byte_array_to_bigint.rs +++ b/ergotree-interpreter/src/eval/byte_array_to_bigint.rs @@ -12,7 +12,7 @@ use crate::eval::EvalError::UnexpectedValue; use crate::eval::Evaluable; impl Evaluable for ByteArrayToBigInt { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input = self.input.eval(env, ctx)?.try_extract_into::>()?; if input.is_empty() { return Err(UnexpectedValue( diff --git a/ergotree-interpreter/src/eval/byte_array_to_long.rs b/ergotree-interpreter/src/eval/byte_array_to_long.rs index e97ca35ed..bd2c3b8c0 100644 --- a/ergotree-interpreter/src/eval/byte_array_to_long.rs +++ b/ergotree-interpreter/src/eval/byte_array_to_long.rs @@ -9,7 +9,7 @@ use crate::eval::Evaluable; use ergotree_ir::mir::constant::TryExtractInto; impl Evaluable for ByteArrayToLong { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input = self.input.eval(env, ctx)?.try_extract_into::>()?; if input.len() < 8 { return Err(UnexpectedValue( diff --git a/ergotree-interpreter/src/eval/calc_blake2b256.rs b/ergotree-interpreter/src/eval/calc_blake2b256.rs index 3d5485ace..4c97ef5c6 100644 --- a/ergotree-interpreter/src/eval/calc_blake2b256.rs +++ b/ergotree-interpreter/src/eval/calc_blake2b256.rs @@ -11,7 +11,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for CalcBlake2b256 { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; match input_v.clone() { Value::Coll(CollKind::NativeColl(NativeColl::CollByte(coll_byte))) => { diff --git a/ergotree-interpreter/src/eval/calc_sha256.rs b/ergotree-interpreter/src/eval/calc_sha256.rs index 3d01d786e..3cefa834c 100644 --- a/ergotree-interpreter/src/eval/calc_sha256.rs +++ b/ergotree-interpreter/src/eval/calc_sha256.rs @@ -11,7 +11,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for CalcSha256 { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; match input_v.clone() { Value::Coll(CollKind::NativeColl(NativeColl::CollByte(coll_byte))) => { diff --git a/ergotree-interpreter/src/eval/coll_append.rs b/ergotree-interpreter/src/eval/coll_append.rs index 7b2d58144..9e0f5a423 100644 --- a/ergotree-interpreter/src/eval/coll_append.rs +++ b/ergotree-interpreter/src/eval/coll_append.rs @@ -35,7 +35,7 @@ fn extract_elem_tpe(inp: &Value) -> Result { } impl Evaluable for Append { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; let col2_v = self.col_2.eval(env, ctx)?; let input_elem_tpe = extract_elem_tpe(&input_v)?; @@ -111,7 +111,7 @@ mod tests { fn append_byte_array_and_byte() { let byte_coll: Constant = vec![1i8, 2i8].into(); let byte: Expr = Expr::Collection(Collection::new(SType::SByte, vec![3i8.into()]).unwrap()); - let expr: Expr = Expr::Append(Append::new(byte_coll.into(), byte).unwrap()); + let expr: Expr = Expr::Append(Append::new(byte_coll.into(), byte).unwrap().into()); assert_eq!(eval_out_wo_ctx::>(&expr), vec![1i8, 2, 3]); } } diff --git a/ergotree-interpreter/src/eval/coll_by_index.rs b/ergotree-interpreter/src/eval/coll_by_index.rs index a79683b59..e6f47ec53 100644 --- a/ergotree-interpreter/src/eval/coll_by_index.rs +++ b/ergotree-interpreter/src/eval/coll_by_index.rs @@ -8,7 +8,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for ByIndex { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; let index_v = self.index.eval(env, ctx)?; let normalized_input_vals: Vec = match input_v { diff --git a/ergotree-interpreter/src/eval/coll_exists.rs b/ergotree-interpreter/src/eval/coll_exists.rs index 4819d0b5a..2a97500c2 100644 --- a/ergotree-interpreter/src/eval/coll_exists.rs +++ b/ergotree-interpreter/src/eval/coll_exists.rs @@ -8,7 +8,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for Exists { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; let condition_v = self.condition.eval(env, ctx)?; let input_v_clone = input_v.clone(); @@ -19,8 +19,8 @@ impl Evaluable for Exists { "Exists: evaluated condition has empty arguments list".to_string(), ) })?; - let env1 = env.clone().extend(func_arg.idx, arg); - func_value.body.eval(&env1, ctx) + env.insert(func_arg.idx, arg); + func_value.body.eval(env, ctx) } _ => Err(EvalError::UnexpectedValue(format!( "expected Exists::condition to be Value::FuncValue got: {0:?}", diff --git a/ergotree-interpreter/src/eval/coll_filter.rs b/ergotree-interpreter/src/eval/coll_filter.rs index 28fc94d82..951adaf63 100644 --- a/ergotree-interpreter/src/eval/coll_filter.rs +++ b/ergotree-interpreter/src/eval/coll_filter.rs @@ -9,7 +9,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for Filter { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; let condition_v = self.condition.eval(env, ctx)?; let input_v_clone = input_v.clone(); @@ -20,8 +20,8 @@ impl Evaluable for Filter { "Filter: evaluated condition has empty arguments list".to_string(), ) })?; - let env1 = env.clone().extend(func_arg.idx, arg); - func_value.body.eval(&env1, ctx) + env.insert(func_arg.idx, arg); + func_value.body.eval(env, ctx) } _ => Err(EvalError::UnexpectedValue(format!( "expected Filter::condition to be Value::FuncValue got: {0:?}", diff --git a/ergotree-interpreter/src/eval/coll_fold.rs b/ergotree-interpreter/src/eval/coll_fold.rs index 6b3e10fc1..385870eaa 100644 --- a/ergotree-interpreter/src/eval/coll_fold.rs +++ b/ergotree-interpreter/src/eval/coll_fold.rs @@ -9,7 +9,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for Fold { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; let zero_v = self.zero.eval(env, ctx)?; let fold_op_v = self.fold_op.eval(env, ctx)?; @@ -20,8 +20,8 @@ impl Evaluable for Fold { .args .first() .ok_or_else(|| EvalError::NotFound("empty argument for fold op".to_string()))?; - let env1 = env.clone().extend(func_arg.idx, arg); - func_value.body.eval(&env1, ctx) + env.insert(func_arg.idx, arg); + func_value.body.eval(env, ctx) } _ => Err(EvalError::UnexpectedValue(format!( "expected fold_op to be Value::FuncValue got: {0:?}", @@ -97,11 +97,11 @@ mod tests { let fold_op_body: Expr = BinOp { kind: ArithOp::Plus.into(), left: Box::new(Expr::SelectField( - SelectField::new(tuple.clone(), 1.try_into().unwrap()).unwrap(), + SelectField::new(tuple.clone(), 1.try_into().unwrap()).unwrap().into(), )), right: Box::new(Expr::ExtractAmount( ExtractAmount::try_build(Expr::SelectField( - SelectField::new(tuple, 2.try_into().unwrap()).unwrap(), + SelectField::new(tuple, 2.try_into().unwrap()).unwrap().into(), )) .unwrap(), )), diff --git a/ergotree-interpreter/src/eval/coll_forall.rs b/ergotree-interpreter/src/eval/coll_forall.rs index 186ec5f45..0dec1520a 100644 --- a/ergotree-interpreter/src/eval/coll_forall.rs +++ b/ergotree-interpreter/src/eval/coll_forall.rs @@ -8,7 +8,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for ForAll { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; let condition_v = self.condition.eval(env, ctx)?; let input_v_clone = input_v.clone(); @@ -19,8 +19,8 @@ impl Evaluable for ForAll { "ForAll: evaluated condition has empty arguments list".to_string(), ) })?; - let env1 = env.clone().extend(func_arg.idx, arg); - func_value.body.eval(&env1, ctx) + env.insert(func_arg.idx, arg); + func_value.body.eval(env, ctx) } _ => Err(EvalError::UnexpectedValue(format!( "expected ForAll::condition to be Value::FuncValue got: {0:?}", diff --git a/ergotree-interpreter/src/eval/coll_map.rs b/ergotree-interpreter/src/eval/coll_map.rs index 9b76ada8b..6745d08f3 100644 --- a/ergotree-interpreter/src/eval/coll_map.rs +++ b/ergotree-interpreter/src/eval/coll_map.rs @@ -8,7 +8,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for Map { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; let mapper_v = self.mapper.eval(env, ctx)?; let input_v_clone = input_v.clone(); @@ -19,8 +19,8 @@ impl Evaluable for Map { "Map: evaluated mapper has empty arguments list".to_string(), ) })?; - let env1 = env.clone().extend(func_arg.idx, arg); - func_value.body.eval(&env1, ctx) + env.insert(func_arg.idx, arg); + func_value.body.eval(env, ctx) } _ => Err(EvalError::UnexpectedValue(format!( "expected mapper to be Value::FuncValue got: {0:?}", diff --git a/ergotree-interpreter/src/eval/coll_size.rs b/ergotree-interpreter/src/eval/coll_size.rs index 87db1e37c..2f8cb4614 100644 --- a/ergotree-interpreter/src/eval/coll_size.rs +++ b/ergotree-interpreter/src/eval/coll_size.rs @@ -7,7 +7,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for SizeOf { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; let normalized_input_vals: Vec = match input_v { Value::Coll(coll) => Ok(coll.as_vec()), diff --git a/ergotree-interpreter/src/eval/coll_slice.rs b/ergotree-interpreter/src/eval/coll_slice.rs index feef572f0..c39de608a 100644 --- a/ergotree-interpreter/src/eval/coll_slice.rs +++ b/ergotree-interpreter/src/eval/coll_slice.rs @@ -9,7 +9,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for Slice { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; let from_v = self.from.eval(env, ctx)?; let until_v = self.until.eval(env, ctx)?; diff --git a/ergotree-interpreter/src/eval/collection.rs b/ergotree-interpreter/src/eval/collection.rs index 185acad0d..e497e22f6 100644 --- a/ergotree-interpreter/src/eval/collection.rs +++ b/ergotree-interpreter/src/eval/collection.rs @@ -12,7 +12,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for Collection { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { Ok(match self { Collection::BoolConstants(bools) => bools.clone().into(), Collection::Exprs { elem_tpe, items } => { diff --git a/ergotree-interpreter/src/eval/create_avl_tree.rs b/ergotree-interpreter/src/eval/create_avl_tree.rs index e493d08a0..e9a3dd4c5 100644 --- a/ergotree-interpreter/src/eval/create_avl_tree.rs +++ b/ergotree-interpreter/src/eval/create_avl_tree.rs @@ -11,7 +11,7 @@ use sigma_util::AsVecU8; use std::convert::TryFrom; impl Evaluable for CreateAvlTree { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let flags_v = self.flags.eval(env, ctx)?.try_extract_into::()? as u8; let digest_v = self.digest.eval(env, ctx)?.try_extract_into::>()?; let key_length = self.key_length.eval(env, ctx)?.try_extract_into::()? as u32; diff --git a/ergotree-interpreter/src/eval/create_prove_dh_tuple.rs b/ergotree-interpreter/src/eval/create_prove_dh_tuple.rs index d06f9c604..e58311b16 100644 --- a/ergotree-interpreter/src/eval/create_prove_dh_tuple.rs +++ b/ergotree-interpreter/src/eval/create_prove_dh_tuple.rs @@ -10,7 +10,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for CreateProveDhTuple { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let g = self.g.eval(env, ctx)?.try_extract_into::()?; let h = self.h.eval(env, ctx)?.try_extract_into::()?; let u = self.u.eval(env, ctx)?.try_extract_into::()?; diff --git a/ergotree-interpreter/src/eval/create_provedlog.rs b/ergotree-interpreter/src/eval/create_provedlog.rs index b672cc79e..dc0036a1b 100644 --- a/ergotree-interpreter/src/eval/create_provedlog.rs +++ b/ergotree-interpreter/src/eval/create_provedlog.rs @@ -8,7 +8,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for CreateProveDlog { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let value_v = self.input.eval(env, ctx)?; match value_v { Value::GroupElement(ecpoint) => { diff --git a/ergotree-interpreter/src/eval/decode_point.rs b/ergotree-interpreter/src/eval/decode_point.rs index 8ef37704b..f088e6078 100644 --- a/ergotree-interpreter/src/eval/decode_point.rs +++ b/ergotree-interpreter/src/eval/decode_point.rs @@ -11,11 +11,12 @@ use ergotree_ir::mir::constant::TryExtractInto; use ergotree_ir::serialization::SigmaSerializable; impl Evaluable for DecodePoint { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let point_bytes = self.input.eval(env, ctx)?.try_extract_into::>()?; let point: EcPoint = SigmaSerializable::sigma_parse_bytes(&point_bytes).map_err(|_| { - Misc(String::from( - "DecodePoint: Failed to parse EC point from bytes", + Misc(format!( + "DecodePoint: Failed to parse EC point from bytes {:?}", + point_bytes, )) })?; Ok(point.into()) diff --git a/ergotree-interpreter/src/eval/deserialize_context.rs b/ergotree-interpreter/src/eval/deserialize_context.rs index 4fa22e2fe..6a4e48f6e 100644 --- a/ergotree-interpreter/src/eval/deserialize_context.rs +++ b/ergotree-interpreter/src/eval/deserialize_context.rs @@ -11,23 +11,27 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for DeserializeContext { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { match ctx.ctx.extension.values.get(&self.id) { Some(c) => { - if c.tpe != SType::SColl(SType::SByte.into()) { - Err(EvalError::UnexpectedExpr(format!("DeserializeContext: expected extension value to have type SColl(SByte), got {:?}", c.tpe))) + let expected_tpe = SType::SColl(SType::SByte.into()); + if c.tpe != expected_tpe { + Err(EvalError::UnexpectedExpr(format!( + "DeserializeContext: expected extension value {} with id {} to have type {:?} got {:?}", + c, self.id, expected_tpe, c.tpe + ))) } else { let bytes = c.v.clone().try_extract_into::>()?; let expr = Expr::sigma_parse_bytes(bytes.as_slice())?; if expr.tpe() != self.tpe { - return Err(EvalError::UnexpectedExpr(format!("DeserializeContext: expected deserialized expr to have type {:?}, got {:?}", self.tpe, expr.tpe()))); + return Err(EvalError::UnexpectedExpr(format!("DeserializeContext: expected deserialized expr from extension value {} with id {} to have type {:?}, got {:?}", c, self.id, self.tpe, expr.tpe()))); } expr.eval(env, ctx) } } None => Err(EvalError::NotFound(format!( - "DeserializeContext: no value with id {} in context extension", - self.id + "DeserializeContext: no value with id {} in context extension map {}", + self.id, ctx.ctx.extension ))), } } diff --git a/ergotree-interpreter/src/eval/deserialize_register.rs b/ergotree-interpreter/src/eval/deserialize_register.rs index 92d4dfad0..10ee394a9 100644 --- a/ergotree-interpreter/src/eval/deserialize_register.rs +++ b/ergotree-interpreter/src/eval/deserialize_register.rs @@ -1,5 +1,6 @@ use std::convert::TryInto; +use ergotree_ir::chain::ergo_box::RegisterId; use ergotree_ir::mir::constant::TryExtractInto; use ergotree_ir::mir::deserialize_register::DeserializeRegister; use ergotree_ir::mir::expr::Expr; @@ -13,27 +14,23 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for DeserializeRegister { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { - match ctx - .ctx - .self_box - .get_register(self.reg.try_into().map_err(|e| { - EvalError::RegisterIdOutOfBounds(format!( - "register index is out of bounds: {:?} ", - e - )) - })?) { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { + let reg_id: RegisterId = self.reg.try_into().map_err(|e| { + EvalError::RegisterIdOutOfBounds(format!("register index is out of bounds: {:?} ", e)) + })?; + match ctx.ctx.self_box.get_register(reg_id) { Ok(Some(c)) => { if c.tpe != SType::SColl(SType::SByte.into()) { Err(EvalError::UnexpectedExpr(format!( - "DeserializeRegister: expected value to have type SColl(SByte), got {:?}", - c.tpe + "DeserializeRegister: expected register {} value {} to have type SColl(SByte), got {:?}", + reg_id, c, c.tpe ))) } else { let bytes = c.v.try_extract_into::>()?; let expr = Expr::sigma_parse_bytes(bytes.as_slice())?; if expr.tpe() != self.tpe { - Err(EvalError::UnexpectedExpr(format!("DeserializeRegister: expected deserialized expr to have type {:?}, got {:?}", self.tpe, expr.tpe()))) + let pretty_expr = expr.to_string_pretty(); + Err(EvalError::UnexpectedExpr(format!("DeserializeRegister: expected register {reg_id} deserialized expr {pretty_expr} to have type {:?}, got {:?}", self.tpe, expr.tpe()))) } else { expr.eval(env, ctx) } @@ -42,15 +39,13 @@ impl Evaluable for DeserializeRegister { Ok(None) => match &self.default { Some(default_expr) => eval_default(&self.tpe, default_expr, env, ctx), None => Err(EvalError::NotFound(format!( - "DeserializeRegister: register {:?} is empty", - self.reg + "DeserializeRegister: register {reg_id} is empty" ))), }, Err(e) => match &self.default { Some(default_expr) => eval_default(&self.tpe, default_expr, env, ctx), None => Err(EvalError::NotFound(format!( - "DeserializeRegister: failed to get the register id {} with error: {e:?}", - self.reg + "DeserializeRegister: failed to get the register id {reg_id} with error: {e:?}" ))), }, } @@ -60,7 +55,7 @@ impl Evaluable for DeserializeRegister { fn eval_default( deserialize_reg_tpe: &SType, default_expr: &Expr, - env: &Env, + env: &mut Env, ctx: &mut EvalContext, ) -> Result { if &default_expr.tpe() != deserialize_reg_tpe { diff --git a/ergotree-interpreter/src/eval/downcast.rs b/ergotree-interpreter/src/eval/downcast.rs index 639cbd0ab..003c5f1b4 100644 --- a/ergotree-interpreter/src/eval/downcast.rs +++ b/ergotree-interpreter/src/eval/downcast.rs @@ -104,7 +104,7 @@ fn downcast_to_byte(in_v: Value) -> Result { } impl Evaluable for Downcast { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; match self.tpe { SType::SBigInt => downcast_to_bigint(input_v), diff --git a/ergotree-interpreter/src/eval/error.rs b/ergotree-interpreter/src/eval/error.rs new file mode 100644 index 000000000..0f4b539ed --- /dev/null +++ b/ergotree-interpreter/src/eval/error.rs @@ -0,0 +1,339 @@ +use miette::miette; +use miette::LabeledSpan; +use std::fmt::Display; + +use bounded_vec::BoundedVecOutOfBounds; +use derive_more::TryInto; +use ergotree_ir::ergo_tree::ErgoTreeError; +use ergotree_ir::mir::constant::TryExtractFromError; +use ergotree_ir::serialization::SigmaParsingError; +use ergotree_ir::serialization::SigmaSerializationError; +use ergotree_ir::source_span::SourceSpan; +use sigma_ser::ScorexParsingError; +use sigma_ser::ScorexSerializationError; +use thiserror::Error; + +use super::cost_accum::CostError; +use super::env::Env; + +/// Interpreter errors +#[derive(Error, PartialEq, Eq, Debug, Clone, TryInto)] +pub enum EvalError { + /// AVL tree errors + #[error("AvlTree: {0}")] + AvlTree(String), + /// Only boolean or SigmaBoolean is a valid result expr type + #[error("Only boolean or SigmaBoolean is a valid result expr type")] + InvalidResultType, + /// Unexpected Expr encountered during the evaluation + #[error("Unexpected Expr: {0}")] + UnexpectedExpr(String), + /// Error on cost calculation + #[error("Error on cost calculation: {0:?}")] + CostError(#[from] CostError), + /// Unexpected value type + #[error("Unexpected value type: {0:?}")] + TryExtractFrom(#[from] TryExtractFromError), + /// Not found (missing value, argument, etc.) + #[error("Not found: {0}")] + NotFound(String), + /// Register id out of bounds + #[error("{0}")] + RegisterIdOutOfBounds(String), + /// Unexpected value + #[error("Unexpected value: {0}")] + UnexpectedValue(String), + /// Arithmetic exception error + #[error("Arithmetic exception: {0}")] + ArithmeticException(String), + /// Misc error + #[error("error: {0}")] + Misc(String), + /// Sigma serialization error + #[error("Serialization error: {0}")] + SigmaSerializationError(#[from] SigmaSerializationError), + /// Sigma serialization parsing error + #[error("Serialization parsing error: {0}")] + SigmaParsingError(#[from] SigmaParsingError), + /// ErgoTree error + #[error("ErgoTree error: {0}")] + ErgoTreeError(#[from] ErgoTreeError), + /// Invalid item quantity for BoundedVec + #[error("Invalid item quantity for BoundedVec: {0}")] + BoundedVecError(#[from] BoundedVecOutOfBounds), + /// Scorex serialization error + #[error("Serialization error: {0}")] + ScorexSerializationError(#[from] ScorexSerializationError), + /// Scorex serialization parsing error + #[error("Serialization parsing error: {0}")] + ScorexParsingError(#[from] ScorexParsingError), + /// Wrapped error with source span and source code + #[error("eval error: {0}")] + SpannedWithSource(SpannedWithSourceEvalError), + /// Wrapped error with source span + #[error("eval error: {0:?}")] + Spanned(SpannedEvalError), +} + +/// Wrapped error with source span +#[derive(PartialEq, Eq, Debug, Clone)] +pub struct SpannedEvalError { + /// eval error + error: Box, + /// source span for the expression where error occurred + source_span: SourceSpan, + /// environment at the time when error occurred + env: Env, +} + +/// Wrapped error with source span and source code +#[derive(PartialEq, Eq, Debug, Clone)] +pub struct SpannedWithSourceEvalError { + /// eval error + error: Box, + /// source span for the expression where error occurred + source_span: SourceSpan, + /// environment at the time when error occurred + env: Env, + /// source code + source: String, +} + +impl Display for SpannedWithSourceEvalError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[allow(clippy::unwrap_used)] + miette::set_hook(Box::new(|_| { + Box::new( + miette::MietteHandlerOpts::new() + .terminal_links(false) + .unicode(false) + .color(false) + .context_lines(5) + .tab_width(2) + .build(), + ) + })) + .unwrap(); + let err_msg = self.error.to_string(); + let report = miette!( + labels = vec![LabeledSpan::at(self.source_span, err_msg,)], + // help = "Help msg", + "Evaluation error" + ) + .with_source_code(self.source.clone()); + write!(f, "{:?}", report) + } +} + +impl EvalError { + /// Wrap eval error with source span + pub fn wrap(self, source_span: SourceSpan, env: Env) -> Self { + EvalError::Spanned(SpannedEvalError { + error: Box::new(self), + source_span, + env, + }) + } + + /// Wrap eval error with source code + pub fn wrap_spanned_with_src(self, source: String) -> Self { + #[allow(clippy::panic)] + match self { + EvalError::Spanned(e) => EvalError::SpannedWithSource(SpannedWithSourceEvalError { + error: e.error, + source_span: e.source_span, + env: e.env, + source, + }), + e => panic!("Expected Spanned, got {:?}", e), + } + } +} + +pub trait ExtResultEvalError { + fn enrich_err(self, span: SourceSpan, env: Env) -> Result; +} + +impl ExtResultEvalError for Result { + fn enrich_err(self, span: SourceSpan, env: Env) -> Result { + self.map_err(|e| match e { + // skip already wrapped errors + w @ EvalError::Spanned { .. } => w, + e => e.wrap(span, env), + }) + } +} + +#[allow(clippy::unwrap_used, unused_imports, dead_code)] +#[cfg(test)] +mod tests { + use std::rc::Rc; + + use ergotree_ir::mir::coll_by_index::ByIndex; + use ergotree_ir::mir::global_vars::GlobalVars; + use ergotree_ir::source_span::SourceSpan; + use expect_test::expect; + + use ergotree_ir::mir::bin_op::ArithOp; + use ergotree_ir::mir::bin_op::BinOp; + use ergotree_ir::mir::block::BlockValue; + use ergotree_ir::mir::expr::Expr; + use ergotree_ir::mir::val_def::ValDef; + use ergotree_ir::mir::val_use::ValUse; + use ergotree_ir::pretty_printer::PosTrackingWriter; + use ergotree_ir::pretty_printer::Print; + use ergotree_ir::types::stype::SType; + use sigma_test_util::force_any_val; + + use crate::eval::context::Context; + use crate::eval::error::SpannedEvalError; + use crate::eval::error::SpannedWithSourceEvalError; + use crate::eval::tests::try_eval_out; + + fn check(expr: Expr, expected_tree: expect_test::Expect) { + let mut w = PosTrackingWriter::new(); + let spanned_expr = expr.print(&mut w).unwrap(); + dbg!(&spanned_expr); + let ctx = Rc::new(force_any_val::()); + let err_raw: SpannedEvalError = try_eval_out::(&spanned_expr, ctx) + .err() + .unwrap() + .try_into() + .unwrap(); + let err = SpannedWithSourceEvalError { + error: err_raw.error, + source_span: err_raw.source_span, + env: err_raw.env, + source: w.get_buf().to_string(), + }; + expected_tree.assert_eq(&err.to_string()); + } + + fn check_error_span(expr: Expr, expected_span: SourceSpan) { + let mut w = PosTrackingWriter::new(); + let spanned_expr = expr.print(&mut w).unwrap(); + dbg!(&spanned_expr); + let ctx = Rc::new(force_any_val::()); + let err_raw: SpannedEvalError = try_eval_out::(&spanned_expr, ctx) + .err() + .unwrap() + .try_into() + .unwrap(); + assert_eq!(err_raw.source_span, expected_span); + } + + #[test] + fn pretty_binop_div_zero() { + let lhs_val_id = 1.into(); + let rhs_val_id = 2.into(); + let res_val_id = 3.into(); + let expr = Expr::BlockValue( + BlockValue { + items: vec![ + ValDef { + id: lhs_val_id, + rhs: Box::new(Expr::Const(42i32.into())), + } + .into(), + ValDef { + id: rhs_val_id, + rhs: Box::new(Expr::Const(0i32.into())), + } + .into(), + ValDef { + id: res_val_id, + rhs: Box::new( + BinOp { + kind: ArithOp::Divide.into(), + left: Box::new( + ValUse { + val_id: lhs_val_id, + tpe: SType::SInt, + } + .into(), + ), + right: Box::new( + ValUse { + val_id: rhs_val_id, + tpe: SType::SInt, + } + .into(), + ), + } + .into(), + ), + } + .into(), + ], + result: Box::new( + ValUse { + val_id: res_val_id, + tpe: SType::SInt, + } + .into(), + ), + } + .into(), + ); + // check( + // expr, + // expect![[r#" + // x Evaluation error + // ,-[1:1] + // 1 | { + // 2 | val v1 = 42 + // 3 | val v2 = 0 + // 4 | val v3 = v1 / v2 + // : ^^^|^^^ + // : `-- Arithmetic exception: (42) / (0) resulted in exception + // 5 | v3 + // 6 | } + // `---- + // "#]], + // ); + check_error_span(expr, (40, 7).into()); + } + + #[test] + fn pretty_out_of_bounds() { + let v1_id = 1.into(); + let expr = Expr::BlockValue( + BlockValue { + items: vec![ValDef { + id: v1_id, + rhs: Box::new(Expr::Const(99999999i32.into())), + } + .into()], + result: Box::new(Expr::ByIndex( + ByIndex::new( + Expr::GlobalVars(GlobalVars::Outputs), + ValUse { + val_id: v1_id, + tpe: SType::SInt, + } + .into(), + None, + ) + .unwrap() + .into(), + )), + } + .into(), + ); + // check( + // expr, + // expect![[r#" + // x Evaluation error + // ,-[1:1] + // 1 | { + // 2 | val v1 = 99999999 + // 3 | OUTPUTS(v1) + // : ^^|^ + // : `-- error: ByIndex: index Int(99999999) out of bounds for collection size 1 + // 4 | } + // `---- + // "#]], + // ); + check_error_span(expr, (31, 4).into()); + } +} diff --git a/ergotree-interpreter/src/eval/exponentiate.rs b/ergotree-interpreter/src/eval/exponentiate.rs index 5326d8a9e..dc00bd0f7 100644 --- a/ergotree-interpreter/src/eval/exponentiate.rs +++ b/ergotree-interpreter/src/eval/exponentiate.rs @@ -9,7 +9,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for Exponentiate { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let left_v = self.left.eval(env, ctx)?; let right_v = self.right.eval(env, ctx)?; diff --git a/ergotree-interpreter/src/eval/expr.rs b/ergotree-interpreter/src/eval/expr.rs index e36aff9a0..ebbe93f66 100644 --- a/ergotree-interpreter/src/eval/expr.rs +++ b/ergotree-interpreter/src/eval/expr.rs @@ -1,35 +1,39 @@ +//! Evaluation of ErgoTree expressions + use ergotree_ir::mir::expr::Expr; use ergotree_ir::mir::value::Value; +use ergotree_ir::source_span::Spanned; +use super::error::ExtResultEvalError; use super::Env; use super::EvalContext; use super::EvalError; use super::Evaluable; impl Evaluable for Expr { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { ctx.cost_accum.add_cost_of(self)?; - match self { + let res = match self { Expr::Const(c) => Ok(Value::from(c.v.clone())), - Expr::SubstConstants(op) => op.eval(env, ctx), - Expr::ByteArrayToLong(op) => op.eval(env, ctx), - Expr::ByteArrayToBigInt(op) => op.eval(env, ctx), + Expr::SubstConstants(op) => op.expr().eval(env, ctx), + Expr::ByteArrayToLong(op) => op.expr().eval(env, ctx), + Expr::ByteArrayToBigInt(op) => op.expr().eval(env, ctx), Expr::LongToByteArray(op) => op.eval(env, ctx), Expr::CalcBlake2b256(op) => op.eval(env, ctx), Expr::CalcSha256(op) => op.eval(env, ctx), Expr::Fold(op) => op.eval(env, ctx), - Expr::ExtractRegisterAs(op) => op.eval(env, ctx), + Expr::ExtractRegisterAs(op) => op.expr().eval(env, ctx), Expr::GlobalVars(op) => op.eval(env, ctx), - Expr::MethodCall(op) => op.eval(env, ctx), - Expr::ProperyCall(op) => op.eval(env, ctx), - Expr::BinOp(op) => op.eval(env, ctx), + Expr::MethodCall(op) => op.expr().eval(env, ctx), + Expr::PropertyCall(op) => op.expr().eval(env, ctx), + Expr::BinOp(op) => op.expr().eval(env, ctx), Expr::Global => Ok(Value::Global), Expr::Context => Ok(Value::Context), - Expr::OptionGet(v) => v.eval(env, ctx), + Expr::OptionGet(v) => v.expr().eval(env, ctx), Expr::Apply(op) => op.eval(env, ctx), Expr::FuncValue(op) => op.eval(env, ctx), Expr::ValUse(op) => op.eval(env, ctx), - Expr::BlockValue(op) => op.eval(env, ctx), + Expr::BlockValue(op) => op.expr().eval(env, ctx), Expr::SelectField(op) => op.eval(env, ctx), Expr::ExtractAmount(op) => op.eval(env, ctx), Expr::ConstPlaceholder(_) => Err(EvalError::UnexpectedExpr( @@ -37,7 +41,7 @@ impl Evaluable for Expr { )), Expr::Collection(op) => op.eval(env, ctx), Expr::ValDef(_) => Err(EvalError::UnexpectedExpr( - ("ValDef is evaluated in BlockValue").to_string(), + ("ValDef should be evaluated in BlockValue").to_string(), )), Expr::And(op) => op.eval(env, ctx), Expr::Or(op) => op.eval(env, ctx), @@ -50,8 +54,8 @@ impl Evaluable for Expr { Expr::Upcast(op) => op.eval(env, ctx), Expr::Downcast(op) => op.eval(env, ctx), Expr::If(op) => op.eval(env, ctx), - Expr::Append(op) => op.eval(env, ctx), - Expr::ByIndex(op) => op.eval(env, ctx), + Expr::Append(op) => op.expr().eval(env, ctx), + Expr::ByIndex(op) => op.expr().eval(env, ctx), Expr::ExtractScriptBytes(op) => op.eval(env, ctx), Expr::SizeOf(op) => op.eval(env, ctx), Expr::Slice(op) => op.eval(env, ctx), @@ -61,9 +65,9 @@ impl Evaluable for Expr { Expr::Exists(op) => op.eval(env, ctx), Expr::ExtractId(op) => op.eval(env, ctx), Expr::SigmaPropBytes(op) => op.eval(env, ctx), - Expr::OptionIsDefined(op) => op.eval(env, ctx), - Expr::OptionGetOrElse(op) => op.eval(env, ctx), - Expr::Negation(op) => op.eval(env, ctx), + Expr::OptionIsDefined(op) => op.expr().eval(env, ctx), + Expr::OptionGetOrElse(op) => op.expr().eval(env, ctx), + Expr::Negation(op) => op.expr().eval(env, ctx), Expr::BitInversion(op) => op.eval(env, ctx), Expr::ForAll(op) => op.eval(env, ctx), Expr::Tuple(op) => op.eval(env, ctx), @@ -80,6 +84,13 @@ impl Evaluable for Expr { Expr::ExtractBytesWithNoRef(op) => op.eval(env, ctx), Expr::TreeLookup(op) => op.eval(env, ctx), Expr::CreateAvlTree(op) => op.eval(env, ctx), - } + }; + res.enrich_err(self.span(), env.clone()) + } +} + +impl Evaluable for Spanned { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { + self.expr.eval(env, ctx) } } diff --git a/ergotree-interpreter/src/eval/extract_amount.rs b/ergotree-interpreter/src/eval/extract_amount.rs index 196ad030e..25c4e3cce 100644 --- a/ergotree-interpreter/src/eval/extract_amount.rs +++ b/ergotree-interpreter/src/eval/extract_amount.rs @@ -7,7 +7,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for ExtractAmount { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; match input_v { Value::CBox(b) => Ok(Value::Long(b.value.as_i64())), diff --git a/ergotree-interpreter/src/eval/extract_bytes.rs b/ergotree-interpreter/src/eval/extract_bytes.rs index 98af3d964..060fe203e 100644 --- a/ergotree-interpreter/src/eval/extract_bytes.rs +++ b/ergotree-interpreter/src/eval/extract_bytes.rs @@ -8,7 +8,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for ExtractBytes { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; match input_v { Value::CBox(b) => Ok(b.sigma_serialize_bytes()?.into()), diff --git a/ergotree-interpreter/src/eval/extract_bytes_with_no_ref.rs b/ergotree-interpreter/src/eval/extract_bytes_with_no_ref.rs index bd30d2723..f97e8fd1e 100644 --- a/ergotree-interpreter/src/eval/extract_bytes_with_no_ref.rs +++ b/ergotree-interpreter/src/eval/extract_bytes_with_no_ref.rs @@ -7,7 +7,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for ExtractBytesWithNoRef { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; match input_v { Value::CBox(b) => Ok(b.bytes_without_ref()?.into()), diff --git a/ergotree-interpreter/src/eval/extract_creation_info.rs b/ergotree-interpreter/src/eval/extract_creation_info.rs index bda52b36c..0cebb3376 100644 --- a/ergotree-interpreter/src/eval/extract_creation_info.rs +++ b/ergotree-interpreter/src/eval/extract_creation_info.rs @@ -7,7 +7,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for ExtractCreationInfo { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; match input_v { Value::CBox(b) => Ok(b.creation_info().into()), diff --git a/ergotree-interpreter/src/eval/extract_id.rs b/ergotree-interpreter/src/eval/extract_id.rs index 83571158a..ce7d9c18d 100644 --- a/ergotree-interpreter/src/eval/extract_id.rs +++ b/ergotree-interpreter/src/eval/extract_id.rs @@ -7,7 +7,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for ExtractId { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; match input_v { Value::CBox(b) => { diff --git a/ergotree-interpreter/src/eval/extract_reg_as.rs b/ergotree-interpreter/src/eval/extract_reg_as.rs index a9bd6efa7..656220c63 100644 --- a/ergotree-interpreter/src/eval/extract_reg_as.rs +++ b/ergotree-interpreter/src/eval/extract_reg_as.rs @@ -12,18 +12,20 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for ExtractRegisterAs { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let ir_box = self .input .eval(env, ctx)? .try_extract_into::>()?; let id = self.register_id.try_into().map_err(|e| { - EvalError::RegisterIdOutOfBounds(format!("register index is out of bounds: {:?} ", e)) + EvalError::RegisterIdOutOfBounds(format!( + "register index {} is out of bounds: {:?} ", + self.register_id, e + )) })?; let reg_val_opt = ir_box.get_register(id).map_err(|e| { EvalError::NotFound(format!( - "Error getting the register id {:?} with error {e:?}", - id + "Error getting the register id {id} with error {e:?}" )) })?; Ok(Value::Opt(Box::new(reg_val_opt.map(|c| Value::from(c.v))))) diff --git a/ergotree-interpreter/src/eval/extract_script_bytes.rs b/ergotree-interpreter/src/eval/extract_script_bytes.rs index 745936e4f..71075ced4 100644 --- a/ergotree-interpreter/src/eval/extract_script_bytes.rs +++ b/ergotree-interpreter/src/eval/extract_script_bytes.rs @@ -7,7 +7,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for ExtractScriptBytes { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; match input_v { Value::CBox(b) => Ok(b.script_bytes()?.into()), diff --git a/ergotree-interpreter/src/eval/func_value.rs b/ergotree-interpreter/src/eval/func_value.rs index f342bb1e6..1cf0d944a 100644 --- a/ergotree-interpreter/src/eval/func_value.rs +++ b/ergotree-interpreter/src/eval/func_value.rs @@ -8,7 +8,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for FuncValue { - fn eval(&self, _env: &Env, _ctx: &mut EvalContext) -> Result { + fn eval(&self, _env: &mut Env, _ctx: &mut EvalContext) -> Result { Ok(Value::Lambda(Lambda { args: self.args().to_vec(), body: self.body().clone().into(), diff --git a/ergotree-interpreter/src/eval/get_var.rs b/ergotree-interpreter/src/eval/get_var.rs index 276742a51..1ac5e34e6 100644 --- a/ergotree-interpreter/src/eval/get_var.rs +++ b/ergotree-interpreter/src/eval/get_var.rs @@ -1,4 +1,3 @@ -// use ergotree_ir::mir::constant::TryExtractInto; use ergotree_ir::mir::constant::TryExtractFromError; use ergotree_ir::mir::get_var::GetVar; use ergotree_ir::mir::value::Value; @@ -9,13 +8,13 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for GetVar { - fn eval(&self, _env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, _env: &mut Env, ctx: &mut EvalContext) -> Result { match ctx.ctx.extension.values.get(&self.var_id) { None => Ok(Value::Opt(None.into())), Some(v) if v.tpe == self.var_tpe => Ok((Some(v.v.clone())).into()), Some(v) => Err(TryExtractFromError(format!( - "GetVar: expected {:?}, found {:?}", - self.var_tpe, v.tpe + "GetVar: expected extension value id {} to have type {:?}, found {:?} in context extension map {}", + self.var_id, self.var_tpe, v, ctx.ctx.extension )) .into()), } diff --git a/ergotree-interpreter/src/eval/global_vars.rs b/ergotree-interpreter/src/eval/global_vars.rs index 9f1371ee6..148c6e7de 100644 --- a/ergotree-interpreter/src/eval/global_vars.rs +++ b/ergotree-interpreter/src/eval/global_vars.rs @@ -8,7 +8,7 @@ use super::EvalError; use super::Evaluable; impl Evaluable for GlobalVars { - fn eval(&self, _env: &Env, ectx: &mut EvalContext) -> Result { + fn eval(&self, _env: &mut Env, ectx: &mut EvalContext) -> Result { match self { GlobalVars::Height => Ok((ectx.ctx.height as i32).into()), GlobalVars::SelfBox => Ok(ectx.ctx.self_box.clone().into()), diff --git a/ergotree-interpreter/src/eval/if_op.rs b/ergotree-interpreter/src/eval/if_op.rs index 0795c7674..fce7a6ede 100644 --- a/ergotree-interpreter/src/eval/if_op.rs +++ b/ergotree-interpreter/src/eval/if_op.rs @@ -8,7 +8,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for If { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let condition_v = self.condition.eval(env, ctx)?; if condition_v.try_extract_into::()? { self.true_branch.eval(env, ctx) diff --git a/ergotree-interpreter/src/eval/logical_not.rs b/ergotree-interpreter/src/eval/logical_not.rs index 9d9829b13..2e2c8ec62 100644 --- a/ergotree-interpreter/src/eval/logical_not.rs +++ b/ergotree-interpreter/src/eval/logical_not.rs @@ -8,7 +8,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for LogicalNot { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; let input_v_bool = input_v.try_extract_into::()?; Ok((!input_v_bool).into()) diff --git a/ergotree-interpreter/src/eval/long_to_byte_array.rs b/ergotree-interpreter/src/eval/long_to_byte_array.rs index cba7a4a98..745e0ff3e 100644 --- a/ergotree-interpreter/src/eval/long_to_byte_array.rs +++ b/ergotree-interpreter/src/eval/long_to_byte_array.rs @@ -8,7 +8,7 @@ use crate::eval::Evaluable; use ergotree_ir::mir::constant::TryExtractInto; impl Evaluable for LongToByteArray { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let mut val = self.input.eval(env, ctx)?.try_extract_into::()?; let mut buf = vec![42_i8; 8]; for i in (0..8).rev() { diff --git a/ergotree-interpreter/src/eval/method_call.rs b/ergotree-interpreter/src/eval/method_call.rs index 5791db03f..fe6ac8174 100644 --- a/ergotree-interpreter/src/eval/method_call.rs +++ b/ergotree-interpreter/src/eval/method_call.rs @@ -8,7 +8,7 @@ use super::EvalError; use super::Evaluable; impl Evaluable for MethodCall { - fn eval(&self, env: &Env, ectx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ectx: &mut EvalContext) -> Result { let ov = self.obj.eval(env, ectx)?; let argsv: Result, EvalError> = self.args.iter().map(|arg| arg.eval(env, ectx)).collect(); diff --git a/ergotree-interpreter/src/eval/multiply_group.rs b/ergotree-interpreter/src/eval/multiply_group.rs index de6d8a732..aa1973f52 100644 --- a/ergotree-interpreter/src/eval/multiply_group.rs +++ b/ergotree-interpreter/src/eval/multiply_group.rs @@ -7,7 +7,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for MultiplyGroup { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let left_v = self.left.eval(env, ctx)?; let right_v = self.right.eval(env, ctx)?; diff --git a/ergotree-interpreter/src/eval/negation.rs b/ergotree-interpreter/src/eval/negation.rs index dc58a609b..5a6b15129 100644 --- a/ergotree-interpreter/src/eval/negation.rs +++ b/ergotree-interpreter/src/eval/negation.rs @@ -8,7 +8,7 @@ use crate::eval::Evaluable; use num_traits::CheckedNeg; impl Evaluable for Negation { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; fn overflow_err(v: &T) -> EvalError { diff --git a/ergotree-interpreter/src/eval/option_get.rs b/ergotree-interpreter/src/eval/option_get.rs index d844887bf..bcbace334 100644 --- a/ergotree-interpreter/src/eval/option_get.rs +++ b/ergotree-interpreter/src/eval/option_get.rs @@ -7,7 +7,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for OptionGet { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let v = self.input.eval(env, ctx)?; match v { Value::Opt(opt_v) => { diff --git a/ergotree-interpreter/src/eval/option_get_or_else.rs b/ergotree-interpreter/src/eval/option_get_or_else.rs index c29844eff..5e307518a 100644 --- a/ergotree-interpreter/src/eval/option_get_or_else.rs +++ b/ergotree-interpreter/src/eval/option_get_or_else.rs @@ -7,7 +7,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for OptionGetOrElse { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let v = self.input.eval(env, ctx)?; let default_v = self.default.eval(env, ctx)?; match v { diff --git a/ergotree-interpreter/src/eval/option_is_defined.rs b/ergotree-interpreter/src/eval/option_is_defined.rs index a85bc6969..814667ab9 100644 --- a/ergotree-interpreter/src/eval/option_is_defined.rs +++ b/ergotree-interpreter/src/eval/option_is_defined.rs @@ -7,7 +7,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for OptionIsDefined { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let v = self.input.eval(env, ctx)?; match v { Value::Opt(opt_v) => Ok(opt_v.is_some().into()), diff --git a/ergotree-interpreter/src/eval/or.rs b/ergotree-interpreter/src/eval/or.rs index 8e212e06d..561fb4dc7 100644 --- a/ergotree-interpreter/src/eval/or.rs +++ b/ergotree-interpreter/src/eval/or.rs @@ -8,7 +8,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for Or { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; let input_v_bools = input_v.try_extract_into::>()?; Ok(input_v_bools.iter().any(|b| *b).into()) diff --git a/ergotree-interpreter/src/eval/property_call.rs b/ergotree-interpreter/src/eval/property_call.rs index c36479b25..79996caa3 100644 --- a/ergotree-interpreter/src/eval/property_call.rs +++ b/ergotree-interpreter/src/eval/property_call.rs @@ -8,7 +8,7 @@ use super::EvalError; use super::Evaluable; impl Evaluable for PropertyCall { - fn eval(&self, env: &Env, ectx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ectx: &mut EvalContext) -> Result { let ov = self.obj.eval(env, ectx)?; smethod_eval_fn(&self.method)?(env, ectx, ov, vec![]) } diff --git a/ergotree-interpreter/src/eval/sbox.rs b/ergotree-interpreter/src/eval/sbox.rs index aa28cf416..26d130584 100644 --- a/ergotree-interpreter/src/eval/sbox.rs +++ b/ergotree-interpreter/src/eval/sbox.rs @@ -20,19 +20,20 @@ pub(crate) static GET_REG_EVAL_FN: EvalFn = |_env, _ctx, obj, args| { .get(0) .cloned() .ok_or_else(|| EvalError::NotFound("register index is missing".to_string()))? - .try_extract_into::()? - .try_into() - .map_err(|e| { - EvalError::RegisterIdOutOfBounds(format!("register index is out of bounds: {:?} ", e)) - })?; + .try_extract_into::()?; + let reg_id = reg_id.try_into().map_err(|e| { + EvalError::RegisterIdOutOfBounds(format!( + "register index {reg_id} is out of bounds: {:?} ", + e + )) + })?; Ok(Value::Opt(Box::new( obj.try_extract_into::>()? .get_register(reg_id) .map_err(|e| { EvalError::NotFound(format!( - "Error getting the register id {:?} with error {e:?}", - reg_id + "Error getting the register id {reg_id} with error {e:?}" )) })? .map(|c| Value::from(c.v)), diff --git a/ergotree-interpreter/src/eval/scoll.rs b/ergotree-interpreter/src/eval/scoll.rs index 8256e5657..4e1f0c625 100644 --- a/ergotree-interpreter/src/eval/scoll.rs +++ b/ergotree-interpreter/src/eval/scoll.rs @@ -60,7 +60,7 @@ pub(crate) static FLATMAP_EVAL_FN: EvalFn = |env, ctx, obj, args| { "unsupported lambda in flatMap: allowed usage `xs.flatMap(x => x.property)".to_string(); match &*lambda.body { Expr::MethodCall(mc) => { - if !mc.args.is_empty() { + if !mc.expr().args.is_empty() { return Err(EvalError::UnexpectedValue(unsupported_msg)); } } @@ -75,8 +75,8 @@ pub(crate) static FLATMAP_EVAL_FN: EvalFn = |env, ctx, obj, args| { let func_arg = lambda.args.first().ok_or_else(|| { EvalError::NotFound("flatmap: lambda has empty arguments list".to_string()) })?; - let env1 = env.clone().extend(func_arg.idx, arg); - lambda.body.eval(&env1, ctx) + env.insert(func_arg.idx, arg); + lambda.body.eval(env, ctx) }; let mapper_input_tpe = lambda .args diff --git a/ergotree-interpreter/src/eval/select_field.rs b/ergotree-interpreter/src/eval/select_field.rs index c5aea0a92..9bb4ebbf9 100644 --- a/ergotree-interpreter/src/eval/select_field.rs +++ b/ergotree-interpreter/src/eval/select_field.rs @@ -7,7 +7,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for SelectField { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; match input_v { Value::Tup(items) => items diff --git a/ergotree-interpreter/src/eval/sigma_and.rs b/ergotree-interpreter/src/eval/sigma_and.rs index 7912d6c7b..de9437520 100644 --- a/ergotree-interpreter/src/eval/sigma_and.rs +++ b/ergotree-interpreter/src/eval/sigma_and.rs @@ -10,7 +10,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for SigmaAnd { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let items_v_res = self.items.try_mapped_ref(|it| it.eval(env, ctx)); let items_sigmabool = items_v_res? .try_mapped(|it| it.try_extract_into::())? diff --git a/ergotree-interpreter/src/eval/sigma_or.rs b/ergotree-interpreter/src/eval/sigma_or.rs index 1ef93782e..b2324e06a 100644 --- a/ergotree-interpreter/src/eval/sigma_or.rs +++ b/ergotree-interpreter/src/eval/sigma_or.rs @@ -10,7 +10,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for SigmaOr { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let items_v_res = self.items.try_mapped_ref(|it| it.eval(env, ctx)); let items_sigmabool = items_v_res? .try_mapped(|it| it.try_extract_into::())? diff --git a/ergotree-interpreter/src/eval/sigma_prop_bytes.rs b/ergotree-interpreter/src/eval/sigma_prop_bytes.rs index 09a395b07..11cbde0ed 100644 --- a/ergotree-interpreter/src/eval/sigma_prop_bytes.rs +++ b/ergotree-interpreter/src/eval/sigma_prop_bytes.rs @@ -7,7 +7,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for SigmaPropBytes { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; match input_v { Value::SigmaProp(sigma_prop) => Ok(sigma_prop.prop_bytes()?.into()), diff --git a/ergotree-interpreter/src/eval/soption.rs b/ergotree-interpreter/src/eval/soption.rs index e2c4932fb..f981f1444 100644 --- a/ergotree-interpreter/src/eval/soption.rs +++ b/ergotree-interpreter/src/eval/soption.rs @@ -23,8 +23,8 @@ pub(crate) static MAP_EVAL_FN: EvalFn = |env, ctx, obj, args| { let func_arg = lambda.args.first().ok_or_else(|| { EvalError::NotFound("map: lambda has empty arguments list".to_string()) })?; - let env1 = env.clone().extend(func_arg.idx, arg); - lambda.body.eval(&env1, ctx) + env.insert(func_arg.idx, arg); + lambda.body.eval(env, ctx) }; let normalized_input_val: Option = match input_v { Value::Opt(opt) => Ok(*opt), @@ -58,8 +58,8 @@ pub(crate) static FILTER_EVAL_FN: EvalFn = |env, ctx, obj, args| { let func_arg = lambda.args.first().ok_or_else(|| { EvalError::NotFound("filter: lambda has empty arguments list".to_string()) })?; - let env1 = env.clone().extend(func_arg.idx, arg); - lambda.body.eval(&env1, ctx) + env.insert(func_arg.idx, arg); + lambda.body.eval(env, ctx) }; let normalized_input_val: Option = match input_v { Value::Opt(opt) => Ok(*opt), diff --git a/ergotree-interpreter/src/eval/subst_const.rs b/ergotree-interpreter/src/eval/subst_const.rs index f99e52481..c92943f1f 100644 --- a/ergotree-interpreter/src/eval/subst_const.rs +++ b/ergotree-interpreter/src/eval/subst_const.rs @@ -15,7 +15,7 @@ use sigma_util::AsVecU8; use std::convert::TryFrom; impl Evaluable for SubstConstants { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let script_bytes_v = self.script_bytes.eval(env, ctx)?; let positions_v = self.positions.eval(env, ctx)?; let new_values_v = self.new_values.eval(env, ctx)?; @@ -131,11 +131,14 @@ mod tests { let positions = Expr::Const(Constant::from(vec![0])).into(); let new_values = Expr::Const(Constant::from(vec![new.clone()])).into(); - let subst_const = Expr::SubstConstants(SubstConstants { - script_bytes, - positions, - new_values, - }); + let subst_const = Expr::SubstConstants( + SubstConstants { + script_bytes, + positions, + new_values, + } + .into(), + ); let x: Value = try_eval_out_wo_ctx(&subst_const).unwrap(); if let Value::Coll(CollKind::NativeColl(NativeColl::CollByte(b))) = x { @@ -150,15 +153,21 @@ mod tests { fn test_3_substitutions(original: (i32, i32, i32), new: (i32, i32, i32)) { let (o0, o1, o2) = original; let (n0, n1, n2) = new; - let expr = Expr::BinOp(BinOp { - kind: BinOpKind::Arith(ArithOp::Plus), - left: Box::new(Expr::Const(o0.into())), - right: Box::new(Expr::BinOp(BinOp { - kind: BinOpKind::Arith(ArithOp::Multiply), - left: Box::new(Expr::Const(o1.into())), - right: Box::new(Expr::Const(o2.into())), - })), - }); + let expr = Expr::BinOp( + BinOp { + kind: BinOpKind::Arith(ArithOp::Plus), + left: Box::new(Expr::Const(o0.into())), + right: Box::new(Expr::BinOp( + BinOp { + kind: BinOpKind::Arith(ArithOp::Multiply), + left: Box::new(Expr::Const(o1.into())), + right: Box::new(Expr::Const(o2.into())), + } + .into(), + )), + } + .into(), + ); let ergo_tree = ErgoTree::new(ErgoTreeHeader::v0(true), &expr).unwrap(); assert_eq!(ergo_tree.constants_len().unwrap(), 3); assert_eq!(ergo_tree.get_constant(0).unwrap().unwrap(), o0.into()); @@ -172,11 +181,14 @@ mod tests { let new_values = Expr::Const(Constant::from(vec![n0, n1, n2])).into(); - let subst_const = Expr::SubstConstants(SubstConstants { - script_bytes, - positions, - new_values, - }); + let subst_const = Expr::SubstConstants( + SubstConstants { + script_bytes, + positions, + new_values, + } + .into(), + ); let x: Value = try_eval_out_wo_ctx(&subst_const).unwrap(); if let Value::Coll(CollKind::NativeColl(NativeColl::CollByte(b))) = x { diff --git a/ergotree-interpreter/src/eval/tree_lookup.rs b/ergotree-interpreter/src/eval/tree_lookup.rs index 379dfefb9..b4a8e7ae3 100644 --- a/ergotree-interpreter/src/eval/tree_lookup.rs +++ b/ergotree-interpreter/src/eval/tree_lookup.rs @@ -14,7 +14,7 @@ use scorex_crypto_avltree::operation::Operation; use sigma_util::AsVecU8; impl Evaluable for TreeLookup { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let normalized_tree_val = self .tree .eval(env, ctx)? diff --git a/ergotree-interpreter/src/eval/tuple.rs b/ergotree-interpreter/src/eval/tuple.rs index 0a1ea5261..7ed505b22 100644 --- a/ergotree-interpreter/src/eval/tuple.rs +++ b/ergotree-interpreter/src/eval/tuple.rs @@ -7,7 +7,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for Tuple { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let items_v = self.items.try_mapped_ref(|i| i.eval(env, ctx)); Ok(Value::Tup(items_v?)) } diff --git a/ergotree-interpreter/src/eval/upcast.rs b/ergotree-interpreter/src/eval/upcast.rs index 878e0a66f..647cdf1f1 100644 --- a/ergotree-interpreter/src/eval/upcast.rs +++ b/ergotree-interpreter/src/eval/upcast.rs @@ -69,7 +69,7 @@ fn upcast_to_byte(in_v: Value) -> Result { } impl Evaluable for Upcast { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; match self.tpe { SType::SBigInt => upcast_to_bigint(input_v), diff --git a/ergotree-interpreter/src/eval/val_use.rs b/ergotree-interpreter/src/eval/val_use.rs index fa647df5a..53ddfa9ae 100644 --- a/ergotree-interpreter/src/eval/val_use.rs +++ b/ergotree-interpreter/src/eval/val_use.rs @@ -7,7 +7,7 @@ use crate::eval::EvalError; use crate::eval::Evaluable; impl Evaluable for ValUse { - fn eval(&self, env: &Env, _ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, _ctx: &mut EvalContext) -> Result { env.get(self.val_id).cloned().ok_or_else(|| { EvalError::NotFound(format!("no value in env for id: {0:?}", self.val_id)) }) diff --git a/ergotree-interpreter/src/eval/xor.rs b/ergotree-interpreter/src/eval/xor.rs index 88a51ef84..254c3e208 100644 --- a/ergotree-interpreter/src/eval/xor.rs +++ b/ergotree-interpreter/src/eval/xor.rs @@ -14,7 +14,7 @@ fn helper_xor(mut x: Vec, y: Vec) -> Vec { } impl Evaluable for Xor { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let left_v = self.left.eval(env, ctx)?; let right_v = self.right.eval(env, ctx)?; diff --git a/ergotree-interpreter/src/eval/xor_of.rs b/ergotree-interpreter/src/eval/xor_of.rs index 028122074..8e7ca385c 100644 --- a/ergotree-interpreter/src/eval/xor_of.rs +++ b/ergotree-interpreter/src/eval/xor_of.rs @@ -8,7 +8,7 @@ use crate::eval::Evaluable; use ergotree_ir::mir::xor_of::XorOf; impl Evaluable for XorOf { - fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result { + fn eval(&self, env: &mut Env, ctx: &mut EvalContext) -> Result { let input_v = self.input.eval(env, ctx)?; let input_v_bools = input_v.try_extract_into::>()?; Ok(input_v_bools.into_iter().fold(false, |a, b| a ^ b).into()) diff --git a/ergotree-interpreter/src/sigma_protocol/prover.rs b/ergotree-interpreter/src/sigma_protocol/prover.rs index 61a9b5324..3f016b5e9 100644 --- a/ergotree-interpreter/src/sigma_protocol/prover.rs +++ b/ergotree-interpreter/src/sigma_protocol/prover.rs @@ -132,7 +132,7 @@ pub trait Prover { /// The comments in this section are taken from the algorithm for the /// Sigma-protocol prover as described in the ErgoScript white-paper /// , Appendix A - /// + /// /// Generate proofs for the given message for ErgoTree reduced to Sigma boolean expression fn prove( &self, @@ -145,7 +145,6 @@ pub trait Prover { let expr = tree.proposition()?; let ctx_ext = ctx.extension.clone(); let reduction_result = reduce_to_crypto(&expr, env, ctx).map_err(ProverError::EvalError)?; - self.generate_proof(reduction_result.sigma_prop, message, hints_bag) .map(|p| ProverResult { proof: p, diff --git a/ergotree-interpreter/src/sigma_protocol/prover/context_extension.rs b/ergotree-interpreter/src/sigma_protocol/prover/context_extension.rs index 09aed3e41..ed245147f 100644 --- a/ergotree-interpreter/src/sigma_protocol/prover/context_extension.rs +++ b/ergotree-interpreter/src/sigma_protocol/prover/context_extension.rs @@ -7,6 +7,7 @@ use ergotree_ir::serialization::SigmaSerializable; use ergotree_ir::serialization::SigmaSerializeResult; use indexmap::IndexMap; use std::convert::TryFrom; +use std::fmt; use thiserror::Error; /// User-defined variables to be put into context @@ -25,6 +26,12 @@ impl ContextExtension { } } +impl fmt::Display for ContextExtension { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_map().entries(self.values.iter()).finish() + } +} + impl SigmaSerializable for ContextExtension { fn sigma_serialize(&self, w: &mut W) -> SigmaSerializeResult { w.put_u8(self.values.len() as u8)?; diff --git a/ergotree-ir/Cargo.toml b/ergotree-ir/Cargo.toml index 7711bdaf5..7ce35189d 100644 --- a/ergotree-ir/Cargo.toml +++ b/ergotree-ir/Cargo.toml @@ -6,9 +6,7 @@ authors = ["Denys Zadorozhnyi "] repository.workspace = true edition.workspace = true description = "ErgoTree IR, serialization" -exclude = [ - "proptest-regressions/*" -] +exclude = ["proptest-regressions/*"] [lib] crate-type = ["cdylib", "rlib"] @@ -22,7 +20,7 @@ elliptic-curve = { workspace = true } thiserror = { workspace = true } lazy_static = { workspace = true } derive_more = { workspace = true } -proptest = { workspace = true , optional = true } +proptest = { workspace = true, optional = true } proptest-derive = { workspace = true, optional = true } bs58 = { workspace = true } base16 = { workspace = true } @@ -32,13 +30,14 @@ num-traits = { workspace = true } num-derive = { workspace = true } num-integer = { workspace = true } indexmap = { workspace = true } -serde = { workspace = true , optional = true } +serde = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } serde_with = { workspace = true, optional = true } num256 = "0.3.1" impl-trait-for-tuples = "0.2.0" strum = "0.21" strum_macros = "0.21" +miette = { workspace = true } [features] default = ["json"] @@ -49,3 +48,4 @@ json = ["serde", "serde_json", "serde_with", "bounded-vec/serde"] sigma-test-util = { workspace = true } rand = { workspace = true } pretty_assertions = { workspace = true } +expect-test = { workspace = true } diff --git a/ergotree-ir/src/chain/address.rs b/ergotree-ir/src/chain/address.rs index ebd2dcdb2..43e2b9b5f 100644 --- a/ergotree-ir/src/chain/address.rs +++ b/ergotree-ir/src/chain/address.rs @@ -22,6 +22,7 @@ use crate::sigma_protocol::sigma_boolean::ProveDlog; use crate::sigma_protocol::sigma_boolean::SigmaBoolean; use crate::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree; use crate::sigma_protocol::sigma_boolean::SigmaProp; +use crate::source_span::Spanned; use crate::types::stype::SType; use ergo_chain_types::EcPoint; @@ -110,12 +111,21 @@ impl Address { if let [Expr::BoolToSigmaProp(BoolToSigmaProp { input }), Expr::DeserializeContext(DeserializeContext { tpe, id })] = items.as_slice() { - if let (Expr::BinOp(BinOp { kind, left, right }), SType::SSigmaProp, 1) = - (*input.clone(), tpe.clone(), id) + if let ( + Expr::BinOp(Spanned { + source_span: _, + expr: BinOp { kind, left, right }, + }), + SType::SSigmaProp, + 1, + ) = (*input.clone(), tpe.clone(), id) { if let ( Relation(RelationOp::Eq), - Expr::Slice(Slice { input, from, until }), + Expr::Slice(Spanned { + expr: Slice { input, from, until }, + source_span: _, + }), Expr::Const(Constant { v, .. }), ) = (kind, *left, *right) { @@ -200,23 +210,32 @@ impl Address { } Address::P2S(bytes) => ErgoTree::sigma_parse_bytes(bytes), Address::P2SH(script_hash) => { - let get_var_expr = Expr::GetVar(GetVar { - var_id: 1, - var_tpe: SType::SColl(Box::new(SType::SByte)), - }); + let get_var_expr = Expr::GetVar( + GetVar { + var_id: 1, + var_tpe: SType::SColl(Box::new(SType::SByte)), + } + .into(), + ); let hash_expr = Expr::CalcBlake2b256(CalcBlake2b256 { input: Box::new(get_var_expr), }); - let slice_expr = Expr::Slice(Slice { - input: Box::new(hash_expr), - from: Box::new(0i32.into()), - until: Box::new(24i32.into()), - }); - let hash_equals = Expr::BinOp(BinOp { - kind: Relation(RelationOp::Eq), - left: Box::new(slice_expr), - right: Box::new(Expr::Const(Constant::from(script_hash.to_vec()))), - }); + let slice_expr = Expr::Slice( + Slice { + input: Box::new(hash_expr), + from: Box::new(0i32.into()), + until: Box::new(24i32.into()), + } + .into(), + ); + let hash_equals = Expr::BinOp( + BinOp { + kind: Relation(RelationOp::Eq), + left: Box::new(slice_expr), + right: Box::new(Expr::Const(Constant::from(script_hash.to_vec()))), + } + .into(), + ); let script_is_correct = Expr::DeserializeContext(DeserializeContext { tpe: SType::SSigmaProp, id: 1, diff --git a/ergotree-ir/src/chain/ergo_box/register/id.rs b/ergotree-ir/src/chain/ergo_box/register/id.rs index 6fefa624e..65355bdb4 100644 --- a/ergotree-ir/src/chain/ergo_box/register/id.rs +++ b/ergotree-ir/src/chain/ergo_box/register/id.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use derive_more::From; use thiserror::Error; @@ -52,8 +54,17 @@ impl TryFrom for RegisterId { } } +impl Display for RegisterId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RegisterId::MandatoryRegisterId(id) => write!(f, "{}", id), + RegisterId::NonMandatoryRegisterId(id) => write!(f, "{}", id), + } + } +} + /// Register ids that every box have (box properties exposed as registers) -#[derive(PartialEq, Eq, Debug, Clone, Copy)] +#[derive(PartialEq, Eq, Debug, Clone, Copy, derive_more::Display)] pub enum MandatoryRegisterId { /// Monetary value, in Ergo tokens R0 = 0, @@ -80,7 +91,7 @@ impl TryFrom for MandatoryRegisterId { } /// newtype for additional registers R4 - R9 -#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)] +#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, derive_more::Display)] #[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "json", serde(into = "String", try_from = "String"))] #[repr(u8)] diff --git a/ergotree-ir/src/lib.rs b/ergotree-ir/src/lib.rs index 9f81748ec..b30bff3ae 100644 --- a/ergotree-ir/src/lib.rs +++ b/ergotree-ir/src/lib.rs @@ -26,8 +26,10 @@ pub mod bigint256; pub mod chain; pub mod ergo_tree; pub mod mir; +pub mod pretty_printer; pub mod serialization; pub mod sigma_protocol; +pub mod source_span; pub mod type_check; pub mod types; pub mod util; diff --git a/ergotree-ir/src/mir/bin_op.rs b/ergotree-ir/src/mir/bin_op.rs index 102cf9b25..cc82e4d8c 100644 --- a/ergotree-ir/src/mir/bin_op.rs +++ b/ergotree-ir/src/mir/bin_op.rs @@ -1,5 +1,7 @@ //! Operators in ErgoTree +use std::fmt::Display; + use super::expr::Expr; use crate::has_opcode::HasOpCode; use crate::serialization::op_code::OpCode; @@ -45,6 +47,20 @@ impl From for OpCode { } } +impl Display for ArithOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ArithOp::Plus => write!(f, "+"), + ArithOp::Minus => write!(f, "-"), + ArithOp::Multiply => write!(f, "*"), + ArithOp::Divide => write!(f, "/"), + ArithOp::Max => write!(f, "max"), + ArithOp::Min => write!(f, "min"), + ArithOp::Modulo => write!(f, "%"), + } + } +} + /// Relational operations #[derive(PartialEq, Eq, Debug, Clone, Copy)] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] @@ -76,6 +92,19 @@ impl From for OpCode { } } +impl Display for RelationOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RelationOp::Eq => write!(f, "=="), + RelationOp::NEq => write!(f, "!="), + RelationOp::Ge => write!(f, ">="), + RelationOp::Gt => write!(f, ">"), + RelationOp::Le => write!(f, "<="), + RelationOp::Lt => write!(f, "<"), + } + } +} + /// Logical operations #[derive(PartialEq, Eq, Debug, Clone, Copy)] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] @@ -98,6 +127,16 @@ impl From for OpCode { } } +impl Display for LogicalOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LogicalOp::And => write!(f, "&&"), + LogicalOp::Or => write!(f, "||"), + LogicalOp::Xor => write!(f, "^"), + } + } +} + /// Bitwise operations #[derive(PartialEq, Eq, Debug, Clone, Copy)] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] @@ -120,6 +159,16 @@ impl From for OpCode { } } +impl Display for BitOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BitOp::BitOr => write!(f, "|"), + BitOp::BitAnd => write!(f, "&"), + BitOp::BitXor => write!(f, "^"), + } + } +} + /// Binary operations #[derive(PartialEq, Eq, Debug, Clone, Copy, From)] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] @@ -145,6 +194,17 @@ impl From for OpCode { } } +impl Display for BinOpKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BinOpKind::Arith(op) => write!(f, "{}", op), + BinOpKind::Relation(op) => write!(f, "{}", op), + BinOpKind::Logical(op) => write!(f, "{}", op), + BinOpKind::Bit(op) => write!(f, "{}", op), + } + } +} + /// Binary operation #[derive(PartialEq, Eq, Debug, Clone)] pub struct BinOp { @@ -292,19 +352,22 @@ mod tests { let e = Expr::sigma_parse_bytes(&[0xed, 0x85, 0x03]); assert_eq!( e, - Ok(Expr::BinOp(BinOp { - kind: BinOpKind::Logical(LogicalOp::And,), - left: Expr::Const(Constant { - tpe: SType::SBoolean, - v: Boolean(true), - }) - .into(), - right: Expr::Const(Constant { - tpe: SType::SBoolean, - v: Boolean(true), - }) - .into(), - })) + Ok(Expr::BinOp( + BinOp { + kind: BinOpKind::Logical(LogicalOp::And,), + left: Expr::Const(Constant { + tpe: SType::SBoolean, + v: Boolean(true), + }) + .into(), + right: Expr::Const(Constant { + tpe: SType::SBoolean, + v: Boolean(true), + }) + .into(), + } + .into() + )) ); } } diff --git a/ergotree-ir/src/mir/block.rs b/ergotree-ir/src/mir/block.rs index e7870f8a6..186771970 100644 --- a/ergotree-ir/src/mir/block.rs +++ b/ergotree-ir/src/mir/block.rs @@ -87,7 +87,7 @@ pub mod tests { #[test] fn ser_roundtrip(block in any::()) { - let e = Expr::BlockValue(block); + let e = Expr::BlockValue(block.into()); prop_assert_eq![sigma_serialize_roundtrip(&e), e]; } } diff --git a/ergotree-ir/src/mir/collection.rs b/ergotree-ir/src/mir/collection.rs index 82f650e10..574a082bd 100644 --- a/ergotree-ir/src/mir/collection.rs +++ b/ergotree-ir/src/mir/collection.rs @@ -51,6 +51,11 @@ impl Collection { } } + /// Create a collection from a vector of booleans + pub fn from_bools(bools: Vec) -> Self { + Collection::BoolConstants(bools) + } + /// Type pub fn tpe(&self) -> SType { SType::SColl( diff --git a/ergotree-ir/src/mir/constant.rs b/ergotree-ir/src/mir/constant.rs index e3ff14b09..78f51a549 100644 --- a/ergotree-ir/src/mir/constant.rs +++ b/ergotree-ir/src/mir/constant.rs @@ -88,6 +88,12 @@ impl std::fmt::Debug for Constant { } } +impl std::fmt::Display for Constant { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.v.fmt(f) + } +} + impl std::fmt::Debug for Literal { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { @@ -112,6 +118,63 @@ impl std::fmt::Debug for Literal { } } +impl std::fmt::Display for Literal { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Literal::Coll(CollKind::NativeColl(NativeColl::CollByte(i8_bytes))) => { + write!(f, "Coll[Byte](")?; + for (i, b) in i8_bytes.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", b)?; + } + write!(f, ")") + } + Literal::Coll(CollKind::WrappedColl { elem_tpe, items }) => { + write!(f, "Coll[{}](", elem_tpe)?; + for (i, item) in items.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + item.fmt(f)?; + } + write!(f, ")") + } + Literal::Opt(boxed_opt) => { + if let Some(v) = &**boxed_opt { + write!(f, "Some(")?; + v.fmt(f)?; + write!(f, ")") + } else { + write!(f, "None") + } + } + Literal::Tup(items) => { + write!(f, "(")?; + for (i, item) in items.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + item.fmt(f)?; + } + write!(f, ")") + } + Literal::Unit => write!(f, "()"), + Literal::Boolean(v) => v.fmt(f), + Literal::Byte(v) => v.fmt(f), + Literal::Short(v) => v.fmt(f), + Literal::Int(v) => v.fmt(f), + Literal::Long(v) => write!(f, "{}L", v), + Literal::BigInt(v) => v.fmt(f), + Literal::SigmaProp(v) => v.fmt(f), + Literal::GroupElement(v) => v.fmt(f), + Literal::AvlTree(v) => write!(f, "AvlTree({:?})", v), + Literal::CBox(v) => write!(f, "ErgoBox({:?})", v), + } + } +} + impl From<()> for Literal { fn from(_: ()) -> Literal { Literal::Unit diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs index d75cee57d..f508c5009 100644 --- a/ergotree-ir/src/mir/expr.rs +++ b/ergotree-ir/src/mir/expr.rs @@ -3,6 +3,9 @@ use std::convert::TryFrom; use std::convert::TryInto; +use crate::pretty_printer::PosTrackingWriter; +use crate::pretty_printer::Print; +use crate::source_span::Spanned; use crate::types::stype::LiftIntoSType; use crate::types::stype::SType; @@ -84,17 +87,17 @@ use thiserror::Error; /// Expression in ErgoTree pub enum Expr { /// Append - Concatenation of two collections - Append(Append), + Append(Spanned), /// Constant value Const(Constant), /// Placeholder for a constant ConstPlaceholder(ConstantPlaceholder), /// Substitute constants in serialized ergo tree - SubstConstants(SubstConstants), + SubstConstants(Spanned), /// Convert byte array to SLong - ByteArrayToLong(ByteArrayToLong), + ByteArrayToLong(Spanned), /// Convert byte array to SLong - ByteArrayToBigInt(ByteArrayToBigInt), + ByteArrayToBigInt(Spanned), /// Convert SLong to a byte array LongToByteArray(LongToByteArray), /// Collection declaration (array of expressions of the same type) @@ -117,43 +120,43 @@ pub enum Expr { /// Function application Apply(Apply), /// Method call - MethodCall(MethodCall), + MethodCall(Spanned), /// Property call - ProperyCall(PropertyCall), + PropertyCall(Spanned), /// Block (statements, followed by an expression) - BlockValue(BlockValue), + BlockValue(Spanned), /// let-bound expression - ValDef(ValDef), + ValDef(Spanned), /// Reference to ValDef ValUse(ValUse), /// If, non-lazy - evaluate both branches If(If), /// Binary operation - BinOp(BinOp), + BinOp(Spanned), /// Logical AND - And(And), + And(Spanned), /// Logical OR - Or(Or), + Or(Spanned), /// Byte-wise XOR Xor(Xor), /// THRESHOLD composition for sigma expressions Atleast(Atleast), /// LogicalNot - LogicalNot(LogicalNot), + LogicalNot(Spanned), /// Negation on numeric type - Negation(Negation), + Negation(Spanned), /// Bit inversion on numeric type BitInversion(BitInversion), /// Option.get method - OptionGet(OptionGet), + OptionGet(Spanned), /// Option.isDefined method - OptionIsDefined(OptionIsDefined), + OptionIsDefined(Spanned), /// Returns the option's value if the option is nonempty, otherwise return the result of evaluating `default`. - OptionGetOrElse(OptionGetOrElse), + OptionGetOrElse(Spanned), /// Box monetary value ExtractAmount(ExtractAmount), /// Extract register's value (box.RX properties) - ExtractRegisterAs(ExtractRegisterAs), + ExtractRegisterAs(Spanned), /// Extract serialized box bytes ExtractBytes(ExtractBytes), /// Extract serialized box bytes excluding transaction_id & index @@ -166,23 +169,23 @@ pub enum Expr { /// Box id, Blake2b256 hash of this box's content, basically equals to `blake2b256(bytes)` ExtractId(ExtractId), /// Collection, get element by index - ByIndex(ByIndex), + ByIndex(Spanned), /// Collection size SizeOf(SizeOf), /// Collection slice - Slice(Slice), + Slice(Spanned), /// Collection fold op - Fold(Fold), + Fold(Spanned), /// Collection map op - Map(Map), + Map(Spanned), /// Collection filter op - Filter(Filter), + Filter(Spanned), /// Tests whether a predicate holds for at least one element of this collection - Exists(Exists), + Exists(Spanned), /// Tests whether a predicate holds for all elements of this collection. - ForAll(ForAll), + ForAll(Spanned), /// Tuple field access - SelectField(SelectField), + SelectField(Spanned), /// Bool to SigmaProp BoolToSigmaProp(BoolToSigmaProp), /// Upcast numeric value @@ -202,7 +205,7 @@ pub enum Expr { /// OR conjunction for sigma propositions SigmaOr(SigmaOr), /// Extracts Context variable by id and type - GetVar(GetVar), + GetVar(Spanned), /// Extract register of SELF box as `Coll[Byte]`, deserialize it into Value and inline into /// the executing script. DeserializeRegister(DeserializeRegister), @@ -218,7 +221,7 @@ pub enum Expr { /// XOR for collection of booleans XorOf(XorOf), /// Perform a lookup by key in an AVL tree - TreeLookup(TreeLookup), + TreeLookup(Spanned), /// Create an AVL tree CreateAvlTree(CreateAvlTree), } @@ -227,12 +230,12 @@ impl Expr { /// Type of the expression pub fn tpe(&self) -> SType { match self { - Expr::Append(ap) => ap.tpe(), + Expr::Append(ap) => ap.expr().tpe(), Expr::Const(v) => v.tpe.clone(), Expr::Collection(v) => v.tpe(), - Expr::SubstConstants(v) => v.tpe(), - Expr::ByteArrayToLong(v) => v.tpe(), - Expr::ByteArrayToBigInt(v) => v.tpe(), + Expr::SubstConstants(v) => v.expr().tpe(), + Expr::ByteArrayToLong(v) => v.expr().tpe(), + Expr::ByteArrayToBigInt(v) => v.expr().tpe(), Expr::LongToByteArray(v) => v.tpe(), Expr::ConstPlaceholder(v) => v.tpe.clone(), Expr::CalcBlake2b256(v) => v.tpe(), @@ -242,56 +245,56 @@ impl Expr { Expr::GlobalVars(v) => v.tpe(), Expr::FuncValue(v) => v.tpe(), Expr::Apply(v) => v.tpe(), - Expr::MethodCall(v) => v.tpe(), - Expr::ProperyCall(v) => v.tpe(), - Expr::BlockValue(v) => v.tpe(), - Expr::ValDef(v) => v.tpe(), + Expr::MethodCall(v) => v.expr().tpe(), + Expr::PropertyCall(v) => v.expr().tpe(), + Expr::BlockValue(v) => v.expr().tpe(), + Expr::ValDef(v) => v.expr().tpe(), Expr::ValUse(v) => v.tpe.clone(), - Expr::BinOp(v) => v.tpe(), - Expr::OptionGet(v) => v.tpe(), - Expr::ExtractRegisterAs(v) => v.tpe(), - Expr::Fold(v) => v.tpe(), - Expr::SelectField(v) => v.tpe(), + Expr::BinOp(v) => v.expr().tpe(), + Expr::OptionGet(v) => v.expr().tpe(), + Expr::ExtractRegisterAs(v) => v.expr().tpe(), + Expr::Fold(v) => v.expr().tpe(), + Expr::SelectField(v) => v.expr().tpe(), Expr::ExtractAmount(v) => v.tpe(), - Expr::And(v) => v.tpe(), - Expr::Or(v) => v.tpe(), + Expr::And(v) => v.expr().tpe(), + Expr::Or(v) => v.expr().tpe(), Expr::Xor(v) => v.tpe(), Expr::Atleast(v) => v.tpe(), - Expr::LogicalNot(v) => v.tpe(), - Expr::Map(v) => v.tpe(), - Expr::Filter(v) => v.tpe(), + Expr::LogicalNot(v) => v.expr().tpe(), + Expr::Map(v) => v.expr().tpe(), + Expr::Filter(v) => v.expr().tpe(), Expr::BoolToSigmaProp(v) => v.tpe(), Expr::Upcast(v) => v.tpe(), Expr::Downcast(v) => v.tpe(), Expr::If(v) => v.tpe(), - Expr::ByIndex(v) => v.tpe(), + Expr::ByIndex(v) => v.expr().tpe(), Expr::ExtractScriptBytes(v) => v.tpe(), Expr::SizeOf(v) => v.tpe(), - Expr::Slice(v) => v.tpe(), + Expr::Slice(v) => v.expr().tpe(), Expr::CreateProveDlog(v) => v.tpe(), Expr::CreateProveDhTuple(v) => v.tpe(), Expr::ExtractCreationInfo(v) => v.tpe(), - Expr::Exists(v) => v.tpe(), + Expr::Exists(v) => v.expr().tpe(), Expr::ExtractId(v) => v.tpe(), Expr::SigmaPropBytes(v) => v.tpe(), - Expr::OptionIsDefined(v) => v.tpe(), - Expr::OptionGetOrElse(v) => v.tpe(), - Expr::Negation(v) => v.tpe(), + Expr::OptionIsDefined(v) => v.expr().tpe(), + Expr::OptionGetOrElse(v) => v.expr().tpe(), + Expr::Negation(v) => v.expr().tpe(), Expr::BitInversion(v) => v.tpe(), - Expr::ForAll(v) => v.tpe(), + Expr::ForAll(v) => v.expr().tpe(), Expr::Tuple(v) => v.tpe(), Expr::DecodePoint(v) => v.tpe(), Expr::SigmaAnd(v) => v.tpe(), Expr::SigmaOr(v) => v.tpe(), Expr::DeserializeRegister(v) => v.tpe(), Expr::DeserializeContext(v) => v.tpe(), - Expr::GetVar(v) => v.tpe(), + Expr::GetVar(v) => v.expr().tpe(), Expr::MultiplyGroup(v) => v.tpe(), Expr::Exponentiate(v) => v.tpe(), Expr::XorOf(v) => v.tpe(), Expr::ExtractBytes(v) => v.tpe(), Expr::ExtractBytesWithNoRef(v) => v.tpe(), - Expr::TreeLookup(v) => v.tpe(), + Expr::TreeLookup(v) => v.expr().tpe(), Expr::CreateAvlTree(v) => v.tpe(), } } @@ -327,6 +330,14 @@ impl Expr { let tree = format!("{:#?}", self); tree } + + /// Pretty prints the tree + pub fn to_string_pretty(&self) -> String { + let mut printer = PosTrackingWriter::new(); + #[allow(clippy::unwrap_used)] // it only fail due to formatting errors + let _spanned_expr = self.print(&mut printer).unwrap(); + printer.as_string() + } } impl + LiftIntoSType> From for Expr { diff --git a/ergotree-ir/src/mir/func_value.rs b/ergotree-ir/src/mir/func_value.rs index e313a35e0..83ab57552 100644 --- a/ergotree-ir/src/mir/func_value.rs +++ b/ergotree-ir/src/mir/func_value.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crate::has_opcode::HasStaticOpCode; use crate::serialization::op_code::OpCode; use crate::serialization::sigma_byte_reader::SigmaByteRead; @@ -37,6 +39,12 @@ impl SigmaSerializable for FuncArg { } } +impl std::fmt::Display for FuncArg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "v{}: {}", self.idx, self.tpe) + } +} + /// User-defined function #[derive(PartialEq, Eq, Debug, Clone)] pub struct FuncValue { diff --git a/ergotree-ir/src/mir/global_vars.rs b/ergotree-ir/src/mir/global_vars.rs index b2b40c196..ce5a35708 100644 --- a/ergotree-ir/src/mir/global_vars.rs +++ b/ergotree-ir/src/mir/global_vars.rs @@ -1,5 +1,7 @@ //! Global variables +use std::fmt::Display; + use crate::has_opcode::HasOpCode; use crate::serialization::op_code::OpCode; use crate::types::stype::SType; @@ -49,6 +51,19 @@ impl HasOpCode for GlobalVars { } } +impl Display for GlobalVars { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + GlobalVars::SelfBox => write!(f, "SELF"), + GlobalVars::Inputs => write!(f, "INPUTS"), + GlobalVars::Outputs => write!(f, "OUTPUTS"), + GlobalVars::Height => write!(f, "HEIGHT"), + GlobalVars::MinerPubKey => write!(f, "MINER_PUBKEY"), + GlobalVars::GroupGenerator => write!(f, "GROUP_GENERATOR"), + } + } +} + #[cfg(test)] #[cfg(feature = "arbitrary")] mod tests { diff --git a/ergotree-ir/src/mir/select_field.rs b/ergotree-ir/src/mir/select_field.rs index c84dc42b1..02f5bd306 100644 --- a/ergotree-ir/src/mir/select_field.rs +++ b/ergotree-ir/src/mir/select_field.rs @@ -14,7 +14,7 @@ use super::expr::InvalidArgumentError; use crate::has_opcode::HasStaticOpCode; /// Tuple field access index (1..=255) -#[derive(PartialEq, Eq, Debug, Copy, Clone)] +#[derive(PartialEq, Eq, Debug, Copy, Clone, derive_more::Display)] pub struct TupleFieldIndex(u8); /// Error for tuple index being out of bounds (1..=255) diff --git a/ergotree-ir/src/mir/val_def.rs b/ergotree-ir/src/mir/val_def.rs index 4858c5ad0..b2f5c0580 100644 --- a/ergotree-ir/src/mir/val_def.rs +++ b/ergotree-ir/src/mir/val_def.rs @@ -9,6 +9,7 @@ use crate::types::stype::SType; use super::expr::Expr; extern crate derive_more; +use derive_more::Display; use derive_more::From; use crate::has_opcode::HasStaticOpCode; @@ -16,7 +17,7 @@ use crate::has_opcode::HasStaticOpCode; use proptest_derive::Arbitrary; /// Variable id -#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, From)] +#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, From, Display)] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub struct ValId(pub u32); @@ -86,7 +87,7 @@ mod tests { #[test] fn ser_roundtrip(v in any::()) { - let e = Expr::ValDef(v); + let e = Expr::ValDef(v.into()); prop_assert_eq![sigma_serialize_roundtrip(&e), e]; } } diff --git a/ergotree-ir/src/pretty_printer.rs b/ergotree-ir/src/pretty_printer.rs new file mode 100644 index 000000000..234eb8358 --- /dev/null +++ b/ergotree-ir/src/pretty_printer.rs @@ -0,0 +1,733 @@ +//! Pretty printer for ErgoTree IR + +use std::fmt::Write; + +mod print; +pub use print::Print; + +// TODO: extract to a separate module +/// Printer trait with tracking of current position and indent +pub trait Printer: Write { + /// Current position (last printed char) + fn current_pos(&self) -> usize; + /// Increase indent + fn inc_ident(&mut self); + /// Decrease indent + fn dec_ident(&mut self); + /// Get current indent + fn get_indent(&self) -> usize; + /// Print the current indent + fn print_indent(&mut self) -> std::fmt::Result { + write!(self, "{:indent$}", "", indent = self.get_indent()) + } +} + +/// Printer implementation with tracking of current position and indent +pub struct PosTrackingWriter { + print_buf: String, + current_pos: usize, + current_indent: usize, +} + +impl Write for PosTrackingWriter { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + let len = s.len(); + self.current_pos += len; + write!(self.print_buf, "{}", s) + } +} + +impl Printer for PosTrackingWriter { + fn current_pos(&self) -> usize { + self.current_pos + } + + fn inc_ident(&mut self) { + self.current_indent += Self::INDENT; + } + + fn dec_ident(&mut self) { + self.current_indent -= Self::INDENT; + } + + fn get_indent(&self) -> usize { + self.current_indent + } +} + +impl PosTrackingWriter { + const INDENT: usize = 2; + + /// Create new printer + pub fn new() -> Self { + Self { + print_buf: String::new(), + current_pos: 0, + current_indent: 0, + } + } + + /// Get printed buffer + pub fn get_buf(&self) -> &str { + &self.print_buf + } + + /// Get printed buffer as String + pub fn as_string(self) -> String { + self.print_buf + } +} + +impl Default for PosTrackingWriter { + fn default() -> Self { + Self::new() + } +} + +#[allow(clippy::unwrap_used)] +#[cfg(test)] +mod tests { + + use expect_test::expect; + + use crate::chain::address::AddressEncoder; + use crate::chain::address::NetworkPrefix; + use crate::ergo_tree::ErgoTree; + use crate::mir::bin_op::ArithOp; + use crate::mir::bin_op::BinOp; + use crate::mir::block::BlockValue; + use crate::mir::expr::Expr; + use crate::mir::val_def::ValDef; + use crate::mir::val_use::ValUse; + use crate::serialization::SigmaSerializable; + use crate::types::stype::SType; + + use super::*; + + fn check_pretty(expr: Expr, expected_tree: expect_test::Expect) { + let print_buf = String::new(); + let mut w = PosTrackingWriter { + print_buf, + current_pos: 0, + current_indent: 0, + }; + let _ = expr.print(&mut w).unwrap(); + expected_tree.assert_eq(w.get_buf()); + } + + fn check_spans(expr: Expr, expected_tree: expect_test::Expect) { + let print_buf = String::new(); + let mut w = PosTrackingWriter { + print_buf, + current_pos: 0, + current_indent: 0, + }; + let spanned_expr = expr.print(&mut w).unwrap(); + expected_tree.assert_eq(format!("{:?}", spanned_expr).as_str()); + } + + #[test] + fn print_block() { + let val_id = 1.into(); + let expr = Expr::BlockValue( + BlockValue { + items: vec![ValDef { + id: val_id, + rhs: Box::new(Expr::Const(1i32.into())), + } + .into()], + result: Box::new( + ValUse { + val_id, + tpe: SType::SInt, + } + .into(), + ), + } + .into(), + ); + check_pretty( + expr, + expect![[r#" + { + val v1 = 1 + v1 + } + "#]], + ); + } + + #[test] + fn print_binop() { + let val_id = 1.into(); + let expr = Expr::BlockValue( + BlockValue { + items: vec![ValDef { + id: val_id, + rhs: Box::new( + BinOp { + kind: ArithOp::Divide.into(), + left: Expr::Const(4i32.into()).into(), + right: Expr::Const(2i32.into()).into(), + } + .into(), + ), + } + .into()], + result: Box::new( + ValUse { + val_id, + tpe: SType::SInt, + } + .into(), + ), + } + .into(), + ); + check_pretty( + expr.clone(), + expect![[r#" + { + val v1 = 4 / 2 + v1 + } + "#]], + ); + + check_spans( + expr, + expect![[ + r#"BlockValue(Spanned { source_span: SourceSpan { offset: 0, length: 26 }, expr: BlockValue { items: [ValDef(Spanned { source_span: SourceSpan { offset: 4, length: 14 }, expr: ValDef { id: ValId(1), rhs: BinOp(Spanned { source_span: SourceSpan { offset: 13, length: 5 }, expr: BinOp { kind: Arith(Divide), left: Const("4: SInt"), right: Const("2: SInt") } }) } })], result: ValUse(ValUse { val_id: ValId(1), tpe: SInt }) } })"# + ]], + ); + } + + #[test] + fn eip23_refresh_contract() { + let ergo_tree_bytes = base16::decode("1016043c040004000e202a472d4a614e645267556b58703273357638792f423f4528482b4d625065536801000502010105000400040004020402040204080400040a05c8010e20472b4b6250655368566d597133743677397a24432646294a404d635166546a570400040404020408d80ed60199a37300d602b2a4730100d603b5a4d901036395e6c672030605eded928cc77203017201938cb2db6308720373020001730393e4c672030504e4c6720205047304d604b17203d605b0720386027305860273067307d901053c413d0563d803d607e4c68c7205020605d6088c720501d6098c720802860272078602ed8c720901908c72080172079a8c7209027207d6068c720502d6078c720501d608db63087202d609b27208730800d60ab2a5730900d60bdb6308720ad60cb2720b730a00d60db27208730b00d60eb2a5730c00ea02ea02ea02ea02ea02ea02ea02ea02ea02ea02ea02ea02ea02ea02ea02ea02ea02cde4c6b27203e4e30004000407d18f8cc77202017201d1927204730dd18c720601d190997207e4c6b27203730e0006059d9c72077e730f057310d1938c7209017311d193b2720b7312007209d1938c720c018c720d01d1928c720c02998c720d027e9c7204731305d193b1720bb17208d193e4c6720a04059d8c7206027e720405d193e4c6720a05049ae4c6720205047314d193c2720ac27202d192c1720ac17202d1928cc7720a0199a37315d193db6308720edb6308a7d193c2720ec2a7d192c1720ec1a7").unwrap(); + let ergo_tree = ErgoTree::sigma_parse_bytes(&ergo_tree_bytes).unwrap(); + check_pretty( + ergo_tree.proposition().unwrap(), + expect![[r#" + { + val v1 = HEIGHT - 30 + val v2 = INPUTS(0) + val v3 = INPUTS.filter({ + (v3: Box) => + if (v3.getReg(6).isDefined()) v3.creationInfo._1 >= v1 && v3.tokens(0)._1 == "2a472d4a614e645267556b58703273357638792f423f4528482b4d6250655368" && v3.getReg(5).get == v2.getReg(5).get else false + } + ) + val v4 = v3.size + val v5 = v3.fold((, 1, (, true, 0)))({ + (v5: ((Long, (Boolean, Long)), Box)) => + { + val v7 = v5._2.getReg(6).get + val v8 = v5._1 + val v9 = v8._2 + (, v7, (, v9._1 && v8._1 <= v7, v9._2 + v7)) + } + + } + ) + val v6 = v5._2 + val v7 = v5._1 + val v8 = v2.tokens + val v9 = v8(0) + val v10 = OUTPUTS(0) + val v11 = v10.tokens + val v12 = v11(1) + val v13 = v8(1) + val v14 = OUTPUTS(1) + allOf( + allOf( + allOf( + allOf( + allOf( + allOf( + allOf( + allOf( + allOf( + allOf( + allOf( + allOf( + allOf( + allOf( + allOf( + allOf( + allOf( + proveDlog(v3(getVar(0).get).getReg(4).get), + sigmaProp(v2.creationInfo._1 < v1), + ), + sigmaProp(v4 >= 4), + ), + sigmaProp(v6._1), + ), + sigmaProp(v7 - v3(0).getReg(6).get <= v7 * upcast(5) / 100), + ), + sigmaProp(v9._1 == "472b4b6250655368566d597133743677397a24432646294a404d635166546a57"), + ), + sigmaProp(v11(0) == v9), + ), + sigmaProp(v12._1 == v13._1), + ), + sigmaProp(v12._2 >= v13._2 - upcast(v4 * 2)), + ), + sigmaProp(v11.size == v8.size), + ), + sigmaProp(v10.getReg(4).get == v6._2 / upcast(v4)), + ), + sigmaProp(v10.getReg(5).get == v2.getReg(5).get + 1), + ), + sigmaProp(v10.propBytes == v2.propBytes), + ), + sigmaProp(v10.value >= v2.value), + ), + sigmaProp(v10.creationInfo._1 >= HEIGHT - 4), + ), + sigmaProp(v14.tokens == SELF.tokens), + ), + sigmaProp(v14.propBytes == SELF.propBytes), + ), + sigmaProp(v14.value >= SELF.value), + ) + } + "#]], + ) + } + + #[test] + fn eip23_update_contract() { + let ergo_tree_bytes = base16::decode("100f0400040004000402040204020e20472b4b6250655368566d597133743677397a24432646294a404d635166546a570400040004000e203f4428472d4b6150645367566b5970337336763979244226452948404d625165010005000400040cd80ad601b2a4730000d602db63087201d603b27202730100d604b2a5730200d605db63087204d606b2a5730300d607b27205730400d6088c720701d6098c720702d60ab27202730500d1ededed938c7203017306edededed937203b2720573070093c17201c1720493c672010405c67204040593c672010504c672040504efe6c672040661edededed93db63087206db6308a793c27206c2a792c17206c1a7918cc77206018cc7a701efe6c67206046192b0b5a4d9010b63d801d60ddb6308720b9591b1720d7308d801d60ec6720b070eededed938cb2720d73090001730a93e4c6720b05048cc7a70193e4c6720b060ecbc2720495ede6720ee6c6720b0805ed93e4720e720893e4c6720b08057209ed938c720a017208938c720a027209730b730cd9010b41639a8c720b018cb2db63088c720b02730d00027e730e05").unwrap(); + let ergo_tree = ErgoTree::sigma_parse_bytes(&ergo_tree_bytes).unwrap(); + check_pretty( + ergo_tree.proposition().unwrap(), + expect![[r#" + { + val v1 = INPUTS(0) + val v2 = v1.tokens + val v3 = v2(0) + val v4 = OUTPUTS(0) + val v5 = v4.tokens + val v6 = OUTPUTS(1) + val v7 = v5(1) + val v8 = v7._1 + val v9 = v7._2 + val v10 = v2(1) + sigmaProp(v3._1 == "472b4b6250655368566d597133743677397a24432646294a404d635166546a57" && v3 == v5(0) && v1.value == v4.value && v1.getReg(4) == v4.getReg(4) && v1.getReg(5) == v4.getReg(5) && !v4.getReg(6).isDefined() && v6.tokens == SELF.tokens && v6.propBytes == SELF.propBytes && v6.value >= SELF.value && v6.creationInfo._1 > SELF.creationInfo._1 && !v6.getReg(4).isDefined() && INPUTS.filter({ + (v11: Box) => + { + val v13 = v11.tokens + if (v13.size > 0) { + val v14 = v11.getReg(7) + v13(0)._1 == "3f4428472d4b6150645367566b5970337336763979244226452948404d625165" && v11.getReg(5).get == SELF.creationInfo._1 && v11.getReg(6).get == blake2b256(v4.propBytes) && if (v14.isDefined() && v11.getReg(8).isDefined()) v14.get == v8 && v11.getReg(8).get == v9 else v10._1 == v8 && v10._2 == v9 + } + else false + } + + } + ).fold(0)({ + (v11: (Long, Box)) => + v11._1 + v11._2.tokens(0)._2 + } + ) >= upcast(6)) + } + "#]], + ) + } + + #[test] + fn eip23_ballot_contract() { + let ergo_tree_bytes = base16::decode("10070580dac409040204020400040204000e206251655468576d5a7134743777217a25432a462d4a404e635266556a586e3272d803d601e4c6a70407d602b2a5e4e3000400d603c672020407eb02cd7201d1edededede6720393c27202c2a793db63087202db6308a792c172027300ededededed91b1a4730191b1db6308b2a47302007303938cb2db6308b2a473040073050001730693e47203720192c17202c1a7efe6c672020561").unwrap(); + let ergo_tree = ErgoTree::sigma_parse_bytes(&ergo_tree_bytes).unwrap(); + check_pretty( + ergo_tree.proposition().unwrap(), + expect![[r#" + { + val v1 = SELF.getReg(4).get + val v2 = OUTPUTS(getVar(0).get) + val v3 = v2.getReg(4) + anyOf( + proveDlog(v1), + sigmaProp(v3.isDefined() && v2.propBytes == SELF.propBytes && v2.tokens == SELF.tokens && v2.value >= 10000000 && INPUTS.size > 1 && INPUTS(1).tokens.size > 0 && INPUTS(1).tokens(0)._1 == "6251655468576d5a7134743777217a25432a462d4a404e635266556a586e3272" && v3.get == v1 && v2.value >= SELF.value && !v2.getReg(5).isDefined()), + ) + } + "#]], + ) + } + + #[test] + fn eip23_oracle_contract() { + let ergo_tree_bytes = base16::decode("100a040004000580dac409040004000e20472b4b6250655368566d597133743677397a24432646294a404d635166546a570402040204020402d804d601b2a5e4e3000400d602db63087201d603db6308a7d604e4c6a70407ea02d1ededed93b27202730000b2720373010093c27201c2a7e6c67201040792c172017302eb02cd7204d1ededededed938cb2db6308b2a4730300730400017305938cb27202730600018cb2720373070001918cb27202730800028cb272037309000293e4c672010407720492c17201c1a7efe6c672010561").unwrap(); + let ergo_tree = ErgoTree::sigma_parse_bytes(&ergo_tree_bytes).unwrap(); + check_pretty( + ergo_tree.proposition().unwrap(), + expect![[r#" + { + val v1 = OUTPUTS(getVar(0).get) + val v2 = v1.tokens + val v3 = SELF.tokens + val v4 = SELF.getReg(4).get + allOf( + sigmaProp(v2(0) == v3(0) && v1.propBytes == SELF.propBytes && v1.getReg(4).isDefined() && v1.value >= 10000000), + anyOf( + proveDlog(v4), + sigmaProp(INPUTS(0).tokens(0)._1 == "472b4b6250655368566d597133743677397a24432646294a404d635166546a57" && v2(1)._1 == v3(1)._1 && v2(1)._2 > v3(1)._2 && v1.getReg(4).get == v4 && v1.value >= SELF.value && !v1.getReg(5).isDefined()), + ), + ) + } + "#]], + ) + } + + #[test] + fn ageusd_bank_full() { + // from eip-15 https://github.com/ergoplatform/eips/pull/27/files + let p2s_addr_str = "MUbV38YgqHy7XbsoXWF5z7EZm524Ybdwe5p9WDrbhruZRtehkRPT92imXer2eTkjwPDfboa1pR3zb3deVKVq3H7Xt98qcTqLuSBSbHb7izzo5jphEpcnqyKJ2xhmpNPVvmtbdJNdvdopPrHHDBbAGGeW7XYTQwEeoRfosXzcDtiGgw97b2aqjTsNFmZk7khBEQywjYfmoDc9nUCJMZ3vbSspnYo3LarLe55mh2Np8MNJqUN9APA6XkhZCrTTDRZb1B4krgFY1sVMswg2ceqguZRvC9pqt3tUUxmSnB24N6dowfVJKhLXwHPbrkHViBv1AKAJTmEaQW2DN1fRmD9ypXxZk8GXmYtxTtrj3BiunQ4qzUCu1eGzxSREjpkFSi2ATLSSDqUwxtRz639sHM6Lav4axoJNPCHbY8pvuBKUxgnGRex8LEGM8DeEJwaJCaoy8dBw9Lz49nq5mSsXLeoC4xpTUmp47Bh7GAZtwkaNreCu74m9rcZ8Di4w1cmdsiK1NWuDh9pJ2Bv7u3EfcurHFVqCkT3P86JUbKnXeNxCypfrWsFuYNKYqmjsix82g9vWcGMmAcu5nagxD4iET86iE2tMMfZZ5vqZNvntQswJyQqv2Wc6MTh4jQx1q2qJZCQe4QdEK63meTGbZNNKMctHQbp3gRkZYNrBtxQyVtNLR8xEY8zGp85GeQKbb37vqLXxRpGiigAdMe3XZA4hhYPmAAU5hpSMYaRAjtvvMT3bNiHRACGrfjvSsEG9G2zY5in2YWz5X9zXQLGTYRsQ4uNFkYoQRCBdjNxGv6R58Xq74zCgt19TxYZ87gPWxkXpWwTaHogG1eps8WXt8QzwJ9rVx6Vu9a5GjtcGsQxHovWmYixgBU8X9fPNJ9UQhYyAWbjtRSuVBtDAmoV1gCBEPwnYVP5GCGhCocbwoYhZkZjFZy6ws4uxVLid3FxuvhWvQrVEDYp7WRvGXbNdCbcSXnbeTrPMey1WPaXX"; + let encoder = AddressEncoder::new(NetworkPrefix::Mainnet); + let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap(); + let expr = addr.script().unwrap().proposition().unwrap(); + check_pretty( + expr, + expect![[r#" + { + val v1 = CONTEXT.dataInputs + sigmaProp(if (v1.size > 0) { + val v2 = v1(0) + val v3 = v2.tokens(0)._1 == "011d3364de07e5a26f0c4eef0852cddb387039a921b7154ef3cab22c6eda887f" + val v4 = OUTPUTS(0) + val v5 = v4.value + val v6 = SELF.tokens + val v7 = v6(1) + val v8 = v7._2 + val v9 = v4.tokens + val v10 = v9(1) + val v11 = v10._2 + val v12 = v8 != v11 + val v13 = v6(0) + val v14 = v13._2 + val v15 = v9(0) + val v16 = v15._2 + val v17 = v14 != v16 + val v18 = SELF.getReg(5).get + val v19 = v4.getReg(5).get + val v20 = SELF.getReg(4).get + val v21 = v4.getReg(4).get + val v22 = OUTPUTS(1) + val v23 = v22.getReg(4).get + val v24 = if (v12) 0 else v23 + val v25 = if (v12) v23 else 0 + val v26 = SELF.value + val v27 = v22.getReg(5).get + val v28 = v2.getReg(4).get / 100 + val v29 = v26 min v20 * v28 max 0 + val v30 = if (v17) v28 min if (v20 == 0) 9223372036854775807 else v29 / v20 * v24 else { + val v30 = v26 - v29 + if (v30 == 0) 1000000 else if (v18 == 0) 1000000 else v30 / v18 * v25 + } + + val v31 = v30 * upcast(2) / 100 + val v32 = v21 * v28 + val v33 = if (HEIGHT > 460000) 800 else 1000000000 + val v34 = if (v32 == 0) v33 else v5 * 100 / v32 + v3 && v5 >= 10000000 && v4.propBytes == SELF.propBytes && v12 || v17 && !v12 && v17 && v8 + v18 == v11 + v19 && v14 + v20 == v16 + v21 && v20 + v24 == v21 && v18 + v25 == v19 && v26 + v27 == v5 && v21 >= 0 && v19 >= 0 && v15._1 == v13._1 && v10._1 == v7._1 && v9(2)._1 == v6(2)._1 && v27 == v30 + if (v31 < 0) -v31 else v31 && if (v17) if (v24 > 0) v34 >= 400 else true else if (v25 > 0) v34 <= v33 else v34 >= 400 && v3 + } + else false || INPUTS(0).tokens(0)._1 == "239c170b7e82f94e6b05416f14b8a2a57e0bfff0e3c93f4abbcd160b6a5b271a") + } + "#]], + ) + } + + #[test] + fn ageusd_update() { + // from eip-15 https://github.com/ergoplatform/eips/pull/27/files + let p2s_addr_str = "VLyjpv3dse3PbatT83GnDkBQasGqY52dAEdi9XpXhuSUn1FS1Tm7XxtAgmBiqY9pJXtEAsDKwX9ygSjrFu7vnUQZudhC2sSmxhxqgD3ZxJ2VsGwmPG77F6EiEZhcq71oqEq31y9XvCCXL5nqqszdENPAVhu7xT296qZ7w1x6hmwdh9ZE89bjfgbhfNYopoqsCaNLWYHJ12TDSY93kaGqCVKSu6gEF1gLpXBfRCnAPPxYswJPmK8oWDn8PKrUGs3MjVsj6bGXiW3VTGP4VsNH8YSSkjyj1FZ9azLsyfnNJ3zah2zUHdCCqY6PjH9JfHf9joCPf6TusvXgr71XWvh5e2HPEPQr4eJMD4S96cGTiSs3J5XcRd1tCDYoiis8nxv99zFFhHgpqXHgeqjhJ5sPot9eRYTsmm4cRTVLXYAiuKPS2qW5"; + let encoder = AddressEncoder::new(NetworkPrefix::Mainnet); + let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap(); + let expr = addr.script().unwrap().proposition().unwrap(); + check_pretty( + expr, + expect![[r#" + { + val v1 = INPUTS(1) + val v2 = v1.tokens + val v3 = OUTPUTS(1) + val v4 = SELF.id + val v5 = OUTPUTS(0) + sigmaProp(v2.size == 3 && v2(2)._1 == "7d672d1def471720ca5782fd6473e47e796d9ac0c138d9911346f118b2f6d9d9" && v2 == v3.tokens && v1.value == v3.value && v1.getReg(4).get == v3.getReg(4).get && v1.getReg(5).get == v3.getReg(5).get && v4 == INPUTS(0).id && SELF.tokens == v5.tokens && SELF.propBytes == v5.propBytes && v5.value >= SELF.value && INPUTS.filter({ + (v6: Box) => + { + val v8 = v6.tokens + v8.size > 0 && v8(0)._1 == "f7995f212216fcf21854f56df7a9a0a9fc9b7ae4c0f1cc40f5b406371286a5e0" && v6.getReg(6).get == v4 && v6.getReg(7).get == blake2b256(v3.propBytes) + } + + } + ).fold(0)({ + (v6: (Long, Box)) => + v6._1 + v6._2.tokens(0)._2 + } + ) >= upcast(3)) + } + "#]], + ) + } + + #[test] + fn ageusd_ballot() { + // from eip-15 https://github.com/ergoplatform/eips/pull/27/files + let p2s_addr_str = "22ELWBHzyWGjPRE48ZJDfFmD24myYdG3vHz8CipSS7rgE65ABmEj9QJiy3rG2PTJeCaZw9VX56GY6uoA3hQch7i5BfFU3AprUWTABi4X1VWtRdK9yrYJkmN6fq8hGfvmWTrsyh4fXZoGETpLuXQViYo194ajej2h7dr3oqNATdMskSXzxJi83bFdAvQ"; + let encoder = AddressEncoder::new(NetworkPrefix::Mainnet); + let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap(); + let expr = addr.script().unwrap().proposition().unwrap(); + check_pretty( + expr, + expect![[r#" + { + val v1 = OUTPUTS(INPUTS.indexOf(SELF0)) + val v2 = SELF.getReg(4).get + allOf( + sigmaProp(v1.getReg(4).get == v2 && v1.propBytes == SELF.propBytes && v1.tokens == SELF.tokens && v1.value >= SELF.value), + anyOf( + proveDlog(v2), + sigmaProp(INPUTS(0).tokens(0)._1 == "239c170b7e82f94e6b05416f14b8a2a57e0bfff0e3c93f4abbcd160b6a5b271a" && !v1.getReg(7).isDefined()), + ), + ) + } + "#]], + ) + } + + #[test] + fn amm_simple_pool() { + // from eip-14 https://github.com/ergoplatform/eips/pull/27/files + let p2s_addr_str = "k6fD5ht5e1itDejPFV2VzAoHv478KQCbDnLAL6XUVeEu8KDaboCVZAoFz2AtMoLqM3CgQfr2TZhpwz7K96AgwTXDvBVeTchJ31jjD46Di1W67H8wwFcivnY62UB6L7HWzCkbYuiZaAq2qSJta5Twt4A2Aaoy7xViWcyLUVNAyQYDJXKhVBAGwp76i2too5yWUmEU4zt9XnjJAUt1FFfurNtTNHNPDbqmTRE4crz347q6rfbvkMmg9Jtk9rSiPCQpKjdbZVzUnP4CUw6AvQH6rZXxgNMktAtjQdHhCnrCmf78FwCKqYS54asKd1MFgYNT4NzPwmdZF6JtQt1vvkjZXqpGkjy33xxDNYy8JZS8eeqVgZErPeJ1aj4aaK8gvmApUgGStMDFeFYjuQqZiZxEAHNdAXDg7hyGnmfzA6Hj9zcB7p9nKCDNhEQEMPL1kMG5aXvt2HUPXqiCkLrv596DaGmRMN3gMJaj1T1AfMYNwZozcJ9uUSK4i6Xham28HWAekTtDPhobnmjvkubwLVTtvUumWHtDWFxYSJPF7vqzgZqg6Y5unMF"; + let encoder = AddressEncoder::new(NetworkPrefix::Mainnet); + let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap(); + let expr = addr.script().unwrap().proposition().unwrap(); + check_pretty( + expr, + expect![[r#" + { + val v1 = OUTPUTS(0) + val v2 = v1.tokens + val v3 = SELF.tokens + val v4 = v2(0) + val v5 = v3(0) + val v6 = v2(2) + val v7 = v3(2) + val v8 = v2(3) + val v9 = v3(3) + val v10 = 1000000000000000000 - v5._2 + val v11 = 1000000000000000000 - v4._2 - v10 + val v12 = v7._2 + val v13 = v6._2 - v12 + val v14 = v13 > 0 + val v15 = v9._2 + val v16 = upcast(v15) + val v17 = upcast(v13) + val v18 = v8._2 - v15 + val v19 = upcast(v12) + val v20 = upcast(v18) + val v21 = upcast(v10) + val v22 = upcast(v11) / v21 + sigmaProp(v1.propBytes == SELF.propBytes && v1.value >= SELF.value && v2(1) == v3(1) && v4._1 == v5._1 && v6._1 == v7._1 && v8._1 == v9._1 && if (v11 == 0) if (v14) v16 * v17 * BigInt256(Int256(997)) >= upcast(-v18) * v19 * BigInt256(Int256(1000)) + upcast(v13 * 997) else v19 * v20 * BigInt256(Int256(997)) >= upcast(-v13) * v16 * BigInt256(Int256(1000)) + upcast(v18 * 997) else if (v14 && v18 > 0) upcast(-v11) <= v17 * v21 / v19 min v20 * v21 / v16 else v17 >= v22 * v19 && v20 >= v22 * v16) + } + "#]], + ) + } + + #[test] + fn amm_simple_swap() { + // from eip-14 https://github.com/ergoplatform/eips/pull/27/files + let p2s_addr_str = "cLPHJ3MHuKAHoCUwGhcEFw5sWJqvPwFyKxTRj1aUoMwgAz78Fg3zLXRhBup9Te1WLau1gZXNmXvUmeXGCd7QLeqB7ArrT3v5cg26piEtqymM6j2SkgYVCobgoAGKeTf6nMLxv1uVrLdjt1GnPxG1MuWj7Es7Dfumotbx9YEaxwqtTUC5SKsJc9LCpAmNWRAQbU6tVVEvmfwWivrGoZ3L5C4DMisxN3U"; + let encoder = AddressEncoder::new(NetworkPrefix::Mainnet); + let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap(); + let expr = addr.script().unwrap().proposition().unwrap(); + check_pretty( + expr, + expect![[r#" + { + val v1 = INPUTS(0).tokens + val v2 = v1(2) + val v3 = SELF.tokens(0) + val v4 = v3._1 + val v5 = v1(3) + val v6 = v3._2 + sigmaProp(v2._1 == v4 || v5._1 == v4 && OUTPUTS.exists({ + (v7: Box) => + { + val v9 = v7.tokens(0)._2 + v9 >= upcast(1000) && upcast(v5._2) * upcast(v6) * BigInt256(Int256(997)) <= upcast(v9) * upcast(v2._2) * BigInt256(Int256(1000)) + upcast(v6 * 997) + } + + } + )) + } + "#]], + ) + } + + #[test] + fn amm_conc_pool_root() { + // from eip-14 https://github.com/ergoplatform/eips/pull/27/files + let p2s_addr_str = "3STRfQWC9Xb5wAxBiEQ74uTFSemk1oHn43mwj9tMCeu2a3A4kie1bY2qsCdRaEmdQoq3B4tXQuzq9nm84A8PmBgCzgGDEZf2pgYoAUc6krZxUY3rvKWW44ZpzN3u5bFRpKDo6rxKtxX2tw99xmfyfaVBejgDaTfsib2PSVsu9hrLQ3SouECWHQMjDA3Pi8ZuCvQeW8GDkZfHPr3SgwaxY1jpY2njsmf3JBASMoVZ6Mfpg63Q6mBno7mKUSCE7vNHHUZe2V7JEikwjPkaxSWxnwy3J17faGtiEHZLKiNQ9WNtsJLbdVp56dQGfC2zaiXjhx1XJK6m4Nh2M8yEvSuBzanRBAJqrNseGS97tk2iLqqfHrqqmmDsHY3mujCURky4SLr7YLk4B"; + let encoder = AddressEncoder::new(NetworkPrefix::Mainnet); + let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap(); + let expr = addr.script().unwrap().proposition().unwrap(); + check_pretty( + expr, + expect![[r#" + { + val v1 = OUTPUTS(0) + val v2 = SELF.tokens(0) + val v3 = v2._1 + val v4 = SELF.getReg(4).get + val v5 = SELF.getReg(5).get + val v6 = SELF.getReg(6).get + val v7 = OUTPUTS(1) + val v8 = v7.tokens + val v9 = v7.getReg(6).get + val v10 = v5._2 + val v11 = v5._1 + val v12 = v7.getReg(5).get + val v13 = v7.getReg(7).get + sigmaProp(v1.propBytes == SELF.propBytes && v1.value >= SELF.value && v1.tokens(0) == (, v3, v2._2 - 1) && v1.getReg(4).get == v4 && v1.getReg(5).get == v5 && v1.getReg(6).get == v6 && v7.getReg(4).get == v4 && v7.getReg(8).get == v6 && v8(1)._1 == v3 && v8(0) == (, SELF.id, 1000000000000000000) && v9._1 * v10 == v9._2 * v11 * v12 && v13._1 * v10 == v13._2 * v11 * v12 + 1) + } + "#]], + ) + } + + #[test] + fn amm_conc_pool_boot() { + // from eip-14 https://github.com/ergoplatform/eips/pull/27/files + let p2s_addr_str = "6Mv73vd1MnJp6AQg5vHGP9nujFc3Y1PL5gzeUt9PzCaUiQug7ueQGU1bDkmFkCspq4LU8j3T8yY6UyJQKSfah5qEDzjx8QCJF47NBG5jxgPxmBHkM6cUgnYa5ngzn9jrpAn379UC7o5nugTg3HYWZGk3APMcRftkrC3EgroiVMEmSkDcDwaebkNWKfKe3JXgewoTrgZ2YLMafr3JfX47C1zddoWDhS8TWryQYEprkP334eisuh1Fr2iNTW9ruV6m38cRkfRfzSBHYq45mvNLH7JQo6uQZ4NFPx4t27Q5A3mSqCpk7ATThFcQmc2w3Pp2F6xL87c94gxk83G8UEqkAhmaNfoj19zji9rxqRzq9gJeTLBraHR2DchKtahH8HhFPg5DZ4SjwJ4MHqTDF"; + let encoder = AddressEncoder::new(NetworkPrefix::Mainnet); + let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap(); + let expr = addr.script().unwrap().proposition().unwrap(); + check_pretty( + expr, + expect![[r#" + { + val v1 = OUTPUTS(0) + val v2 = v1.tokens + val v3 = SELF.tokens + val v4 = v1.getReg(5).get + val v5 = v1.getReg(6).get + val v6 = v2(3) + val v7 = v6._2 + val v8 = upcast(v7) + val v9 = v1.getReg(7).get + val v10 = upcast(v9) + val v11 = v2(2) + val v12 = v3(0) + sigmaProp(true && v1.value >= SELF.value && v2(0) == (, SELF.id, 1) && v2(1) == (, v3(1)._1, 1) && v1.getReg(4).get == SELF.getReg(4).get && v4 == SELF.getReg(6).get && v5 == SELF.getReg(7).get && (, v6._1, v2(4)._1) == SELF.getReg(8).get && v8 * v8 == v10 * v10 && if (v11._1 == v12._1) v11._2 else 0 >= v12._2 - v9 && v7 * upcast(v4._2) >= v7 * upcast(v4._1) && v7 * upcast(v5._2) < v7 * upcast(v5._1)) + } + "#]], + ) + } + + #[test] + fn amm_conc_pool() { + // from eip-14 https://github.com/ergoplatform/eips/pull/27/files + let p2s_addr_str = "AhCu1UkNT4c9q3B2Lb7gNgvZWCdXL8iYgmNxTYiy4S3wgKWFFW6kz9v7pvY8NqC7g4wgXXwzJY1fQVn2xrLkiyiQWsorq5dR7d5KnDAY43H4GvSVjaDciadXCSHCb8jgk8mFSQCwoZHweLmMJ25312wT85AySJgYUuzdUxMz4EnQpiwZR2XVZq3M81gycuqP9gUryryjN4J1cAF3yL3kZR3rREubBvJ2CY5hF74Xaj2jwajivkESkqq22ieWWG2sK7dk1A7KHr1MmiXGcUBAMMGPAu3mVCeFW9SongxP9hodnJThLknjWRBBBC6wq5jNkSdHrMbdaQM3XesXqGTk9KwWpnSL92E96muU2k8FQbo5isps1r5ciYVrFptfEAC3tWbwcVmRKtrgxtCex6bP5aBZYjaH6L9QQbkYriDAcQ1iZcpf3hHCqURjRXL7i72C3aGBwzzspQvhLof6x4f4gPxTCtF1bNUxddUL6DJ1PbQWzVH8taivjhHohis6sRn3Akvv4xaZRJdKZ8rDuiounRKNXi8VoNgVEZbSFYtfweRSdsiXJCkhtehLWdtFTk1eg7djASdBGKaguvtEBcGaAALVDUoH479VskPUQ6hrfS7KcWrATBdb8sf4W5MFpx7UNitzq2fzSKC96mQRUzy5uELe7Y7vexm5ArNEyr6ARkypZypSzJ2CEifjVxxRBEWVtbdqHrwP4gWv6cMdbqFWwuXAw2BZQnWpZFtKAGQ9m"; + let encoder = AddressEncoder::new(NetworkPrefix::Mainnet); + let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap(); + let expr = addr.script().unwrap().proposition().unwrap(); + check_pretty( + expr, + expect![[r#" + { + val v1 = OUTPUTS(0) + val v2 = v1.tokens + val v3 = SELF.tokens + val v4 = v2(2) + val v5 = v3(2) + val v6 = v2(3) + val v7 = v3(3) + val v8 = v2(4) + val v9 = v3(4) + val v10 = SELF.getReg(4).get + val v11 = SELF.getReg(5).get + val v12 = SELF.getReg(6).get + val v13 = 1000000000000000000 - v5._2 + val v14 = 1000000000000000000 - v4._2 - v13 + val v15 = v6._2 + val v16 = upcast(v15) + val v17 = v8._2 + val v18 = upcast(v17) + val v19 = v16 * upcast(v11._2) >= v18 * upcast(v11._1) && v16 * upcast(v12._2) < v18 * upcast(v12._1) + val v20 = v7._2 + val v21 = v15 - v20 + val v22 = v21 > 0 + val v23 = v9._2 + val v24 = upcast(v23) + val v25 = upcast(v21) + val v26 = v17 - v23 + val v27 = upcast(v20) + val v28 = 1000 + val v29 = upcast(v26) + val v30 = upcast(v13) + val v31 = upcast(v14) / v30 + sigmaProp(v1.propBytes == SELF.propBytes && v1.value >= SELF.value && v2(0) == v3(0) && v2(1) == v3(1) && v4._1 == v5._1 && v6._1 == v7._1 && v8._1 == v9._1 && v1.getReg(4).get == v10 && v1.getReg(5).get == v11 && v1.getReg(6).get == v12 && if (v14 == 0) if (v22) v24 * v25 * upcast(v10) >= upcast(-v26) * v27 * upcast(v28) + upcast(v21 * upcast(v10)) else v27 * v29 * upcast(v10) >= upcast(-v21) * v24 * upcast(v28) + upcast(v26 * upcast(v10)) && v19 else if (v22 && v26 > 0) upcast(-v14) <= v25 * v30 / v27 min v29 * v30 / v24 && v19 else v25 >= v31 * v27 && v29 >= v31 * v24) + } + "#]], + ) + } + + #[test] + fn eip_22_auction() { + // from https://github.com/ergoplatform/eips/blob/adbe21512cadf51a2d9af8406cfd418f95335899/eip-0022.md + let p2s_addr_str = "GE68RH3VEgW6b4kN3GhYigrLxoXr9jMgMpmm3KnXJaYq1PzHieYhz7Uka86idxvBWLRLmpiA3HrxHPsX1jzQZEv5yaRDueiJqks1keM7iB1eYWMEVRUUq1MLFzdA1FHQwCfSakM3Uc8uBPqk2icxhoXvw1CVbUVtFCzcPrZzf8Jjf8gS5bCFpWQscHo14HTsdBxyV3dwL6wKu8LP8FuWJE7qCEgX9ToEiztH4ZLmFwBejnUFrCQqjLVLWpdgdnAXVyewiX9DxXKJKL4wNqhPUrYjmHEVvpZAezXjzfVMr7gKupTqAgx2AJYGh4winEDeYq9MVshX8xjJweGhbAm2RXN1euQpoepFaKqfrT2mQBTmr6edbbzYg6VJ7DoSCDzmcUupFAmZMjMiaUbgtyz2VEbPEKsmAFrZ6zdB5EUxhiYZMd6KdstsJwZCgKJSSCShTgpfqNLCdpR9JbZFQpA1uhUkuLMPvGi74V5EwijTEEtjmTVcWcVhJKv4GDr1Lqe2bMPq4jfEfqvemaY8FcrCsCSi2LZoQUeJ9VrBeotGTKccq8JhwnvNGhLUUrrm32v3bhU82jbtVBVFRD3FSv5hhS6pKHtTevjwuG7JWoR3LN7279A7zQGJWmkSWDoEhHjgxseqZ2G5bLB7ZVEzKM261QhwMwmXA1eWgq8zdBH1u9kFC9bMQ812q2DPZTuhzpBWJh74UGwaEgZLhnUrDKT58cEa4R3kfWyGCMoNw78q1E3a2eKDz8Va5wnixzT2SZFHU8DfHjPSz5rm8Mr3YxgRC6GzaasPDxTrZjuMJHU2exhqsoFvur7Q"; + let encoder = AddressEncoder::new(NetworkPrefix::Mainnet); + let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap(); + let expr = addr.script().unwrap().proposition().unwrap(); + check_pretty( + expr, + expect![[r#" + { + val v1 = OUTPUTS(0) + val v2 = CONTEXT.preHeader.timestamp + val v3 = SELF.getReg(7).get + val v4 = SELF.tokens + val v5 = v4.size + val v6 = v5 == 1 + val v7 = { + (v7: Box) => + if (v6) v7.value else v7.tokens(1)._2 + } + + val v8 = v7(SELF) + val v9 = SELF.getReg(6).get + val v10 = SELF.getReg(8).get + val v11 = Coll[Coll[Byte]]() + val v12 = OUTPUTS(1) + val v13 = SELF.getReg(5).get + val v14 = SELF.getReg(4).get + val v15 = CONTEXT.dataInputs(0) + val v16 = v15.getReg(8).get + sigmaProp(v1.value >= SELF.value && v2 < v3 && v1.tokens(0) == v4(0) && { + val v17 = v7(v1) + v17 >= v8 + v9 || v10 != -1 && v17 >= v10 + } + && v1.propBytes == SELF.propBytes && v5 == v1.tokens.size && { + (v17: Box) => + if (v6) v11 else v17.tokens(1)._1 + } + (SELF) == { + (v17: Box) => + if (v6) v11 else v17.tokens(1)._1 + } + (v1) && v12.propBytes == v13 && v7(v12) >= v8 && v1.getReg(4).get == v14 && v1.getReg(5).get.size > 0 && v1.getReg(6).get == v9 && v1.getReg(7).get == if (v3 - v2 <= v16(0)) v3 + v16(1) else v3 && v1.getReg(8).get == v10 && v1.getReg(9) == SELF.getReg(9) || if (OUTPUTS.size == 5) { + val v17 = OUTPUTS(2) + val v18 = v8 / upcast(v15.getReg(4).get) + val v19 = v4(0) + val v20 = v8 / upcast(v15.getReg(6).get) + val v21 = OUTPUTS(3) + val v22 = v21.getReg(4).get + v2 >= v3 || v8 >= v10 && v10 != -1 && v7(v17) >= v18 && v17.propBytes == v15.getReg(5).get && v1.tokens(0) == v19 && v1.propBytes == v13 && v7(v12) >= v8 - v18 - v20 - if (v6) v15.getReg(7).get * 2 else 0 && v12.propBytes == v14 && blake2b256(v22.bytes) == v19._1 && v7(v21) >= v20 && v21.propBytes == v22.propBytes + } + else false) + } + "#]], + ) + } +} diff --git a/ergotree-ir/src/pretty_printer/print.rs b/ergotree-ir/src/pretty_printer/print.rs new file mode 100644 index 000000000..870ff6c13 --- /dev/null +++ b/ergotree-ir/src/pretty_printer/print.rs @@ -0,0 +1,1112 @@ +use thiserror::Error; + +use crate::mir::and::And; +use crate::mir::apply::Apply; +use crate::mir::atleast::Atleast; +use crate::mir::bin_op::BinOp; +use crate::mir::bit_inversion::BitInversion; +use crate::mir::block::BlockValue; +use crate::mir::bool_to_sigma::BoolToSigmaProp; +use crate::mir::byte_array_to_bigint::ByteArrayToBigInt; +use crate::mir::byte_array_to_long::ByteArrayToLong; +use crate::mir::calc_blake2b256::CalcBlake2b256; +use crate::mir::calc_sha256::CalcSha256; +use crate::mir::coll_append::Append; +use crate::mir::coll_by_index::ByIndex; +use crate::mir::coll_exists::Exists; +use crate::mir::coll_filter::Filter; +use crate::mir::coll_fold::Fold; +use crate::mir::coll_forall::ForAll; +use crate::mir::coll_map::Map; +use crate::mir::coll_size::SizeOf; +use crate::mir::coll_slice::Slice; +use crate::mir::collection::Collection; +use crate::mir::constant::Constant; +use crate::mir::create_avl_tree::CreateAvlTree; +use crate::mir::create_prove_dh_tuple::CreateProveDhTuple; +use crate::mir::create_provedlog::CreateProveDlog; +use crate::mir::decode_point::DecodePoint; +use crate::mir::deserialize_context::DeserializeContext; +use crate::mir::deserialize_register::DeserializeRegister; +use crate::mir::downcast::Downcast; +use crate::mir::exponentiate::Exponentiate; +use crate::mir::expr::Expr; +use crate::mir::extract_amount::ExtractAmount; +use crate::mir::extract_bytes::ExtractBytes; +use crate::mir::extract_bytes_with_no_ref::ExtractBytesWithNoRef; +use crate::mir::extract_creation_info::ExtractCreationInfo; +use crate::mir::extract_id::ExtractId; +use crate::mir::extract_reg_as::ExtractRegisterAs; +use crate::mir::extract_script_bytes::ExtractScriptBytes; +use crate::mir::func_value::FuncValue; +use crate::mir::get_var::GetVar; +use crate::mir::global_vars::GlobalVars; +use crate::mir::if_op::If; +use crate::mir::logical_not::LogicalNot; +use crate::mir::long_to_byte_array::LongToByteArray; +use crate::mir::method_call::MethodCall; +use crate::mir::multiply_group::MultiplyGroup; +use crate::mir::negation::Negation; +use crate::mir::option_get::OptionGet; +use crate::mir::option_get_or_else::OptionGetOrElse; +use crate::mir::option_is_defined::OptionIsDefined; +use crate::mir::or::Or; +use crate::mir::property_call::PropertyCall; +use crate::mir::select_field::SelectField; +use crate::mir::sigma_and::SigmaAnd; +use crate::mir::sigma_or::SigmaOr; +use crate::mir::sigma_prop_bytes::SigmaPropBytes; +use crate::mir::subst_const::SubstConstants; +use crate::mir::tree_lookup::TreeLookup; +use crate::mir::tuple::Tuple; +use crate::mir::unary_op::OneArgOpTryBuild; +use crate::mir::upcast::Upcast; +use crate::mir::val_def::ValDef; +use crate::mir::val_use::ValUse; +use crate::mir::xor::Xor; +use crate::mir::xor_of::XorOf; +use crate::source_span::SourceSpan; +use crate::source_span::Spanned; +use crate::types::stype::SType; + +use super::Printer; + +/// Print error +#[allow(missing_docs)] +#[derive(PartialEq, Eq, Debug, Clone, Error)] +pub enum PrintError { + #[error("fmt error: {0:?}")] + FmtError(#[from] std::fmt::Error), +} + +/// Print trait for Expr that sets the source span for the resulting Expr +pub trait Print { + /// Print the expression and return the resulting expression with source span + fn print(&self, w: &mut dyn Printer) -> Result; +} + +impl Print for Expr { + fn print(&self, w: &mut dyn Printer) -> Result { + match self { + Expr::Append(v) => v.expr().print(w), + Expr::BlockValue(v) => v.expr().print(w), + Expr::ValDef(v) => v.expr().print(w), + Expr::ValUse(v) => v.print(w), + Expr::Const(v) => v.print(w), + Expr::BinOp(v) => v.expr().print(w), + Expr::GlobalVars(v) => v.print(w), + Expr::ByIndex(v) => v.expr().print(w), + Expr::ConstPlaceholder(_) => Ok(self.clone()), + Expr::SubstConstants(v) => v.expr().print(w), + Expr::ByteArrayToLong(v) => v.expr().print(w), + Expr::ByteArrayToBigInt(v) => v.expr().print(w), + Expr::LongToByteArray(v) => v.print(w), + Expr::Collection(v) => v.print(w), + Expr::Tuple(v) => v.print(w), + Expr::CalcBlake2b256(v) => v.print(w), + Expr::CalcSha256(v) => v.print(w), + Expr::Context => { + write!(w, "CONTEXT")?; + Ok(self.clone()) + } + Expr::Global => { + write!(w, "GLOBAL")?; + Ok(self.clone()) + } + Expr::FuncValue(v) => v.print(w), + Expr::Apply(v) => v.print(w), + Expr::MethodCall(v) => v.expr().print(w), + Expr::PropertyCall(v) => v.expr().print(w), + Expr::If(v) => v.print(w), + Expr::And(v) => v.expr().print(w), + Expr::Or(v) => v.expr().print(w), + Expr::Xor(v) => v.print(w), + Expr::Atleast(v) => v.print(w), + Expr::LogicalNot(v) => v.expr().print(w), + Expr::Negation(v) => v.expr().print(w), + Expr::BitInversion(v) => v.print(w), + Expr::OptionGet(v) => v.expr().print(w), + Expr::OptionIsDefined(v) => v.expr().print(w), + Expr::OptionGetOrElse(v) => v.expr().print(w), + Expr::ExtractAmount(v) => v.print(w), + Expr::ExtractRegisterAs(v) => v.expr().print(w), + Expr::ExtractBytes(v) => v.print(w), + Expr::ExtractBytesWithNoRef(v) => v.print(w), + Expr::ExtractScriptBytes(v) => v.print(w), + Expr::ExtractCreationInfo(v) => v.print(w), + Expr::ExtractId(v) => v.print(w), + Expr::SizeOf(v) => v.print(w), + Expr::Slice(v) => v.expr().print(w), + Expr::Fold(v) => v.expr().print(w), + Expr::Map(v) => v.expr().print(w), + Expr::Filter(v) => v.expr().print(w), + Expr::Exists(v) => v.expr().print(w), + Expr::ForAll(v) => v.expr().print(w), + Expr::SelectField(v) => v.expr().print(w), + Expr::BoolToSigmaProp(v) => v.print(w), + Expr::Upcast(v) => v.print(w), + Expr::Downcast(v) => v.print(w), + Expr::CreateProveDlog(v) => v.print(w), + Expr::CreateProveDhTuple(v) => v.print(w), + Expr::SigmaPropBytes(v) => v.print(w), + Expr::DecodePoint(v) => v.print(w), + Expr::SigmaAnd(v) => v.print(w), + Expr::SigmaOr(v) => v.print(w), + Expr::GetVar(v) => v.expr().print(w), + Expr::DeserializeRegister(v) => v.print(w), + Expr::DeserializeContext(v) => v.print(w), + Expr::MultiplyGroup(v) => v.print(w), + Expr::Exponentiate(v) => v.print(w), + Expr::XorOf(v) => v.print(w), + Expr::TreeLookup(v) => v.expr().print(w), + Expr::CreateAvlTree(v) => v.print(w), + } + } +} + +impl Print for BlockValue { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + writeln!(w, "{{")?; + w.inc_ident(); + let mut items = Vec::new(); + for item in &self.items { + w.print_indent()?; + items.push(item.print(w)?); + writeln!(w)?; + } + // indent for result + w.print_indent()?; + let res = self.result.print(w)?; + writeln!(w)?; + w.dec_ident(); + w.print_indent()?; + writeln!(w, "}}")?; + let length = w.current_pos() - offset; + Ok(Spanned { + source_span: SourceSpan { offset, length }, + expr: BlockValue { + items, + result: Box::new(res), + }, + } + .into()) + } +} + +impl Print for ValDef { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + write!(w, "val v{} = ", self.id)?; + let rhs = self.rhs.print(w)?; + let length = w.current_pos() - offset; + Ok(Spanned { + source_span: SourceSpan { offset, length }, + expr: ValDef { + id: self.id, + rhs: Box::new(rhs), + }, + } + .into()) + } +} + +impl Print for Constant { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "{:?}", self.v)?; + Ok(self.clone().into()) + } +} + +impl Print for ValUse { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "v{}", self.val_id)?; + Ok(self.clone().into()) + } +} + +impl Print for Append { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + let input = self.input.print(w)?; + write!(w, ".append(")?; + let col_2 = self.col_2.print(w)?; + write!(w, ")")?; + let length = w.current_pos() - offset; + Ok(Spanned { + source_span: SourceSpan { offset, length }, + expr: Append { + input: Box::new(input), + col_2: Box::new(col_2), + }, + } + .into()) + } +} + +impl Print for BinOp { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + let left = self.left.print(w)?; + write!(w, " {} ", self.kind)?; + let right = self.right.print(w)?; + let length = w.current_pos() - offset; + // dbg!(offset, length); + Ok(Spanned { + source_span: SourceSpan { offset, length }, + expr: BinOp { + kind: self.kind, + left: Box::new(left), + right: Box::new(right), + }, + } + .into()) + } +} + +impl Print for GlobalVars { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "{}", self)?; + Ok(self.clone().into()) + } +} + +impl Print for ByIndex { + fn print(&self, w: &mut dyn Printer) -> Result { + let input = self.input.print(w)?; + let offset = w.current_pos(); + write!(w, "(")?; + let index = self.index.print(w)?; + write!(w, ")")?; + let length = w.current_pos() - offset; + #[allow(clippy::unwrap_used)] // we only added spans + Ok(Spanned { + source_span: SourceSpan { offset, length }, + expr: ByIndex::new(input, index, self.default.clone()).unwrap(), + } + .into()) + } +} + +impl Print for Map { + fn print(&self, w: &mut dyn Printer) -> Result { + let input = self.input.print(w)?; + let offset = w.current_pos(); + write!(w, ".map(")?; + let mapper = self.mapper.print(w)?; + write!(w, ")")?; + let length = w.current_pos() - offset; + #[allow(clippy::unwrap_used)] // we only added spans + Ok(Spanned { + source_span: SourceSpan { offset, length }, + expr: Map::new(input, mapper).unwrap(), + } + .into()) + } +} + +impl Print for Exists { + fn print(&self, w: &mut dyn Printer) -> Result { + let input = self.input.print(w)?; + let offset = w.current_pos(); + write!(w, ".exists(")?; + let condition = self.condition.print(w)?; + write!(w, ")")?; + let length = w.current_pos() - offset; + #[allow(clippy::unwrap_used)] // we only added spans + Ok(Spanned { + source_span: SourceSpan { offset, length }, + expr: Exists::new(input, condition).unwrap(), + } + .into()) + } +} + +impl Print for Fold { + fn print(&self, w: &mut dyn Printer) -> Result { + let input = self.input.print(w)?; + let offset = w.current_pos(); + write!(w, ".fold(")?; + let zero = self.zero.print(w)?; + write!(w, ")(")?; + let fold_op = self.fold_op.print(w)?; + w.print_indent()?; + write!(w, ")")?; + w.dec_ident(); + let length = w.current_pos() - offset; + #[allow(clippy::unwrap_used)] // we only added spans + Ok(Spanned { + source_span: SourceSpan { offset, length }, + expr: Fold::new(input, zero, fold_op).unwrap(), + } + .into()) + } +} + +impl Print for FuncValue { + fn print(&self, w: &mut dyn Printer) -> Result { + w.inc_ident(); + writeln!(w, "{{")?; + w.inc_ident(); + w.print_indent()?; + writeln!( + w, + "({}) => ", + self.args() + .iter() + .map(|a| format!("{}", a)) + .collect::>() + .join(", ") + )?; + w.inc_ident(); + w.print_indent()?; + let body = self.body().print(w)?; + w.dec_ident(); + writeln!(w)?; + w.print_indent()?; + writeln!(w, "}}")?; + w.dec_ident(); + Ok(FuncValue::new(self.args().to_vec(), body).into()) + } +} + +impl Print for Filter { + fn print(&self, w: &mut dyn Printer) -> Result { + let input = self.input.print(w)?; + let offset = w.current_pos(); + write!(w, ".filter(")?; + let condition = self.condition.print(w)?; + w.print_indent()?; + write!(w, ")")?; + w.dec_ident(); + let length = w.current_pos() - offset; + #[allow(clippy::unwrap_used)] // we only added spans + Ok(Spanned { + source_span: SourceSpan { offset, length }, + expr: Filter::new(input, condition).unwrap(), + } + .into()) + } +} + +impl Print for If { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "if (")?; + let condition = self.condition.print(w)?; + write!(w, ") ")?; + let true_branch = self.true_branch.print(w)?; + write!(w, " else ")?; + let false_branch = self.false_branch.print(w)?; + #[allow(clippy::unwrap_used)] // we only added spans + Ok(If { + condition: condition.into(), + true_branch: true_branch.into(), + false_branch: false_branch.into(), + } + .into()) + } +} + +impl Print for OptionIsDefined { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + let input = self.input.print(w)?; + write!(w, ".isDefined()")?; + let length = w.current_pos() - offset; + #[allow(clippy::unwrap_used)] // we only added spans + Ok(Spanned { + source_span: SourceSpan { offset, length }, + expr: OptionIsDefined { + input: Box::new(input), + }, + } + .into()) + } +} + +impl Print for ExtractRegisterAs { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + let input = self.input.print(w)?; + write!(w, ".getReg")?; + write!(w, "({})", self.register_id)?; + let length = w.current_pos() - offset; + #[allow(clippy::unwrap_used)] // we only added spans + Ok(Spanned { + source_span: SourceSpan { offset, length }, + expr: ExtractRegisterAs::new( + input, + self.register_id, + SType::SOption(self.elem_tpe.clone().into()), + ) + .unwrap(), + } + .into()) + } +} + +impl Print for SelectField { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + let input = self.input.print(w)?; + write!(w, "._{}", self.field_index)?; + let length = w.current_pos() - offset; + #[allow(clippy::unwrap_used)] // we only added spans + Ok(Spanned { + source_span: SourceSpan { offset, length }, + expr: SelectField::new(input, self.field_index).unwrap(), + } + .into()) + } +} + +impl Print for ExtractCreationInfo { + fn print(&self, w: &mut dyn Printer) -> Result { + let input = self.input.print(w)?; + write!(w, ".creationInfo")?; + Ok(ExtractCreationInfo { + input: input.into(), + } + .into()) + } +} + +impl Print for PropertyCall { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + let obj = self.obj.print(w)?; + write!(w, ".{}", self.method.name())?; + let length = w.current_pos() - offset; + Ok(Spanned { + source_span: SourceSpan { offset, length }, + expr: PropertyCall { + obj: Box::new(obj), + method: self.method.clone(), + }, + } + .into()) + } +} + +impl Print for MethodCall { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + let obj = self.obj.print(w)?; + write!(w, ".{}", self.method.name())?; + write!(w, "(")?; + let args = self + .args + .iter() + .map(|a| -> Result { a.print(w) }) + .collect::, _>>()?; + write!(w, ")")?; + let length = w.current_pos() - offset; + Ok(Spanned { + source_span: SourceSpan { offset, length }, + expr: MethodCall { + obj: Box::new(obj), + method: self.method.clone(), + args, + }, + } + .into()) + } +} + +impl Print for OptionGet { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + let input = self.input.print(w)?; + write!(w, ".get")?; + let length = w.current_pos() - offset; + #[allow(clippy::unwrap_used)] // we only added spans + Ok(Spanned { + source_span: SourceSpan { offset, length }, + expr: OptionGet::try_build(input).unwrap(), + } + .into()) + } +} + +impl Print for SizeOf { + fn print(&self, w: &mut dyn Printer) -> Result { + let input = self.input.print(w)?; + write!(w, ".size")?; + Ok(SizeOf { + input: Box::new(input), + } + .into()) + } +} + +impl Print for Tuple { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "(")?; + let items = self.items.try_mapped_ref(|i| { + write!(w, ", ")?; + i.print(w) + })?; + write!(w, ")")?; + Ok(Tuple { items }.into()) + } +} + +impl Print for SigmaAnd { + fn print(&self, w: &mut dyn Printer) -> Result { + writeln!(w, "allOf(")?; + let items = self.items.try_mapped_ref(|i| -> Result { + w.inc_ident(); + w.print_indent()?; + let item = i.print(w)?; + write!(w, ", ")?; + writeln!(w)?; + w.dec_ident(); + Ok(item) + })?; + w.print_indent()?; + write!(w, ")")?; + Ok(SigmaAnd { items }.into()) + } +} + +impl Print for SigmaOr { + fn print(&self, w: &mut dyn Printer) -> Result { + writeln!(w, "anyOf(")?; + let items = self.items.try_mapped_ref(|i| -> Result { + w.inc_ident(); + w.print_indent()?; + let item = i.print(w)?; + write!(w, ", ")?; + writeln!(w)?; + w.dec_ident(); + Ok(item) + })?; + w.print_indent()?; + write!(w, ")")?; + Ok(SigmaOr { items }.into()) + } +} + +impl Print for And { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + write!(w, "&&")?; + let input = self.input.print(w)?; + let length = w.current_pos() - offset; + Ok(Spanned { + expr: And { + input: Box::new(input), + }, + source_span: SourceSpan { offset, length }, + } + .into()) + } +} + +impl Print for Or { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + write!(w, "||")?; + let input = self.input.print(w)?; + let length = w.current_pos() - offset; + Ok(Spanned { + expr: Or { + input: Box::new(input), + }, + source_span: SourceSpan { offset, length }, + } + .into()) + } +} + +impl Print for CreateProveDlog { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "proveDlog(")?; + let input = self.input.print(w)?; + write!(w, ")")?; + Ok(CreateProveDlog { + input: Box::new(input), + } + .into()) + } +} + +impl Print for GetVar { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "getVar({})", self.var_id)?; + Ok(self.clone().into()) + } +} + +impl Print for BoolToSigmaProp { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "sigmaProp(")?; + let input = self.input.print(w)?; + write!(w, ")")?; + Ok(BoolToSigmaProp { + input: Box::new(input), + } + .into()) + } +} + +impl Print for Upcast { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "upcast(")?; + let input = self.input.print(w)?; + write!(w, ")")?; + #[allow(clippy::unwrap_used)] // we only added spans + Ok(Upcast::new(input, self.tpe.clone()).unwrap().into()) + } +} + +impl Print for ExtractScriptBytes { + fn print(&self, w: &mut dyn Printer) -> Result { + let input = self.input.print(w)?; + write!(w, ".propBytes")?; + Ok(ExtractScriptBytes { + input: Box::new(input), + } + .into()) + } +} + +impl Print for ExtractAmount { + fn print(&self, w: &mut dyn Printer) -> Result { + let input = self.input.print(w)?; + write!(w, ".value")?; + Ok(ExtractAmount { + input: Box::new(input), + } + .into()) + } +} + +impl Print for LogicalNot { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "!")?; + let input = self.input.print(w)?; + Ok(LogicalNot { + input: Box::new(input), + } + .into()) + } +} + +impl Print for CalcBlake2b256 { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "blake2b256(")?; + let input = self.input.print(w)?; + write!(w, ")")?; + Ok(CalcBlake2b256 { + input: Box::new(input), + } + .into()) + } +} + +impl Print for Negation { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "-")?; + let input = self.input.print(w)?; + Ok(Negation { + input: Box::new(input), + } + .into()) + } +} + +impl Print for ExtractId { + fn print(&self, w: &mut dyn Printer) -> Result { + let input = self.input.print(w)?; + write!(w, ".id")?; + Ok(ExtractId { + input: Box::new(input), + } + .into()) + } +} + +impl Print for Apply { + fn print(&self, w: &mut dyn Printer) -> Result { + let func = self.func.print(w)?; + write!(w, "(")?; + let args = self + .args + .iter() + .map(|a| -> Result { a.print(w) }) + .collect::, _>>()?; + write!(w, ")")?; + #[allow(clippy::unwrap_used)] // we only added spans + Ok(Apply::new(func, args).unwrap().into()) + } +} + +impl Print for Collection { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "Coll[{}](", self.tpe())?; + match self { + Collection::BoolConstants(bools) => { + for b in bools { + write!(w, "{}, ", b)?; + } + write!(w, ")")?; + Ok(Collection::from_bools(bools.clone()).into()) + } + Collection::Exprs { elem_tpe, items } => { + let items = items + .iter() + .map(|i| { + write!(w, ", ")?; + i.print(w) + }) + .collect::, _>>()?; + write!(w, ")")?; + #[allow(clippy::unwrap_used)] // we only added spans + Ok(Collection::new(elem_tpe.clone(), items).unwrap().into()) + } + } + } +} + +impl Print for ExtractBytes { + fn print(&self, w: &mut dyn Printer) -> Result { + let input = self.input.print(w)?; + write!(w, ".bytes")?; + Ok(ExtractBytes { + input: Box::new(input), + } + .into()) + } +} + +impl Print for ByteArrayToLong { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "byteArrayToLong(")?; + let input = self.input.print(w)?; + write!(w, ")")?; + Ok(ByteArrayToLong { + input: Box::new(input), + } + .into()) + } +} + +impl Print for ByteArrayToBigInt { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "byteArrayToBigInt(")?; + let input = self.input.print(w)?; + write!(w, ")")?; + Ok(ByteArrayToBigInt { + input: Box::new(input), + } + .into()) + } +} + +impl Print for LongToByteArray { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "longToByteArray(")?; + let input = self.input.print(w)?; + write!(w, ")")?; + Ok(LongToByteArray { + input: Box::new(input), + } + .into()) + } +} + +impl Print for CalcSha256 { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "sha256(")?; + let input = self.input.print(w)?; + write!(w, ")")?; + Ok(CalcSha256 { + input: Box::new(input), + } + .into()) + } +} + +impl Print for Xor { + fn print(&self, w: &mut dyn Printer) -> Result { + let left = self.left.print(w)?; + write!(w, "^")?; + let right = self.right.print(w)?; + Ok(Xor { + left: left.into(), + right: right.into(), + } + .into()) + } +} + +impl Print for Atleast { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, ".atLeast(")?; + let bound = self.bound.print(w)?; + write!(w, ", ")?; + let input = self.input.print(w)?; + write!(w, ")")?; + Ok(Atleast { + input: Box::new(input), + bound: Box::new(bound), + } + .into()) + } +} + +impl Print for SubstConstants { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + write!(w, ".substConstants(")?; + let script_bytes = self.script_bytes.print(w)?; + write!(w, ", ")?; + let positions = self.positions.print(w)?; + write!(w, ", ")?; + let new_values = self.new_values.print(w)?; + write!(w, ")")?; + let length = w.current_pos() - offset; + Ok(Spanned { + expr: SubstConstants { + script_bytes: script_bytes.into(), + positions: positions.into(), + new_values: new_values.into(), + }, + source_span: SourceSpan { offset, length }, + } + .into()) + } +} + +impl Print for BitInversion { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "~")?; + let input = self.input.print(w)?; + Ok(BitInversion { + input: Box::new(input), + } + .into()) + } +} + +impl Print for OptionGetOrElse { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + let input = self.input.print(w)?; + write!(w, ".getOrElse(")?; + let default = self.default.print(w)?; + write!(w, ")")?; + let length = w.current_pos() - offset; + #[allow(clippy::unwrap_used)] // we only added spans + Ok(Spanned { + expr: OptionGetOrElse::new(input, default).unwrap(), + source_span: SourceSpan { offset, length }, + } + .into()) + } +} + +impl Print for ExtractBytesWithNoRef { + fn print(&self, w: &mut dyn Printer) -> Result { + let input = self.input.print(w)?; + write!(w, ".bytesWithNoRef")?; + Ok(ExtractBytesWithNoRef { + input: Box::new(input), + } + .into()) + } +} + +impl Print for Slice { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + let input = self.input.print(w)?; + write!(w, ".slice(")?; + let from = self.from.print(w)?; + write!(w, ", ")?; + let until = self.until.print(w)?; + write!(w, ")")?; + let length = w.current_pos() - offset; + Ok(Spanned { + expr: Slice { + input: Box::new(input), + from: Box::new(from), + until: Box::new(until), + }, + source_span: SourceSpan { offset, length }, + } + .into()) + } +} + +impl Print for ForAll { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + let input = self.input.print(w)?; + write!(w, ".forall(")?; + let condition = self.condition.print(w)?; + write!(w, ")")?; + let length = w.current_pos() - offset; + #[allow(clippy::unwrap_used)] // we only added spans + Ok(Spanned { + expr: ForAll::new(input, condition).unwrap(), + source_span: SourceSpan { offset, length }, + } + .into()) + } +} + +impl Print for Downcast { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "downcast(")?; + let input = self.input.print(w)?; + write!(w, ")")?; + Ok(Downcast { + input: Box::new(input), + tpe: self.tpe.clone(), + } + .into()) + } +} + +impl Print for CreateProveDhTuple { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "proveDHTuple(")?; + let g = self.g.print(w)?; + write!(w, ", ")?; + let h = self.h.print(w)?; + write!(w, ", ")?; + let u = self.u.print(w)?; + write!(w, ", ")?; + let v = self.v.print(w)?; + write!(w, ")")?; + Ok(CreateProveDhTuple { + g: Box::new(g), + h: Box::new(h), + u: Box::new(u), + v: Box::new(v), + } + .into()) + } +} + +impl Print for SigmaPropBytes { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "sigmaPropBytes(")?; + let input = self.input.print(w)?; + write!(w, ")")?; + Ok(SigmaPropBytes { + input: Box::new(input), + } + .into()) + } +} + +impl Print for DecodePoint { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "decodePoint(")?; + let input = self.input.print(w)?; + write!(w, ")")?; + Ok(DecodePoint { + input: Box::new(input), + } + .into()) + } +} + +impl Print for DeserializeRegister { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "deserializeRegister({})", self.reg)?; + Ok(self.clone().into()) + } +} + +impl Print for DeserializeContext { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "deserializeContext({})", self.id)?; + Ok(self.clone().into()) + } +} + +impl Print for MultiplyGroup { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "multiplyGroup(")?; + let left = self.left.print(w)?; + write!(w, ", ")?; + let right = self.right.print(w)?; + write!(w, ")")?; + Ok(MultiplyGroup { + left: left.into(), + right: right.into(), + } + .into()) + } +} + +impl Print for Exponentiate { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "exponentiate(")?; + let left = self.left.print(w)?; + write!(w, ", ")?; + let right = self.right.print(w)?; + write!(w, ")")?; + Ok(Exponentiate { + left: left.into(), + right: right.into(), + } + .into()) + } +} + +impl Print for XorOf { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "xorOf(")?; + let input = self.input.print(w)?; + write!(w, ")")?; + Ok(XorOf { + input: Box::new(input), + } + .into()) + } +} + +impl Print for TreeLookup { + fn print(&self, w: &mut dyn Printer) -> Result { + let offset = w.current_pos(); + write!(w, "treeLookup(")?; + let tree = self.tree.print(w)?; + write!(w, ", ")?; + let key = self.key.print(w)?; + write!(w, ", ")?; + let proof = self.proof.print(w)?; + write!(w, ")")?; + let length = w.current_pos() - offset; + Ok(Spanned { + expr: TreeLookup { + tree: Box::new(tree), + key: Box::new(key), + proof: Box::new(proof), + }, + source_span: SourceSpan { offset, length }, + } + .into()) + } +} + +impl Print for CreateAvlTree { + fn print(&self, w: &mut dyn Printer) -> Result { + write!(w, "avlTree(")?; + let digest = self.digest.print(w)?; + write!(w, ", ")?; + let key_length = self.key_length.print(w)?; + write!(w, ")")?; + Ok(CreateAvlTree { + digest: Box::new(digest), + key_length: Box::new(key_length), + flags: self.flags.clone(), + value_length: self.value_length.clone(), + } + .into()) + } +} diff --git a/ergotree-ir/src/serialization/expr.rs b/ergotree-ir/src/serialization/expr.rs index f4b97ba50..4abb5eef8 100644 --- a/ergotree-ir/src/serialization/expr.rs +++ b/ergotree-ir/src/serialization/expr.rs @@ -79,6 +79,7 @@ use crate::serialization::{ use crate::mir::xor_of::XorOf; use crate::serialization::types::TypeCode; +use crate::source_span::Spanned; impl Expr { /// Parse expression from byte stream. This function should be used instead of @@ -112,8 +113,10 @@ impl Expr { OpCode::MINER_PUBKEY => Ok(Expr::GlobalVars(GlobalVars::MinerPubKey)), OpCode::GROUP_GENERATOR => Ok(Expr::GlobalVars(GlobalVars::GroupGenerator)), OpCode::GLOBAL => Ok(Expr::Global), - OpCode::PROPERTY_CALL => Ok(Expr::ProperyCall(PropertyCall::sigma_parse(r)?)), - OpCode::METHOD_CALL => Ok(Expr::MethodCall(MethodCall::sigma_parse(r)?)), + OpCode::PROPERTY_CALL => { + Ok(Expr::PropertyCall(PropertyCall::sigma_parse(r)?.into())) + } + OpCode::METHOD_CALL => Ok(Expr::MethodCall(MethodCall::sigma_parse(r)?.into())), OpCode::CONTEXT => Ok(Expr::Context), OptionGet::OP_CODE => Ok(OptionGet::sigma_parse(r)?.into()), OptionIsDefined::OP_CODE => Ok(OptionIsDefined::sigma_parse(r)?.into()), @@ -146,13 +149,13 @@ impl Expr { OpCode::BIT_OR => Ok(bin_op_sigma_parse(BitOp::BitOr.into(), r)?), OpCode::BIT_AND => Ok(bin_op_sigma_parse(BitOp::BitAnd.into(), r)?), OpCode::BIT_XOR => Ok(bin_op_sigma_parse(BitOp::BitXor.into(), r)?), - OpCode::BLOCK_VALUE => Ok(Expr::BlockValue(BlockValue::sigma_parse(r)?)), + OpCode::BLOCK_VALUE => Ok(Expr::BlockValue(BlockValue::sigma_parse(r)?.into())), OpCode::FUNC_VALUE => Ok(Expr::FuncValue(FuncValue::sigma_parse(r)?)), OpCode::APPLY => Ok(Expr::Apply(Apply::sigma_parse(r)?)), - OpCode::VAL_DEF => Ok(Expr::ValDef(ValDef::sigma_parse(r)?)), + OpCode::VAL_DEF => Ok(Expr::ValDef(ValDef::sigma_parse(r)?.into())), OpCode::VAL_USE => Ok(Expr::ValUse(ValUse::sigma_parse(r)?)), ExtractAmount::OP_CODE => Ok(Expr::ExtractAmount(ExtractAmount::sigma_parse(r)?)), - OpCode::SELECT_FIELD => Ok(Expr::SelectField(SelectField::sigma_parse(r)?)), + OpCode::SELECT_FIELD => Ok(Expr::SelectField(SelectField::sigma_parse(r)?.into())), OpCode::CALC_BLAKE2B256 => Ok(CalcBlake2b256::sigma_parse(r)?.into()), CalcSha256::OP_CODE => Ok(CalcSha256::sigma_parse(r)?.into()), And::OP_CODE => Ok(And::sigma_parse(r)?.into()), @@ -222,27 +225,27 @@ impl SigmaSerializable for Expr { } None => c.sigma_serialize(w), }, - Expr::Append(ap) => ap.sigma_serialize_w_opcode(w), - Expr::Fold(op) => op.sigma_serialize_w_opcode(w), + Expr::Append(ap) => ap.expr().sigma_serialize_w_opcode(w), + Expr::Fold(op) => op.expr().sigma_serialize_w_opcode(w), Expr::ConstPlaceholder(cp) => cp.sigma_serialize_w_opcode(w), - Expr::SubstConstants(s) => s.sigma_serialize_w_opcode(w), - Expr::ByteArrayToLong(s) => s.sigma_serialize_w_opcode(w), - Expr::ByteArrayToBigInt(s) => s.sigma_serialize_w_opcode(w), + Expr::SubstConstants(s) => s.expr().sigma_serialize_w_opcode(w), + Expr::ByteArrayToLong(s) => s.expr().sigma_serialize_w_opcode(w), + Expr::ByteArrayToBigInt(s) => s.expr().sigma_serialize_w_opcode(w), Expr::LongToByteArray(s) => s.sigma_serialize_w_opcode(w), Expr::GlobalVars(op) => op.op_code().sigma_serialize(w), - Expr::MethodCall(mc) => mc.sigma_serialize_w_opcode(w), - Expr::ProperyCall(pc) => pc.sigma_serialize_w_opcode(w), + Expr::MethodCall(mc) => mc.expr().sigma_serialize_w_opcode(w), + Expr::PropertyCall(pc) => pc.expr().sigma_serialize_w_opcode(w), Expr::Global => OpCode::GLOBAL.sigma_serialize(w), Expr::Context => OpCode::CONTEXT.sigma_serialize(w), - Expr::OptionGet(v) => v.sigma_serialize_w_opcode(w), - Expr::ExtractRegisterAs(v) => v.sigma_serialize_w_opcode(w), + Expr::OptionGet(v) => v.expr().sigma_serialize_w_opcode(w), + Expr::ExtractRegisterAs(v) => v.expr().sigma_serialize_w_opcode(w), Expr::BinOp(op) => { - op.op_code().sigma_serialize(w)?; - bin_op_sigma_serialize(op, w) + op.expr().op_code().sigma_serialize(w)?; + bin_op_sigma_serialize(op.expr(), w) } - Expr::BlockValue(op) => op.sigma_serialize_w_opcode(w), + Expr::BlockValue(op) => op.expr().sigma_serialize_w_opcode(w), Expr::ValUse(op) => op.sigma_serialize_w_opcode(w), - Expr::ValDef(op) => op.sigma_serialize_w_opcode(w), + Expr::ValDef(op) => op.expr().sigma_serialize_w_opcode(w), Expr::FuncValue(op) => op.sigma_serialize_w_opcode(w), Expr::Apply(op) => op.sigma_serialize_w_opcode(w), Expr::ExtractAmount(op) => op.sigma_serialize_w_opcode(w), @@ -264,19 +267,19 @@ impl SigmaSerializable for Expr { Expr::Upcast(op) => op.sigma_serialize_w_opcode(w), Expr::Downcast(op) => op.sigma_serialize_w_opcode(w), Expr::If(op) => op.sigma_serialize_w_opcode(w), - Expr::ByIndex(op) => op.sigma_serialize_w_opcode(w), + Expr::ByIndex(op) => op.expr().sigma_serialize_w_opcode(w), Expr::ExtractScriptBytes(op) => op.sigma_serialize_w_opcode(w), Expr::SizeOf(op) => op.sigma_serialize_w_opcode(w), - Expr::Slice(op) => op.sigma_serialize_w_opcode(w), + Expr::Slice(op) => op.expr().sigma_serialize_w_opcode(w), Expr::CreateProveDlog(op) => op.sigma_serialize_w_opcode(w), Expr::CreateProveDhTuple(op) => op.sigma_serialize_w_opcode(w), Expr::ExtractCreationInfo(op) => op.sigma_serialize_w_opcode(w), Expr::Exists(op) => op.sigma_serialize_w_opcode(w), Expr::ExtractId(op) => op.sigma_serialize_w_opcode(w), Expr::SigmaPropBytes(op) => op.sigma_serialize_w_opcode(w), - Expr::OptionIsDefined(op) => op.sigma_serialize_w_opcode(w), - Expr::OptionGetOrElse(op) => op.sigma_serialize_w_opcode(w), - Expr::Negation(op) => op.sigma_serialize_w_opcode(w), + Expr::OptionIsDefined(op) => op.expr().sigma_serialize_w_opcode(w), + Expr::OptionGetOrElse(op) => op.expr().sigma_serialize_w_opcode(w), + Expr::Negation(op) => op.expr().sigma_serialize_w_opcode(w), Expr::BitInversion(op) => op.sigma_serialize_w_opcode(w), Expr::ForAll(op) => op.sigma_serialize_w_opcode(w), Expr::Tuple(op) => op.sigma_serialize_w_opcode(w), @@ -302,6 +305,22 @@ impl SigmaSerializable for Expr { } } +impl SigmaSerializable for Spanned { + fn sigma_serialize(&self, w: &mut W) -> SigmaSerializeResult { + self.expr.sigma_serialize(w) + } + + fn sigma_parse(r: &mut R) -> Result { + T::sigma_parse(r).map(Into::into) + } +} + +impl HasOpCode for Spanned { + fn op_code(&self) -> OpCode { + self.expr.op_code() + } +} + #[cfg(test)] #[cfg(feature = "arbitrary")] #[allow(clippy::unwrap_used)] diff --git a/ergotree-ir/src/serialization/property_call.rs b/ergotree-ir/src/serialization/property_call.rs index 9551de5ac..1a33d86c3 100644 --- a/ergotree-ir/src/serialization/property_call.rs +++ b/ergotree-ir/src/serialization/property_call.rs @@ -40,7 +40,7 @@ mod tests { #[test] fn ser_roundtrip_property() { let mc = PropertyCall::new(Expr::Context, scontext::DATA_INPUTS_PROPERTY.clone()).unwrap(); - let expr = Expr::ProperyCall(mc); + let expr = Expr::PropertyCall(mc.into()); assert_eq![sigma_serialize_roundtrip(&expr), expr]; } } diff --git a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs index fe0f71866..6ed07ae5d 100644 --- a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs +++ b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs @@ -13,6 +13,7 @@ use crate::serialization::SigmaSerializable; use ergo_chain_types::EcPoint; use std::convert::TryFrom; use std::convert::TryInto; +use std::fmt::Formatter; extern crate derive_more; use bounded_vec::BoundedVec; @@ -53,6 +54,12 @@ impl From for ProveDlog { } } +impl std::fmt::Display for ProveDlog { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "proveDlog({})", self.h) + } +} + /// Construct a new SigmaProp value representing public key of Diffie Hellman signature protocol. /// Used in a proof that of equality of discrete logarithms (i.e., a proof of a Diffie-Hellman tuple): /// given group elements g, h, u, v, the proof convinces a verifier that the prover knows `w` such @@ -86,8 +93,18 @@ impl ProveDhTuple { } } +impl std::fmt::Display for ProveDhTuple { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "ProveDhTuple(g: {}, h: {}, u: {}, v: {})", + self.g, self.h, self.u, self.v + ) + } +} + /// Sigma proposition -#[derive(PartialEq, Eq, Debug, Clone, From)] +#[derive(PartialEq, Eq, Debug, Clone, From, derive_more::Display)] pub enum SigmaProofOfKnowledgeTree { /// public key of Diffie Hellman signature protocol ProveDhTuple(ProveDhTuple), @@ -125,6 +142,16 @@ impl HasOpCode for SigmaConjecture { } } +impl std::fmt::Display for SigmaConjecture { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + SigmaConjecture::Cand(c) => write!(f, "{}", c), + SigmaConjecture::Cor(c) => write!(f, "{}", c), + SigmaConjecture::Cthreshold(c) => write!(f, "{}", c), + } + } +} + /// Algebraic data type of sigma proposition expressions /// Values of this type are used as values of SigmaProp type #[derive(PartialEq, Eq, Debug, Clone, From, TryInto)] @@ -246,8 +273,18 @@ impl From for SigmaBoolean { } } +impl std::fmt::Display for SigmaBoolean { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + SigmaBoolean::TrivialProp(b) => write!(f, "sigmaProp({})", b), + SigmaBoolean::ProofOfKnowledge(kt) => write!(f, "{}", kt), + SigmaBoolean::SigmaConjecture(sc) => write!(f, "{}", sc), + } + } +} + /// Proposition which can be proven and verified by sigma protocol. -#[derive(PartialEq, Eq, Debug, Clone, From, Into)] +#[derive(PartialEq, Eq, Debug, Clone, From, Into, derive_more::Display)] pub struct SigmaProp(SigmaBoolean); impl SigmaProp { @@ -295,6 +332,7 @@ impl From for SigmaProp { )) } } + /// Arbitrary impl for ProveDlog #[cfg(feature = "arbitrary")] #[allow(clippy::unwrap_used)] diff --git a/ergotree-ir/src/sigma_protocol/sigma_boolean/cand.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean/cand.rs index 3b6c31afa..957f46354 100644 --- a/ergotree-ir/src/sigma_protocol/sigma_boolean/cand.rs +++ b/ergotree-ir/src/sigma_protocol/sigma_boolean/cand.rs @@ -48,6 +48,19 @@ impl Cand { } } +impl std::fmt::Display for Cand { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("(")?; + for (i, item) in self.items.iter().enumerate() { + if i > 0 { + f.write_str(" && ")?; + } + item.fmt(f)?; + } + f.write_str(")") + } +} + impl SigmaSerializable for Cand { fn sigma_serialize(&self, w: &mut W) -> SigmaSerializeResult { w.put_u16(self.items.len() as u16)?; diff --git a/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs index 04f589586..acf0c3f9e 100644 --- a/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs +++ b/ergotree-ir/src/sigma_protocol/sigma_boolean/cor.rs @@ -49,6 +49,19 @@ impl Cor { } } +impl std::fmt::Display for Cor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("(")?; + for (i, item) in self.items.iter().enumerate() { + if i > 0 { + f.write_str(" || ")?; + } + item.fmt(f)?; + } + f.write_str(")") + } +} + impl SigmaSerializable for Cor { fn sigma_serialize(&self, w: &mut W) -> SigmaSerializeResult { w.put_u16(self.items.len() as u16)?; diff --git a/ergotree-ir/src/sigma_protocol/sigma_boolean/cthreshold.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean/cthreshold.rs index 8db1243d6..125661d68 100644 --- a/ergotree-ir/src/sigma_protocol/sigma_boolean/cthreshold.rs +++ b/ergotree-ir/src/sigma_protocol/sigma_boolean/cthreshold.rs @@ -81,6 +81,21 @@ impl Cthreshold { } } +impl std::fmt::Display for Cthreshold { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("atLeast(")?; + f.write_str(self.k.to_string().as_str())?; + f.write_str(", (")?; + for (i, item) in self.children.iter().enumerate() { + if i > 0 { + f.write_str(", ")?; + } + item.fmt(f)?; + } + f.write_str(")") + } +} + impl HasStaticOpCode for Cthreshold { const OP_CODE: OpCode = OpCode::ATLEAST; } diff --git a/ergotree-ir/src/source_span.rs b/ergotree-ir/src/source_span.rs new file mode 100644 index 000000000..a48ae4a54 --- /dev/null +++ b/ergotree-ir/src/source_span.rs @@ -0,0 +1,204 @@ +//! Source position for an IR node in the source code + +use crate::mir::and::And; +use crate::mir::bin_op::BinOp; +use crate::mir::block::BlockValue; +use crate::mir::byte_array_to_bigint::ByteArrayToBigInt; +use crate::mir::byte_array_to_long::ByteArrayToLong; +use crate::mir::coll_append::Append; +use crate::mir::coll_by_index::ByIndex; +use crate::mir::coll_exists::Exists; +use crate::mir::coll_filter::Filter; +use crate::mir::coll_fold::Fold; +use crate::mir::coll_forall::ForAll; +use crate::mir::coll_map::Map; +use crate::mir::coll_slice::Slice; +use crate::mir::expr::Expr; +use crate::mir::extract_reg_as::ExtractRegisterAs; +use crate::mir::get_var::GetVar; +use crate::mir::logical_not::LogicalNot; +use crate::mir::method_call::MethodCall; +use crate::mir::negation::Negation; +use crate::mir::option_get::OptionGet; +use crate::mir::option_get_or_else::OptionGetOrElse; +use crate::mir::option_is_defined::OptionIsDefined; +use crate::mir::or::Or; +use crate::mir::property_call::PropertyCall; +use crate::mir::select_field::SelectField; +use crate::mir::subst_const::SubstConstants; +use crate::mir::tree_lookup::TreeLookup; +use crate::mir::val_def::ValDef; + +/// Source position for the Expr +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub struct SourceSpan { + /// Start position in the span + pub offset: usize, + /// The length of the span + pub length: usize, +} + +impl SourceSpan { + /// Empty span + pub fn empty() -> Self { + SourceSpan { + offset: 0, + length: 0, + } + } +} + +impl From<(usize, usize)> for SourceSpan { + fn from(value: (usize, usize)) -> Self { + SourceSpan { + offset: value.0, + length: value.1, + } + } +} + +impl From for miette::SourceSpan { + fn from(value: SourceSpan) -> Self { + miette::SourceSpan::new(value.offset.into(), value.length.into()) + } +} + +/// Wrapper for Expr with source position +#[derive(PartialEq, Eq, Debug, Clone)] +pub struct Spanned { + /// Source position + pub source_span: SourceSpan, + /// Wrapped value + pub expr: T, +} + +impl Spanned { + /// Expression + pub fn expr(&self) -> &T { + &self.expr + } +} + +macro_rules! into_expr { + ($variant: ident) => { + impl From<$variant> for Expr { + fn from(v: $variant) -> Self { + Expr::$variant(Spanned { + source_span: SourceSpan::empty(), + expr: v, + }) + } + } + }; +} + +into_expr!(Append); +into_expr!(BlockValue); +into_expr!(ValDef); +into_expr!(BinOp); +into_expr!(ByIndex); +into_expr!(SubstConstants); +into_expr!(ByteArrayToLong); +into_expr!(ByteArrayToBigInt); +into_expr!(MethodCall); +into_expr!(PropertyCall); +into_expr!(Negation); +into_expr!(OptionGet); +into_expr!(OptionIsDefined); +into_expr!(OptionGetOrElse); +into_expr!(ExtractRegisterAs); +into_expr!(Slice); +into_expr!(Fold); +into_expr!(Map); +into_expr!(Filter); +into_expr!(Exists); +into_expr!(ForAll); +into_expr!(SelectField); +into_expr!(GetVar); +into_expr!(TreeLookup); +into_expr!(And); +into_expr!(Or); +into_expr!(LogicalNot); + +impl From for Spanned { + fn from(v: T) -> Self { + Spanned { + source_span: SourceSpan::empty(), + expr: v, + } + } +} + +impl Expr { + /// Source span for the Expr + pub fn span(&self) -> SourceSpan { + match self { + Expr::Append(op) => op.source_span, + Expr::Const(_) => SourceSpan::empty(), + Expr::ConstPlaceholder(_) => SourceSpan::empty(), + Expr::SubstConstants(op) => op.source_span, + Expr::ByteArrayToLong(op) => op.source_span, + Expr::ByteArrayToBigInt(op) => op.source_span, + Expr::LongToByteArray(_) => SourceSpan::empty(), + Expr::Collection(_) => SourceSpan::empty(), + Expr::Tuple(_) => SourceSpan::empty(), + Expr::CalcBlake2b256(_) => SourceSpan::empty(), + Expr::CalcSha256(_) => SourceSpan::empty(), + Expr::Context => SourceSpan::empty(), + Expr::Global => SourceSpan::empty(), + Expr::GlobalVars(_) => SourceSpan::empty(), + Expr::FuncValue(_) => SourceSpan::empty(), + Expr::Apply(_) => SourceSpan::empty(), + Expr::MethodCall(op) => op.source_span, + Expr::PropertyCall(op) => op.source_span, + Expr::BlockValue(op) => op.source_span, + Expr::ValDef(op) => op.source_span, + Expr::ValUse(_) => SourceSpan::empty(), + Expr::If(_) => SourceSpan::empty(), + Expr::BinOp(op) => op.source_span, + Expr::And(_) => SourceSpan::empty(), + Expr::Or(_) => SourceSpan::empty(), + Expr::Xor(_) => SourceSpan::empty(), + Expr::Atleast(_) => SourceSpan::empty(), + Expr::LogicalNot(_) => SourceSpan::empty(), + Expr::Negation(op) => op.source_span, + Expr::BitInversion(_) => SourceSpan::empty(), + Expr::OptionGet(op) => op.source_span, + Expr::OptionIsDefined(op) => op.source_span, + Expr::OptionGetOrElse(op) => op.source_span, + Expr::ExtractAmount(_) => SourceSpan::empty(), + Expr::ExtractRegisterAs(op) => op.source_span, + Expr::ExtractBytes(_) => SourceSpan::empty(), + Expr::ExtractBytesWithNoRef(_) => SourceSpan::empty(), + Expr::ExtractScriptBytes(_) => SourceSpan::empty(), + Expr::ExtractCreationInfo(_) => SourceSpan::empty(), + Expr::ExtractId(_) => SourceSpan::empty(), + Expr::ByIndex(op) => op.source_span, + Expr::SizeOf(_) => SourceSpan::empty(), + Expr::Slice(op) => op.source_span, + Expr::Fold(op) => op.source_span, + Expr::Map(op) => op.source_span, + Expr::Filter(op) => op.source_span, + Expr::Exists(op) => op.source_span, + Expr::ForAll(op) => op.source_span, + Expr::SelectField(op) => op.source_span, + Expr::BoolToSigmaProp(_) => SourceSpan::empty(), + Expr::Upcast(_) => SourceSpan::empty(), + Expr::Downcast(_) => SourceSpan::empty(), + Expr::CreateProveDlog(_) => SourceSpan::empty(), + Expr::CreateProveDhTuple(_) => SourceSpan::empty(), + Expr::SigmaPropBytes(_) => SourceSpan::empty(), + Expr::DecodePoint(_) => SourceSpan::empty(), + Expr::SigmaAnd(_) => SourceSpan::empty(), + Expr::SigmaOr(_) => SourceSpan::empty(), + Expr::GetVar(op) => op.source_span, + Expr::DeserializeRegister(_) => SourceSpan::empty(), + Expr::DeserializeContext(_) => SourceSpan::empty(), + Expr::MultiplyGroup(_) => SourceSpan::empty(), + Expr::Exponentiate(_) => SourceSpan::empty(), + Expr::XorOf(_) => SourceSpan::empty(), + Expr::TreeLookup(op) => op.source_span, + Expr::CreateAvlTree(_) => SourceSpan::empty(), + } + } +} diff --git a/ergotree-ir/src/type_check.rs b/ergotree-ir/src/type_check.rs index 53b8bf457..447db8593 100644 --- a/ergotree-ir/src/type_check.rs +++ b/ergotree-ir/src/type_check.rs @@ -1,6 +1,7 @@ //! Type checking use crate::mir::expr::Expr; +use crate::source_span::Spanned; /// Typecheck error #[derive(Debug, PartialEq, Eq)] @@ -24,7 +25,10 @@ impl TypeCheckError { pub fn type_check(e: Expr) -> Result { // not really a relevant check, since such kind of check should be in BinOp::new() match &e { - Expr::BinOp(bin) => { + Expr::BinOp(Spanned { + source_span: _, + expr: bin, + }) => { if bin.left.tpe() == bin.right.tpe() { Ok(e) } else { diff --git a/ergotree-ir/src/types/sfunc.rs b/ergotree-ir/src/types/sfunc.rs index f838a3304..1add72303 100644 --- a/ergotree-ir/src/types/sfunc.rs +++ b/ergotree-ir/src/types/sfunc.rs @@ -15,6 +15,20 @@ pub struct SFunc { pub tpe_params: Vec, } +impl std::fmt::Display for SFunc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "(")?; + for (i, item) in self.t_dom.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + item.fmt(f)?; + } + write!(f, ") => ")?; + self.t_range.fmt(f) + } +} + impl SFunc { /// Create new SFunc pub fn new(t_dom: Vec, t_range: SType) -> Self { diff --git a/ergotree-ir/src/types/stuple.rs b/ergotree-ir/src/types/stuple.rs index 95afe5947..51f992d14 100644 --- a/ergotree-ir/src/types/stuple.rs +++ b/ergotree-ir/src/types/stuple.rs @@ -34,6 +34,19 @@ impl std::fmt::Debug for STuple { } } +impl std::fmt::Display for STuple { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "(")?; + for (i, item) in self.items.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + item.fmt(f)?; + } + write!(f, ")") + } +} + impl STuple { /// Create a tuple type for a given type pair pub fn pair(t1: SType, t2: SType) -> Self { diff --git a/ergotree-ir/src/types/stype.rs b/ergotree-ir/src/types/stype.rs index eba90fea1..bc926bdfa 100644 --- a/ergotree-ir/src/types/stype.rs +++ b/ergotree-ir/src/types/stype.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::convert::TryInto; +use std::fmt::Debug; use impl_trait_for_tuples::impl_for_tuples; @@ -126,6 +127,34 @@ impl From for SType { } } +impl std::fmt::Display for SType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SType::STypeVar(t) => write!(f, "{}", t.as_string()), + SType::SAny => write!(f, "Any"), + SType::SUnit => write!(f, "Unit"), + SType::SBoolean => write!(f, "Boolean"), + SType::SByte => write!(f, "Byte"), + SType::SShort => write!(f, "Short"), + SType::SInt => write!(f, "Int"), + SType::SLong => write!(f, "Long"), + SType::SBigInt => write!(f, "BigInt"), + SType::SGroupElement => write!(f, "GroupElement"), + SType::SSigmaProp => write!(f, "SigmaProp"), + SType::SBox => write!(f, "Box"), + SType::SAvlTree => write!(f, "AvlTree"), + SType::SOption(t) => write!(f, "Option[{}]", t), + SType::SColl(t) => write!(f, "Coll[{}]", t), + SType::STuple(t) => write!(f, "{}", t), + SType::SFunc(t) => write!(f, "{}", t), + SType::SContext => write!(f, "Context"), + SType::SHeader => write!(f, "Header"), + SType::SPreHeader => write!(f, "PreHeader"), + SType::SGlobal => write!(f, "Global"), + } + } +} + /// Conversion to SType pub trait LiftIntoSType { /// get SType