use miden_stdlib_sys::{Felt, Word, WordAligned};
use super::types::{AccountId, AssetAmount, AssetId, BlockNumber};
pub const FOREIGN_PROCEDURE_SLOTS: usize = 16;
#[doc(hidden)]
pub trait SupportedForeignProcedureInputLen {}
macro_rules! supported_foreign_procedure_input_len {
($($len:expr),* $(,)?) => {
$(
impl SupportedForeignProcedureInputLen for [(); $len] {}
)*
};
}
supported_foreign_procedure_input_len!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct ForeignProcedureInputs {
felts: [Felt; FOREIGN_PROCEDURE_SLOTS],
}
impl ForeignProcedureInputs {
pub fn new<const N: usize>(values: [Felt; N]) -> Self
where
[(); N]: SupportedForeignProcedureInputLen,
{
let mut felts = [Felt::ZERO; FOREIGN_PROCEDURE_SLOTS];
felts[..N].copy_from_slice(&values);
Self { felts }
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct ForeignProcedureOutputs {
felts: [Felt; FOREIGN_PROCEDURE_SLOTS],
}
impl ForeignProcedureOutputs {
pub fn get(&self, index: usize) -> Felt {
self.felts[index]
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct ForeignProcedureInvocation {
pub words: [Word; 6],
}
impl ForeignProcedureInvocation {
pub fn new(
foreign_account_id: AccountId,
foreign_proc_root: Word,
inputs: ForeignProcedureInputs,
) -> Self {
let zero = Felt::ZERO;
Self {
words: [
Word::new([
foreign_account_id.prefix,
foreign_account_id.suffix,
foreign_proc_root[0],
foreign_proc_root[1],
]),
Word::new([
foreign_proc_root[2],
foreign_proc_root[3],
inputs.felts[0],
inputs.felts[1],
]),
Word::new([inputs.felts[2], inputs.felts[3], inputs.felts[4], inputs.felts[5]]),
Word::new([inputs.felts[6], inputs.felts[7], inputs.felts[8], inputs.felts[9]]),
Word::new([inputs.felts[10], inputs.felts[11], inputs.felts[12], inputs.felts[13]]),
Word::new([inputs.felts[14], inputs.felts[15], zero, zero]),
],
}
}
}
#[allow(improper_ctypes)]
unsafe extern "C" {
#[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
#[link_name = "miden::protocol::tx::get_reference_block_number"]
pub fn extern_tx_get_reference_block_number() -> Felt;
#[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
#[link_name = "miden::protocol::tx::get_reference_block_commitment"]
pub fn extern_tx_get_reference_block_commitment(ptr: *mut Word);
#[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
#[link_name = "miden::protocol::tx::get_block_commitment"]
pub fn extern_tx_get_block_commitment(block_number: Felt, ptr: *mut Word);
#[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
#[link_name = "miden::protocol::tx::get_block_timestamp"]
pub fn extern_tx_get_block_timestamp() -> Felt;
#[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
#[link_name = "miden::protocol::tx::get_input_notes_commitment"]
pub fn extern_tx_get_input_notes_commitment(ptr: *mut Word);
#[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
#[link_name = "miden::protocol::tx::get_output_notes_commitment"]
pub fn extern_tx_get_output_notes_commitment(ptr: *mut Word);
#[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
#[link_name = "miden::protocol::tx::get_num_input_notes"]
pub fn extern_tx_get_num_input_notes() -> Felt;
#[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
#[link_name = "miden::protocol::tx::get_num_output_notes"]
pub fn extern_tx_get_num_output_notes() -> Felt;
#[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
#[link_name = "miden::protocol::tx::get_expiration_block_delta"]
pub fn extern_tx_get_expiration_block_delta() -> Felt;
#[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
#[link_name = "miden::protocol::tx::update_expiration_block_delta"]
pub fn extern_tx_update_expiration_block_delta(delta: Felt);
#[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
#[link_name = "miden::protocol::tx::get_tx_script_root"]
pub fn extern_tx_get_tx_script_root(ptr: *mut Word);
#[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
#[link_name = "miden::protocol::tx::execute_foreign_procedure_indirect"]
pub fn extern_tx_execute_foreign_procedure(
invocation: *const ForeignProcedureInvocation,
ptr: *mut ForeignProcedureOutputs,
);
#[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
#[link_name = "miden::protocol::tx::compute_fee"]
fn extern_tx_compute_fee(
num_extra_cycles: Felt,
exclude_notes_commitment_0: Felt,
exclude_notes_commitment_1: Felt,
exclude_notes_commitment_2: Felt,
exclude_notes_commitment_3: Felt,
) -> Felt;
#[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
#[link_name = "miden::protocol::tx::get_fee_asset_id"]
fn extern_tx_get_fee_asset_id(ptr: *mut AssetId);
}
pub fn get_reference_block_number() -> BlockNumber {
BlockNumber {
inner: unsafe { extern_tx_get_reference_block_number() },
}
}
pub fn get_input_notes_commitment() -> Word {
unsafe {
let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
extern_tx_get_input_notes_commitment(ret_area.as_mut_ptr());
ret_area.into_inner().assume_init()
}
}
pub fn get_reference_block_commitment() -> Word {
unsafe {
let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
extern_tx_get_reference_block_commitment(ret_area.as_mut_ptr());
ret_area.into_inner().assume_init()
}
}
pub fn get_block_commitment(block_number: BlockNumber) -> Word {
unsafe {
let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
extern_tx_get_block_commitment(block_number.as_felt(), ret_area.as_mut_ptr());
ret_area.into_inner().assume_init()
}
}
pub fn get_block_timestamp() -> u32 {
let timestamp = unsafe { extern_tx_get_block_timestamp() };
timestamp.as_canonical_u64() as u32
}
pub fn get_num_input_notes() -> u32 {
let count = unsafe { extern_tx_get_num_input_notes() };
count.as_canonical_u64() as u32
}
pub fn get_num_output_notes() -> u32 {
let count = unsafe { extern_tx_get_num_output_notes() };
count.as_canonical_u64() as u32
}
pub fn get_expiration_block_delta() -> u16 {
let delta = unsafe { extern_tx_get_expiration_block_delta() };
delta.as_canonical_u64() as u16
}
pub fn update_expiration_block_delta(delta: u16) {
unsafe {
extern_tx_update_expiration_block_delta(Felt::from(delta));
}
}
pub fn get_tx_script_root() -> Word {
unsafe {
let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
extern_tx_get_tx_script_root(ret_area.as_mut_ptr());
ret_area.into_inner().assume_init()
}
}
pub fn get_output_notes_commitment() -> Word {
unsafe {
let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
extern_tx_get_output_notes_commitment(ret_area.as_mut_ptr());
ret_area.into_inner().assume_init()
}
}
pub fn execute_foreign_procedure(
foreign_account_id: AccountId,
foreign_proc_root: Word,
inputs: ForeignProcedureInputs,
) -> ForeignProcedureOutputs {
unsafe {
let invocation =
ForeignProcedureInvocation::new(foreign_account_id, foreign_proc_root, inputs);
let mut ret_area =
WordAligned::new(::core::mem::MaybeUninit::<ForeignProcedureOutputs>::uninit());
extern_tx_execute_foreign_procedure(&invocation, ret_area.as_mut_ptr());
ret_area.into_inner().assume_init()
}
}
pub fn compute_fee(num_extra_cycles: u32, exclude_notes_commitment: Word) -> AssetAmount {
let fee = unsafe {
extern_tx_compute_fee(
Felt::from_u32(num_extra_cycles),
exclude_notes_commitment[0],
exclude_notes_commitment[1],
exclude_notes_commitment[2],
exclude_notes_commitment[3],
)
};
AssetAmount::try_from(fee).expect("transaction fee exceeds the maximum asset amount")
}
pub fn get_fee_asset_id() -> AssetId {
unsafe {
let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<AssetId>::uninit());
extern_tx_get_fee_asset_id(ret_area.as_mut_ptr());
ret_area.into_inner().assume_init()
}
}
#[cfg(test)]
mod tests {
use miden_stdlib_sys::{Felt, Word, felt};
use super::{
AccountId, FOREIGN_PROCEDURE_SLOTS, ForeignProcedureInputs, ForeignProcedureInvocation,
ForeignProcedureOutputs,
};
#[test]
fn inputs_keep_slot_order_and_zero_pad_trailing_slots() {
let inputs = ForeignProcedureInputs::new([felt!(7), felt!(8)]);
let mut expected = [Felt::ZERO; FOREIGN_PROCEDURE_SLOTS];
expected[0] = felt!(7);
expected[1] = felt!(8);
assert_eq!(inputs.felts, expected);
}
#[test]
fn outputs_get_reads_slots_in_order() {
let felts: [Felt; FOREIGN_PROCEDURE_SLOTS] =
core::array::from_fn(|i| Felt::from_u32(i as u32 + 1));
let outputs = ForeignProcedureOutputs { felts };
for (index, felt) in felts.iter().enumerate() {
assert_eq!(outputs.get(index), *felt);
}
}
#[test]
fn invocation_flattens_to_prefix_root_and_inputs() {
let account_id = AccountId::new(felt!(1), felt!(2));
let root = Word::new([felt!(3), felt!(4), felt!(5), felt!(6)]);
let inputs =
ForeignProcedureInputs::new(core::array::from_fn::<Felt, FOREIGN_PROCEDURE_SLOTS, _>(
|i| Felt::from_u32(i as u32 + 7),
));
let invocation = ForeignProcedureInvocation::new(account_id, root, inputs);
let expected: [Felt; 24] = core::array::from_fn(|i| {
if i < 22 {
Felt::from_u32(i as u32 + 1)
} else {
Felt::ZERO
}
});
assert_eq!(Word::words_as_elements(&invocation.words), &expected[..]);
}
}