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(Encode, Decode, 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(Encode, Decode, 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
183use std::collections::{HashMap, HashSet};
184
185pub use applying::{apply_args, apply_fees, apply_inputs, find_params, find_queries, reduce};
186use bincode::{Decode, Encode};
187use serde::{Deserialize, Serialize};
188
189#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
190pub struct ProtoTx {
191    ir: ir::Tx,
192    args: std::collections::BTreeMap<String, ArgValue>,
193    inputs: std::collections::BTreeMap<String, UtxoSet>,
194    fees: Option<u64>,
195}
196
197impl From<ir::Tx> for ProtoTx {
198    fn from(ir: ir::Tx) -> Self {
199        Self {
200            ir,
201            args: std::collections::BTreeMap::new(),
202            inputs: std::collections::BTreeMap::new(),
203            fees: None,
204        }
205    }
206}
207
208impl From<ProtoTx> for ir::Tx {
209    fn from(tx: ProtoTx) -> Self {
210        tx.ir
211    }
212}
213
214impl ProtoTx {
215    pub fn find_params(&self) -> std::collections::BTreeMap<String, ir::Type> {
216        find_params(&self.ir)
217    }
218
219    pub fn find_queries(&self) -> std::collections::BTreeMap<String, ir::InputQuery> {
220        find_queries(&self.ir)
221    }
222
223    pub fn set_arg(&mut self, name: &str, value: ArgValue) {
224        self.args.insert(name.to_lowercase().to_string(), value);
225    }
226
227    pub fn with_arg(mut self, name: &str, value: ArgValue) -> Self {
228        self.args.insert(name.to_lowercase().to_string(), value);
229        self
230    }
231
232    pub fn set_input(&mut self, name: &str, value: UtxoSet) {
233        self.inputs.insert(name.to_lowercase().to_string(), value);
234    }
235
236    pub fn set_fees(&mut self, value: u64) {
237        self.fees = Some(value);
238    }
239
240    pub fn apply(self) -> Result<Self, applying::Error> {
241        let tx = apply_args(self.ir, &self.args)?;
242
243        let tx = if let Some(fees) = self.fees {
244            apply_fees(tx, fees)?
245        } else {
246            tx
247        };
248
249        let tx = apply_inputs(tx, &self.inputs)?;
250
251        let tx = reduce(tx)?;
252
253        Ok(tx.into())
254    }
255
256    pub fn ir_bytes(&self) -> Vec<u8> {
257        let config = bincode::config::standard();
258        bincode::encode_to_vec(&self.ir, config).unwrap()
259    }
260
261    pub fn from_ir_bytes(bytes: &[u8]) -> Result<Self, bincode::error::DecodeError> {
262        let config = bincode::config::standard();
263        let (ir, _) = bincode::decode_from_slice::<ir::Tx, _>(bytes, config)?;
264        Ok(Self::from(ir))
265    }
266}
267
268impl AsRef<ir::Tx> for ProtoTx {
269    fn as_ref(&self) -> &ir::Tx {
270        &self.ir
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use std::collections::HashSet;
277
278    use super::*;
279
280    #[test]
281    fn happy_path() {
282        let manifest_dir = env!("CARGO_MANIFEST_DIR");
283        let code = format!("{manifest_dir}/../../examples/transfer.tx3");
284
285        let protocol = Protocol::from_file(&code)
286            .with_env_arg("sender", ArgValue::Address(b"sender".to_vec()))
287            .load()
288            .unwrap();
289
290        let tx = protocol.new_tx("transfer").unwrap();
291
292        dbg!(&tx.find_params());
293        dbg!(&tx.find_queries());
294
295        let mut tx = tx
296            .with_arg("quantity", ArgValue::Int(100_000_000))
297            .apply()
298            .unwrap();
299
300        dbg!(&tx.find_params());
301        dbg!(&tx.find_queries());
302
303        tx.set_input(
304            "source",
305            HashSet::from([Utxo {
306                r#ref: UtxoRef {
307                    txid: b"fafafafafafafafafafafafafafafafafafafafafafafafafafafafafafafafa"
308                        .to_vec(),
309                    index: 0,
310                },
311                address: b"abababa".to_vec(),
312                datum: None,
313                assets: CanonicalAssets::from_defined_asset(b"abababa", b"asset", 100),
314                script: Some(ir::Expression::Bytes(b"abce".to_vec())),
315            }]),
316        );
317
318        let tx = tx.apply().unwrap();
319
320        dbg!(&tx.find_params());
321        dbg!(&tx.find_queries());
322    }
323}