1use std::collections::{BTreeMap, HashMap, HashSet};
2
3use crate::{
4 ir::{self, BinaryOp},
5 ArgValue, Utxo,
6};
7
8#[derive(Debug, thiserror::Error)]
9pub enum Error {
10 #[error("invalid binary operation {0:?}")]
11 InvalidBinaryOp(BinaryOp),
12
13 #[error("invalid argument {0:?} for {1}")]
14 InvalidArgument(ArgValue, String),
15
16 #[error("property {0} not found in {1}")]
17 PropertyNotFound(String, String),
18}
19
20fn arg_value_into_expr(arg: ArgValue) -> ir::Expression {
21 match arg {
22 ArgValue::Address(x) => ir::Expression::Address(x),
23 ArgValue::Int(x) => ir::Expression::Number(x),
24 ArgValue::Bool(x) => ir::Expression::Bool(x),
25 ArgValue::String(x) => ir::Expression::String(x),
26 ArgValue::Bytes(x) => ir::Expression::Bytes(x),
27 ArgValue::UtxoSet(x) => ir::Expression::UtxoSet(x),
28 ArgValue::UtxoRef(x) => ir::Expression::UtxoRefs(vec![x]),
29 }
30}
31
32pub trait Apply: Sized + std::fmt::Debug {
33 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error>;
34 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error>;
35 fn apply_fees(self, fees: u64) -> Result<Self, Error>;
36 fn is_constant(&self) -> bool;
37 fn params(&self) -> BTreeMap<String, ir::Type>;
38 fn queries(&self) -> BTreeMap<String, ir::InputQuery>;
39
40 fn reduce_self(self) -> Result<Self, Error>;
41 fn reduce_nested(self) -> Result<Self, Error>;
42
43 fn reduce(self) -> Result<Self, Error> {
44 let reduced = self.reduce_nested()?;
45
46 if reduced.is_constant() {
47 reduced.reduce_self()
48 } else {
49 Ok(reduced)
50 }
51 }
52}
53
54impl<T> Apply for Option<T>
55where
56 T: Apply,
57{
58 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
59 self.map(|x| x.apply_args(args)).transpose()
60 }
61
62 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
63 self.map(|x| x.apply_inputs(args)).transpose()
64 }
65
66 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
67 self.map(|x| x.apply_fees(fees)).transpose()
68 }
69
70 fn is_constant(&self) -> bool {
71 match self {
72 Some(x) => x.is_constant(),
73 None => true,
74 }
75 }
76
77 fn params(&self) -> BTreeMap<String, ir::Type> {
78 match self {
79 Some(x) => x.params(),
80 None => BTreeMap::new(),
81 }
82 }
83
84 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
85 match self {
86 Some(x) => x.queries(),
87 None => BTreeMap::new(),
88 }
89 }
90
91 fn reduce_self(self) -> Result<Self, Error> {
92 Ok(self)
93 }
94
95 fn reduce_nested(self) -> Result<Self, Error> {
96 self.map(|x| x.reduce()).transpose()
97 }
98}
99
100impl<T> Apply for Box<T>
101where
102 T: Apply,
103{
104 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
105 let x = *self;
106 Ok(Box::new(x.apply_args(args)?))
107 }
108
109 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
110 let x = *self;
111 Ok(Box::new(x.apply_inputs(args)?))
112 }
113
114 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
115 let x = *self;
116 Ok(Box::new(x.apply_fees(fees)?))
117 }
118
119 fn is_constant(&self) -> bool {
120 self.as_ref().is_constant()
121 }
122
123 fn params(&self) -> BTreeMap<String, ir::Type> {
124 self.as_ref().params()
125 }
126
127 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
128 self.as_ref().queries()
129 }
130
131 fn reduce_self(self) -> Result<Self, Error> {
132 Ok(self)
133 }
134
135 fn reduce_nested(self) -> Result<Self, Error> {
136 let x = (*self).reduce()?;
137 Ok(Box::new(x))
138 }
139}
140
141impl<T> Apply for Vec<T>
142where
143 T: Apply,
144{
145 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
146 self.into_iter().map(|x| x.apply_args(args)).collect()
147 }
148
149 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
150 self.into_iter().map(|x| x.apply_inputs(args)).collect()
151 }
152
153 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
154 self.into_iter().map(|x| x.apply_fees(fees)).collect()
155 }
156
157 fn is_constant(&self) -> bool {
158 self.iter().all(|x| x.is_constant())
159 }
160
161 fn params(&self) -> BTreeMap<String, ir::Type> {
162 self.iter()
165 .map(|x| x.params())
166 .fold(BTreeMap::new(), |mut acc, map| {
167 acc.extend(map);
168 acc
169 })
170 }
171
172 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
173 self.iter()
174 .map(|x| x.queries())
175 .fold(BTreeMap::new(), |mut acc, map| {
176 acc.extend(map);
177 acc
178 })
179 }
180
181 fn reduce_self(self) -> Result<Self, Error> {
182 Ok(self)
183 }
184
185 fn reduce_nested(self) -> Result<Self, Error> {
186 let reduced = self
187 .into_iter()
188 .map(|x| x.reduce())
189 .collect::<Result<Vec<_>, _>>()?;
190
191 Ok(reduced)
192 }
193}
194
195impl<T> Apply for HashMap<String, T>
196where
197 T: Apply,
198{
199 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
200 let items = self
201 .into_iter()
202 .map(|(k, v)| v.apply_args(args).map(|v| (k, v)))
203 .collect::<Result<Vec<_>, _>>()?
204 .into_iter()
205 .collect();
206
207 Ok(items)
208 }
209
210 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
211 let items = self
212 .into_iter()
213 .map(|(k, v)| v.apply_inputs(args).map(|v| (k, v)))
214 .collect::<Result<Vec<_>, _>>()?
215 .into_iter()
216 .collect();
217
218 Ok(items)
219 }
220
221 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
222 let items = self
223 .into_iter()
224 .map(|(k, v)| v.apply_fees(fees).map(|v| (k, v)))
225 .collect::<Result<Vec<_>, _>>()?
226 .into_iter()
227 .collect();
228
229 Ok(items)
230 }
231
232 fn is_constant(&self) -> bool {
233 self.iter().all(|(_, v)| v.is_constant())
234 }
235
236 fn params(&self) -> BTreeMap<String, ir::Type> {
237 self.iter()
238 .map(|(_, v)| v.params())
239 .fold(BTreeMap::new(), |mut acc, map| {
240 acc.extend(map);
241 acc
242 })
243 }
244
245 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
246 self.iter()
247 .map(|(_, v)| v.queries())
248 .fold(BTreeMap::new(), |mut acc, map| {
249 acc.extend(map);
250 acc
251 })
252 }
253
254 fn reduce_self(self) -> Result<Self, Error> {
255 Ok(self)
256 }
257
258 fn reduce_nested(self) -> Result<Self, Error> {
259 let reduced = self
260 .into_iter()
261 .map(|(k, v)| v.reduce().map(|v| (k, v)))
262 .collect::<Result<Vec<_>, _>>()?
263 .into_iter()
264 .collect();
265
266 Ok(reduced)
267 }
268}
269
270impl Apply for ir::ScriptSource {
271 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
272 match self {
273 ir::ScriptSource::Embedded(x) => Ok(ir::ScriptSource::Embedded(x.apply_args(args)?)),
274 ir::ScriptSource::UtxoRef { r#ref, source } => Ok(ir::ScriptSource::UtxoRef {
275 r#ref: r#ref.apply_args(args)?,
276 source: source.apply_args(args)?,
277 }),
278 }
279 }
280
281 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
282 match self {
283 ir::ScriptSource::Embedded(x) => Ok(ir::ScriptSource::Embedded(x.apply_inputs(args)?)),
284 ir::ScriptSource::UtxoRef { r#ref, source } => Ok(ir::ScriptSource::UtxoRef {
285 r#ref: r#ref.apply_inputs(args)?,
286 source: source.apply_inputs(args)?,
287 }),
288 }
289 }
290
291 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
292 match self {
293 ir::ScriptSource::Embedded(x) => Ok(ir::ScriptSource::Embedded(x.apply_fees(fees)?)),
294 ir::ScriptSource::UtxoRef { r#ref, source } => Ok(ir::ScriptSource::UtxoRef {
295 r#ref: r#ref.apply_fees(fees)?,
296 source: source.apply_fees(fees)?,
297 }),
298 }
299 }
300
301 fn is_constant(&self) -> bool {
302 match self {
303 ir::ScriptSource::Embedded(x) => x.is_constant(),
304 ir::ScriptSource::UtxoRef { r#ref, source } => {
305 r#ref.is_constant() && source.is_constant()
306 }
307 }
308 }
309
310 fn params(&self) -> BTreeMap<String, ir::Type> {
311 let mut params = BTreeMap::new();
312
313 match self {
314 ir::ScriptSource::Embedded(x) => params.extend(x.params()),
315 ir::ScriptSource::UtxoRef { r#ref, source } => {
316 params.extend(r#ref.params());
317 params.extend(source.params());
318 }
319 }
320
321 params
322 }
323
324 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
325 let mut queries = BTreeMap::new();
326
327 match self {
328 ir::ScriptSource::Embedded(x) => queries.extend(x.queries()),
329 ir::ScriptSource::UtxoRef { r#ref, source } => {
330 queries.extend(r#ref.queries());
331 queries.extend(source.queries());
332 }
333 }
334
335 queries
336 }
337
338 fn reduce_self(self) -> Result<Self, Error> {
339 Ok(self)
340 }
341
342 fn reduce_nested(self) -> Result<Self, Error> {
343 match self {
344 ir::ScriptSource::Embedded(x) => Ok(ir::ScriptSource::Embedded(x.reduce_nested()?)),
345 ir::ScriptSource::UtxoRef { r#ref, source } => Ok(ir::ScriptSource::UtxoRef {
346 r#ref: r#ref.reduce_nested()?,
347 source: source.reduce_nested()?,
348 }),
349 }
350 }
351}
352
353impl TryFrom<&ArgValue> for ir::ScriptSource {
354 type Error = Error;
355
356 fn try_from(value: &ArgValue) -> Result<Self, Self::Error> {
357 match value {
358 ArgValue::Bytes(x) => Ok(ir::ScriptSource::Embedded(ir::Expression::Bytes(x.clone()))),
359 ArgValue::UtxoRef(x) => Ok(ir::ScriptSource::UtxoRef {
360 r#ref: ir::Expression::UtxoRefs(vec![x.clone()]),
361 source: None,
362 }),
363 _ => Err(Error::InvalidArgument(value.clone(), "script".to_string())),
364 }
365 }
366}
367
368impl Apply for ir::PolicyExpr {
369 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
370 let name = self.name;
371 let hash = self.hash.apply_args(args)?;
372
373 let script = if self.script.is_some() {
374 self.script.apply_args(args)?
375 } else {
376 let defined = args.get(&format!("{}_script", name.to_lowercase()));
377 defined.map(TryFrom::try_from).transpose()?
378 };
379
380 Ok(Self { name, hash, script })
381 }
382
383 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
384 let name = self.name;
385 let hash = self.hash.apply_inputs(args)?;
386
387 let script = self
388 .script
389 .map(|x| match x {
390 ir::ScriptSource::UtxoRef { r#ref, .. } => {
391 let source = args
392 .get(&name.to_lowercase())
393 .and_then(|x| x.iter().next())
394 .and_then(|x| x.script.clone());
395
396 Ok(ir::ScriptSource::UtxoRef { r#ref, source })
397 }
398 x => Ok(x),
399 })
400 .transpose()?;
401
402 Ok(Self { name, hash, script })
403 }
404
405 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
406 Ok(Self {
407 name: self.name,
408 hash: self.hash.apply_fees(fees)?,
409 script: self.script.apply_fees(fees)?,
410 })
411 }
412
413 fn is_constant(&self) -> bool {
414 self.hash.is_constant() && self.script.is_constant()
415 }
416
417 fn params(&self) -> BTreeMap<String, ir::Type> {
418 let mut params = BTreeMap::new();
419 params.extend(self.hash.params());
420 params.extend(self.script.params());
421
422 if self.script.is_none() {
423 params.insert(
424 format!("{}_script", self.name.to_lowercase()),
425 ir::Type::UtxoRef,
426 );
427 }
428
429 params
430 }
431
432 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
433 let mut queries = BTreeMap::new();
434
435 if let Some(ir::ScriptSource::UtxoRef { r#ref, source }) = &self.script {
436 if source.is_none() {
437 queries.insert(
438 self.name.to_lowercase(),
439 ir::InputQuery {
440 r#ref: Some(r#ref.clone()),
441 ..Default::default()
442 },
443 );
444 }
445 }
446
447 queries
448 }
449
450 fn reduce_self(self) -> Result<Self, Error> {
451 Ok(self)
452 }
453
454 fn reduce_nested(self) -> Result<Self, Error> {
455 Ok(Self {
456 name: self.name,
457 hash: self.hash.reduce_nested()?,
458 script: self.script.reduce_nested()?,
459 })
460 }
461}
462
463impl Apply for ir::StructExpr {
464 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
465 Ok(Self {
466 constructor: self.constructor,
467 fields: self
468 .fields
469 .into_iter()
470 .map(|x| x.apply_args(args))
471 .collect::<Result<Vec<_>, _>>()?,
472 })
473 }
474
475 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
476 Ok(Self {
477 constructor: self.constructor,
478 fields: self
479 .fields
480 .into_iter()
481 .map(|x| x.apply_inputs(args))
482 .collect::<Result<Vec<_>, _>>()?,
483 })
484 }
485
486 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
487 Ok(Self {
488 constructor: self.constructor,
489 fields: self
490 .fields
491 .into_iter()
492 .map(|x| x.apply_fees(fees))
493 .collect::<Result<Vec<_>, _>>()?,
494 })
495 }
496
497 fn is_constant(&self) -> bool {
498 self.fields.iter().all(|x| x.is_constant())
499 }
500
501 fn params(&self) -> BTreeMap<String, ir::Type> {
502 self.fields
503 .iter()
504 .map(|x| x.params())
505 .fold(BTreeMap::new(), |mut acc, map| {
506 acc.extend(map);
507 acc
508 })
509 }
510
511 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
512 BTreeMap::new()
514 }
515
516 fn reduce_self(self) -> Result<Self, Error> {
517 Ok(self)
518 }
519
520 fn reduce_nested(self) -> Result<Self, Error> {
521 Ok(Self {
522 constructor: self.constructor,
523 fields: self
524 .fields
525 .into_iter()
526 .map(|x| x.reduce())
527 .collect::<Result<Vec<_>, _>>()?,
528 })
529 }
530}
531
532impl Apply for ir::PropertyAccess {
533 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
534 Ok(Self {
535 object: self.object.apply_args(args)?,
536 field: self.field,
537 })
538 }
539
540 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
541 Ok(Self {
542 object: self.object.apply_inputs(args)?,
543 field: self.field,
544 })
545 }
546
547 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
548 Ok(Self {
549 object: self.object.apply_fees(fees)?,
550 field: self.field,
551 })
552 }
553
554 fn is_constant(&self) -> bool {
555 self.object.is_constant()
556 }
557
558 fn params(&self) -> BTreeMap<String, ir::Type> {
559 self.object.params()
560 }
561
562 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
563 self.object.queries()
564 }
565
566 fn reduce_self(self) -> Result<Self, Error> {
567 Ok(self)
568 }
569
570 fn reduce_nested(self) -> Result<Self, Error> {
571 Ok(Self {
572 object: self.object.reduce()?,
573 field: self.field,
574 })
575 }
576}
577
578impl Apply for ir::AssetExpr {
579 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
580 Ok(Self {
581 policy: self.policy.apply_args(args)?,
582 asset_name: self.asset_name.apply_args(args)?,
583 amount: self.amount.apply_args(args)?,
584 })
585 }
586
587 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
588 Ok(Self {
589 policy: self.policy.apply_inputs(args)?,
590 asset_name: self.asset_name.apply_inputs(args)?,
591 amount: self.amount.apply_inputs(args)?,
592 })
593 }
594
595 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
596 Ok(Self {
597 policy: self.policy.apply_fees(fees)?,
598 asset_name: self.asset_name.apply_fees(fees)?,
599 amount: self.amount.apply_fees(fees)?,
600 })
601 }
602
603 fn is_constant(&self) -> bool {
604 self.policy.is_constant() && self.asset_name.is_constant() && self.amount.is_constant()
605 }
606
607 fn params(&self) -> BTreeMap<String, ir::Type> {
608 let mut params = BTreeMap::new();
609 params.extend(self.policy.params());
610 params.extend(self.asset_name.params());
611 params.extend(self.amount.params());
612 params
613 }
614
615 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
616 BTreeMap::new()
618 }
619
620 fn reduce_self(self) -> Result<Self, Error> {
621 Ok(self)
622 }
623
624 fn reduce_nested(self) -> Result<Self, Error> {
625 Ok(Self {
626 policy: self.policy.reduce()?,
627 asset_name: self.asset_name.reduce()?,
628 amount: self.amount.reduce()?,
629 })
630 }
631}
632
633impl Apply for ir::BinaryOp {
634 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
635 let left = self.left.apply_args(args)?;
636 let right = self.right.apply_args(args)?;
637
638 let op = Self {
639 left,
640 right,
641 op: self.op,
642 };
643
644 Ok(op)
647 }
648
649 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
650 let left = self.left.apply_inputs(args)?;
651 let right = self.right.apply_inputs(args)?;
652
653 Ok(Self {
654 left,
655 right,
656 op: self.op,
657 })
658 }
659
660 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
661 Ok(Self {
662 left: self.left.apply_fees(fees)?,
663 right: self.right.apply_fees(fees)?,
664 op: self.op,
665 })
666 }
667
668 fn is_constant(&self) -> bool {
669 self.left.is_constant() && self.right.is_constant()
670 }
671
672 fn params(&self) -> BTreeMap<String, ir::Type> {
673 let mut params = BTreeMap::new();
674 params.extend(self.left.params());
675 params.extend(self.right.params());
676 params
677 }
678
679 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
680 BTreeMap::new()
682 }
683
684 fn reduce_self(self) -> Result<Self, Error> {
685 Ok(self)
686 }
687
688 fn reduce_nested(self) -> Result<Self, Error> {
689 Ok(Self {
690 left: self.left.reduce()?,
691 right: self.right.reduce()?,
692 op: self.op,
693 })
694 }
695}
696
697fn build_assets(aggregated: HashMap<AssetClass, i128>) -> Vec<ir::AssetExpr> {
698 aggregated
700 .into_iter()
701 .map(|((policy, asset_name), amount)| ir::AssetExpr {
702 policy: match policy {
703 Some(policy) => ir::Expression::Bytes(policy.to_vec()),
704 None => ir::Expression::None,
705 },
706 asset_name: match asset_name {
707 Some(asset_name) => ir::Expression::Bytes(asset_name.to_vec()),
708 None => ir::Expression::None,
709 },
710 amount: ir::Expression::Number(amount),
711 })
712 .collect()
713}
714
715type AssetClass<'a> = (Option<&'a [u8]>, Option<&'a [u8]>);
716
717impl ir::AssetExpr {
718 fn expect_constant_policy(&self) -> Option<&[u8]> {
719 match &self.policy {
720 ir::Expression::None => None,
721 ir::Expression::Bytes(x) => Some(x.as_slice()),
722 _ => None,
723 }
724 }
725
726 fn expect_constant_name(&self) -> Option<&[u8]> {
727 match &self.asset_name {
728 ir::Expression::None => None,
729 ir::Expression::Bytes(x) => Some(x.as_slice()),
730 _ => None,
731 }
732 }
733
734 fn expect_constant_amount(&self) -> i128 {
735 match &self.amount {
736 ir::Expression::Number(x) => *x,
737 _ => unreachable!("amount expected to be Number"),
738 }
739 }
740
741 fn aggregate(items: &[Self]) -> HashMap<AssetClass, i128> {
742 let mut aggregated: HashMap<AssetClass, i128> = HashMap::new();
743
744 for asset in items.iter() {
746 let policy = asset.expect_constant_policy();
747 let asset_name = asset.expect_constant_name();
748 let amount = asset.expect_constant_amount();
749
750 let key = (policy, asset_name);
751 *aggregated.entry(key).or_default() += amount;
752 }
753
754 aggregated
755 }
756
757 fn sum<'a>(
758 a: HashMap<AssetClass<'a>, i128>,
759 b: HashMap<AssetClass<'a>, i128>,
760 ) -> HashMap<AssetClass<'a>, i128> {
761 let mut aggregated = a;
762
763 for (key, value) in b {
764 *aggregated.entry(key).or_default() += value;
765 }
766
767 aggregated
768 }
769
770 fn sub<'a>(
771 a: HashMap<AssetClass<'a>, i128>,
772 b: HashMap<AssetClass<'a>, i128>,
773 ) -> HashMap<AssetClass<'a>, i128> {
774 let mut aggregated = a;
775
776 for (key, value) in b {
777 *aggregated.entry(key).or_default() -= value;
778 }
779
780 aggregated
781 }
782}
783
784impl Apply for ir::InputQuery {
785 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
786 Ok(Self {
787 address: self.address.apply_args(args)?,
788 min_amount: self.min_amount.apply_args(args)?,
789 r#ref: self.r#ref.apply_args(args)?,
790 })
791 }
792
793 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
794 Ok(Self {
795 address: self.address.apply_inputs(args)?,
796 min_amount: self.min_amount.apply_inputs(args)?,
797 r#ref: self.r#ref.apply_inputs(args)?,
798 })
799 }
800
801 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
802 Ok(Self {
803 address: self.address.apply_fees(fees)?,
804 min_amount: self.min_amount.apply_fees(fees)?,
805 r#ref: self.r#ref.apply_fees(fees)?,
806 })
807 }
808
809 fn is_constant(&self) -> bool {
810 self.address.is_constant() && self.min_amount.is_constant() && self.r#ref.is_constant()
811 }
812
813 fn params(&self) -> BTreeMap<String, ir::Type> {
814 let mut params = BTreeMap::new();
815 params.extend(self.address.params());
816 params.extend(self.min_amount.params());
817 params.extend(self.r#ref.params());
818 params
819 }
820
821 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
822 BTreeMap::new()
825 }
826
827 fn reduce_self(self) -> Result<Self, Error> {
828 Ok(self)
829 }
830
831 fn reduce_nested(self) -> Result<Self, Error> {
832 Ok(Self {
833 address: self.address.reduce()?,
834 min_amount: self.min_amount.reduce()?,
835 r#ref: self.r#ref.reduce()?,
836 })
837 }
838}
839
840impl Apply for ir::Input {
841 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
842 Ok(Self {
843 query: self.query.apply_args(args)?,
844 redeemer: self.redeemer.apply_args(args)?,
845 policy: self.policy.apply_args(args)?,
846 ..self
847 })
848 }
849
850 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
851 let defined = args.get(&self.name).cloned();
852
853 if let Some(refs) = defined {
854 return Ok(Self {
855 query: None,
856 refs: refs.into_iter().map(|x| x.r#ref).collect(),
857 ..self
858 });
859 }
860
861 Ok(self)
862 }
863
864 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
865 Ok(Self {
866 query: self.query.apply_fees(fees)?,
867 redeemer: self.redeemer.apply_fees(fees)?,
868 policy: self.policy.apply_fees(fees)?,
869 ..self
870 })
871 }
872
873 fn is_constant(&self) -> bool {
874 self.query.is_constant() && self.redeemer.is_constant() && self.policy.is_constant()
875 }
876
877 fn params(&self) -> BTreeMap<String, ir::Type> {
878 let mut params = BTreeMap::new();
879 params.extend(self.query.params());
880 params.extend(self.redeemer.params());
881 params.extend(self.policy.params());
882 params
883 }
884
885 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
886 let input = self.query.iter().map(|x| (self.name.clone(), x.clone()));
887 let policy = self.policy.queries();
888
889 BTreeMap::from_iter(input.chain(policy))
890 }
891
892 fn reduce_self(self) -> Result<Self, Error> {
893 Ok(self)
894 }
895
896 fn reduce_nested(self) -> Result<Self, Error> {
897 Ok(Self {
898 query: self.query.reduce()?,
899 redeemer: self.redeemer.reduce()?,
900 policy: self.policy.reduce()?,
901 ..self
902 })
903 }
904}
905
906impl Apply for ir::Expression {
907 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
908 match self {
909 ir::Expression::Struct(x) => Ok(ir::Expression::Struct(x.apply_args(args)?)),
910 ir::Expression::List(x) => Ok(ir::Expression::List(x.apply_args(args)?)),
911 ir::Expression::Assets(x) => Ok(ir::Expression::Assets(x.apply_args(args)?)),
912 ir::Expression::EvalCustom(x) => Ok(ir::Expression::EvalCustom(x.apply_args(args)?)),
913 ir::Expression::EvalParameter(name, ty) => {
914 let defined = args.get(&name).cloned();
915
916 match defined {
917 Some(x) => Ok(arg_value_into_expr(x)),
918 None => Ok(ir::Expression::EvalParameter(name, ty)),
919 }
920 }
921
922 x => Ok(x),
924 }
925 }
926
927 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
928 match self {
929 ir::Expression::EvalInputDatum(x) => {
930 let defined = args.get(&x).cloned();
931
932 match defined {
933 Some(x) => {
934 let datum = x
935 .into_iter()
936 .flat_map(|x| x.datum)
937 .next()
938 .unwrap_or(ir::Expression::None);
939
940 Ok(datum)
941 }
942 None => Ok(ir::Expression::EvalInputDatum(x)),
943 }
944 }
945 ir::Expression::EvalInputAssets(x) => {
946 let defined = args.get(&x).cloned();
947
948 match defined {
949 Some(x) => {
950 let assets = x.into_iter().flat_map(|x| x.assets).collect();
951
952 Ok(ir::Expression::Assets(assets))
953 }
954 None => Ok(ir::Expression::EvalInputAssets(x)),
955 }
956 }
957 ir::Expression::Struct(x) => Ok(ir::Expression::Struct(x.apply_inputs(args)?)),
958 ir::Expression::List(x) => Ok(ir::Expression::List(x.apply_inputs(args)?)),
959 ir::Expression::Assets(x) => Ok(ir::Expression::Assets(x.apply_inputs(args)?)),
960 ir::Expression::EvalCustom(x) => Ok(ir::Expression::EvalCustom(x.apply_inputs(args)?)),
961 _ => Ok(self),
962 }
963 }
964
965 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
966 match self {
967 ir::Expression::FeeQuery => Ok(ir::Expression::Assets(vec![ir::AssetExpr {
968 policy: ir::Expression::None,
969 asset_name: ir::Expression::None,
970 amount: ir::Expression::Number(fees as i128),
971 }])),
972 ir::Expression::Struct(x) => Ok(ir::Expression::Struct(x.apply_fees(fees)?)),
973 ir::Expression::List(x) => Ok(ir::Expression::List(x.apply_fees(fees)?)),
974 ir::Expression::Assets(x) => Ok(ir::Expression::Assets(x.apply_fees(fees)?)),
975 ir::Expression::EvalCustom(x) => Ok(ir::Expression::EvalCustom(x.apply_fees(fees)?)),
976 _ => Ok(self),
977 }
978 }
979
980 fn is_constant(&self) -> bool {
981 match self {
982 Self::None => true,
983 Self::Bytes(_) => true,
984 Self::Number(_) => true,
985 Self::Bool(_) => true,
986 Self::String(_) => true,
987 Self::Address(_) => true,
988 Self::Hash(_) => true,
989 Self::UtxoRefs(_) => true,
990 Self::UtxoSet(_) => true,
991 Self::List(x) => x.is_constant(),
992 Self::Struct(x) => x.is_constant(),
993 Self::Assets(x) => x.is_constant(),
994 Self::EvalCustom(x) => x.is_constant(),
995 Self::EvalProperty(x) => x.is_constant(),
996 Self::AdHocDirective(x) => x.is_constant(),
997 Self::EvalInputDatum(..) => false,
998 Self::EvalInputAssets(..) => false,
999 Self::FeeQuery => false,
1000 Self::EvalParameter(..) => false,
1001 }
1002 }
1003
1004 fn params(&self) -> BTreeMap<String, ir::Type> {
1005 match self {
1006 ir::Expression::Struct(x) => x.params(),
1007 ir::Expression::Assets(x) => x.params(),
1008 ir::Expression::EvalCustom(x) => x.params(),
1009 ir::Expression::EvalParameter(x, ty) => BTreeMap::from([(x.to_string(), ty.clone())]),
1010
1011 _ => BTreeMap::new(),
1013 }
1014 }
1015
1016 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
1017 BTreeMap::new()
1018 }
1019
1020 fn reduce_self(self) -> Result<Self, Error> {
1021 match self {
1022 ir::Expression::EvalCustom(op) => match (&op.left, &op.right) {
1023 (ir::Expression::Number(x), ir::Expression::Number(y)) => {
1024 let result = match &op.op {
1025 ir::BinaryOpKind::Add => x + y,
1026 ir::BinaryOpKind::Sub => x - y,
1027 };
1028
1029 Ok(ir::Expression::Number(result))
1030 }
1031 (ir::Expression::Assets(x), ir::Expression::Assets(y)) => {
1032 let x = ir::AssetExpr::aggregate(x);
1033 let y = ir::AssetExpr::aggregate(y);
1034
1035 let result = match &op.op {
1036 ir::BinaryOpKind::Add => ir::AssetExpr::sum(x, y),
1037 ir::BinaryOpKind::Sub => ir::AssetExpr::sub(x, y),
1038 };
1039
1040 Ok(ir::Expression::Assets(build_assets(result)))
1041 }
1042 _ => Err(Error::InvalidBinaryOp(*op)),
1043 },
1044 ir::Expression::EvalProperty(_x) => {
1045 todo!("fix this")
1050 }
1051 _ => Ok(self),
1052 }
1053 }
1054
1055 fn reduce_nested(self) -> Result<Self, Error> {
1056 match self {
1057 ir::Expression::Struct(x) => Ok(ir::Expression::Struct(x.reduce()?)),
1058 ir::Expression::Assets(x) => Ok(ir::Expression::Assets(x.reduce()?)),
1059 ir::Expression::EvalCustom(x) => Ok(ir::Expression::EvalCustom(x.reduce()?)),
1060 _ => Ok(self),
1061 }
1062 }
1063}
1064
1065impl Apply for ir::Output {
1066 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
1067 Ok(Self {
1068 address: self.address.apply_args(args)?,
1069 datum: self.datum.apply_args(args)?,
1070 amount: self.amount.apply_args(args)?,
1071 })
1072 }
1073
1074 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
1075 Ok(Self {
1076 address: self.address.apply_inputs(args)?,
1077 datum: self.datum.apply_inputs(args)?,
1078 amount: self.amount.apply_inputs(args)?,
1079 })
1080 }
1081
1082 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
1083 Ok(Self {
1084 address: self.address.apply_fees(fees)?,
1085 datum: self.datum.apply_fees(fees)?,
1086 amount: self.amount.apply_fees(fees)?,
1087 })
1088 }
1089
1090 fn is_constant(&self) -> bool {
1091 self.address.is_constant() && self.datum.is_constant() && self.amount.is_constant()
1092 }
1093
1094 fn params(&self) -> BTreeMap<String, ir::Type> {
1095 let mut params = BTreeMap::new();
1096 params.extend(self.address.params());
1097 params.extend(self.datum.params());
1098 params.extend(self.amount.params());
1099 params
1100 }
1101
1102 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
1103 BTreeMap::new()
1105 }
1106
1107 fn reduce_self(self) -> Result<Self, Error> {
1108 Ok(self)
1109 }
1110
1111 fn reduce_nested(self) -> Result<Self, Error> {
1112 Ok(Self {
1113 address: self.address.reduce()?,
1114 datum: self.datum.reduce()?,
1115 amount: self.amount.reduce()?,
1116 })
1117 }
1118}
1119
1120impl Apply for ir::Mint {
1121 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
1122 Ok(Self {
1123 amount: self.amount.apply_args(args)?,
1124 redeemer: self.redeemer.apply_args(args)?,
1125 })
1126 }
1127
1128 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
1129 Ok(Self {
1130 amount: self.amount.apply_inputs(args)?,
1131 redeemer: self.redeemer.apply_inputs(args)?,
1132 })
1133 }
1134
1135 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
1136 Ok(Self {
1137 amount: self.amount.apply_fees(fees)?,
1138 redeemer: self.redeemer.apply_fees(fees)?,
1139 })
1140 }
1141
1142 fn is_constant(&self) -> bool {
1143 self.amount.is_constant() && self.redeemer.is_constant()
1144 }
1145
1146 fn params(&self) -> BTreeMap<String, ir::Type> {
1147 let mut params = BTreeMap::new();
1148 params.extend(self.amount.params());
1149 params.extend(self.redeemer.params());
1150 params
1151 }
1152
1153 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
1154 BTreeMap::new()
1156 }
1157
1158 fn reduce_self(self) -> Result<Self, Error> {
1159 Ok(self)
1160 }
1161
1162 fn reduce_nested(self) -> Result<Self, Error> {
1163 Ok(Self {
1164 amount: self.amount.reduce()?,
1165 redeemer: self.redeemer.reduce()?,
1166 })
1167 }
1168}
1169
1170impl Apply for ir::AdHocDirective {
1171 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
1172 Ok(Self {
1173 name: self.name,
1174 data: self.data.apply_args(args)?,
1175 })
1176 }
1177
1178 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
1179 Ok(Self {
1180 name: self.name,
1181 data: self.data.apply_inputs(args)?,
1182 })
1183 }
1184
1185 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
1186 Ok(Self {
1187 name: self.name,
1188 data: self.data.apply_fees(fees)?,
1189 })
1190 }
1191
1192 fn is_constant(&self) -> bool {
1193 self.data.is_constant()
1194 }
1195
1196 fn params(&self) -> BTreeMap<String, ir::Type> {
1197 let mut params = BTreeMap::new();
1198 params.extend(self.data.params());
1199 params
1200 }
1201
1202 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
1203 BTreeMap::new()
1204 }
1205
1206 fn reduce_self(self) -> Result<Self, Error> {
1207 Ok(self)
1208 }
1209
1210 fn reduce_nested(self) -> Result<Self, Error> {
1211 Ok(Self {
1212 name: self.name,
1213 data: self.data.reduce()?,
1214 })
1215 }
1216}
1217
1218impl Apply for ir::Collateral {
1219 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
1220 Ok(Self {
1221 query: self.query.apply_args(args)?,
1222 })
1223 }
1224
1225 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
1226 Ok(Self {
1227 query: self.query.apply_inputs(args)?,
1228 })
1229 }
1230
1231 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
1232 Ok(Self {
1233 query: self.query.apply_fees(fees)?,
1234 })
1235 }
1236
1237 fn is_constant(&self) -> bool {
1238 self.query.is_constant()
1239 }
1240
1241 fn params(&self) -> BTreeMap<String, ir::Type> {
1242 let mut params = BTreeMap::new();
1243 params.extend(self.query.params());
1244 params
1245 }
1246
1247 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
1248 let mut b_tree = BTreeMap::new();
1249 b_tree.insert("collateral".to_string(), self.query.clone());
1250 b_tree
1251 }
1252
1253 fn reduce_self(self) -> Result<Self, Error> {
1254 Ok(self)
1255 }
1256
1257 fn reduce_nested(self) -> Result<Self, Error> {
1258 Ok(Self {
1259 query: self.query.reduce()?,
1260 })
1261 }
1262}
1263
1264impl Apply for ir::Tx {
1265 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
1266 let tx = ir::Tx {
1267 references: self.references.apply_args(args)?,
1268 inputs: self.inputs.apply_args(args)?,
1269 outputs: self.outputs.apply_args(args)?,
1270 mint: self.mint.apply_args(args)?,
1271 fees: self.fees.apply_args(args)?,
1272 adhoc: self.adhoc.apply_args(args)?,
1273 collateral: self.collateral.apply_args(args)?,
1274 };
1275
1276 Ok(tx)
1277 }
1278
1279 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
1280 Ok(Self {
1281 references: self.references.apply_inputs(args)?,
1282 inputs: self.inputs.apply_inputs(args)?,
1283 outputs: self.outputs.apply_inputs(args)?,
1284 mint: self.mint.apply_inputs(args)?,
1285 fees: self.fees.apply_inputs(args)?,
1286 adhoc: self.adhoc.apply_inputs(args)?,
1287 collateral: self.collateral.apply_inputs(args)?,
1288 })
1289 }
1290
1291 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
1292 Ok(Self {
1293 references: self.references.apply_fees(fees)?,
1294 inputs: self.inputs.apply_fees(fees)?,
1295 outputs: self.outputs.apply_fees(fees)?,
1296 mint: self.mint.apply_fees(fees)?,
1297 fees: self.fees.apply_fees(fees)?,
1298 adhoc: self.adhoc.apply_fees(fees)?,
1299 collateral: self.collateral.apply_fees(fees)?,
1300 })
1301 }
1302
1303 fn is_constant(&self) -> bool {
1304 self.inputs.iter().all(|x| x.is_constant())
1305 && self.outputs.iter().all(|x| x.is_constant())
1306 && self.mint.is_constant()
1307 && self.fees.is_constant()
1308 && self.adhoc.iter().all(|x| x.is_constant())
1309 }
1310
1311 fn params(&self) -> BTreeMap<String, ir::Type> {
1312 let mut params = BTreeMap::new();
1314 params.extend(self.inputs.params());
1315 params.extend(self.outputs.params());
1316 params.extend(self.mint.params());
1317 params.extend(self.fees.params());
1318 params.extend(self.adhoc.params());
1319 params
1320 }
1321
1322 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
1323 let mut queries = BTreeMap::new();
1325 queries.extend(self.inputs.queries());
1326 queries.extend(self.outputs.queries());
1327 queries.extend(self.mint.queries());
1328 queries.extend(self.fees.queries());
1329 queries.extend(self.adhoc.queries());
1330 queries
1331 }
1332
1333 fn reduce_self(self) -> Result<Self, Error> {
1334 Ok(self)
1335 }
1336
1337 fn reduce_nested(self) -> Result<Self, Error> {
1338 Ok(Self {
1339 references: self.references.reduce()?,
1340 inputs: self.inputs.reduce()?,
1341 outputs: self.outputs.reduce()?,
1342 mint: self.mint.reduce()?,
1343 fees: self.fees.reduce()?,
1344 adhoc: self.adhoc.reduce()?,
1345 collateral: self.collateral.reduce()?,
1346 })
1347 }
1348}
1349
1350pub fn apply_args(template: ir::Tx, args: &BTreeMap<String, ArgValue>) -> Result<ir::Tx, Error> {
1351 template.apply_args(args)
1352}
1353
1354pub fn apply_inputs(
1355 template: ir::Tx,
1356 args: &BTreeMap<String, HashSet<Utxo>>,
1357) -> Result<ir::Tx, Error> {
1358 template.apply_inputs(args)
1359}
1360
1361pub fn apply_fees(template: ir::Tx, fees: u64) -> Result<ir::Tx, Error> {
1362 template.apply_fees(fees)
1363}
1364
1365pub fn reduce(template: ir::Tx) -> Result<ir::Tx, Error> {
1366 template.reduce()
1367}
1368
1369pub fn find_params(template: &ir::Tx) -> BTreeMap<String, ir::Type> {
1370 template.params()
1371}
1372
1373pub fn find_queries(template: &ir::Tx) -> BTreeMap<String, ir::InputQuery> {
1374 template.queries()
1375}
1376
1377#[cfg(test)]
1378mod tests {
1379
1380 use super::*;
1381
1382 const SUBJECT_PROTOCOL: &str = r#"
1383 party Sender;
1384
1385 tx swap(a: Int, b: Int) {
1386 input source {
1387 from: Sender,
1388 min_amount: Ada(a) + Ada(b),
1389 }
1390 }
1391 "#;
1392
1393 #[test]
1394 fn test_apply_args() {
1395 let mut ast = crate::parsing::parse_string(SUBJECT_PROTOCOL).unwrap();
1396 crate::analyzing::analyze(&mut ast).ok().unwrap();
1397
1398 let before = crate::lowering::lower(&ast, "swap").unwrap();
1399
1400 let params = find_params(&before);
1401 assert_eq!(params.len(), 3);
1402 assert_eq!(params.get("sender"), Some(&ir::Type::Address));
1403 assert_eq!(params.get("a"), Some(&ir::Type::Int));
1404 assert_eq!(params.get("b"), Some(&ir::Type::Int));
1405
1406 let args = BTreeMap::from([
1407 ("sender".to_string(), ArgValue::Address(b"abc".to_vec())),
1408 ("a".to_string(), ArgValue::Int(100)),
1409 ("b".to_string(), ArgValue::Int(200)),
1410 ]);
1411
1412 let after = apply_args(before, &args).unwrap();
1413
1414 let params = find_params(&after);
1415 assert_eq!(params.len(), 0);
1416 }
1417
1418 #[test]
1419 fn test_reduce_numeric_binary_op() {
1420 let op = ir::Expression::EvalCustom(
1421 ir::BinaryOp {
1422 op: ir::BinaryOpKind::Add,
1423 left: ir::Expression::Number(1),
1424 right: ir::Expression::EvalCustom(
1425 ir::BinaryOp {
1426 op: ir::BinaryOpKind::Sub,
1427 left: ir::Expression::Number(5),
1428 right: ir::Expression::Number(3),
1429 }
1430 .into(),
1431 ),
1432 }
1433 .into(),
1434 );
1435
1436 let reduced = op.reduce().unwrap();
1437
1438 match reduced {
1439 ir::Expression::Number(3) => (),
1440 _ => panic!("Expected number 3"),
1441 };
1442 }
1443
1444 #[test]
1445 fn test_reduce_single_custom_asset_binary_op() {
1446 let op = ir::Expression::EvalCustom(
1447 ir::BinaryOp {
1448 op: ir::BinaryOpKind::Add,
1449 left: ir::Expression::Assets(vec![ir::AssetExpr {
1450 policy: ir::Expression::Bytes(b"abc".to_vec()),
1451 asset_name: ir::Expression::Bytes(b"111".to_vec()),
1452 amount: ir::Expression::Number(100),
1453 }]),
1454 right: ir::Expression::Assets(vec![ir::AssetExpr {
1455 policy: ir::Expression::Bytes(b"abc".to_vec()),
1456 asset_name: ir::Expression::Bytes(b"111".to_vec()),
1457 amount: ir::Expression::Number(200),
1458 }]),
1459 }
1460 .into(),
1461 );
1462
1463 let reduced = op.reduce().unwrap();
1464
1465 match reduced {
1466 ir::Expression::Assets(assets) => {
1467 assert_eq!(assets.len(), 1);
1468 assert_eq!(assets[0].policy, ir::Expression::Bytes(b"abc".to_vec()));
1469 assert_eq!(assets[0].asset_name, ir::Expression::Bytes(b"111".to_vec()));
1470 assert_eq!(assets[0].amount, ir::Expression::Number(300));
1471 }
1472 _ => panic!("Expected assets"),
1473 };
1474 }
1475
1476 #[test]
1477 fn test_reduce_native_asset_binary_op() {
1478 let op = ir::Expression::EvalCustom(
1479 ir::BinaryOp {
1480 op: ir::BinaryOpKind::Add,
1481 left: ir::Expression::Assets(vec![ir::AssetExpr {
1482 policy: ir::Expression::None,
1483 asset_name: ir::Expression::None,
1484 amount: ir::Expression::Number(100),
1485 }]),
1486 right: ir::Expression::Assets(vec![ir::AssetExpr {
1487 policy: ir::Expression::None,
1488 asset_name: ir::Expression::None,
1489 amount: ir::Expression::Number(200),
1490 }]),
1491 }
1492 .into(),
1493 );
1494
1495 let reduced = op.reduce().unwrap();
1496
1497 match reduced {
1498 ir::Expression::Assets(assets) => {
1499 assert_eq!(assets.len(), 1);
1500 assert_eq!(assets[0].policy, ir::Expression::None);
1501 assert_eq!(assets[0].asset_name, ir::Expression::None);
1502 assert_eq!(assets[0].amount, ir::Expression::Number(300));
1503 }
1504 _ => panic!("Expected assets"),
1505 };
1506 }
1507
1508 #[test]
1509 fn test_reduce_mixed_asset_binary_op() {
1510 let op = ir::Expression::EvalCustom(
1511 ir::BinaryOp {
1512 op: ir::BinaryOpKind::Add,
1513 left: ir::Expression::Assets(vec![ir::AssetExpr {
1514 policy: ir::Expression::None,
1515 asset_name: ir::Expression::None,
1516 amount: ir::Expression::Number(100),
1517 }]),
1518 right: ir::Expression::Assets(vec![ir::AssetExpr {
1519 policy: ir::Expression::Bytes(b"abc".to_vec()),
1520 asset_name: ir::Expression::Bytes(b"111".to_vec()),
1521 amount: ir::Expression::Number(200),
1522 }]),
1523 }
1524 .into(),
1525 );
1526
1527 let reduced = op.reduce().unwrap();
1528
1529 match reduced {
1530 ir::Expression::Assets(assets) => {
1531 assert_eq!(assets.len(), 2);
1532
1533 for asset in assets {
1534 if asset.policy == ir::Expression::None {
1535 assert_eq!(asset.asset_name, ir::Expression::None);
1536 assert_eq!(asset.amount, ir::Expression::Number(100));
1537 } else {
1538 assert_eq!(asset.policy, ir::Expression::Bytes(b"abc".to_vec()));
1539 assert_eq!(asset.asset_name, ir::Expression::Bytes(b"111".to_vec()));
1540 assert_eq!(asset.amount, ir::Expression::Number(200));
1541 }
1542 }
1543 }
1544 _ => panic!("Expected assets"),
1545 };
1546 }
1547}