1use std::collections::{BTreeMap, HashMap, HashSet};
2
3use crate::ir::Expression;
4use crate::{backend, ir, ArgValue, CanonicalAssets, Utxo};
5
6#[derive(Debug, thiserror::Error)]
7pub enum Error {
8 #[error("invalid built-in operation {0:?}")]
9 InvalidBuiltInOp(Box<ir::BuiltInOp>),
10
11 #[error(transparent)]
12 BackendError(#[from] backend::Error),
13
14 #[error("invalid argument {0:?} for {1}")]
15 InvalidArgument(ArgValue, String),
16
17 #[error("property {0} not found in {1}")]
18 PropertyNotFound(String, String),
19
20 #[error("property index {0} not found in {1}")]
21 PropertyIndexNotFound(usize, String),
22
23 #[error("invalid {0} operation over {1:?} and {2:?}")]
24 InvalidBinaryOp(String, String, String),
25
26 #[error("invalid {0} operation over {1:?}")]
27 InvalidUnaryOp(String, String),
28
29 #[error("cannot coerce {0:?} into assets")]
30 CannotCoerceIntoAssets(ir::Expression),
31
32 #[error("cannot coerce {0:?} into datum")]
33 CannotCoerceIntoDatum(ir::Expression),
34}
35
36pub trait Indexable: std::fmt::Debug {
37 fn index(&self, index: ir::Expression) -> Option<ir::Expression>;
38
39 fn index_or_err(&self, index: ir::Expression) -> Result<ir::Expression, Error> {
40 let index_value = index.as_number().unwrap_or(0) as usize;
41 self.index(index).ok_or(Error::PropertyIndexNotFound(
42 index_value,
43 format!("{self:?}"),
44 ))
45 }
46}
47
48impl Indexable for ir::StructExpr {
49 fn index(&self, index: ir::Expression) -> Option<ir::Expression> {
50 match index {
52 ir::Expression::Number(n) => self.fields.get(n as usize).cloned(),
53 _ => return None,
54 }
55 }
56}
57
58impl Indexable for ir::Expression {
59 fn index(&self, index: ir::Expression) -> Option<ir::Expression> {
60 match self {
61 ir::Expression::None => None,
62 ir::Expression::Map(x) => x
63 .iter()
64 .find(|(k, _)| *k == index)
65 .map(|(k, v)| ir::Expression::Tuple(Box::new((k.clone(), v.clone())))),
66 ir::Expression::List(x) => x.get(index.as_number()? as usize).cloned(),
67 ir::Expression::Tuple(x) => match index.as_number()? {
68 0 => Some(x.0.clone()),
69 1 => Some(x.1.clone()),
70 _ => None,
71 },
72 ir::Expression::Struct(x) => x.index(index.clone()),
73 _ => None,
74 }
75 }
76}
77
78pub trait Concatenable {
79 fn concat(self, other: ir::Expression) -> Result<ir::Expression, Error>;
80}
81
82pub trait Arithmetic {
83 fn add(self, other: ir::Expression) -> Result<ir::Expression, Error>;
84 fn sub(self, other: ir::Expression) -> Result<ir::Expression, Error>;
85 fn neg(self) -> Result<ir::Expression, Error>;
86}
87
88impl<T> Arithmetic for T
89where
90 T: Into<CanonicalAssets> + std::fmt::Debug,
91{
92 fn add(self, other: ir::Expression) -> Result<ir::Expression, Error> {
93 let y = match other {
94 ir::Expression::Assets(x) => CanonicalAssets::from(x),
95 ir::Expression::None => CanonicalAssets::empty(),
96 other => {
97 return Err(Error::InvalidBinaryOp(
98 "add".to_string(),
99 format!("{self:?}"),
100 format!("{other:?}"),
101 ))
102 }
103 };
104
105 let x = self.into();
106 let total = x + y;
107 Ok(ir::Expression::Assets(total.into()))
108 }
109
110 fn sub(self, other: ir::Expression) -> Result<ir::Expression, Error> {
111 let other_neg = other.neg()?;
112 self.add(other_neg)
113 }
114
115 fn neg(self) -> Result<ir::Expression, Error> {
116 let negated = std::ops::Neg::neg(self.into());
117 Ok(ir::Expression::Assets(negated.into()))
118 }
119}
120
121impl Arithmetic for i128 {
122 fn add(self, other: ir::Expression) -> Result<ir::Expression, Error> {
123 match other {
124 ir::Expression::Number(y) => Ok(ir::Expression::Number(self + y)),
125 ir::Expression::None => Ok(ir::Expression::Number(self)),
126 _ => Err(Error::InvalidBinaryOp(
127 "add".to_string(),
128 format!("{self:?}"),
129 format!("{other:?}"),
130 )),
131 }
132 }
133
134 fn sub(self, other: ir::Expression) -> Result<ir::Expression, Error> {
135 let other_neg = other.neg()?;
136 self.add(other_neg)
137 }
138
139 fn neg(self) -> Result<ir::Expression, Error> {
140 Ok(ir::Expression::Number(-self))
141 }
142}
143
144impl Arithmetic for ir::Expression {
145 fn add(self, other: ir::Expression) -> Result<ir::Expression, Error> {
146 match self {
147 ir::Expression::None => Ok(other),
148 ir::Expression::Number(x) => Arithmetic::add(x, other),
149 ir::Expression::Assets(x) => Arithmetic::add(x, other),
150 x => Err(Error::InvalidBinaryOp(
151 "add".to_string(),
152 format!("{x:?}"),
153 format!("{other:?}"),
154 )),
155 }
156 }
157
158 fn sub(self, other: ir::Expression) -> Result<ir::Expression, Error> {
159 match self {
160 ir::Expression::None => Ok(other),
161 ir::Expression::Number(x) => Arithmetic::sub(x, other),
162 ir::Expression::Assets(x) => Arithmetic::sub(x, other),
163 x => Err(Error::InvalidBinaryOp(
164 "sub".to_string(),
165 format!("{x:?}"),
166 format!("{other:?}"),
167 )),
168 }
169 }
170
171 fn neg(self) -> Result<ir::Expression, Error> {
172 match self {
173 ir::Expression::None => Ok(ir::Expression::None),
174 ir::Expression::Number(x) => Arithmetic::neg(x),
175 ir::Expression::Assets(x) => Arithmetic::neg(x),
176 x => Err(Error::InvalidUnaryOp("neg".to_string(), format!("{x:?}"))),
177 }
178 }
179}
180
181impl Concatenable for String {
182 fn concat(self, other: ir::Expression) -> Result<ir::Expression, Error> {
183 match other {
184 ir::Expression::String(y) => Ok(ir::Expression::String(self + &y)),
185 ir::Expression::None => Ok(ir::Expression::String(self)),
186 _ => Err(Error::InvalidBinaryOp(
187 "concat".to_string(),
188 format!("String({self:?})"),
189 format!("{other:?}"),
190 )),
191 }
192 }
193}
194
195impl Concatenable for Vec<Expression> {
196 fn concat(self, other: Expression) -> Result<Expression, Error> {
197 match other {
198 Expression::List(expressions) => {
199 Ok(Expression::List([&self[..], &expressions[..]].concat()))
200 }
201 _ => Err(Error::InvalidBinaryOp(
202 "concat".to_string(),
203 format!("List({:?})", self),
204 format!("{:?}", other),
205 )),
206 }
207 }
208}
209
210impl Concatenable for Vec<u8> {
211 fn concat(self, other: ir::Expression) -> Result<ir::Expression, Error> {
212 match other {
213 ir::Expression::Bytes(y) => {
214 let mut result = self;
215 result.extend(y);
216 Ok(ir::Expression::Bytes(result))
217 }
218 ir::Expression::None => Ok(ir::Expression::Bytes(self)),
219 _ => Err(Error::InvalidBinaryOp(
220 "concat".to_string(),
221 format!("Bytes({self:?})"),
222 format!("{other:?}"),
223 )),
224 }
225 }
226}
227
228impl Concatenable for ir::Expression {
229 fn concat(self, other: ir::Expression) -> Result<ir::Expression, Error> {
230 match self {
231 ir::Expression::None => Ok(other),
232 ir::Expression::String(x) => Concatenable::concat(x, other),
233 ir::Expression::Bytes(x) => Concatenable::concat(x, other),
234 ir::Expression::List(x) => Concatenable::concat(x, other),
235 x => Err(Error::InvalidBinaryOp(
236 "concat".to_string(),
237 format!("{x:?}"),
238 format!("{other:?}"),
239 )),
240 }
241 }
242}
243
244pub trait Coerceable {
245 fn into_assets(self) -> Result<ir::Expression, Error>;
246 fn into_datum(self) -> Result<ir::Expression, Error>;
247}
248
249impl Coerceable for ir::Expression {
250 fn into_assets(self) -> Result<ir::Expression, Error> {
251 match self {
252 ir::Expression::None => Ok(ir::Expression::None),
253 ir::Expression::Assets(x) => Ok(ir::Expression::Assets(x)),
254 ir::Expression::UtxoSet(x) => {
255 let all = x
256 .into_iter()
257 .map(|x| x.assets)
258 .fold(CanonicalAssets::empty(), |acc, x| acc + x);
259
260 Ok(ir::Expression::Assets(all.into()))
261 }
262 _ => Err(Error::CannotCoerceIntoAssets(self)),
263 }
264 }
265
266 fn into_datum(self) -> Result<ir::Expression, Error> {
267 match self {
268 ir::Expression::None => Ok(ir::Expression::None),
269 ir::Expression::UtxoSet(x) => Ok(x
270 .into_iter()
271 .next()
272 .and_then(|x| x.datum)
273 .unwrap_or(ir::Expression::None)),
274 ir::Expression::List(x) => Ok(ir::Expression::List(x)),
275 ir::Expression::Map(x) => Ok(ir::Expression::Map(x)),
276 ir::Expression::Tuple(x) => Ok(ir::Expression::Tuple(x)),
277 ir::Expression::Struct(x) => Ok(ir::Expression::Struct(x)),
278 ir::Expression::Bytes(x) => Ok(ir::Expression::Bytes(x)),
279 ir::Expression::Number(x) => Ok(ir::Expression::Number(x)),
280 ir::Expression::String(x) => Ok(ir::Expression::String(x)),
281 ir::Expression::Address(x) => Ok(ir::Expression::Bytes(x)),
282 ir::Expression::Hash(x) => Ok(ir::Expression::Bytes(x)),
283 _ => Err(Error::CannotCoerceIntoDatum(self)),
284 }
285 }
286}
287
288fn arg_value_into_expr(arg: ArgValue) -> ir::Expression {
289 match arg {
290 ArgValue::Address(x) => ir::Expression::Address(x),
291 ArgValue::Int(x) => ir::Expression::Number(x),
292 ArgValue::Bool(x) => ir::Expression::Bool(x),
293 ArgValue::String(x) => ir::Expression::String(x),
294 ArgValue::Bytes(x) => ir::Expression::Bytes(x),
295 ArgValue::UtxoSet(x) => ir::Expression::UtxoSet(x),
296 ArgValue::UtxoRef(x) => ir::Expression::UtxoRefs(vec![x]),
297 }
298}
299
300pub trait Apply: Sized + std::fmt::Debug {
301 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error>;
302 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error>;
303 fn apply_fees(self, fees: u64) -> Result<Self, Error>;
304
305 fn is_constant(&self) -> bool;
306
307 fn params(&self) -> BTreeMap<String, ir::Type>;
308 fn queries(&self) -> BTreeMap<String, ir::InputQuery>;
309
310 fn reduce(self) -> Result<Self, Error>;
311}
312
313pub trait Composite: Sized {
314 fn reduce_self(self) -> Result<Self, Error> {
315 Ok(self)
316 }
317
318 fn components(&self) -> Vec<&ir::Expression>;
319
320 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
321 where
322 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone;
323
324 fn reduce_nested(self) -> Result<Self, Error> {
325 self.try_map_components(|x| x.reduce())
326 }
327}
328
329impl<T> Apply for T
330where
331 T: Composite + std::fmt::Debug,
332{
333 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
334 self.try_map_components(|x| x.apply_args(args))
335 }
336
337 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
338 self.try_map_components(|x| x.apply_inputs(args))
339 }
340
341 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
342 self.try_map_components(|x| x.apply_fees(fees))
343 }
344
345 fn is_constant(&self) -> bool {
346 self.components().iter().all(|x| x.is_constant())
347 }
348
349 fn params(&self) -> BTreeMap<String, ir::Type> {
350 self.components().iter().flat_map(|x| x.params()).collect()
351 }
352
353 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
354 self.components().iter().flat_map(|x| x.queries()).collect()
355 }
356
357 fn reduce(self) -> Result<Self, Error> {
358 let x = self.reduce_nested()?;
359
360 if x.is_constant() {
361 x.reduce_self()
362 } else {
363 Ok(x)
364 }
365 }
366}
367
368impl<T> Apply for Option<T>
369where
370 T: Apply,
371{
372 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
373 self.map(|x| x.apply_args(args)).transpose()
374 }
375
376 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
377 self.map(|x| x.apply_inputs(args)).transpose()
378 }
379
380 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
381 self.map(|x| x.apply_fees(fees)).transpose()
382 }
383
384 fn is_constant(&self) -> bool {
385 match self {
386 Some(x) => x.is_constant(),
387 None => true,
388 }
389 }
390
391 fn params(&self) -> BTreeMap<String, ir::Type> {
392 match self {
393 Some(x) => x.params(),
394 None => BTreeMap::new(),
395 }
396 }
397
398 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
399 match self {
400 Some(x) => x.queries(),
401 None => BTreeMap::new(),
402 }
403 }
404
405 fn reduce(self) -> Result<Self, Error> {
406 self.map(|x| x.reduce()).transpose()
407 }
408}
409
410impl<T> Apply for Vec<T>
411where
412 T: Apply,
413{
414 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
415 self.into_iter().map(|x| x.apply_args(args)).collect()
416 }
417
418 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
419 self.into_iter().map(|x| x.apply_inputs(args)).collect()
420 }
421
422 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
423 self.into_iter().map(|x| x.apply_fees(fees)).collect()
424 }
425
426 fn is_constant(&self) -> bool {
427 self.iter().all(|x| x.is_constant())
428 }
429
430 fn params(&self) -> BTreeMap<String, ir::Type> {
431 self.iter().flat_map(|x| x.params()).collect()
432 }
433
434 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
435 self.iter().flat_map(|x| x.queries()).collect()
436 }
437
438 fn reduce(self) -> Result<Self, Error> {
439 self.into_iter().map(|x| x.reduce()).collect()
440 }
441}
442
443impl<T> Apply for HashMap<String, T>
444where
445 T: Apply,
446{
447 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
448 self.into_iter()
449 .map(|(k, v)| v.apply_args(args).map(|v| (k, v)))
450 .collect()
451 }
452
453 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
454 self.into_iter()
455 .map(|(k, v)| v.apply_inputs(args).map(|v| (k, v)))
456 .collect()
457 }
458
459 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
460 self.into_iter()
461 .map(|(k, v)| v.apply_fees(fees).map(|v| (k, v)))
462 .collect()
463 }
464
465 fn is_constant(&self) -> bool {
466 self.values().all(|x| x.is_constant())
467 }
468
469 fn params(&self) -> BTreeMap<String, ir::Type> {
470 self.values().flat_map(|x| x.params()).collect()
471 }
472
473 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
474 self.values().flat_map(|x| x.queries()).collect()
475 }
476
477 fn reduce(self) -> Result<Self, Error> {
478 self.into_iter()
479 .map(|(k, v)| v.reduce().map(|v| (k, v)))
480 .collect()
481 }
482}
483
484impl Composite for ir::ScriptSource {
485 fn reduce_self(self) -> Result<Self, Error> {
486 Ok(self)
487 }
488
489 fn components(&self) -> Vec<&ir::Expression> {
490 match self {
491 ir::ScriptSource::Embedded(x) => vec![x],
492 ir::ScriptSource::UtxoRef { r#ref, source } => {
493 std::iter::once(r#ref).chain(source.as_ref()).collect()
494 }
495 }
496 }
497
498 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
499 where
500 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
501 {
502 match self {
503 ir::ScriptSource::Embedded(x) => Ok(ir::ScriptSource::Embedded(f(x)?)),
504 ir::ScriptSource::UtxoRef { r#ref, source } => Ok(ir::ScriptSource::UtxoRef {
505 r#ref: f(r#ref)?,
506 source: source.map(&f).transpose()?,
507 }),
508 }
509 }
510}
511
512impl TryFrom<&ArgValue> for ir::ScriptSource {
513 type Error = Error;
514
515 fn try_from(value: &ArgValue) -> Result<Self, Self::Error> {
516 match value {
517 ArgValue::Bytes(x) => Ok(ir::ScriptSource::Embedded(ir::Expression::Bytes(x.clone()))),
518 ArgValue::UtxoRef(x) => Ok(ir::ScriptSource::UtxoRef {
519 r#ref: ir::Expression::UtxoRefs(vec![x.clone()]),
520 source: None,
521 }),
522 _ => Err(Error::InvalidArgument(value.clone(), "script".to_string())),
523 }
524 }
525}
526
527impl Composite for ir::PolicyExpr {
528 fn components(&self) -> Vec<&ir::Expression> {
529 let script = self.script.components();
530 std::iter::once(&self.hash).chain(script).collect()
531 }
532
533 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
534 where
535 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
536 {
537 Ok(Self {
538 name: self.name,
539 hash: f(self.hash)?,
540 script: self.script.try_map_components(f)?,
541 })
542 }
543}
544
545impl Composite for ir::StructExpr {
546 fn components(&self) -> Vec<&ir::Expression> {
547 self.fields.iter().collect()
548 }
549
550 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
551 where
552 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
553 {
554 Ok(Self {
555 constructor: self.constructor,
556 fields: self
557 .fields
558 .into_iter()
559 .map(&f)
560 .collect::<Result<Vec<_>, _>>()?,
561 })
562 }
563}
564
565impl Composite for ir::AssetExpr {
566 fn components(&self) -> Vec<&ir::Expression> {
567 vec![&self.policy, &self.asset_name, &self.amount]
568 }
569
570 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
571 where
572 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
573 {
574 Ok(Self {
575 policy: f(self.policy)?,
576 asset_name: f(self.asset_name)?,
577 amount: f(self.amount)?,
578 })
579 }
580}
581
582impl Composite for ir::Coerce {
583 fn components(&self) -> Vec<&ir::Expression> {
584 match self {
585 Self::IntoAssets(x) => vec![x],
586 Self::IntoDatum(x) => vec![x],
587 Self::IntoScript(x) => vec![x],
588 Self::NoOp(x) => vec![x],
589 }
590 }
591
592 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
593 where
594 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
595 {
596 match self {
597 Self::IntoAssets(x) => Ok(Self::IntoAssets(f(x)?)),
598 Self::IntoDatum(x) => Ok(Self::IntoDatum(f(x)?)),
599 Self::IntoScript(x) => Ok(Self::IntoScript(f(x)?)),
600 Self::NoOp(x) => Ok(Self::NoOp(f(x)?)),
601 }
602 }
603
604 fn reduce_self(self) -> Result<Self, Error> {
605 match self {
606 Self::NoOp(x) => Ok(Self::NoOp(x)),
607 Self::IntoAssets(x) => Ok(Self::NoOp(x.into_assets()?)),
608 Self::IntoDatum(x) => Ok(Self::NoOp(x.into_datum()?)),
609 Self::IntoScript(x) => todo!(),
610 }
611 }
612}
613
614impl Composite for ir::BuiltInOp {
615 fn components(&self) -> Vec<&ir::Expression> {
616 match self {
617 Self::NoOp(x) => vec![x],
618 Self::Add(x, y) => vec![x, y],
619 Self::Sub(x, y) => vec![x, y],
620 Self::Concat(x, y) => vec![x, y],
621 Self::Negate(x) => vec![x],
622 Self::Property(x, _) => vec![x],
623 }
624 }
625
626 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
627 where
628 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
629 {
630 match self {
631 Self::NoOp(x) => Ok(Self::NoOp(f(x)?)),
632 Self::Add(x, y) => Ok(Self::Add(f(x)?, f(y)?)),
633 Self::Sub(x, y) => Ok(Self::Sub(f(x)?, f(y)?)),
634 Self::Concat(x, y) => Ok(Self::Concat(f(x)?, f(y)?)),
635 Self::Negate(x) => Ok(Self::Negate(f(x)?)),
636 Self::Property(x, prop) => Ok(Self::Property(f(x)?, prop)),
637 }
638 }
639
640 fn reduce_self(self) -> Result<Self, Error> {
641 match self {
642 Self::Add(x, y) => Ok(Self::NoOp(x.add(y)?)),
643 Self::Sub(x, y) => Ok(Self::NoOp(x.sub(y)?)),
644 Self::Concat(x, y) => Ok(Self::NoOp(x.concat(y)?)),
645 Self::Negate(x) => Ok(Self::NoOp(x.neg()?)),
646 Self::Property(x, prop) => Ok(Self::NoOp(x.index_or_err(prop)?)),
647 Self::NoOp(x) => Ok(Self::NoOp(x)),
648 }
649 }
650
651 fn reduce_nested(self) -> Result<Self, Error> {
652 match self {
653 Self::Add(x, y) => Ok(Self::Add(x.reduce()?, y.reduce()?)),
654 Self::Sub(x, y) => Ok(Self::Sub(x.reduce()?, y.reduce()?)),
655 Self::Concat(x, y) => Ok(Self::Concat(x.reduce()?, y.reduce()?)),
656 Self::Negate(x) => Ok(Self::Negate(x.reduce()?)),
657 Self::Property(x, y) => Ok(Self::Property(x.reduce()?, y.reduce()?)),
658 Self::NoOp(x) => Ok(Self::NoOp(x.reduce()?)),
659 }
660 }
661}
662
663impl From<ir::AssetExpr> for CanonicalAssets {
664 fn from(asset: ir::AssetExpr) -> Self {
665 let policy = asset.expect_constant_policy();
666 let name = asset.expect_constant_name();
667 let amount = asset.expect_constant_amount();
668
669 Self::from_asset(policy, name, amount)
670 }
671}
672
673impl From<Vec<ir::AssetExpr>> for CanonicalAssets {
674 fn from(assets: Vec<ir::AssetExpr>) -> Self {
675 let mut result = CanonicalAssets::empty();
676
677 for asset in assets {
678 let asset = asset.into();
679 result = result + asset;
680 }
681
682 result
683 }
684}
685
686impl From<CanonicalAssets> for Vec<ir::AssetExpr> {
687 fn from(assets: CanonicalAssets) -> Self {
688 let mut result = Vec::new();
689
690 for (class, amount) in assets.into_iter() {
691 result.push(ir::AssetExpr {
692 policy: class
693 .policy()
694 .map(|x| ir::Expression::Bytes(x.to_vec()))
695 .unwrap_or(ir::Expression::None),
696 asset_name: class
697 .name()
698 .map(|x| ir::Expression::Bytes(x.to_vec()))
699 .unwrap_or(ir::Expression::None),
700 amount: ir::Expression::Number(amount),
701 });
702 }
703
704 result
705 }
706}
707
708impl ir::AssetExpr {
709 fn expect_constant_policy(&self) -> Option<&[u8]> {
710 match &self.policy {
711 ir::Expression::None => None,
712 ir::Expression::Bytes(x) => Some(x.as_slice()),
713 _ => None,
714 }
715 }
716
717 fn expect_constant_name(&self) -> Option<&[u8]> {
718 match &self.asset_name {
719 ir::Expression::None => None,
720 ir::Expression::Bytes(x) => Some(x.as_slice()),
721 ir::Expression::String(x) => Some(x.as_bytes()),
722 _ => None,
723 }
724 }
725
726 fn expect_constant_amount(&self) -> i128 {
727 match &self.amount {
728 ir::Expression::Number(x) => *x,
729 _ => unreachable!("amount expected to be Number"),
730 }
731 }
732}
733
734impl Composite for ir::Input {
735 fn components(&self) -> Vec<&ir::Expression> {
736 vec![&self.utxos, &self.redeemer]
737 }
738
739 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
740 where
741 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
742 {
743 Ok(Self {
744 name: self.name,
745 utxos: f(self.utxos)?,
746 redeemer: f(self.redeemer)?,
747 })
748 }
749}
750
751impl Composite for ir::InputQuery {
752 fn components(&self) -> Vec<&ir::Expression> {
753 vec![&self.address, &self.min_amount, &self.r#ref]
754 }
755
756 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
757 where
758 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
759 {
760 Ok(Self {
761 address: f(self.address)?,
762 min_amount: f(self.min_amount)?,
763 r#ref: f(self.r#ref)?,
764 ..self
765 })
766 }
767}
768
769impl Apply for ir::Param {
770 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
771 match self {
772 ir::Param::ExpectValue(name, ty) => {
773 let defined = args.get(&name).cloned();
774
775 match defined {
776 Some(x) => Ok(ir::Param::Set(arg_value_into_expr(x))),
777 None => Ok(Self::ExpectValue(name, ty)),
778 }
779 }
780 ir::Param::ExpectInput(name, query) => {
782 Ok(ir::Param::ExpectInput(name, query.apply_args(args)?))
783 }
784 x => Ok(x),
785 }
786 }
787
788 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
789 match self {
790 ir::Param::ExpectInput(name, query) => {
791 let defined = args.get(&name).cloned();
792
793 match defined {
794 Some(x) => Ok(ir::Param::Set(ir::Expression::UtxoSet(x))),
795 None => Ok(Self::ExpectInput(name, query)),
796 }
797 }
798 x => Ok(x),
799 }
800 }
801
802 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
803 match self {
804 ir::Param::ExpectFees => Ok(ir::Param::Set(ir::Expression::Assets(vec![
805 ir::AssetExpr {
806 policy: ir::Expression::None,
807 asset_name: ir::Expression::None,
808 amount: ir::Expression::Number(fees as i128),
809 },
810 ]))),
811 ir::Param::ExpectInput(name, query) => {
813 Ok(ir::Param::ExpectInput(name, query.apply_fees(fees)?))
814 }
815 x => Ok(x),
816 }
817 }
818
819 fn is_constant(&self) -> bool {
820 match self {
821 ir::Param::Set(x) => x.is_constant(),
822 _ => false,
823 }
824 }
825
826 fn params(&self) -> BTreeMap<String, ir::Type> {
827 match self {
828 ir::Param::ExpectValue(name, ty) => BTreeMap::from([(name.clone(), ty.clone())]),
829 ir::Param::ExpectInput(_, x) => x.params(),
831 _ => BTreeMap::new(),
832 }
833 }
834
835 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
836 match self {
837 ir::Param::ExpectInput(name, query) => BTreeMap::from([(name.clone(), query.clone())]),
838 _ => BTreeMap::new(),
839 }
840 }
841
842 fn reduce(self) -> Result<Self, Error> {
843 match self {
844 ir::Param::ExpectInput(name, query) => {
846 Ok(ir::Param::ExpectInput(name, query.reduce()?))
847 }
848 x => Ok(x),
849 }
850 }
851}
852
853impl Apply for ir::Expression {
854 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
855 match self {
856 Self::List(x) => Ok(Self::List(
857 x.into_iter()
858 .map(|x| x.apply_args(args))
859 .collect::<Result<_, _>>()?,
860 )),
861 Self::Tuple(x) => Ok(Self::Tuple(Box::new((
862 x.0.apply_args(args)?,
863 x.1.apply_args(args)?,
864 )))),
865 Self::Map(x) => Ok(Self::Map(
866 x.into_iter()
867 .map(|(k, v)| {
868 Ok::<(Expression, Expression), Error>((
869 k.apply_args(args)?,
870 v.apply_args(args)?,
871 ))
872 })
873 .collect::<Result<_, _>>()?,
874 )),
875 Self::Struct(x) => Ok(Self::Struct(x.apply_args(args)?)),
876 Self::Assets(x) => Ok(Self::Assets(
877 x.into_iter()
878 .map(|x| x.apply_args(args))
879 .collect::<Result<_, _>>()?,
880 )),
881 Self::EvalParam(x) => Ok(Self::EvalParam(Box::new(x.apply_args(args)?))),
882 Self::EvalBuiltIn(x) => Ok(Self::EvalBuiltIn(Box::new(x.apply_args(args)?))),
883 Self::EvalCoerce(x) => Ok(Self::EvalCoerce(Box::new(x.apply_args(args)?))),
884 Self::EvalCompiler(x) => Ok(Self::EvalCompiler(Box::new(x.apply_args(args)?))),
885 Self::AdHocDirective(x) => Ok(Self::AdHocDirective(Box::new(x.apply_args(args)?))),
886
887 Self::None => Ok(self),
891 Self::Bytes(_) => Ok(self),
892 Self::Number(_) => Ok(self),
893 Self::Bool(_) => Ok(self),
894 Self::String(_) => Ok(self),
895 Self::Address(_) => Ok(self),
896 Self::Hash(_) => Ok(self),
897 Self::UtxoRefs(_) => Ok(self),
898 Self::UtxoSet(_) => Ok(self),
899 }
900 }
901
902 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
903 match self {
904 Self::List(x) => Ok(Self::List(
905 x.into_iter()
906 .map(|x| x.apply_inputs(args))
907 .collect::<Result<_, _>>()?,
908 )),
909 Self::Map(x) => Ok(Self::Map(
910 x.into_iter()
911 .map(|(k, v)| {
912 Ok::<(Expression, Expression), Error>((
913 k.apply_inputs(args)?,
914 v.apply_inputs(args)?,
915 ))
916 })
917 .collect::<Result<_, _>>()?,
918 )),
919 Self::Tuple(x) => Ok(Self::Tuple(Box::new((
920 x.0.apply_inputs(args)?,
921 x.1.apply_inputs(args)?,
922 )))),
923 Self::Struct(x) => Ok(Self::Struct(x.apply_inputs(args)?)),
924 Self::Assets(x) => Ok(Self::Assets(
925 x.into_iter()
926 .map(|x| x.apply_inputs(args))
927 .collect::<Result<_, _>>()?,
928 )),
929 Self::EvalParam(x) => Ok(Self::EvalParam(Box::new(x.apply_inputs(args)?))),
930 Self::EvalBuiltIn(x) => Ok(Self::EvalBuiltIn(Box::new(x.apply_inputs(args)?))),
931 Self::EvalCoerce(x) => Ok(Self::EvalCoerce(Box::new(x.apply_inputs(args)?))),
932 Self::EvalCompiler(x) => Ok(Self::EvalCompiler(Box::new(x.apply_inputs(args)?))),
933 Self::AdHocDirective(x) => Ok(Self::AdHocDirective(Box::new(x.apply_inputs(args)?))),
934
935 Self::None => Ok(self),
939 Self::Bytes(_) => Ok(self),
940 Self::Number(_) => Ok(self),
941 Self::Bool(_) => Ok(self),
942 Self::String(_) => Ok(self),
943 Self::Address(_) => Ok(self),
944 Self::Hash(_) => Ok(self),
945 Self::UtxoRefs(_) => Ok(self),
946 Self::UtxoSet(_) => Ok(self),
947 }
948 }
949
950 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
951 match self {
952 Self::List(x) => Ok(Self::List(
953 x.into_iter()
954 .map(|x| x.apply_fees(fees))
955 .collect::<Result<_, _>>()?,
956 )),
957 Self::Map(x) => Ok(Self::Map(
958 x.into_iter()
959 .map(|(k, v)| {
960 Ok::<(Expression, Expression), Error>((
961 k.apply_fees(fees)?,
962 v.apply_fees(fees)?,
963 ))
964 })
965 .collect::<Result<_, _>>()?,
966 )),
967 Self::Tuple(x) => Ok(Self::Tuple(Box::new((
968 x.0.apply_fees(fees)?,
969 x.1.apply_fees(fees)?,
970 )))),
971 Self::Struct(x) => Ok(Self::Struct(x.apply_fees(fees)?)),
972 Self::Assets(x) => Ok(Self::Assets(
973 x.into_iter()
974 .map(|x| x.apply_fees(fees))
975 .collect::<Result<_, _>>()?,
976 )),
977 Self::EvalParam(x) => Ok(Self::EvalParam(Box::new(x.apply_fees(fees)?))),
978 Self::EvalBuiltIn(x) => Ok(Self::EvalBuiltIn(Box::new(x.apply_fees(fees)?))),
979 Self::EvalCoerce(x) => Ok(Self::EvalCoerce(Box::new(x.apply_fees(fees)?))),
980 Self::EvalCompiler(x) => Ok(Self::EvalCompiler(Box::new(x.apply_fees(fees)?))),
981 Self::AdHocDirective(x) => Ok(Self::AdHocDirective(Box::new(x.apply_fees(fees)?))),
982
983 Self::None => Ok(self),
987 Self::Bytes(_) => Ok(self),
988 Self::Number(_) => Ok(self),
989 Self::Bool(_) => Ok(self),
990 Self::String(_) => Ok(self),
991 Self::Address(_) => Ok(self),
992 Self::Hash(_) => Ok(self),
993 Self::UtxoRefs(_) => Ok(self),
994 Self::UtxoSet(_) => Ok(self),
995 }
996 }
997
998 fn is_constant(&self) -> bool {
999 match self {
1000 Self::List(x) => x.iter().all(|x| x.is_constant()),
1001 Self::Map(x) => x.iter().all(|(k, v)| k.is_constant() && v.is_constant()),
1002 Self::Tuple(x) => x.0.is_constant() && x.1.is_constant(),
1003 Self::Struct(x) => x.is_constant(),
1004 Self::Assets(x) => x.iter().all(|x| x.is_constant()),
1005 Self::EvalParam(x) => x.is_constant(),
1006 Self::EvalBuiltIn(x) => x.is_constant(),
1007 Self::EvalCoerce(x) => x.is_constant(),
1008 Self::EvalCompiler(_) => false,
1009 Self::AdHocDirective(x) => x.is_constant(),
1010
1011 Self::None => true,
1015 Self::Bytes(_) => true,
1016 Self::Number(_) => true,
1017 Self::Bool(_) => true,
1018 Self::String(_) => true,
1019 Self::Address(_) => true,
1020 Self::Hash(_) => true,
1021 Self::UtxoRefs(_) => true,
1022 Self::UtxoSet(_) => true,
1023 }
1024 }
1025
1026 fn params(&self) -> BTreeMap<String, ir::Type> {
1027 match self {
1028 Self::List(x) => x.iter().flat_map(|x| x.params()).collect(),
1029 Self::Map(x) => x
1030 .iter()
1031 .flat_map(|(k, v)| [k.params(), v.params()].into_iter().flatten())
1032 .collect(),
1033 Self::Tuple(x) => [x.0.params(), x.1.params()].into_iter().flatten().collect(),
1034 Self::Struct(x) => x.params(),
1035 Self::Assets(x) => x.iter().flat_map(|x| x.params()).collect(),
1036 Self::EvalParam(x) => x.params(),
1037 Self::EvalBuiltIn(x) => x.params(),
1038 Self::EvalCoerce(x) => x.params(),
1039 Self::EvalCompiler(x) => x.params(),
1040 Self::AdHocDirective(x) => x.params(),
1041
1042 Self::None => BTreeMap::new(),
1046 Self::Bytes(_) => BTreeMap::new(),
1047 Self::Number(_) => BTreeMap::new(),
1048 Self::Bool(_) => BTreeMap::new(),
1049 Self::String(_) => BTreeMap::new(),
1050 Self::Address(_) => BTreeMap::new(),
1051 Self::Hash(_) => BTreeMap::new(),
1052 Self::UtxoRefs(_) => BTreeMap::new(),
1053 Self::UtxoSet(_) => BTreeMap::new(),
1054 }
1055 }
1056
1057 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
1058 match self {
1059 Self::List(x) => x.iter().flat_map(|x| x.queries()).collect(),
1060 Self::Map(x) => x
1061 .iter()
1062 .flat_map(|(k, v)| [k.queries(), v.queries()].into_iter().flatten())
1063 .collect(),
1064 Self::Tuple(x) => [x.0.queries(), x.1.queries()]
1065 .into_iter()
1066 .flatten()
1067 .collect(),
1068 Self::Struct(x) => x.queries(),
1069 Self::Assets(x) => x.iter().flat_map(|x| x.queries()).collect(),
1070 Self::EvalParam(x) => x.queries(),
1071 Self::EvalBuiltIn(x) => x.queries(),
1072 Self::EvalCoerce(x) => x.queries(),
1073 Self::EvalCompiler(x) => x.queries(),
1074 Self::AdHocDirective(x) => x.queries(),
1075
1076 Self::None => BTreeMap::new(),
1080 Self::Bytes(_) => BTreeMap::new(),
1081 Self::Number(_) => BTreeMap::new(),
1082 Self::Bool(_) => BTreeMap::new(),
1083 Self::String(_) => BTreeMap::new(),
1084 Self::Address(_) => BTreeMap::new(),
1085 Self::Hash(_) => BTreeMap::new(),
1086 Self::UtxoRefs(_) => BTreeMap::new(),
1087 Self::UtxoSet(_) => BTreeMap::new(),
1088 }
1089 }
1090
1091 fn reduce(self) -> Result<Self, Error> {
1092 match self {
1093 ir::Expression::List(x) => Ok(Self::List(
1095 x.into_iter()
1096 .map(|x| x.reduce())
1097 .collect::<Result<_, _>>()?,
1098 )),
1099 ir::Expression::Map(x) => Ok(Self::Map(
1100 x.into_iter()
1101 .map(|(k, v)| Ok::<(Expression, Expression), Error>((k.reduce()?, v.reduce()?)))
1102 .collect::<Result<_, _>>()?,
1103 )),
1104 ir::Expression::Tuple(x) => Ok(Self::Tuple(Box::new((x.0.reduce()?, x.1.reduce()?)))),
1105 ir::Expression::Struct(x) => Ok(Self::Struct(x.reduce()?)),
1106 ir::Expression::Assets(x) => Ok(Self::Assets(
1107 x.into_iter()
1108 .map(|x| x.reduce())
1109 .collect::<Result<_, _>>()?,
1110 )),
1111 ir::Expression::EvalCompiler(x) => Ok(Self::EvalCompiler(Box::new(x.reduce()?))),
1112 ir::Expression::AdHocDirective(x) => Ok(Self::AdHocDirective(Box::new(x.reduce()?))),
1113
1114 ir::Expression::EvalBuiltIn(x) => match x.reduce()? {
1116 ir::BuiltInOp::NoOp(x) => Ok(x),
1117 x => Ok(ir::Expression::EvalBuiltIn(Box::new(x.reduce()?))),
1118 },
1119 ir::Expression::EvalCoerce(x) => match x.reduce()? {
1120 ir::Coerce::NoOp(x) => Ok(x),
1121 x => Ok(ir::Expression::EvalCoerce(Box::new(x.reduce()?))),
1122 },
1123 ir::Expression::EvalParam(x) => match x.reduce()? {
1124 ir::Param::Set(x) => Ok(x),
1125 x => Ok(ir::Expression::EvalParam(Box::new(x.reduce()?))),
1126 },
1127
1128 Self::None => Ok(self),
1132 Self::Bytes(_) => Ok(self),
1133 Self::Number(_) => Ok(self),
1134 Self::Bool(_) => Ok(self),
1135 Self::String(_) => Ok(self),
1136 Self::Address(_) => Ok(self),
1137 Self::Hash(_) => Ok(self),
1138 Self::UtxoRefs(_) => Ok(self),
1139 Self::UtxoSet(_) => Ok(self),
1140 }
1141 }
1142}
1143
1144impl Composite for ir::Output {
1145 fn components(&self) -> Vec<&ir::Expression> {
1146 vec![&self.address, &self.datum, &self.amount]
1147 }
1148
1149 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1150 where
1151 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1152 {
1153 Ok(Self {
1154 address: f(self.address)?,
1155 datum: f(self.datum)?,
1156 amount: f(self.amount)?,
1157 optional: self.optional,
1158 })
1159 }
1160}
1161
1162impl Composite for ir::Mint {
1163 fn components(&self) -> Vec<&ir::Expression> {
1164 vec![&self.amount, &self.redeemer]
1165 }
1166
1167 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1168 where
1169 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1170 {
1171 Ok(Self {
1172 amount: f(self.amount)?,
1173 redeemer: f(self.redeemer)?,
1174 })
1175 }
1176}
1177
1178impl Composite for ir::AdHocDirective {
1179 fn components(&self) -> Vec<&ir::Expression> {
1180 self.data.values().collect()
1181 }
1182
1183 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1184 where
1185 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1186 {
1187 Ok(Self {
1188 name: self.name,
1189 data: self
1190 .data
1191 .into_iter()
1192 .map(|(k, v)| f(v).map(|v| (k, v)))
1193 .collect::<Result<_, _>>()?,
1194 })
1195 }
1196}
1197
1198impl Composite for ir::CompilerOp {
1199 fn components(&self) -> Vec<&ir::Expression> {
1200 match self {
1201 ir::CompilerOp::BuildScriptAddress(x) => vec![x],
1202 ir::CompilerOp::ComputeMinUtxo(x) => vec![x],
1203 ir::CompilerOp::ComputeTipSlot => vec![],
1204 ir::CompilerOp::ComputeSlotToTime(x) => vec![x],
1205 ir::CompilerOp::ComputeTimeToSlot(x) => vec![x],
1206 }
1207 }
1208
1209 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1210 where
1211 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1212 {
1213 match self {
1214 ir::CompilerOp::BuildScriptAddress(x) => Ok(ir::CompilerOp::BuildScriptAddress(f(x)?)),
1215 ir::CompilerOp::ComputeMinUtxo(x) => Ok(ir::CompilerOp::ComputeMinUtxo(f(x)?)),
1216 ir::CompilerOp::ComputeTipSlot => Ok(ir::CompilerOp::ComputeTipSlot),
1217 ir::CompilerOp::ComputeSlotToTime(x) => Ok(ir::CompilerOp::ComputeSlotToTime(f(x)?)),
1218 ir::CompilerOp::ComputeTimeToSlot(x) => Ok(ir::CompilerOp::ComputeTimeToSlot(f(x)?)),
1219 }
1220 }
1221}
1222
1223impl Composite for ir::Signers {
1224 fn components(&self) -> Vec<&ir::Expression> {
1225 self.signers.iter().collect()
1226 }
1227
1228 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1229 where
1230 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1231 {
1232 Ok(Self {
1233 signers: self.signers.into_iter().map(f).collect::<Result<_, _>>()?,
1234 })
1235 }
1236}
1237
1238impl Composite for ir::Collateral {
1239 fn components(&self) -> Vec<&ir::Expression> {
1240 vec![&self.utxos]
1241 }
1242
1243 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1244 where
1245 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1246 {
1247 Ok(Self {
1248 utxos: f(self.utxos)?,
1249 })
1250 }
1251}
1252
1253impl Composite for ir::Validity {
1254 fn components(&self) -> Vec<&ir::Expression> {
1255 vec![&self.since, &self.until]
1256 }
1257
1258 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1259 where
1260 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1261 {
1262 Ok(Self {
1263 since: f(self.since)?,
1264 until: f(self.until)?,
1265 })
1266 }
1267}
1268
1269impl Composite for ir::Metadata {
1270 fn components(&self) -> Vec<&ir::Expression> {
1271 vec![&self.key, &self.value]
1272 }
1273
1274 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1275 where
1276 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1277 {
1278 Ok(Self {
1279 key: f(self.key)?,
1280 value: f(self.value)?,
1281 })
1282 }
1283}
1284
1285impl Apply for ir::Tx {
1286 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
1287 let tx = ir::Tx {
1288 references: self.references.apply_args(args)?,
1289 inputs: self.inputs.apply_args(args)?,
1290 outputs: self.outputs.apply_args(args)?,
1291 validity: self.validity.apply_args(args)?,
1292 mints: self.mints.apply_args(args)?,
1293 burns: self.burns.apply_args(args)?,
1294 fees: self.fees.apply_args(args)?,
1295 adhoc: self.adhoc.apply_args(args)?,
1296 collateral: self.collateral.apply_args(args)?,
1297 signers: self.signers.apply_args(args)?,
1298 metadata: self.metadata.apply_args(args)?,
1299 };
1300
1301 Ok(tx)
1302 }
1303
1304 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
1305 Ok(Self {
1306 references: self.references.apply_inputs(args)?,
1307 inputs: self.inputs.apply_inputs(args)?,
1308 outputs: self.outputs.apply_inputs(args)?,
1309 validity: self.validity.apply_inputs(args)?,
1310 mints: self.mints.apply_inputs(args)?,
1311 burns: self.burns.apply_inputs(args)?,
1312 fees: self.fees.apply_inputs(args)?,
1313 adhoc: self.adhoc.apply_inputs(args)?,
1314 collateral: self.collateral.apply_inputs(args)?,
1315 signers: self.signers.apply_inputs(args)?,
1316 metadata: self.metadata.apply_inputs(args)?,
1317 })
1318 }
1319
1320 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
1321 Ok(Self {
1322 references: self.references.apply_fees(fees)?,
1323 inputs: self.inputs.apply_fees(fees)?,
1324 outputs: self.outputs.apply_fees(fees)?,
1325 validity: self.validity.apply_fees(fees)?,
1326 mints: self.mints.apply_fees(fees)?,
1327 burns: self.burns.apply_fees(fees)?,
1328 fees: self.fees.apply_fees(fees)?,
1329 adhoc: self.adhoc.apply_fees(fees)?,
1330 collateral: self.collateral.apply_fees(fees)?,
1331 signers: self.signers.apply_fees(fees)?,
1332 metadata: self.metadata.apply_fees(fees)?,
1333 })
1334 }
1335
1336 fn is_constant(&self) -> bool {
1337 self.inputs.iter().all(|x| x.is_constant())
1338 && self.outputs.iter().all(|x| x.is_constant())
1339 && self.mints.iter().all(|x| x.is_constant())
1340 && self.burns.iter().all(|x| x.is_constant())
1341 && self.fees.is_constant()
1342 && self.metadata.is_constant()
1343 && self.validity.is_constant()
1344 && self.references.is_constant()
1345 && self.collateral.is_constant()
1346 && self.adhoc.iter().all(|x| x.is_constant())
1347 && self.signers.is_constant()
1348 }
1349
1350 fn params(&self) -> BTreeMap<String, ir::Type> {
1351 let mut params = BTreeMap::new();
1353 params.extend(self.inputs.params());
1354 params.extend(self.outputs.params());
1355 params.extend(self.mints.params());
1356 params.extend(self.burns.params());
1357 params.extend(self.fees.params());
1358 params.extend(self.adhoc.params());
1359 params.extend(self.signers.params());
1360 params.extend(self.validity.params());
1361 params.extend(self.metadata.params());
1362 params.extend(self.references.params());
1363 params.extend(self.collateral.params());
1364 params
1365 }
1366
1367 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
1368 let mut queries = BTreeMap::new();
1369 queries.extend(self.inputs.queries());
1370 queries.extend(self.outputs.queries());
1371 queries.extend(self.mints.queries());
1372 queries.extend(self.burns.queries());
1373 queries.extend(self.fees.queries());
1374 queries.extend(self.adhoc.queries());
1375 queries.extend(self.signers.queries());
1376 queries.extend(self.validity.queries());
1377 queries.extend(self.metadata.queries());
1378 queries.extend(self.collateral.queries());
1379 queries.extend(self.references.queries());
1380 queries
1381 }
1382
1383 fn reduce(self) -> Result<Self, Error> {
1384 Ok(Self {
1385 references: self.references.reduce()?,
1386 inputs: self.inputs.reduce()?,
1387 outputs: self.outputs.reduce()?,
1388 validity: self.validity.reduce()?,
1389 mints: self.mints.reduce()?,
1390 burns: self.burns.reduce()?,
1391 fees: self.fees.reduce()?,
1392 adhoc: self.adhoc.reduce()?,
1393 collateral: self.collateral.reduce()?,
1394 signers: self.signers.reduce()?,
1395 metadata: self.metadata.reduce()?,
1396 })
1397 }
1398}
1399
1400pub fn apply_args(template: ir::Tx, args: &BTreeMap<String, ArgValue>) -> Result<ir::Tx, Error> {
1401 template.apply_args(args)
1402}
1403
1404pub fn apply_inputs(
1405 template: ir::Tx,
1406 args: &BTreeMap<String, HashSet<Utxo>>,
1407) -> Result<ir::Tx, Error> {
1408 template.apply_inputs(args)
1409}
1410
1411pub fn apply_fees(template: ir::Tx, fees: u64) -> Result<ir::Tx, Error> {
1412 template.apply_fees(fees)
1413}
1414
1415pub fn reduce(template: ir::Tx) -> Result<ir::Tx, Error> {
1416 template.reduce()
1417}
1418
1419pub fn find_params(template: &ir::Tx) -> BTreeMap<String, ir::Type> {
1420 template.params()
1421}
1422
1423pub fn find_queries(template: &ir::Tx) -> BTreeMap<String, ir::InputQuery> {
1424 template.queries()
1425}
1426
1427#[cfg(test)]
1428mod tests {
1429
1430 use crate::UtxoRef;
1431
1432 use super::*;
1433
1434 const SUBJECT_PROTOCOL: &str = r#"
1435 party Sender;
1436
1437 tx swap(a: Int, b: Int) {
1438 input source {
1439 from: Sender,
1440 min_amount: Ada(a) + Ada(b) + fees,
1441 }
1442 }
1443 "#;
1444
1445 #[test]
1446 fn param_expression_is_applied() {
1447 let ir = ir::Expression::EvalParam(Box::new(ir::Param::ExpectValue(
1448 "a".to_string(),
1449 ir::Type::Int,
1450 )));
1451
1452 let params = ir.params();
1453 assert_eq!(params.len(), 1);
1454 assert_eq!(params.get("a"), Some(&ir::Type::Int));
1455
1456 let args = BTreeMap::from([("a".to_string(), ArgValue::Int(100))]);
1457
1458 let after = ir.apply_args(&args).unwrap();
1459
1460 assert_eq!(
1461 after,
1462 ir::Expression::EvalParam(Box::new(ir::Param::Set(ir::Expression::Number(100),)))
1463 );
1464 }
1465
1466 #[test]
1467 fn nested_param_expression_is_applied() {
1468 let ir = ir::Expression::EvalParam(Box::new(ir::Param::ExpectInput(
1469 "out".to_string(),
1470 ir::InputQuery {
1471 address: ir::Expression::None,
1472 min_amount: ir::Expression::None,
1473 r#ref: ir::Expression::EvalParam(Box::new(ir::Param::ExpectValue(
1474 "in".to_string(),
1475 ir::Type::Int,
1476 ))),
1477 many: false,
1478 collateral: false,
1479 },
1480 )));
1481
1482 let params = ir.params();
1483 assert_eq!(params.len(), 1);
1484 assert_eq!(params.get("in"), Some(&ir::Type::Int));
1485
1486 let args = BTreeMap::from([("in".to_string(), ArgValue::Int(100))]);
1487 let after = ir.apply_args(&args).unwrap();
1488
1489 let after = after.reduce().unwrap();
1490
1491 let queries = after.queries();
1492 assert_eq!(queries.len(), 1);
1493 assert_eq!(
1494 queries.get("out"),
1495 Some(&ir::InputQuery {
1496 address: ir::Expression::None,
1497 min_amount: ir::Expression::None,
1498 r#ref: ir::Expression::Number(100),
1499 many: false,
1500 collateral: false,
1501 })
1502 );
1503 }
1504
1505 #[test]
1506 fn param_expression_is_reduced() {
1507 let ir = ir::Expression::EvalParam(Box::new(ir::Param::Set(ir::Expression::Number(3))));
1508
1509 let after = ir.reduce().unwrap();
1510
1511 assert_eq!(after, ir::Expression::Number(3));
1512 }
1513
1514 #[test]
1515 fn test_apply_args() {
1516 let mut ast = crate::parsing::parse_string(SUBJECT_PROTOCOL).unwrap();
1517 crate::analyzing::analyze(&mut ast).ok().unwrap();
1518
1519 let before = crate::lowering::lower(&ast, "swap").unwrap();
1520
1521 dbg!(&before);
1522
1523 let params = find_params(&before);
1524 assert_eq!(params.len(), 3);
1525 assert_eq!(params.get("sender"), Some(&ir::Type::Address));
1526 assert_eq!(params.get("a"), Some(&ir::Type::Int));
1527 assert_eq!(params.get("b"), Some(&ir::Type::Int));
1528
1529 let args = BTreeMap::from([
1530 ("sender".to_string(), ArgValue::Address(b"abc".to_vec())),
1531 ("a".to_string(), ArgValue::Int(100)),
1532 ("b".to_string(), ArgValue::Int(200)),
1533 ]);
1534
1535 let after = apply_args(before, &args).unwrap();
1536
1537 let params = find_params(&after);
1538 assert_eq!(params.len(), 0);
1539 }
1540
1541 #[test]
1542 fn test_apply_inputs() {
1543 let mut ast = crate::parsing::parse_string(SUBJECT_PROTOCOL).unwrap();
1544 crate::analyzing::analyze(&mut ast).ok().unwrap();
1545
1546 let before = crate::lowering::lower(&ast, "swap").unwrap();
1547
1548 let args = BTreeMap::from([
1549 ("sender".to_string(), ArgValue::Address(b"abc".to_vec())),
1550 ("a".to_string(), ArgValue::Int(100)),
1551 ("b".to_string(), ArgValue::Int(200)),
1552 ]);
1553
1554 let before = apply_args(before, &args).unwrap();
1555
1556 let before = before.reduce().unwrap();
1557
1558 let queries = find_queries(&before);
1559 dbg!(&queries);
1560
1561 assert_eq!(queries.len(), 1);
1562 assert!(queries.contains_key("source"));
1563
1564 let inputs = BTreeMap::from([(
1565 "source".to_string(),
1566 HashSet::from([Utxo {
1567 r#ref: UtxoRef::new(b"abc", 0),
1568 address: b"abc".to_vec(),
1569 datum: None,
1570 assets: CanonicalAssets::from_naked_amount(300),
1571 script: None,
1572 }]),
1573 )]);
1574
1575 let after = apply_inputs(before, &inputs).unwrap();
1576
1577 let queries = find_queries(&after);
1578 dbg!(&queries);
1579
1580 assert_eq!(queries.len(), 0);
1581 }
1582
1583 #[test]
1584 fn test_apply_fees() {
1585 let mut ast = crate::parsing::parse_string(SUBJECT_PROTOCOL).unwrap();
1586 crate::analyzing::analyze(&mut ast).ok().unwrap();
1587
1588 let before = crate::lowering::lower(&ast, "swap").unwrap();
1589
1590 let args = BTreeMap::from([
1591 ("sender".to_string(), ArgValue::Address(b"abc".to_vec())),
1592 ("a".to_string(), ArgValue::Int(100)),
1593 ("b".to_string(), ArgValue::Int(200)),
1594 ]);
1595
1596 let before = apply_args(before, &args).unwrap().reduce().unwrap();
1597
1598 let after = before.apply_fees(100).unwrap().reduce().unwrap();
1599
1600 let queries = find_queries(&after);
1601
1602 let query = queries.get("source").unwrap();
1603
1604 assert_eq!(
1605 query,
1606 &ir::InputQuery {
1607 address: ir::Expression::Address(b"abc".to_vec()),
1608 min_amount: ir::Expression::Assets(vec![ir::AssetExpr {
1609 policy: ir::Expression::None,
1610 asset_name: ir::Expression::None,
1611 amount: ir::Expression::Number(400),
1612 }]),
1613 r#ref: ir::Expression::None,
1614 many: false,
1615 collateral: false,
1616 }
1617 );
1618 }
1619
1620 #[test]
1621 fn built_in_expression_is_reduced() {
1622 let op =
1623 ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::NoOp(ir::Expression::Number(5))));
1624
1625 let after = op.reduce().unwrap();
1626
1627 assert_eq!(after, ir::Expression::Number(5))
1628 }
1629
1630 #[test]
1631 fn numeric_add_is_reduced() {
1632 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Add(
1633 ir::Expression::Number(1),
1634 ir::Expression::Number(5),
1635 )));
1636
1637 let after = op.reduce().unwrap();
1638
1639 assert_eq!(after, ir::Expression::Number(6));
1640 }
1641
1642 #[test]
1643 fn numeric_sub_is_reduced() {
1644 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Sub(
1645 ir::Expression::Number(8),
1646 ir::Expression::Number(5),
1647 )));
1648
1649 let after = op.reduce().unwrap();
1650
1651 assert_eq!(after, ir::Expression::Number(3));
1652 }
1653
1654 #[test]
1655 fn nested_numeric_binary_op_is_reduced() {
1656 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Add(
1657 ir::Expression::Number(1),
1658 ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Sub(
1659 ir::Expression::Number(5),
1660 ir::Expression::Number(3),
1661 ))),
1662 )));
1663
1664 let after = op.reduce().unwrap();
1665
1666 assert_eq!(after, ir::Expression::Number(3));
1667 }
1668
1669 #[test]
1670 fn test_reduce_single_custom_asset_binary_op() {
1671 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Add(
1672 ir::Expression::Assets(vec![ir::AssetExpr {
1673 policy: ir::Expression::Bytes(b"abc".to_vec()),
1674 asset_name: ir::Expression::Bytes(b"111".to_vec()),
1675 amount: ir::Expression::Number(100),
1676 }]),
1677 ir::Expression::Assets(vec![ir::AssetExpr {
1678 policy: ir::Expression::Bytes(b"abc".to_vec()),
1679 asset_name: ir::Expression::Bytes(b"111".to_vec()),
1680 amount: ir::Expression::Number(200),
1681 }]),
1682 )));
1683
1684 let reduced = op.reduce().unwrap();
1685
1686 match reduced {
1687 ir::Expression::Assets(assets) => {
1688 assert_eq!(assets.len(), 1);
1689 assert_eq!(assets[0].policy, ir::Expression::Bytes(b"abc".to_vec()));
1690 assert_eq!(assets[0].asset_name, ir::Expression::Bytes(b"111".to_vec()));
1691 assert_eq!(assets[0].amount, ir::Expression::Number(300));
1692 }
1693 _ => panic!("Expected assets"),
1694 };
1695 }
1696
1697 #[test]
1698 fn test_reduce_native_asset_binary_op() {
1699 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Add(
1700 ir::Expression::Assets(vec![ir::AssetExpr {
1701 policy: ir::Expression::None,
1702 asset_name: ir::Expression::None,
1703 amount: ir::Expression::Number(100),
1704 }]),
1705 ir::Expression::Assets(vec![ir::AssetExpr {
1706 policy: ir::Expression::None,
1707 asset_name: ir::Expression::None,
1708 amount: ir::Expression::Number(200),
1709 }]),
1710 )));
1711
1712 let reduced = op.reduce().unwrap();
1713
1714 match reduced {
1715 ir::Expression::Assets(assets) => {
1716 assert_eq!(assets.len(), 1);
1717 assert_eq!(assets[0].policy, ir::Expression::None);
1718 assert_eq!(assets[0].asset_name, ir::Expression::None);
1719 assert_eq!(assets[0].amount, ir::Expression::Number(300));
1720 }
1721 _ => panic!("Expected assets"),
1722 };
1723 }
1724
1725 #[test]
1726 fn test_reduce_mixed_asset_binary_op() {
1727 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Add(
1728 ir::Expression::Assets(vec![ir::AssetExpr {
1729 policy: ir::Expression::None,
1730 asset_name: ir::Expression::None,
1731 amount: ir::Expression::Number(100),
1732 }]),
1733 ir::Expression::Assets(vec![ir::AssetExpr {
1734 policy: ir::Expression::Bytes(b"abc".to_vec()),
1735 asset_name: ir::Expression::Bytes(b"111".to_vec()),
1736 amount: ir::Expression::Number(200),
1737 }]),
1738 )));
1739
1740 let reduced = op.reduce().unwrap();
1741
1742 match reduced {
1743 ir::Expression::Assets(assets) => {
1744 assert_eq!(assets.len(), 2);
1745
1746 for asset in assets {
1747 if asset.policy == ir::Expression::None {
1748 assert_eq!(asset.asset_name, ir::Expression::None);
1749 assert_eq!(asset.amount, ir::Expression::Number(100));
1750 } else {
1751 assert_eq!(asset.policy, ir::Expression::Bytes(b"abc".to_vec()));
1752 assert_eq!(asset.asset_name, ir::Expression::Bytes(b"111".to_vec()));
1753 assert_eq!(asset.amount, ir::Expression::Number(200));
1754 }
1755 }
1756 }
1757 _ => panic!("Expected assets"),
1758 };
1759 }
1760
1761 #[test]
1762 fn test_reduce_coerce_noop() {
1763 let op = ir::Expression::EvalCoerce(Box::new(ir::Coerce::NoOp(ir::Expression::Number(5))));
1764
1765 let reduced = op.reduce().unwrap();
1766
1767 match reduced {
1768 ir::Expression::Number(5) => (),
1769 _ => panic!("Expected number 5"),
1770 };
1771 }
1772
1773 #[test]
1774 fn test_coerce_utxo_set_into_assets() {
1775 let utxos = vec![Utxo {
1776 r#ref: UtxoRef::new(b"abc", 1),
1777 address: b"abc".into(),
1778 datum: Some(ir::Expression::Number(1)),
1779 assets: CanonicalAssets::from_defined_asset(b"abc", b"111", 1),
1780 script: None,
1781 }];
1782
1783 let op = ir::Coerce::IntoAssets(ir::Expression::UtxoSet(HashSet::from_iter(
1784 utxos.clone().into_iter(),
1785 )));
1786
1787 let reduced = op.reduce().unwrap();
1788
1789 assert_eq!(
1790 reduced,
1791 ir::Coerce::NoOp(ir::Expression::Assets(utxos[0].assets.clone().into()))
1792 );
1793 }
1794
1795 #[test]
1796 fn test_coerce_utxo_set_into_datum() {
1797 let utxos = vec![Utxo {
1798 r#ref: UtxoRef::new(b"abc", 1),
1799 address: b"abc".into(),
1800 datum: Some(ir::Expression::Number(1)),
1801 assets: CanonicalAssets::from_naked_amount(1),
1802 script: None,
1803 }];
1804
1805 let op = ir::Coerce::IntoDatum(ir::Expression::UtxoSet(HashSet::from_iter(
1806 utxos.clone().into_iter(),
1807 )));
1808
1809 let reduced = op.reduce().unwrap();
1810
1811 assert_eq!(reduced, ir::Coerce::NoOp(utxos[0].datum.clone().unwrap()));
1812 }
1813
1814 #[test]
1815 fn test_reduce_struct_property_access() {
1816 let object = ir::Expression::Struct(ir::StructExpr {
1817 constructor: 0,
1818 fields: vec![
1819 ir::Expression::Number(1),
1820 ir::Expression::Number(2),
1821 ir::Expression::Number(3),
1822 ],
1823 });
1824
1825 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(
1826 object.clone(),
1827 Expression::Number(1),
1828 )));
1829
1830 let reduced = op.reduce();
1831
1832 match reduced {
1833 Ok(ir::Expression::Number(2)) => (),
1834 _ => panic!("Expected number 2"),
1835 };
1836
1837 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(
1838 object.clone(),
1839 Expression::Number(100),
1840 )));
1841
1842 let reduced = op.reduce();
1843
1844 match reduced {
1845 Err(Error::PropertyIndexNotFound(100, _)) => (),
1846 _ => panic!("Expected property index not found"),
1847 };
1848 }
1849
1850 #[test]
1851 fn test_reduce_list_property_access() {
1852 let object = ir::Expression::List(vec![
1853 ir::Expression::Number(1),
1854 ir::Expression::Number(2),
1855 ir::Expression::Number(3),
1856 ]);
1857
1858 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(
1859 object.clone(),
1860 Expression::Number(1),
1861 )));
1862
1863 let reduced = op.reduce();
1864
1865 match reduced {
1866 Ok(ir::Expression::Number(2)) => (),
1867 _ => panic!("Expected number 2"),
1868 };
1869
1870 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(
1871 object.clone(),
1872 ir::Expression::Number(100),
1873 )));
1874
1875 let reduced = op.reduce();
1876
1877 match reduced {
1878 Err(Error::PropertyIndexNotFound(100, _)) => (),
1879 _ => panic!("Expected property index not found"),
1880 };
1881 }
1882
1883 #[test]
1884 fn test_reduce_tuple_property_access() {
1885 let object = ir::Expression::Tuple(Box::new((
1886 ir::Expression::Number(1),
1887 ir::Expression::Number(2),
1888 )));
1889
1890 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(
1891 object.clone(),
1892 Expression::Number(1),
1893 )));
1894
1895 let reduced = op.reduce();
1896
1897 match reduced {
1898 Ok(ir::Expression::Number(2)) => (),
1899 _ => panic!("Expected number 2"),
1900 };
1901
1902 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(
1903 object.clone(),
1904 Expression::Number(100),
1905 )));
1906
1907 let reduced = op.reduce();
1908
1909 match reduced {
1910 Err(Error::PropertyIndexNotFound(100, _)) => (),
1911 _ => panic!("Expected property index not found"),
1912 };
1913 }
1914
1915 #[test]
1916 fn test_string_concat_is_reduced() {
1917 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Concat(
1918 ir::Expression::String("hello".to_string()),
1919 ir::Expression::String("world".to_string()),
1920 )));
1921
1922 let reduced = op.reduce().unwrap();
1923
1924 match reduced {
1925 ir::Expression::String(s) => assert_eq!(s, "helloworld"),
1926 _ => panic!("Expected string 'helloworld'"),
1927 }
1928 }
1929
1930 #[test]
1931 fn test_bytes_concat_is_reduced() {
1932 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Concat(
1933 ir::Expression::Bytes(vec![1, 2, 3]),
1934 ir::Expression::Bytes(vec![4, 5, 6]),
1935 )));
1936
1937 let reduced = op.reduce().unwrap();
1938
1939 match reduced {
1940 ir::Expression::Bytes(b) => assert_eq!(b, vec![1, 2, 3, 4, 5, 6]),
1941 _ => panic!("Expected bytes [1, 2, 3, 4, 5, 6]"),
1942 }
1943 }
1944
1945 #[test]
1946 fn test_list_concat_is_reduced() {
1947 let op = Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Concat(
1948 Expression::List(vec![Expression::Number(1)]),
1949 Expression::List(vec![Expression::Number(2)]),
1950 )));
1951
1952 let reduced = op.reduce().unwrap();
1953
1954 match reduced {
1955 ir::Expression::List(b) => {
1956 assert_eq!(b, vec![Expression::Number(1), Expression::Number(2)])
1957 }
1958 _ => panic!("Expected List [Number(1), Number(2)"),
1959 }
1960 }
1961
1962 #[test]
1963 fn test_concat_type_mismatch_error() {
1964 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Concat(
1965 ir::Expression::String("hello".to_string()),
1966 ir::Expression::Bytes(vec![1, 2, 3]),
1967 )));
1968
1969 let reduced = op.reduce();
1970
1971 match reduced {
1972 Err(Error::InvalidBinaryOp(op, _, _)) => assert_eq!(op, "concat"),
1973 _ => panic!("Expected InvalidBinaryOp error"),
1974 }
1975 }
1976
1977 #[test]
1978 fn test_concat_with_none() {
1979 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Concat(
1980 ir::Expression::String("hello".to_string()),
1981 ir::Expression::None,
1982 )));
1983
1984 let reduced = op.reduce().unwrap();
1985
1986 match reduced {
1987 ir::Expression::String(s) => assert_eq!(s, "hello"),
1988 _ => panic!("Expected string 'hello'"),
1989 }
1990 }
1991
1992 #[test]
1993 fn test_min_utxo_add_non_reduction() {
1994 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Add(
1995 ir::Expression::Assets(vec![ir::AssetExpr {
1996 policy: ir::Expression::None,
1997 asset_name: ir::Expression::None,
1998 amount: ir::Expression::Number(29),
1999 }]),
2000 ir::Expression::EvalCompiler(Box::new(ir::CompilerOp::ComputeMinUtxo(
2001 ir::Expression::Number(20),
2002 ))),
2003 )));
2004
2005 let reduced = op.clone().reduce().unwrap();
2006
2007 assert!(op == reduced)
2008 }
2009
2010 #[test]
2011 fn test_min_utxo_sub_non_reduction() {
2012 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Sub(
2013 ir::Expression::Assets(vec![ir::AssetExpr {
2014 policy: ir::Expression::None,
2015 asset_name: ir::Expression::None,
2016 amount: ir::Expression::Number(29),
2017 }]),
2018 ir::Expression::EvalCompiler(Box::new(ir::CompilerOp::ComputeMinUtxo(
2019 ir::Expression::Number(20),
2020 ))),
2021 )));
2022
2023 let reduced = op.clone().reduce().unwrap();
2024
2025 assert!(op == reduced)
2026 }
2027
2028 #[test]
2029 fn test_index_list_with_expression() {
2030 let list = ir::Expression::List(vec![
2031 ir::Expression::String("first".to_string()),
2032 ir::Expression::String("second".to_string()),
2033 ir::Expression::String("third".to_string()),
2034 ]);
2035
2036 let index_expr = ir::Expression::Number(1);
2037 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(
2038 list.clone(),
2039 index_expr,
2040 )));
2041
2042 let reduced = op.reduce().unwrap();
2043
2044 match reduced {
2045 ir::Expression::String(s) => assert_eq!(s, "second"),
2046 _ => panic!("Expected string 'second'"),
2047 }
2048 }
2049
2050 #[test]
2051 fn test_index_list_out_of_bounds() {
2052 let list = ir::Expression::List(vec![ir::Expression::Number(1), ir::Expression::Number(2)]);
2053
2054 let index_expr = ir::Expression::Number(5);
2055 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(
2056 list.clone(),
2057 index_expr,
2058 )));
2059
2060 let reduced = op.reduce();
2061
2062 match reduced {
2063 Err(Error::PropertyIndexNotFound(5, _)) => (),
2064 _ => panic!("Expected PropertyIndexNotFound error"),
2065 }
2066 }
2067
2068 #[test]
2069 fn test_index_tuple_with_expression() {
2070 let tuple = ir::Expression::Tuple(Box::new((
2071 ir::Expression::String("left".to_string()),
2072 ir::Expression::String("right".to_string()),
2073 )));
2074
2075 let index_expr = ir::Expression::Number(0);
2076 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(
2077 tuple.clone(),
2078 index_expr,
2079 )));
2080
2081 let reduced = op.reduce().unwrap();
2082
2083 match reduced {
2084 ir::Expression::String(s) => assert_eq!(s, "left"),
2085 _ => panic!("Expected string 'left'"),
2086 }
2087
2088 let index_expr = ir::Expression::Number(1);
2089 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(
2090 tuple.clone(),
2091 index_expr,
2092 )));
2093
2094 let reduced = op.reduce().unwrap();
2095
2096 match reduced {
2097 ir::Expression::String(s) => assert_eq!(s, "right"),
2098 _ => panic!("Expected string 'right'"),
2099 }
2100 }
2101
2102 #[test]
2103 fn test_index_struct_with_expression() {
2104 let struct_expr = ir::Expression::Struct(ir::StructExpr {
2105 constructor: 0,
2106 fields: vec![
2107 ir::Expression::String("field0".to_string()),
2108 ir::Expression::String("field1".to_string()),
2109 ir::Expression::String("field2".to_string()),
2110 ],
2111 });
2112
2113 let index_expr = ir::Expression::Number(1);
2114 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(
2115 struct_expr.clone(),
2116 index_expr,
2117 )));
2118
2119 let reduced = op.reduce().unwrap();
2120
2121 match reduced {
2122 ir::Expression::String(s) => assert_eq!(s, "field1"),
2123 _ => panic!("Expected string 'field1'"),
2124 }
2125 }
2126
2127 #[test]
2128 fn test_index_none_expression() {
2129 let none_expr = ir::Expression::None;
2130 let index_expr = ir::Expression::Number(0);
2131
2132 let op =
2133 ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(none_expr, index_expr)));
2134
2135 let reduced = op.reduce();
2136
2137 match reduced {
2138 Err(Error::PropertyIndexNotFound(0, _)) => (),
2139 _ => panic!("Expected PropertyIndexNotFound error for None expression"),
2140 }
2141 }
2142
2143 #[test]
2144 fn test_indexable_trait_on_expression() {
2145 let list_expr =
2146 ir::Expression::List(vec![ir::Expression::Number(10), ir::Expression::Number(20)]);
2147
2148 let result = list_expr.index(ir::Expression::Number(0));
2149 assert_eq!(result, Some(ir::Expression::Number(10)));
2150
2151 let result = list_expr.index(ir::Expression::Number(1));
2152 assert_eq!(result, Some(ir::Expression::Number(20)));
2153
2154 let result = list_expr.index(ir::Expression::Number(2));
2155 assert_eq!(result, None);
2156
2157 let tuple_expr = ir::Expression::Tuple(Box::new((
2158 ir::Expression::Number(100),
2159 ir::Expression::Number(200),
2160 )));
2161
2162 let result = tuple_expr.index(ir::Expression::Number(0));
2163 assert_eq!(result, Some(ir::Expression::Number(100)));
2164
2165 let result = tuple_expr.index(ir::Expression::Number(1));
2166 assert_eq!(result, Some(ir::Expression::Number(200)));
2167
2168 let result = tuple_expr.index(ir::Expression::Number(2));
2169 assert_eq!(result, None);
2170 }
2171
2172 #[test]
2173 fn test_nested_property_access() {
2174 let nested_expr = ir::Expression::List(vec![
2175 ir::Expression::Tuple(Box::new((
2176 ir::Expression::Number(1),
2177 ir::Expression::Number(2),
2178 ))),
2179 ir::Expression::Tuple(Box::new((
2180 ir::Expression::Number(3),
2181 ir::Expression::Number(4),
2182 ))),
2183 ]);
2184
2185 let first_access = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(
2187 nested_expr,
2188 ir::Expression::Number(1),
2189 )));
2190
2191 let second_access = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(
2192 first_access,
2193 ir::Expression::Number(0),
2194 )));
2195
2196 let reduced = second_access.reduce().unwrap();
2197
2198 match reduced {
2199 ir::Expression::Number(n) => assert_eq!(n, 3),
2200 _ => panic!("Expected number 3"),
2201 }
2202 }
2203}