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(&note_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}