diff --git a/.deny.toml b/.deny.toml index fb1ce3cf..0a9a3df1 100644 --- a/.deny.toml +++ b/.deny.toml @@ -13,7 +13,6 @@ ignore = [ { id = "RUSTSEC-2025-0055", reason = "`tracing-subscriber` v0.2.25 pulled in by ark-relations v0.4.0 - will be addressed before mainnet" }, { id = "RUSTSEC-2025-0141", reason = "`bincode` is unmaintained but continuing to use it." }, { id = "RUSTSEC-2023-0089", reason = "atomic-polyfill is pulled transitively via risc0-zkvm; waiting on upstream fix (see https://github.com/risc0/risc0/issues/3453)" }, - { id = "RUSTSEC-2026-0097", reason = "`rand` v0.8.5 is present transitively from logos crates, modification may break integration" }, { id = "RUSTSEC-2026-0118", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration" }, { id = "RUSTSEC-2026-0119", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration" }, ] diff --git a/integration_tests/tests/wallet_ffi.rs b/integration_tests/tests/wallet_ffi.rs index 82ac6b52..93939cd1 100644 --- a/integration_tests/tests/wallet_ffi.rs +++ b/integration_tests/tests/wallet_ffi.rs @@ -7,6 +7,7 @@ clippy::undocumented_unsafe_blocks, clippy::multiple_unsafe_ops_per_block, clippy::shadow_unrelated, + clippy::as_conversions, reason = "We don't care about these in tests" )] @@ -31,7 +32,7 @@ use wallet::account::HumanReadableAccount; use wallet_ffi::{ FfiAccount, FfiAccountIdentity, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey, FfiTransferResult, FfiU128, WalletHandle, error, - generic_transaction::FfiProgramWithDependencies, + generic_transaction::{FfiProgramWithDependencies, FfiTransactionResult}, }; unsafe extern "C" { @@ -196,9 +197,29 @@ unsafe extern "C" { account_identities_size: usize, instruction_words: *const u32, instruction_words_size: usize, - program_with_dependencies: FfiProgramWithDependencies, - out_result: *mut FfiTransferResult, + program_with_dependencies: *const FfiProgramWithDependencies, + out_result: *mut FfiTransactionResult, ) -> error::WalletFfiError; + + fn wallet_ffi_resolve_private_account( + handle: *mut WalletHandle, + account_id: FfiBytes32, + out_account_identity: *mut FfiAccountIdentity, + ) -> error::WalletFfiError; + + fn wallet_ffi_send_generic_private_transaction( + handle: *mut WalletHandle, + account_identities: *const FfiAccountIdentity, + account_identities_size: usize, + instruction_words: *const u32, + instruction_words_size: usize, + program_with_dependencies: *const FfiProgramWithDependencies, + out_result: *mut FfiTransactionResult, + ) -> error::WalletFfiError; + + fn wallet_ffi_free_transaction_result(result: *mut FfiTransactionResult); + + fn wallet_ffi_free_account_identity(account_identity: *mut FfiAccountIdentity); } fn new_wallet_ffi_with_test_context_config( @@ -1096,7 +1117,7 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> { let to: FfiBytes32 = ctx.ctx().existing_public_accounts()[1].into(); let amount = 100_u128; - let mut transfer_result = FfiTransferResult::default(); + let mut transaction_result = FfiTransactionResult::default(); let mut from_account_identity = FfiAccountIdentity::default(); let mut to_account_identity = FfiAccountIdentity::default(); @@ -1123,7 +1144,7 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> { let instruction_words = Box::into_raw(instruction_data.into_boxed_slice()) as *const u32; let program: ProgramWithDependencies = Program::authenticated_transfer_program().into(); - let program_with_dependencies = program.into(); + let program_with_dependencies: FfiProgramWithDependencies = program.into(); unsafe { wallet_ffi_send_generic_public_transaction( @@ -1132,8 +1153,8 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> { account_identities_size, instruction_words, instruction_words_size, - program_with_dependencies, - &raw mut transfer_result, + &raw const program_with_dependencies, + &raw mut transaction_result, ) .unwrap(); } @@ -1164,7 +1185,121 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> { assert_eq!(to_balance, 20100); unsafe { - wallet_ffi_free_transfer_result(&raw mut transfer_result); + let account_identities_mut = account_identities.cast_mut(); + wallet_ffi_free_account_identity(account_identities_mut); + wallet_ffi_free_account_identity(account_identities_mut.add(1)); + + let instruction_data = + std::slice::from_raw_parts_mut(instruction_words.cast_mut(), instruction_words_size); + drop(Box::from_raw(std::ptr::from_mut(instruction_data))); + + wallet_ffi_free_transaction_result(&raw mut transaction_result); + wallet_ffi_destroy(wallet_ffi_handle); + } + + Ok(()) +} + +#[test] +fn test_wallet_ffi_transfer_generic_private() -> Result<()> { + let ctx = BlockingTestContext::new()?; + let home = tempfile::tempdir()?; + let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let from: FfiBytes32 = ctx.ctx().existing_private_accounts()[0].into(); + let to: FfiBytes32 = ctx.ctx().existing_private_accounts()[1].into(); + let amount = 100_u128; + + let mut transaction_result = FfiTransactionResult::default(); + + let mut from_account_identity = FfiAccountIdentity::default(); + let mut to_account_identity = FfiAccountIdentity::default(); + + unsafe { + wallet_ffi_resolve_private_account(wallet_ffi_handle, from, &raw mut from_account_identity) + .unwrap(); + } + + unsafe { + wallet_ffi_resolve_private_account(wallet_ffi_handle, to, &raw mut to_account_identity) + .unwrap(); + } + + let ffi_accs = vec![from_account_identity, to_account_identity]; + let account_identities_size = ffi_accs.len(); + let account_identities = + Box::into_raw(ffi_accs.into_boxed_slice()) as *const FfiAccountIdentity; + + let instruction_data = + Program::serialize_instruction(authenticated_transfer_core::Instruction::Transfer { + amount, + }) + .unwrap(); + let instruction_words_size = instruction_data.len(); + let instruction_words = Box::into_raw(instruction_data.into_boxed_slice()) as *const u32; + + let program: ProgramWithDependencies = Program::authenticated_transfer_program().into(); + let program_with_dependencies: FfiProgramWithDependencies = program.into(); + + unsafe { + wallet_ffi_send_generic_private_transaction( + wallet_ffi_handle, + account_identities, + account_identities_size, + instruction_words, + instruction_words_size, + &raw const program_with_dependencies, + &raw mut transaction_result, + ) + .unwrap(); + } + + assert_eq!(transaction_result.secrets_size, 2); + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + // Sync private account local storage with onchain encrypted state + unsafe { + let mut current_height = 0; + wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap(); + wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap(); + }; + + let from_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + let _result = wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const from, + false, + &raw mut out_balance, + ); + u128::from_le_bytes(out_balance) + }; + + let to_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + let _result = wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const to, + false, + &raw mut out_balance, + ); + u128::from_le_bytes(out_balance) + }; + + assert_eq!(from_balance, 9900); + assert_eq!(to_balance, 20100); + + unsafe { + let account_identities_mut = account_identities.cast_mut(); + wallet_ffi_free_account_identity(account_identities_mut); + wallet_ffi_free_account_identity(account_identities_mut.add(1)); + + let instruction_data = + std::slice::from_raw_parts_mut(instruction_words.cast_mut(), instruction_words_size); + drop(Box::from_raw(std::ptr::from_mut(instruction_data))); + + wallet_ffi_free_transaction_result(&raw mut transaction_result); wallet_ffi_destroy(wallet_ffi_handle); } diff --git a/wallet-ffi/src/error.rs b/wallet-ffi/src/error.rs index 35b084a9..e95b47a0 100644 --- a/wallet-ffi/src/error.rs +++ b/wallet-ffi/src/error.rs @@ -41,7 +41,7 @@ pub enum WalletFfiError { InvalidTypeConversion = 15, /// Invalid Key value. InvalidKeyValue = 16, - /// Invalid program bytecode + /// Invalid program bytecode. InvalidBytecode = 17, /// Internal error (catch-all). InternalError = 99, diff --git a/wallet-ffi/src/generic_transaction.rs b/wallet-ffi/src/generic_transaction.rs index 193ccad9..1351a7be 100644 --- a/wallet-ffi/src/generic_transaction.rs +++ b/wallet-ffi/src/generic_transaction.rs @@ -1,9 +1,16 @@ -use std::{collections::HashMap, ffi::{CString, c_char}}; +use std::{ + collections::HashMap, + ffi::{c_char, CString}, +}; use nssa::{privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program}; use crate::{ - FfiAccountIdentity, FfiBytes32, WalletHandle, block_on, error::{WalletFfiError, print_error}, map_execution_error, wallet::get_wallet + block_on, + error::{print_error, WalletFfiError}, + map_execution_error, + wallet::get_wallet, + FfiAccountIdentity, FfiBytes32, WalletHandle, }; #[repr(C)] @@ -14,7 +21,7 @@ pub struct SerializationHelperResult { } impl SerializationHelperResult { - fn from_err(error: WalletFfiError) -> Self { + const fn from_err(error: WalletFfiError) -> Self { Self { instruction_words: std::ptr::null_mut(), instruction_words_size: 0, @@ -23,52 +30,8 @@ impl SerializationHelperResult { } } -/// Serialize sequence of bytes into RISC0 readable words -/// -/// # Parameters -/// - `input_instruction_data`: Valid pointer to a sequence of bytes -/// - `input_instruction_data_size`: Size of `input_instruction_data` -/// -/// # Returns -/// - `Success` on successful creation -/// - Error code on failure -/// -/// # Safety -/// - `input_instruction_data` must be a valid pointer -#[no_mangle] -pub unsafe extern "C" fn wallet_ffi_serialization_helper( - input_instruction_data: *const u8, - input_instruction_data_size: usize, -) -> SerializationHelperResult { - if input_instruction_data.is_null() { - print_error("Null input pointer for instruction_data"); - return SerializationHelperResult::from_err(WalletFfiError::NullPointer); - } - - let input_slice = - unsafe { std::slice::from_raw_parts(input_instruction_data, input_instruction_data_size) }; - let res_vec_u32 = match risc0_zkvm::serde::to_vec(input_slice).map_err(|err| { - print_error(format!( - "Failed to serialize input into words with err {err}" - )); - WalletFfiError::SerializationError - }) { - Ok(res) => res, - Err(err) => return SerializationHelperResult::from_err(err), - }; - let res_len = res_vec_u32.len(); - let res_boxed = res_vec_u32.into_boxed_slice(); - let res_ptr = Box::into_raw(res_boxed).cast::(); - - SerializationHelperResult { - instruction_words: res_ptr, - instruction_words_size: res_len, - error: WalletFfiError::Success, - } -} - #[repr(C)] -/// Intended to be created manually +/// Intended to be created manually. pub struct FfiProgram { pub elf_data: *const u8, pub elf_size: usize, @@ -102,17 +65,17 @@ impl From for FfiProgram { } #[repr(C)] -/// Intended to be created manually +/// Intended to be created manually. pub struct FfiProgramWithDependencies { pub program: FfiProgram, pub deps: *const FfiProgram, pub deps_size: usize, } -impl TryFrom for ProgramWithDependencies { +impl TryFrom<&FfiProgramWithDependencies> for ProgramWithDependencies { type Error = WalletFfiError; - fn try_from(value: FfiProgramWithDependencies) -> Result { + fn try_from(value: &FfiProgramWithDependencies) -> Result { let mut program_map = HashMap::new(); let orig_program = (&value.program).try_into()?; @@ -163,7 +126,7 @@ pub struct FfiTransactionResult { /// Whether the transaction succeeded. pub success: bool, pub secrets_data: *const FfiBytes32, - /// Public transaction have 0 secrets + /// Public transaction have 0 secrets. pub secrets_size: usize, } @@ -178,7 +141,55 @@ impl Default for FfiTransactionResult { } } -/// Send generic public transaction +/// Serialize sequence of bytes into RISC0 readable words. +/// +/// # Parameters +/// - `input_instruction_data`: Valid pointer to a sequence of bytes +/// - `input_instruction_data_size`: Size of `input_instruction_data` +/// +/// # Returns +/// - `Success` on successful creation +/// - Error code on failure +/// +/// # Safety +/// - `input_instruction_data` must be a valid pointer +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_serialization_helper( + input_instruction_data: *const u8, + input_instruction_data_size: usize, +) -> SerializationHelperResult { + if input_instruction_data.is_null() { + print_error("Null input pointer for instruction_data"); + return SerializationHelperResult::from_err(WalletFfiError::NullPointer); + } + + let input_slice = + unsafe { std::slice::from_raw_parts(input_instruction_data, input_instruction_data_size) }; + let res_vec_u32_with_prefix = match risc0_zkvm::serde::to_vec(input_slice).map_err(|err| { + print_error(format!( + "Failed to serialize input into words with err {err}" + )); + WalletFfiError::SerializationError + }) { + Ok(res) => res, + Err(err) => return SerializationHelperResult::from_err(err), + }; + + // The resulting vec contains len as prefix + let res_vec_u32 = res_vec_u32_with_prefix[1..].to_vec(); + + let res_len = res_vec_u32.len(); + let res_boxed = res_vec_u32.into_boxed_slice(); + let res_ptr = Box::into_raw(res_boxed).cast::(); + + SerializationHelperResult { + instruction_words: res_ptr, + instruction_words_size: res_len, + error: WalletFfiError::Success, + } +} + +/// Send generic public transaction. /// /// # Parameters /// - `handle`: Valid pointer to wallet handle @@ -202,7 +213,7 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( account_identities_size: usize, instruction_words: *const u32, instruction_words_size: usize, - program_with_dependencies: FfiProgramWithDependencies, + program_with_dependencies: *const FfiProgramWithDependencies, out_result: *mut FfiTransactionResult, ) -> WalletFfiError { let wrapper = match get_wallet(handle) { @@ -244,9 +255,9 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( { Ok(v) => v, Err(err) => { - print_error(format!( - "account_identities_size does not match actual size of account_identities" - )); + print_error( + "account_identities_size does not match actual size of account_identities", + ); return err; } } @@ -263,7 +274,7 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( instruction_data.push(unsafe { *instruction_words.add(i) }); } - let program = match program_with_dependencies.try_into() { + let program = match unsafe { &*program_with_dependencies }.try_into() { Ok(v) => v, Err(err) => return err, }; @@ -290,7 +301,7 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( } } -/// Send generic private transaction +/// Send generic private transaction. /// /// # Parameters /// - `handle`: Valid pointer to wallet handle @@ -314,7 +325,7 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction( account_identities_size: usize, instruction_words: *const u32, instruction_words_size: usize, - program_with_dependencies: FfiProgramWithDependencies, + program_with_dependencies: *const FfiProgramWithDependencies, out_result: *mut FfiTransactionResult, ) -> WalletFfiError { let wrapper = match get_wallet(handle) { @@ -356,9 +367,9 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction( { Ok(v) => v, Err(err) => { - print_error(format!( - "account_identities_size does not match actual size of account_identities" - )); + print_error( + "account_identities_size does not match actual size of account_identities", + ); return err; } } @@ -375,7 +386,7 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction( instruction_data.push(unsafe { *instruction_words.add(i) }); } - let program = match program_with_dependencies.try_into() { + let program = match unsafe { &*program_with_dependencies }.try_into() { Ok(v) => v, Err(err) => return err, }; @@ -390,7 +401,11 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction( (*out_result).success = true; let secrets_size = secrets.len(); - let boxed_slice = secrets.into_iter().map(Into::into).collect::>().into_boxed_slice(); + let boxed_slice = secrets + .into_iter() + .map(Into::into) + .collect::>() + .into_boxed_slice(); let secrets_data = Box::into_raw(boxed_slice) as *const FfiBytes32; (*out_result).secrets_size = secrets_size; @@ -407,3 +422,28 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction( } } } + +/// Free a transaction result returned by `wallet_ffi_send_generic_public_transaction` or +/// `wallet_ffi_send_generic_private_transaction`. +/// +/// # Safety +/// The result must be either null or a valid result from a transaction function. +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_free_transaction_result(result: *mut FfiTransactionResult) { + if result.is_null() { + return; + } + + unsafe { + let result = &*result; + if !result.tx_hash.is_null() { + drop(CString::from_raw(result.tx_hash)); + } + + if !result.secrets_data.is_null() { + let secrets = + std::slice::from_raw_parts_mut(result.secrets_data.cast_mut(), result.secrets_size); + drop(Box::from_raw(std::ptr::from_mut::<[FfiBytes32]>(secrets))); + } + } +} diff --git a/wallet-ffi/src/keys.rs b/wallet-ffi/src/keys.rs index a0c90440..65aa5ef0 100644 --- a/wallet-ffi/src/keys.rs +++ b/wallet-ffi/src/keys.rs @@ -320,12 +320,9 @@ pub unsafe extern "C" fn wallet_ffi_resolve_private_account( let account_id = account_id.into(); - let resolved_account = match wallet.resolve_private_account(account_id) { - Some(v) => v, - None => { - print_error(format!("Account not found")); - return WalletFfiError::AccountNotFound; - } + let Some(resolved_account) = wallet.resolve_private_account(account_id) else { + print_error("Account not found"); + return WalletFfiError::AccountNotFound; }; unsafe { diff --git a/wallet-ffi/src/lib.rs b/wallet-ffi/src/lib.rs index e28a0560..9502d0e0 100644 --- a/wallet-ffi/src/lib.rs +++ b/wallet-ffi/src/lib.rs @@ -23,6 +23,7 @@ #![expect( clippy::undocumented_unsafe_blocks, clippy::multiple_unsafe_ops_per_block, + clippy::as_conversions, reason = "TODO: fix later" )] diff --git a/wallet-ffi/src/types.rs b/wallet-ffi/src/types.rs index 960cba47..149b646b 100644 --- a/wallet-ffi/src/types.rs +++ b/wallet-ffi/src/types.rs @@ -179,7 +179,7 @@ impl FfiPrivateAccountKeys { } } -/// Enumeration to represent kinds of FfiAccountManagerAccountIdentity +/// Enumeration to represent kinds of `FfiAccountManagerAccountIdentity`. #[repr(C)] pub enum FfiAccountIdentityKind { Public = 0, @@ -192,7 +192,7 @@ pub enum FfiAccountIdentityKind { PrivatePdaShared = 7, } -/// Struct representing of account identity, given to `AccountManager` at intialization +/// Struct representing of account identity, given to `AccountManager` at intialization. #[repr(C)] pub struct FfiAccountIdentity { pub kind: FfiAccountIdentityKind, @@ -450,13 +450,9 @@ impl TryFrom<&FfiAccountIdentity> for AccountIdentity { fn try_from(value: &FfiAccountIdentity) -> Result { match value.kind { - FfiAccountIdentityKind::Public => Ok(AccountIdentity::Public(value.account_id.into())), - FfiAccountIdentityKind::PublicNoSign => { - Ok(AccountIdentity::PublicNoSign(value.account_id.into())) - } - FfiAccountIdentityKind::PrivateOwned => { - Ok(AccountIdentity::PrivateOwned(value.account_id.into())) - } + FfiAccountIdentityKind::Public => Ok(Self::Public(value.account_id.into())), + FfiAccountIdentityKind::PublicNoSign => Ok(Self::PublicNoSign(value.account_id.into())), + FfiAccountIdentityKind::PrivateOwned => Ok(Self::PrivateOwned(value.account_id.into())), FfiAccountIdentityKind::PrivateForeign => { let vpk = if value.viewing_public_key_len == 33 { let slice = unsafe { @@ -470,14 +466,14 @@ impl TryFrom<&FfiAccountIdentity> for AccountIdentity { Err(WalletFfiError::InvalidKeyValue) }?; - Ok(AccountIdentity::PrivateForeign { + Ok(Self::PrivateForeign { npk: NullifierPublicKey(value.nullifier_public_key.data), vpk, identifier: value.identifier.into(), }) } FfiAccountIdentityKind::PrivatePdaOwned => { - Ok(AccountIdentity::PrivatePdaOwned(value.account_id.into())) + Ok(Self::PrivatePdaOwned(value.account_id.into())) } FfiAccountIdentityKind::PrivatePdaForeign => { let vpk = if value.viewing_public_key_len == 33 { @@ -492,7 +488,7 @@ impl TryFrom<&FfiAccountIdentity> for AccountIdentity { Err(WalletFfiError::InvalidKeyValue) }?; - Ok(AccountIdentity::PrivatePdaForeign { + Ok(Self::PrivatePdaForeign { account_id: value.account_id.into(), npk: NullifierPublicKey(value.nullifier_public_key.data), vpk, @@ -512,7 +508,7 @@ impl TryFrom<&FfiAccountIdentity> for AccountIdentity { Err(WalletFfiError::InvalidKeyValue) }?; - Ok(AccountIdentity::PrivateShared { + Ok(Self::PrivateShared { nsk: value.nullifier_secret_key.data, npk: NullifierPublicKey(value.nullifier_public_key.data), vpk, @@ -532,7 +528,7 @@ impl TryFrom<&FfiAccountIdentity> for AccountIdentity { Err(WalletFfiError::InvalidKeyValue) }?; - Ok(AccountIdentity::PrivatePdaShared { + Ok(Self::PrivatePdaShared { account_id: value.account_id.into(), nsk: value.nullifier_secret_key.data, npk: NullifierPublicKey(value.nullifier_public_key.data), diff --git a/wallet-ffi/wallet_ffi.h b/wallet-ffi/wallet_ffi.h index d35463fc..03b139d7 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -104,7 +104,7 @@ typedef enum WalletFfiError { */ INVALID_KEY_VALUE = 16, /** - * Invalid program bytecode + * Invalid program bytecode. */ INVALID_BYTECODE = 17, /** @@ -114,7 +114,7 @@ typedef enum WalletFfiError { } WalletFfiError; /** - * Enumeration to represent kinds of FfiAccountManagerAccountIdentity + * Enumeration to represent kinds of `FfiAccountManagerAccountIdentity`. */ typedef enum FfiAccountIdentityKind { PUBLIC = 0, @@ -225,7 +225,7 @@ typedef struct SerializationHelperResult { } SerializationHelperResult; /** - * Struct representing of account identity, given to `AccountManager` at intialization + * Struct representing of account identity, given to `AccountManager` at intialization. */ typedef struct FfiAccountIdentity { enum FfiAccountIdentityKind kind; @@ -238,7 +238,7 @@ typedef struct FfiAccountIdentity { } FfiAccountIdentity; /** - * Intended to be created manually + * Intended to be created manually. */ typedef struct FfiProgram { const uint8_t *elf_data; @@ -246,7 +246,7 @@ typedef struct FfiProgram { } FfiProgram; /** - * Intended to be created manually + * Intended to be created manually. */ typedef struct FfiProgramWithDependencies { struct FfiProgram program; @@ -268,7 +268,7 @@ typedef struct FfiTransactionResult { bool success; const struct FfiBytes32 *secrets_data; /** - * Public transaction have 0 secrets + * Public transaction have 0 secrets. */ uintptr_t secrets_size; } FfiTransactionResult; @@ -528,7 +528,7 @@ enum WalletFfiError wallet_ffi_import_private_account(struct WalletHandle *handl const char *account_state_json); /** - * Serialize sequence of bytes into RISC0 readable words + * Serialize sequence of bytes into RISC0 readable words. * * # Parameters * - `input_instruction_data`: Valid pointer to a sequence of bytes @@ -545,7 +545,7 @@ struct SerializationHelperResult wallet_ffi_serialization_helper(const uint8_t * uintptr_t input_instruction_data_size); /** - * Send generic public transaction + * Send generic public transaction. * * # Parameters * - `handle`: Valid pointer to wallet handle @@ -568,11 +568,11 @@ enum WalletFfiError wallet_ffi_send_generic_public_transaction(struct WalletHand uintptr_t account_identities_size, const uint32_t *instruction_words, uintptr_t instruction_words_size, - struct FfiProgramWithDependencies program_with_dependencies, + const struct FfiProgramWithDependencies *program_with_dependencies, struct FfiTransactionResult *out_result); /** - * Send generic private transaction + * Send generic private transaction. * * # Parameters * - `handle`: Valid pointer to wallet handle @@ -595,9 +595,18 @@ enum WalletFfiError wallet_ffi_send_generic_private_transaction(struct WalletHan uintptr_t account_identities_size, const uint32_t *instruction_words, uintptr_t instruction_words_size, - struct FfiProgramWithDependencies program_with_dependencies, + const struct FfiProgramWithDependencies *program_with_dependencies, struct FfiTransactionResult *out_result); +/** + * Free a transaction result returned by `wallet_ffi_send_generic_public_transaction` or + * `wallet_ffi_send_generic_private_transaction`. + * + * # Safety + * The result must be either null or a valid result from a transaction function. + */ +void wallet_ffi_free_transaction_result(struct FfiTransactionResult *result); + /** * Get the public key for a public account. *