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