tx3_lang/
lib.rs

1//! The Tx3 language
2//!
3//! This crate provides the parser, analyzer and lowering logic for the Tx3
4//! language.
5//!
6//! # Parsing
7//!
8//! ```
9//! let program = tx3_lang::parsing::parse_string("tx swap() {}").unwrap();
10//! ```
11//!
12//! # Analyzing
13//!
14//! ```
15//! let mut program = tx3_lang::parsing::parse_string("tx swap() {}").unwrap();
16//! tx3_lang::analyzing::analyze(&mut program).ok().unwrap();
17//! ```
18//!
19//! # Lowering
20//!
21//! ```
22//! let mut program = tx3_lang::parsing::parse_string("tx swap() {}").unwrap();
23//! tx3_lang::analyzing::analyze(&mut program).ok().unwrap();
24//! let ir = tx3_lang::lowering::lower(&program, "swap").unwrap();
25//! ```
26
27pub mod analyzing;
28pub mod applying;
29pub mod assets;
30pub mod ast;
31pub mod backend;
32pub mod ir;
33pub mod loading;
34pub mod lowering;
35pub mod parsing;
36
37// chain specific
38pub mod cardano;
39
40#[macro_export]
41macro_rules! include_tx3_build {
42    ($package: tt) => {
43        include!(concat!(env!("OUT_DIR"), concat!("/", $package, ".rs")));
44    };
45}
46
47#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)]
48pub struct UtxoRef {
49    pub txid: Vec<u8>,
50    pub index: u32,
51}
52
53impl UtxoRef {
54    pub fn new(txid: &[u8], index: u32) -> Self {
55        Self {
56            txid: txid.to_vec(),
57            index,
58        }
59    }
60}
61
62impl std::fmt::Display for UtxoRef {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        write!(f, "{}#{}", hex::encode(&self.txid), self.index)
65    }
66}
67
68pub use assets::{AssetClass, AssetName, AssetPolicy, CanonicalAssets};
69
70#[derive(Serialize, Deserialize, Debug, Clone)]
71pub struct Utxo {
72    pub r#ref: UtxoRef,
73    pub address: Vec<u8>,
74    pub datum: Option<ir::Expression>,
75    pub assets: CanonicalAssets,
76    pub script: Option<ir::Expression>,
77}
78
79impl std::hash::Hash for Utxo {
80    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
81        self.r#ref.hash(state);
82    }
83}
84
85impl PartialEq for Utxo {
86    fn eq(&self, other: &Self) -> bool {
87        self.r#ref == other.r#ref
88    }
89}
90
91impl Eq for Utxo {}
92
93pub type UtxoSet = HashSet<Utxo>;
94
95#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
96pub enum ArgValue {
97    Int(i128),
98    Bool(bool),
99    String(String),
100    Bytes(Vec<u8>),
101    Address(Vec<u8>),
102    UtxoSet(UtxoSet),
103    UtxoRef(UtxoRef),
104}
105
106impl From<Vec<u8>> for ArgValue {
107    fn from(value: Vec<u8>) -> Self {
108        Self::Bytes(value)
109    }
110}
111
112impl From<String> for ArgValue {
113    fn from(value: String) -> Self {
114        Self::String(value)
115    }
116}
117
118impl From<&str> for ArgValue {
119    fn from(value: &str) -> Self {
120        Self::String(value.to_string())
121    }
122}
123
124impl From<bool> for ArgValue {
125    fn from(value: bool) -> Self {
126        Self::Bool(value)
127    }
128}
129
130macro_rules! impl_from_int_for_arg_value {
131    ($($t:ty),*) => {
132        $(
133            impl From<$t> for ArgValue {
134                fn from(value: $t) -> Self {
135                    Self::Int(value as i128)
136                }
137            }
138        )*
139    };
140}
141
142impl_from_int_for_arg_value!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
143
144pub struct Protocol {
145    pub(crate) ast: ast::Program,
146    pub(crate) env_args: std::collections::HashMap<String, ArgValue>,
147}
148
149impl Protocol {
150    pub fn from_file(path: impl AsRef<std::path::Path>) -> loading::ProtocolLoader {
151        loading::ProtocolLoader::from_file(path)
152    }
153
154    pub fn from_string(code: String) -> loading::ProtocolLoader {
155        loading::ProtocolLoader::from_string(code)
156    }
157
158    pub fn new_tx(&self, template: &str) -> Result<ProtoTx, lowering::Error> {
159        let ir = lowering::lower(&self.ast, template)?;
160        let mut tx = ProtoTx::from(ir);
161
162        if !self.env_args.is_empty() {
163            for (k, v) in &self.env_args {
164                tx.set_arg(k, v.clone());
165            }
166        }
167
168        // TODO: merge lower and apply errors?
169        let tx = tx.apply().unwrap();
170
171        Ok(tx)
172    }
173
174    pub fn ast(&self) -> &ast::Program {
175        &self.ast
176    }
177
178    pub fn txs(&self) -> impl Iterator<Item = &ast::TxDef> {
179        self.ast.txs.iter()
180    }
181}
182
183pub use applying::{apply_args, apply_fees, apply_inputs, find_params, find_queries, reduce};
184use serde::{Deserialize, Serialize};
185use std::collections::HashSet;
186
187#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
188pub struct ProtoTx {
189    ir: ir::Tx,
190    args: std::collections::BTreeMap<String, ArgValue>,
191    inputs: std::collections::BTreeMap<String, UtxoSet>,
192    fees: Option<u64>,
193}
194
195impl From<ir::Tx> for ProtoTx {
196    fn from(ir: ir::Tx) -> Self {
197        Self {
198            ir,
199            args: std::collections::BTreeMap::new(),
200            inputs: std::collections::BTreeMap::new(),
201            fees: None,
202        }
203    }
204}
205
206impl From<ProtoTx> for ir::Tx {
207    fn from(tx: ProtoTx) -> Self {
208        tx.ir
209    }
210}
211
212impl ProtoTx {
213    pub fn find_params(&self) -> std::collections::BTreeMap<String, ir::Type> {
214        find_params(&self.ir)
215    }
216
217    pub fn find_queries(&self) -> std::collections::BTreeMap<String, ir::InputQuery> {
218        find_queries(&self.ir)
219    }
220
221    pub fn set_arg(&mut self, name: &str, value: ArgValue) {
222        self.args.insert(name.to_lowercase().to_string(), value);
223    }
224
225    pub fn with_arg(mut self, name: &str, value: ArgValue) -> Self {
226        self.args.insert(name.to_lowercase().to_string(), value);
227        self
228    }
229
230    pub fn set_input(&mut self, name: &str, value: UtxoSet) {
231        self.inputs.insert(name.to_lowercase().to_string(), value);
232    }
233
234    pub fn set_fees(&mut self, value: u64) {
235        self.fees = Some(value);
236    }
237
238    pub fn apply(self) -> Result<Self, applying::Error> {
239        let tx = apply_args(self.ir, &self.args)?;
240
241        let tx = if let Some(fees) = self.fees {
242            apply_fees(tx, fees)?
243        } else {
244            tx
245        };
246
247        let tx = apply_inputs(tx, &self.inputs)?;
248
249        let tx = reduce(tx)?;
250
251        Ok(tx.into())
252    }
253
254    pub fn ir_bytes(&self) -> Vec<u8> {
255        ir::to_vec(&self.ir)
256    }
257
258    pub fn from_ir_bytes(bytes: &[u8]) -> Result<Self, ir::Error> {
259        let ir: ir::Tx = ir::from_bytes(bytes)?;
260        Ok(Self::from(ir))
261    }
262}
263
264impl AsRef<ir::Tx> for ProtoTx {
265    fn as_ref(&self) -> &ir::Tx {
266        &self.ir
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use std::collections::HashSet;
273
274    use super::*;
275
276    #[test]
277    fn happy_path() {
278        let manifest_dir = env!("CARGO_MANIFEST_DIR");
279        let code = format!("{manifest_dir}/../../examples/transfer.tx3");
280
281        let protocol = Protocol::from_file(&code)
282            .with_env_arg("sender", ArgValue::Address(b"sender".to_vec()))
283            .load()
284            .unwrap();
285
286        let tx = protocol.new_tx("transfer").unwrap();
287
288        dbg!(&tx.find_params());
289        dbg!(&tx.find_queries());
290
291        let mut tx = tx
292            .with_arg("quantity", ArgValue::Int(100_000_000))
293            .apply()
294            .unwrap();
295
296        dbg!(&tx.find_params());
297        dbg!(&tx.find_queries());
298
299        tx.set_input(
300            "source",
301            HashSet::from([Utxo {
302                r#ref: UtxoRef {
303                    txid: b"fafafafafafafafafafafafafafafafafafafafafafafafafafafafafafafafa"
304                        .to_vec(),
305                    index: 0,
306                },
307                address: b"abababa".to_vec(),
308                datum: None,
309                assets: CanonicalAssets::from_defined_asset(b"abababa", b"asset", 100),
310                script: Some(ir::Expression::Bytes(b"abce".to_vec())),
311            }]),
312        );
313
314        let tx = tx.apply().unwrap();
315
316        dbg!(&tx.find_params());
317        dbg!(&tx.find_queries());
318    }
319}