1use 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
15pub mod context;
17pub 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#[derive(PartialEq, Eq, Debug, Clone)]
99pub struct ReductionDiagnosticInfo {
100 pub env: Env<'static>,
102 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#[derive(PartialEq, Eq, Debug, Clone)]
117pub struct ReductionResult {
118 pub sigma_prop: SigmaBoolean,
120 pub cost: u64,
122 pub diag: ReductionDiagnosticInfo,
124}
125
126pub 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
179pub 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
187pub(crate) trait Evaluable {
190 fn eval<'ctx>(
192 &self,
193 env: &mut Env<'ctx>,
194 ctx: &Context<'ctx>,
195 ) -> 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}