From 5ac9953488b4878f70b8c21b774a61a4e13775db Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Wed, 4 Feb 2026 11:20:01 -0300 Subject: [PATCH] add ffi get account test --- integration_tests/tests/wallet_ffi.rs | 92 ++++++++++++++++++++------- nssa/src/lib.rs | 2 +- wallet-ffi/src/account.rs | 24 +------ wallet-ffi/src/error.rs | 2 + wallet-ffi/src/types.rs | 55 +++++++++++++++- wallet-ffi/wallet_ffi.h | 4 ++ wallet/src/lib.rs | 1 - 7 files changed, 132 insertions(+), 48 deletions(-) diff --git a/integration_tests/tests/wallet_ffi.rs b/integration_tests/tests/wallet_ffi.rs index 90a3feaa..a95ab319 100644 --- a/integration_tests/tests/wallet_ffi.rs +++ b/integration_tests/tests/wallet_ffi.rs @@ -6,10 +6,11 @@ use std::{ use anyhow::Result; use integration_tests::{ACC_SENDER, TestContext}; -use nssa::AccountId; +use log::info; +use nssa::{Account, AccountId, program::Program}; use tempfile::tempdir; use wallet::WalletCore; -use wallet_ffi::{FfiAccountList, FfiBytes32, WalletHandle, error}; +use wallet_ffi::{FfiAccount, FfiAccountList, FfiBytes32, WalletHandle, error}; unsafe extern "C" { fn wallet_ffi_create_new( @@ -41,11 +42,20 @@ unsafe extern "C" { is_public: bool, out_balance: *mut [u8; 16], ) -> error::WalletFfiError; + + fn wallet_ffi_get_account_public( + handle: *mut WalletHandle, + account_id: *const FfiBytes32, + out_account: *mut FfiAccount, + ) -> error::WalletFfiError; + + fn wallet_ffi_free_account_data(account: *mut FfiAccount); } -fn new_wallet_from_test_context(ctx: &TestContext) -> *mut WalletHandle { +fn new_wallet_ffi_with_test_context_config(ctx: &TestContext) -> *mut WalletHandle { let tempdir = tempfile::tempdir().unwrap(); - let temp_config_path = tempdir.path().join("wallet_config.json"); + let config_path = tempdir.path().join("wallet_config.json"); + let storage_path = tempdir.path().join("storage.json"); let mut config = ctx.wallet().config().to_owned(); if let Some(config_overrides) = ctx.wallet().config_overrides().clone() { config.apply_overrides(config_overrides); @@ -54,15 +64,15 @@ fn new_wallet_from_test_context(ctx: &TestContext) -> *mut WalletHandle { .write(true) .create(true) .truncate(true) - .open(&temp_config_path) + .open(&config_path) .unwrap(); let config_with_overrides_serialized = serde_json::to_vec_pretty(&config).unwrap(); file.write_all(&config_with_overrides_serialized).unwrap(); - let config_path = CString::new(temp_config_path.to_str().unwrap()).unwrap(); - let storage_path = CString::new(ctx.wallet().storage_path().to_str().unwrap()).unwrap(); + let config_path = CString::new(config_path.to_str().unwrap()).unwrap(); + let storage_path = CString::new(storage_path.to_str().unwrap()).unwrap(); let password = CString::new(ctx.wallet_password()).unwrap(); unsafe { @@ -74,7 +84,7 @@ fn new_wallet_from_test_context(ctx: &TestContext) -> *mut WalletHandle { } } -fn new_wallet_ffi_on_tempdir_for_tests(password: &str) -> *mut WalletHandle { +fn new_wallet_ffi_with_default_config(password: &str) -> *mut WalletHandle { let tempdir = tempdir().unwrap(); let config_path = tempdir.path().join("wallet_config.json"); let storage_path = tempdir.path().join("storage.json"); @@ -91,7 +101,7 @@ fn new_wallet_ffi_on_tempdir_for_tests(password: &str) -> *mut WalletHandle { } } -fn new_wallet_rust_for_tests(password: &str) -> WalletCore { +fn new_wallet_rust_with_default_config(password: &str) -> WalletCore { let tempdir = tempdir().unwrap(); let config_path = tempdir.path().join("wallet_config.json"); let storage_path = tempdir.path().join("storage.json"); @@ -106,14 +116,14 @@ fn new_wallet_rust_for_tests(password: &str) -> WalletCore { } #[test] -fn test_create_public_accounts() { +fn test_wallet_ffi_create_public_accounts() { let password = "password_for_tests"; let n_accounts = 10; // First `n_accounts` public accounts created with Rust wallet let new_public_account_ids_rust = { let mut account_ids = Vec::new(); - let mut wallet_rust = new_wallet_rust_for_tests(password); + let mut wallet_rust = new_wallet_rust_with_default_config(password); for _ in 0..n_accounts { let account_id = wallet_rust.create_new_account_public(None).0; account_ids.push(*account_id.value()); @@ -125,7 +135,7 @@ fn test_create_public_accounts() { let new_public_account_ids_ffi = unsafe { let mut account_ids = Vec::new(); - let wallet_ffi_handle = new_wallet_ffi_on_tempdir_for_tests(password); + let wallet_ffi_handle = new_wallet_ffi_with_default_config(password); for _ in 0..n_accounts { let mut out_account_id = FfiBytes32::from_bytes([0; 32]); wallet_ffi_create_account_public( @@ -141,14 +151,14 @@ fn test_create_public_accounts() { } #[test] -fn test_create_private_accounts() { +fn test_wallet_ffi_create_private_accounts() { let password = "password_for_tests"; let n_accounts = 10; // First `n_accounts` private accounts created with Rust wallet let new_private_account_ids_rust = { let mut account_ids = Vec::new(); - let mut wallet_rust = new_wallet_rust_for_tests(password); + let mut wallet_rust = new_wallet_rust_with_default_config(password); for _ in 0..n_accounts { let account_id = wallet_rust.create_new_account_private(None).0; account_ids.push(*account_id.value()); @@ -160,7 +170,7 @@ fn test_create_private_accounts() { let new_private_account_ids_ffi = unsafe { let mut account_ids = Vec::new(); - let wallet_ffi_handle = new_wallet_ffi_on_tempdir_for_tests(password); + let wallet_ffi_handle = new_wallet_ffi_with_default_config(password); for _ in 0..n_accounts { let mut out_account_id = FfiBytes32::from_bytes([0; 32]); wallet_ffi_create_account_private( @@ -176,12 +186,12 @@ fn test_create_private_accounts() { } #[test] -fn test_list_accounts() { +fn test_wallet_ffi_list_accounts() { let password = "password_for_tests"; // Create the wallet FFI let wallet_ffi_handle = unsafe { - let handle = new_wallet_ffi_on_tempdir_for_tests(password); + let handle = new_wallet_ffi_with_default_config(password); // Create 5 public accounts and 5 private accounts for _ in 0..5 { let mut out_account_id = FfiBytes32::from_bytes([0; 32]); @@ -194,7 +204,7 @@ fn test_list_accounts() { // Create the wallet Rust let wallet_rust = { - let mut wallet = new_wallet_rust_for_tests(password); + let mut wallet = new_wallet_rust_with_default_config(password); // Create 5 public accounts and 5 private accounts for _ in 0..5 { wallet.create_new_account_public(None); @@ -265,21 +275,57 @@ fn test_list_accounts() { fn test_wallet_ffi_get_balance_public() -> Result<()> { let ctx = TestContext::new_blocking()?; let account_id: AccountId = ACC_SENDER.parse().unwrap(); - let wallet_ffi_handle = new_wallet_from_test_context(&ctx); + let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx); let balance = unsafe { let mut out_balance: [u8; 16] = [0; 16]; - let ffi_account_id = FfiBytes32::from_bytes(*account_id.value()); - let result = wallet_ffi_get_balance( + let ffi_account_id = FfiBytes32::from(&account_id); + let _result = wallet_ffi_get_balance( wallet_ffi_handle, (&ffi_account_id) as *const FfiBytes32, true, (&mut out_balance) as *mut [u8; 16], ); - println!("result: {:?}", result); out_balance }; - println!("balance: {balance:?}"); + let balance = u128::from_le_bytes(balance); + assert_eq!(balance, 10000); + + info!("Successfully retrieved account balance"); + + Ok(()) +} + +#[test] +fn test_wallet_ffi_get_account_public() -> Result<()> { + let ctx = TestContext::new_blocking()?; + let account_id: AccountId = ACC_SENDER.parse().unwrap(); + let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx); + let mut out_account = FfiAccount::default(); + + let account: Account = unsafe { + let ffi_account_id = FfiBytes32::from(&account_id); + let _result = wallet_ffi_get_account_public( + wallet_ffi_handle, + (&ffi_account_id) as *const FfiBytes32, + (&mut out_account) as *mut FfiAccount, + ); + (&out_account).try_into().unwrap() + }; + + assert_eq!( + account.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!(account.balance, 10000); + assert!(account.data.is_empty()); + assert_eq!(account.nonce, 0); + + unsafe { + wallet_ffi_free_account_data((&mut out_account) as *mut FfiAccount); + } + + info!("Successfully retrieved account with correct details"); Ok(()) } diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index de4b65b2..47a0eadb 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -14,7 +14,7 @@ mod state; pub use nssa_core::{ SharedSecretKey, - account::{Account, AccountId}, + account::{Account, AccountId, Data}, encryption::EphemeralPublicKey, program::ProgramId, }; diff --git a/wallet-ffi/src/account.rs b/wallet-ffi/src/account.rs index e5fa84b0..d96369d0 100644 --- a/wallet-ffi/src/account.rs +++ b/wallet-ffi/src/account.rs @@ -7,9 +7,7 @@ use nssa::AccountId; use crate::{ block_on, error::{print_error, WalletFfiError}, - types::{ - FfiAccount, FfiAccountList, FfiAccountListEntry, FfiBytes32, FfiProgramId, WalletHandle, - }, + types::{FfiAccount, FfiAccountList, FfiAccountListEntry, FfiBytes32, WalletHandle}, wallet::get_wallet, }; @@ -350,26 +348,8 @@ pub unsafe extern "C" fn wallet_ffi_get_account_public( Err(e) => return e, }; - // Convert account data to FFI type - let data_vec: Vec = account.data.into(); - let data_len = data_vec.len(); - let data_ptr = if data_len > 0 { - let data_boxed = data_vec.into_boxed_slice(); - Box::into_raw(data_boxed) as *const u8 - } else { - ptr::null() - }; - - let program_owner = FfiProgramId { - data: account.program_owner, - }; - unsafe { - (*out_account).program_owner = program_owner; - (*out_account).balance = account.balance.to_le_bytes(); - (*out_account).nonce = account.nonce.to_le_bytes(); - (*out_account).data = data_ptr; - (*out_account).data_len = data_len; + *out_account = account.into(); } WalletFfiError::Success diff --git a/wallet-ffi/src/error.rs b/wallet-ffi/src/error.rs index ea366475..c57505a8 100644 --- a/wallet-ffi/src/error.rs +++ b/wallet-ffi/src/error.rs @@ -36,6 +36,8 @@ pub enum WalletFfiError { SyncError = 13, /// Serialization/deserialization error SerializationError = 14, + /// Found invalid data for account + InvalidAccountData, /// Internal error (catch-all) InternalError = 99, } diff --git a/wallet-ffi/src/types.rs b/wallet-ffi/src/types.rs index 3bcfd9fd..e7629afc 100644 --- a/wallet-ffi/src/types.rs +++ b/wallet-ffi/src/types.rs @@ -1,6 +1,11 @@ //! C-compatible type definitions for the FFI layer. -use std::ffi::c_char; +use core::slice; +use std::{ffi::c_char, ptr}; + +use nssa::{Account, Data}; + +use crate::error::WalletFfiError; /// Opaque pointer to the Wallet instance. /// @@ -149,3 +154,51 @@ impl From for nssa::AccountId { nssa::AccountId::new(bytes.data) } } + +impl From for FfiAccount { + fn from(value: nssa::Account) -> Self { + // Convert account data to FFI type + let data_vec: Vec = value.data.into(); + let data_len = data_vec.len(); + let data = if data_len > 0 { + let data_boxed = data_vec.into_boxed_slice(); + Box::into_raw(data_boxed) as *const u8 + } else { + ptr::null() + }; + + let program_owner = FfiProgramId { + data: value.program_owner, + }; + FfiAccount { + program_owner, + balance: value.balance.to_le_bytes(), + data, + data_len, + nonce: value.nonce.to_le_bytes(), + } + } +} + +impl TryFrom<&FfiAccount> for nssa::Account { + type Error = WalletFfiError; + + fn try_from(value: &FfiAccount) -> Result { + let data = if value.data_len > 0 { + unsafe { + let slice = slice::from_raw_parts(value.data, value.data_len); + Data::try_from(slice.to_vec()).map_err(|_| WalletFfiError::InvalidAccountData)? + } + } else { + Data::default() + }; + let balance = u128::from_le_bytes(value.balance); + let nonce = u128::from_le_bytes(value.nonce); + Ok(Account { + program_owner: value.program_owner.data, + balance, + data, + nonce, + }) + } +} diff --git a/wallet-ffi/wallet_ffi.h b/wallet-ffi/wallet_ffi.h index 70229e96..9b1850d8 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -95,6 +95,10 @@ typedef enum WalletFfiError { * Serialization/deserialization error */ SERIALIZATION_ERROR = 14, + /** + * Found invalid data for account + */ + INVALID_ACCOUNT_DATA, /** * Internal error (catch-all) */ diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index cdefd380..22e3be08 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -191,7 +191,6 @@ impl WalletCore { config.apply_overrides(config_overrides); } - println!("sequencer url: {}", config.sequencer_addr); let sequencer_client = Arc::new(SequencerClient::new_with_auth( Url::parse(&config.sequencer_addr)?, config.basic_auth.clone(),