mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-02-23 23:03:15 +00:00
Merge branch 'main' into marvin/private_keys
This commit is contained in:
commit
f8dfcac17a
11
.deny.toml
11
.deny.toml
@ -12,6 +12,7 @@ ignore = [
|
|||||||
{ id = "RUSTSEC-2024-0436", reason = "`paste` has a security vulnerability; consider using an alternative. Use `cargo tree -p paste -i > tmp.txt` to check the dependency tree." },
|
{ id = "RUSTSEC-2024-0436", reason = "`paste` has a security vulnerability; consider using an alternative. Use `cargo tree -p paste -i > tmp.txt` to check the dependency tree." },
|
||||||
{ id = "RUSTSEC-2025-0055", reason = "`tracing-subscriber` v0.2.25 pulled in by ark-relations v0.4.0 - will be addressed before mainnet" },
|
{ id = "RUSTSEC-2025-0055", reason = "`tracing-subscriber` v0.2.25 pulled in by ark-relations v0.4.0 - will be addressed before mainnet" },
|
||||||
{ id = "RUSTSEC-2025-0141", reason = "`bincode` is unmaintained but continuing to use it." },
|
{ id = "RUSTSEC-2025-0141", reason = "`bincode` is unmaintained but continuing to use it." },
|
||||||
|
{ id = "RUSTSEC-2023-0089", reason = "atomic-polyfill is pulled transitively via risc0-zkvm; waiting on upstream fix (see https://github.com/risc0/risc0/issues/3453)" },
|
||||||
]
|
]
|
||||||
yanked = "deny"
|
yanked = "deny"
|
||||||
unused-ignored-advisory = "deny"
|
unused-ignored-advisory = "deny"
|
||||||
@ -35,6 +36,16 @@ allow = [
|
|||||||
"Unicode-3.0",
|
"Unicode-3.0",
|
||||||
"Zlib",
|
"Zlib",
|
||||||
]
|
]
|
||||||
|
exceptions = [
|
||||||
|
# TEMP: Pending legal review. Pulled transitively via `risc0-zkvm`
|
||||||
|
{ name = "downloader", version = "0.2.8", allow = ["LGPL-3.0-or-later"] },
|
||||||
|
{ name = "malachite", version = "0.4.22", allow = ["LGPL-3.0-only"] },
|
||||||
|
{ name = "malachite-base", version = "0.4.22", allow = ["LGPL-3.0-only"] },
|
||||||
|
{ name = "malachite-float", version = "0.4.22", allow = ["LGPL-3.0-only"] },
|
||||||
|
{ name = "malachite-nz", version = "0.4.22", allow = ["LGPL-3.0-only"] },
|
||||||
|
{ name = "malachite-q", version = "0.4.22", allow = ["LGPL-3.0-only"] },
|
||||||
|
{ name = "managed", version = "0.8.0", allow = ["0BSD"] },
|
||||||
|
]
|
||||||
private = { ignore = false }
|
private = { ignore = false }
|
||||||
unused-allowed-license = "deny"
|
unused-allowed-license = "deny"
|
||||||
|
|
||||||
|
|||||||
911
Cargo.lock
generated
911
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -67,8 +67,8 @@ tokio = { version = "1.28.2", features = [
|
|||||||
"fs",
|
"fs",
|
||||||
] }
|
] }
|
||||||
tokio-util = "0.7.18"
|
tokio-util = "0.7.18"
|
||||||
risc0-zkvm = { version = "3.0.3", features = ['std'] }
|
risc0-zkvm = { version = "3.0.5", features = ['std'] }
|
||||||
risc0-build = "3.0.3"
|
risc0-build = "3.0.5"
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.98"
|
||||||
num_cpus = "1.13.1"
|
num_cpus = "1.13.1"
|
||||||
openssl = { version = "0.10", features = ["vendored"] }
|
openssl = { version = "0.10", features = ["vendored"] }
|
||||||
|
|||||||
@ -254,7 +254,7 @@ impl indexer_service_rpc::RpcServer for MockIndexerService {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Sort by block ID descending (most recent first)
|
// Sort by block ID descending (most recent first)
|
||||||
account_txs.sort_by(|a, b| b.1.cmp(&a.1));
|
account_txs.sort_by_key(|b| std::cmp::Reverse(b.1));
|
||||||
|
|
||||||
let start = offset as usize;
|
let start = offset as usize;
|
||||||
if start >= account_txs.len() {
|
if start >= account_txs.len() {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use std::{
|
|||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
ffi::{CStr, CString, c_char},
|
ffi::{CStr, CString, c_char},
|
||||||
io::Write,
|
io::Write,
|
||||||
|
path::Path,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -24,6 +25,11 @@ unsafe extern "C" {
|
|||||||
password: *const c_char,
|
password: *const c_char,
|
||||||
) -> *mut WalletHandle;
|
) -> *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_destroy(handle: *mut WalletHandle);
|
||||||
|
|
||||||
fn wallet_ffi_create_account_public(
|
fn wallet_ffi_create_account_public(
|
||||||
@ -56,6 +62,12 @@ unsafe extern "C" {
|
|||||||
out_account: *mut FfiAccount,
|
out_account: *mut FfiAccount,
|
||||||
) -> error::WalletFfiError;
|
) -> 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_free_account_data(account: *mut FfiAccount);
|
||||||
|
|
||||||
fn wallet_ffi_get_public_account_key(
|
fn wallet_ffi_get_public_account_key(
|
||||||
@ -89,6 +101,30 @@ unsafe extern "C" {
|
|||||||
out_result: *mut FfiTransferResult,
|
out_result: *mut FfiTransferResult,
|
||||||
) -> error::WalletFfiError;
|
) -> 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_free_transfer_result(result: *mut FfiTransferResult);
|
||||||
|
|
||||||
fn wallet_ffi_register_public_account(
|
fn wallet_ffi_register_public_account(
|
||||||
@ -96,12 +132,29 @@ unsafe extern "C" {
|
|||||||
account_id: *const FfiBytes32,
|
account_id: *const FfiBytes32,
|
||||||
out_result: *mut FfiTransferResult,
|
out_result: *mut FfiTransferResult,
|
||||||
) -> error::WalletFfiError;
|
) -> 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) -> *mut WalletHandle {
|
fn new_wallet_ffi_with_test_context_config(
|
||||||
let tempdir = tempfile::tempdir().unwrap();
|
ctx: &BlockingTestContext,
|
||||||
let config_path = tempdir.path().join("wallet_config.json");
|
home: &Path,
|
||||||
let storage_path = tempdir.path().join("storage.json");
|
) -> *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();
|
let mut config = ctx.ctx().wallet().config().to_owned();
|
||||||
if let Some(config_overrides) = ctx.ctx().wallet().config_overrides().clone() {
|
if let Some(config_overrides) = ctx.ctx().wallet().config_overrides().clone() {
|
||||||
config.apply_overrides(config_overrides);
|
config.apply_overrides(config_overrides);
|
||||||
@ -161,6 +214,15 @@ fn new_wallet_rust_with_default_config(password: &str) -> WalletCore {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_existing_ffi_wallet(home: &Path) -> *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()).unwrap();
|
||||||
|
let storage_path = CString::new(storage_path.to_str().unwrap()).unwrap();
|
||||||
|
|
||||||
|
unsafe { wallet_ffi_open(config_path.as_ptr(), storage_path.as_ptr()) }
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_wallet_ffi_create_public_accounts() {
|
fn test_wallet_ffi_create_public_accounts() {
|
||||||
let password = "password_for_tests";
|
let password = "password_for_tests";
|
||||||
@ -232,6 +294,56 @@ fn test_wallet_ffi_create_private_accounts() {
|
|||||||
|
|
||||||
assert_eq!(new_private_account_ids_ffi, new_private_account_ids_rust)
|
assert_eq!(new_private_account_ids_ffi, new_private_account_ids_rust)
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_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().unwrap();
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
(&mut out_private_account_id) as *mut FfiBytes32,
|
||||||
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
(&out_private_account_id) as *const FfiBytes32,
|
||||||
|
(&mut private_account) as *mut FfiAccount,
|
||||||
|
);
|
||||||
|
assert_eq!(result, error::WalletFfiError::Success);
|
||||||
|
|
||||||
|
let mut out_keys = FfiPrivateAccountKeys::default();
|
||||||
|
let result = wallet_ffi_get_private_account_keys(
|
||||||
|
wallet_ffi_handle,
|
||||||
|
(&out_private_account_id) as *const FfiBytes32,
|
||||||
|
(&mut out_keys) as *mut FfiPrivateAccountKeys,
|
||||||
|
);
|
||||||
|
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]
|
#[test]
|
||||||
fn test_wallet_ffi_list_accounts() {
|
fn test_wallet_ffi_list_accounts() {
|
||||||
@ -326,7 +438,8 @@ fn test_wallet_ffi_list_accounts() {
|
|||||||
fn test_wallet_ffi_get_balance_public() -> Result<()> {
|
fn test_wallet_ffi_get_balance_public() -> Result<()> {
|
||||||
let ctx = BlockingTestContext::new()?;
|
let ctx = BlockingTestContext::new()?;
|
||||||
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
|
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
|
||||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx);
|
let home = tempfile::tempdir().unwrap();
|
||||||
|
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||||
|
|
||||||
let balance = unsafe {
|
let balance = unsafe {
|
||||||
let mut out_balance: [u8; 16] = [0; 16];
|
let mut out_balance: [u8; 16] = [0; 16];
|
||||||
@ -354,7 +467,8 @@ fn test_wallet_ffi_get_balance_public() -> Result<()> {
|
|||||||
fn test_wallet_ffi_get_account_public() -> Result<()> {
|
fn test_wallet_ffi_get_account_public() -> Result<()> {
|
||||||
let ctx = BlockingTestContext::new()?;
|
let ctx = BlockingTestContext::new()?;
|
||||||
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
|
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
|
||||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx);
|
let home = tempfile::tempdir().unwrap();
|
||||||
|
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||||
let mut out_account = FfiAccount::default();
|
let mut out_account = FfiAccount::default();
|
||||||
|
|
||||||
let account: Account = unsafe {
|
let account: Account = unsafe {
|
||||||
@ -385,11 +499,48 @@ fn test_wallet_ffi_get_account_public() -> Result<()> {
|
|||||||
Ok(())
|
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().unwrap();
|
||||||
|
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);
|
||||||
|
let _result = wallet_ffi_get_account_private(
|
||||||
|
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);
|
||||||
|
wallet_ffi_destroy(wallet_ffi_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Successfully retrieved account with correct details");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_wallet_ffi_get_public_account_keys() -> Result<()> {
|
fn test_wallet_ffi_get_public_account_keys() -> Result<()> {
|
||||||
let ctx = BlockingTestContext::new()?;
|
let ctx = BlockingTestContext::new()?;
|
||||||
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
|
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
|
||||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx);
|
let home = tempfile::tempdir().unwrap();
|
||||||
|
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||||
let mut out_key = FfiPublicAccountKey::default();
|
let mut out_key = FfiPublicAccountKey::default();
|
||||||
|
|
||||||
let key: PublicKey = unsafe {
|
let key: PublicKey = unsafe {
|
||||||
@ -426,7 +577,8 @@ fn test_wallet_ffi_get_public_account_keys() -> Result<()> {
|
|||||||
fn test_wallet_ffi_get_private_account_keys() -> Result<()> {
|
fn test_wallet_ffi_get_private_account_keys() -> Result<()> {
|
||||||
let ctx = BlockingTestContext::new()?;
|
let ctx = BlockingTestContext::new()?;
|
||||||
let account_id: AccountId = ctx.ctx().existing_private_accounts()[0];
|
let account_id: AccountId = ctx.ctx().existing_private_accounts()[0];
|
||||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx);
|
let home = tempfile::tempdir().unwrap();
|
||||||
|
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||||
let mut keys = FfiPrivateAccountKeys::default();
|
let mut keys = FfiPrivateAccountKeys::default();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -504,7 +656,8 @@ fn test_wallet_ffi_base58_to_account_id() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
|
fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
|
||||||
let ctx = BlockingTestContext::new().unwrap();
|
let ctx = BlockingTestContext::new().unwrap();
|
||||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx);
|
let home = tempfile::tempdir().unwrap();
|
||||||
|
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
|
||||||
|
|
||||||
// Create a new uninitialized public account
|
// Create a new uninitialized public account
|
||||||
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
|
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
|
||||||
@ -563,10 +716,81 @@ fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
|
||||||
|
let ctx = BlockingTestContext::new().unwrap();
|
||||||
|
let home = tempfile::tempdir().unwrap();
|
||||||
|
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,
|
||||||
|
(&mut out_account_id) as *mut FfiBytes32,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
(&out_account_id) as *const FfiBytes32,
|
||||||
|
(&mut out_account) as *mut FfiAccount,
|
||||||
|
);
|
||||||
|
(&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,
|
||||||
|
(&out_account_id) as *const FfiBytes32,
|
||||||
|
(&mut transfer_result) as *mut FfiTransferResult,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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, (&mut current_height) as *mut u64);
|
||||||
|
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();
|
||||||
|
let _result = wallet_ffi_get_account_private(
|
||||||
|
wallet_ffi_handle,
|
||||||
|
(&out_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()
|
||||||
|
);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
|
||||||
|
wallet_ffi_destroy(wallet_ffi_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_wallet_ffi_transfer_public() -> Result<()> {
|
fn test_wallet_ffi_transfer_public() -> Result<()> {
|
||||||
let ctx = BlockingTestContext::new().unwrap();
|
let ctx = BlockingTestContext::new().unwrap();
|
||||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx);
|
let home = tempfile::tempdir().unwrap();
|
||||||
|
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 from: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[0]).into();
|
||||||
let to: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[1]).into();
|
let to: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[1]).into();
|
||||||
let amount: [u8; 16] = 100u128.to_le_bytes();
|
let amount: [u8; 16] = 100u128.to_le_bytes();
|
||||||
@ -617,3 +841,220 @@ fn test_wallet_ffi_transfer_public() -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wallet_ffi_transfer_shielded() -> Result<()> {
|
||||||
|
let ctx = BlockingTestContext::new().unwrap();
|
||||||
|
let home = tempfile::tempdir().unwrap();
|
||||||
|
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,
|
||||||
|
(&mut out_account_id) as *mut FfiBytes32,
|
||||||
|
);
|
||||||
|
wallet_ffi_get_private_account_keys(
|
||||||
|
wallet_ffi_handle,
|
||||||
|
(&out_account_id) as *const FfiBytes32,
|
||||||
|
(&mut out_keys) as *mut FfiPrivateAccountKeys,
|
||||||
|
);
|
||||||
|
(out_account_id, out_keys)
|
||||||
|
};
|
||||||
|
let amount: [u8; 16] = 100u128.to_le_bytes();
|
||||||
|
|
||||||
|
let mut transfer_result = FfiTransferResult::default();
|
||||||
|
unsafe {
|
||||||
|
wallet_ffi_transfer_shielded(
|
||||||
|
wallet_ffi_handle,
|
||||||
|
(&from) as *const FfiBytes32,
|
||||||
|
(&to_keys) as *const FfiPrivateAccountKeys,
|
||||||
|
(&amount) as *const [u8; 16],
|
||||||
|
(&mut transfer_result) as *mut FfiTransferResult,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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, (&mut current_height) as *mut u64);
|
||||||
|
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,
|
||||||
|
(&from) as *const FfiBytes32,
|
||||||
|
true,
|
||||||
|
(&mut out_balance) as *mut [u8; 16],
|
||||||
|
);
|
||||||
|
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,
|
||||||
|
(&to) as *const FfiBytes32,
|
||||||
|
false,
|
||||||
|
(&mut out_balance) as *mut [u8; 16],
|
||||||
|
);
|
||||||
|
u128::from_le_bytes(out_balance)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(from_balance, 9900);
|
||||||
|
assert_eq!(to_balance, 100);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
|
||||||
|
wallet_ffi_destroy(wallet_ffi_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
|
||||||
|
let ctx = BlockingTestContext::new().unwrap();
|
||||||
|
let home = tempfile::tempdir().unwrap();
|
||||||
|
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] = 100u128.to_le_bytes();
|
||||||
|
|
||||||
|
let mut transfer_result = FfiTransferResult::default();
|
||||||
|
unsafe {
|
||||||
|
wallet_ffi_transfer_deshielded(
|
||||||
|
wallet_ffi_handle,
|
||||||
|
(&from) as *const FfiBytes32,
|
||||||
|
(&to) as *const FfiBytes32,
|
||||||
|
(&amount) as *const [u8; 16],
|
||||||
|
(&mut transfer_result) as *mut FfiTransferResult,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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, (&mut current_height) as *mut u64);
|
||||||
|
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,
|
||||||
|
(&from) as *const FfiBytes32,
|
||||||
|
false,
|
||||||
|
(&mut out_balance) as *mut [u8; 16],
|
||||||
|
);
|
||||||
|
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,
|
||||||
|
(&to) as *const FfiBytes32,
|
||||||
|
true,
|
||||||
|
(&mut out_balance) as *mut [u8; 16],
|
||||||
|
);
|
||||||
|
u128::from_le_bytes(out_balance)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(from_balance, 9900);
|
||||||
|
assert_eq!(to_balance, 100);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
|
||||||
|
wallet_ffi_destroy(wallet_ffi_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wallet_ffi_transfer_private() -> Result<()> {
|
||||||
|
let ctx = BlockingTestContext::new().unwrap();
|
||||||
|
let home = tempfile::tempdir().unwrap();
|
||||||
|
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,
|
||||||
|
(&mut out_account_id) as *mut FfiBytes32,
|
||||||
|
);
|
||||||
|
wallet_ffi_get_private_account_keys(
|
||||||
|
wallet_ffi_handle,
|
||||||
|
(&out_account_id) as *const FfiBytes32,
|
||||||
|
(&mut out_keys) as *mut FfiPrivateAccountKeys,
|
||||||
|
);
|
||||||
|
(out_account_id, out_keys)
|
||||||
|
};
|
||||||
|
|
||||||
|
let amount: [u8; 16] = 100u128.to_le_bytes();
|
||||||
|
|
||||||
|
let mut transfer_result = FfiTransferResult::default();
|
||||||
|
unsafe {
|
||||||
|
wallet_ffi_transfer_private(
|
||||||
|
wallet_ffi_handle,
|
||||||
|
(&from) as *const FfiBytes32,
|
||||||
|
(&to_keys) as *const FfiPrivateAccountKeys,
|
||||||
|
(&amount) as *const [u8; 16],
|
||||||
|
(&mut transfer_result) as *mut FfiTransferResult,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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, (&mut current_height) as *mut u64);
|
||||||
|
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,
|
||||||
|
(&from) as *const FfiBytes32,
|
||||||
|
false,
|
||||||
|
(&mut out_balance) as *mut [u8; 16],
|
||||||
|
);
|
||||||
|
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,
|
||||||
|
(&to) as *const FfiBytes32,
|
||||||
|
false,
|
||||||
|
(&mut out_balance) as *mut [u8; 16],
|
||||||
|
);
|
||||||
|
u128::from_le_bytes(out_balance)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(from_balance, 9900);
|
||||||
|
assert_eq!(to_balance, 100);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
|
||||||
|
wallet_ffi_destroy(wallet_ffi_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@ -33,3 +33,4 @@ test-case = "3.3.1"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
prove = ["risc0-zkvm/prove"]
|
||||||
|
|||||||
@ -118,7 +118,7 @@ pub fn add_liquidity(
|
|||||||
assert!(delta_lp != 0, "Payable LP must be nonzero");
|
assert!(delta_lp != 0, "Payable LP must be nonzero");
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
delta_lp >= min_amount_liquidity.into(),
|
delta_lp >= min_amount_liquidity.get(),
|
||||||
"Payable LP is less than provided minimum LP amount"
|
"Payable LP is less than provided minimum LP amount"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -19,3 +19,7 @@ cbindgen = "0.29"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
prove = ["nssa/prove"]
|
||||||
|
|||||||
@ -354,6 +354,61 @@ pub unsafe extern "C" fn wallet_ffi_get_account_public(
|
|||||||
WalletFfiError::Success
|
WalletFfiError::Success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get full private account data from the local storage.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `handle`: Valid wallet handle
|
||||||
|
/// - `account_id`: The account ID (32 bytes)
|
||||||
|
/// - `out_account`: Output pointer for account data
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// - `Success` on successful query
|
||||||
|
/// - Error code on failure
|
||||||
|
///
|
||||||
|
/// # 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 unsafe extern "C" fn wallet_ffi_get_account_private(
|
||||||
|
handle: *mut WalletHandle,
|
||||||
|
account_id: *const FfiBytes32,
|
||||||
|
out_account: *mut FfiAccount,
|
||||||
|
) -> WalletFfiError {
|
||||||
|
let wrapper = match get_wallet(handle) {
|
||||||
|
Ok(w) => w,
|
||||||
|
Err(e) => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
if account_id.is_null() || out_account.is_null() {
|
||||||
|
print_error("Null pointer argument");
|
||||||
|
return WalletFfiError::NullPointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
let wallet = match wrapper.core.lock() {
|
||||||
|
Ok(w) => w,
|
||||||
|
Err(e) => {
|
||||||
|
print_error(format!("Failed to lock wallet: {}", e));
|
||||||
|
return WalletFfiError::InternalError;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let account_id = AccountId::new(unsafe { (*account_id).data });
|
||||||
|
|
||||||
|
let Some(account) = wallet.get_account_private(account_id) else {
|
||||||
|
return WalletFfiError::AccountNotFound;
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
*out_account = account.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
WalletFfiError::Success
|
||||||
|
}
|
||||||
|
|
||||||
/// Free account data returned by `wallet_ffi_get_account_public`.
|
/// Free account data returned by `wallet_ffi_get_account_public`.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
|||||||
@ -11,6 +11,7 @@ use crate::{
|
|||||||
error::{print_error, WalletFfiError},
|
error::{print_error, WalletFfiError},
|
||||||
types::{FfiBytes32, FfiTransferResult, WalletHandle},
|
types::{FfiBytes32, FfiTransferResult, WalletHandle},
|
||||||
wallet::get_wallet,
|
wallet::get_wallet,
|
||||||
|
FfiPrivateAccountKeys,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Send a public token transfer.
|
/// Send a public token transfer.
|
||||||
@ -89,13 +90,270 @@ pub unsafe extern "C" fn wallet_ffi_transfer_public(
|
|||||||
(*out_result).tx_hash = ptr::null_mut();
|
(*out_result).tx_hash = ptr::null_mut();
|
||||||
(*out_result).success = false;
|
(*out_result).success = false;
|
||||||
}
|
}
|
||||||
match e {
|
map_execution_error(e)
|
||||||
ExecutionFailureKind::InsufficientFundsError => WalletFfiError::InsufficientFunds,
|
}
|
||||||
ExecutionFailureKind::KeyNotFoundError => WalletFfiError::KeyNotFound,
|
Err(e) => e,
|
||||||
ExecutionFailureKind::SequencerError => WalletFfiError::NetworkError,
|
}
|
||||||
ExecutionFailureKind::SequencerClientError(_) => WalletFfiError::NetworkError,
|
}
|
||||||
_ => WalletFfiError::InternalError,
|
|
||||||
|
/// Send a shielded token transfer.
|
||||||
|
///
|
||||||
|
/// Transfers tokens from a public account to a private account.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `handle`: Valid wallet handle
|
||||||
|
/// - `from`: Source account ID (must be owned by this wallet)
|
||||||
|
/// - `to_keys`: Destination account keys
|
||||||
|
/// - `amount`: Amount to transfer as little-endian [u8; 16]
|
||||||
|
/// - `out_result`: Output pointer for transfer result
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// - `Success` if the transfer was submitted successfully
|
||||||
|
/// - `InsufficientFunds` if the source account doesn't have enough balance
|
||||||
|
/// - `KeyNotFound` if the source account's signing key is not in this wallet
|
||||||
|
/// - Error code on other failures
|
||||||
|
///
|
||||||
|
/// # 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_keys` must be a valid pointer to a `FfiPrivateAccountKeys` 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 unsafe extern "C" fn wallet_ffi_transfer_shielded(
|
||||||
|
handle: *mut WalletHandle,
|
||||||
|
from: *const FfiBytes32,
|
||||||
|
to_keys: *const FfiPrivateAccountKeys,
|
||||||
|
amount: *const [u8; 16],
|
||||||
|
out_result: *mut FfiTransferResult,
|
||||||
|
) -> WalletFfiError {
|
||||||
|
let wrapper = match get_wallet(handle) {
|
||||||
|
Ok(w) => w,
|
||||||
|
Err(e) => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
if from.is_null() || to_keys.is_null() || amount.is_null() || out_result.is_null() {
|
||||||
|
print_error("Null pointer argument");
|
||||||
|
return WalletFfiError::NullPointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
let wallet = match wrapper.core.lock() {
|
||||||
|
Ok(w) => w,
|
||||||
|
Err(e) => {
|
||||||
|
print_error(format!("Failed to lock wallet: {}", e));
|
||||||
|
return WalletFfiError::InternalError;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_id = AccountId::new(unsafe { (*from).data });
|
||||||
|
let to_npk = (*to_keys).npk();
|
||||||
|
let to_ipk = match (*to_keys).ivk() {
|
||||||
|
Ok(ipk) => ipk,
|
||||||
|
Err(e) => {
|
||||||
|
print_error("Invalid viewing key");
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let amount = u128::from_le_bytes(unsafe { *amount });
|
||||||
|
|
||||||
|
let transfer = NativeTokenTransfer(&wallet);
|
||||||
|
|
||||||
|
match block_on(
|
||||||
|
transfer.send_shielded_transfer_to_outer_account(from_id, to_npk, to_ipk, amount),
|
||||||
|
) {
|
||||||
|
Ok(Ok((response, _shared_key))) => {
|
||||||
|
let tx_hash = CString::new(response.tx_hash)
|
||||||
|
.map(|s| s.into_raw())
|
||||||
|
.unwrap_or(ptr::null_mut());
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
(*out_result).tx_hash = tx_hash;
|
||||||
|
(*out_result).success = true;
|
||||||
}
|
}
|
||||||
|
WalletFfiError::Success
|
||||||
|
}
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
print_error(format!("Transfer failed: {:?}", e));
|
||||||
|
unsafe {
|
||||||
|
(*out_result).tx_hash = ptr::null_mut();
|
||||||
|
(*out_result).success = false;
|
||||||
|
}
|
||||||
|
map_execution_error(e)
|
||||||
|
}
|
||||||
|
Err(e) => e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a deshielded token transfer.
|
||||||
|
///
|
||||||
|
/// Transfers tokens from a private account to a public account.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `handle`: Valid wallet handle
|
||||||
|
/// - `from`: Source account ID (must be owned by this wallet)
|
||||||
|
/// - `to`: Destination account ID
|
||||||
|
/// - `amount`: Amount to transfer as little-endian [u8; 16]
|
||||||
|
/// - `out_result`: Output pointer for transfer result
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// - `Success` if the transfer was submitted successfully
|
||||||
|
/// - `InsufficientFunds` if the source account doesn't have enough balance
|
||||||
|
/// - `KeyNotFound` if the source account's signing key is not in this wallet
|
||||||
|
/// - Error code on other failures
|
||||||
|
///
|
||||||
|
/// # 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 unsafe extern "C" fn wallet_ffi_transfer_deshielded(
|
||||||
|
handle: *mut WalletHandle,
|
||||||
|
from: *const FfiBytes32,
|
||||||
|
to: *const FfiBytes32,
|
||||||
|
amount: *const [u8; 16],
|
||||||
|
out_result: *mut FfiTransferResult,
|
||||||
|
) -> WalletFfiError {
|
||||||
|
let wrapper = match get_wallet(handle) {
|
||||||
|
Ok(w) => w,
|
||||||
|
Err(e) => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
if from.is_null() || to.is_null() || amount.is_null() || out_result.is_null() {
|
||||||
|
print_error("Null pointer argument");
|
||||||
|
return WalletFfiError::NullPointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
let wallet = match wrapper.core.lock() {
|
||||||
|
Ok(w) => w,
|
||||||
|
Err(e) => {
|
||||||
|
print_error(format!("Failed to lock wallet: {}", e));
|
||||||
|
return WalletFfiError::InternalError;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_id = AccountId::new(unsafe { (*from).data });
|
||||||
|
let to_id = AccountId::new(unsafe { (*to).data });
|
||||||
|
let amount = u128::from_le_bytes(unsafe { *amount });
|
||||||
|
|
||||||
|
let transfer = NativeTokenTransfer(&wallet);
|
||||||
|
|
||||||
|
match block_on(transfer.send_deshielded_transfer(from_id, to_id, amount)) {
|
||||||
|
Ok(Ok((response, _shared_key))) => {
|
||||||
|
let tx_hash = CString::new(response.tx_hash)
|
||||||
|
.map(|s| s.into_raw())
|
||||||
|
.unwrap_or(ptr::null_mut());
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
(*out_result).tx_hash = tx_hash;
|
||||||
|
(*out_result).success = true;
|
||||||
|
}
|
||||||
|
WalletFfiError::Success
|
||||||
|
}
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
print_error(format!("Transfer failed: {:?}", e));
|
||||||
|
unsafe {
|
||||||
|
(*out_result).tx_hash = ptr::null_mut();
|
||||||
|
(*out_result).success = false;
|
||||||
|
}
|
||||||
|
map_execution_error(e)
|
||||||
|
}
|
||||||
|
Err(e) => e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a private token transfer.
|
||||||
|
///
|
||||||
|
/// Transfers tokens from a private account to another private account.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `handle`: Valid wallet handle
|
||||||
|
/// - `from`: Source account ID (must be owned by this wallet)
|
||||||
|
/// - `to_keys`: Destination account keys
|
||||||
|
/// - `amount`: Amount to transfer as little-endian [u8; 16]
|
||||||
|
/// - `out_result`: Output pointer for transfer result
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// - `Success` if the transfer was submitted successfully
|
||||||
|
/// - `InsufficientFunds` if the source account doesn't have enough balance
|
||||||
|
/// - `KeyNotFound` if the source account's signing key is not in this wallet
|
||||||
|
/// - Error code on other failures
|
||||||
|
///
|
||||||
|
/// # 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_keys` must be a valid pointer to a `FfiPrivateAccountKeys` 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 unsafe extern "C" fn wallet_ffi_transfer_private(
|
||||||
|
handle: *mut WalletHandle,
|
||||||
|
from: *const FfiBytes32,
|
||||||
|
to_keys: *const FfiPrivateAccountKeys,
|
||||||
|
amount: *const [u8; 16],
|
||||||
|
out_result: *mut FfiTransferResult,
|
||||||
|
) -> WalletFfiError {
|
||||||
|
let wrapper = match get_wallet(handle) {
|
||||||
|
Ok(w) => w,
|
||||||
|
Err(e) => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
if from.is_null() || to_keys.is_null() || amount.is_null() || out_result.is_null() {
|
||||||
|
print_error("Null pointer argument");
|
||||||
|
return WalletFfiError::NullPointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
let wallet = match wrapper.core.lock() {
|
||||||
|
Ok(w) => w,
|
||||||
|
Err(e) => {
|
||||||
|
print_error(format!("Failed to lock wallet: {}", e));
|
||||||
|
return WalletFfiError::InternalError;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_id = AccountId::new(unsafe { (*from).data });
|
||||||
|
let to_npk = (*to_keys).npk();
|
||||||
|
let to_ipk = match (*to_keys).ivk() {
|
||||||
|
Ok(ipk) => ipk,
|
||||||
|
Err(e) => {
|
||||||
|
print_error("Invalid viewing key");
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let amount = u128::from_le_bytes(unsafe { *amount });
|
||||||
|
|
||||||
|
let transfer = NativeTokenTransfer(&wallet);
|
||||||
|
|
||||||
|
match block_on(transfer.send_private_transfer_to_outer_account(from_id, to_npk, to_ipk, amount))
|
||||||
|
{
|
||||||
|
Ok(Ok((response, _shared_key))) => {
|
||||||
|
let tx_hash = CString::new(response.tx_hash)
|
||||||
|
.map(|s| s.into_raw())
|
||||||
|
.unwrap_or(ptr::null_mut());
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
(*out_result).tx_hash = tx_hash;
|
||||||
|
(*out_result).success = true;
|
||||||
|
}
|
||||||
|
WalletFfiError::Success
|
||||||
|
}
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
print_error(format!("Transfer failed: {:?}", e));
|
||||||
|
unsafe {
|
||||||
|
(*out_result).tx_hash = ptr::null_mut();
|
||||||
|
(*out_result).success = false;
|
||||||
|
}
|
||||||
|
map_execution_error(e)
|
||||||
}
|
}
|
||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
}
|
}
|
||||||
@ -168,12 +426,80 @@ pub unsafe extern "C" fn wallet_ffi_register_public_account(
|
|||||||
(*out_result).tx_hash = ptr::null_mut();
|
(*out_result).tx_hash = ptr::null_mut();
|
||||||
(*out_result).success = false;
|
(*out_result).success = false;
|
||||||
}
|
}
|
||||||
match e {
|
map_execution_error(e)
|
||||||
ExecutionFailureKind::KeyNotFoundError => WalletFfiError::KeyNotFound,
|
}
|
||||||
ExecutionFailureKind::SequencerError => WalletFfiError::NetworkError,
|
Err(e) => e,
|
||||||
ExecutionFailureKind::SequencerClientError(_) => WalletFfiError::NetworkError,
|
}
|
||||||
_ => WalletFfiError::InternalError,
|
}
|
||||||
|
|
||||||
|
/// Register a private account on the network.
|
||||||
|
///
|
||||||
|
/// This initializes a private account. The account must be
|
||||||
|
/// owned by this wallet.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `handle`: Valid wallet handle
|
||||||
|
/// - `account_id`: Account ID to register
|
||||||
|
/// - `out_result`: Output pointer for registration result
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// - `Success` if the registration was submitted successfully
|
||||||
|
/// - Error code on failure
|
||||||
|
///
|
||||||
|
/// # 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 unsafe extern "C" fn wallet_ffi_register_private_account(
|
||||||
|
handle: *mut WalletHandle,
|
||||||
|
account_id: *const FfiBytes32,
|
||||||
|
out_result: *mut FfiTransferResult,
|
||||||
|
) -> WalletFfiError {
|
||||||
|
let wrapper = match get_wallet(handle) {
|
||||||
|
Ok(w) => w,
|
||||||
|
Err(e) => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
if account_id.is_null() || out_result.is_null() {
|
||||||
|
print_error("Null pointer argument");
|
||||||
|
return WalletFfiError::NullPointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
let wallet = match wrapper.core.lock() {
|
||||||
|
Ok(w) => w,
|
||||||
|
Err(e) => {
|
||||||
|
print_error(format!("Failed to lock wallet: {}", e));
|
||||||
|
return WalletFfiError::InternalError;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let account_id = AccountId::new(unsafe { (*account_id).data });
|
||||||
|
|
||||||
|
let transfer = NativeTokenTransfer(&wallet);
|
||||||
|
|
||||||
|
match block_on(transfer.register_account_private(account_id)) {
|
||||||
|
Ok(Ok((res, _secret))) => {
|
||||||
|
let tx_hash = CString::new(res.tx_hash)
|
||||||
|
.map(|s| s.into_raw())
|
||||||
|
.unwrap_or(ptr::null_mut());
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
(*out_result).tx_hash = tx_hash;
|
||||||
|
(*out_result).success = true;
|
||||||
}
|
}
|
||||||
|
WalletFfiError::Success
|
||||||
|
}
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
print_error(format!("Registration failed: {:?}", e));
|
||||||
|
unsafe {
|
||||||
|
(*out_result).tx_hash = ptr::null_mut();
|
||||||
|
(*out_result).success = false;
|
||||||
|
}
|
||||||
|
map_execution_error(e)
|
||||||
}
|
}
|
||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
}
|
}
|
||||||
@ -197,3 +523,13 @@ pub unsafe extern "C" fn wallet_ffi_free_transfer_result(result: *mut FfiTransfe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_execution_error(e: ExecutionFailureKind) -> WalletFfiError {
|
||||||
|
match e {
|
||||||
|
ExecutionFailureKind::InsufficientFundsError => WalletFfiError::InsufficientFunds,
|
||||||
|
ExecutionFailureKind::KeyNotFoundError => WalletFfiError::KeyNotFound,
|
||||||
|
ExecutionFailureKind::SequencerError => WalletFfiError::NetworkError,
|
||||||
|
ExecutionFailureKind::SequencerClientError(_) => WalletFfiError::NetworkError,
|
||||||
|
_ => WalletFfiError::InternalError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -298,6 +298,8 @@ impl WalletCore {
|
|||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
program: &ProgramWithDependencies,
|
program: &ProgramWithDependencies,
|
||||||
) -> Result<(SendTxResponse, Vec<SharedSecretKey>), ExecutionFailureKind> {
|
) -> Result<(SendTxResponse, Vec<SharedSecretKey>), ExecutionFailureKind> {
|
||||||
|
// TODO: handle large Err-variant properly
|
||||||
|
#[allow(clippy::result_large_err)]
|
||||||
self.send_privacy_preserving_tx_with_pre_check(accounts, instruction_data, program, |_| {
|
self.send_privacy_preserving_tx_with_pre_check(accounts, instruction_data, program, |_| {
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|||||||
@ -20,6 +20,9 @@ fn auth_transfer_preparation(
|
|||||||
) {
|
) {
|
||||||
let instruction_data = Program::serialize_instruction(balance_to_move).unwrap();
|
let instruction_data = Program::serialize_instruction(balance_to_move).unwrap();
|
||||||
let program = Program::authenticated_transfer_program();
|
let program = Program::authenticated_transfer_program();
|
||||||
|
|
||||||
|
// TODO: handle large Err-variant properly
|
||||||
|
#[allow(clippy::result_large_err)]
|
||||||
let tx_pre_check = move |accounts: &[&Account]| {
|
let tx_pre_check = move |accounts: &[&Account]| {
|
||||||
let from = accounts[0];
|
let from = accounts[0];
|
||||||
if from.balance >= balance_to_move {
|
if from.balance >= balance_to_move {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user