miden_testing/mock_transaction/
builder.rs1use alloc::collections::{BTreeMap, BTreeSet};
5use alloc::sync::Arc;
6use alloc::vec::Vec;
7
8use anyhow::Context;
9use miden_processor::advice::AdviceInputs;
10use miden_processor::{Felt, Word};
11use miden_protocol::EMPTY_WORD;
12use miden_protocol::account::auth::{PublicKeyCommitment, Signature};
13use miden_protocol::account::{Account, AccountId};
14use miden_protocol::assembly::DefaultSourceManager;
15use miden_protocol::assembly::debuginfo::SourceManagerSync;
16use miden_protocol::block::BlockNumber;
17use miden_protocol::block::account_tree::AccountWitness;
18use miden_protocol::note::{Note, NoteId, NoteScript, NoteScriptRoot};
19use miden_protocol::transaction::{RawOutputNote, TransactionArgs, TransactionScript};
20use miden_standards::tx_script::SendNotesTransactionScript;
21use miden_tx::TransactionMastStore;
22use miden_tx::auth::BasicAuthenticator;
23
24use super::MockTransaction;
25use crate::MockChain;
26use crate::mock_chain::MockTransactionInput;
27
28#[derive(Clone)]
71pub struct MockTransactionBuilder<'chain> {
72 chain: &'chain MockChain,
73 input: MockTransactionInput,
74 reference_block: Option<BlockNumber>,
75 authenticated_notes: Vec<NoteId>,
76 unauthenticated_notes: Vec<Note>,
77 authenticator: Option<BasicAuthenticator>,
78 advice_inputs: AdviceInputs,
79 foreign_account_inputs: BTreeMap<AccountId, (Account, AccountWitness)>,
80 expected_output_notes: Vec<Note>,
81 tx_script: Option<TransactionScript>,
82 tx_script_args: Word,
83 auth_args: Word,
84 required_blocks: BTreeSet<BlockNumber>,
85 note_args: BTreeMap<NoteId, Word>,
86 signatures: Vec<(PublicKeyCommitment, Word, Signature)>,
87 note_scripts: BTreeMap<NoteScriptRoot, NoteScript>,
88 source_manager: Option<Arc<dyn SourceManagerSync>>,
89}
90
91impl<'chain> MockTransactionBuilder<'chain> {
92 pub(crate) fn new(chain: &'chain MockChain, input: impl Into<MockTransactionInput>) -> Self {
96 let input = input.into();
97 let authenticator = chain.account_authenticator(input.id());
101
102 Self {
103 chain,
104 input,
105 reference_block: None,
106 authenticated_notes: Vec::new(),
107 unauthenticated_notes: Vec::new(),
108 authenticator,
109 advice_inputs: AdviceInputs::default(),
110 foreign_account_inputs: BTreeMap::new(),
111 expected_output_notes: Vec::new(),
112 tx_script: None,
113 tx_script_args: EMPTY_WORD,
114 auth_args: EMPTY_WORD,
115 required_blocks: BTreeSet::new(),
116 note_args: BTreeMap::new(),
117 signatures: Vec::new(),
118 note_scripts: BTreeMap::new(),
119 source_manager: None,
120 }
121 }
122
123 pub fn authenticated_input_note(mut self, note_id: NoteId) -> Self {
128 self.authenticated_notes.push(note_id);
129 self
130 }
131
132 pub fn authenticated_input_notes(mut self, note_ids: impl IntoIterator<Item = NoteId>) -> Self {
136 self.authenticated_notes.extend(note_ids);
137 self
138 }
139
140 pub fn unauthenticated_input_note(mut self, note: Note) -> Self {
145 self.unauthenticated_notes.push(note);
146 self
147 }
148
149 pub fn unauthenticated_input_notes(mut self, notes: impl IntoIterator<Item = Note>) -> Self {
153 self.unauthenticated_notes.extend(notes);
154 self
155 }
156
157 pub fn reference_block(mut self, reference_block: impl Into<BlockNumber>) -> Self {
164 self.reference_block = Some(reference_block.into());
165 self
166 }
167
168 pub fn authenticator(mut self, authenticator: Option<BasicAuthenticator>) -> Self {
170 self.authenticator = authenticator;
171 self
172 }
173
174 pub fn extend_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
176 self.advice_inputs.extend(advice_inputs);
177 self
178 }
179
180 pub fn add_advice_map_entry(mut self, key: Word, value: Vec<Felt>) -> Self {
184 self.advice_inputs = self.advice_inputs.with_map([(key, value)]);
185 self
186 }
187
188 pub fn foreign_accounts(
190 mut self,
191 inputs: impl IntoIterator<Item = (Account, AccountWitness)>,
192 ) -> Self {
193 self.foreign_account_inputs.extend(
194 inputs.into_iter().map(|(account, witness)| (account.id(), (account, witness))),
195 );
196 self
197 }
198
199 pub fn tx_script(mut self, tx_script: TransactionScript) -> Self {
201 self.tx_script = Some(tx_script);
202 self
203 }
204
205 pub fn tx_script_args(mut self, tx_script_args: Word) -> Self {
207 self.tx_script_args = tx_script_args;
208 self
209 }
210
211 pub fn send_notes_script(self, script: &SendNotesTransactionScript) -> Self {
217 self.tx_script(script.tx_script().clone())
218 .tx_script_args(script.tx_script_args())
219 }
220
221 pub fn auth_args(mut self, auth_args: Word) -> Self {
223 self.auth_args = auth_args;
224 self
225 }
226
227 pub fn required_block(mut self, block_num: BlockNumber) -> Self {
233 self.required_blocks.insert(block_num);
234 self
235 }
236
237 pub fn extend_note_args(mut self, note_args: BTreeMap<NoteId, Word>) -> Self {
239 self.note_args.extend(note_args);
240 self
241 }
242
243 pub fn expected_output_note(mut self, output_note: RawOutputNote) -> Self {
248 if let RawOutputNote::Full(note) = output_note {
249 self.expected_output_notes.push(note);
250 }
251 self
252 }
253
254 pub fn expected_output_notes(mut self, output_notes: Vec<RawOutputNote>) -> Self {
258 let output_notes = output_notes.into_iter().filter_map(|note| match note {
259 RawOutputNote::Full(note) => Some(note),
260 RawOutputNote::Partial(_) => None,
261 });
262 self.expected_output_notes.extend(output_notes);
263 self
264 }
265
266 pub fn add_signature(
268 mut self,
269 pub_key: PublicKeyCommitment,
270 message: Word,
271 signature: Signature,
272 ) -> Self {
273 self.signatures.push((pub_key, message, signature));
274 self
275 }
276
277 pub fn add_note_script(mut self, script: NoteScript) -> Self {
279 self.note_scripts.insert(script.root(), script);
280 self
281 }
282
283 pub fn with_source_manager(mut self, source_manager: Arc<dyn SourceManagerSync>) -> Self {
288 self.source_manager = Some(source_manager);
289 self
290 }
291
292 pub fn build(self) -> anyhow::Result<MockTransaction> {
301 let account = self.chain.resolve_tx_account(self.input)?;
302
303 let latest_block = self.chain.latest_block_header().block_num();
304 let reference_block = self.reference_block.unwrap_or(latest_block);
305 anyhow::ensure!(
306 reference_block <= latest_block,
307 "reference block {reference_block} is out of range (latest {latest_block})",
308 );
309
310 let mut tx_inputs = self
311 .chain
312 .get_transaction_inputs_at(
313 reference_block,
314 &account,
315 &self.authenticated_notes,
316 &self.unauthenticated_notes,
317 self.required_blocks,
318 )
319 .context("failed to resolve transaction inputs from mock chain")?;
320
321 let mut tx_args = TransactionArgs::default().with_note_args(self.note_args);
322 if let Some(tx_script) = self.tx_script {
323 tx_args = tx_args.with_tx_script_and_args(tx_script, self.tx_script_args);
324 }
325 tx_args = tx_args.with_auth_args(self.auth_args);
326 tx_args.extend_advice_inputs(self.advice_inputs);
327 tx_args.extend_output_note_recipients(&self.expected_output_notes);
328 for (public_key_commitment, message, signature) in self.signatures {
329 tx_args.add_signature(public_key_commitment, message, signature);
330 }
331 tx_inputs.set_tx_args(tx_args);
332
333 let mast_store = TransactionMastStore::new();
334 mast_store.load_account_code(tx_inputs.account().code());
335 for (account, _) in self.foreign_account_inputs.values() {
336 mast_store.load_account_code(account.code());
337 }
338
339 let source_manager =
340 self.source_manager.unwrap_or_else(|| Arc::new(DefaultSourceManager::default()));
341
342 Ok(MockTransaction {
343 account,
344 expected_output_notes: self.expected_output_notes,
345 foreign_account_inputs: self.foreign_account_inputs,
346 tx_inputs,
347 mast_store,
348 authenticator: self.authenticator,
349 source_manager,
350 note_scripts: self.note_scripts,
351 })
352 }
353}