miden_objects/transaction/
tx_args.rs

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