1pub mod analyzing;
28pub mod applying;
29pub mod ast;
30pub mod ir;
31pub mod loading;
32pub mod lowering;
33pub mod parsing;
34
35pub 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 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}