1use std::collections::HashMap;
2
3pub mod coercion;
4pub mod compile;
5pub mod ops;
6
7pub 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 eval = CompiledTx {
98 payload,
99 hash: hash.to_vec(),
100 fee: size_fees, 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}