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