tx3_lang/
ir.rs

1//! The Tx3 language intermediate representation (IR).
2//!
3//! This module defines the intermediate representation (IR) for the Tx3
4//! language. It provides the structure for representing Tx3 programs in a more
5//! abstract form, suitable for further processing or execution.
6//!
7//! This module is not intended to be used directly by end-users. See
8//! [`lower`](crate::lower) for lowering an AST to the intermediate
9//! representation.
10
11use 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/// Operations that are executed during the "apply" phase.
42///
43/// These are operations that are executed during the "apply" phase, as opposed
44/// to the compiler operations that are executed during the "compile" phase.
45///
46/// These ops can be executed (aka "reduced") very early in the process. As long
47/// as they underlying expressions are "constant" (aka: don't rely on external
48/// data), the will be simplified directly during the "apply" phase.
49#[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/// Operations that are performed by the compiler.
60///
61/// These are operations that are performed by the compiler, as opposed to the
62/// built-in operations that are executed (aka "reduced") during the "apply"
63/// phase.
64///
65/// These ops can't be executed earlier because they are either: chain-specific
66/// or rely on data that is only available to the compiler.
67#[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/// An ad-hoc compile directive.
91///
92/// It's a generic, pass-through structure that the final chain-specific
93/// compiler can use to compile custom structures. Tx3 won't attempt to process
94/// this IR structure for anything other than trying to apply / reduce its
95/// expressions.
96#[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    // pass-through
220    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        // first we visit the nested expressions
493        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            // leaf expressions don't need to be visited
506            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        // then we reduce the visited expression
518        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(); // infallible
651    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        // if we can decode it without error, the test passes
677        _ = 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}