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,
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,
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,
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.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.asset_name.params());
561 params.extend(self.amount.params());
562 params
563 }
564
565 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
566 BTreeMap::new()
568 }
569
570 fn reduce_self(self) -> Result<Self, Error> {
571 Ok(self)
572 }
573
574 fn reduce_nested(self) -> Result<Self, Error> {
575 Ok(Self {
576 policy: self.policy,
577 asset_name: self.asset_name.reduce()?,
578 amount: self.amount.reduce()?,
579 })
580 }
581}
582
583impl Apply for ir::BinaryOp {
584 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
585 let left = self.left.apply_args(args)?;
586 let right = self.right.apply_args(args)?;
587
588 let op = Self {
589 left,
590 right,
591 op: self.op,
592 };
593
594 Ok(op)
597 }
598
599 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
600 let left = self.left.apply_inputs(args)?;
601 let right = self.right.apply_inputs(args)?;
602
603 Ok(Self {
604 left,
605 right,
606 op: self.op,
607 })
608 }
609
610 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
611 Ok(Self {
612 left: self.left.apply_fees(fees)?,
613 right: self.right.apply_fees(fees)?,
614 op: self.op,
615 })
616 }
617
618 fn is_constant(&self) -> bool {
619 self.left.is_constant() && self.right.is_constant()
620 }
621
622 fn params(&self) -> BTreeMap<String, ir::Type> {
623 let mut params = BTreeMap::new();
624 params.extend(self.left.params());
625 params.extend(self.right.params());
626 params
627 }
628
629 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
630 BTreeMap::new()
632 }
633
634 fn reduce_self(self) -> Result<Self, Error> {
635 Ok(self)
636 }
637
638 fn reduce_nested(self) -> Result<Self, Error> {
639 Ok(Self {
640 left: self.left.reduce()?,
641 right: self.right.reduce()?,
642 op: self.op,
643 })
644 }
645}
646
647fn build_assets(aggregated: HashMap<AssetClass, i128>) -> Vec<ir::AssetExpr> {
648 aggregated
650 .into_iter()
651 .map(|((policy, asset_name), amount)| ir::AssetExpr {
652 policy: policy.to_vec(),
653 asset_name: ir::Expression::Bytes(asset_name.to_vec()),
654 amount: ir::Expression::Number(amount),
655 })
656 .collect()
657}
658
659type AssetClass<'a> = (&'a [u8], &'a [u8]);
660
661impl ir::AssetExpr {
662 fn expect_constant_name(&self) -> &[u8] {
663 match &self.asset_name {
664 ir::Expression::Bytes(x) => x.as_slice(),
665 _ => unreachable!("asset_name expected to be Bytes"),
666 }
667 }
668
669 fn expect_constant_amount(&self) -> i128 {
670 match &self.amount {
671 ir::Expression::Number(x) => *x,
672 _ => unreachable!("amount expected to be Number"),
673 }
674 }
675
676 fn aggregate(items: &[Self]) -> HashMap<AssetClass, i128> {
677 let mut aggregated: HashMap<AssetClass, i128> = HashMap::new();
678
679 for asset in items.iter() {
681 let asset_name = asset.expect_constant_name();
682 let amount = asset.expect_constant_amount();
683
684 let key = (asset.policy.as_slice(), asset_name);
685 *aggregated.entry(key).or_default() += amount;
686 }
687
688 aggregated
689 }
690
691 fn sum<'a>(
692 a: HashMap<AssetClass<'a>, i128>,
693 b: HashMap<AssetClass<'a>, i128>,
694 ) -> HashMap<AssetClass<'a>, i128> {
695 let mut aggregated = a;
696
697 for (key, value) in b {
698 *aggregated.entry(key).or_default() += value;
699 }
700
701 aggregated
702 }
703
704 fn sub<'a>(
705 a: HashMap<AssetClass<'a>, i128>,
706 b: HashMap<AssetClass<'a>, i128>,
707 ) -> HashMap<AssetClass<'a>, i128> {
708 let mut aggregated = a;
709
710 for (key, value) in b {
711 *aggregated.entry(key).or_default() -= value;
712 }
713
714 aggregated
715 }
716}
717
718impl Apply for ir::InputQuery {
719 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
720 Ok(Self {
721 address: self.address.apply_args(args)?,
722 min_amount: self.min_amount.apply_args(args)?,
723 r#ref: self.r#ref.apply_args(args)?,
724 })
725 }
726
727 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
728 Ok(Self {
729 address: self.address.apply_inputs(args)?,
730 min_amount: self.min_amount.apply_inputs(args)?,
731 r#ref: self.r#ref.apply_inputs(args)?,
732 })
733 }
734
735 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
736 Ok(Self {
737 address: self.address.apply_fees(fees)?,
738 min_amount: self.min_amount.apply_fees(fees)?,
739 r#ref: self.r#ref.apply_fees(fees)?,
740 })
741 }
742
743 fn is_constant(&self) -> bool {
744 self.address.is_constant() && self.min_amount.is_constant() && self.r#ref.is_constant()
745 }
746
747 fn params(&self) -> BTreeMap<String, ir::Type> {
748 let mut params = BTreeMap::new();
749 params.extend(self.address.params());
750 params.extend(self.min_amount.params());
751 params.extend(self.r#ref.params());
752 params
753 }
754
755 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
756 BTreeMap::new()
759 }
760
761 fn reduce_self(self) -> Result<Self, Error> {
762 Ok(self)
763 }
764
765 fn reduce_nested(self) -> Result<Self, Error> {
766 Ok(Self {
767 address: self.address.reduce()?,
768 min_amount: self.min_amount.reduce()?,
769 r#ref: self.r#ref.reduce()?,
770 })
771 }
772}
773
774impl Apply for ir::Input {
775 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
776 Ok(Self {
777 query: self.query.apply_args(args)?,
778 redeemer: self.redeemer.apply_args(args)?,
779 policy: self.policy.apply_args(args)?,
780 ..self
781 })
782 }
783
784 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
785 let defined = args.get(&self.name).cloned();
786
787 if let Some(refs) = defined {
788 return Ok(Self {
789 query: None,
790 refs: refs.into_iter().map(|x| x.r#ref).collect(),
791 ..self
792 });
793 }
794
795 Ok(self)
796 }
797
798 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
799 Ok(Self {
800 query: self.query.apply_fees(fees)?,
801 redeemer: self.redeemer.apply_fees(fees)?,
802 policy: self.policy.apply_fees(fees)?,
803 ..self
804 })
805 }
806
807 fn is_constant(&self) -> bool {
808 self.query.is_constant() && self.redeemer.is_constant() && self.policy.is_constant()
809 }
810
811 fn params(&self) -> BTreeMap<String, ir::Type> {
812 let mut params = BTreeMap::new();
813 params.extend(self.query.params());
814 params.extend(self.redeemer.params());
815 params.extend(self.policy.params());
816 params
817 }
818
819 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
820 let input = self.query.iter().map(|x| (self.name.clone(), x.clone()));
821 let policy = self.policy.queries();
822
823 BTreeMap::from_iter(input.chain(policy))
824 }
825
826 fn reduce_self(self) -> Result<Self, Error> {
827 Ok(self)
828 }
829
830 fn reduce_nested(self) -> Result<Self, Error> {
831 Ok(Self {
832 query: self.query.reduce()?,
833 redeemer: self.redeemer.reduce()?,
834 policy: self.policy.reduce()?,
835 ..self
836 })
837 }
838}
839
840impl Apply for ir::Expression {
841 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
842 match self {
843 ir::Expression::Struct(x) => Ok(ir::Expression::Struct(x.apply_args(args)?)),
844 ir::Expression::Assets(x) => Ok(ir::Expression::Assets(x.apply_args(args)?)),
845 ir::Expression::EvalCustom(x) => Ok(ir::Expression::EvalCustom(x.apply_args(args)?)),
846 ir::Expression::EvalParameter(name, ty) => {
847 let defined = args.get(&name).cloned();
848
849 match defined {
850 Some(x) => Ok(arg_value_into_expr(x)),
851 None => Ok(ir::Expression::EvalParameter(name, ty)),
852 }
853 }
854
855 x => Ok(x),
857 }
858 }
859
860 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
861 match self {
862 ir::Expression::EvalInputDatum(x) => {
863 let defined = args.get(&x).cloned();
864
865 match defined {
866 Some(x) => {
867 let datum = x
868 .into_iter()
869 .flat_map(|x| x.datum)
870 .next()
871 .unwrap_or(ir::Expression::None);
872
873 Ok(datum)
874 }
875 None => Ok(ir::Expression::EvalInputDatum(x)),
876 }
877 }
878 ir::Expression::EvalInputAssets(x) => {
879 let defined = args.get(&x).cloned();
880
881 match defined {
882 Some(x) => {
883 let assets = x.into_iter().flat_map(|x| x.assets).collect();
884
885 Ok(ir::Expression::Assets(assets))
886 }
887 None => Ok(ir::Expression::EvalInputAssets(x)),
888 }
889 }
890 ir::Expression::Struct(x) => Ok(ir::Expression::Struct(x.apply_inputs(args)?)),
891 ir::Expression::Assets(x) => Ok(ir::Expression::Assets(x.apply_inputs(args)?)),
892 ir::Expression::EvalCustom(x) => Ok(ir::Expression::EvalCustom(x.apply_inputs(args)?)),
893 _ => Ok(self),
894 }
895 }
896
897 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
898 match self {
899 ir::Expression::FeeQuery => Ok(ir::Expression::Assets(vec![ir::AssetExpr {
900 policy: vec![],
901 asset_name: ir::Expression::Bytes(b"".to_vec()),
902 amount: ir::Expression::Number(fees as i128),
903 }])),
904 ir::Expression::Struct(x) => Ok(ir::Expression::Struct(x.apply_fees(fees)?)),
905 ir::Expression::Assets(x) => Ok(ir::Expression::Assets(x.apply_fees(fees)?)),
906 ir::Expression::EvalCustom(x) => Ok(ir::Expression::EvalCustom(x.apply_fees(fees)?)),
907 _ => Ok(self),
908 }
909 }
910
911 fn is_constant(&self) -> bool {
912 match self {
913 Self::Bytes(_) => true,
914 Self::Number(_) => true,
915 Self::Bool(_) => true,
916 Self::String(_) => true,
917 Self::Address(_) => true,
918 Self::Hash(_) => true,
919 Self::Struct(x) => x.is_constant(),
920 Self::Assets(x) => x.is_constant(),
921 Self::EvalCustom(x) => x.is_constant(),
922 _ => false,
923 }
924 }
925
926 fn params(&self) -> BTreeMap<String, ir::Type> {
927 match self {
928 ir::Expression::Struct(x) => x.params(),
929 ir::Expression::Assets(x) => x.params(),
930 ir::Expression::EvalCustom(x) => x.params(),
931 ir::Expression::EvalParameter(x, ty) => BTreeMap::from([(x.to_string(), ty.clone())]),
932
933 _ => BTreeMap::new(),
935 }
936 }
937
938 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
939 BTreeMap::new()
940 }
941
942 fn reduce_self(self) -> Result<Self, Error> {
943 match self {
944 ir::Expression::EvalCustom(op) => match (&op.left, &op.right) {
945 (ir::Expression::Number(x), ir::Expression::Number(y)) => {
946 let result = match &op.op {
947 ir::BinaryOpKind::Add => x + y,
948 ir::BinaryOpKind::Sub => x - y,
949 };
950
951 Ok(ir::Expression::Number(result))
952 }
953 (ir::Expression::Assets(x), ir::Expression::Assets(y)) => {
954 let x = ir::AssetExpr::aggregate(x);
955 let y = ir::AssetExpr::aggregate(y);
956
957 let result = match &op.op {
958 ir::BinaryOpKind::Add => ir::AssetExpr::sum(x, y),
959 ir::BinaryOpKind::Sub => ir::AssetExpr::sub(x, y),
960 };
961
962 Ok(ir::Expression::Assets(build_assets(result)))
963 }
964 _ => Err(Error::InvalidBinaryOp(*op)),
965 },
966 _ => Ok(self),
967 }
968 }
969
970 fn reduce_nested(self) -> Result<Self, Error> {
971 match self {
972 ir::Expression::Struct(x) => Ok(ir::Expression::Struct(x.reduce()?)),
973 ir::Expression::Assets(x) => Ok(ir::Expression::Assets(x.reduce()?)),
974 ir::Expression::EvalCustom(x) => Ok(ir::Expression::EvalCustom(x.reduce()?)),
975 _ => Ok(self),
976 }
977 }
978}
979
980impl Apply for ir::Output {
981 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
982 Ok(Self {
983 address: self.address.apply_args(args)?,
984 datum: self.datum.apply_args(args)?,
985 amount: self.amount.apply_args(args)?,
986 })
987 }
988
989 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
990 Ok(Self {
991 address: self.address.apply_inputs(args)?,
992 datum: self.datum.apply_inputs(args)?,
993 amount: self.amount.apply_inputs(args)?,
994 })
995 }
996
997 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
998 Ok(Self {
999 address: self.address.apply_fees(fees)?,
1000 datum: self.datum.apply_fees(fees)?,
1001 amount: self.amount.apply_fees(fees)?,
1002 })
1003 }
1004
1005 fn is_constant(&self) -> bool {
1006 self.address.is_constant() && self.datum.is_constant() && self.amount.is_constant()
1007 }
1008
1009 fn params(&self) -> BTreeMap<String, ir::Type> {
1010 let mut params = BTreeMap::new();
1011 params.extend(self.address.params());
1012 params.extend(self.datum.params());
1013 params.extend(self.amount.params());
1014 params
1015 }
1016
1017 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
1018 BTreeMap::new()
1020 }
1021
1022 fn reduce_self(self) -> Result<Self, Error> {
1023 Ok(self)
1024 }
1025
1026 fn reduce_nested(self) -> Result<Self, Error> {
1027 Ok(Self {
1028 address: self.address.reduce()?,
1029 datum: self.datum.reduce()?,
1030 amount: self.amount.reduce()?,
1031 })
1032 }
1033}
1034
1035impl Apply for ir::Mint {
1036 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
1037 Ok(Self {
1038 amount: self.amount.apply_args(args)?,
1039 redeemer: self.redeemer.apply_args(args)?,
1040 })
1041 }
1042
1043 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
1044 Ok(Self {
1045 amount: self.amount.apply_inputs(args)?,
1046 redeemer: self.redeemer.apply_inputs(args)?,
1047 })
1048 }
1049
1050 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
1051 Ok(Self {
1052 amount: self.amount.apply_fees(fees)?,
1053 redeemer: self.redeemer.apply_fees(fees)?,
1054 })
1055 }
1056
1057 fn is_constant(&self) -> bool {
1058 self.amount.is_constant() && self.redeemer.is_constant()
1059 }
1060
1061 fn params(&self) -> BTreeMap<String, ir::Type> {
1062 let mut params = BTreeMap::new();
1063 params.extend(self.amount.params());
1064 params.extend(self.redeemer.params());
1065 params
1066 }
1067
1068 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
1069 BTreeMap::new()
1071 }
1072
1073 fn reduce_self(self) -> Result<Self, Error> {
1074 Ok(self)
1075 }
1076
1077 fn reduce_nested(self) -> Result<Self, Error> {
1078 Ok(Self {
1079 amount: self.amount.reduce()?,
1080 redeemer: self.redeemer.reduce()?,
1081 })
1082 }
1083}
1084
1085impl Apply for ir::AdHocDirective {
1086 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
1087 Ok(Self {
1088 name: self.name,
1089 data: self.data.apply_args(args)?,
1090 })
1091 }
1092
1093 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
1094 Ok(Self {
1095 name: self.name,
1096 data: self.data.apply_inputs(args)?,
1097 })
1098 }
1099
1100 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
1101 Ok(Self {
1102 name: self.name,
1103 data: self.data.apply_fees(fees)?,
1104 })
1105 }
1106
1107 fn is_constant(&self) -> bool {
1108 self.data.is_constant()
1109 }
1110
1111 fn params(&self) -> BTreeMap<String, ir::Type> {
1112 let mut params = BTreeMap::new();
1113 params.extend(self.data.params());
1114 params
1115 }
1116
1117 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
1118 BTreeMap::new()
1119 }
1120
1121 fn reduce_self(self) -> Result<Self, Error> {
1122 Ok(self)
1123 }
1124
1125 fn reduce_nested(self) -> Result<Self, Error> {
1126 Ok(Self {
1127 name: self.name,
1128 data: self.data.reduce()?,
1129 })
1130 }
1131}
1132
1133impl Apply for ir::Tx {
1134 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
1135 let tx = ir::Tx {
1136 inputs: self.inputs.apply_args(args)?,
1137 outputs: self.outputs.apply_args(args)?,
1138 mint: self.mint.apply_args(args)?,
1139 fees: self.fees.apply_args(args)?,
1140 adhoc: self.adhoc.apply_args(args)?,
1141 };
1142
1143 Ok(tx)
1144 }
1145
1146 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
1147 Ok(Self {
1148 inputs: self.inputs.apply_inputs(args)?,
1149 outputs: self.outputs.apply_inputs(args)?,
1150 mint: self.mint.apply_inputs(args)?,
1151 fees: self.fees.apply_inputs(args)?,
1152 adhoc: self.adhoc.apply_inputs(args)?,
1153 })
1154 }
1155
1156 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
1157 Ok(Self {
1158 inputs: self.inputs.apply_fees(fees)?,
1159 outputs: self.outputs.apply_fees(fees)?,
1160 mint: self.mint.apply_fees(fees)?,
1161 fees: self.fees.apply_fees(fees)?,
1162 adhoc: self.adhoc.apply_fees(fees)?,
1163 })
1164 }
1165
1166 fn is_constant(&self) -> bool {
1167 self.inputs.iter().all(|x| x.is_constant())
1168 && self.outputs.iter().all(|x| x.is_constant())
1169 && self.mint.is_constant()
1170 && self.fees.is_constant()
1171 && self.adhoc.iter().all(|x| x.is_constant())
1172 }
1173
1174 fn params(&self) -> BTreeMap<String, ir::Type> {
1175 let mut params = BTreeMap::new();
1176 params.extend(self.inputs.params());
1177 params.extend(self.outputs.params());
1178 params.extend(self.mint.params());
1179 params.extend(self.fees.params());
1180 params.extend(self.adhoc.params());
1181 params
1182 }
1183
1184 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
1185 let mut queries = BTreeMap::new();
1186 queries.extend(self.inputs.queries());
1187 queries.extend(self.outputs.queries());
1188 queries.extend(self.mint.queries());
1189 queries.extend(self.fees.queries());
1190 queries.extend(self.adhoc.queries());
1191 queries
1192 }
1193
1194 fn reduce_self(self) -> Result<Self, Error> {
1195 Ok(self)
1196 }
1197
1198 fn reduce_nested(self) -> Result<Self, Error> {
1199 Ok(Self {
1200 inputs: self.inputs.reduce()?,
1201 outputs: self.outputs.reduce()?,
1202 mint: self.mint.reduce()?,
1203 fees: self.fees.reduce()?,
1204 adhoc: self.adhoc.reduce()?,
1205 })
1206 }
1207}
1208
1209pub fn apply_args(template: ir::Tx, args: &BTreeMap<String, ArgValue>) -> Result<ir::Tx, Error> {
1210 template.apply_args(args)
1211}
1212
1213pub fn apply_inputs(
1214 template: ir::Tx,
1215 args: &BTreeMap<String, HashSet<Utxo>>,
1216) -> Result<ir::Tx, Error> {
1217 template.apply_inputs(args)
1218}
1219
1220pub fn apply_fees(template: ir::Tx, fees: u64) -> Result<ir::Tx, Error> {
1221 template.apply_fees(fees)
1222}
1223
1224pub fn reduce(template: ir::Tx) -> Result<ir::Tx, Error> {
1225 template.reduce()
1226}
1227
1228pub fn find_params(template: &ir::Tx) -> BTreeMap<String, ir::Type> {
1229 template.params()
1230}
1231
1232pub fn find_queries(template: &ir::Tx) -> BTreeMap<String, ir::InputQuery> {
1233 template.queries()
1234}
1235
1236#[cfg(test)]
1237mod tests {
1238
1239 use super::*;
1240
1241 const SUBJECT_PROTOCOL: &str = r#"
1242 party Sender;
1243
1244 tx swap(a: Int, b: Int) {
1245 input source {
1246 from: Sender,
1247 min_amount: Ada(a) + Ada(b),
1248 }
1249 }
1250 "#;
1251
1252 #[test]
1253 fn test_apply_args() {
1254 let mut ast = crate::parsing::parse_string(SUBJECT_PROTOCOL).unwrap();
1255 crate::analyzing::analyze(&mut ast).ok().unwrap();
1256
1257 let before = crate::lowering::lower(&ast, "swap").unwrap();
1258
1259 let params = find_params(&before);
1260 assert_eq!(params.len(), 3);
1261 assert_eq!(params.get("sender"), Some(&ir::Type::Address));
1262 assert_eq!(params.get("a"), Some(&ir::Type::Int));
1263 assert_eq!(params.get("b"), Some(&ir::Type::Int));
1264
1265 let args = BTreeMap::from([
1266 ("sender".to_string(), ArgValue::Address(b"abc".to_vec())),
1267 ("a".to_string(), ArgValue::Int(100)),
1268 ("b".to_string(), ArgValue::Int(200)),
1269 ]);
1270
1271 let after = apply_args(before, &args).unwrap();
1272
1273 let params = find_params(&after);
1274 assert_eq!(params.len(), 0);
1275 }
1276
1277 #[test]
1278 fn test_reduce_numeric_binary_op() {
1279 let op = ir::Expression::EvalCustom(
1280 ir::BinaryOp {
1281 op: ir::BinaryOpKind::Add,
1282 left: ir::Expression::Number(1),
1283 right: ir::Expression::EvalCustom(
1284 ir::BinaryOp {
1285 op: ir::BinaryOpKind::Sub,
1286 left: ir::Expression::Number(5),
1287 right: ir::Expression::Number(3),
1288 }
1289 .into(),
1290 ),
1291 }
1292 .into(),
1293 );
1294
1295 let reduced = op.reduce().unwrap();
1296
1297 match reduced {
1298 ir::Expression::Number(3) => (),
1299 _ => panic!("Expected number 3"),
1300 };
1301 }
1302
1303 #[test]
1304 fn test_reduce_assets_binary_op() {
1305 let op = ir::Expression::EvalCustom(
1306 ir::BinaryOp {
1307 op: ir::BinaryOpKind::Add,
1308 left: ir::Expression::Assets(vec![ir::AssetExpr {
1309 policy: b"abc".to_vec(),
1310 asset_name: ir::Expression::Bytes(b"111".to_vec()),
1311 amount: ir::Expression::Number(100),
1312 }]),
1313 right: ir::Expression::Assets(vec![ir::AssetExpr {
1314 policy: b"abc".to_vec(),
1315 asset_name: ir::Expression::Bytes(b"111".to_vec()),
1316 amount: ir::Expression::Number(200),
1317 }]),
1318 }
1319 .into(),
1320 );
1321
1322 let reduced = op.reduce().unwrap();
1323
1324 match reduced {
1325 ir::Expression::Assets(assets) => {
1326 assert_eq!(assets.len(), 1);
1327 assert_eq!(assets[0].policy, b"abc".to_vec());
1328 assert_eq!(assets[0].asset_name, ir::Expression::Bytes(b"111".to_vec()));
1329 assert_eq!(assets[0].amount, ir::Expression::Number(300));
1330 }
1331 _ => panic!("Expected assets"),
1332 };
1333 }
1334}