Skip to main content

tx3_cardano/
lib.rs

1use std::collections::HashMap;
2
3pub mod coercion;
4pub mod compile;
5pub mod ops;
6
7// Re-export pallas for upstream users
8pub use pallas;
9
10use pallas::ledger::primitives;
11use pallas::ledger::traverse::ComputeHash;
12use tx3_tir::compile::CompiledTx;
13use tx3_tir::compile::Error as CompileError;
14use tx3_tir::encoding::AnyTir;
15use tx3_tir::model::v1beta0 as tir;
16use tx3_tir::reduce::Error as ReduceError;
17
18#[cfg(test)]
19pub mod tests;
20
21pub type Network = pallas::ledger::primitives::NetworkId;
22pub type PlutusVersion = u8;
23pub type CostModel = Vec<i64>;
24
25#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
26pub struct PParams {
27    pub network: Network,
28    pub min_fee_coefficient: u64,
29    pub min_fee_constant: u64,
30    pub coins_per_utxo_byte: u64,
31    pub cost_models: HashMap<PlutusVersion, CostModel>,
32}
33
34pub const EXECUTION_UNITS: primitives::ExUnits = primitives::ExUnits {
35    mem: 2000000,
36    steps: 2000000000,
37};
38
39const DEFAULT_EXTRA_FEES: u64 = 200_000;
40const MIN_UTXO_BYTES: i128 = 197;
41
42#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
43pub struct Config {
44    pub extra_fees: Option<u64>,
45}
46
47pub type TxBody =
48    pallas::codec::utils::KeepRaw<'static, primitives::conway::TransactionBody<'static>>;
49
50#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
51pub struct ChainPoint {
52    pub slot: u64,
53    pub hash: Vec<u8>,
54    pub timestamp: u128,
55}
56
57#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
58pub struct Compiler {
59    pub pparams: PParams,
60    pub config: Config,
61    #[serde(skip)]
62    pub latest_tx_body: Option<TxBody>,
63    pub cursor: ChainPoint,
64}
65
66impl Compiler {
67    pub fn new(pparams: PParams, config: Config, cursor: ChainPoint) -> Self {
68        Self {
69            pparams,
70            config,
71            latest_tx_body: None,
72            cursor,
73        }
74    }
75}
76
77impl tx3_tir::compile::Compiler for Compiler {
78    type CompilerOp = tir::CompilerOp;
79    type Expression = tir::Expression;
80
81    fn compile(&mut self, tir: &AnyTir) -> Result<CompiledTx, CompileError> {
82        let AnyTir::V1Beta0(tx) = tir else {
83            return Err(CompileError::UnsupportedTirVersion(tir.version()));
84        };
85
86        let compiled_tx = compile::entry_point(tx, &self.pparams)?;
87
88        let hash = compiled_tx.transaction_body.compute_hash();
89        let payload = pallas::codec::minicbor::to_vec(&compiled_tx).unwrap();
90
91        self.latest_tx_body = Some(compiled_tx.transaction_body);
92
93        let size_fees = ops::eval_size_fees(&payload, &self.pparams, self.config.extra_fees);
94
95        //let redeemer_fees = eval_redeemer_fees(tx, pparams)?;
96
97        let eval = CompiledTx {
98            payload,
99            hash: hash.to_vec(),
100            fee: size_fees, // TODO: add redeemer fees
101            ex_units: 0,
102        };
103
104        Ok(eval)
105    }
106
107    fn reduce_op(&self, op: Self::CompilerOp) -> Result<Self::Expression, ReduceError> {
108        match op {
109            tir::CompilerOp::BuildScriptAddress(x) => {
110                let hash: primitives::Hash<28> = coercion::expr_into_hash(&x)?;
111                let address = coercion::policy_into_address(hash.as_ref(), self.pparams.network)?;
112                Ok(tir::Expression::Address(address.to_vec()))
113            }
114            tir::CompilerOp::ComputeMinUtxo(x) => {
115                let lovelace = ops::compute_min_utxo(
116                    x,
117                    &self.latest_tx_body,
118                    self.pparams.coins_per_utxo_byte as i128,
119                )?;
120
121                Ok(tir::Expression::Assets(vec![tir::AssetExpr {
122                    policy: tir::Expression::None,
123                    asset_name: tir::Expression::None,
124                    amount: tir::Expression::Number(lovelace),
125                }]))
126            }
127            tir::CompilerOp::ComputeTipSlot => {
128                Ok(tir::Expression::Number(self.cursor.slot as i128))
129            }
130            tir::CompilerOp::ComputeSlotToTime(x) => {
131                let slot = coercion::expr_into_number(&x)?;
132
133                if slot < 0 {
134                    return Err(CompileError::CoerceError(
135                        format!("{}", slot),
136                        "positive slot number".to_string(),
137                    )
138                    .into());
139                }
140
141                Ok(tir::Expression::Number(ops::slot_to_time(
142                    slot,
143                    &self.cursor,
144                )))
145            }
146            tir::CompilerOp::ComputeTimeToSlot(x) => {
147                let time = coercion::expr_into_number(&x)?;
148                if time < 0 {
149                    return Err(CompileError::CoerceError(
150                        format!("{}", time),
151                        "positive timestamp".to_string(),
152                    )
153                    .into());
154                }
155
156                Ok(tir::Expression::Number(ops::time_to_slot(
157                    time,
158                    &self.cursor,
159                )))
160            }
161        }
162    }
163}