1use miden_stdlib_sys::{Felt, Word, WordAligned};
2
3use super::types::{AccountId, AssetAmount, AssetId, BlockNumber};
4
5pub const FOREIGN_PROCEDURE_SLOTS: usize = 16;
7
8#[doc(hidden)]
10pub trait SupportedForeignProcedureInputLen {}
11
12macro_rules! supported_foreign_procedure_input_len {
13 ($($len:expr),* $(,)?) => {
14 $(
15 impl SupportedForeignProcedureInputLen for [(); $len] {}
16 )*
17 };
18}
19
20supported_foreign_procedure_input_len!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
21
22#[derive(Clone, Copy, Debug)]
28#[repr(C)]
29pub struct ForeignProcedureInputs {
30 felts: [Felt; FOREIGN_PROCEDURE_SLOTS],
31}
32
33impl ForeignProcedureInputs {
34 pub fn new<const N: usize>(values: [Felt; N]) -> Self
39 where
40 [(); N]: SupportedForeignProcedureInputLen,
41 {
42 let mut felts = [Felt::ZERO; FOREIGN_PROCEDURE_SLOTS];
43 felts[..N].copy_from_slice(&values);
44 Self { felts }
45 }
46}
47
48#[derive(Clone, Copy, Debug)]
54#[repr(C)]
55pub struct ForeignProcedureOutputs {
56 felts: [Felt; FOREIGN_PROCEDURE_SLOTS],
58}
59
60impl ForeignProcedureOutputs {
61 pub fn get(&self, index: usize) -> Felt {
67 self.felts[index]
68 }
69}
70
71#[derive(Clone, Copy, Debug)]
73#[repr(C)]
74pub struct ForeignProcedureInvocation {
75 pub words: [Word; 6],
77}
78
79impl ForeignProcedureInvocation {
80 pub fn new(
82 foreign_account_id: AccountId,
83 foreign_proc_root: Word,
84 inputs: ForeignProcedureInputs,
85 ) -> Self {
86 let zero = Felt::ZERO;
91 Self {
92 words: [
93 Word::new([
94 foreign_account_id.prefix,
95 foreign_account_id.suffix,
96 foreign_proc_root[0],
97 foreign_proc_root[1],
98 ]),
99 Word::new([
100 foreign_proc_root[2],
101 foreign_proc_root[3],
102 inputs.felts[0],
103 inputs.felts[1],
104 ]),
105 Word::new([inputs.felts[2], inputs.felts[3], inputs.felts[4], inputs.felts[5]]),
106 Word::new([inputs.felts[6], inputs.felts[7], inputs.felts[8], inputs.felts[9]]),
107 Word::new([inputs.felts[10], inputs.felts[11], inputs.felts[12], inputs.felts[13]]),
108 Word::new([inputs.felts[14], inputs.felts[15], zero, zero]),
109 ],
110 }
111 }
112}
113
114#[allow(improper_ctypes)]
115unsafe extern "C" {
116 #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
117 #[link_name = "miden::protocol::tx::get_reference_block_number"]
118 pub fn extern_tx_get_reference_block_number() -> Felt;
119 #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
120 #[link_name = "miden::protocol::tx::get_reference_block_commitment"]
121 pub fn extern_tx_get_reference_block_commitment(ptr: *mut Word);
122 #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
123 #[link_name = "miden::protocol::tx::get_block_commitment"]
124 pub fn extern_tx_get_block_commitment(block_number: Felt, ptr: *mut Word);
125 #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
126 #[link_name = "miden::protocol::tx::get_block_timestamp"]
127 pub fn extern_tx_get_block_timestamp() -> Felt;
128 #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
129 #[link_name = "miden::protocol::tx::get_input_notes_commitment"]
130 pub fn extern_tx_get_input_notes_commitment(ptr: *mut Word);
131 #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
132 #[link_name = "miden::protocol::tx::get_output_notes_commitment"]
133 pub fn extern_tx_get_output_notes_commitment(ptr: *mut Word);
134 #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
135 #[link_name = "miden::protocol::tx::get_num_input_notes"]
136 pub fn extern_tx_get_num_input_notes() -> Felt;
137 #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
138 #[link_name = "miden::protocol::tx::get_num_output_notes"]
139 pub fn extern_tx_get_num_output_notes() -> Felt;
140 #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
141 #[link_name = "miden::protocol::tx::get_expiration_block_delta"]
142 pub fn extern_tx_get_expiration_block_delta() -> Felt;
143 #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
144 #[link_name = "miden::protocol::tx::update_expiration_block_delta"]
145 pub fn extern_tx_update_expiration_block_delta(delta: Felt);
146 #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
147 #[link_name = "miden::protocol::tx::get_tx_script_root"]
148 pub fn extern_tx_get_tx_script_root(ptr: *mut Word);
149 #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
150 #[link_name = "miden::protocol::tx::execute_foreign_procedure_indirect"]
151 pub fn extern_tx_execute_foreign_procedure(
152 invocation: *const ForeignProcedureInvocation,
153 ptr: *mut ForeignProcedureOutputs,
154 );
155 #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
156 #[link_name = "miden::protocol::tx::compute_fee"]
157 fn extern_tx_compute_fee(
158 num_extra_cycles: Felt,
159 exclude_notes_commitment_0: Felt,
160 exclude_notes_commitment_1: Felt,
161 exclude_notes_commitment_2: Felt,
162 exclude_notes_commitment_3: Felt,
163 ) -> Felt;
164 #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
165 #[link_name = "miden::protocol::tx::get_fee_asset_id"]
166 fn extern_tx_get_fee_asset_id(ptr: *mut AssetId);
167}
168
169pub fn get_reference_block_number() -> BlockNumber {
171 BlockNumber {
172 inner: unsafe { extern_tx_get_reference_block_number() },
174 }
175}
176
177pub fn get_input_notes_commitment() -> Word {
179 unsafe {
180 let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
181 extern_tx_get_input_notes_commitment(ret_area.as_mut_ptr());
182 ret_area.into_inner().assume_init()
183 }
184}
185
186pub fn get_reference_block_commitment() -> Word {
188 unsafe {
189 let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
190 extern_tx_get_reference_block_commitment(ret_area.as_mut_ptr());
191 ret_area.into_inner().assume_init()
192 }
193}
194
195pub fn get_block_commitment(block_number: BlockNumber) -> Word {
200 unsafe {
201 let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
202 extern_tx_get_block_commitment(block_number.as_felt(), ret_area.as_mut_ptr());
203 ret_area.into_inner().assume_init()
204 }
205}
206
207pub fn get_block_timestamp() -> u32 {
209 let timestamp = unsafe { extern_tx_get_block_timestamp() };
211 timestamp.as_canonical_u64() as u32
212}
213
214pub fn get_num_input_notes() -> u32 {
216 let count = unsafe { extern_tx_get_num_input_notes() };
218 count.as_canonical_u64() as u32
219}
220
221pub fn get_num_output_notes() -> u32 {
223 let count = unsafe { extern_tx_get_num_output_notes() };
225 count.as_canonical_u64() as u32
226}
227
228pub fn get_expiration_block_delta() -> u16 {
230 let delta = unsafe { extern_tx_get_expiration_block_delta() };
232 delta.as_canonical_u64() as u16
233}
234
235pub fn update_expiration_block_delta(delta: u16) {
239 unsafe {
240 extern_tx_update_expiration_block_delta(Felt::from(delta));
241 }
242}
243
244pub fn get_tx_script_root() -> Word {
246 unsafe {
247 let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
248 extern_tx_get_tx_script_root(ret_area.as_mut_ptr());
249 ret_area.into_inner().assume_init()
250 }
251}
252
253pub fn get_output_notes_commitment() -> Word {
255 unsafe {
256 let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
257 extern_tx_get_output_notes_commitment(ret_area.as_mut_ptr());
258 ret_area.into_inner().assume_init()
259 }
260}
261
262pub fn execute_foreign_procedure(
276 foreign_account_id: AccountId,
277 foreign_proc_root: Word,
278 inputs: ForeignProcedureInputs,
279) -> ForeignProcedureOutputs {
280 unsafe {
281 let invocation =
282 ForeignProcedureInvocation::new(foreign_account_id, foreign_proc_root, inputs);
283 let mut ret_area =
284 WordAligned::new(::core::mem::MaybeUninit::<ForeignProcedureOutputs>::uninit());
285 extern_tx_execute_foreign_procedure(&invocation, ret_area.as_mut_ptr());
286 ret_area.into_inner().assume_init()
287 }
288}
289
290pub fn compute_fee(num_extra_cycles: u32, exclude_notes_commitment: Word) -> AssetAmount {
301 let fee = unsafe {
302 extern_tx_compute_fee(
303 Felt::from_u32(num_extra_cycles),
304 exclude_notes_commitment[0],
305 exclude_notes_commitment[1],
306 exclude_notes_commitment[2],
307 exclude_notes_commitment[3],
308 )
309 };
310 AssetAmount::try_from(fee).expect("transaction fee exceeds the maximum asset amount")
311}
312
313pub fn get_fee_asset_id() -> AssetId {
315 unsafe {
316 let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<AssetId>::uninit());
317 extern_tx_get_fee_asset_id(ret_area.as_mut_ptr());
318 ret_area.into_inner().assume_init()
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use miden_stdlib_sys::{Felt, Word, felt};
325
326 use super::{
327 AccountId, FOREIGN_PROCEDURE_SLOTS, ForeignProcedureInputs, ForeignProcedureInvocation,
328 ForeignProcedureOutputs,
329 };
330
331 #[test]
333 fn inputs_keep_slot_order_and_zero_pad_trailing_slots() {
334 let inputs = ForeignProcedureInputs::new([felt!(7), felt!(8)]);
335 let mut expected = [Felt::ZERO; FOREIGN_PROCEDURE_SLOTS];
336 expected[0] = felt!(7);
337 expected[1] = felt!(8);
338 assert_eq!(inputs.felts, expected);
339 }
340
341 #[test]
344 fn outputs_get_reads_slots_in_order() {
345 let felts: [Felt; FOREIGN_PROCEDURE_SLOTS] =
346 core::array::from_fn(|i| Felt::from_u32(i as u32 + 1));
347 let outputs = ForeignProcedureOutputs { felts };
348 for (index, felt) in felts.iter().enumerate() {
349 assert_eq!(outputs.get(index), *felt);
350 }
351 }
352
353 #[test]
356 fn invocation_flattens_to_prefix_root_and_inputs() {
357 let account_id = AccountId::new(felt!(1), felt!(2));
358 let root = Word::new([felt!(3), felt!(4), felt!(5), felt!(6)]);
359 let inputs =
360 ForeignProcedureInputs::new(core::array::from_fn::<Felt, FOREIGN_PROCEDURE_SLOTS, _>(
361 |i| Felt::from_u32(i as u32 + 7),
362 ));
363 let invocation = ForeignProcedureInvocation::new(account_id, root, inputs);
364
365 let expected: [Felt; 24] = core::array::from_fn(|i| {
366 if i < 22 {
367 Felt::from_u32(i as u32 + 1)
368 } else {
369 Felt::ZERO
370 }
371 });
372 assert_eq!(Word::words_as_elements(&invocation.words), &expected[..]);
373 }
374}