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