From 7c0701bb7d25c0eb53f553952cf7f3da7a464681 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Wed, 3 Jun 2026 14:51:15 +0300 Subject: [PATCH] fix(wallet_ffi): suggestions fix 2 --- nssa/src/program.rs | 4 +- wallet-ffi/src/generic_transaction.rs | 65 +++++++++++++++++++++++---- wallet-ffi/src/keys.rs | 20 +++++++++ wallet-ffi/wallet_ffi.h | 16 +++++-- wallet/src/account_manager.rs | 63 +++++++++++++++++++++++++- 5 files changed, 153 insertions(+), 15 deletions(-) diff --git a/nssa/src/program.rs b/nssa/src/program.rs index c624af3b..adc69b3f 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -22,8 +22,8 @@ const MAX_NUM_CYCLES_PUBLIC_EXECUTION: u64 = 1024 * 1024 * 32; // 32M cycles #[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct Program { - pub id: ProgramId, - pub elf: Vec, + id: ProgramId, + elf: Vec, } impl Program { diff --git a/wallet-ffi/src/generic_transaction.rs b/wallet-ffi/src/generic_transaction.rs index 9ec3db33..a6a806c8 100644 --- a/wallet-ffi/src/generic_transaction.rs +++ b/wallet-ffi/src/generic_transaction.rs @@ -14,13 +14,13 @@ use crate::{ }; #[repr(C)] -pub struct SerializationHelperResult { +pub struct FfiInstructionWords { pub instruction_words: *mut u32, pub instruction_words_size: usize, pub error: WalletFfiError, } -impl SerializationHelperResult { +impl FfiInstructionWords { const fn from_err(error: WalletFfiError) -> Self { Self { instruction_words: std::ptr::null_mut(), @@ -57,8 +57,9 @@ impl TryFrom<&FfiProgram> for Program { impl From for FfiProgram { fn from(value: Program) -> Self { - let elf_size = value.elf.len(); - let elf_data = Box::into_raw(value.elf.into_boxed_slice()) as *const u8; + let elf_clone = value.elf().to_vec(); + let elf_size = elf_clone.len(); + let elf_data = Box::into_raw(elf_clone.into_boxed_slice()) as *const u8; Self { elf_data, elf_size } } @@ -157,10 +158,10 @@ impl Default for FfiTransactionResult { pub unsafe extern "C" fn wallet_ffi_serialization_helper( input_instruction_data: *const u8, input_instruction_data_size: usize, -) -> SerializationHelperResult { +) -> FfiInstructionWords { if input_instruction_data.is_null() { print_error("Null input pointer for instruction_data"); - return SerializationHelperResult::from_err(WalletFfiError::NullPointer); + return FfiInstructionWords::from_err(WalletFfiError::NullPointer); } let input_slice = @@ -172,7 +173,7 @@ pub unsafe extern "C" fn wallet_ffi_serialization_helper( WalletFfiError::SerializationError }) { Ok(res) => res, - Err(err) => return SerializationHelperResult::from_err(err), + Err(err) => return FfiInstructionWords::from_err(err), }; // The resulting vec contains len as prefix @@ -182,7 +183,7 @@ pub unsafe extern "C" fn wallet_ffi_serialization_helper( let res_boxed = res_vec_u32.into_boxed_slice(); let res_ptr = Box::into_raw(res_boxed).cast::(); - SerializationHelperResult { + FfiInstructionWords { instruction_words: res_ptr, instruction_words_size: res_len, error: WalletFfiError::Success, @@ -418,3 +419,51 @@ pub unsafe extern "C" fn wallet_ffi_free_transaction_result(result: *mut FfiTran } } } + +/// Free a instruction words returned by `wallet_ffi_serialization_helper`. +/// +/// # Safety +/// The result must be either null or a valid result from a serialization helper function. +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_free_instruction_words(words: *mut FfiInstructionWords) { + if words.is_null() { + return; + } + + unsafe { + let words = &*words; + + if !words.instruction_words.is_null() { + let words = std::slice::from_raw_parts_mut( + words.instruction_words, + words.instruction_words_size, + ); + drop(Box::from_raw(std::ptr::from_mut::<[u32]>(words))); + } + } +} + +#[cfg(test)] +mod tests { + use nssa::program::Program; + + use crate::generic_transaction::FfiProgram; + + #[test] + fn program_cast_consistency() { + let prog = Program::amm(); + + let first_5_bytes = prog.elf()[..5].to_vec(); + + let ffi_prog: FfiProgram = prog.into(); + + assert!(!ffi_prog.elf_data.is_null()); + + let mut ffi_first_5_bytes = vec![]; + for i in 0..5 { + ffi_first_5_bytes.push(unsafe { *ffi_prog.elf_data.add(i) }); + } + + assert_eq!(ffi_first_5_bytes, first_5_bytes); + } +} diff --git a/wallet-ffi/src/keys.rs b/wallet-ffi/src/keys.rs index ae7fdc36..0d361f4b 100644 --- a/wallet-ffi/src/keys.rs +++ b/wallet-ffi/src/keys.rs @@ -376,3 +376,23 @@ pub unsafe extern "C" fn wallet_ffi_free_account_identity( } } } + +#[cfg(test)] +mod tests { + use nssa::AccountId; + use wallet::AccountIdentity; + + use crate::{keys::wallet_ffi_free_account_identity, FfiAccountIdentity}; + + #[test] + fn acc_identity_correct_free() { + let acc_identity = AccountIdentity::Public(AccountId::new([42; 32])); + let mut ffi_acc_identity: FfiAccountIdentity = acc_identity.into(); + + unsafe { + wallet_ffi_free_account_identity(&raw mut ffi_acc_identity); + } + + assert!(ffi_acc_identity.viewing_public_key.is_null()); + } +} diff --git a/wallet-ffi/wallet_ffi.h b/wallet-ffi/wallet_ffi.h index 3b31516a..071d5452 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -218,11 +218,11 @@ typedef struct FfiAccount { struct FfiU128 nonce; } FfiAccount; -typedef struct SerializationHelperResult { +typedef struct FfiInstructionWords { uint32_t *instruction_words; uintptr_t instruction_words_size; enum WalletFfiError error; -} SerializationHelperResult; +} FfiInstructionWords; /** * Struct representing an account identity, given to `AccountManager` at intialization. @@ -541,8 +541,8 @@ enum WalletFfiError wallet_ffi_import_private_account(struct WalletHandle *handl * # Safety * - `input_instruction_data` must be a valid pointer */ -struct SerializationHelperResult wallet_ffi_serialization_helper(const uint8_t *input_instruction_data, - uintptr_t input_instruction_data_size); +struct FfiInstructionWords wallet_ffi_serialization_helper(const uint8_t *input_instruction_data, + uintptr_t input_instruction_data_size); /** * Send generic public transaction. @@ -607,6 +607,14 @@ enum WalletFfiError wallet_ffi_send_generic_private_transaction(struct WalletHan */ void wallet_ffi_free_transaction_result(struct FfiTransactionResult *result); +/** + * Free a instruction words returned by `wallet_ffi_serialization_helper`. + * + * # Safety + * The result must be either null or a valid result from a serialization helper function. + */ +void wallet_ffi_free_instruction_words(struct FfiInstructionWords *words); + /** * Get the public key for a public account. * diff --git a/wallet/src/account_manager.rs b/wallet/src/account_manager.rs index 3934a2a5..3d811ae7 100644 --- a/wallet/src/account_manager.rs +++ b/wallet/src/account_manager.rs @@ -1,3 +1,5 @@ +use core::fmt; + use anyhow::Result; use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; use nssa::{AccountId, PrivateKey}; @@ -10,7 +12,7 @@ use nssa_core::{ use crate::{ExecutionFailureKind, WalletCore}; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] pub enum AccountIdentity { Public(AccountId), /// A public account without signing. Would not try to sign, even if account is owned. @@ -52,6 +54,65 @@ pub enum AccountIdentity { }, } +impl fmt::Debug for AccountIdentity { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Public(id) => f.debug_tuple("Public").field(id).finish(), + Self::PublicNoSign(id) => f.debug_tuple("PublicNoSign").field(id).finish(), + Self::PrivateOwned(id) => f.debug_tuple("PrivateOwned").field(id).finish(), + Self::PrivateForeign { + npk, + vpk, + identifier, + } => f + .debug_struct("PrivateForeign") + .field("npk", npk) + .field("vpk", vpk) + .field("identifier", identifier) + .finish(), + Self::PrivatePdaOwned(id) => f.debug_tuple("PrivatePdaOwned").field(id).finish(), + Self::PrivatePdaForeign { + account_id, + npk, + vpk, + identifier, + } => f + .debug_struct("PrivatePdaForeign") + .field("account_id", account_id) + .field("npk", npk) + .field("vpk", vpk) + .field("identifier", identifier) + .finish(), + Self::PrivateShared { + npk, + vpk, + identifier, + .. + } => f + .debug_struct("PrivateShared") + .field("nsk", &"") + .field("npk", npk) + .field("vpk", vpk) + .field("identifier", identifier) + .finish(), + Self::PrivatePdaShared { + account_id, + npk, + vpk, + identifier, + .. + } => f + .debug_struct("PrivatePdaShared") + .field("account_id", account_id) + .field("nsk", &"") + .field("npk", npk) + .field("vpk", vpk) + .field("identifier", identifier) + .finish(), + } + } +} + impl AccountIdentity { #[must_use] /// Note: `PublicNoSign` still counts as public, the variant just suppresses the signing-key