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(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 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}