miden_objects/transaction/tx_args.rs
1use alloc::{
2 collections::{BTreeMap, BTreeSet},
3 sync::Arc,
4 vec::Vec,
5};
6
7use assembly::{Assembler, Compile};
8use miden_crypto::merkle::InnerNodeInfo;
9
10use super::{AccountInputs, Digest, Felt, Word};
11use crate::{
12 EMPTY_WORD, MastForest, MastNodeId, TransactionScriptError,
13 note::{NoteId, NoteRecipient},
14 utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
15 vm::{AdviceInputs, AdviceMap, Program},
16};
17
18// TRANSACTION ARGUMENTS
19// ================================================================================================
20
21/// Optional transaction arguments.
22///
23/// - Transaction script: a program that is executed in a transaction after all input notes scripts
24/// have been executed.
25/// - Transaction script argument: a [`Word`], which will be pushed to the operand stack before the
26/// transaction script execution. If this argument is not specified, the [`EMPTY_WORD`] would be
27/// used as a default value. If the [AdviceInputs] are propagated with some user defined map
28/// entries, this script argument could be used as a key to access the corresponding value.
29/// - Note arguments: data put onto the stack right before a note script is executed. These are
30/// different from note inputs, as the user executing the transaction can specify arbitrary note
31/// args.
32/// - Advice inputs: provides data needed by the runtime, like the details of public output notes.
33/// - Foreign account inputs: provides foreign account data that will be used during the foreign
34/// procedure invocation (FPI).
35#[derive(Clone, Debug, Default, PartialEq, Eq)]
36pub struct TransactionArgs {
37 tx_script: Option<TransactionScript>,
38 tx_script_arg: Word,
39 note_args: BTreeMap<NoteId, Word>,
40 advice_inputs: AdviceInputs,
41 foreign_account_inputs: Vec<AccountInputs>,
42}
43
44impl TransactionArgs {
45 // CONSTRUCTORS
46 // --------------------------------------------------------------------------------------------
47
48 /// Returns new [TransactionArgs] instantiated with the provided transaction script, advice
49 /// map and foreign account inputs.
50 pub fn new(advice_map: AdviceMap, foreign_account_inputs: Vec<AccountInputs>) -> Self {
51 Self {
52 tx_script: None,
53 tx_script_arg: EMPTY_WORD,
54 note_args: Default::default(),
55 advice_inputs: AdviceInputs::default().with_map(advice_map),
56 foreign_account_inputs,
57 }
58 }
59
60 /// Returns new [TransactionArgs] instantiated with the provided transaction script.
61 ///
62 /// If the transaction script is already set, it will be overwritten with the newly provided
63 /// one.
64 #[must_use]
65 pub fn with_tx_script(mut self, tx_script: TransactionScript) -> Self {
66 self.tx_script = Some(tx_script);
67 self
68 }
69
70 /// Returns new [TransactionArgs] instantiated with the provided transaction script and its
71 /// argument.
72 ///
73 /// If the transaction script and argument are already set, they will be overwritten with the
74 /// newly provided ones.
75 #[must_use]
76 pub fn with_tx_script_and_arg(
77 mut self,
78 tx_script: TransactionScript,
79 tx_script_arg: Word,
80 ) -> Self {
81 self.tx_script = Some(tx_script);
82 self.tx_script_arg = tx_script_arg;
83 self
84 }
85
86 /// Returns new [TransactionArgs] instantiated with the provided note arguments.
87 ///
88 /// If the note arguments were already set, they will be overwritten with the newly provided
89 /// ones.
90 #[must_use]
91 pub fn with_note_args(mut self, note_args: BTreeMap<NoteId, Word>) -> Self {
92 self.note_args = note_args;
93 self
94 }
95
96 // PUBLIC ACCESSORS
97 // --------------------------------------------------------------------------------------------
98
99 /// Returns a reference to the transaction script.
100 pub fn tx_script(&self) -> Option<&TransactionScript> {
101 self.tx_script.as_ref()
102 }
103
104 /// Returns the transaction script argument, or [`EMPTY_WORD`] if the argument was not
105 /// specified.
106 ///
107 /// This argument could be potentially used as a key to access the advice map during the
108 /// transaction script execution. Notice that the corresponding map entry should be provided
109 /// separately during the creation with the [`TransactionArgs::new`] or using the
110 /// [`TransactionArgs::extend_advice_map`] method.
111 pub fn tx_script_arg(&self) -> Word {
112 self.tx_script_arg
113 }
114
115 /// Returns a reference to a specific note argument.
116 pub fn get_note_args(&self, note_id: NoteId) -> Option<&Word> {
117 self.note_args.get(¬e_id)
118 }
119
120 /// Returns a reference to the internal [AdviceInputs].
121 pub fn advice_inputs(&self) -> &AdviceInputs {
122 &self.advice_inputs
123 }
124
125 /// Returns a reference to the foreign account inputs in the transaction arguments.
126 pub fn foreign_account_inputs(&self) -> &[AccountInputs] {
127 &self.foreign_account_inputs
128 }
129
130 /// Collects and returns a set containing all code commitments from foreign accounts.
131 pub fn foreign_account_code_commitments(&self) -> BTreeSet<Digest> {
132 self.foreign_account_inputs()
133 .iter()
134 .map(|acc| acc.code().commitment())
135 .collect()
136 }
137
138 // STATE MUTATORS
139 // --------------------------------------------------------------------------------------------
140
141 /// Populates the advice inputs with the expected recipient data for creating output notes.
142 ///
143 /// The advice inputs' map is extended with the following keys:
144 ///
145 /// - recipient_digest |-> recipient details (inputs_hash, script_root, serial_num).
146 /// - inputs_commitment |-> inputs.
147 /// - script_root |-> script.
148 pub fn add_output_note_recipient<T: AsRef<NoteRecipient>>(&mut self, note_recipient: T) {
149 let note_recipient = note_recipient.as_ref();
150 let inputs = note_recipient.inputs();
151 let script = note_recipient.script();
152 let script_encoded: Vec<Felt> = script.into();
153
154 let new_elements = [
155 (note_recipient.digest(), note_recipient.format_for_advice()),
156 (inputs.commitment(), inputs.format_for_advice()),
157 (script.root(), script_encoded),
158 ];
159
160 self.advice_inputs.extend_map(new_elements);
161 }
162
163 /// Populates the advice inputs with the specified note recipient details.
164 ///
165 /// The advice inputs' map is extended with the following keys:
166 ///
167 /// - recipient |-> recipient details (inputs_hash, script_root, serial_num).
168 /// - inputs_commitment |-> inputs.
169 /// - script_root |-> script.
170 pub fn extend_output_note_recipients<T, L>(&mut self, notes: L)
171 where
172 L: IntoIterator<Item = T>,
173 T: AsRef<NoteRecipient>,
174 {
175 for note in notes {
176 self.add_output_note_recipient(note);
177 }
178 }
179
180 /// Extends the internal advice inputs' map with the provided key-value pairs.
181 pub fn extend_advice_map<T: IntoIterator<Item = (Digest, Vec<Felt>)>>(&mut self, iter: T) {
182 self.advice_inputs.extend_map(iter)
183 }
184
185 /// Extends the internal advice inputs' merkle store with the provided nodes.
186 pub fn extend_merkle_store<I: Iterator<Item = InnerNodeInfo>>(&mut self, iter: I) {
187 self.advice_inputs.extend_merkle_store(iter)
188 }
189
190 /// Extends the advice inputs in self with the provided ones.
191 #[cfg(feature = "testing")]
192 pub fn extend_advice_inputs(&mut self, advice_inputs: AdviceInputs) {
193 self.advice_inputs.extend(advice_inputs);
194 }
195}
196
197impl Serializable for TransactionArgs {
198 fn write_into<W: ByteWriter>(&self, target: &mut W) {
199 self.tx_script.write_into(target);
200 self.tx_script_arg.write_into(target);
201 self.note_args.write_into(target);
202 self.advice_inputs.write_into(target);
203 self.foreign_account_inputs.write_into(target);
204 }
205}
206
207impl Deserializable for TransactionArgs {
208 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
209 let tx_script = Option::<TransactionScript>::read_from(source)?;
210 let tx_script_arg = Word::read_from(source)?;
211 let note_args = BTreeMap::<NoteId, Word>::read_from(source)?;
212 let advice_inputs = AdviceInputs::read_from(source)?;
213 let foreign_account_inputs = Vec::<AccountInputs>::read_from(source)?;
214
215 Ok(Self {
216 tx_script,
217 tx_script_arg,
218 note_args,
219 advice_inputs,
220 foreign_account_inputs,
221 })
222 }
223}
224
225// TRANSACTION SCRIPT
226// ================================================================================================
227
228/// Transaction script.
229///
230/// A transaction script is a program that is executed in a transaction after all input notes
231/// have been executed.
232///
233/// The [TransactionScript] object is composed of an executable program defined by a [MastForest]
234/// and an associated entrypoint.
235#[derive(Clone, Debug, PartialEq, Eq)]
236pub struct TransactionScript {
237 mast: Arc<MastForest>,
238 entrypoint: MastNodeId,
239}
240
241impl TransactionScript {
242 // CONSTRUCTORS
243 // --------------------------------------------------------------------------------------------
244
245 /// Returns a new [TransactionScript] instantiated with the provided code.
246 pub fn new(code: Program) -> Self {
247 Self::from_parts(code.mast_forest().clone(), code.entrypoint())
248 }
249
250 /// Returns a new [TransactionScript] compiled from the provided source code using the specified
251 /// assembler.
252 ///
253 /// # Errors
254 /// Returns an error if the compilation of the provided source code fails.
255 pub fn compile(
256 source_code: impl Compile,
257 assembler: Assembler,
258 ) -> Result<Self, TransactionScriptError> {
259 let program = assembler
260 .assemble_program(source_code)
261 .map_err(TransactionScriptError::AssemblyError)?;
262 Ok(Self::new(program))
263 }
264
265 /// Returns a new [TransactionScript] instantiated from the provided MAST forest and entrypoint.
266 ///
267 /// # Panics
268 /// Panics if the specified entrypoint is not in the provided MAST forest.
269 pub fn from_parts(mast: Arc<MastForest>, entrypoint: MastNodeId) -> Self {
270 assert!(mast.get_node_by_id(entrypoint).is_some());
271
272 Self { mast, entrypoint }
273 }
274
275 // PUBLIC ACCESSORS
276 // --------------------------------------------------------------------------------------------
277
278 /// Returns a reference to the [MastForest] backing this transaction script.
279 pub fn mast(&self) -> Arc<MastForest> {
280 self.mast.clone()
281 }
282
283 /// Returns the commitment of this transaction script (i.e., the script's MAST root).
284 pub fn root(&self) -> Digest {
285 self.mast[self.entrypoint].digest()
286 }
287}
288
289// SERIALIZATION
290// ================================================================================================
291
292impl Serializable for TransactionScript {
293 fn write_into<W: ByteWriter>(&self, target: &mut W) {
294 self.mast.write_into(target);
295 target.write_u32(self.entrypoint.as_u32());
296 }
297}
298
299impl Deserializable for TransactionScript {
300 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
301 let mast = MastForest::read_from(source)?;
302 let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?;
303
304 Ok(Self::from_parts(Arc::new(mast), entrypoint))
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use vm_core::{
311 AdviceMap,
312 utils::{Deserializable, Serializable},
313 };
314
315 use crate::transaction::TransactionArgs;
316
317 #[test]
318 fn test_tx_args_serialization() {
319 let tx_args = TransactionArgs::new(AdviceMap::default(), std::vec::Vec::default());
320 let bytes: std::vec::Vec<u8> = tx_args.to_bytes();
321 let decoded = TransactionArgs::read_from_bytes(&bytes).unwrap();
322
323 assert_eq!(tx_args, decoded);
324 }
325}