miden_testing/mock_transaction/
builder.rs1use alloc::collections::BTreeMap;
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 note_args: BTreeMap<NoteId, Word>,
85 signatures: Vec<(PublicKeyCommitment, Word, Signature)>,
86 note_scripts: BTreeMap<NoteScriptRoot, NoteScript>,
87 source_manager: Option<Arc<dyn SourceManagerSync>>,
88}
89
90impl<'chain> MockTransactionBuilder<'chain> {
91 pub(crate) fn new(chain: &'chain MockChain, input: impl Into<MockTransactionInput>) -> Self {
95 let input = input.into();
96 let authenticator = chain.account_authenticator(input.id());
100
101 Self {
102 chain,
103 input,
104 reference_block: None,
105 authenticated_notes: Vec::new(),
106 unauthenticated_notes: Vec::new(),
107 authenticator,
108 advice_inputs: AdviceInputs::default(),
109 foreign_account_inputs: BTreeMap::new(),
110 expected_output_notes: Vec::new(),
111 tx_script: None,
112 tx_script_args: EMPTY_WORD,
113 auth_args: EMPTY_WORD,
114 note_args: BTreeMap::new(),
115 signatures: Vec::new(),
116 note_scripts: BTreeMap::new(),
117 source_manager: None,
118 }
119 }
120
121 pub fn authenticated_input_note(mut self, note_id: NoteId) -> Self {
126 self.authenticated_notes.push(note_id);
127 self
128 }
129
130 pub fn authenticated_input_notes(mut self, note_ids: impl IntoIterator<Item = NoteId>) -> Self {
134 self.authenticated_notes.extend(note_ids);
135 self
136 }
137
138 pub fn unauthenticated_input_note(mut self, note: Note) -> Self {
143 self.unauthenticated_notes.push(note);
144 self
145 }
146
147 pub fn unauthenticated_input_notes(mut self, notes: impl IntoIterator<Item = Note>) -> Self {
151 self.unauthenticated_notes.extend(notes);
152 self
153 }
154
155 pub fn reference_block(mut self, reference_block: impl Into<BlockNumber>) -> Self {
162 self.reference_block = Some(reference_block.into());
163 self
164 }
165
166 pub fn authenticator(mut self, authenticator: Option<BasicAuthenticator>) -> Self {
168 self.authenticator = authenticator;
169 self
170 }
171
172 pub fn extend_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
174 self.advice_inputs.extend(advice_inputs);
175 self
176 }
177
178 pub fn add_advice_map_entry(mut self, key: Word, value: Vec<Felt>) -> Self {
182 self.advice_inputs.map.insert(key, value);
183 self
184 }
185
186 pub fn foreign_accounts(
188 mut self,
189 inputs: impl IntoIterator<Item = (Account, AccountWitness)>,
190 ) -> Self {
191 self.foreign_account_inputs.extend(
192 inputs.into_iter().map(|(account, witness)| (account.id(), (account, witness))),
193 );
194 self
195 }
196
197 pub fn tx_script(mut self, tx_script: TransactionScript) -> Self {
199 self.tx_script = Some(tx_script);
200 self
201 }
202
203 pub fn tx_script_args(mut self, tx_script_args: Word) -> Self {
205 self.tx_script_args = tx_script_args;
206 self
207 }
208
209 pub fn send_notes_script(self, script: &SendNotesTransactionScript) -> Self {
215 self.tx_script(script.tx_script().clone())
216 .tx_script_args(script.tx_script_args())
217 }
218
219 pub fn auth_args(mut self, auth_args: Word) -> Self {
221 self.auth_args = auth_args;
222 self
223 }
224
225 pub fn extend_note_args(mut self, note_args: BTreeMap<NoteId, Word>) -> Self {
227 self.note_args.extend(note_args);
228 self
229 }
230
231 pub fn expected_output_note(mut self, output_note: RawOutputNote) -> Self {
236 if let RawOutputNote::Full(note) = output_note {
237 self.expected_output_notes.push(note);
238 }
239 self
240 }
241
242 pub fn expected_output_notes(mut self, output_notes: Vec<RawOutputNote>) -> Self {
246 let output_notes = output_notes.into_iter().filter_map(|note| match note {
247 RawOutputNote::Full(note) => Some(note),
248 RawOutputNote::Partial(_) => None,
249 });
250 self.expected_output_notes.extend(output_notes);
251 self
252 }
253
254 pub fn add_signature(
256 mut self,
257 pub_key: PublicKeyCommitment,
258 message: Word,
259 signature: Signature,
260 ) -> Self {
261 self.signatures.push((pub_key, message, signature));
262 self
263 }
264
265 pub fn add_note_script(mut self, script: NoteScript) -> Self {
267 self.note_scripts.insert(script.root(), script);
268 self
269 }
270
271 pub fn with_source_manager(mut self, source_manager: Arc<dyn SourceManagerSync>) -> Self {
276 self.source_manager = Some(source_manager);
277 self
278 }
279
280 pub fn build(self) -> anyhow::Result<MockTransaction> {
289 let account = self.chain.resolve_tx_account(self.input)?;
290
291 let mut tx_inputs = match self.reference_block {
292 Some(reference_block) => {
293 let latest_block = self.chain.latest_block_header().block_num();
294 anyhow::ensure!(
295 reference_block <= latest_block,
296 "reference block {reference_block} is out of range (latest {latest_block})",
297 );
298
299 self.chain.get_transaction_inputs_at(
300 reference_block,
301 &account,
302 &self.authenticated_notes,
303 &self.unauthenticated_notes,
304 )
305 },
306 None => self.chain.get_transaction_inputs(
307 &account,
308 &self.authenticated_notes,
309 &self.unauthenticated_notes,
310 ),
311 }
312 .context("failed to resolve transaction inputs from mock chain")?;
313
314 let mut tx_args = TransactionArgs::default().with_note_args(self.note_args);
315 if let Some(tx_script) = self.tx_script {
316 tx_args = tx_args.with_tx_script_and_args(tx_script, self.tx_script_args);
317 }
318 tx_args = tx_args.with_auth_args(self.auth_args);
319 tx_args.extend_advice_inputs(self.advice_inputs);
320 tx_args.extend_output_note_recipients(&self.expected_output_notes);
321 for (public_key_commitment, message, signature) in self.signatures {
322 tx_args.add_signature(public_key_commitment, message, signature);
323 }
324 tx_inputs.set_tx_args(tx_args);
325
326 let mast_store = TransactionMastStore::new();
327 mast_store.load_account_code(tx_inputs.account().code());
328 for (account, _) in self.foreign_account_inputs.values() {
329 mast_store.load_account_code(account.code());
330 }
331
332 let source_manager =
333 self.source_manager.unwrap_or_else(|| Arc::new(DefaultSourceManager::default()));
334
335 Ok(MockTransaction {
336 account,
337 expected_output_notes: self.expected_output_notes,
338 foreign_account_inputs: self.foreign_account_inputs,
339 tx_inputs,
340 mast_store,
341 authenticator: self.authenticator,
342 source_manager,
343 note_scripts: self.note_scripts,
344 })
345 }
346}