miden_objects/transaction/
tx_args.rs

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