diff --git a/wallet-ffi/src/account.rs b/wallet-ffi/src/account.rs index db668e75..a058acc7 100644 --- a/wallet-ffi/src/account.rs +++ b/wallet-ffi/src/account.rs @@ -5,7 +5,7 @@ use std::ptr; use nssa::AccountId; use crate::block_on; -use crate::error::{set_last_error, WalletFfiError}; +use crate::error::{print_error, WalletFfiError}; use crate::types::{ FfiAccount, FfiAccountList, FfiAccountListEntry, FfiBytes32, FfiProgramId, WalletHandle, }; @@ -23,8 +23,12 @@ use crate::wallet::get_wallet; /// # Returns /// - `Success` on successful creation /// - Error code on failure +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `out_account_id` must be a valid pointer to a `FfiBytes32` struct #[no_mangle] -pub extern "C" fn wallet_ffi_create_account_public( +pub unsafe extern "C" fn wallet_ffi_create_account_public( handle: *mut WalletHandle, out_account_id: *mut FfiBytes32, ) -> WalletFfiError { @@ -34,14 +38,14 @@ pub extern "C" fn wallet_ffi_create_account_public( }; if out_account_id.is_null() { - set_last_error("Null output pointer for account_id"); + print_error("Null output pointer for account_id"); return WalletFfiError::NullPointer; } let mut wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -67,8 +71,12 @@ pub extern "C" fn wallet_ffi_create_account_public( /// # Returns /// - `Success` on successful creation /// - Error code on failure +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `out_account_id` must be a valid pointer to a `FfiBytes32` struct #[no_mangle] -pub extern "C" fn wallet_ffi_create_account_private( +pub unsafe extern "C" fn wallet_ffi_create_account_private( handle: *mut WalletHandle, out_account_id: *mut FfiBytes32, ) -> WalletFfiError { @@ -78,14 +86,14 @@ pub extern "C" fn wallet_ffi_create_account_private( }; if out_account_id.is_null() { - set_last_error("Null output pointer for account_id"); + print_error("Null output pointer for account_id"); return WalletFfiError::NullPointer; } let mut wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -113,8 +121,12 @@ pub extern "C" fn wallet_ffi_create_account_private( /// /// # Memory /// The returned list must be freed with `wallet_ffi_free_account_list()`. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `out_list` must be a valid pointer to a `FfiAccountList` struct #[no_mangle] -pub extern "C" fn wallet_ffi_list_accounts( +pub unsafe extern "C" fn wallet_ffi_list_accounts( handle: *mut WalletHandle, out_list: *mut FfiAccountList, ) -> WalletFfiError { @@ -124,14 +136,14 @@ pub extern "C" fn wallet_ffi_list_accounts( }; if out_list.is_null() { - set_last_error("Null output pointer for account list"); + print_error("Null output pointer for account list"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -196,7 +208,7 @@ pub extern "C" fn wallet_ffi_list_accounts( /// # Safety /// The list must be either null or a valid list returned by `wallet_ffi_list_accounts`. #[no_mangle] -pub extern "C" fn wallet_ffi_free_account_list(list: *mut FfiAccountList) { +pub unsafe extern "C" fn wallet_ffi_free_account_list(list: *mut FfiAccountList) { if list.is_null() { return; } @@ -224,8 +236,13 @@ pub extern "C" fn wallet_ffi_free_account_list(list: *mut FfiAccountList) { /// # Returns /// - `Success` on successful query /// - Error code on failure +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `account_id` must be a valid pointer to a `FfiBytes32` struct +/// - `out_balance` must be a valid pointer to a `[u8; 16]` array #[no_mangle] -pub extern "C" fn wallet_ffi_get_balance( +pub unsafe extern "C" fn wallet_ffi_get_balance( handle: *mut WalletHandle, account_id: *const FfiBytes32, is_public: bool, @@ -237,14 +254,14 @@ pub extern "C" fn wallet_ffi_get_balance( }; if account_id.is_null() || out_balance.is_null() { - set_last_error("Null pointer argument"); + print_error("Null pointer argument"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -255,7 +272,7 @@ pub extern "C" fn wallet_ffi_get_balance( match block_on(wallet.get_account_balance(account_id)) { Ok(Ok(b)) => b, Ok(Err(e)) => { - set_last_error(format!("Failed to get balance: {}", e)); + print_error(format!("Failed to get balance: {}", e)); return WalletFfiError::NetworkError; } Err(e) => return e, @@ -264,7 +281,7 @@ pub extern "C" fn wallet_ffi_get_balance( match wallet.get_account_private(&account_id) { Some(account) => account.balance, None => { - set_last_error("Private account not found"); + print_error("Private account not found"); return WalletFfiError::AccountNotFound; } } @@ -290,8 +307,13 @@ pub extern "C" fn wallet_ffi_get_balance( /// /// # Memory /// The account data must be freed with `wallet_ffi_free_account_data()`. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `account_id` must be a valid pointer to a `FfiBytes32` struct +/// - `out_account` must be a valid pointer to a `FfiAccount` struct #[no_mangle] -pub extern "C" fn wallet_ffi_get_account_public( +pub unsafe extern "C" fn wallet_ffi_get_account_public( handle: *mut WalletHandle, account_id: *const FfiBytes32, out_account: *mut FfiAccount, @@ -302,14 +324,14 @@ pub extern "C" fn wallet_ffi_get_account_public( }; if account_id.is_null() || out_account.is_null() { - set_last_error("Null pointer argument"); + print_error("Null pointer argument"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -319,7 +341,7 @@ pub extern "C" fn wallet_ffi_get_account_public( let account = match block_on(wallet.get_account_public(account_id)) { Ok(Ok(a)) => a, Ok(Err(e)) => { - set_last_error(format!("Failed to get account: {}", e)); + print_error(format!("Failed to get account: {}", e)); return WalletFfiError::NetworkError; } Err(e) => return e, @@ -356,7 +378,7 @@ pub extern "C" fn wallet_ffi_get_account_public( /// The account must be either null or a valid account returned by /// `wallet_ffi_get_account_public`. #[no_mangle] -pub extern "C" fn wallet_ffi_free_account_data(account: *mut FfiAccount) { +pub unsafe extern "C" fn wallet_ffi_free_account_data(account: *mut FfiAccount) { if account.is_null() { return; } diff --git a/wallet-ffi/src/error.rs b/wallet-ffi/src/error.rs index 46646259..ea366475 100644 --- a/wallet-ffi/src/error.rs +++ b/wallet-ffi/src/error.rs @@ -1,10 +1,6 @@ //! Error handling for the FFI layer. //! -//! Uses numeric error codes with a thread-local last error message. - -use std::cell::RefCell; -use std::ffi::{c_char, CString}; -use std::ptr; +//! Uses numeric error codes with error messages printed to stderr. /// Error codes returned by FFI functions. #[repr(C)] @@ -44,50 +40,7 @@ pub enum WalletFfiError { InternalError = 99, } -// Thread-local storage for the last error message -thread_local! { - static LAST_ERROR: RefCell> = const { RefCell::new(None) }; -} - -/// Set the last error message for the current thread. -pub fn set_last_error(msg: impl Into) { - LAST_ERROR.with(|e| { - *e.borrow_mut() = Some(msg.into()); - }); -} - -/// Clear the last error message. -pub fn clear_last_error() { - LAST_ERROR.with(|e| { - *e.borrow_mut() = None; - }); -} - -/// Get the last error message. -/// -/// Returns a pointer to a null-terminated string, or null if no error is set. -/// The caller owns the returned string and must free it with -/// `wallet_ffi_free_error_string`. -#[no_mangle] -pub extern "C" fn wallet_ffi_get_last_error() -> *mut c_char { - LAST_ERROR.with(|e| match e.borrow_mut().take() { - Some(msg) => CString::new(msg) - .map(|s| s.into_raw()) - .unwrap_or(ptr::null_mut()), - None => ptr::null_mut(), - }) -} - -/// Free an error string returned by `wallet_ffi_get_last_error`. -/// -/// # Safety -/// The pointer must be either null or a valid pointer returned by -/// `wallet_ffi_get_last_error`. -#[no_mangle] -pub extern "C" fn wallet_ffi_free_error_string(ptr: *mut c_char) { - if !ptr.is_null() { - unsafe { - drop(CString::from_raw(ptr)); - } - } +/// Log an error message to stderr. +pub fn print_error(msg: impl Into) { + eprintln!("[wallet-ffi] {}", msg.into()); } diff --git a/wallet-ffi/src/keys.rs b/wallet-ffi/src/keys.rs index 779dbce1..27989c0e 100644 --- a/wallet-ffi/src/keys.rs +++ b/wallet-ffi/src/keys.rs @@ -4,7 +4,7 @@ use std::ptr; use nssa::{AccountId, PublicKey}; -use crate::error::{set_last_error, WalletFfiError}; +use crate::error::{print_error, WalletFfiError}; use crate::types::{FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey, WalletHandle}; use crate::wallet::get_wallet; @@ -21,8 +21,13 @@ use crate::wallet::get_wallet; /// - `Success` on successful retrieval /// - `KeyNotFound` if the account's key is not in this wallet /// - Error code on other failures +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `account_id` must be a valid pointer to a `FfiBytes32` struct +/// - `out_public_key` must be a valid pointer to a `FfiPublicAccountKey` struct #[no_mangle] -pub extern "C" fn wallet_ffi_get_public_account_key( +pub unsafe extern "C" fn wallet_ffi_get_public_account_key( handle: *mut WalletHandle, account_id: *const FfiBytes32, out_public_key: *mut FfiPublicAccountKey, @@ -33,14 +38,14 @@ pub extern "C" fn wallet_ffi_get_public_account_key( }; if account_id.is_null() || out_public_key.is_null() { - set_last_error("Null pointer argument"); + print_error("Null pointer argument"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -50,7 +55,7 @@ pub extern "C" fn wallet_ffi_get_public_account_key( let private_key = match wallet.get_account_public_signing_key(&account_id) { Some(k) => k, None => { - set_last_error("Public account key not found in wallet"); + print_error("Public account key not found in wallet"); return WalletFfiError::KeyNotFound; } }; @@ -81,8 +86,13 @@ pub extern "C" fn wallet_ffi_get_public_account_key( /// /// # Memory /// The keys structure must be freed with `wallet_ffi_free_private_account_keys()`. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `account_id` must be a valid pointer to a `FfiBytes32` struct +/// - `out_keys` must be a valid pointer to a `FfiPrivateAccountKeys` struct #[no_mangle] -pub extern "C" fn wallet_ffi_get_private_account_keys( +pub unsafe extern "C" fn wallet_ffi_get_private_account_keys( handle: *mut WalletHandle, account_id: *const FfiBytes32, out_keys: *mut FfiPrivateAccountKeys, @@ -93,14 +103,14 @@ pub extern "C" fn wallet_ffi_get_private_account_keys( }; if account_id.is_null() || out_keys.is_null() { - set_last_error("Null pointer argument"); + print_error("Null pointer argument"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -110,7 +120,7 @@ pub extern "C" fn wallet_ffi_get_private_account_keys( let (key_chain, _account) = match wallet.storage().user_data.get_private_account(&account_id) { Some(k) => k, None => { - set_last_error("Private account not found in wallet"); + print_error("Private account not found in wallet"); return WalletFfiError::AccountNotFound; } }; @@ -140,7 +150,7 @@ pub extern "C" fn wallet_ffi_get_private_account_keys( /// The keys must be either null or valid keys returned by /// `wallet_ffi_get_private_account_keys`. #[no_mangle] -pub extern "C" fn wallet_ffi_free_private_account_keys(keys: *mut FfiPrivateAccountKeys) { +pub unsafe extern "C" fn wallet_ffi_free_private_account_keys(keys: *mut FfiPrivateAccountKeys) { if keys.is_null() { return; } @@ -168,12 +178,15 @@ pub extern "C" fn wallet_ffi_free_private_account_keys(keys: *mut FfiPrivateAcco /// /// # Memory /// The returned string must be freed with `wallet_ffi_free_string()`. +/// +/// # Safety +/// - `account_id` must be a valid pointer to a `FfiBytes32` struct #[no_mangle] -pub extern "C" fn wallet_ffi_account_id_to_base58( +pub unsafe extern "C" fn wallet_ffi_account_id_to_base58( account_id: *const FfiBytes32, ) -> *mut std::ffi::c_char { if account_id.is_null() { - set_last_error("Null account_id pointer"); + print_error("Null account_id pointer"); return ptr::null_mut(); } @@ -183,7 +196,7 @@ pub extern "C" fn wallet_ffi_account_id_to_base58( match std::ffi::CString::new(base58_str) { Ok(s) => s.into_raw(), Err(e) => { - set_last_error(format!("Failed to create C string: {}", e)); + print_error(format!("Failed to create C string: {}", e)); ptr::null_mut() } } @@ -199,13 +212,17 @@ pub extern "C" fn wallet_ffi_account_id_to_base58( /// - `Success` on successful parsing /// - `InvalidAccountId` if the string is not valid Base58 /// - Error code on other failures +/// +/// # Safety +/// - `base58_str` must be a valid pointer to a null-terminated C string +/// - `out_account_id` must be a valid pointer to a `FfiBytes32` struct #[no_mangle] -pub extern "C" fn wallet_ffi_account_id_from_base58( +pub unsafe extern "C" fn wallet_ffi_account_id_from_base58( base58_str: *const std::ffi::c_char, out_account_id: *mut FfiBytes32, ) -> WalletFfiError { if base58_str.is_null() || out_account_id.is_null() { - set_last_error("Null pointer argument"); + print_error("Null pointer argument"); return WalletFfiError::NullPointer; } @@ -213,7 +230,7 @@ pub extern "C" fn wallet_ffi_account_id_from_base58( let str_slice = match c_str.to_str() { Ok(s) => s, Err(e) => { - set_last_error(format!("Invalid UTF-8: {}", e)); + print_error(format!("Invalid UTF-8: {}", e)); return WalletFfiError::InvalidUtf8; } }; @@ -221,7 +238,7 @@ pub extern "C" fn wallet_ffi_account_id_from_base58( let account_id: AccountId = match str_slice.parse() { Ok(id) => id, Err(e) => { - set_last_error(format!("Invalid Base58 account ID: {}", e)); + print_error(format!("Invalid Base58 account ID: {}", e)); return WalletFfiError::InvalidAccountId; } }; diff --git a/wallet-ffi/src/lib.rs b/wallet-ffi/src/lib.rs index 2b5713b7..093c2f21 100644 --- a/wallet-ffi/src/lib.rs +++ b/wallet-ffi/src/lib.rs @@ -32,7 +32,7 @@ use once_cell::sync::OnceCell; use std::sync::Arc; use tokio::runtime::Runtime; -use crate::error::{set_last_error, WalletFfiError}; +use crate::error::{print_error, WalletFfiError}; // Re-export public types for cbindgen pub use error::WalletFfiError as FfiError; @@ -44,7 +44,7 @@ static RUNTIME: OnceCell> = OnceCell::new(); /// Get a reference to the global runtime. pub(crate) fn get_runtime() -> Result<&'static Arc, WalletFfiError> { RUNTIME.get().ok_or_else(|| { - set_last_error("Runtime not initialized. Call wallet_ffi_init_runtime() first."); + print_error("Runtime not initialized. Call wallet_ffi_init_runtime() first."); WalletFfiError::RuntimeError }) } @@ -75,7 +75,7 @@ pub extern "C" fn wallet_ffi_init_runtime() -> WalletFfiError { match result { Ok(_) => WalletFfiError::Success, Err(e) => { - set_last_error(format!("Failed to initialize runtime: {}", e)); + print_error(format!("Failed to initialize runtime: {}", e)); WalletFfiError::RuntimeError } } diff --git a/wallet-ffi/src/sync.rs b/wallet-ffi/src/sync.rs index b4e9deb8..d1d7d560 100644 --- a/wallet-ffi/src/sync.rs +++ b/wallet-ffi/src/sync.rs @@ -1,7 +1,7 @@ //! Block synchronization functions. use crate::block_on; -use crate::error::{set_last_error, WalletFfiError}; +use crate::error::{print_error, WalletFfiError}; use crate::types::WalletHandle; use crate::wallet::get_wallet; @@ -22,8 +22,11 @@ use crate::wallet::get_wallet; /// # Note /// This operation can take a while for large block ranges. The wallet /// internally uses a progress bar which may output to stdout. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` #[no_mangle] -pub extern "C" fn wallet_ffi_sync_to_block( +pub unsafe extern "C" fn wallet_ffi_sync_to_block( handle: *mut WalletHandle, block_id: u64, ) -> WalletFfiError { @@ -35,7 +38,7 @@ pub extern "C" fn wallet_ffi_sync_to_block( let mut wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -43,7 +46,7 @@ pub extern "C" fn wallet_ffi_sync_to_block( match block_on(wallet.sync_to_block(block_id)) { Ok(Ok(())) => WalletFfiError::Success, Ok(Err(e)) => { - set_last_error(format!("Sync failed: {}", e)); + print_error(format!("Sync failed: {}", e)); WalletFfiError::SyncError } Err(e) => e, @@ -59,8 +62,12 @@ pub extern "C" fn wallet_ffi_sync_to_block( /// # Returns /// - `Success` on success /// - Error code on failure +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `out_block_id` must be a valid pointer to a `u64` #[no_mangle] -pub extern "C" fn wallet_ffi_get_last_synced_block( +pub unsafe extern "C" fn wallet_ffi_get_last_synced_block( handle: *mut WalletHandle, out_block_id: *mut u64, ) -> WalletFfiError { @@ -70,14 +77,14 @@ pub extern "C" fn wallet_ffi_get_last_synced_block( }; if out_block_id.is_null() { - set_last_error("Null output pointer"); + print_error("Null output pointer"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -99,8 +106,12 @@ pub extern "C" fn wallet_ffi_get_last_synced_block( /// - `Success` on success /// - `NetworkError` if the sequencer is unreachable /// - Error code on other failures +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `out_block_height` must be a valid pointer to a `u64` #[no_mangle] -pub extern "C" fn wallet_ffi_get_current_block_height( +pub unsafe extern "C" fn wallet_ffi_get_current_block_height( handle: *mut WalletHandle, out_block_height: *mut u64, ) -> WalletFfiError { @@ -110,14 +121,14 @@ pub extern "C" fn wallet_ffi_get_current_block_height( }; if out_block_height.is_null() { - set_last_error("Null output pointer"); + print_error("Null output pointer"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -130,7 +141,7 @@ pub extern "C" fn wallet_ffi_get_current_block_height( WalletFfiError::Success } Ok(Err(e)) => { - set_last_error(format!("Failed to get block height: {:?}", e)); + print_error(format!("Failed to get block height: {:?}", e)); WalletFfiError::NetworkError } Err(e) => e, diff --git a/wallet-ffi/src/transfer.rs b/wallet-ffi/src/transfer.rs index e11858da..c8357dcf 100644 --- a/wallet-ffi/src/transfer.rs +++ b/wallet-ffi/src/transfer.rs @@ -8,7 +8,7 @@ use nssa::AccountId; use wallet::program_facades::native_token_transfer::NativeTokenTransfer; use crate::block_on; -use crate::error::{set_last_error, WalletFfiError}; +use crate::error::{print_error, WalletFfiError}; use crate::types::{FfiBytes32, FfiTransferResult, WalletHandle}; use crate::wallet::get_wallet; @@ -31,8 +31,15 @@ use crate::wallet::get_wallet; /// /// # Memory /// The result must be freed with `wallet_ffi_free_transfer_result()`. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `from` must be a valid pointer to a `FfiBytes32` struct +/// - `to` must be a valid pointer to a `FfiBytes32` struct +/// - `amount` must be a valid pointer to a `[u8; 16]` array +/// - `out_result` must be a valid pointer to a `FfiTransferResult` struct #[no_mangle] -pub extern "C" fn wallet_ffi_transfer_public( +pub unsafe extern "C" fn wallet_ffi_transfer_public( handle: *mut WalletHandle, from: *const FfiBytes32, to: *const FfiBytes32, @@ -45,14 +52,14 @@ pub extern "C" fn wallet_ffi_transfer_public( }; if from.is_null() || to.is_null() || amount.is_null() || out_result.is_null() { - set_last_error("Null pointer argument"); + print_error("Null pointer argument"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -76,7 +83,7 @@ pub extern "C" fn wallet_ffi_transfer_public( WalletFfiError::Success } Ok(Err(e)) => { - set_last_error(format!("Transfer failed: {:?}", e)); + print_error(format!("Transfer failed: {:?}", e)); unsafe { (*out_result).tx_hash = ptr::null_mut(); (*out_result).success = false; @@ -109,8 +116,13 @@ pub extern "C" fn wallet_ffi_transfer_public( /// /// # Memory /// The result must be freed with `wallet_ffi_free_transfer_result()`. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `account_id` must be a valid pointer to a `FfiBytes32` struct +/// - `out_result` must be a valid pointer to a `FfiTransferResult` struct #[no_mangle] -pub extern "C" fn wallet_ffi_register_public_account( +pub unsafe extern "C" fn wallet_ffi_register_public_account( handle: *mut WalletHandle, account_id: *const FfiBytes32, out_result: *mut FfiTransferResult, @@ -121,14 +133,14 @@ pub extern "C" fn wallet_ffi_register_public_account( }; if account_id.is_null() || out_result.is_null() { - set_last_error("Null pointer argument"); + print_error("Null pointer argument"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -150,7 +162,7 @@ pub extern "C" fn wallet_ffi_register_public_account( WalletFfiError::Success } Ok(Err(e)) => { - set_last_error(format!("Registration failed: {:?}", e)); + print_error(format!("Registration failed: {:?}", e)); unsafe { (*out_result).tx_hash = ptr::null_mut(); (*out_result).success = false; @@ -172,7 +184,7 @@ pub extern "C" fn wallet_ffi_register_public_account( /// # Safety /// The result must be either null or a valid result from a transfer function. #[no_mangle] -pub extern "C" fn wallet_ffi_free_transfer_result(result: *mut FfiTransferResult) { +pub unsafe extern "C" fn wallet_ffi_free_transfer_result(result: *mut FfiTransferResult) { if result.is_null() { return; } diff --git a/wallet-ffi/src/wallet.rs b/wallet-ffi/src/wallet.rs index 7c70e19d..6c17d930 100644 --- a/wallet-ffi/src/wallet.rs +++ b/wallet-ffi/src/wallet.rs @@ -8,7 +8,7 @@ use std::sync::Mutex; use wallet::WalletCore; use crate::block_on; -use crate::error::{set_last_error, WalletFfiError}; +use crate::error::{print_error, WalletFfiError}; use crate::types::WalletHandle; /// Internal wrapper around WalletCore with mutex for thread safety. @@ -21,7 +21,7 @@ pub(crate) fn get_wallet( handle: *mut WalletHandle, ) -> Result<&'static WalletWrapper, WalletFfiError> { if handle.is_null() { - set_last_error("Null wallet handle"); + print_error("Null wallet handle"); return Err(WalletFfiError::NullPointer); } Ok(unsafe { &*(handle as *mut WalletWrapper) }) @@ -33,7 +33,7 @@ pub(crate) fn get_wallet_mut( handle: *mut WalletHandle, ) -> Result<&'static mut WalletWrapper, WalletFfiError> { if handle.is_null() { - set_last_error("Null wallet handle"); + print_error("Null wallet handle"); return Err(WalletFfiError::NullPointer); } Ok(unsafe { &mut *(handle as *mut WalletWrapper) }) @@ -42,7 +42,7 @@ pub(crate) fn get_wallet_mut( /// Helper to convert a C string to a Rust PathBuf. fn c_str_to_path(ptr: *const c_char, name: &str) -> Result { if ptr.is_null() { - set_last_error(format!("Null pointer for {}", name)); + print_error(format!("Null pointer for {}", name)); return Err(WalletFfiError::NullPointer); } @@ -50,7 +50,7 @@ fn c_str_to_path(ptr: *const c_char, name: &str) -> Result Ok(PathBuf::from(s)), Err(e) => { - set_last_error(format!("Invalid UTF-8 in {}: {}", name, e)); + print_error(format!("Invalid UTF-8 in {}: {}", name, e)); Err(WalletFfiError::InvalidUtf8) } } @@ -59,7 +59,7 @@ fn c_str_to_path(ptr: *const c_char, name: &str) -> Result Result { if ptr.is_null() { - set_last_error(format!("Null pointer for {}", name)); + print_error(format!("Null pointer for {}", name)); return Err(WalletFfiError::NullPointer); } @@ -67,7 +67,7 @@ fn c_str_to_string(ptr: *const c_char, name: &str) -> Result Ok(s.to_string()), Err(e) => { - set_last_error(format!("Invalid UTF-8 in {}: {}", name, e)); + print_error(format!("Invalid UTF-8 in {}: {}", name, e)); Err(WalletFfiError::InvalidUtf8) } } @@ -90,7 +90,7 @@ fn c_str_to_string(ptr: *const c_char, name: &str) -> Result { - set_last_error(format!("Failed to create wallet: {}", e)); + print_error(format!("Failed to create wallet: {}", e)); ptr::null_mut() } } @@ -139,7 +139,7 @@ pub extern "C" fn wallet_ffi_create_new( /// # Safety /// All string parameters must be valid null-terminated UTF-8 strings. #[no_mangle] -pub extern "C" fn wallet_ffi_open( +pub unsafe extern "C" fn wallet_ffi_open( config_path: *const c_char, storage_path: *const c_char, ) -> *mut WalletHandle { @@ -161,7 +161,7 @@ pub extern "C" fn wallet_ffi_open( Box::into_raw(wrapper) as *mut WalletHandle } Err(e) => { - set_last_error(format!("Failed to open wallet: {}", e)); + print_error(format!("Failed to open wallet: {}", e)); ptr::null_mut() } } @@ -176,7 +176,7 @@ pub extern "C" fn wallet_ffi_open( /// or `wallet_ffi_open()`. /// - The handle must not be used after this call. #[no_mangle] -pub extern "C" fn wallet_ffi_destroy(handle: *mut WalletHandle) { +pub unsafe extern "C" fn wallet_ffi_destroy(handle: *mut WalletHandle) { if !handle.is_null() { unsafe { drop(Box::from_raw(handle as *mut WalletWrapper)); @@ -195,8 +195,11 @@ pub extern "C" fn wallet_ffi_destroy(handle: *mut WalletHandle) { /// # Returns /// - `Success` on successful save /// - Error code on failure +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` #[no_mangle] -pub extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfiError { +pub unsafe extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfiError { let wrapper = match get_wallet(handle) { Ok(w) => w, Err(e) => return e, @@ -205,7 +208,7 @@ pub extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfiError { let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -213,7 +216,7 @@ pub extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfiError { match block_on(wallet.store_persistent_data()) { Ok(Ok(())) => WalletFfiError::Success, Ok(Err(e)) => { - set_last_error(format!("Failed to save wallet: {}", e)); + print_error(format!("Failed to save wallet: {}", e)); WalletFfiError::StorageError } Err(e) => e, @@ -228,8 +231,11 @@ pub extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfiError { /// # Returns /// - Pointer to null-terminated string on success (caller must free with `wallet_ffi_free_string()`) /// - Null pointer on error +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` #[no_mangle] -pub extern "C" fn wallet_ffi_get_sequencer_addr(handle: *mut WalletHandle) -> *mut c_char { +pub unsafe extern "C" fn wallet_ffi_get_sequencer_addr(handle: *mut WalletHandle) -> *mut c_char { let wrapper = match get_wallet(handle) { Ok(w) => w, Err(_) => return ptr::null_mut(), @@ -238,7 +244,7 @@ pub extern "C" fn wallet_ffi_get_sequencer_addr(handle: *mut WalletHandle) -> *m let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return ptr::null_mut(); } }; @@ -248,7 +254,7 @@ pub extern "C" fn wallet_ffi_get_sequencer_addr(handle: *mut WalletHandle) -> *m match std::ffi::CString::new(addr) { Ok(s) => s.into_raw(), Err(e) => { - set_last_error(format!("Invalid sequencer address: {}", e)); + print_error(format!("Invalid sequencer address: {}", e)); ptr::null_mut() } } @@ -259,7 +265,7 @@ pub extern "C" fn wallet_ffi_get_sequencer_addr(handle: *mut WalletHandle) -> *m /// # Safety /// The pointer must be either null or a valid string returned by an FFI function. #[no_mangle] -pub extern "C" fn wallet_ffi_free_string(ptr: *mut c_char) { +pub unsafe extern "C" fn wallet_ffi_free_string(ptr: *mut c_char) { if !ptr.is_null() { unsafe { drop(std::ffi::CString::from_raw(ptr)); diff --git a/wallet-ffi/wallet_ffi.h b/wallet-ffi/wallet_ffi.h index 6465ac25..7ae524ba 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -240,6 +240,10 @@ bool wallet_ffi_runtime_initialized(void); * # Returns * - `Success` on successful creation * - Error code on failure + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `out_account_id` must be a valid pointer to a `FfiBytes32` struct */ enum WalletFfiError wallet_ffi_create_account_public(struct WalletHandle *handle, struct FfiBytes32 *out_account_id); @@ -257,6 +261,10 @@ enum WalletFfiError wallet_ffi_create_account_public(struct WalletHandle *handle * # Returns * - `Success` on successful creation * - Error code on failure + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `out_account_id` must be a valid pointer to a `FfiBytes32` struct */ enum WalletFfiError wallet_ffi_create_account_private(struct WalletHandle *handle, struct FfiBytes32 *out_account_id); @@ -276,6 +284,10 @@ enum WalletFfiError wallet_ffi_create_account_private(struct WalletHandle *handl * * # Memory * The returned list must be freed with `wallet_ffi_free_account_list()`. + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `out_list` must be a valid pointer to a `FfiAccountList` struct */ enum WalletFfiError wallet_ffi_list_accounts(struct WalletHandle *handle, struct FfiAccountList *out_list); @@ -303,6 +315,11 @@ void wallet_ffi_free_account_list(struct FfiAccountList *list); * # Returns * - `Success` on successful query * - Error code on failure + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `account_id` must be a valid pointer to a `FfiBytes32` struct + * - `out_balance` must be a valid pointer to a `[u8; 16]` array */ enum WalletFfiError wallet_ffi_get_balance(struct WalletHandle *handle, const struct FfiBytes32 *account_id, @@ -323,6 +340,11 @@ enum WalletFfiError wallet_ffi_get_balance(struct WalletHandle *handle, * * # Memory * The account data must be freed with `wallet_ffi_free_account_data()`. + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `account_id` must be a valid pointer to a `FfiBytes32` struct + * - `out_account` must be a valid pointer to a `FfiAccount` struct */ enum WalletFfiError wallet_ffi_get_account_public(struct WalletHandle *handle, const struct FfiBytes32 *account_id, @@ -337,24 +359,6 @@ enum WalletFfiError wallet_ffi_get_account_public(struct WalletHandle *handle, */ void wallet_ffi_free_account_data(struct FfiAccount *account); -/** - * Get the last error message. - * - * Returns a pointer to a null-terminated string, or null if no error is set. - * The caller owns the returned string and must free it with - * `wallet_ffi_free_error_string`. - */ -char *wallet_ffi_get_last_error(void); - -/** - * Free an error string returned by `wallet_ffi_get_last_error`. - * - * # Safety - * The pointer must be either null or a valid pointer returned by - * `wallet_ffi_get_last_error`. - */ -void wallet_ffi_free_error_string(char *ptr); - /** * Get the public key for a public account. * @@ -369,6 +373,11 @@ void wallet_ffi_free_error_string(char *ptr); * - `Success` on successful retrieval * - `KeyNotFound` if the account's key is not in this wallet * - Error code on other failures + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `account_id` must be a valid pointer to a `FfiBytes32` struct + * - `out_public_key` must be a valid pointer to a `FfiPublicAccountKey` struct */ enum WalletFfiError wallet_ffi_get_public_account_key(struct WalletHandle *handle, const struct FfiBytes32 *account_id, @@ -392,6 +401,11 @@ enum WalletFfiError wallet_ffi_get_public_account_key(struct WalletHandle *handl * * # Memory * The keys structure must be freed with `wallet_ffi_free_private_account_keys()`. + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `account_id` must be a valid pointer to a `FfiBytes32` struct + * - `out_keys` must be a valid pointer to a `FfiPrivateAccountKeys` struct */ enum WalletFfiError wallet_ffi_get_private_account_keys(struct WalletHandle *handle, const struct FfiBytes32 *account_id, @@ -418,6 +432,9 @@ void wallet_ffi_free_private_account_keys(struct FfiPrivateAccountKeys *keys); * * # Memory * The returned string must be freed with `wallet_ffi_free_string()`. + * + * # Safety + * - `account_id` must be a valid pointer to a `FfiBytes32` struct */ char *wallet_ffi_account_id_to_base58(const struct FfiBytes32 *account_id); @@ -432,6 +449,10 @@ char *wallet_ffi_account_id_to_base58(const struct FfiBytes32 *account_id); * - `Success` on successful parsing * - `InvalidAccountId` if the string is not valid Base58 * - Error code on other failures + * + * # Safety + * - `base58_str` must be a valid pointer to a null-terminated C string + * - `out_account_id` must be a valid pointer to a `FfiBytes32` struct */ enum WalletFfiError wallet_ffi_account_id_from_base58(const char *base58_str, struct FfiBytes32 *out_account_id); @@ -454,6 +475,9 @@ enum WalletFfiError wallet_ffi_account_id_from_base58(const char *base58_str, * # Note * This operation can take a while for large block ranges. The wallet * internally uses a progress bar which may output to stdout. + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` */ enum WalletFfiError wallet_ffi_sync_to_block(struct WalletHandle *handle, uint64_t block_id); @@ -467,6 +491,10 @@ enum WalletFfiError wallet_ffi_sync_to_block(struct WalletHandle *handle, uint64 * # Returns * - `Success` on success * - Error code on failure + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `out_block_id` must be a valid pointer to a `u64` */ enum WalletFfiError wallet_ffi_get_last_synced_block(struct WalletHandle *handle, uint64_t *out_block_id); @@ -482,6 +510,10 @@ enum WalletFfiError wallet_ffi_get_last_synced_block(struct WalletHandle *handle * - `Success` on success * - `NetworkError` if the sequencer is unreachable * - Error code on other failures + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `out_block_height` must be a valid pointer to a `u64` */ enum WalletFfiError wallet_ffi_get_current_block_height(struct WalletHandle *handle, uint64_t *out_block_height); @@ -506,6 +538,13 @@ enum WalletFfiError wallet_ffi_get_current_block_height(struct WalletHandle *han * * # Memory * The result must be freed with `wallet_ffi_free_transfer_result()`. + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `from` must be a valid pointer to a `FfiBytes32` struct + * - `to` must be a valid pointer to a `FfiBytes32` struct + * - `amount` must be a valid pointer to a `[u8; 16]` array + * - `out_result` must be a valid pointer to a `FfiTransferResult` struct */ enum WalletFfiError wallet_ffi_transfer_public(struct WalletHandle *handle, const struct FfiBytes32 *from, @@ -530,6 +569,11 @@ enum WalletFfiError wallet_ffi_transfer_public(struct WalletHandle *handle, * * # Memory * The result must be freed with `wallet_ffi_free_transfer_result()`. + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `account_id` must be a valid pointer to a `FfiBytes32` struct + * - `out_result` must be a valid pointer to a `FfiTransferResult` struct */ enum WalletFfiError wallet_ffi_register_public_account(struct WalletHandle *handle, const struct FfiBytes32 *account_id, @@ -608,6 +652,9 @@ void wallet_ffi_destroy(struct WalletHandle *handle); * # Returns * - `Success` on successful save * - Error code on failure + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` */ enum WalletFfiError wallet_ffi_save(struct WalletHandle *handle); @@ -620,6 +667,9 @@ enum WalletFfiError wallet_ffi_save(struct WalletHandle *handle); * # Returns * - Pointer to null-terminated string on success (caller must free with `wallet_ffi_free_string()`) * - Null pointer on error + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` */ char *wallet_ffi_get_sequencer_addr(struct WalletHandle *handle);