balius_sdk/txbuilder/
build.rs1use pallas_traverse::MultiEraValue;
2use std::sync::Arc;
3use std::{collections::HashMap, ops::Deref as _};
4
5use super::{
6 asset_math, primitives, BuildContext, BuildError, Ledger, PParams, TxExpr, TxoRef, UtxoPattern,
7 UtxoSet,
8};
9
10impl BuildContext {
11 pub fn mint_redeemer_index(&self, policy: primitives::ScriptHash) -> Result<u32, BuildError> {
12 if let Some(tx_body) = &self.tx_body {
13 let mut out: Vec<_> = tx_body
14 .mint
15 .iter()
16 .flat_map(|x| x.iter())
17 .map(|(p, _)| *p)
18 .collect();
19
20 out.sort();
21 out.dedup();
22
23 if let Some(index) = out.iter().position(|p| *p == policy) {
24 return Ok(index as u32);
25 }
26 }
27
28 Err(BuildError::RedeemerTargetMissing)
29 }
30
31 pub fn eval_ex_units(
32 &self,
33 _script: primitives::ScriptHash,
34 _data: &primitives::PlutusData,
35 ) -> primitives::ExUnits {
36 primitives::ExUnits { mem: 8, steps: 8 }
38 }
39}
40
41pub(crate) struct ExtLedgerFacade;
42
43impl crate::txbuilder::Ledger for ExtLedgerFacade {
44 fn read_utxos(&self, refs: &[TxoRef]) -> Result<UtxoSet, BuildError> {
45 let refs: Vec<_> = refs.iter().cloned().map(Into::into).collect();
46 let x = crate::wit::balius::app::ledger::read_utxos(&refs)?;
47
48 let x: Vec<_> = x
49 .into_iter()
50 .map(|x| (TxoRef::from(x.ref_), x.body.to_vec()))
51 .collect();
52
53 Ok(UtxoSet::from_iter(x))
54 }
55
56 fn search_utxos(&self, pattern: &UtxoPattern) -> Result<UtxoSet, BuildError> {
57 let pattern = pattern.clone().into();
58 let mut utxos = HashMap::new();
59 let max_items = 32;
60 let mut utxo_page = Some(crate::wit::balius::app::ledger::search_utxos(
61 &pattern, None, max_items,
62 )?);
63 while let Some(page) = utxo_page.take() {
64 for utxo in page.utxos {
65 utxos.insert(utxo.ref_.into(), utxo.body);
66 }
67 if let Some(next) = page.next_token {
68 utxo_page = Some(crate::wit::balius::app::ledger::search_utxos(
69 &pattern,
70 Some(&next),
71 max_items,
72 )?);
73 }
74 }
75 Ok(utxos.into())
76 }
77
78 fn read_params(&self) -> Result<PParams, BuildError> {
79 let bytes = crate::wit::balius::app::ledger::read_params()?;
80
81 serde_json::from_slice(&bytes)
82 .map_err(|_| BuildError::LedgerError("failed to parse params json".to_string()))
83 }
84}
85
86pub fn build<T, L>(mut tx: T, ledger: L) -> Result<primitives::Tx, BuildError>
87where
88 T: TxExpr,
89 L: Ledger + 'static,
90{
91 let mut ctx = BuildContext {
92 network: primitives::NetworkId::Testnet,
93 pparams: ledger.read_params()?,
94 total_input: primitives::Value::Coin(0),
95 spent_output: primitives::Value::Coin(0),
96 estimated_fee: 0,
97 ledger: Arc::new(Box::new(ledger)),
98 tx_body: None,
99 parent_output: None,
100 };
101
102 let body = tx.eval_body(&ctx)?;
105
106 let input_refs: Vec<_> = body
107 .inputs
108 .iter()
109 .map(|i| TxoRef {
110 hash: i.transaction_id,
111 index: i.index,
112 })
113 .collect();
114 let utxos = ctx.ledger.read_utxos(&input_refs)?;
115 ctx.total_input =
116 asset_math::aggregate_values(utxos.txos().map(|txo| input_into_conway(&txo.value())));
117 if let Some(mint) = &body.mint {
118 ctx.total_input = asset_math::add_mint(&ctx.total_input, mint)?;
119 }
120 ctx.spent_output = asset_math::aggregate_values(body.outputs.iter().map(output_into_conway));
121 ctx.estimated_fee = 2_000_000;
123
124 let body = tx.eval_body(&ctx)?;
126 ctx.tx_body = Some(body);
127 for _ in 0..3 {
128 let body = tx.eval_body(&ctx)?;
129 ctx.tx_body = Some(body);
130 }
131
132 let wit = tx.eval_witness_set(&ctx).unwrap();
133
134 let tx = primitives::Tx {
135 transaction_body: ctx.tx_body.take().unwrap(),
136 transaction_witness_set: wit,
137 auxiliary_data: pallas_codec::utils::Nullable::Null,
138 success: true,
139 };
140
141 Ok(tx)
142}
143
144fn input_into_conway(value: &MultiEraValue) -> primitives::Value {
147 use pallas_primitives::{alonzo, conway};
148 match value {
149 MultiEraValue::Byron(x) => conway::Value::Coin(*x),
150 MultiEraValue::AlonzoCompatible(x) => match x.deref() {
151 alonzo::Value::Coin(x) => conway::Value::Coin(*x),
152 alonzo::Value::Multiasset(x, assets) => {
153 let coin = *x;
154 let assets = assets
155 .iter()
156 .filter_map(|(k, v)| {
157 let v: Vec<(conway::Bytes, conway::PositiveCoin)> = v
158 .iter()
159 .filter_map(|(k, v)| Some((k.clone(), (*v).try_into().ok()?)))
160 .collect();
161 Some((*k, conway::NonEmptyKeyValuePairs::from_vec(v)?))
162 })
163 .collect();
164 if let Some(assets) = conway::NonEmptyKeyValuePairs::from_vec(assets) {
165 conway::Value::Multiasset(coin, assets)
166 } else {
167 conway::Value::Coin(coin)
168 }
169 }
170 },
171 MultiEraValue::Conway(x) => x.deref().clone(),
172 _ => panic!("unrecognized value"),
173 }
174}
175
176fn output_into_conway(output: &primitives::TransactionOutput) -> primitives::Value {
177 use pallas_primitives::{alonzo, conway};
178 match output {
179 primitives::TransactionOutput::Legacy(o) => match &o.amount {
180 alonzo::Value::Coin(c) => primitives::Value::Coin(*c),
181 alonzo::Value::Multiasset(c, assets) => {
182 let assets = assets
183 .iter()
184 .filter_map(|(k, v)| {
185 let v: Vec<(conway::Bytes, conway::PositiveCoin)> = v
186 .iter()
187 .filter_map(|(k, v)| Some((k.clone(), (*v).try_into().ok()?)))
188 .collect();
189 Some((*k, conway::NonEmptyKeyValuePairs::from_vec(v)?))
190 })
191 .collect();
192 if let Some(assets) = conway::NonEmptyKeyValuePairs::from_vec(assets) {
193 primitives::Value::Multiasset(*c, assets)
194 } else {
195 primitives::Value::Coin(*c)
196 }
197 }
198 },
199 primitives::TransactionOutput::PostAlonzo(o) => o.value.clone(),
200 }
201}