Skip to main content

tx3_tir/model/
v1beta0.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;
13
14use crate::{
15    encoding::{TirRoot, TirVersion},
16    model::core::*,
17    Node, Visitor,
18};
19
20pub const IR_VERSION: &str = "v1beta0";
21
22#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
23pub struct StructExpr {
24    pub constructor: usize,
25    pub fields: Vec<Expression>,
26}
27
28impl StructExpr {
29    pub fn unit() -> Self {
30        Self {
31            constructor: 0,
32            fields: vec![],
33        }
34    }
35}
36
37#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
38pub enum Coerce {
39    NoOp(Expression),
40    IntoAssets(Expression),
41    IntoDatum(Expression),
42    IntoScript(Expression),
43}
44
45/// Operations that are executed during the "apply" phase.
46///
47/// These are operations that are executed during the "apply" phase, as opposed
48/// to the compiler operations that are executed during the "compile" phase.
49///
50/// These ops can be executed (aka "reduced") very early in the process. As long
51/// as they underlying expressions are "constant" (aka: don't rely on external
52/// data), the will be simplified directly during the "apply" phase.
53#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
54pub enum BuiltInOp {
55    NoOp(Expression),
56    Add(Expression, Expression),
57    Sub(Expression, Expression),
58    Concat(Expression, Expression),
59    Negate(Expression),
60    Property(Expression, Expression),
61    // end v1beta0 1st publish
62    Mul(Expression, Expression),
63    Div(Expression, Expression),
64}
65
66/// Operations that are performed by the compiler.
67///
68/// These are operations that are performed by the compiler, as opposed to the
69/// built-in operations that are executed (aka "reduced") during the "apply"
70/// phase.
71///
72/// These ops can't be executed earlier because they are either: chain-specific
73/// or rely on data that is only available to the compiler.
74#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
75pub enum CompilerOp {
76    BuildScriptAddress(Expression),
77    ComputeMinUtxo(Expression),
78    ComputeTipSlot,
79    ComputeSlotToTime(Expression),
80    ComputeTimeToSlot(Expression),
81}
82
83#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
84pub struct AssetExpr {
85    pub policy: Expression,
86    pub asset_name: Expression,
87    pub amount: Expression,
88}
89
90impl AssetExpr {
91    pub fn class_matches(&self, other: &Self) -> bool {
92        self.policy.as_bytes() == other.policy.as_bytes()
93            && self.asset_name.as_bytes() == other.asset_name.as_bytes()
94    }
95}
96
97/// An ad-hoc compile directive.
98///
99/// It's a generic, pass-through structure that the final chain-specific
100/// compiler can use to compile custom structures. Tx3 won't attempt to process
101/// this IR structure for anything other than trying to apply / reduce its
102/// expressions.
103#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
104pub struct AdHocDirective {
105    pub name: String,
106    pub data: HashMap<String, Expression>,
107}
108
109#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
110pub enum ScriptSource {
111    Embedded(Expression),
112    UtxoRef {
113        r#ref: Expression,
114        source: Option<Expression>,
115    },
116}
117
118impl ScriptSource {
119    pub fn new_ref(r#ref: Expression, source: Expression) -> Self {
120        Self::UtxoRef {
121            r#ref,
122            source: Some(source),
123        }
124    }
125
126    pub fn new_embedded(source: Expression) -> Self {
127        Self::Embedded(source)
128    }
129
130    pub fn expect_parameter(policy_name: String) -> Self {
131        Self::Embedded(
132            Param::ExpectValue(
133                format!("{}_script", policy_name.to_lowercase()),
134                Type::Bytes,
135            )
136            .into(),
137        )
138    }
139
140    pub fn expect_ref_input(policy_name: String, r#ref: Expression) -> Self {
141        Self::UtxoRef {
142            r#ref: r#ref.clone(),
143            source: Some(
144                Coerce::IntoScript(
145                    Param::ExpectInput(
146                        format!("{}_script", policy_name.to_lowercase()),
147                        InputQuery {
148                            address: Expression::None,
149                            min_amount: Expression::None,
150                            many: false,
151                            r#ref,
152                            collateral: false,
153                        },
154                    )
155                    .into(),
156                )
157                .into(),
158            ),
159        }
160    }
161
162    pub fn as_utxo_ref(&self) -> Option<Expression> {
163        match self {
164            Self::UtxoRef { r#ref, .. } => Some(r#ref.clone()),
165            Self::Embedded(Expression::UtxoRefs(x)) => Some(Expression::UtxoRefs(x.clone())),
166            _ => None,
167        }
168    }
169}
170
171#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
172pub struct PolicyExpr {
173    pub name: String,
174    pub hash: Expression,
175    pub script: ScriptSource,
176}
177
178#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
179pub enum Param {
180    Set(Expression),
181    ExpectValue(String, Type),
182    ExpectInput(String, InputQuery),
183    ExpectFees,
184}
185
186#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
187pub enum Expression {
188    #[default]
189    None,
190
191    List(Vec<Expression>),
192    Map(Vec<(Expression, Expression)>),
193    Tuple(Vec<Expression>),
194    Struct(StructExpr),
195    Bytes(Vec<u8>),
196    Number(i128),
197    Bool(bool),
198    String(String),
199    Address(Vec<u8>),
200    Hash(Vec<u8>),
201    UtxoRefs(Vec<UtxoRef>),
202    UtxoSet(UtxoSet),
203    Assets(Vec<AssetExpr>),
204
205    EvalParam(Box<Param>),
206    EvalBuiltIn(Box<BuiltInOp>),
207    EvalCompiler(Box<CompilerOp>),
208    EvalCoerce(Box<Coerce>),
209
210    // pass-through
211    AdHocDirective(Box<AdHocDirective>),
212}
213
214impl Expression {
215    pub fn is_none(&self) -> bool {
216        matches!(self, Self::None)
217    }
218
219    pub fn as_option(&self) -> Option<&Self> {
220        match self {
221            Self::None => None,
222            _ => Some(self),
223        }
224    }
225
226    pub fn into_option(self) -> Option<Self> {
227        match self {
228            Self::None => None,
229            _ => Some(self),
230        }
231    }
232
233    pub fn as_bytes(&self) -> Option<&[u8]> {
234        match self {
235            Self::Bytes(bytes) => Some(bytes),
236            Self::String(s) => Some(s.as_bytes()),
237            Self::Address(x) => Some(x),
238            Self::Hash(x) => Some(x),
239            _ => None,
240        }
241    }
242
243    pub fn as_number(&self) -> Option<i128> {
244        match self {
245            Self::Number(x) => Some(*x),
246            _ => None,
247        }
248    }
249
250    pub fn as_assets(&self) -> Option<&[AssetExpr]> {
251        match self {
252            Self::Assets(assets) => Some(assets),
253            _ => None,
254        }
255    }
256
257    pub fn as_utxo_refs(&self) -> Option<&[UtxoRef]> {
258        match self {
259            Self::UtxoRefs(refs) => Some(refs),
260            _ => None,
261        }
262    }
263}
264
265impl From<BuiltInOp> for Expression {
266    fn from(op: BuiltInOp) -> Self {
267        Self::EvalBuiltIn(Box::new(op))
268    }
269}
270
271impl From<CompilerOp> for Expression {
272    fn from(op: CompilerOp) -> Self {
273        Self::EvalCompiler(Box::new(op))
274    }
275}
276
277impl From<Coerce> for Expression {
278    fn from(coerce: Coerce) -> Self {
279        Self::EvalCoerce(Box::new(coerce))
280    }
281}
282
283impl From<Param> for Expression {
284    fn from(param: Param) -> Self {
285        Self::EvalParam(Box::new(param))
286    }
287}
288
289#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
290pub struct InputQuery {
291    pub address: Expression,
292    pub min_amount: Expression,
293    pub r#ref: Expression,
294    pub many: bool,
295    pub collateral: bool,
296}
297
298#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
299pub struct Input {
300    pub name: String,
301    pub utxos: Expression,
302    pub redeemer: Expression,
303}
304
305#[derive(Serialize, Deserialize, Debug, Clone)]
306pub struct Output {
307    pub address: Expression,
308    pub datum: Expression,
309    pub amount: Expression,
310    pub optional: bool,
311}
312
313#[derive(Serialize, Deserialize, Debug, Clone)]
314pub struct Validity {
315    pub since: Expression,
316    pub until: Expression,
317}
318
319#[derive(Serialize, Deserialize, Debug, Clone)]
320pub struct Mint {
321    pub amount: Expression,
322    pub redeemer: Expression,
323}
324
325#[derive(Serialize, Deserialize, Debug, Clone)]
326pub struct Collateral {
327    pub utxos: Expression,
328}
329
330#[derive(Serialize, Deserialize, Debug, Clone)]
331pub struct Metadata {
332    pub key: Expression,
333    pub value: Expression,
334}
335
336#[derive(Serialize, Deserialize, Debug, Clone)]
337pub struct Signers {
338    pub signers: Vec<Expression>,
339}
340
341#[derive(Serialize, Deserialize, Debug, Clone)]
342pub struct Tx {
343    pub fees: Expression,
344    pub references: Vec<Expression>,
345    pub inputs: Vec<Input>,
346    pub outputs: Vec<Output>,
347    pub validity: Option<Validity>,
348    pub mints: Vec<Mint>,
349    pub burns: Vec<Mint>,
350    pub adhoc: Vec<AdHocDirective>,
351    pub collateral: Vec<Collateral>,
352    pub signers: Option<Signers>,
353    pub metadata: Vec<Metadata>,
354}
355
356impl TirRoot for Tx {
357    const VERSION: TirVersion = TirVersion::V1Beta0;
358}
359
360impl<T: Node> Node for Option<T> {
361    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
362        self.map(|x| x.apply(visitor)).transpose()
363    }
364}
365
366impl<T: Node> Node for Box<T> {
367    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
368        let visited = (*self).apply(visitor)?;
369        Ok(Box::new(visited))
370    }
371}
372
373impl Node for (Expression, Expression) {
374    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
375        let (a, b) = self;
376        Ok((a.apply(visitor)?, b.apply(visitor)?))
377    }
378}
379
380impl<T: Node> Node for Vec<T> {
381    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
382        self.into_iter().map(|x| x.apply(visitor)).collect()
383    }
384}
385
386impl Node for StructExpr {
387    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
388        let visited = Self {
389            constructor: self.constructor,
390            fields: self.fields.apply(visitor)?,
391        };
392
393        Ok(visited)
394    }
395}
396
397impl Node for AssetExpr {
398    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
399        let visited = Self {
400            policy: self.policy.apply(visitor)?,
401            asset_name: self.asset_name.apply(visitor)?,
402            amount: self.amount.apply(visitor)?,
403        };
404
405        Ok(visited)
406    }
407}
408
409impl Node for InputQuery {
410    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
411        let visited = Self {
412            address: self.address.apply(visitor)?,
413            min_amount: self.min_amount.apply(visitor)?,
414            r#ref: self.r#ref.apply(visitor)?,
415            ..self
416        };
417
418        Ok(visited)
419    }
420}
421
422impl Node for Param {
423    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
424        let visited = match self {
425            Param::Set(x) => Param::Set(x.apply(visitor)?),
426            Param::ExpectValue(name, ty) => Param::ExpectValue(name, ty),
427            Param::ExpectInput(name, query) => Param::ExpectInput(name, query.apply(visitor)?),
428            Param::ExpectFees => Param::ExpectFees,
429        };
430
431        Ok(visited)
432    }
433}
434
435impl Node for BuiltInOp {
436    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
437        let visited = match self {
438            BuiltInOp::NoOp(x) => BuiltInOp::NoOp(x.apply(visitor)?),
439            BuiltInOp::Add(a, b) => BuiltInOp::Add(a.apply(visitor)?, b.apply(visitor)?),
440            BuiltInOp::Sub(a, b) => BuiltInOp::Sub(a.apply(visitor)?, b.apply(visitor)?),
441            BuiltInOp::Mul(a, b) => BuiltInOp::Mul(a.apply(visitor)?, b.apply(visitor)?),
442            BuiltInOp::Div(a, b) => BuiltInOp::Div(a.apply(visitor)?, b.apply(visitor)?),
443            BuiltInOp::Concat(a, b) => BuiltInOp::Concat(a.apply(visitor)?, b.apply(visitor)?),
444            BuiltInOp::Negate(x) => BuiltInOp::Negate(x.apply(visitor)?),
445            BuiltInOp::Property(x, i) => BuiltInOp::Property(x.apply(visitor)?, i),
446        };
447
448        Ok(visited)
449    }
450}
451
452impl Node for CompilerOp {
453    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
454        let visited = match self {
455            CompilerOp::BuildScriptAddress(x) => CompilerOp::BuildScriptAddress(x.apply(visitor)?),
456            CompilerOp::ComputeMinUtxo(x) => CompilerOp::ComputeMinUtxo(x.apply(visitor)?),
457            CompilerOp::ComputeTipSlot => CompilerOp::ComputeTipSlot,
458            CompilerOp::ComputeSlotToTime(x) => CompilerOp::ComputeSlotToTime(x.apply(visitor)?),
459            CompilerOp::ComputeTimeToSlot(x) => CompilerOp::ComputeTimeToSlot(x.apply(visitor)?),
460        };
461
462        Ok(visited)
463    }
464}
465
466impl Node for Coerce {
467    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
468        let visited = match self {
469            Coerce::NoOp(x) => Coerce::NoOp(x.apply(visitor)?),
470            Coerce::IntoAssets(x) => Coerce::IntoAssets(x.apply(visitor)?),
471            Coerce::IntoDatum(x) => Coerce::IntoDatum(x.apply(visitor)?),
472            Coerce::IntoScript(x) => Coerce::IntoScript(x.apply(visitor)?),
473        };
474
475        Ok(visited)
476    }
477}
478
479impl Node for Expression {
480    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
481        // first we visit the nested expressions
482        let visited = match self {
483            Expression::List(x) => Expression::List(x.apply(visitor)?),
484            Expression::Map(x) => Expression::Map(x.apply(visitor)?),
485            Expression::Tuple(x) => Expression::Tuple(x.apply(visitor)?),
486            Expression::Struct(x) => Expression::Struct(x.apply(visitor)?),
487            Expression::Assets(x) => Expression::Assets(x.apply(visitor)?),
488            Expression::EvalParam(x) => Expression::EvalParam(x.apply(visitor)?),
489            Expression::AdHocDirective(x) => Expression::AdHocDirective(x.apply(visitor)?),
490            Expression::EvalBuiltIn(x) => Expression::EvalBuiltIn(x.apply(visitor)?),
491            Expression::EvalCompiler(x) => Expression::EvalCompiler(x.apply(visitor)?),
492            Expression::EvalCoerce(x) => Expression::EvalCoerce(x.apply(visitor)?),
493
494            // leaf expressions don't need to be visited
495            Expression::Bytes(x) => Expression::Bytes(x),
496            Expression::None => Expression::None,
497            Expression::Number(x) => Expression::Number(x),
498            Expression::Bool(x) => Expression::Bool(x),
499            Expression::String(x) => Expression::String(x),
500            Expression::Address(x) => Expression::Address(x),
501            Expression::Hash(x) => Expression::Hash(x),
502            Expression::UtxoRefs(x) => Expression::UtxoRefs(x),
503            Expression::UtxoSet(x) => Expression::UtxoSet(x),
504        };
505
506        // then we reduce the visited expression
507        visitor.reduce(visited)
508    }
509}
510
511impl Node for Input {
512    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
513        let visited = Self {
514            utxos: self.utxos.apply(visitor)?,
515            redeemer: self.redeemer.apply(visitor)?,
516            ..self
517        };
518
519        Ok(visited)
520    }
521}
522
523impl Node for Output {
524    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
525        let visited = Self {
526            address: self.address.apply(visitor)?,
527            datum: self.datum.apply(visitor)?,
528            amount: self.amount.apply(visitor)?,
529            optional: self.optional,
530        };
531
532        Ok(visited)
533    }
534}
535
536impl Node for Validity {
537    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
538        let visited = Self {
539            since: self.since.apply(visitor)?,
540            until: self.until.apply(visitor)?,
541        };
542
543        Ok(visited)
544    }
545}
546
547impl Node for Mint {
548    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
549        let visited = Self {
550            amount: self.amount.apply(visitor)?,
551            redeemer: self.redeemer.apply(visitor)?,
552        };
553
554        Ok(visited)
555    }
556}
557
558impl Node for Collateral {
559    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
560        let visited = Self {
561            utxos: self.utxos.apply(visitor)?,
562        };
563
564        Ok(visited)
565    }
566}
567
568impl Node for Metadata {
569    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
570        let visited = Self {
571            key: self.key.apply(visitor)?,
572            value: self.value.apply(visitor)?,
573        };
574
575        Ok(visited)
576    }
577}
578
579impl Node for Signers {
580    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
581        let visited = Self {
582            signers: self.signers.apply(visitor)?,
583        };
584
585        Ok(visited)
586    }
587}
588
589impl Node for HashMap<String, Expression> {
590    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
591        let visited: Vec<_> = self
592            .into_iter()
593            .map(|(k, v)| visitor.reduce(v).map(|v| (k, v)))
594            .collect::<Result<_, _>>()?;
595
596        Ok(visited.into_iter().collect())
597    }
598}
599
600impl Node for AdHocDirective {
601    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
602        let visited = Self {
603            name: self.name,
604            data: self.data.apply(visitor)?,
605        };
606
607        Ok(visited)
608    }
609}
610
611impl Node for Tx {
612    fn apply<V: Visitor>(self, visitor: &mut V) -> Result<Self, crate::reduce::Error> {
613        let visited = Self {
614            fees: self.fees.apply(visitor)?,
615            references: self.references.apply(visitor)?,
616            inputs: self.inputs.apply(visitor)?,
617            outputs: self.outputs.apply(visitor)?,
618            validity: self.validity.apply(visitor)?,
619            mints: self.mints.apply(visitor)?,
620            burns: self.burns.apply(visitor)?,
621            adhoc: self.adhoc.apply(visitor)?,
622            collateral: self.collateral.apply(visitor)?,
623            signers: self.signers.apply(visitor)?,
624            metadata: self.metadata.apply(visitor)?,
625        };
626
627        Ok(visited)
628    }
629}