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