1use serde::{Deserialize, Serialize};
12use std::collections::{HashMap, HashSet};
13
14use crate::{Utxo, UtxoRef};
15
16pub const IR_VERSION: &str = "v1alpha8";
17
18#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
19pub struct StructExpr {
20 pub constructor: usize,
21 pub fields: Vec<Expression>,
22}
23
24impl StructExpr {
25 pub fn unit() -> Self {
26 Self {
27 constructor: 0,
28 fields: vec![],
29 }
30 }
31}
32
33#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
34pub enum Coerce {
35 NoOp(Expression),
36 IntoAssets(Expression),
37 IntoDatum(Expression),
38 IntoScript(Expression),
39}
40
41#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
50pub enum BuiltInOp {
51 NoOp(Expression),
52 Add(Expression, Expression),
53 Sub(Expression, Expression),
54 Concat(Expression, Expression),
55 Negate(Expression),
56 Property(Expression, Expression),
57}
58
59#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
68pub enum CompilerOp {
69 BuildScriptAddress(Expression),
70 ComputeMinUtxo(Expression),
71 ComputeTipSlot,
72 ComputeSlotToTime(Expression),
73 ComputeTimeToSlot(Expression),
74}
75
76#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
77pub struct AssetExpr {
78 pub policy: Expression,
79 pub asset_name: Expression,
80 pub amount: Expression,
81}
82
83impl AssetExpr {
84 pub fn class_matches(&self, other: &Self) -> bool {
85 self.policy.as_bytes() == other.policy.as_bytes()
86 && self.asset_name.as_bytes() == other.asset_name.as_bytes()
87 }
88}
89
90#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
97pub struct AdHocDirective {
98 pub name: String,
99 pub data: HashMap<String, Expression>,
100}
101
102#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
103pub enum ScriptSource {
104 Embedded(Expression),
105 UtxoRef {
106 r#ref: Expression,
107 source: Option<Expression>,
108 },
109}
110
111impl ScriptSource {
112 pub fn new_ref(r#ref: Expression, source: Expression) -> Self {
113 Self::UtxoRef {
114 r#ref,
115 source: Some(source),
116 }
117 }
118
119 pub fn new_embedded(source: Expression) -> Self {
120 Self::Embedded(source)
121 }
122
123 pub fn expect_parameter(policy_name: String) -> Self {
124 Self::Embedded(
125 Param::ExpectValue(
126 format!("{}_script", policy_name.to_lowercase()),
127 Type::Bytes,
128 )
129 .into(),
130 )
131 }
132
133 pub fn expect_ref_input(policy_name: String, r#ref: Expression) -> Self {
134 Self::UtxoRef {
135 r#ref: r#ref.clone(),
136 source: Some(
137 Coerce::IntoScript(
138 Param::ExpectInput(
139 format!("{}_script", policy_name.to_lowercase()),
140 InputQuery {
141 address: Expression::None,
142 min_amount: Expression::None,
143 many: false,
144 r#ref,
145 collateral: false,
146 },
147 )
148 .into(),
149 )
150 .into(),
151 ),
152 }
153 }
154
155 pub fn as_utxo_ref(&self) -> Option<Expression> {
156 match self {
157 Self::UtxoRef { r#ref, .. } => Some(r#ref.clone()),
158 Self::Embedded(Expression::UtxoRefs(x)) => Some(Expression::UtxoRefs(x.clone())),
159 _ => None,
160 }
161 }
162}
163
164#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
165pub struct PolicyExpr {
166 pub name: String,
167 pub hash: Expression,
168 pub script: ScriptSource,
169}
170
171#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
172pub enum Type {
173 Undefined,
174 Unit,
175 Int,
176 Bool,
177 Bytes,
178 Address,
179 Utxo,
180 UtxoRef,
181 AnyAsset,
182 List,
183 Map,
184 Custom(String),
185}
186
187#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
188pub enum Param {
189 Set(Expression),
190 ExpectValue(String, Type),
191 ExpectInput(String, InputQuery),
192 ExpectFees,
193}
194
195#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
196pub enum Expression {
197 #[default]
198 None,
199
200 List(Vec<Expression>),
201 Map(Vec<(Expression, Expression)>),
202 Tuple(Box<(Expression, Expression)>),
203 Struct(StructExpr),
204 Bytes(Vec<u8>),
205 Number(i128),
206 Bool(bool),
207 String(String),
208 Address(Vec<u8>),
209 Hash(Vec<u8>),
210 UtxoRefs(Vec<UtxoRef>),
211 UtxoSet(HashSet<Utxo>),
212 Assets(Vec<AssetExpr>),
213
214 EvalParam(Box<Param>),
215 EvalBuiltIn(Box<BuiltInOp>),
216 EvalCompiler(Box<CompilerOp>),
217 EvalCoerce(Box<Coerce>),
218
219 AdHocDirective(Box<AdHocDirective>),
221}
222
223impl Expression {
224 pub fn is_none(&self) -> bool {
225 matches!(self, Self::None)
226 }
227
228 pub fn as_option(&self) -> Option<&Self> {
229 match self {
230 Self::None => None,
231 _ => Some(self),
232 }
233 }
234
235 pub fn into_option(self) -> Option<Self> {
236 match self {
237 Self::None => None,
238 _ => Some(self),
239 }
240 }
241
242 pub fn as_bytes(&self) -> Option<&[u8]> {
243 match self {
244 Self::Bytes(bytes) => Some(bytes),
245 Self::String(s) => Some(s.as_bytes()),
246 Self::Address(x) => Some(x),
247 Self::Hash(x) => Some(x),
248 _ => None,
249 }
250 }
251
252 pub fn as_number(&self) -> Option<i128> {
253 match self {
254 Self::Number(x) => Some(*x),
255 _ => None,
256 }
257 }
258
259 pub fn as_assets(&self) -> Option<&[AssetExpr]> {
260 match self {
261 Self::Assets(assets) => Some(assets),
262 _ => None,
263 }
264 }
265
266 pub fn as_utxo_refs(&self) -> Option<&[UtxoRef]> {
267 match self {
268 Self::UtxoRefs(refs) => Some(refs),
269 _ => None,
270 }
271 }
272}
273
274impl From<BuiltInOp> for Expression {
275 fn from(op: BuiltInOp) -> Self {
276 Self::EvalBuiltIn(Box::new(op))
277 }
278}
279
280impl From<CompilerOp> for Expression {
281 fn from(op: CompilerOp) -> Self {
282 Self::EvalCompiler(Box::new(op))
283 }
284}
285
286impl From<Coerce> for Expression {
287 fn from(coerce: Coerce) -> Self {
288 Self::EvalCoerce(Box::new(coerce))
289 }
290}
291
292impl From<Param> for Expression {
293 fn from(param: Param) -> Self {
294 Self::EvalParam(Box::new(param))
295 }
296}
297
298#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
299pub struct InputQuery {
300 pub address: Expression,
301 pub min_amount: Expression,
302 pub r#ref: Expression,
303 pub many: bool,
304 pub collateral: bool,
305}
306
307#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
308pub struct Input {
309 pub name: String,
310 pub utxos: Expression,
311 pub redeemer: Expression,
312}
313
314#[derive(Serialize, Deserialize, Debug, Clone)]
315pub struct Output {
316 pub address: Expression,
317 pub datum: Expression,
318 pub amount: Expression,
319 pub optional: bool,
320}
321
322#[derive(Serialize, Deserialize, Debug, Clone)]
323pub struct Validity {
324 pub since: Expression,
325 pub until: Expression,
326}
327
328#[derive(Serialize, Deserialize, Debug, Clone)]
329pub struct Mint {
330 pub amount: Expression,
331 pub redeemer: Expression,
332}
333
334#[derive(Serialize, Deserialize, Debug, Clone)]
335pub struct Collateral {
336 pub utxos: Expression,
337}
338
339#[derive(Serialize, Deserialize, Debug, Clone)]
340pub struct Metadata {
341 pub key: Expression,
342 pub value: Expression,
343}
344
345#[derive(Serialize, Deserialize, Debug, Clone)]
346pub struct Signers {
347 pub signers: Vec<Expression>,
348}
349
350#[derive(Serialize, Deserialize, Debug, Clone)]
351pub struct Tx {
352 pub fees: Expression,
353 pub references: Vec<Expression>,
354 pub inputs: Vec<Input>,
355 pub outputs: Vec<Output>,
356 pub validity: Option<Validity>,
357 pub mints: Vec<Mint>,
358 pub burns: Vec<Mint>,
359 pub adhoc: Vec<AdHocDirective>,
360 pub collateral: Vec<Collateral>,
361 pub signers: Option<Signers>,
362 pub metadata: Vec<Metadata>,
363}
364
365pub trait Visitor {
366 fn reduce(&mut self, op: Expression) -> Result<Expression, crate::applying::Error>;
367}
368
369pub trait Node: Sized {
370 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error>;
371}
372
373impl<T: Node> Node for Option<T> {
374 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
375 self.map(|x| x.apply(visitor)).transpose()
376 }
377}
378
379impl<T: Node> Node for Box<T> {
380 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
381 let visited = (*self).apply(visitor)?;
382 Ok(Box::new(visited))
383 }
384}
385
386impl Node for (Expression, Expression) {
387 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
388 let (a, b) = self;
389 Ok((a.apply(visitor)?, b.apply(visitor)?))
390 }
391}
392
393impl<T: Node> Node for Vec<T> {
394 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
395 self.into_iter().map(|x| x.apply(visitor)).collect()
396 }
397}
398
399impl Node for StructExpr {
400 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
401 let visited = Self {
402 constructor: self.constructor,
403 fields: self.fields.apply(visitor)?,
404 };
405
406 Ok(visited)
407 }
408}
409
410impl Node for AssetExpr {
411 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
412 let visited = Self {
413 policy: self.policy.apply(visitor)?,
414 asset_name: self.asset_name.apply(visitor)?,
415 amount: self.amount.apply(visitor)?,
416 };
417
418 Ok(visited)
419 }
420}
421
422impl Node for InputQuery {
423 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
424 let visited = Self {
425 address: self.address.apply(visitor)?,
426 min_amount: self.min_amount.apply(visitor)?,
427 r#ref: self.r#ref.apply(visitor)?,
428 ..self
429 };
430
431 Ok(visited)
432 }
433}
434
435impl Node for Param {
436 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
437 let visited = match self {
438 Param::Set(x) => Param::Set(x.apply(visitor)?),
439 Param::ExpectValue(name, ty) => Param::ExpectValue(name, ty),
440 Param::ExpectInput(name, query) => Param::ExpectInput(name, query.apply(visitor)?),
441 Param::ExpectFees => Param::ExpectFees,
442 };
443
444 Ok(visited)
445 }
446}
447
448impl Node for BuiltInOp {
449 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
450 let visited = match self {
451 BuiltInOp::NoOp(x) => BuiltInOp::NoOp(x.apply(visitor)?),
452 BuiltInOp::Add(a, b) => BuiltInOp::Add(a.apply(visitor)?, b.apply(visitor)?),
453 BuiltInOp::Sub(a, b) => BuiltInOp::Sub(a.apply(visitor)?, b.apply(visitor)?),
454 BuiltInOp::Concat(a, b) => BuiltInOp::Concat(a.apply(visitor)?, b.apply(visitor)?),
455 BuiltInOp::Negate(x) => BuiltInOp::Negate(x.apply(visitor)?),
456 BuiltInOp::Property(x, i) => BuiltInOp::Property(x.apply(visitor)?, i),
457 };
458
459 Ok(visited)
460 }
461}
462
463impl Node for CompilerOp {
464 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
465 let visited = match self {
466 CompilerOp::BuildScriptAddress(x) => CompilerOp::BuildScriptAddress(x.apply(visitor)?),
467 CompilerOp::ComputeMinUtxo(x) => CompilerOp::ComputeMinUtxo(x.apply(visitor)?),
468 CompilerOp::ComputeTipSlot => CompilerOp::ComputeTipSlot,
469 CompilerOp::ComputeSlotToTime(x) => CompilerOp::ComputeSlotToTime(x.apply(visitor)?),
470 CompilerOp::ComputeTimeToSlot(x) => CompilerOp::ComputeTimeToSlot(x.apply(visitor)?),
471 };
472
473 Ok(visited)
474 }
475}
476
477impl Node for Coerce {
478 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
479 let visited = match self {
480 Coerce::NoOp(x) => Coerce::NoOp(x.apply(visitor)?),
481 Coerce::IntoAssets(x) => Coerce::IntoAssets(x.apply(visitor)?),
482 Coerce::IntoDatum(x) => Coerce::IntoDatum(x.apply(visitor)?),
483 Coerce::IntoScript(x) => Coerce::IntoScript(x.apply(visitor)?),
484 };
485
486 Ok(visited)
487 }
488}
489
490impl Node for Expression {
491 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
492 let visited = match self {
494 Expression::List(x) => Expression::List(x.apply(visitor)?),
495 Expression::Map(x) => Expression::Map(x.apply(visitor)?),
496 Expression::Tuple(x) => Expression::Tuple(x.apply(visitor)?),
497 Expression::Struct(x) => Expression::Struct(x.apply(visitor)?),
498 Expression::Assets(x) => Expression::Assets(x.apply(visitor)?),
499 Expression::EvalParam(x) => Expression::EvalParam(x.apply(visitor)?),
500 Expression::AdHocDirective(x) => Expression::AdHocDirective(x.apply(visitor)?),
501 Expression::EvalBuiltIn(x) => Expression::EvalBuiltIn(x.apply(visitor)?),
502 Expression::EvalCompiler(x) => Expression::EvalCompiler(x.apply(visitor)?),
503 Expression::EvalCoerce(x) => Expression::EvalCoerce(x.apply(visitor)?),
504
505 Expression::Bytes(x) => Expression::Bytes(x),
507 Expression::None => Expression::None,
508 Expression::Number(x) => Expression::Number(x),
509 Expression::Bool(x) => Expression::Bool(x),
510 Expression::String(x) => Expression::String(x),
511 Expression::Address(x) => Expression::Address(x),
512 Expression::Hash(x) => Expression::Hash(x),
513 Expression::UtxoRefs(x) => Expression::UtxoRefs(x),
514 Expression::UtxoSet(x) => Expression::UtxoSet(x),
515 };
516
517 visitor.reduce(visited)
519 }
520}
521
522impl Node for Input {
523 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
524 let visited = Self {
525 utxos: self.utxos.apply(visitor)?,
526 redeemer: self.redeemer.apply(visitor)?,
527 ..self
528 };
529
530 Ok(visited)
531 }
532}
533
534impl Node for Output {
535 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
536 let visited = Self {
537 address: self.address.apply(visitor)?,
538 datum: self.datum.apply(visitor)?,
539 amount: self.amount.apply(visitor)?,
540 optional: self.optional,
541 };
542
543 Ok(visited)
544 }
545}
546
547impl Node for Validity {
548 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
549 let visited = Self {
550 since: self.since.apply(visitor)?,
551 until: self.until.apply(visitor)?,
552 };
553
554 Ok(visited)
555 }
556}
557
558impl Node for Mint {
559 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
560 let visited = Self {
561 amount: self.amount.apply(visitor)?,
562 redeemer: self.redeemer.apply(visitor)?,
563 };
564
565 Ok(visited)
566 }
567}
568
569impl Node for Collateral {
570 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
571 let visited = Self {
572 utxos: self.utxos.apply(visitor)?,
573 };
574
575 Ok(visited)
576 }
577}
578
579impl Node for Metadata {
580 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
581 let visited = Self {
582 key: self.key.apply(visitor)?,
583 value: self.value.apply(visitor)?,
584 };
585
586 Ok(visited)
587 }
588}
589
590impl Node for Signers {
591 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
592 let visited = Self {
593 signers: self.signers.apply(visitor)?,
594 };
595
596 Ok(visited)
597 }
598}
599
600impl Node for HashMap<String, Expression> {
601 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
602 let visited: Vec<_> = self
603 .into_iter()
604 .map(|(k, v)| visitor.reduce(v).map(|v| (k, v)))
605 .collect::<Result<_, _>>()?;
606
607 Ok(visited.into_iter().collect())
608 }
609}
610
611impl Node for AdHocDirective {
612 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
613 let visited = Self {
614 name: self.name,
615 data: self.data.apply(visitor)?,
616 };
617
618 Ok(visited)
619 }
620}
621
622impl Node for Tx {
623 fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::applying::Error> {
624 let visited = Self {
625 fees: self.fees.apply(visitor)?,
626 references: self.references.apply(visitor)?,
627 inputs: self.inputs.apply(visitor)?,
628 outputs: self.outputs.apply(visitor)?,
629 validity: self.validity.apply(visitor)?,
630 mints: self.mints.apply(visitor)?,
631 burns: self.burns.apply(visitor)?,
632 adhoc: self.adhoc.apply(visitor)?,
633 collateral: self.collateral.apply(visitor)?,
634 signers: self.signers.apply(visitor)?,
635 metadata: self.metadata.apply(visitor)?,
636 };
637
638 Ok(visited)
639 }
640}
641
642#[derive(Debug, thiserror::Error, miette::Diagnostic)]
643pub enum Error {
644 #[error("Decoding error: {0}")]
645 Decoding(String),
646}
647
648pub fn to_vec(tx: &Tx) -> Vec<u8> {
649 let mut buffer = Vec::new();
650 ciborium::into_writer(tx, &mut buffer).unwrap(); buffer
652}
653
654pub fn from_bytes(bytes: &[u8]) -> Result<Tx, Error> {
655 let tx: Tx = ciborium::from_reader(bytes).map_err(|e| Error::Decoding(e.to_string()))?;
656 Ok(tx)
657}
658
659#[cfg(test)]
660mod tests {
661 use super::*;
662
663 const BACKWARDS_SUPPORTED_VERSIONS: &[&str] = &["v1alpha9"];
664
665 fn decode_version_snapshot(version: &str) {
666 let manifest_dir = env!("CARGO_MANIFEST_DIR");
667
668 let path = format!(
669 "{}/../../test_data/backwards/{version}.tir.hex",
670 manifest_dir
671 );
672
673 let bytes = std::fs::read_to_string(path).unwrap();
674 let bytes = hex::decode(bytes).unwrap();
675
676 _ = from_bytes(&bytes).unwrap();
678 }
679
680 #[test]
681 fn test_decoding_is_backward_compatible() {
682 for version in BACKWARDS_SUPPORTED_VERSIONS {
683 decode_version_snapshot(version);
684 }
685 }
686}