miden_objects/transaction/
tx_args.rs

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