mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-03-27 04:33:13 +00:00
The mnemonic/wallet generation was using a constant zero-byte array for entropy ([0u8; 32]), making all wallets deterministic based solely on the password. This commit introduces proper random entropy using OsRng and enables users to back up their recovery phrase. Changes: - SeedHolder::new_mnemonic() now uses OsRng for 256-bit random entropy and returns the generated mnemonic - Added SeedHolder::from_mnemonic() to recover a wallet from an existing mnemonic phrase - WalletChainStore::new_storage() returns the mnemonic for user backup - Added WalletChainStore::restore_storage() for recovery from a mnemonic - WalletCore::new_init_storage() now returns the mnemonic - Renamed reset_storage to restore_storage, which accepts a mnemonic for recovery - CLI displays the recovery phrase when a new wallet is created - RestoreKeys command now prompts for the mnemonic phrase via read_mnemonic_from_stdin() Note: The password parameter is retained for future storage encryption but is no longer used in seed derivation (empty passphrase is used instead). This means the mnemonic alone is sufficient to recover accounts. Usage: On first wallet initialization, users will see: IMPORTANT: Write down your recovery phrase and store it securely. This is the only way to recover your wallet if you lose access. Recovery phrase: word1 word2 word3 ... word24 To restore keys: wallet restore-keys --depth 5 Input recovery phrase: <24 words> Input password: <password>
1035 lines
32 KiB
Rust
1035 lines
32 KiB
Rust
#![expect(
|
|
clippy::redundant_test_prefix,
|
|
reason = "Otherwise names interfere with ffi bindings"
|
|
)]
|
|
#![expect(
|
|
clippy::tests_outside_test_module,
|
|
clippy::undocumented_unsafe_blocks,
|
|
clippy::multiple_unsafe_ops_per_block,
|
|
clippy::shadow_unrelated,
|
|
reason = "We don't care about these in tests"
|
|
)]
|
|
|
|
use std::{
|
|
collections::HashSet,
|
|
ffi::{CStr, CString, c_char},
|
|
io::Write as _,
|
|
path::Path,
|
|
time::Duration,
|
|
};
|
|
|
|
use anyhow::Result;
|
|
use integration_tests::{BlockingTestContext, TIME_TO_WAIT_FOR_BLOCK_SECONDS};
|
|
use log::info;
|
|
use nssa::{Account, AccountId, PrivateKey, PublicKey, program::Program};
|
|
use nssa_core::program::DEFAULT_PROGRAM_ID;
|
|
use tempfile::tempdir;
|
|
use wallet_ffi::{
|
|
FfiAccount, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey,
|
|
FfiTransferResult, WalletHandle, error,
|
|
};
|
|
|
|
unsafe extern "C" {
|
|
fn wallet_ffi_create_new(
|
|
config_path: *const c_char,
|
|
storage_path: *const c_char,
|
|
password: *const c_char,
|
|
) -> *mut WalletHandle;
|
|
|
|
fn wallet_ffi_open(
|
|
config_path: *const c_char,
|
|
storage_path: *const c_char,
|
|
) -> *mut WalletHandle;
|
|
|
|
fn wallet_ffi_destroy(handle: *mut WalletHandle);
|
|
|
|
fn wallet_ffi_create_account_public(
|
|
handle: *mut WalletHandle,
|
|
out_account_id: *mut FfiBytes32,
|
|
) -> error::WalletFfiError;
|
|
|
|
fn wallet_ffi_create_account_private(
|
|
handle: *mut WalletHandle,
|
|
out_account_id: *mut FfiBytes32,
|
|
) -> error::WalletFfiError;
|
|
|
|
fn wallet_ffi_list_accounts(
|
|
handle: *mut WalletHandle,
|
|
out_list: *mut FfiAccountList,
|
|
) -> error::WalletFfiError;
|
|
|
|
fn wallet_ffi_free_account_list(list: *mut FfiAccountList);
|
|
|
|
fn wallet_ffi_get_balance(
|
|
handle: *mut WalletHandle,
|
|
account_id: *const FfiBytes32,
|
|
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_get_account_private(
|
|
handle: *mut WalletHandle,
|
|
account_id: *const FfiBytes32,
|
|
out_account: *mut FfiAccount,
|
|
) -> error::WalletFfiError;
|
|
|
|
fn wallet_ffi_free_account_data(account: *mut FfiAccount);
|
|
|
|
fn wallet_ffi_get_public_account_key(
|
|
handle: *mut WalletHandle,
|
|
account_id: *const FfiBytes32,
|
|
out_public_key: *mut FfiPublicAccountKey,
|
|
) -> error::WalletFfiError;
|
|
|
|
fn wallet_ffi_get_private_account_keys(
|
|
handle: *mut WalletHandle,
|
|
account_id: *const FfiBytes32,
|
|
out_keys: *mut FfiPrivateAccountKeys,
|
|
) -> error::WalletFfiError;
|
|
|
|
fn wallet_ffi_free_private_account_keys(keys: *mut FfiPrivateAccountKeys);
|
|
|
|
fn wallet_ffi_account_id_to_base58(account_id: *const FfiBytes32) -> *mut std::ffi::c_char;
|
|
|
|
fn wallet_ffi_free_string(ptr: *mut c_char);
|
|
|
|
fn wallet_ffi_account_id_from_base58(
|
|
base58_str: *const std::ffi::c_char,
|
|
out_account_id: *mut FfiBytes32,
|
|
) -> error::WalletFfiError;
|
|
|
|
fn wallet_ffi_transfer_public(
|
|
handle: *mut WalletHandle,
|
|
from: *const FfiBytes32,
|
|
to: *const FfiBytes32,
|
|
amount: *const [u8; 16],
|
|
out_result: *mut FfiTransferResult,
|
|
) -> error::WalletFfiError;
|
|
|
|
fn wallet_ffi_transfer_shielded(
|
|
handle: *mut WalletHandle,
|
|
from: *const FfiBytes32,
|
|
to_keys: *const FfiPrivateAccountKeys,
|
|
amount: *const [u8; 16],
|
|
out_result: *mut FfiTransferResult,
|
|
) -> error::WalletFfiError;
|
|
|
|
fn wallet_ffi_transfer_deshielded(
|
|
handle: *mut WalletHandle,
|
|
from: *const FfiBytes32,
|
|
to: *const FfiBytes32,
|
|
amount: *const [u8; 16],
|
|
out_result: *mut FfiTransferResult,
|
|
) -> error::WalletFfiError;
|
|
|
|
fn wallet_ffi_transfer_private(
|
|
handle: *mut WalletHandle,
|
|
from: *const FfiBytes32,
|
|
to_keys: *const FfiPrivateAccountKeys,
|
|
amount: *const [u8; 16],
|
|
out_result: *mut FfiTransferResult,
|
|
) -> error::WalletFfiError;
|
|
|
|
fn wallet_ffi_free_transfer_result(result: *mut FfiTransferResult);
|
|
|
|
fn wallet_ffi_register_public_account(
|
|
handle: *mut WalletHandle,
|
|
account_id: *const FfiBytes32,
|
|
out_result: *mut FfiTransferResult,
|
|
) -> error::WalletFfiError;
|
|
|
|
fn wallet_ffi_register_private_account(
|
|
handle: *mut WalletHandle,
|
|
account_id: *const FfiBytes32,
|
|
out_result: *mut FfiTransferResult,
|
|
) -> error::WalletFfiError;
|
|
|
|
fn wallet_ffi_save(handle: *mut WalletHandle) -> error::WalletFfiError;
|
|
|
|
fn wallet_ffi_sync_to_block(handle: *mut WalletHandle, block_id: u64) -> error::WalletFfiError;
|
|
|
|
fn wallet_ffi_get_current_block_height(
|
|
handle: *mut WalletHandle,
|
|
out_block_height: *mut u64,
|
|
) -> error::WalletFfiError;
|
|
}
|
|
|
|
fn new_wallet_ffi_with_test_context_config(
|
|
ctx: &BlockingTestContext,
|
|
home: &Path,
|
|
) -> Result<*mut WalletHandle> {
|
|
let config_path = home.join("wallet_config.json");
|
|
let storage_path = home.join("storage.json");
|
|
let mut config = ctx.ctx().wallet().config().to_owned();
|
|
if let Some(config_overrides) = ctx.ctx().wallet().config_overrides().clone() {
|
|
config.apply_overrides(config_overrides);
|
|
}
|
|
let mut file = std::fs::OpenOptions::new()
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true)
|
|
.open(&config_path)?;
|
|
|
|
let config_with_overrides_serialized = serde_json::to_vec_pretty(&config)?;
|
|
|
|
file.write_all(&config_with_overrides_serialized)?;
|
|
|
|
let config_path = CString::new(config_path.to_str().unwrap())?;
|
|
let storage_path = CString::new(storage_path.to_str().unwrap())?;
|
|
let password = CString::new(ctx.ctx().wallet_password())?;
|
|
|
|
Ok(unsafe {
|
|
wallet_ffi_create_new(
|
|
config_path.as_ptr(),
|
|
storage_path.as_ptr(),
|
|
password.as_ptr(),
|
|
)
|
|
})
|
|
}
|
|
|
|
fn new_wallet_ffi_with_default_config(password: &str) -> Result<*mut WalletHandle> {
|
|
let tempdir = tempdir()?;
|
|
let config_path = tempdir.path().join("wallet_config.json");
|
|
let storage_path = tempdir.path().join("storage.json");
|
|
let config_path_c = CString::new(config_path.to_str().unwrap())?;
|
|
let storage_path_c = CString::new(storage_path.to_str().unwrap())?;
|
|
let password = CString::new(password)?;
|
|
|
|
Ok(unsafe {
|
|
wallet_ffi_create_new(
|
|
config_path_c.as_ptr(),
|
|
storage_path_c.as_ptr(),
|
|
password.as_ptr(),
|
|
)
|
|
})
|
|
}
|
|
|
|
fn load_existing_ffi_wallet(home: &Path) -> Result<*mut WalletHandle> {
|
|
let config_path = home.join("wallet_config.json");
|
|
let storage_path = home.join("storage.json");
|
|
let config_path = CString::new(config_path.to_str().unwrap())?;
|
|
let storage_path = CString::new(storage_path.to_str().unwrap())?;
|
|
|
|
Ok(unsafe { wallet_ffi_open(config_path.as_ptr(), storage_path.as_ptr()) })
|
|
}
|
|
|
|
#[test]
|
|
fn wallet_ffi_create_public_accounts() -> Result<()> {
|
|
let password = "password_for_tests";
|
|
let n_accounts = 10;
|
|
|
|
// Create `n_accounts` public accounts with wallet FFI
|
|
let new_public_account_ids_ffi = unsafe {
|
|
let mut account_ids = Vec::new();
|
|
|
|
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(wallet_ffi_handle, &raw mut out_account_id);
|
|
account_ids.push(out_account_id.data);
|
|
}
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
account_ids
|
|
};
|
|
|
|
// All returned IDs must be unique and non-zero
|
|
assert_eq!(new_public_account_ids_ffi.len(), n_accounts);
|
|
let unique: HashSet<_> = new_public_account_ids_ffi.iter().collect();
|
|
assert_eq!(
|
|
unique.len(),
|
|
n_accounts,
|
|
"Duplicate public account IDs returned"
|
|
);
|
|
assert!(
|
|
new_public_account_ids_ffi
|
|
.iter()
|
|
.all(|id| *id != [0_u8; 32]),
|
|
"Zero account ID returned"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn wallet_ffi_create_private_accounts() -> Result<()> {
|
|
let password = "password_for_tests";
|
|
let n_accounts = 10;
|
|
// Create `n_accounts` private accounts with wallet FFI
|
|
let new_private_account_ids_ffi = unsafe {
|
|
let mut account_ids = Vec::new();
|
|
|
|
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(wallet_ffi_handle, &raw mut out_account_id);
|
|
account_ids.push(out_account_id.data);
|
|
}
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
account_ids
|
|
};
|
|
|
|
// All returned IDs must be unique and non-zero
|
|
assert_eq!(new_private_account_ids_ffi.len(), n_accounts);
|
|
let unique: HashSet<_> = new_private_account_ids_ffi.iter().collect();
|
|
assert_eq!(
|
|
unique.len(),
|
|
n_accounts,
|
|
"Duplicate private account IDs returned"
|
|
);
|
|
assert!(
|
|
new_private_account_ids_ffi
|
|
.iter()
|
|
.all(|id| *id != [0_u8; 32]),
|
|
"Zero account ID returned"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
#[test]
|
|
fn wallet_ffi_save_and_load_persistent_storage() -> Result<()> {
|
|
let ctx = BlockingTestContext::new()?;
|
|
let mut out_private_account_id = FfiBytes32::from_bytes([0; 32]);
|
|
let home = tempfile::tempdir()?;
|
|
|
|
// Create a private account with the wallet FFI and save it
|
|
unsafe {
|
|
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
|
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_private_account_id);
|
|
|
|
wallet_ffi_save(wallet_ffi_handle);
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
}
|
|
|
|
let private_account_keys = unsafe {
|
|
let wallet_ffi_handle = load_existing_ffi_wallet(home.path())?;
|
|
|
|
let mut private_account = FfiAccount::default();
|
|
|
|
let result = wallet_ffi_get_account_private(
|
|
wallet_ffi_handle,
|
|
&raw const out_private_account_id,
|
|
&raw mut private_account,
|
|
);
|
|
assert_eq!(result, error::WalletFfiError::Success);
|
|
|
|
let mut out_keys = FfiPrivateAccountKeys::default();
|
|
let result = wallet_ffi_get_private_account_keys(
|
|
wallet_ffi_handle,
|
|
&raw const out_private_account_id,
|
|
&raw mut out_keys,
|
|
);
|
|
assert_eq!(result, error::WalletFfiError::Success);
|
|
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
|
|
out_keys
|
|
};
|
|
|
|
assert_eq!(
|
|
nssa::AccountId::from(&private_account_keys.npk()),
|
|
out_private_account_id.into()
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_wallet_ffi_list_accounts() -> Result<()> {
|
|
let password = "password_for_tests";
|
|
|
|
// Create the wallet FFI and track which account IDs were created as public/private
|
|
let (wallet_ffi_handle, created_public_ids, created_private_ids) = unsafe {
|
|
let handle = new_wallet_ffi_with_default_config(password)?;
|
|
let mut public_ids: Vec<[u8; 32]> = Vec::new();
|
|
let mut private_ids: Vec<[u8; 32]> = Vec::new();
|
|
|
|
// Create 5 public accounts and 5 private accounts, recording their IDs
|
|
for _ in 0..5 {
|
|
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
|
|
wallet_ffi_create_account_public(handle, &raw mut out_account_id);
|
|
public_ids.push(out_account_id.data);
|
|
|
|
wallet_ffi_create_account_private(handle, &raw mut out_account_id);
|
|
private_ids.push(out_account_id.data);
|
|
}
|
|
|
|
(handle, public_ids, private_ids)
|
|
};
|
|
|
|
// Get the account list with FFI method
|
|
let mut wallet_ffi_account_list = unsafe {
|
|
let mut out_list = FfiAccountList::default();
|
|
wallet_ffi_list_accounts(wallet_ffi_handle, &raw mut out_list);
|
|
out_list
|
|
};
|
|
|
|
let wallet_ffi_account_list_slice = unsafe {
|
|
core::slice::from_raw_parts(
|
|
wallet_ffi_account_list.entries,
|
|
wallet_ffi_account_list.count,
|
|
)
|
|
};
|
|
|
|
// All created accounts must appear in the list
|
|
let listed_public_ids: HashSet<[u8; 32]> = wallet_ffi_account_list_slice
|
|
.iter()
|
|
.filter(|e| e.is_public)
|
|
.map(|e| e.account_id.data)
|
|
.collect();
|
|
let listed_private_ids: HashSet<[u8; 32]> = wallet_ffi_account_list_slice
|
|
.iter()
|
|
.filter(|e| !e.is_public)
|
|
.map(|e| e.account_id.data)
|
|
.collect();
|
|
|
|
for id in &created_public_ids {
|
|
assert!(
|
|
listed_public_ids.contains(id),
|
|
"Created public account not found in list with is_public=true"
|
|
);
|
|
}
|
|
for id in &created_private_ids {
|
|
assert!(
|
|
listed_private_ids.contains(id),
|
|
"Created private account not found in list with is_public=false"
|
|
);
|
|
}
|
|
|
|
// Total listed accounts must be at least the number we created
|
|
assert!(
|
|
wallet_ffi_account_list.count >= created_public_ids.len() + created_private_ids.len(),
|
|
"Listed account count ({}) is less than the number of created accounts ({})",
|
|
wallet_ffi_account_list.count,
|
|
created_public_ids.len() + created_private_ids.len()
|
|
);
|
|
|
|
unsafe {
|
|
wallet_ffi_free_account_list(&raw mut wallet_ffi_account_list);
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_wallet_ffi_get_balance_public() -> Result<()> {
|
|
let ctx = BlockingTestContext::new()?;
|
|
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
|
|
let home = tempfile::tempdir()?;
|
|
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
|
|
|
let balance = unsafe {
|
|
let mut out_balance: [u8; 16] = [0; 16];
|
|
let ffi_account_id = FfiBytes32::from(&account_id);
|
|
wallet_ffi_get_balance(
|
|
wallet_ffi_handle,
|
|
&raw const ffi_account_id,
|
|
true,
|
|
&raw mut out_balance,
|
|
)
|
|
.unwrap();
|
|
u128::from_le_bytes(out_balance)
|
|
};
|
|
assert_eq!(balance, 10000);
|
|
|
|
info!("Successfully retrieved account balance");
|
|
|
|
unsafe {
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_wallet_ffi_get_account_public() -> Result<()> {
|
|
let ctx = BlockingTestContext::new()?;
|
|
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
|
|
let home = tempfile::tempdir()?;
|
|
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
|
let mut out_account = FfiAccount::default();
|
|
|
|
let account: Account = unsafe {
|
|
let ffi_account_id = FfiBytes32::from(&account_id);
|
|
wallet_ffi_get_account_public(
|
|
wallet_ffi_handle,
|
|
&raw const ffi_account_id,
|
|
&raw mut out_account,
|
|
)
|
|
.unwrap();
|
|
(&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, 0);
|
|
|
|
unsafe {
|
|
wallet_ffi_free_account_data(&raw mut out_account);
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
}
|
|
|
|
info!("Successfully retrieved account with correct details");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_wallet_ffi_get_account_private() -> Result<()> {
|
|
let ctx = BlockingTestContext::new()?;
|
|
let account_id: AccountId = ctx.ctx().existing_private_accounts()[0];
|
|
let home = tempfile::tempdir()?;
|
|
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
|
let mut out_account = FfiAccount::default();
|
|
|
|
let account: Account = unsafe {
|
|
let ffi_account_id = FfiBytes32::from(&account_id);
|
|
wallet_ffi_get_account_private(
|
|
wallet_ffi_handle,
|
|
&raw const ffi_account_id,
|
|
&raw mut out_account,
|
|
)
|
|
.unwrap();
|
|
(&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_u128.into());
|
|
|
|
unsafe {
|
|
wallet_ffi_free_account_data(&raw mut out_account);
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
}
|
|
|
|
info!("Successfully retrieved account with correct details");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_wallet_ffi_get_public_account_keys() -> Result<()> {
|
|
let ctx = BlockingTestContext::new()?;
|
|
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
|
|
let home = tempfile::tempdir()?;
|
|
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
|
let mut out_key = FfiPublicAccountKey::default();
|
|
|
|
let key: PublicKey = unsafe {
|
|
let ffi_account_id = FfiBytes32::from(&account_id);
|
|
wallet_ffi_get_public_account_key(
|
|
wallet_ffi_handle,
|
|
&raw const ffi_account_id,
|
|
&raw mut out_key,
|
|
)
|
|
.unwrap();
|
|
(&out_key).try_into().unwrap()
|
|
};
|
|
|
|
let expected_key = {
|
|
let private_key = ctx
|
|
.ctx()
|
|
.wallet()
|
|
.get_account_public_signing_key(account_id)
|
|
.unwrap();
|
|
PublicKey::new_from_private_key(private_key)
|
|
};
|
|
|
|
assert_eq!(key, expected_key);
|
|
|
|
info!("Successfully retrieved account key");
|
|
|
|
unsafe {
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_wallet_ffi_get_private_account_keys() -> Result<()> {
|
|
let ctx = BlockingTestContext::new()?;
|
|
let account_id: AccountId = ctx.ctx().existing_private_accounts()[0];
|
|
let home = tempfile::tempdir()?;
|
|
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
|
let mut keys = FfiPrivateAccountKeys::default();
|
|
|
|
unsafe {
|
|
let ffi_account_id = FfiBytes32::from(&account_id);
|
|
wallet_ffi_get_private_account_keys(
|
|
wallet_ffi_handle,
|
|
&raw const ffi_account_id,
|
|
&raw mut keys,
|
|
)
|
|
.unwrap();
|
|
};
|
|
|
|
let key_chain = &ctx
|
|
.ctx()
|
|
.wallet()
|
|
.storage()
|
|
.user_data
|
|
.get_private_account(account_id)
|
|
.unwrap()
|
|
.0;
|
|
|
|
let expected_npk = &key_chain.nullifier_public_key;
|
|
let expected_vpk = &key_chain.viewing_public_key;
|
|
|
|
assert_eq!(&keys.npk(), expected_npk);
|
|
assert_eq!(&keys.vpk().unwrap(), expected_vpk);
|
|
|
|
unsafe {
|
|
wallet_ffi_free_private_account_keys(&raw mut keys);
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
}
|
|
|
|
info!("Successfully retrieved account keys");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_wallet_ffi_account_id_to_base58() -> Result<()> {
|
|
let private_key = PrivateKey::new_os_random();
|
|
let public_key = PublicKey::new_from_private_key(&private_key);
|
|
let account_id = AccountId::from(&public_key);
|
|
let ffi_bytes: FfiBytes32 = (&account_id).into();
|
|
let ptr = unsafe { wallet_ffi_account_id_to_base58(&raw const ffi_bytes) };
|
|
|
|
let ffi_result = unsafe { CStr::from_ptr(ptr).to_str()? };
|
|
|
|
assert_eq!(account_id.to_string(), ffi_result);
|
|
|
|
unsafe {
|
|
wallet_ffi_free_string(ptr);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn wallet_ffi_base58_to_account_id() -> Result<()> {
|
|
let private_key = PrivateKey::new_os_random();
|
|
let public_key = PublicKey::new_from_private_key(&private_key);
|
|
let account_id = AccountId::from(&public_key);
|
|
let account_id_str = account_id.to_string();
|
|
let account_id_c_str = CString::new(account_id_str.clone())?;
|
|
let account_id: AccountId = unsafe {
|
|
let mut out_account_id_bytes = FfiBytes32::default();
|
|
wallet_ffi_account_id_from_base58(account_id_c_str.as_ptr(), &raw mut out_account_id_bytes);
|
|
out_account_id_bytes.into()
|
|
};
|
|
|
|
let expected_account_id = account_id_str.parse()?;
|
|
|
|
assert_eq!(account_id, expected_account_id);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
|
|
let ctx = BlockingTestContext::new()?;
|
|
let home = tempfile::tempdir()?;
|
|
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
|
|
|
// Create a new uninitialized public account
|
|
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
|
|
unsafe {
|
|
wallet_ffi_create_account_public(wallet_ffi_handle, &raw mut out_account_id);
|
|
}
|
|
|
|
// Check its program owner is the default program id
|
|
let account: Account = unsafe {
|
|
let mut out_account = FfiAccount::default();
|
|
wallet_ffi_get_account_public(
|
|
wallet_ffi_handle,
|
|
&raw const out_account_id,
|
|
&raw mut out_account,
|
|
)
|
|
.unwrap();
|
|
(&out_account).try_into().unwrap()
|
|
};
|
|
assert_eq!(account.program_owner, DEFAULT_PROGRAM_ID);
|
|
|
|
// Call the init funciton
|
|
let mut transfer_result = FfiTransferResult::default();
|
|
unsafe {
|
|
wallet_ffi_register_public_account(
|
|
wallet_ffi_handle,
|
|
&raw const out_account_id,
|
|
&raw mut transfer_result,
|
|
);
|
|
}
|
|
|
|
info!("Waiting for next block creation");
|
|
std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS));
|
|
|
|
// Check that the program owner is now the authenticated transfer program
|
|
let account: Account = unsafe {
|
|
let mut out_account = FfiAccount::default();
|
|
wallet_ffi_get_account_public(
|
|
wallet_ffi_handle,
|
|
&raw const out_account_id,
|
|
&raw mut out_account,
|
|
)
|
|
.unwrap();
|
|
(&out_account).try_into().unwrap()
|
|
};
|
|
assert_eq!(
|
|
account.program_owner,
|
|
Program::authenticated_transfer_program().id()
|
|
);
|
|
|
|
unsafe {
|
|
wallet_ffi_free_transfer_result(&raw mut transfer_result);
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
|
|
let ctx = BlockingTestContext::new()?;
|
|
let home = tempfile::tempdir()?;
|
|
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
|
|
|
// Create a new uninitialized public account
|
|
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
|
|
unsafe {
|
|
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_account_id);
|
|
}
|
|
|
|
// Check its program owner is the default program id
|
|
let account: Account = unsafe {
|
|
let mut out_account = FfiAccount::default();
|
|
wallet_ffi_get_account_private(
|
|
wallet_ffi_handle,
|
|
&raw const out_account_id,
|
|
&raw mut out_account,
|
|
);
|
|
(&out_account).try_into().unwrap()
|
|
};
|
|
assert_eq!(account.program_owner, DEFAULT_PROGRAM_ID);
|
|
|
|
// Call the init funciton
|
|
let mut transfer_result = FfiTransferResult::default();
|
|
unsafe {
|
|
wallet_ffi_register_private_account(
|
|
wallet_ffi_handle,
|
|
&raw const out_account_id,
|
|
&raw mut transfer_result,
|
|
);
|
|
}
|
|
|
|
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);
|
|
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height);
|
|
};
|
|
|
|
// Check that the program owner is now the authenticated transfer program
|
|
let account: Account = unsafe {
|
|
let mut out_account = FfiAccount::default();
|
|
wallet_ffi_get_account_private(
|
|
wallet_ffi_handle,
|
|
&raw const out_account_id,
|
|
&raw mut out_account,
|
|
)
|
|
.unwrap();
|
|
(&out_account).try_into().unwrap()
|
|
};
|
|
assert_eq!(
|
|
account.program_owner,
|
|
Program::authenticated_transfer_program().id()
|
|
);
|
|
|
|
unsafe {
|
|
wallet_ffi_free_transfer_result(&raw mut transfer_result);
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_wallet_ffi_transfer_public() -> 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_public_accounts()[0]).into();
|
|
let to: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[1]).into();
|
|
let amount: [u8; 16] = 100_u128.to_le_bytes();
|
|
|
|
let mut transfer_result = FfiTransferResult::default();
|
|
unsafe {
|
|
wallet_ffi_transfer_public(
|
|
wallet_ffi_handle,
|
|
&raw const from,
|
|
&raw const to,
|
|
&raw const amount,
|
|
&raw mut transfer_result,
|
|
);
|
|
}
|
|
|
|
info!("Waiting for next block creation");
|
|
std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS));
|
|
|
|
let from_balance = unsafe {
|
|
let mut out_balance: [u8; 16] = [0; 16];
|
|
wallet_ffi_get_balance(
|
|
wallet_ffi_handle,
|
|
&raw const from,
|
|
true,
|
|
&raw mut out_balance,
|
|
)
|
|
.unwrap();
|
|
u128::from_le_bytes(out_balance)
|
|
};
|
|
|
|
let to_balance = unsafe {
|
|
let mut out_balance: [u8; 16] = [0; 16];
|
|
wallet_ffi_get_balance(wallet_ffi_handle, &raw const to, true, &raw mut out_balance)
|
|
.unwrap();
|
|
u128::from_le_bytes(out_balance)
|
|
};
|
|
|
|
assert_eq!(from_balance, 9900);
|
|
assert_eq!(to_balance, 20100);
|
|
|
|
unsafe {
|
|
wallet_ffi_free_transfer_result(&raw mut transfer_result);
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_wallet_ffi_transfer_shielded() -> 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_public_accounts()[0]).into();
|
|
let (to, to_keys) = unsafe {
|
|
let mut out_account_id = FfiBytes32::default();
|
|
let mut out_keys = FfiPrivateAccountKeys::default();
|
|
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_account_id);
|
|
wallet_ffi_get_private_account_keys(
|
|
wallet_ffi_handle,
|
|
&raw const out_account_id,
|
|
&raw mut out_keys,
|
|
);
|
|
(out_account_id, out_keys)
|
|
};
|
|
let amount: [u8; 16] = 100_u128.to_le_bytes();
|
|
|
|
let mut transfer_result = FfiTransferResult::default();
|
|
unsafe {
|
|
wallet_ffi_transfer_shielded(
|
|
wallet_ffi_handle,
|
|
&raw const from,
|
|
&raw const to_keys,
|
|
&raw const amount,
|
|
&raw mut transfer_result,
|
|
);
|
|
}
|
|
|
|
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);
|
|
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height);
|
|
};
|
|
|
|
let from_balance = unsafe {
|
|
let mut out_balance: [u8; 16] = [0; 16];
|
|
wallet_ffi_get_balance(
|
|
wallet_ffi_handle,
|
|
&raw const from,
|
|
true,
|
|
&raw mut out_balance,
|
|
)
|
|
.unwrap();
|
|
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, 100);
|
|
|
|
unsafe {
|
|
wallet_ffi_free_transfer_result(&raw mut transfer_result);
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_wallet_ffi_transfer_deshielded() -> 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::from_bytes([37; 32]);
|
|
let amount: [u8; 16] = 100_u128.to_le_bytes();
|
|
|
|
let mut transfer_result = FfiTransferResult::default();
|
|
unsafe {
|
|
wallet_ffi_transfer_deshielded(
|
|
wallet_ffi_handle,
|
|
&raw const from,
|
|
&raw const to,
|
|
&raw const amount,
|
|
&raw mut transfer_result,
|
|
);
|
|
}
|
|
|
|
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);
|
|
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height);
|
|
};
|
|
|
|
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, true, &raw mut out_balance);
|
|
u128::from_le_bytes(out_balance)
|
|
};
|
|
|
|
assert_eq!(from_balance, 9900);
|
|
assert_eq!(to_balance, 100);
|
|
|
|
unsafe {
|
|
wallet_ffi_free_transfer_result(&raw mut transfer_result);
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_wallet_ffi_transfer_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, to_keys) = unsafe {
|
|
let mut out_account_id = FfiBytes32::default();
|
|
let mut out_keys = FfiPrivateAccountKeys::default();
|
|
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_account_id);
|
|
wallet_ffi_get_private_account_keys(
|
|
wallet_ffi_handle,
|
|
&raw const out_account_id,
|
|
&raw mut out_keys,
|
|
);
|
|
(out_account_id, out_keys)
|
|
};
|
|
|
|
let amount: [u8; 16] = 100_u128.to_le_bytes();
|
|
|
|
let mut transfer_result = FfiTransferResult::default();
|
|
unsafe {
|
|
wallet_ffi_transfer_private(
|
|
wallet_ffi_handle,
|
|
&raw const from,
|
|
&raw const to_keys,
|
|
&raw const amount,
|
|
&raw mut transfer_result,
|
|
);
|
|
}
|
|
|
|
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);
|
|
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height);
|
|
};
|
|
|
|
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, 100);
|
|
|
|
unsafe {
|
|
wallet_ffi_free_transfer_result(&raw mut transfer_result);
|
|
wallet_ffi_destroy(wallet_ffi_handle);
|
|
}
|
|
|
|
Ok(())
|
|
}
|