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,
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        // assets don't have queries
567        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        // TODO: reduce if both sides are constants
595
596        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        // binary ops don't have queries
631        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    // Convert back to Vec<AssetExpr>
649    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        // Group assets by policy and asset_name, summing their amounts
680        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        // input queries itself don't have inner queries. This is assuming that an
757        // expression higher up the tree will return this as a required query.
758        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            // the remaining cases are constants, so we can just return them
856            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            // the remaining cases are constants, so we can just return them
934            _ => 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        // outputs don't have queries
1019        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        // mints don't have queries
1070        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}