tx3_lang/
applying.rs

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        // TODO: what happens if there's a conflict on types?
160
161        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        // structs don't have queries
510        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        // assets don't have queries
568        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        // TODO: reduce if both sides are constants
596
597        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        // binary ops don't have queries
632        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    // Convert back to Vec<AssetExpr>
650    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        // Group assets by policy and asset_name, summing their amounts
696        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        // input queries itself don't have inner queries. This is assuming that an
774        // expression higher up the tree will return this as a required query.
775        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            // the remaining cases are constants, so we can just return them
873            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            // the remaining cases are constants, so we can just return them
952            _ => 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        // outputs don't have queries
1037        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        // mints don't have queries
1088        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}