1use std::collections::{BTreeMap, HashMap, HashSet};
2
3use crate::{backend, ir, ArgValue, CanonicalAssets, Utxo};
4
5#[derive(Debug, thiserror::Error)]
6pub enum Error {
7 #[error("invalid built-in operation {0:?}")]
8 InvalidBuiltInOp(Box<ir::BuiltInOp>),
9
10 #[error(transparent)]
11 BackendError(#[from] backend::Error),
12
13 #[error("invalid argument {0:?} for {1}")]
14 InvalidArgument(ArgValue, String),
15
16 #[error("property {0} not found in {1}")]
17 PropertyNotFound(String, String),
18
19 #[error("property index {0} not found in {1}")]
20 PropertyIndexNotFound(usize, String),
21
22 #[error("invalid {0} operation over {1:?} and {2:?}")]
23 InvalidBinaryOp(String, String, String),
24
25 #[error("invalid {0} operation over {1:?}")]
26 InvalidUnaryOp(String, String),
27
28 #[error("cannot coerce {0:?} into assets")]
29 CannotCoerceIntoAssets(ir::Expression),
30
31 #[error("cannot coerce {0:?} into datum")]
32 CannotCoerceIntoDatum(ir::Expression),
33}
34
35pub trait Indexable: std::fmt::Debug {
36 fn index(&self, index: usize) -> Option<ir::Expression>;
37
38 fn index_or_err(&self, index: usize) -> Result<ir::Expression, Error> {
39 self.index(index)
40 .ok_or(Error::PropertyIndexNotFound(index, format!("{self:?}")))
41 }
42}
43
44impl Indexable for ir::StructExpr {
45 fn index(&self, index: usize) -> Option<ir::Expression> {
46 self.fields.get(index).cloned()
47 }
48}
49
50impl Indexable for ir::Expression {
51 fn index(&self, index: usize) -> Option<ir::Expression> {
52 match self {
53 ir::Expression::None => None,
54 ir::Expression::List(x) => x.get(index).cloned(),
55 ir::Expression::Tuple(x) => match index {
56 0 => Some(x.0.clone()),
57 1 => Some(x.1.clone()),
58 _ => None,
59 },
60 ir::Expression::Struct(x) => x.index(index),
61 _ => None,
62 }
63 }
64}
65
66pub trait Concatenable {
67 fn concat(self, other: ir::Expression) -> Result<ir::Expression, Error>;
68}
69
70pub trait Arithmetic {
71 fn add(self, other: ir::Expression) -> Result<ir::Expression, Error>;
72 fn sub(self, other: ir::Expression) -> Result<ir::Expression, Error>;
73 fn neg(self) -> Result<ir::Expression, Error>;
74}
75
76impl<T> Arithmetic for T
77where
78 T: Into<CanonicalAssets> + std::fmt::Debug,
79{
80 fn add(self, other: ir::Expression) -> Result<ir::Expression, Error> {
81 let y = match other {
82 ir::Expression::Assets(x) => CanonicalAssets::from(x),
83 ir::Expression::None => CanonicalAssets::empty(),
84 other => {
85 return Err(Error::InvalidBinaryOp(
86 "add".to_string(),
87 format!("{self:?}"),
88 format!("{other:?}"),
89 ))
90 }
91 };
92
93 let x = self.into();
94 let total = x + y;
95 Ok(ir::Expression::Assets(total.into()))
96 }
97
98 fn sub(self, other: ir::Expression) -> Result<ir::Expression, Error> {
99 let other_neg = other.neg()?;
100 self.add(other_neg)
101 }
102
103 fn neg(self) -> Result<ir::Expression, Error> {
104 let negated = std::ops::Neg::neg(self.into());
105 Ok(ir::Expression::Assets(negated.into()))
106 }
107}
108
109impl Arithmetic for i128 {
110 fn add(self, other: ir::Expression) -> Result<ir::Expression, Error> {
111 match other {
112 ir::Expression::Number(y) => Ok(ir::Expression::Number(self + y)),
113 ir::Expression::None => Ok(ir::Expression::Number(self)),
114 _ => Err(Error::InvalidBinaryOp(
115 "add".to_string(),
116 format!("{self:?}"),
117 format!("{other:?}"),
118 )),
119 }
120 }
121
122 fn sub(self, other: ir::Expression) -> Result<ir::Expression, Error> {
123 let other_neg = other.neg()?;
124 self.add(other_neg)
125 }
126
127 fn neg(self) -> Result<ir::Expression, Error> {
128 Ok(ir::Expression::Number(-self))
129 }
130}
131
132impl Arithmetic for ir::Expression {
133 fn add(self, other: ir::Expression) -> Result<ir::Expression, Error> {
134 match self {
135 ir::Expression::None => Ok(other),
136 ir::Expression::Number(x) => Arithmetic::add(x, other),
137 ir::Expression::Assets(x) => Arithmetic::add(x, other),
138 x => Err(Error::InvalidBinaryOp(
139 "add".to_string(),
140 format!("{x:?}"),
141 format!("{other:?}"),
142 )),
143 }
144 }
145
146 fn sub(self, other: ir::Expression) -> Result<ir::Expression, Error> {
147 match self {
148 ir::Expression::None => Ok(other),
149 ir::Expression::Number(x) => Arithmetic::sub(x, other),
150 ir::Expression::Assets(x) => Arithmetic::sub(x, other),
151 x => Err(Error::InvalidBinaryOp(
152 "sub".to_string(),
153 format!("{x:?}"),
154 format!("{other:?}"),
155 )),
156 }
157 }
158
159 fn neg(self) -> Result<ir::Expression, Error> {
160 match self {
161 ir::Expression::None => Ok(ir::Expression::None),
162 ir::Expression::Number(x) => Arithmetic::neg(x),
163 ir::Expression::Assets(x) => Arithmetic::neg(x),
164 x => Err(Error::InvalidUnaryOp("neg".to_string(), format!("{x:?}"))),
165 }
166 }
167}
168
169impl Concatenable for String {
170 fn concat(self, other: ir::Expression) -> Result<ir::Expression, Error> {
171 match other {
172 ir::Expression::String(y) => Ok(ir::Expression::String(self + &y)),
173 ir::Expression::None => Ok(ir::Expression::String(self)),
174 _ => Err(Error::InvalidBinaryOp(
175 "concat".to_string(),
176 format!("String({self:?})"),
177 format!("{other:?}"),
178 )),
179 }
180 }
181}
182
183impl Concatenable for Vec<u8> {
184 fn concat(self, other: ir::Expression) -> Result<ir::Expression, Error> {
185 match other {
186 ir::Expression::Bytes(y) => {
187 let mut result = self;
188 result.extend(y);
189 Ok(ir::Expression::Bytes(result))
190 }
191 ir::Expression::None => Ok(ir::Expression::Bytes(self)),
192 _ => Err(Error::InvalidBinaryOp(
193 "concat".to_string(),
194 format!("Bytes({self:?})"),
195 format!("{other:?}"),
196 )),
197 }
198 }
199}
200
201impl Concatenable for ir::Expression {
202 fn concat(self, other: ir::Expression) -> Result<ir::Expression, Error> {
203 match self {
204 ir::Expression::None => Ok(other),
205 ir::Expression::String(x) => Concatenable::concat(x, other),
206 ir::Expression::Bytes(x) => Concatenable::concat(x, other),
207 x => Err(Error::InvalidBinaryOp(
208 "concat".to_string(),
209 format!("{x:?}"),
210 format!("{other:?}"),
211 )),
212 }
213 }
214}
215
216pub trait Coerceable {
217 fn into_assets(self) -> Result<ir::Expression, Error>;
218 fn into_datum(self) -> Result<ir::Expression, Error>;
219}
220
221impl Coerceable for ir::Expression {
222 fn into_assets(self) -> Result<ir::Expression, Error> {
223 match self {
224 ir::Expression::None => Ok(ir::Expression::None),
225 ir::Expression::Assets(x) => Ok(ir::Expression::Assets(x)),
226 ir::Expression::UtxoSet(x) => {
227 let all = x
228 .into_iter()
229 .map(|x| x.assets)
230 .fold(CanonicalAssets::empty(), |acc, x| acc + x);
231
232 Ok(ir::Expression::Assets(all.into()))
233 }
234 _ => Err(Error::CannotCoerceIntoAssets(self)),
235 }
236 }
237
238 fn into_datum(self) -> Result<ir::Expression, Error> {
239 match self {
240 ir::Expression::None => Ok(ir::Expression::None),
241 ir::Expression::UtxoSet(x) => Ok(x
242 .into_iter()
243 .next()
244 .and_then(|x| x.datum)
245 .unwrap_or(ir::Expression::None)),
246 ir::Expression::List(x) => Ok(ir::Expression::List(x)),
247 ir::Expression::Tuple(x) => Ok(ir::Expression::Tuple(x)),
248 ir::Expression::Struct(x) => Ok(ir::Expression::Struct(x)),
249 ir::Expression::Bytes(x) => Ok(ir::Expression::Bytes(x)),
250 ir::Expression::Number(x) => Ok(ir::Expression::Number(x)),
251 ir::Expression::String(x) => Ok(ir::Expression::String(x)),
252 ir::Expression::Address(x) => Ok(ir::Expression::Bytes(x)),
253 ir::Expression::Hash(x) => Ok(ir::Expression::Bytes(x)),
254 _ => Err(Error::CannotCoerceIntoDatum(self)),
255 }
256 }
257}
258
259fn arg_value_into_expr(arg: ArgValue) -> ir::Expression {
260 match arg {
261 ArgValue::Address(x) => ir::Expression::Address(x),
262 ArgValue::Int(x) => ir::Expression::Number(x),
263 ArgValue::Bool(x) => ir::Expression::Bool(x),
264 ArgValue::String(x) => ir::Expression::String(x),
265 ArgValue::Bytes(x) => ir::Expression::Bytes(x),
266 ArgValue::UtxoSet(x) => ir::Expression::UtxoSet(x),
267 ArgValue::UtxoRef(x) => ir::Expression::UtxoRefs(vec![x]),
268 }
269}
270
271pub trait Apply: Sized + std::fmt::Debug {
272 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error>;
273 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error>;
274 fn apply_fees(self, fees: u64) -> Result<Self, Error>;
275
276 fn is_constant(&self) -> bool;
277
278 fn params(&self) -> BTreeMap<String, ir::Type>;
279 fn queries(&self) -> BTreeMap<String, ir::InputQuery>;
280
281 fn reduce(self) -> Result<Self, Error>;
282}
283
284pub trait Composite: Sized {
285 fn reduce_self(self) -> Result<Self, Error> {
286 Ok(self)
287 }
288
289 fn components(&self) -> Vec<&ir::Expression>;
290
291 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
292 where
293 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone;
294
295 fn reduce_nested(self) -> Result<Self, Error> {
296 self.try_map_components(|x| x.reduce())
297 }
298}
299
300impl<T> Apply for T
301where
302 T: Composite + std::fmt::Debug,
303{
304 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
305 self.try_map_components(|x| x.apply_args(args))
306 }
307
308 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
309 self.try_map_components(|x| x.apply_inputs(args))
310 }
311
312 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
313 self.try_map_components(|x| x.apply_fees(fees))
314 }
315
316 fn is_constant(&self) -> bool {
317 self.components().iter().all(|x| x.is_constant())
318 }
319
320 fn params(&self) -> BTreeMap<String, ir::Type> {
321 self.components().iter().flat_map(|x| x.params()).collect()
322 }
323
324 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
325 self.components().iter().flat_map(|x| x.queries()).collect()
326 }
327
328 fn reduce(self) -> Result<Self, Error> {
329 let x = self.reduce_nested()?;
330
331 if x.is_constant() {
332 x.reduce_self()
333 } else {
334 Ok(x)
335 }
336 }
337}
338
339impl<T> Apply for Option<T>
340where
341 T: Apply,
342{
343 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
344 self.map(|x| x.apply_args(args)).transpose()
345 }
346
347 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
348 self.map(|x| x.apply_inputs(args)).transpose()
349 }
350
351 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
352 self.map(|x| x.apply_fees(fees)).transpose()
353 }
354
355 fn is_constant(&self) -> bool {
356 match self {
357 Some(x) => x.is_constant(),
358 None => true,
359 }
360 }
361
362 fn params(&self) -> BTreeMap<String, ir::Type> {
363 match self {
364 Some(x) => x.params(),
365 None => BTreeMap::new(),
366 }
367 }
368
369 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
370 match self {
371 Some(x) => x.queries(),
372 None => BTreeMap::new(),
373 }
374 }
375
376 fn reduce(self) -> Result<Self, Error> {
377 self.map(|x| x.reduce()).transpose()
378 }
379}
380
381impl<T> Apply for Vec<T>
382where
383 T: Apply,
384{
385 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
386 self.into_iter().map(|x| x.apply_args(args)).collect()
387 }
388
389 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
390 self.into_iter().map(|x| x.apply_inputs(args)).collect()
391 }
392
393 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
394 self.into_iter().map(|x| x.apply_fees(fees)).collect()
395 }
396
397 fn is_constant(&self) -> bool {
398 self.iter().all(|x| x.is_constant())
399 }
400
401 fn params(&self) -> BTreeMap<String, ir::Type> {
402 self.iter().flat_map(|x| x.params()).collect()
403 }
404
405 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
406 self.iter().flat_map(|x| x.queries()).collect()
407 }
408
409 fn reduce(self) -> Result<Self, Error> {
410 self.into_iter().map(|x| x.reduce()).collect()
411 }
412}
413
414impl<T> Apply for HashMap<String, T>
415where
416 T: Apply,
417{
418 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
419 self.into_iter()
420 .map(|(k, v)| v.apply_args(args).map(|v| (k, v)))
421 .collect()
422 }
423
424 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
425 self.into_iter()
426 .map(|(k, v)| v.apply_inputs(args).map(|v| (k, v)))
427 .collect()
428 }
429
430 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
431 self.into_iter()
432 .map(|(k, v)| v.apply_fees(fees).map(|v| (k, v)))
433 .collect()
434 }
435
436 fn is_constant(&self) -> bool {
437 self.values().all(|x| x.is_constant())
438 }
439
440 fn params(&self) -> BTreeMap<String, ir::Type> {
441 self.values().flat_map(|x| x.params()).collect()
442 }
443
444 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
445 self.values().flat_map(|x| x.queries()).collect()
446 }
447
448 fn reduce(self) -> Result<Self, Error> {
449 self.into_iter()
450 .map(|(k, v)| v.reduce().map(|v| (k, v)))
451 .collect()
452 }
453}
454
455impl Composite for ir::ScriptSource {
456 fn reduce_self(self) -> Result<Self, Error> {
457 Ok(self)
458 }
459
460 fn components(&self) -> Vec<&ir::Expression> {
461 match self {
462 ir::ScriptSource::Embedded(x) => vec![x],
463 ir::ScriptSource::UtxoRef { r#ref, source } => {
464 std::iter::once(r#ref).chain(source.as_ref()).collect()
465 }
466 }
467 }
468
469 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
470 where
471 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
472 {
473 match self {
474 ir::ScriptSource::Embedded(x) => Ok(ir::ScriptSource::Embedded(f(x)?)),
475 ir::ScriptSource::UtxoRef { r#ref, source } => Ok(ir::ScriptSource::UtxoRef {
476 r#ref: f(r#ref)?,
477 source: source.map(&f).transpose()?,
478 }),
479 }
480 }
481}
482
483impl TryFrom<&ArgValue> for ir::ScriptSource {
484 type Error = Error;
485
486 fn try_from(value: &ArgValue) -> Result<Self, Self::Error> {
487 match value {
488 ArgValue::Bytes(x) => Ok(ir::ScriptSource::Embedded(ir::Expression::Bytes(x.clone()))),
489 ArgValue::UtxoRef(x) => Ok(ir::ScriptSource::UtxoRef {
490 r#ref: ir::Expression::UtxoRefs(vec![x.clone()]),
491 source: None,
492 }),
493 _ => Err(Error::InvalidArgument(value.clone(), "script".to_string())),
494 }
495 }
496}
497
498impl Composite for ir::PolicyExpr {
499 fn components(&self) -> Vec<&ir::Expression> {
500 let script = self.script.components();
501 std::iter::once(&self.hash).chain(script).collect()
502 }
503
504 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
505 where
506 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
507 {
508 Ok(Self {
509 name: self.name,
510 hash: f(self.hash)?,
511 script: self.script.try_map_components(f)?,
512 })
513 }
514}
515
516impl Composite for ir::StructExpr {
517 fn components(&self) -> Vec<&ir::Expression> {
518 self.fields.iter().collect()
519 }
520
521 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
522 where
523 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
524 {
525 Ok(Self {
526 constructor: self.constructor,
527 fields: self
528 .fields
529 .into_iter()
530 .map(&f)
531 .collect::<Result<Vec<_>, _>>()?,
532 })
533 }
534}
535
536impl Composite for ir::AssetExpr {
537 fn components(&self) -> Vec<&ir::Expression> {
538 vec![&self.policy, &self.asset_name, &self.amount]
539 }
540
541 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
542 where
543 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
544 {
545 Ok(Self {
546 policy: f(self.policy)?,
547 asset_name: f(self.asset_name)?,
548 amount: f(self.amount)?,
549 })
550 }
551}
552
553impl Composite for ir::Coerce {
554 fn components(&self) -> Vec<&ir::Expression> {
555 match self {
556 Self::IntoAssets(x) => vec![x],
557 Self::IntoDatum(x) => vec![x],
558 Self::IntoScript(x) => vec![x],
559 Self::NoOp(x) => vec![x],
560 }
561 }
562
563 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
564 where
565 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
566 {
567 match self {
568 Self::IntoAssets(x) => Ok(Self::IntoAssets(f(x)?)),
569 Self::IntoDatum(x) => Ok(Self::IntoDatum(f(x)?)),
570 Self::IntoScript(x) => Ok(Self::IntoScript(f(x)?)),
571 Self::NoOp(x) => Ok(Self::NoOp(f(x)?)),
572 }
573 }
574
575 fn reduce_self(self) -> Result<Self, Error> {
576 match self {
577 Self::NoOp(x) => Ok(Self::NoOp(x)),
578 Self::IntoAssets(x) => Ok(Self::NoOp(x.into_assets()?)),
579 Self::IntoDatum(x) => Ok(Self::NoOp(x.into_datum()?)),
580 Self::IntoScript(x) => todo!(),
581 }
582 }
583}
584
585impl Composite for ir::BuiltInOp {
586 fn components(&self) -> Vec<&ir::Expression> {
587 match self {
588 Self::NoOp(x) => vec![x],
589 Self::Add(x, y) => vec![x, y],
590 Self::Sub(x, y) => vec![x, y],
591 Self::Concat(x, y) => vec![x, y],
592 Self::Negate(x) => vec![x],
593 Self::Property(x, _) => vec![x],
594 }
595 }
596
597 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
598 where
599 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
600 {
601 match self {
602 Self::NoOp(x) => Ok(Self::NoOp(f(x)?)),
603 Self::Add(x, y) => Ok(Self::Add(f(x)?, f(y)?)),
604 Self::Sub(x, y) => Ok(Self::Sub(f(x)?, f(y)?)),
605 Self::Concat(x, y) => Ok(Self::Concat(f(x)?, f(y)?)),
606 Self::Negate(x) => Ok(Self::Negate(f(x)?)),
607 Self::Property(x, prop) => Ok(Self::Property(f(x)?, prop)),
608 }
609 }
610
611 fn reduce_self(self) -> Result<Self, Error> {
612 match self {
613 Self::Add(x, y) => Ok(Self::NoOp(x.add(y)?)),
614 Self::Sub(x, y) => Ok(Self::NoOp(x.sub(y)?)),
615 Self::Concat(x, y) => Ok(Self::NoOp(x.concat(y)?)),
616 Self::Negate(x) => Ok(Self::NoOp(x.neg()?)),
617 Self::Property(x, prop) => Ok(Self::NoOp(x.index_or_err(prop)?)),
618 Self::NoOp(x) => Ok(Self::NoOp(x)),
619 }
620 }
621
622 fn reduce_nested(self) -> Result<Self, Error> {
623 match self {
624 Self::Add(x, y) => Ok(Self::Add(x.reduce()?, y.reduce()?)),
625 Self::Sub(x, y) => Ok(Self::Sub(x.reduce()?, y.reduce()?)),
626 Self::Concat(x, y) => Ok(Self::Concat(x.reduce()?, y.reduce()?)),
627 Self::Negate(x) => Ok(Self::Negate(x.reduce()?)),
628 Self::Property(x, y) => Ok(Self::Property(x.reduce()?, y)),
629 Self::NoOp(x) => Ok(Self::NoOp(x.reduce()?)),
630 }
631 }
632}
633
634impl From<ir::AssetExpr> for CanonicalAssets {
635 fn from(asset: ir::AssetExpr) -> Self {
636 let policy = asset.expect_constant_policy();
637 let name = asset.expect_constant_name();
638 let amount = asset.expect_constant_amount();
639
640 Self::from_asset(policy, name, amount)
641 }
642}
643
644impl From<Vec<ir::AssetExpr>> for CanonicalAssets {
645 fn from(assets: Vec<ir::AssetExpr>) -> Self {
646 let mut result = CanonicalAssets::empty();
647
648 for asset in assets {
649 let asset = asset.into();
650 result = result + asset;
651 }
652
653 result
654 }
655}
656
657impl From<CanonicalAssets> for Vec<ir::AssetExpr> {
658 fn from(assets: CanonicalAssets) -> Self {
659 let mut result = Vec::new();
660
661 for (class, amount) in assets.into_iter() {
662 result.push(ir::AssetExpr {
663 policy: class
664 .policy()
665 .map(|x| ir::Expression::Bytes(x.to_vec()))
666 .unwrap_or(ir::Expression::None),
667 asset_name: class
668 .name()
669 .map(|x| ir::Expression::Bytes(x.to_vec()))
670 .unwrap_or(ir::Expression::None),
671 amount: ir::Expression::Number(amount),
672 });
673 }
674
675 result
676 }
677}
678
679impl ir::AssetExpr {
680 fn expect_constant_policy(&self) -> Option<&[u8]> {
681 match &self.policy {
682 ir::Expression::None => None,
683 ir::Expression::Bytes(x) => Some(x.as_slice()),
684 _ => None,
685 }
686 }
687
688 fn expect_constant_name(&self) -> Option<&[u8]> {
689 match &self.asset_name {
690 ir::Expression::None => None,
691 ir::Expression::Bytes(x) => Some(x.as_slice()),
692 ir::Expression::String(x) => Some(x.as_bytes()),
693 _ => None,
694 }
695 }
696
697 fn expect_constant_amount(&self) -> i128 {
698 match &self.amount {
699 ir::Expression::Number(x) => *x,
700 _ => unreachable!("amount expected to be Number"),
701 }
702 }
703}
704
705impl Composite for ir::Input {
706 fn components(&self) -> Vec<&ir::Expression> {
707 vec![&self.utxos, &self.redeemer]
708 }
709
710 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
711 where
712 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
713 {
714 Ok(Self {
715 name: self.name,
716 utxos: f(self.utxos)?,
717 redeemer: f(self.redeemer)?,
718 })
719 }
720}
721
722impl Composite for ir::InputQuery {
723 fn components(&self) -> Vec<&ir::Expression> {
724 vec![&self.address, &self.min_amount, &self.r#ref]
725 }
726
727 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
728 where
729 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
730 {
731 Ok(Self {
732 address: f(self.address)?,
733 min_amount: f(self.min_amount)?,
734 r#ref: f(self.r#ref)?,
735 ..self
736 })
737 }
738}
739
740impl Apply for ir::Param {
741 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
742 match self {
743 ir::Param::ExpectValue(name, ty) => {
744 let defined = args.get(&name).cloned();
745
746 match defined {
747 Some(x) => Ok(ir::Param::Set(arg_value_into_expr(x))),
748 None => Ok(Self::ExpectValue(name, ty)),
749 }
750 }
751 ir::Param::ExpectInput(name, query) => {
753 Ok(ir::Param::ExpectInput(name, query.apply_args(args)?))
754 }
755 x => Ok(x),
756 }
757 }
758
759 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
760 match self {
761 ir::Param::ExpectInput(name, query) => {
762 let defined = args.get(&name).cloned();
763
764 match defined {
765 Some(x) => Ok(ir::Param::Set(ir::Expression::UtxoSet(x))),
766 None => Ok(Self::ExpectInput(name, query)),
767 }
768 }
769 x => Ok(x),
770 }
771 }
772
773 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
774 match self {
775 ir::Param::ExpectFees => Ok(ir::Param::Set(ir::Expression::Assets(vec![
776 ir::AssetExpr {
777 policy: ir::Expression::None,
778 asset_name: ir::Expression::None,
779 amount: ir::Expression::Number(fees as i128),
780 },
781 ]))),
782 ir::Param::ExpectInput(name, query) => {
784 Ok(ir::Param::ExpectInput(name, query.apply_fees(fees)?))
785 }
786 x => Ok(x),
787 }
788 }
789
790 fn is_constant(&self) -> bool {
791 match self {
792 ir::Param::Set(x) => x.is_constant(),
793 _ => false,
794 }
795 }
796
797 fn params(&self) -> BTreeMap<String, ir::Type> {
798 match self {
799 ir::Param::ExpectValue(name, ty) => BTreeMap::from([(name.clone(), ty.clone())]),
800 ir::Param::ExpectInput(_, x) => x.params(),
802 _ => BTreeMap::new(),
803 }
804 }
805
806 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
807 match self {
808 ir::Param::ExpectInput(name, query) => BTreeMap::from([(name.clone(), query.clone())]),
809 _ => BTreeMap::new(),
810 }
811 }
812
813 fn reduce(self) -> Result<Self, Error> {
814 match self {
815 ir::Param::ExpectInput(name, query) => {
817 Ok(ir::Param::ExpectInput(name, query.reduce()?))
818 }
819 x => Ok(x),
820 }
821 }
822}
823
824impl Apply for ir::Expression {
825 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
826 match self {
827 Self::List(x) => Ok(Self::List(
828 x.into_iter()
829 .map(|x| x.apply_args(args))
830 .collect::<Result<_, _>>()?,
831 )),
832 Self::Tuple(x) => Ok(Self::Tuple(Box::new((
833 x.0.apply_args(args)?,
834 x.1.apply_args(args)?,
835 )))),
836 Self::Struct(x) => Ok(Self::Struct(x.apply_args(args)?)),
837 Self::Assets(x) => Ok(Self::Assets(
838 x.into_iter()
839 .map(|x| x.apply_args(args))
840 .collect::<Result<_, _>>()?,
841 )),
842 Self::EvalParam(x) => Ok(Self::EvalParam(Box::new(x.apply_args(args)?))),
843 Self::EvalBuiltIn(x) => Ok(Self::EvalBuiltIn(Box::new(x.apply_args(args)?))),
844 Self::EvalCoerce(x) => Ok(Self::EvalCoerce(Box::new(x.apply_args(args)?))),
845 Self::EvalCompiler(x) => Ok(Self::EvalCompiler(Box::new(x.apply_args(args)?))),
846 Self::AdHocDirective(x) => Ok(Self::AdHocDirective(Box::new(x.apply_args(args)?))),
847
848 Self::None => Ok(self),
852 Self::Bytes(_) => Ok(self),
853 Self::Number(_) => Ok(self),
854 Self::Bool(_) => Ok(self),
855 Self::String(_) => Ok(self),
856 Self::Address(_) => Ok(self),
857 Self::Hash(_) => Ok(self),
858 Self::UtxoRefs(_) => Ok(self),
859 Self::UtxoSet(_) => Ok(self),
860 }
861 }
862
863 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
864 match self {
865 Self::List(x) => Ok(Self::List(
866 x.into_iter()
867 .map(|x| x.apply_inputs(args))
868 .collect::<Result<_, _>>()?,
869 )),
870 Self::Tuple(x) => Ok(Self::Tuple(Box::new((
871 x.0.apply_inputs(args)?,
872 x.1.apply_inputs(args)?,
873 )))),
874 Self::Struct(x) => Ok(Self::Struct(x.apply_inputs(args)?)),
875 Self::Assets(x) => Ok(Self::Assets(
876 x.into_iter()
877 .map(|x| x.apply_inputs(args))
878 .collect::<Result<_, _>>()?,
879 )),
880 Self::EvalParam(x) => Ok(Self::EvalParam(Box::new(x.apply_inputs(args)?))),
881 Self::EvalBuiltIn(x) => Ok(Self::EvalBuiltIn(Box::new(x.apply_inputs(args)?))),
882 Self::EvalCoerce(x) => Ok(Self::EvalCoerce(Box::new(x.apply_inputs(args)?))),
883 Self::EvalCompiler(x) => Ok(Self::EvalCompiler(Box::new(x.apply_inputs(args)?))),
884 Self::AdHocDirective(x) => Ok(Self::AdHocDirective(Box::new(x.apply_inputs(args)?))),
885
886 Self::None => Ok(self),
890 Self::Bytes(_) => Ok(self),
891 Self::Number(_) => Ok(self),
892 Self::Bool(_) => Ok(self),
893 Self::String(_) => Ok(self),
894 Self::Address(_) => Ok(self),
895 Self::Hash(_) => Ok(self),
896 Self::UtxoRefs(_) => Ok(self),
897 Self::UtxoSet(_) => Ok(self),
898 }
899 }
900
901 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
902 match self {
903 Self::List(x) => Ok(Self::List(
904 x.into_iter()
905 .map(|x| x.apply_fees(fees))
906 .collect::<Result<_, _>>()?,
907 )),
908 Self::Tuple(x) => Ok(Self::Tuple(Box::new((
909 x.0.apply_fees(fees)?,
910 x.1.apply_fees(fees)?,
911 )))),
912 Self::Struct(x) => Ok(Self::Struct(x.apply_fees(fees)?)),
913 Self::Assets(x) => Ok(Self::Assets(
914 x.into_iter()
915 .map(|x| x.apply_fees(fees))
916 .collect::<Result<_, _>>()?,
917 )),
918 Self::EvalParam(x) => Ok(Self::EvalParam(Box::new(x.apply_fees(fees)?))),
919 Self::EvalBuiltIn(x) => Ok(Self::EvalBuiltIn(Box::new(x.apply_fees(fees)?))),
920 Self::EvalCoerce(x) => Ok(Self::EvalCoerce(Box::new(x.apply_fees(fees)?))),
921 Self::EvalCompiler(x) => Ok(Self::EvalCompiler(Box::new(x.apply_fees(fees)?))),
922 Self::AdHocDirective(x) => Ok(Self::AdHocDirective(Box::new(x.apply_fees(fees)?))),
923
924 Self::None => Ok(self),
928 Self::Bytes(_) => Ok(self),
929 Self::Number(_) => Ok(self),
930 Self::Bool(_) => Ok(self),
931 Self::String(_) => Ok(self),
932 Self::Address(_) => Ok(self),
933 Self::Hash(_) => Ok(self),
934 Self::UtxoRefs(_) => Ok(self),
935 Self::UtxoSet(_) => Ok(self),
936 }
937 }
938
939 fn is_constant(&self) -> bool {
940 match self {
941 Self::List(x) => x.iter().all(|x| x.is_constant()),
942 Self::Tuple(x) => x.0.is_constant() && x.1.is_constant(),
943 Self::Struct(x) => x.is_constant(),
944 Self::Assets(x) => x.iter().all(|x| x.is_constant()),
945 Self::EvalParam(x) => x.is_constant(),
946 Self::EvalBuiltIn(x) => x.is_constant(),
947 Self::EvalCoerce(x) => x.is_constant(),
948 Self::EvalCompiler(_) => false,
949 Self::AdHocDirective(x) => x.is_constant(),
950
951 Self::None => true,
955 Self::Bytes(_) => true,
956 Self::Number(_) => true,
957 Self::Bool(_) => true,
958 Self::String(_) => true,
959 Self::Address(_) => true,
960 Self::Hash(_) => true,
961 Self::UtxoRefs(_) => true,
962 Self::UtxoSet(_) => true,
963 }
964 }
965
966 fn params(&self) -> BTreeMap<String, ir::Type> {
967 match self {
968 Self::List(x) => x.iter().flat_map(|x| x.params()).collect(),
969 Self::Tuple(x) => [x.0.params(), x.1.params()].into_iter().flatten().collect(),
970 Self::Struct(x) => x.params(),
971 Self::Assets(x) => x.iter().flat_map(|x| x.params()).collect(),
972 Self::EvalParam(x) => x.params(),
973 Self::EvalBuiltIn(x) => x.params(),
974 Self::EvalCoerce(x) => x.params(),
975 Self::EvalCompiler(x) => x.params(),
976 Self::AdHocDirective(x) => x.params(),
977
978 Self::None => BTreeMap::new(),
982 Self::Bytes(_) => BTreeMap::new(),
983 Self::Number(_) => BTreeMap::new(),
984 Self::Bool(_) => BTreeMap::new(),
985 Self::String(_) => BTreeMap::new(),
986 Self::Address(_) => BTreeMap::new(),
987 Self::Hash(_) => BTreeMap::new(),
988 Self::UtxoRefs(_) => BTreeMap::new(),
989 Self::UtxoSet(_) => BTreeMap::new(),
990 }
991 }
992
993 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
994 match self {
995 Self::List(x) => x.iter().flat_map(|x| x.queries()).collect(),
996 Self::Tuple(x) => [x.0.queries(), x.1.queries()]
997 .into_iter()
998 .flatten()
999 .collect(),
1000 Self::Struct(x) => x.queries(),
1001 Self::Assets(x) => x.iter().flat_map(|x| x.queries()).collect(),
1002 Self::EvalParam(x) => x.queries(),
1003 Self::EvalBuiltIn(x) => x.queries(),
1004 Self::EvalCoerce(x) => x.queries(),
1005 Self::EvalCompiler(x) => x.queries(),
1006 Self::AdHocDirective(x) => x.queries(),
1007
1008 Self::None => BTreeMap::new(),
1012 Self::Bytes(_) => BTreeMap::new(),
1013 Self::Number(_) => BTreeMap::new(),
1014 Self::Bool(_) => BTreeMap::new(),
1015 Self::String(_) => BTreeMap::new(),
1016 Self::Address(_) => BTreeMap::new(),
1017 Self::Hash(_) => BTreeMap::new(),
1018 Self::UtxoRefs(_) => BTreeMap::new(),
1019 Self::UtxoSet(_) => BTreeMap::new(),
1020 }
1021 }
1022
1023 fn reduce(self) -> Result<Self, Error> {
1024 match self {
1025 ir::Expression::List(x) => Ok(Self::List(
1027 x.into_iter()
1028 .map(|x| x.reduce())
1029 .collect::<Result<_, _>>()?,
1030 )),
1031 ir::Expression::Tuple(x) => Ok(Self::Tuple(Box::new((x.0.reduce()?, x.1.reduce()?)))),
1032 ir::Expression::Struct(x) => Ok(Self::Struct(x.reduce()?)),
1033 ir::Expression::Assets(x) => Ok(Self::Assets(
1034 x.into_iter()
1035 .map(|x| x.reduce())
1036 .collect::<Result<_, _>>()?,
1037 )),
1038 ir::Expression::EvalCompiler(x) => Ok(Self::EvalCompiler(Box::new(x.reduce()?))),
1039 ir::Expression::AdHocDirective(x) => Ok(Self::AdHocDirective(Box::new(x.reduce()?))),
1040
1041 ir::Expression::EvalBuiltIn(x) => match x.reduce()? {
1043 ir::BuiltInOp::NoOp(x) => Ok(x),
1044 x => Ok(ir::Expression::EvalBuiltIn(Box::new(x.reduce()?))),
1045 },
1046 ir::Expression::EvalCoerce(x) => match x.reduce()? {
1047 ir::Coerce::NoOp(x) => Ok(x),
1048 x => Ok(ir::Expression::EvalCoerce(Box::new(x.reduce()?))),
1049 },
1050 ir::Expression::EvalParam(x) => match x.reduce()? {
1051 ir::Param::Set(x) => Ok(x),
1052 x => Ok(ir::Expression::EvalParam(Box::new(x.reduce()?))),
1053 },
1054
1055 Self::None => Ok(self),
1059 Self::Bytes(_) => Ok(self),
1060 Self::Number(_) => Ok(self),
1061 Self::Bool(_) => Ok(self),
1062 Self::String(_) => Ok(self),
1063 Self::Address(_) => Ok(self),
1064 Self::Hash(_) => Ok(self),
1065 Self::UtxoRefs(_) => Ok(self),
1066 Self::UtxoSet(_) => Ok(self),
1067 }
1068 }
1069}
1070
1071impl Composite for ir::Output {
1072 fn components(&self) -> Vec<&ir::Expression> {
1073 vec![&self.address, &self.datum, &self.amount]
1074 }
1075
1076 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1077 where
1078 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1079 {
1080 Ok(Self {
1081 address: f(self.address)?,
1082 datum: f(self.datum)?,
1083 amount: f(self.amount)?,
1084 })
1085 }
1086}
1087
1088impl Composite for ir::Mint {
1089 fn components(&self) -> Vec<&ir::Expression> {
1090 vec![&self.amount, &self.redeemer]
1091 }
1092
1093 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1094 where
1095 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1096 {
1097 Ok(Self {
1098 amount: f(self.amount)?,
1099 redeemer: f(self.redeemer)?,
1100 })
1101 }
1102}
1103
1104impl Composite for ir::AdHocDirective {
1105 fn components(&self) -> Vec<&ir::Expression> {
1106 self.data.values().collect()
1107 }
1108
1109 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1110 where
1111 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1112 {
1113 Ok(Self {
1114 name: self.name,
1115 data: self
1116 .data
1117 .into_iter()
1118 .map(|(k, v)| f(v).map(|v| (k, v)))
1119 .collect::<Result<_, _>>()?,
1120 })
1121 }
1122}
1123
1124impl Composite for ir::CompilerOp {
1125 fn components(&self) -> Vec<&ir::Expression> {
1126 match self {
1127 ir::CompilerOp::BuildScriptAddress(x) => vec![x],
1128 ir::CompilerOp::ComputeMinUtxo(x) => vec![x],
1129 }
1130 }
1131
1132 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1133 where
1134 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1135 {
1136 match self {
1137 ir::CompilerOp::BuildScriptAddress(x) => Ok(ir::CompilerOp::BuildScriptAddress(f(x)?)),
1138 ir::CompilerOp::ComputeMinUtxo(x) => Ok(ir::CompilerOp::ComputeMinUtxo(f(x)?)),
1139 }
1140 }
1141}
1142
1143impl Composite for ir::Signers {
1144 fn components(&self) -> Vec<&ir::Expression> {
1145 self.signers.iter().collect()
1146 }
1147
1148 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1149 where
1150 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1151 {
1152 Ok(Self {
1153 signers: self.signers.into_iter().map(f).collect::<Result<_, _>>()?,
1154 })
1155 }
1156}
1157
1158impl Composite for ir::Collateral {
1159 fn components(&self) -> Vec<&ir::Expression> {
1160 vec![&self.utxos]
1161 }
1162
1163 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1164 where
1165 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1166 {
1167 Ok(Self {
1168 utxos: f(self.utxos)?,
1169 })
1170 }
1171}
1172
1173impl Composite for ir::Validity {
1174 fn components(&self) -> Vec<&ir::Expression> {
1175 vec![&self.since, &self.until]
1176 }
1177
1178 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1179 where
1180 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1181 {
1182 Ok(Self {
1183 since: f(self.since)?,
1184 until: f(self.until)?,
1185 })
1186 }
1187}
1188
1189impl Composite for ir::Metadata {
1190 fn components(&self) -> Vec<&ir::Expression> {
1191 vec![&self.key, &self.value]
1192 }
1193
1194 fn try_map_components<F>(self, f: F) -> Result<Self, Error>
1195 where
1196 F: Fn(ir::Expression) -> Result<ir::Expression, Error> + Clone,
1197 {
1198 Ok(Self {
1199 key: f(self.key)?,
1200 value: f(self.value)?,
1201 })
1202 }
1203}
1204
1205impl Apply for ir::Tx {
1206 fn apply_args(self, args: &BTreeMap<String, ArgValue>) -> Result<Self, Error> {
1207 let tx = ir::Tx {
1208 references: self.references.apply_args(args)?,
1209 inputs: self.inputs.apply_args(args)?,
1210 outputs: self.outputs.apply_args(args)?,
1211 validity: self.validity.apply_args(args)?,
1212 mints: self.mints.apply_args(args)?,
1213 burns: self.burns.apply_args(args)?,
1214 fees: self.fees.apply_args(args)?,
1215 adhoc: self.adhoc.apply_args(args)?,
1216 collateral: self.collateral.apply_args(args)?,
1217 signers: self.signers.apply_args(args)?,
1218 metadata: self.metadata.apply_args(args)?,
1219 };
1220
1221 Ok(tx)
1222 }
1223
1224 fn apply_inputs(self, args: &BTreeMap<String, HashSet<Utxo>>) -> Result<Self, Error> {
1225 Ok(Self {
1226 references: self.references.apply_inputs(args)?,
1227 inputs: self.inputs.apply_inputs(args)?,
1228 outputs: self.outputs.apply_inputs(args)?,
1229 validity: self.validity.apply_inputs(args)?,
1230 mints: self.mints.apply_inputs(args)?,
1231 burns: self.burns.apply_inputs(args)?,
1232 fees: self.fees.apply_inputs(args)?,
1233 adhoc: self.adhoc.apply_inputs(args)?,
1234 collateral: self.collateral.apply_inputs(args)?,
1235 signers: self.signers.apply_inputs(args)?,
1236 metadata: self.metadata.apply_inputs(args)?,
1237 })
1238 }
1239
1240 fn apply_fees(self, fees: u64) -> Result<Self, Error> {
1241 Ok(Self {
1242 references: self.references.apply_fees(fees)?,
1243 inputs: self.inputs.apply_fees(fees)?,
1244 outputs: self.outputs.apply_fees(fees)?,
1245 validity: self.validity.apply_fees(fees)?,
1246 mints: self.mints.apply_fees(fees)?,
1247 burns: self.burns.apply_fees(fees)?,
1248 fees: self.fees.apply_fees(fees)?,
1249 adhoc: self.adhoc.apply_fees(fees)?,
1250 collateral: self.collateral.apply_fees(fees)?,
1251 signers: self.signers.apply_fees(fees)?,
1252 metadata: self.metadata.apply_fees(fees)?,
1253 })
1254 }
1255
1256 fn is_constant(&self) -> bool {
1257 self.inputs.iter().all(|x| x.is_constant())
1258 && self.outputs.iter().all(|x| x.is_constant())
1259 && self.mints.iter().all(|x| x.is_constant())
1260 && self.burns.iter().all(|x| x.is_constant())
1261 && self.fees.is_constant()
1262 && self.metadata.is_constant()
1263 && self.validity.is_constant()
1264 && self.references.is_constant()
1265 && self.collateral.is_constant()
1266 && self.adhoc.iter().all(|x| x.is_constant())
1267 && self.signers.is_constant()
1268 }
1269
1270 fn params(&self) -> BTreeMap<String, ir::Type> {
1271 let mut params = BTreeMap::new();
1273 params.extend(self.inputs.params());
1274 params.extend(self.outputs.params());
1275 params.extend(self.mints.params());
1276 params.extend(self.burns.params());
1277 params.extend(self.fees.params());
1278 params.extend(self.adhoc.params());
1279 params.extend(self.signers.params());
1280 params.extend(self.validity.params());
1281 params.extend(self.metadata.params());
1282 params.extend(self.references.params());
1283 params.extend(self.collateral.params());
1284 params
1285 }
1286
1287 fn queries(&self) -> BTreeMap<String, ir::InputQuery> {
1288 let mut queries = BTreeMap::new();
1289 queries.extend(self.inputs.queries());
1290 queries.extend(self.outputs.queries());
1291 queries.extend(self.mints.queries());
1292 queries.extend(self.burns.queries());
1293 queries.extend(self.fees.queries());
1294 queries.extend(self.adhoc.queries());
1295 queries.extend(self.signers.queries());
1296 queries.extend(self.validity.queries());
1297 queries.extend(self.metadata.queries());
1298 queries.extend(self.collateral.queries());
1299 queries.extend(self.references.queries());
1300 queries
1301 }
1302
1303 fn reduce(self) -> Result<Self, Error> {
1304 Ok(Self {
1305 references: self.references.reduce()?,
1306 inputs: self.inputs.reduce()?,
1307 outputs: self.outputs.reduce()?,
1308 validity: self.validity.reduce()?,
1309 mints: self.mints.reduce()?,
1310 burns: self.burns.reduce()?,
1311 fees: self.fees.reduce()?,
1312 adhoc: self.adhoc.reduce()?,
1313 collateral: self.collateral.reduce()?,
1314 signers: self.signers.reduce()?,
1315 metadata: self.metadata.reduce()?,
1316 })
1317 }
1318}
1319
1320pub fn apply_args(template: ir::Tx, args: &BTreeMap<String, ArgValue>) -> Result<ir::Tx, Error> {
1321 template.apply_args(args)
1322}
1323
1324pub fn apply_inputs(
1325 template: ir::Tx,
1326 args: &BTreeMap<String, HashSet<Utxo>>,
1327) -> Result<ir::Tx, Error> {
1328 template.apply_inputs(args)
1329}
1330
1331pub fn apply_fees(template: ir::Tx, fees: u64) -> Result<ir::Tx, Error> {
1332 template.apply_fees(fees)
1333}
1334
1335pub fn reduce(template: ir::Tx) -> Result<ir::Tx, Error> {
1336 template.reduce()
1337}
1338
1339pub fn find_params(template: &ir::Tx) -> BTreeMap<String, ir::Type> {
1340 template.params()
1341}
1342
1343pub fn find_queries(template: &ir::Tx) -> BTreeMap<String, ir::InputQuery> {
1344 template.queries()
1345}
1346
1347#[cfg(test)]
1348mod tests {
1349
1350 use crate::UtxoRef;
1351
1352 use super::*;
1353
1354 const SUBJECT_PROTOCOL: &str = r#"
1355 party Sender;
1356
1357 tx swap(a: Int, b: Int) {
1358 input source {
1359 from: Sender,
1360 min_amount: Ada(a) + Ada(b) + fees,
1361 }
1362 }
1363 "#;
1364
1365 #[test]
1366 fn param_expression_is_applied() {
1367 let ir = ir::Expression::EvalParam(Box::new(ir::Param::ExpectValue(
1368 "a".to_string(),
1369 ir::Type::Int,
1370 )));
1371
1372 let params = ir.params();
1373 assert_eq!(params.len(), 1);
1374 assert_eq!(params.get("a"), Some(&ir::Type::Int));
1375
1376 let args = BTreeMap::from([("a".to_string(), ArgValue::Int(100))]);
1377
1378 let after = ir.apply_args(&args).unwrap();
1379
1380 assert_eq!(
1381 after,
1382 ir::Expression::EvalParam(Box::new(ir::Param::Set(ir::Expression::Number(100),)))
1383 );
1384 }
1385
1386 #[test]
1387 fn nested_param_expression_is_applied() {
1388 let ir = ir::Expression::EvalParam(Box::new(ir::Param::ExpectInput(
1389 "out".to_string(),
1390 ir::InputQuery {
1391 address: ir::Expression::None,
1392 min_amount: ir::Expression::None,
1393 r#ref: ir::Expression::EvalParam(Box::new(ir::Param::ExpectValue(
1394 "in".to_string(),
1395 ir::Type::Int,
1396 ))),
1397 many: false,
1398 collateral: false,
1399 },
1400 )));
1401
1402 let params = ir.params();
1403 assert_eq!(params.len(), 1);
1404 assert_eq!(params.get("in"), Some(&ir::Type::Int));
1405
1406 let args = BTreeMap::from([("in".to_string(), ArgValue::Int(100))]);
1407 let after = ir.apply_args(&args).unwrap();
1408
1409 let after = after.reduce().unwrap();
1410
1411 let queries = after.queries();
1412 assert_eq!(queries.len(), 1);
1413 assert_eq!(
1414 queries.get("out"),
1415 Some(&ir::InputQuery {
1416 address: ir::Expression::None,
1417 min_amount: ir::Expression::None,
1418 r#ref: ir::Expression::Number(100),
1419 many: false,
1420 collateral: false,
1421 })
1422 );
1423 }
1424
1425 #[test]
1426 fn param_expression_is_reduced() {
1427 let ir = ir::Expression::EvalParam(Box::new(ir::Param::Set(ir::Expression::Number(3))));
1428
1429 let after = ir.reduce().unwrap();
1430
1431 assert_eq!(after, ir::Expression::Number(3));
1432 }
1433
1434 #[test]
1435 fn test_apply_args() {
1436 let mut ast = crate::parsing::parse_string(SUBJECT_PROTOCOL).unwrap();
1437 crate::analyzing::analyze(&mut ast).ok().unwrap();
1438
1439 let before = crate::lowering::lower(&ast, "swap").unwrap();
1440
1441 dbg!(&before);
1442
1443 let params = find_params(&before);
1444 assert_eq!(params.len(), 3);
1445 assert_eq!(params.get("sender"), Some(&ir::Type::Address));
1446 assert_eq!(params.get("a"), Some(&ir::Type::Int));
1447 assert_eq!(params.get("b"), Some(&ir::Type::Int));
1448
1449 let args = BTreeMap::from([
1450 ("sender".to_string(), ArgValue::Address(b"abc".to_vec())),
1451 ("a".to_string(), ArgValue::Int(100)),
1452 ("b".to_string(), ArgValue::Int(200)),
1453 ]);
1454
1455 let after = apply_args(before, &args).unwrap();
1456
1457 let params = find_params(&after);
1458 assert_eq!(params.len(), 0);
1459 }
1460
1461 #[test]
1462 fn test_apply_inputs() {
1463 let mut ast = crate::parsing::parse_string(SUBJECT_PROTOCOL).unwrap();
1464 crate::analyzing::analyze(&mut ast).ok().unwrap();
1465
1466 let before = crate::lowering::lower(&ast, "swap").unwrap();
1467
1468 let args = BTreeMap::from([
1469 ("sender".to_string(), ArgValue::Address(b"abc".to_vec())),
1470 ("a".to_string(), ArgValue::Int(100)),
1471 ("b".to_string(), ArgValue::Int(200)),
1472 ]);
1473
1474 let before = apply_args(before, &args).unwrap();
1475
1476 let before = before.reduce().unwrap();
1477
1478 let queries = find_queries(&before);
1479 dbg!(&queries);
1480
1481 assert_eq!(queries.len(), 1);
1482 assert!(queries.contains_key("source"));
1483
1484 let inputs = BTreeMap::from([(
1485 "source".to_string(),
1486 HashSet::from([Utxo {
1487 r#ref: UtxoRef::new(b"abc", 0),
1488 address: b"abc".to_vec(),
1489 datum: None,
1490 assets: CanonicalAssets::from_naked_amount(300),
1491 script: None,
1492 }]),
1493 )]);
1494
1495 let after = apply_inputs(before, &inputs).unwrap();
1496
1497 let queries = find_queries(&after);
1498 dbg!(&queries);
1499
1500 assert_eq!(queries.len(), 0);
1501 }
1502
1503 #[test]
1504 fn test_apply_fees() {
1505 let mut ast = crate::parsing::parse_string(SUBJECT_PROTOCOL).unwrap();
1506 crate::analyzing::analyze(&mut ast).ok().unwrap();
1507
1508 let before = crate::lowering::lower(&ast, "swap").unwrap();
1509
1510 let args = BTreeMap::from([
1511 ("sender".to_string(), ArgValue::Address(b"abc".to_vec())),
1512 ("a".to_string(), ArgValue::Int(100)),
1513 ("b".to_string(), ArgValue::Int(200)),
1514 ]);
1515
1516 let before = apply_args(before, &args).unwrap().reduce().unwrap();
1517
1518 let after = before.apply_fees(100).unwrap().reduce().unwrap();
1519
1520 let queries = find_queries(&after);
1521
1522 let query = queries.get("source").unwrap();
1523
1524 assert_eq!(
1525 query,
1526 &ir::InputQuery {
1527 address: ir::Expression::Address(b"abc".to_vec()),
1528 min_amount: ir::Expression::Assets(vec![ir::AssetExpr {
1529 policy: ir::Expression::None,
1530 asset_name: ir::Expression::None,
1531 amount: ir::Expression::Number(400),
1532 }]),
1533 r#ref: ir::Expression::None,
1534 many: false,
1535 collateral: false,
1536 }
1537 );
1538 }
1539
1540 #[test]
1541 fn built_in_expression_is_reduced() {
1542 let op =
1543 ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::NoOp(ir::Expression::Number(5))));
1544
1545 let after = op.reduce().unwrap();
1546
1547 assert_eq!(after, ir::Expression::Number(5))
1548 }
1549
1550 #[test]
1551 fn numeric_add_is_reduced() {
1552 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Add(
1553 ir::Expression::Number(1),
1554 ir::Expression::Number(5),
1555 )));
1556
1557 let after = op.reduce().unwrap();
1558
1559 assert_eq!(after, ir::Expression::Number(6));
1560 }
1561
1562 #[test]
1563 fn numeric_sub_is_reduced() {
1564 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Sub(
1565 ir::Expression::Number(8),
1566 ir::Expression::Number(5),
1567 )));
1568
1569 let after = op.reduce().unwrap();
1570
1571 assert_eq!(after, ir::Expression::Number(3));
1572 }
1573
1574 #[test]
1575 fn nested_numeric_binary_op_is_reduced() {
1576 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Add(
1577 ir::Expression::Number(1),
1578 ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Sub(
1579 ir::Expression::Number(5),
1580 ir::Expression::Number(3),
1581 ))),
1582 )));
1583
1584 let after = op.reduce().unwrap();
1585
1586 assert_eq!(after, ir::Expression::Number(3));
1587 }
1588
1589 #[test]
1590 fn test_reduce_single_custom_asset_binary_op() {
1591 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Add(
1592 ir::Expression::Assets(vec![ir::AssetExpr {
1593 policy: ir::Expression::Bytes(b"abc".to_vec()),
1594 asset_name: ir::Expression::Bytes(b"111".to_vec()),
1595 amount: ir::Expression::Number(100),
1596 }]),
1597 ir::Expression::Assets(vec![ir::AssetExpr {
1598 policy: ir::Expression::Bytes(b"abc".to_vec()),
1599 asset_name: ir::Expression::Bytes(b"111".to_vec()),
1600 amount: ir::Expression::Number(200),
1601 }]),
1602 )));
1603
1604 let reduced = op.reduce().unwrap();
1605
1606 match reduced {
1607 ir::Expression::Assets(assets) => {
1608 assert_eq!(assets.len(), 1);
1609 assert_eq!(assets[0].policy, ir::Expression::Bytes(b"abc".to_vec()));
1610 assert_eq!(assets[0].asset_name, ir::Expression::Bytes(b"111".to_vec()));
1611 assert_eq!(assets[0].amount, ir::Expression::Number(300));
1612 }
1613 _ => panic!("Expected assets"),
1614 };
1615 }
1616
1617 #[test]
1618 fn test_reduce_native_asset_binary_op() {
1619 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Add(
1620 ir::Expression::Assets(vec![ir::AssetExpr {
1621 policy: ir::Expression::None,
1622 asset_name: ir::Expression::None,
1623 amount: ir::Expression::Number(100),
1624 }]),
1625 ir::Expression::Assets(vec![ir::AssetExpr {
1626 policy: ir::Expression::None,
1627 asset_name: ir::Expression::None,
1628 amount: ir::Expression::Number(200),
1629 }]),
1630 )));
1631
1632 let reduced = op.reduce().unwrap();
1633
1634 match reduced {
1635 ir::Expression::Assets(assets) => {
1636 assert_eq!(assets.len(), 1);
1637 assert_eq!(assets[0].policy, ir::Expression::None);
1638 assert_eq!(assets[0].asset_name, ir::Expression::None);
1639 assert_eq!(assets[0].amount, ir::Expression::Number(300));
1640 }
1641 _ => panic!("Expected assets"),
1642 };
1643 }
1644
1645 #[test]
1646 fn test_reduce_mixed_asset_binary_op() {
1647 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Add(
1648 ir::Expression::Assets(vec![ir::AssetExpr {
1649 policy: ir::Expression::None,
1650 asset_name: ir::Expression::None,
1651 amount: ir::Expression::Number(100),
1652 }]),
1653 ir::Expression::Assets(vec![ir::AssetExpr {
1654 policy: ir::Expression::Bytes(b"abc".to_vec()),
1655 asset_name: ir::Expression::Bytes(b"111".to_vec()),
1656 amount: ir::Expression::Number(200),
1657 }]),
1658 )));
1659
1660 let reduced = op.reduce().unwrap();
1661
1662 match reduced {
1663 ir::Expression::Assets(assets) => {
1664 assert_eq!(assets.len(), 2);
1665
1666 for asset in assets {
1667 if asset.policy == ir::Expression::None {
1668 assert_eq!(asset.asset_name, ir::Expression::None);
1669 assert_eq!(asset.amount, ir::Expression::Number(100));
1670 } else {
1671 assert_eq!(asset.policy, ir::Expression::Bytes(b"abc".to_vec()));
1672 assert_eq!(asset.asset_name, ir::Expression::Bytes(b"111".to_vec()));
1673 assert_eq!(asset.amount, ir::Expression::Number(200));
1674 }
1675 }
1676 }
1677 _ => panic!("Expected assets"),
1678 };
1679 }
1680
1681 #[test]
1682 fn test_reduce_coerce_noop() {
1683 let op = ir::Expression::EvalCoerce(Box::new(ir::Coerce::NoOp(ir::Expression::Number(5))));
1684
1685 let reduced = op.reduce().unwrap();
1686
1687 match reduced {
1688 ir::Expression::Number(5) => (),
1689 _ => panic!("Expected number 5"),
1690 };
1691 }
1692
1693 #[test]
1694 fn test_coerce_utxo_set_into_assets() {
1695 let utxos = vec![Utxo {
1696 r#ref: UtxoRef::new(b"abc", 1),
1697 address: b"abc".into(),
1698 datum: Some(ir::Expression::Number(1)),
1699 assets: CanonicalAssets::from_defined_asset(b"abc", b"111", 1),
1700 script: None,
1701 }];
1702
1703 let op = ir::Coerce::IntoAssets(ir::Expression::UtxoSet(HashSet::from_iter(
1704 utxos.clone().into_iter(),
1705 )));
1706
1707 let reduced = op.reduce().unwrap();
1708
1709 assert_eq!(
1710 reduced,
1711 ir::Coerce::NoOp(ir::Expression::Assets(utxos[0].assets.clone().into()))
1712 );
1713 }
1714
1715 #[test]
1716 fn test_coerce_utxo_set_into_datum() {
1717 let utxos = vec![Utxo {
1718 r#ref: UtxoRef::new(b"abc", 1),
1719 address: b"abc".into(),
1720 datum: Some(ir::Expression::Number(1)),
1721 assets: CanonicalAssets::from_naked_amount(1),
1722 script: None,
1723 }];
1724
1725 let op = ir::Coerce::IntoDatum(ir::Expression::UtxoSet(HashSet::from_iter(
1726 utxos.clone().into_iter(),
1727 )));
1728
1729 let reduced = op.reduce().unwrap();
1730
1731 assert_eq!(reduced, ir::Coerce::NoOp(utxos[0].datum.clone().unwrap()));
1732 }
1733
1734 #[test]
1735 fn test_reduce_struct_property_access() {
1736 let object = ir::Expression::Struct(ir::StructExpr {
1737 constructor: 0,
1738 fields: vec![
1739 ir::Expression::Number(1),
1740 ir::Expression::Number(2),
1741 ir::Expression::Number(3),
1742 ],
1743 });
1744
1745 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(object.clone(), 1)));
1746
1747 let reduced = op.reduce();
1748
1749 match reduced {
1750 Ok(ir::Expression::Number(2)) => (),
1751 _ => panic!("Expected number 2"),
1752 };
1753
1754 let op =
1755 ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(object.clone(), 100)));
1756
1757 let reduced = op.reduce();
1758
1759 match reduced {
1760 Err(Error::PropertyIndexNotFound(100, _)) => (),
1761 _ => panic!("Expected property index not found"),
1762 };
1763 }
1764
1765 #[test]
1766 fn test_reduce_list_property_access() {
1767 let object = ir::Expression::List(vec![
1768 ir::Expression::Number(1),
1769 ir::Expression::Number(2),
1770 ir::Expression::Number(3),
1771 ]);
1772
1773 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(object.clone(), 1)));
1774
1775 let reduced = op.reduce();
1776
1777 match reduced {
1778 Ok(ir::Expression::Number(2)) => (),
1779 _ => panic!("Expected number 2"),
1780 };
1781
1782 let op =
1783 ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(object.clone(), 100)));
1784
1785 let reduced = op.reduce();
1786
1787 match reduced {
1788 Err(Error::PropertyIndexNotFound(100, _)) => (),
1789 _ => panic!("Expected property index not found"),
1790 };
1791 }
1792
1793 #[test]
1794 fn test_reduce_tuple_property_access() {
1795 let object = ir::Expression::Tuple(Box::new((
1796 ir::Expression::Number(1),
1797 ir::Expression::Number(2),
1798 )));
1799
1800 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(object.clone(), 1)));
1801
1802 let reduced = op.reduce();
1803
1804 match reduced {
1805 Ok(ir::Expression::Number(2)) => (),
1806 _ => panic!("Expected number 2"),
1807 };
1808
1809 let op =
1810 ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Property(object.clone(), 100)));
1811
1812 let reduced = op.reduce();
1813
1814 match reduced {
1815 Err(Error::PropertyIndexNotFound(100, _)) => (),
1816 _ => panic!("Expected property index not found"),
1817 };
1818 }
1819
1820 #[test]
1821 fn test_string_concat_is_reduced() {
1822 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Concat(
1823 ir::Expression::String("hello".to_string()),
1824 ir::Expression::String("world".to_string()),
1825 )));
1826
1827 let reduced = op.reduce().unwrap();
1828
1829 match reduced {
1830 ir::Expression::String(s) => assert_eq!(s, "helloworld"),
1831 _ => panic!("Expected string 'helloworld'"),
1832 }
1833 }
1834
1835 #[test]
1836 fn test_bytes_concat_is_reduced() {
1837 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Concat(
1838 ir::Expression::Bytes(vec![1, 2, 3]),
1839 ir::Expression::Bytes(vec![4, 5, 6]),
1840 )));
1841
1842 let reduced = op.reduce().unwrap();
1843
1844 match reduced {
1845 ir::Expression::Bytes(b) => assert_eq!(b, vec![1, 2, 3, 4, 5, 6]),
1846 _ => panic!("Expected bytes [1, 2, 3, 4, 5, 6]"),
1847 }
1848 }
1849
1850 #[test]
1851 fn test_concat_type_mismatch_error() {
1852 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Concat(
1853 ir::Expression::String("hello".to_string()),
1854 ir::Expression::Bytes(vec![1, 2, 3]),
1855 )));
1856
1857 let reduced = op.reduce();
1858
1859 match reduced {
1860 Err(Error::InvalidBinaryOp(op, _, _)) => assert_eq!(op, "concat"),
1861 _ => panic!("Expected InvalidBinaryOp error"),
1862 }
1863 }
1864
1865 #[test]
1866 fn test_concat_with_none() {
1867 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Concat(
1868 ir::Expression::String("hello".to_string()),
1869 ir::Expression::None,
1870 )));
1871
1872 let reduced = op.reduce().unwrap();
1873
1874 match reduced {
1875 ir::Expression::String(s) => assert_eq!(s, "hello"),
1876 _ => panic!("Expected string 'hello'"),
1877 }
1878 }
1879
1880 #[test]
1881 fn test_min_utxo_add_non_reduction() {
1882 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Add(
1883 ir::Expression::Assets(vec![ir::AssetExpr {
1884 policy: ir::Expression::None,
1885 asset_name: ir::Expression::None,
1886 amount: ir::Expression::Number(29),
1887 }]),
1888 ir::Expression::EvalCompiler(Box::new(ir::CompilerOp::ComputeMinUtxo(
1889 ir::Expression::Number(20),
1890 ))),
1891 )));
1892
1893 let reduced = op.clone().reduce().unwrap();
1894
1895 assert!(op == reduced)
1896 }
1897
1898 #[test]
1899 fn test_min_utxo_sub_non_reduction() {
1900 let op = ir::Expression::EvalBuiltIn(Box::new(ir::BuiltInOp::Sub(
1901 ir::Expression::Assets(vec![ir::AssetExpr {
1902 policy: ir::Expression::None,
1903 asset_name: ir::Expression::None,
1904 amount: ir::Expression::Number(29),
1905 }]),
1906 ir::Expression::EvalCompiler(Box::new(ir::CompilerOp::ComputeMinUtxo(
1907 ir::Expression::Number(20),
1908 ))),
1909 )));
1910
1911 let reduced = op.clone().reduce().unwrap();
1912
1913 assert!(op == reduced)
1914 }
1915}