1pub 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
37pub 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 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}