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