add ffi get account test

This commit is contained in:
Sergio Chouhy 2026-02-04 11:20:01 -03:00
parent 50b253fa00
commit 5ac9953488
7 changed files with 132 additions and 48 deletions

View File

@ -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(())
}

View File

@ -14,7 +14,7 @@ mod state;
pub use nssa_core::{
SharedSecretKey,
account::{Account, AccountId},
account::{Account, AccountId, Data},
encryption::EphemeralPublicKey,
program::ProgramId,
};

View File

@ -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<u8> = 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

View File

@ -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,
}

View File

@ -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<FfiBytes32> for nssa::AccountId {
nssa::AccountId::new(bytes.data)
}
}
impl From<nssa::Account> for FfiAccount {
fn from(value: nssa::Account) -> Self {
// Convert account data to FFI type
let data_vec: Vec<u8> = 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<Self, Self::Error> {
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,
})
}
}

View File

@ -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)
*/

View File

@ -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(),