Skip to main content

miden_base_sys/bindings/
tx.rs

1use miden_stdlib_sys::{Felt, Word, WordAligned};
2
3use super::types::{AccountId, BlockNumber};
4
5/// Marker trait for raw FPI input array lengths supported by the protocol executor.
6#[doc(hidden)]
7pub trait SupportedForeignProcedureInputLen {}
8
9macro_rules! supported_foreign_procedure_input_len {
10    ($($len:expr),* $(,)?) => {
11        $(
12            impl SupportedForeignProcedureInputLen for [(); $len] {}
13        )*
14    };
15}
16
17supported_foreign_procedure_input_len!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
18
19/// Fully-padded input felts accepted by `execute_foreign_procedure`.
20#[derive(Clone, Copy, Debug)]
21#[repr(C)]
22pub struct ForeignProcedureInputs {
23    words: [Word; 4],
24}
25
26impl ForeignProcedureInputs {
27    /// Creates raw FPI inputs and zero-pads unused protocol input slots.
28    ///
29    /// This is only implemented for input arrays with at most 16 felts.
30    pub fn new<const N: usize>(values: [Felt; N]) -> Self
31    where
32        [(); N]: SupportedForeignProcedureInputLen,
33    {
34        let mut padded = [Felt::ZERO; 16];
35        padded[..N].copy_from_slice(&values);
36
37        Self {
38            words: [
39                Word::new([padded[3], padded[2], padded[1], padded[0]]),
40                Word::new([padded[7], padded[6], padded[5], padded[4]]),
41                Word::new([padded[11], padded[10], padded[9], padded[8]]),
42                Word::new([padded[15], padded[14], padded[13], padded[12]]),
43            ],
44        }
45    }
46}
47
48/// Fully-padded output felts returned by `execute_foreign_procedure`.
49#[derive(Clone, Copy, Debug)]
50#[repr(C)]
51pub struct ForeignProcedureOutputs {
52    words: [Word; 4],
53}
54
55impl ForeignProcedureOutputs {
56    /// Returns the output felt at `index`.
57    ///
58    /// # Panics
59    ///
60    /// Panics if `index` is greater than or equal to 16.
61    pub fn get(&self, index: usize) -> Felt {
62        self.words[index / 4][3 - (index % 4)]
63    }
64}
65
66/// Canonical raw FPI argument tuple consumed by the compiler's indirect lowering.
67#[derive(Clone, Copy, Debug)]
68#[repr(C)]
69pub struct ForeignProcedureInvocation {
70    /// Packed flattened FPI arguments: account id, procedure root, and 16 input felts.
71    pub words: [Word; 6],
72}
73
74impl ForeignProcedureInvocation {
75    /// Creates a raw FPI invocation tuple from SDK account and procedure values.
76    pub fn new(
77        foreign_account_id: AccountId,
78        foreign_proc_root: Word,
79        inputs: ForeignProcedureInputs,
80    ) -> Self {
81        let zero = Felt::ZERO;
82        Self {
83            words: [
84                Word::new([
85                    foreign_account_id.prefix,
86                    foreign_account_id.suffix,
87                    foreign_proc_root[0],
88                    foreign_proc_root[1],
89                ]),
90                Word::new([
91                    foreign_proc_root[2],
92                    foreign_proc_root[3],
93                    inputs.words[0][0],
94                    inputs.words[0][1],
95                ]),
96                Word::new([
97                    inputs.words[0][2],
98                    inputs.words[0][3],
99                    inputs.words[1][0],
100                    inputs.words[1][1],
101                ]),
102                Word::new([
103                    inputs.words[1][2],
104                    inputs.words[1][3],
105                    inputs.words[2][0],
106                    inputs.words[2][1],
107                ]),
108                Word::new([
109                    inputs.words[2][2],
110                    inputs.words[2][3],
111                    inputs.words[3][0],
112                    inputs.words[3][1],
113                ]),
114                Word::new([inputs.words[3][2], inputs.words[3][3], zero, zero]),
115            ],
116        }
117    }
118}
119
120#[allow(improper_ctypes)]
121unsafe extern "C" {
122    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
123    #[link_name = "miden::protocol::tx::get_block_number"]
124    pub fn extern_tx_get_block_number() -> Felt;
125    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
126    #[link_name = "miden::protocol::tx::get_block_commitment"]
127    pub fn extern_tx_get_block_commitment(ptr: *mut Word);
128    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
129    #[link_name = "miden::protocol::tx::get_block_timestamp"]
130    pub fn extern_tx_get_block_timestamp() -> Felt;
131    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
132    #[link_name = "miden::protocol::tx::get_input_notes_commitment"]
133    pub fn extern_tx_get_input_notes_commitment(ptr: *mut Word);
134    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
135    #[link_name = "miden::protocol::tx::get_output_notes_commitment"]
136    pub fn extern_tx_get_output_notes_commitment(ptr: *mut Word);
137    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
138    #[link_name = "miden::protocol::tx::get_num_input_notes"]
139    pub fn extern_tx_get_num_input_notes() -> Felt;
140    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
141    #[link_name = "miden::protocol::tx::get_num_output_notes"]
142    pub fn extern_tx_get_num_output_notes() -> Felt;
143    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
144    #[link_name = "miden::protocol::tx::get_expiration_block_delta"]
145    pub fn extern_tx_get_expiration_block_delta() -> Felt;
146    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
147    #[link_name = "miden::protocol::tx::update_expiration_block_delta"]
148    pub fn extern_tx_update_expiration_block_delta(delta: Felt);
149    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
150    #[link_name = "miden::protocol::tx::get_tx_script_root"]
151    pub fn extern_tx_get_tx_script_root(ptr: *mut Word);
152    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
153    #[link_name = "miden::protocol::tx::execute_foreign_procedure_indirect"]
154    pub fn extern_tx_execute_foreign_procedure(
155        invocation: *const ForeignProcedureInvocation,
156        ptr: *mut ForeignProcedureOutputs,
157    );
158}
159
160/// Returns the current block number.
161pub fn get_block_number() -> BlockNumber {
162    BlockNumber {
163        // The transaction kernel guarantees block numbers fit in a u32.
164        inner: unsafe { extern_tx_get_block_number() },
165    }
166}
167
168/// Returns the input notes commitment digest.
169pub fn get_input_notes_commitment() -> Word {
170    unsafe {
171        let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
172        extern_tx_get_input_notes_commitment(ret_area.as_mut_ptr());
173        ret_area.into_inner().assume_init()
174    }
175}
176
177/// Returns the block commitment of the reference block.
178pub fn get_block_commitment() -> Word {
179    unsafe {
180        let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
181        extern_tx_get_block_commitment(ret_area.as_mut_ptr());
182        ret_area.into_inner().assume_init()
183    }
184}
185
186/// Returns the timestamp of the reference block, in seconds.
187pub fn get_block_timestamp() -> u32 {
188    // The transaction kernel guarantees block timestamps fit in a u32.
189    let timestamp = unsafe { extern_tx_get_block_timestamp() };
190    timestamp.as_canonical_u64() as u32
191}
192
193/// Returns the total number of input notes consumed by the transaction.
194pub fn get_num_input_notes() -> u32 {
195    // The transaction kernel guarantees note counts fit in a u32.
196    let count = unsafe { extern_tx_get_num_input_notes() };
197    count.as_canonical_u64() as u32
198}
199
200/// Returns the number of output notes created so far in the transaction.
201pub fn get_num_output_notes() -> u32 {
202    // The transaction kernel guarantees note counts fit in a u32.
203    let count = unsafe { extern_tx_get_num_output_notes() };
204    count.as_canonical_u64() as u32
205}
206
207/// Returns the transaction expiration block delta, or `0` if no expiration delta has been set.
208pub fn get_expiration_block_delta() -> u16 {
209    // Set deltas are kernel-bounded to 1..=u16::MAX; the kernel returns 0 for an unset delta.
210    let delta = unsafe { extern_tx_get_expiration_block_delta() };
211    delta.as_canonical_u64() as u16
212}
213
214/// Updates the transaction expiration block delta.
215///
216/// The transaction kernel accepts deltas in `1..=u16::MAX`.
217pub fn update_expiration_block_delta(delta: u16) {
218    unsafe {
219        extern_tx_update_expiration_block_delta(Felt::from(delta));
220    }
221}
222
223/// Returns the transaction script root.
224pub fn get_tx_script_root() -> Word {
225    unsafe {
226        let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
227        extern_tx_get_tx_script_root(ret_area.as_mut_ptr());
228        ret_area.into_inner().assume_init()
229    }
230}
231
232/// Returns the output notes commitment digest.
233pub fn get_output_notes_commitment() -> Word {
234    unsafe {
235        let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
236        extern_tx_get_output_notes_commitment(ret_area.as_mut_ptr());
237        ret_area.into_inner().assume_init()
238    }
239}
240
241/// Executes `foreign_proc_root` against `foreign_account_id` with raw felt inputs.
242///
243/// The protocol executor always consumes exactly 16 input felts and returns exactly 16 output
244/// felts. Callers whose target procedure uses fewer values can pass the actual values to
245/// [`ForeignProcedureInputs::new`], which pads the remaining input slots with zeroes. Callers whose
246/// target procedure returns fewer values should ignore the unused padded outputs.
247///
248/// # Panics
249///
250/// Propagates kernel errors if the foreign account ID is invalid, the foreign account inputs are
251/// not available to the transaction, or the procedure root is not exported by the foreign account.
252pub fn execute_foreign_procedure(
253    foreign_account_id: AccountId,
254    foreign_proc_root: Word,
255    inputs: ForeignProcedureInputs,
256) -> ForeignProcedureOutputs {
257    unsafe {
258        let invocation =
259            ForeignProcedureInvocation::new(foreign_account_id, foreign_proc_root, inputs);
260        let mut ret_area =
261            WordAligned::new(::core::mem::MaybeUninit::<ForeignProcedureOutputs>::uninit());
262        extern_tx_execute_foreign_procedure(&invocation, ret_area.as_mut_ptr());
263        ret_area.into_inner().assume_init()
264    }
265}