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