ergotree_interpreter/
eval.rs

1//! Interpreter
2use ergotree_ir::mir::constant::TryExtractInto;
3use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProp;
4use std::fmt::Display;
5
6use ergotree_ir::mir::expr::Expr;
7use ergotree_ir::mir::value::Value;
8use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
9
10use ergotree_ir::types::smethod::SMethod;
11
12use self::context::Context;
13use self::env::Env;
14
15/// Context(blockchain) for the interpreter
16pub mod context;
17/// Environment for
18pub mod env;
19
20pub(crate) mod and;
21pub(crate) mod apply;
22pub(crate) mod atleast;
23pub(crate) mod bin_op;
24pub(crate) mod bit_inversion;
25pub(crate) mod block;
26pub(crate) mod bool_to_sigma;
27pub(crate) mod byte_array_to_bigint;
28pub(crate) mod byte_array_to_long;
29pub(crate) mod calc_blake2b256;
30pub(crate) mod calc_sha256;
31pub(crate) mod coll_append;
32pub(crate) mod coll_by_index;
33pub(crate) mod coll_exists;
34pub(crate) mod coll_filter;
35pub(crate) mod coll_fold;
36pub(crate) mod coll_forall;
37pub(crate) mod coll_map;
38pub(crate) mod coll_size;
39pub(crate) mod coll_slice;
40pub(crate) mod collection;
41pub(crate) mod cost_accum;
42pub(crate) mod costs;
43pub(crate) mod create_avl_tree;
44pub(crate) mod create_prove_dh_tuple;
45pub(crate) mod create_provedlog;
46pub(crate) mod decode_point;
47mod deserialize_context;
48mod deserialize_register;
49pub(crate) mod downcast;
50mod error;
51pub(crate) mod exponentiate;
52pub(crate) mod expr;
53pub(crate) mod extract_amount;
54pub(crate) mod extract_bytes;
55pub(crate) mod extract_bytes_with_no_ref;
56pub(crate) mod extract_creation_info;
57pub(crate) mod extract_id;
58pub(crate) mod extract_reg_as;
59pub(crate) mod extract_script_bytes;
60pub(crate) mod func_value;
61pub(crate) mod get_var;
62pub(crate) mod global_vars;
63pub(crate) mod if_op;
64pub(crate) mod logical_not;
65pub(crate) mod long_to_byte_array;
66pub(crate) mod method_call;
67pub(crate) mod multiply_group;
68pub(crate) mod negation;
69pub(crate) mod option_get;
70pub(crate) mod option_get_or_else;
71pub(crate) mod option_is_defined;
72pub(crate) mod or;
73pub(crate) mod property_call;
74pub(crate) mod savltree;
75pub(crate) mod sbox;
76pub(crate) mod scoll;
77pub(crate) mod scontext;
78pub(crate) mod select_field;
79pub(crate) mod sglobal;
80pub(crate) mod sgroup_elem;
81pub(crate) mod sheader;
82pub(crate) mod sigma_and;
83pub(crate) mod sigma_or;
84pub(crate) mod sigma_prop_bytes;
85pub(crate) mod soption;
86pub(crate) mod spreheader;
87pub(crate) mod subst_const;
88pub(crate) mod tree_lookup;
89pub(crate) mod tuple;
90pub(crate) mod upcast;
91pub(crate) mod val_use;
92pub(crate) mod xor;
93pub(crate) mod xor_of;
94
95pub use error::EvalError;
96
97/// Diagnostic information about the reduction (pretty printed expr and/or env)
98#[derive(PartialEq, Eq, Debug, Clone)]
99pub struct ReductionDiagnosticInfo {
100    /// environment after the evaluation
101    pub env: Env<'static>,
102    /// expression pretty-printed
103    pub pretty_printed_expr: Option<String>,
104}
105
106impl Display for ReductionDiagnosticInfo {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        if let Some(expr_str) = &self.pretty_printed_expr {
109            writeln!(f, "Pretty printed expr:\n{}", expr_str)?;
110        }
111        write!(f, "Env:\n{}", self.env)
112    }
113}
114
115/// Result of expression reduction procedure (see `reduce_to_crypto`).
116#[derive(PartialEq, Eq, Debug, Clone)]
117pub struct ReductionResult {
118    /// value of SigmaProp type which represents a statement verifiable via sigma protocol.
119    pub sigma_prop: SigmaBoolean,
120    /// estimated cost of expression evaluation
121    pub cost: u64,
122    /// Diagnostic information about the reduction (pretty printed expr and/or env)
123    pub diag: ReductionDiagnosticInfo,
124}
125
126/// Evaluate the given expression by reducing it to SigmaBoolean value.
127pub fn reduce_to_crypto(expr: &Expr, ctx: &Context) -> Result<ReductionResult, EvalError> {
128    fn inner<'ctx>(expr: &'ctx Expr, ctx: &Context<'ctx>) -> Result<ReductionResult, EvalError> {
129        let mut env_mut = Env::empty();
130        expr.eval(&mut env_mut, ctx)
131            .and_then(|v| -> Result<ReductionResult, EvalError> {
132                match v {
133                    Value::Boolean(b) => Ok(ReductionResult {
134                        sigma_prop: SigmaBoolean::TrivialProp(b),
135                        cost: 0,
136                        diag: ReductionDiagnosticInfo {
137                            env: env_mut.to_static(),
138                            pretty_printed_expr: None,
139                        },
140                    }),
141                    Value::SigmaProp(sp) => Ok(ReductionResult {
142                        sigma_prop: sp.value().clone(),
143                        cost: 0,
144                        diag: ReductionDiagnosticInfo {
145                            env: env_mut.to_static(),
146                            pretty_printed_expr: None,
147                        },
148                    }),
149                    _ => Err(EvalError::InvalidResultType),
150                }
151            })
152    }
153
154    let res = inner(expr, ctx);
155    if let Ok(reduction) = res {
156        if reduction.sigma_prop == SigmaBoolean::TrivialProp(false) {
157            let (_, printed_expr_str) = expr
158                .pretty_print()
159                .map_err(|e| EvalError::Misc(e.to_string()))?;
160            let new_reduction = ReductionResult {
161                sigma_prop: SigmaBoolean::TrivialProp(false),
162                cost: reduction.cost,
163                diag: ReductionDiagnosticInfo {
164                    env: reduction.diag.env,
165                    pretty_printed_expr: Some(printed_expr_str),
166                },
167            };
168            return Ok(new_reduction);
169        } else {
170            return Ok(reduction);
171        }
172    }
173    let (spanned_expr, printed_expr_str) = expr
174        .pretty_print()
175        .map_err(|e| EvalError::Misc(e.to_string()))?;
176    inner(&spanned_expr, ctx).map_err(|e| e.wrap_spanned_with_src(printed_expr_str.to_string()))
177}
178
179/// Expects SigmaProp constant value and returns it's value. Otherwise, returns an error.
180pub fn extract_sigma_boolean(expr: &Expr) -> Result<SigmaBoolean, EvalError> {
181    match expr {
182        Expr::Const(c) => Ok(c.clone().try_extract_into::<SigmaProp>()?.into()),
183        _ => Err(EvalError::InvalidResultType),
184    }
185}
186
187/// Expression evaluation.
188/// Should be implemented by every node that can be evaluated.
189pub(crate) trait Evaluable {
190    /// Evaluation routine to be implement by each node
191    fn eval<'ctx>(
192        &self,
193        env: &mut Env<'ctx>,
194        ctx: &Context<'ctx>,
195        // TODO for JIT costing: cost_accum: &mut CostAccumulator,
196    ) -> Result<Value<'ctx>, EvalError>;
197}
198
199type EvalFn = for<'ctx> fn(
200    env: &mut Env<'ctx>,
201    ctx: &Context<'ctx>,
202    Value<'ctx>,
203    Vec<Value<'ctx>>,
204) -> Result<Value<'ctx>, EvalError>;
205
206fn smethod_eval_fn(method: &SMethod) -> Result<EvalFn, EvalError> {
207    use ergotree_ir::types::*;
208    Ok(match method.obj_type.type_code() {
209        savltree::TYPE_CODE => match method.method_id() {
210            savltree::DIGEST_METHOD_ID => self::savltree::DIGEST_EVAL_FN,
211            savltree::UPDATE_DIGEST_METHOD_ID => self::savltree::UPDATE_DIGEST_EVAL_FN,
212            savltree::ENABLED_OPERATIONS_METHOD_ID => self::savltree::ENABLED_OPERATIONS_EVAL_FN,
213            savltree::KEY_LENGTH_METHOD_ID => self::savltree::KEY_LENGTH_EVAL_FN,
214            savltree::VALUE_LENGTH_OPT_METHOD_ID => self::savltree::VALUE_LENGTH_OPT_EVAL_FN,
215            savltree::IS_INSERT_ALLOWED_METHOD_ID => self::savltree::IS_INSERT_ALLOWED_EVAL_FN,
216            savltree::IS_UPDATE_ALLOWED_METHOD_ID => self::savltree::IS_UPDATE_ALLOWED_EVAL_FN,
217            savltree::IS_REMOVE_ALLOWED_METHOD_ID => self::savltree::IS_REMOVE_ALLOWED_EVAL_FN,
218            savltree::UPDATE_OPERATIONS_METHOD_ID => self::savltree::UPDATE_OPERATIONS_EVAL_FN,
219            savltree::GET_METHOD_ID => self::savltree::GET_EVAL_FN,
220            savltree::GET_MANY_METHOD_ID => self::savltree::GET_MANY_EVAL_FN,
221            savltree::INSERT_METHOD_ID => self::savltree::INSERT_EVAL_FN,
222            savltree::CONTAINS_METHOD_ID => self::savltree::CONTAINS_EVAL_FN,
223            savltree::REMOVE_METHOD_ID => self::savltree::REMOVE_EVAL_FN,
224            savltree::UPDATE_METHOD_ID => self::savltree::UPDATE_EVAL_FN,
225            method_id => {
226                return Err(EvalError::NotFound(format!(
227                    "Eval fn: unknown method id in SAvlTree: {:?}",
228                    method_id
229                )))
230            }
231        },
232        scontext::TYPE_CODE => match method.method_id() {
233            scontext::DATA_INPUTS_PROPERTY_METHOD_ID => self::scontext::DATA_INPUTS_EVAL_FN,
234            scontext::SELF_BOX_INDEX_PROPERTY_METHOD_ID => self::scontext::SELF_BOX_INDEX_EVAL_FN,
235            scontext::HEADERS_PROPERTY_METHOD_ID => self::scontext::HEADERS_EVAL_FN,
236            scontext::PRE_HEADER_PROPERTY_METHOD_ID => self::scontext::PRE_HEADER_EVAL_FN,
237            scontext::LAST_BLOCK_UTXO_ROOT_HASH_PROPERTY_METHOD_ID => {
238                self::scontext::LAST_BLOCK_UTXO_ROOT_HASH_EVAL_FN
239            }
240            scontext::MINER_PUBKEY_PROPERTY_METHOD_ID => self::scontext::MINER_PUBKEY_EVAL_FN,
241            method_id => {
242                return Err(EvalError::NotFound(format!(
243                    "Eval fn: unknown method id in SContext: {:?}",
244                    method_id
245                )))
246            }
247        },
248        sbox::TYPE_CODE => match method.method_id() {
249            sbox::VALUE_METHOD_ID => self::sbox::VALUE_EVAL_FN,
250            sbox::GET_REG_METHOD_ID => self::sbox::GET_REG_EVAL_FN,
251            sbox::TOKENS_METHOD_ID => self::sbox::TOKENS_EVAL_FN,
252            method_id => {
253                return Err(EvalError::NotFound(format!(
254                    "Eval fn: unknown method id in SBox: {:?}",
255                    method_id
256                )))
257            }
258        },
259        scoll::TYPE_CODE => match method.method_id() {
260            scoll::INDEX_OF_METHOD_ID => self::scoll::INDEX_OF_EVAL_FN,
261            scoll::FLATMAP_METHOD_ID => self::scoll::flatmap_eval,
262            scoll::ZIP_METHOD_ID => self::scoll::ZIP_EVAL_FN,
263            scoll::INDICES_METHOD_ID => self::scoll::INDICES_EVAL_FN,
264            scoll::PATCH_METHOD_ID => self::scoll::PATCH_EVAL_FN,
265            scoll::UPDATED_METHOD_ID => self::scoll::UPDATED_EVAL_FN,
266            scoll::UPDATE_MANY_METHOD_ID => self::scoll::UPDATE_MANY_EVAL_FN,
267            method_id => {
268                return Err(EvalError::NotFound(format!(
269                    "Eval fn: unknown method id in SCollection: {:?}",
270                    method_id
271                )))
272            }
273        },
274        sgroup_elem::TYPE_CODE => match method.method_id() {
275            sgroup_elem::GET_ENCODED_METHOD_ID => self::sgroup_elem::GET_ENCODED_EVAL_FN,
276            sgroup_elem::NEGATE_METHOD_ID => self::sgroup_elem::NEGATE_EVAL_FN,
277            method_id => {
278                return Err(EvalError::NotFound(format!(
279                    "Eval fn: unknown method id in SGroupElement: {:?}",
280                    method_id
281                )))
282            }
283        },
284        soption::TYPE_CODE => match method.method_id() {
285            soption::MAP_METHOD_ID => self::soption::map_eval,
286            soption::FILTER_METHOD_ID => self::soption::filter_eval,
287            method_id => {
288                return Err(EvalError::NotFound(format!(
289                    "Eval fn: unknown method id in SOption: {:?}",
290                    method_id
291                )))
292            }
293        },
294        sheader::TYPE_CODE => match method.method_id() {
295            sheader::VERSION_METHOD_ID => self::sheader::VERSION_EVAL_FN,
296            sheader::ID_METHOD_ID => self::sheader::ID_EVAL_FN,
297            sheader::PARENT_ID_METHOD_ID => self::sheader::PARENT_ID_EVAL_FN,
298            sheader::AD_PROOFS_ROOT_METHOD_ID => self::sheader::AD_PROOFS_ROOT_EVAL_FN,
299            sheader::STATE_ROOT_METHOD_ID => self::sheader::STATE_ROOT_EVAL_FN,
300            sheader::TRANSACTIONS_ROOT_METHOD_ID => self::sheader::TRANSACTION_ROOT_EVAL_FN,
301            sheader::EXTENSION_ROOT_METHOD_ID => self::sheader::EXTENSION_ROOT_EVAL_FN,
302            sheader::TIMESTAMP_METHOD_ID => self::sheader::TIMESTAMP_EVAL_FN,
303            sheader::N_BITS_METHOD_ID => self::sheader::N_BITS_EVAL_FN,
304            sheader::HEIGHT_METHOD_ID => self::sheader::HEIGHT_EVAL_FN,
305            sheader::MINER_PK_METHOD_ID => self::sheader::MINER_PK_EVAL_FN,
306            sheader::POW_ONETIME_PK_METHOD_ID => self::sheader::POW_ONETIME_PK_EVAL_FN,
307            sheader::POW_DISTANCE_METHOD_ID => self::sheader::POW_DISTANCE_EVAL_FN,
308            sheader::POW_NONCE_METHOD_ID => self::sheader::POW_NONCE_EVAL_FN,
309            sheader::VOTES_METHOD_ID => self::sheader::VOTES_EVAL_FN,
310            method_id => {
311                return Err(EvalError::NotFound(format!(
312                    "Eval fn: method {:?} with method id {:?} not found in SHeader",
313                    method.name(),
314                    method_id,
315                )))
316            }
317        },
318        spreheader::TYPE_CODE => match method.method_id() {
319            spreheader::VERSION_METHOD_ID => self::spreheader::VERSION_EVAL_FN,
320            spreheader::PARENT_ID_METHOD_ID => self::spreheader::PARENT_ID_EVAL_FN,
321            spreheader::TIMESTAMP_METHOD_ID => self::spreheader::TIMESTAMP_EVAL_FN,
322            spreheader::N_BITS_METHOD_ID => self::spreheader::N_BITS_EVAL_FN,
323            spreheader::HEIGHT_METHOD_ID => self::spreheader::HEIGHT_EVAL_FN,
324            spreheader::MINER_PK_METHOD_ID => self::spreheader::MINER_PK_EVAL_FN,
325            spreheader::VOTES_METHOD_ID => self::spreheader::VOTES_EVAL_FN,
326            method_id => {
327                return Err(EvalError::NotFound(format!(
328                    "Eval fn: method {:?} with method id {:?} not found in SPreHeader",
329                    method.name(),
330                    method_id,
331                )))
332            }
333        },
334        sglobal::TYPE_CODE => match method.method_id() {
335            sglobal::GROUP_GENERATOR_METHOD_ID => self::sglobal::GROUP_GENERATOR_EVAL_FN,
336            sglobal::XOR_METHOD_ID => self::sglobal::XOR_EVAL_FN,
337            method_id => {
338                return Err(EvalError::NotFound(format!(
339                    "Eval fn: method {:?} with method id {:?} not found in SGlobal",
340                    method.name(),
341                    method_id,
342                )))
343            }
344        },
345        type_id => {
346            return Err(EvalError::NotFound(format!(
347                "Eval fn: unknown type id {:?}",
348                type_id
349            )))
350        }
351    })
352}
353
354#[cfg(test)]
355#[cfg(feature = "arbitrary")]
356#[allow(clippy::unwrap_used)]
357#[allow(clippy::todo)]
358pub(crate) mod tests {
359
360    #![allow(dead_code)]
361
362    use super::env::Env;
363    use super::*;
364    use ergotree_ir::mir::bin_op::BinOp;
365    use ergotree_ir::mir::bin_op::BinOpKind;
366    use ergotree_ir::mir::bin_op::RelationOp;
367    use ergotree_ir::mir::block::BlockValue;
368    use ergotree_ir::mir::constant::TryExtractFrom;
369    use ergotree_ir::mir::constant::TryExtractInto;
370    use ergotree_ir::mir::val_def::ValDef;
371    use ergotree_ir::mir::val_use::ValUse;
372    use ergotree_ir::types::stype::SType;
373    use expect_test::expect;
374    use sigma_test_util::force_any_val;
375
376    pub fn eval_out_wo_ctx<T: TryExtractFrom<Value<'static>> + 'static>(expr: &Expr) -> T {
377        let ctx = force_any_val::<Context>();
378        eval_out(expr, &ctx)
379    }
380
381    pub fn eval_out<T: TryExtractFrom<Value<'static>> + 'static>(
382        expr: &Expr,
383        ctx: &Context<'static>,
384    ) -> T {
385        let mut env = Env::empty();
386        expr.eval(&mut env, ctx)
387            .unwrap()
388            .to_static()
389            .try_extract_into::<T>()
390            .unwrap()
391    }
392
393    pub fn try_eval_out<'ctx, T: TryExtractFrom<Value<'static>> + 'static>(
394        expr: &Expr,
395        ctx: &'ctx Context<'ctx>,
396    ) -> Result<T, EvalError> {
397        let mut env = Env::empty();
398        expr.eval(&mut env, ctx).and_then(|v| {
399            v.to_static()
400                .try_extract_into::<T>()
401                .map_err(EvalError::TryExtractFrom)
402        })
403    }
404
405    pub fn try_eval_out_wo_ctx<T: TryExtractFrom<Value<'static>> + 'static>(
406        expr: &Expr,
407    ) -> Result<T, EvalError> {
408        let ctx = force_any_val::<Context>();
409        try_eval_out(expr, &ctx)
410    }
411
412    #[test]
413    fn diag_on_reduced_to_false() {
414        let bin_op: Expr = BinOp {
415            kind: BinOpKind::Relation(RelationOp::Eq),
416            left: Box::new(
417                ValUse {
418                    val_id: 1.into(),
419                    tpe: SType::SInt,
420                }
421                .into(),
422            ),
423            right: Box::new(0i32.into()),
424        }
425        .into();
426        let block: Expr = Expr::BlockValue(
427            BlockValue {
428                items: vec![ValDef {
429                    id: 1.into(),
430                    rhs: Box::new(Expr::Const(1i32.into())),
431                }
432                .into()],
433                result: Box::new(bin_op),
434            }
435            .into(),
436        );
437        let ctx = force_any_val::<Context>();
438        let res = reduce_to_crypto(&block, &ctx).unwrap();
439        assert!(res.sigma_prop == SigmaBoolean::TrivialProp(false));
440        expect![[r#"
441            Pretty printed expr:
442            {
443              val v1 = 1
444              v1 == 0
445            }
446
447            Env:
448            v1: 1
449        "#]]
450        .assert_eq(&res.diag.to_string());
451    }
452}