feat: introduce parallel integration tests, wallet without global vars and etc

This commit is contained in:
Daniil Polyakov 2025-12-31 04:02:25 +03:00
parent 1d09afd9e0
commit 7296088005
48 changed files with 3218 additions and 3787 deletions

View File

@ -83,7 +83,7 @@ jobs:
RISC0_SKIP_BUILD: "1"
run: cargo clippy -p "*programs" -- -D warnings
unit-tests:
tests:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
@ -104,6 +104,7 @@ jobs:
- name: Run unit tests
env:
RISC0_DEV_MODE: "1"
RUST_LOG: "info"
run: cargo nextest run --no-fail-fast
valid-proof-test:
@ -123,31 +124,8 @@ jobs:
- name: Test valid proof
env:
NSSA_WALLET_HOME_DIR: ./integration_tests/configs/debug/wallet
RUST_LOG: "info"
run: cargo run --bin integration_tests -- ./integration_tests/configs/debug/ test_success_private_transfer_to_another_owned_account
integration-tests:
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@v5
with:
ref: ${{ github.head_ref }}
- uses: ./.github/actions/install-system-deps
- uses: ./.github/actions/install-risc0
- name: Install active toolchain
run: rustup install
- name: Run integration tests
env:
NSSA_WALLET_HOME_DIR: ./integration_tests/configs/debug/wallet
RUST_LOG: "info"
RISC0_DEV_MODE: "1"
run: cargo run --bin integration_tests -- ./integration_tests/configs/debug/ all
run: cargo test -p integration_tests -- --exact private::private_transfer_to_owned_account
artifacts:
runs-on: ubuntu-latest

21
Cargo.lock generated
View File

@ -2248,20 +2248,18 @@ dependencies = [
name = "integration_tests"
version = "0.1.0"
dependencies = [
"actix",
"actix-web",
"anyhow",
"base64 0.22.1",
"borsh",
"clap",
"common",
"env_logger",
"futures",
"hex",
"key_protocol",
"log",
"nssa",
"nssa_core",
"proc_macro_test_attribute",
"sequencer_core",
"sequencer_runner",
"tempfile",
@ -2844,6 +2842,17 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "optfield"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "969ccca8ffc4fb105bd131a228107d5c9dd89d9d627edf3295cbe979156f9712"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
]
[[package]]
name = "option-ext"
version = "0.2.0"
@ -3017,10 +3026,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "proc_macro_test_attribute"
version = "0.1.0"
[[package]]
name = "program_deployment"
version = "0.1.0"
@ -3904,6 +3909,7 @@ dependencies = [
"nssa",
"nssa_core",
"serde",
"serde_json",
"storage",
"tempfile",
"tokio",
@ -4756,6 +4762,7 @@ dependencies = [
"log",
"nssa",
"nssa_core",
"optfield",
"rand 0.8.5",
"risc0-zkvm",
"serde",

View File

@ -16,7 +16,6 @@ members = [
"program_methods/guest",
"test_program_methods",
"test_program_methods/guest",
"integration_tests/proc_macro_test_attribute",
"examples/program_deployment",
"examples/program_deployment/methods",
"examples/program_deployment/methods/guest",

View File

@ -44,8 +44,10 @@ impl SequencerClient {
) -> Result<Self> {
Ok(Self {
client: Client::builder()
//Add more fiedls if needed
// Add more fields if needed
.timeout(std::time::Duration::from_secs(60))
// Should be kept in sync with server keep-alive settings
.pool_idle_timeout(std::time::Duration::from_secs(5))
.build()?,
sequencer_addr,
basic_auth,
@ -60,6 +62,10 @@ impl SequencerClient {
let request =
rpc_primitives::message::Request::from_payload_version_2_0(method.to_string(), payload);
log::debug!(
"Calling method {method} with payload {request:?} to sequencer at {}",
self.sequencer_addr
);
let mut call_builder = self.client.post(&self.sequencer_addr);
if let Some((username, password)) = &self.basic_auth {

View File

@ -3,7 +3,7 @@ use nssa::{
program::Program,
public_transaction::{Message, WitnessSet},
};
use wallet::{WalletCore, helperfunctions::fetch_config};
use wallet::WalletCore;
// Before running this example, compile the `hello_world.rs` guest program with:
//
@ -24,11 +24,8 @@ use wallet::{WalletCore, helperfunctions::fetch_config};
#[tokio::main]
async fn main() {
// Load wallet config and storage
let wallet_config = fetch_config().await.unwrap();
let wallet_core = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
// Initialize wallet
let wallet_core = WalletCore::from_env().unwrap();
// Parse arguments
// First argument is the path to the program binary

View File

@ -1,5 +1,5 @@
use nssa::{AccountId, program::Program};
use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config};
use wallet::{PrivacyPreservingAccount, WalletCore};
// Before running this example, compile the `hello_world.rs` guest program with:
//
@ -22,11 +22,8 @@ use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config
#[tokio::main]
async fn main() {
// Load wallet config and storage
let wallet_config = fetch_config().await.unwrap();
let wallet_core = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
// Initialize wallet
let wallet_core = WalletCore::from_env().unwrap();
// Parse arguments
// First argument is the path to the program binary

View File

@ -3,7 +3,7 @@ use nssa::{
program::Program,
public_transaction::{Message, WitnessSet},
};
use wallet::{WalletCore, helperfunctions::fetch_config};
use wallet::WalletCore;
// Before running this example, compile the `simple_tail_call.rs` guest program with:
//
@ -24,11 +24,8 @@ use wallet::{WalletCore, helperfunctions::fetch_config};
#[tokio::main]
async fn main() {
// Load wallet config and storage
let wallet_config = fetch_config().await.unwrap();
let wallet_core = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
// Initialize wallet
let wallet_core = WalletCore::from_env().unwrap();
// Parse arguments
// First argument is the path to the program binary

View File

@ -4,7 +4,7 @@ use nssa::{
AccountId, ProgramId, privacy_preserving_transaction::circuit::ProgramWithDependencies,
program::Program,
};
use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config};
use wallet::{PrivacyPreservingAccount, WalletCore};
// Before running this example, compile the `simple_tail_call.rs` guest program with:
//
@ -25,11 +25,8 @@ use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config
#[tokio::main]
async fn main() {
// Load wallet config and storage
let wallet_config = fetch_config().await.unwrap();
let wallet_core = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
// Initialize wallet
let wallet_core = WalletCore::from_env().unwrap();
// Parse arguments
// First argument is the path to the simple_tail_call program binary

View File

@ -3,7 +3,7 @@ use nssa::{
program::Program,
public_transaction::{Message, WitnessSet},
};
use wallet::{WalletCore, helperfunctions::fetch_config};
use wallet::WalletCore;
// Before running this example, compile the `hello_world_with_authorization.rs` guest program with:
//
@ -26,11 +26,8 @@ use wallet::{WalletCore, helperfunctions::fetch_config};
#[tokio::main]
async fn main() {
// Load wallet config and storage
let wallet_config = fetch_config().await.unwrap();
let wallet_core = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
// Initialize wallet
let wallet_core = WalletCore::from_env().unwrap();
// Parse arguments
// First argument is the path to the program binary
@ -50,7 +47,7 @@ async fn main() {
// Load signing keys to provide authorization
let signing_key = wallet_core
.storage
.storage()
.user_data
.get_pub_account_signing_key(&account_id)
.expect("Input account should be a self owned public account");

View File

@ -4,7 +4,7 @@ use nssa::{
public_transaction::{Message, WitnessSet},
};
use nssa_core::program::PdaSeed;
use wallet::{WalletCore, helperfunctions::fetch_config};
use wallet::WalletCore;
// Before running this example, compile the `simple_tail_call.rs` guest program with:
//
@ -27,11 +27,8 @@ const PDA_SEED: PdaSeed = PdaSeed::new([37; 32]);
#[tokio::main]
async fn main() {
// Load wallet config and storage
let wallet_config = fetch_config().await.unwrap();
let wallet_core = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
// Initialize wallet
let wallet_core = WalletCore::from_env().unwrap();
// Parse arguments
// First argument is the path to the program binary

View File

@ -1,6 +1,6 @@
use clap::{Parser, Subcommand};
use nssa::{PublicTransaction, program::Program, public_transaction};
use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config};
use wallet::{PrivacyPreservingAccount, WalletCore};
// Before running this example, compile the `hello_world_with_move_function.rs` guest program with:
//
@ -62,11 +62,8 @@ async fn main() {
let bytecode: Vec<u8> = std::fs::read(cli.program_path).unwrap();
let program = Program::new(bytecode).unwrap();
// Load wallet config and storage
let wallet_config = fetch_config().await.unwrap();
let wallet_core = WalletCore::start_from_config_update_chain(wallet_config)
.await
.unwrap();
// Initialize wallet
let wallet_core = WalletCore::from_env().unwrap();
match cli.command {
Command::WritePublic {

View File

@ -11,16 +11,14 @@ sequencer_runner.workspace = true
wallet.workspace = true
common.workspace = true
key_protocol.workspace = true
proc_macro_test_attribute = { path = "./proc_macro_test_attribute" }
clap = { workspace = true, features = ["derive", "env"] }
anyhow.workspace = true
env_logger.workspace = true
log.workspace = true
actix.workspace = true
actix-web.workspace = true
base64.workspace = true
tokio.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
hex.workspace = true
tempfile.workspace = true
borsh.workspace = true
futures.workspace = true

View File

@ -1,12 +1,12 @@
{
"home": "./sequencer",
"home": "",
"override_rust_log": null,
"genesis_id": 1,
"is_genesis_random": true,
"max_num_tx_in_block": 20,
"mempool_max_size": 10000,
"block_create_timeout_millis": 10000,
"port": 3040,
"port": 0,
"initial_accounts": [
{
"account_id": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy",

View File

@ -1,10 +1,11 @@
{
"override_rust_log": null,
"sequencer_addr": "http://127.0.0.1:3040",
"sequencer_addr": "",
"seq_poll_timeout_millis": 12000,
"seq_tx_poll_max_blocks": 5,
"seq_poll_max_retries": 5,
"seq_block_poll_max_amount": 100,
"basic_auth": null,
"initial_accounts": [
{
"Public": {
@ -542,6 +543,5 @@
}
}
}
],
"basic_auth": null
]
}

View File

@ -1,9 +0,0 @@
[package]
name = "proc_macro_test_attribute"
version = "0.1.0"
edition = "2024"
[dependencies]
[lib]
proc-macro = true

View File

@ -1,49 +0,0 @@
extern crate proc_macro;
use proc_macro::*;
#[proc_macro_attribute]
pub fn nssa_integration_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = item.to_string();
let fn_keyword = "fn ";
let fn_keyword_alternative = "fn\n";
let mut start_opt = None;
let mut fn_name = String::new();
if let Some(start) = input.find(fn_keyword) {
start_opt = Some(start);
} else if let Some(start) = input.find(fn_keyword_alternative) {
start_opt = Some(start);
}
if let Some(start) = start_opt {
let rest = &input[start + fn_keyword.len()..];
if let Some(end) = rest.find(|c: char| c == '(' || c.is_whitespace()) {
let name = &rest[..end];
fn_name = name.to_string();
}
} else {
println!("ERROR: keyword fn not found");
}
let extension = format!(
r#"
{input}
function_map.insert("{fn_name}".to_string(), |home_dir: PathBuf| Box::pin(async {{
let res = pre_test(home_dir).await.unwrap();
info!("Waiting for first block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
{fn_name}().await;
post_test(res).await;
}}));
"#
);
extension.parse().unwrap()
}

View File

@ -1,38 +1,25 @@
use std::path::PathBuf;
//! This library contains common code for integration tests.
use std::{net::SocketAddr, path::PathBuf, sync::LazyLock};
use actix_web::dev::ServerHandle;
use anyhow::Result;
use anyhow::{Context as _, Result};
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
use clap::Parser;
use common::{
sequencer_client::SequencerClient,
transaction::{EncodedTransaction, NSSATransaction},
};
use log::{info, warn};
use futures::FutureExt as _;
use log::debug;
use nssa::PrivacyPreservingTransaction;
use nssa_core::Commitment;
use sequencer_core::config::SequencerConfig;
use sequencer_runner::startup_sequencer;
use tempfile::TempDir;
use tokio::task::JoinHandle;
use wallet::{WalletCore, config::WalletConfigOverrides};
use crate::test_suite_map::{prepare_function_map, tps_test};
#[macro_use]
extern crate proc_macro_test_attribute;
pub mod test_suite_map;
mod tps_test_utils;
#[derive(Parser, Debug)]
#[clap(version)]
struct Args {
/// Path to configs
home_dir: PathBuf,
/// Test name
test_name: String,
}
// TODO: Remove this and control time from tests
pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12;
pub const ACC_SENDER: &str = "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy";
pub const ACC_RECEIVER: &str = "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw";
@ -40,104 +27,181 @@ pub const ACC_RECEIVER: &str = "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw";
pub const ACC_SENDER_PRIVATE: &str = "3oCG8gqdKLMegw4rRfyaMQvuPHpcASt7xwttsmnZLSkw";
pub const ACC_RECEIVER_PRIVATE: &str = "AKTcXgJ1xoynta1Ec7y6Jso1z1JQtHqd7aPQ1h9er6xX";
pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12;
pub const NSSA_PROGRAM_FOR_TEST_DATA_CHANGER: &str = "data_changer.bin";
fn make_public_account_input_from_str(account_id: &str) -> String {
static LOGGER: LazyLock<()> = LazyLock::new(env_logger::init);
/// Test context which sets up a sequencer and a wallet for integration tests.
///
/// It's memory and logically safe to create multiple instances of this struct in parallel tests,
/// as each instance uses its own temporary directories for sequencer and wallet data.
pub struct TestContext {
sequencer_server_handle: ServerHandle,
sequencer_loop_handle: JoinHandle<Result<()>>,
sequencer_client: SequencerClient,
wallet: WalletCore,
_temp_sequencer_dir: TempDir,
_temp_wallet_dir: TempDir,
}
impl TestContext {
/// Create new test context.
pub async fn new() -> Result<Self> {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let sequencer_config_path =
PathBuf::from(manifest_dir).join("configs/sequencer/sequencer_config.json");
let sequencer_config = SequencerConfig::from_path(&sequencer_config_path)
.context("Failed to create sequencer config from file")?;
Self::new_with_sequencer_config(sequencer_config).await
}
/// Create new test context with custom sequencer config.
///
/// `home` and `port` fields of the provided config will be overridden to meet tests parallelism
/// requirements.
pub async fn new_with_sequencer_config(sequencer_config: SequencerConfig) -> Result<Self> {
// Ensure logger is initialized only once
*LOGGER;
debug!("Test context setup");
let (sequencer_server_handle, sequencer_addr, sequencer_loop_handle, temp_sequencer_dir) =
Self::setup_sequencer(sequencer_config)
.await
.context("Failed to setup sequencer")?;
// Convert 0.0.0.0 to 127.0.0.1 for client connections
// When binding to port 0, the server binds to 0.0.0.0:<random_port>
// but clients need to connect to 127.0.0.1:<port> to work reliably
let sequencer_addr = if sequencer_addr.ip().is_unspecified() {
format!("http://127.0.0.1:{}", sequencer_addr.port())
} else {
format!("http://{sequencer_addr}")
};
let (wallet, temp_wallet_dir) = Self::setup_wallet(sequencer_addr.clone())
.await
.context("Failed to setup wallet")?;
let sequencer_client =
SequencerClient::new(sequencer_addr).context("Failed to create sequencer client")?;
Ok(Self {
sequencer_server_handle,
sequencer_loop_handle,
sequencer_client,
wallet,
_temp_sequencer_dir: temp_sequencer_dir,
_temp_wallet_dir: temp_wallet_dir,
})
}
async fn setup_sequencer(
mut config: SequencerConfig,
) -> Result<(ServerHandle, SocketAddr, JoinHandle<Result<()>>, TempDir)> {
let temp_sequencer_dir =
tempfile::tempdir().context("Failed to create temp dir for sequencer home")?;
debug!(
"Using temp sequencer home at {:?}",
temp_sequencer_dir.path()
);
config.home = temp_sequencer_dir.path().to_owned();
// Setting port to 0 lets the OS choose a free port for us
config.port = 0;
let (sequencer_server_handle, sequencer_addr, sequencer_loop_handle) =
sequencer_runner::startup_sequencer(config).await?;
Ok((
sequencer_server_handle,
sequencer_addr,
sequencer_loop_handle,
temp_sequencer_dir,
))
}
async fn setup_wallet(sequencer_addr: String) -> Result<(WalletCore, TempDir)> {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let wallet_config_source_path =
PathBuf::from(manifest_dir).join("configs/wallet/wallet_config.json");
let temp_wallet_dir =
tempfile::tempdir().context("Failed to create temp dir for wallet home")?;
let config_path = temp_wallet_dir.path().join("wallet_config.json");
std::fs::copy(&wallet_config_source_path, &config_path)
.context("Failed to copy wallet config to temp dir")?;
let storage_path = temp_wallet_dir.path().join("storage.json");
let config_overrides = WalletConfigOverrides {
sequencer_addr: Some(sequencer_addr),
..Default::default()
};
let wallet = WalletCore::new_init_storage(
config_path,
storage_path,
Some(config_overrides),
"test_pass".to_owned(),
)
.context("Failed to init wallet")?;
wallet
.store_persistent_data()
.await
.context("Failed to store wallet persistent data")?;
Ok((wallet, temp_wallet_dir))
}
/// Get reference to the wallet.
pub fn wallet(&self) -> &WalletCore {
&self.wallet
}
/// Get mutable reference to the wallet.
pub fn wallet_mut(&mut self) -> &mut WalletCore {
&mut self.wallet
}
/// Get reference to the sequencer client.
pub fn sequencer_client(&self) -> &SequencerClient {
&self.sequencer_client
}
}
impl Drop for TestContext {
fn drop(&mut self) {
debug!("Test context cleanup");
let Self {
sequencer_server_handle,
sequencer_loop_handle,
sequencer_client: _,
wallet: _,
_temp_sequencer_dir,
_temp_wallet_dir,
} = self;
sequencer_loop_handle.abort();
// Can't wait here as Drop can't be async, but anyway stop signal should be sent
sequencer_server_handle.stop(true).now_or_never();
}
}
pub fn format_public_account_id(account_id: &str) -> String {
format!("Public/{account_id}")
}
fn make_private_account_input_from_str(account_id: &str) -> String {
pub fn format_private_account_id(account_id: &str) -> String {
format!("Private/{account_id}")
}
#[allow(clippy::type_complexity)]
pub async fn pre_test(
home_dir: PathBuf,
) -> Result<(ServerHandle, JoinHandle<Result<()>>, TempDir)> {
wallet::cli::execute_setup("test_pass".to_owned()).await?;
let home_dir_sequencer = home_dir.join("sequencer");
let mut sequencer_config =
sequencer_runner::config::from_file(home_dir_sequencer.join("sequencer_config.json"))
.unwrap();
let temp_dir_sequencer = replace_home_dir_with_temp_dir_in_configs(&mut sequencer_config);
let (seq_http_server_handle, sequencer_loop_handle) =
startup_sequencer(sequencer_config).await?;
Ok((
seq_http_server_handle,
sequencer_loop_handle,
temp_dir_sequencer,
))
}
pub fn replace_home_dir_with_temp_dir_in_configs(
sequencer_config: &mut SequencerConfig,
) -> TempDir {
let temp_dir_sequencer = tempfile::tempdir().unwrap();
sequencer_config.home = temp_dir_sequencer.path().to_path_buf();
temp_dir_sequencer
}
#[allow(clippy::type_complexity)]
pub async fn post_test(residual: (ServerHandle, JoinHandle<Result<()>>, TempDir)) {
let (seq_http_server_handle, sequencer_loop_handle, _) = residual;
info!("Cleanup");
sequencer_loop_handle.abort();
seq_http_server_handle.stop(true).await;
let wallet_home = wallet::helperfunctions::get_home().unwrap();
let persistent_data_home = wallet_home.join("storage.json");
// Removing persistent accounts after run to not affect other executions
// Not necessary an error, if fails as there is tests for failure scenario
let _ = std::fs::remove_file(persistent_data_home)
.inspect_err(|err| warn!("Failed to remove persistent data with err {err:#?}"));
// At this point all of the references to sequencer_core must be lost.
// So they are dropped and tempdirs will be dropped too,
}
pub async fn main_tests_runner() -> Result<()> {
env_logger::init();
let args = Args::parse();
let Args {
home_dir,
test_name,
} = args;
let function_map = prepare_function_map();
match test_name.as_str() {
"all" => {
// Tests that use default config
for (_, fn_pointer) in function_map {
fn_pointer(home_dir.clone()).await;
}
// Run TPS test with its own specific config
tps_test().await;
}
_ => {
let fn_pointer = function_map.get(&test_name).expect("Unknown test name");
fn_pointer(home_dir.clone()).await;
}
}
Ok(())
}
async fn fetch_privacy_preserving_tx(
pub async fn fetch_privacy_preserving_tx(
seq_client: &SequencerClient,
tx_hash: String,
) -> PrivacyPreservingTransaction {
@ -161,7 +225,7 @@ async fn fetch_privacy_preserving_tx(
}
}
async fn verify_commitment_is_in_state(
pub async fn verify_commitment_is_in_state(
commitment: Commitment,
seq_client: &SequencerClient,
) -> bool {
@ -173,15 +237,15 @@ async fn verify_commitment_is_in_state(
#[cfg(test)]
mod tests {
use crate::{make_private_account_input_from_str, make_public_account_input_from_str};
use super::{format_private_account_id, format_public_account_id};
#[test]
fn correct_account_id_from_prefix() {
let account_id1 = "cafecafe";
let account_id2 = "deadbeaf";
let account_id1_pub = make_public_account_input_from_str(account_id1);
let account_id2_priv = make_private_account_input_from_str(account_id2);
let account_id1_pub = format_public_account_id(account_id1);
let account_id2_priv = format_private_account_id(account_id2);
assert_eq!(account_id1_pub, "Public/cafecafe".to_string());
assert_eq!(account_id2_priv, "Private/deadbeaf".to_string());

View File

@ -1,15 +0,0 @@
use anyhow::Result;
use integration_tests::main_tests_runner;
pub const NUM_THREADS: usize = 8;
fn main() -> Result<()> {
actix::System::with_tokio_rt(|| {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(NUM_THREADS)
.enable_all()
.build()
.unwrap()
})
.block_on(main_tests_runner())
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
use anyhow::Result;
use integration_tests::{ACC_SENDER, TestContext};
use log::info;
use nssa::program::Program;
use tokio::test;
#[test]
async fn get_existing_account() -> Result<()> {
let ctx = TestContext::new().await?;
let account = ctx
.sequencer_client()
.get_account(ACC_SENDER.to_string())
.await?
.account;
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);
info!("Successfully retrieved account with correct details");
Ok(())
}

View File

@ -0,0 +1,405 @@
use std::time::Duration;
use anyhow::Result;
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id};
use log::info;
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::{amm::AmmProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand},
};
#[test]
async fn amm_public() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create new account for the token definition
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id_1,
} = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create new account for the token supply holder
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id_1,
} = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create new account for receiving a token transaction
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id_1,
} = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create new account for the token definition
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id_2,
} = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create new account for the token supply holder
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id_2,
} = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create new account for receiving a token transaction
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id_2,
} = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create new token
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(&definition_account_id_1.to_string()),
supply_account_id: format_public_account_id(&supply_account_id_1.to_string()),
name: "A NAM1".to_string(),
total_supply: 37,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_1`
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_public_account_id(&supply_account_id_1.to_string()),
to: Some(format_public_account_id(
&recipient_account_id_1.to_string(),
)),
to_npk: None,
to_ipk: None,
amount: 7,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Create new token
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(&definition_account_id_2.to_string()),
supply_account_id: format_public_account_id(&supply_account_id_2.to_string()),
name: "A NAM2".to_string(),
total_supply: 37,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_2`
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_public_account_id(&supply_account_id_2.to_string()),
to: Some(format_public_account_id(
&recipient_account_id_2.to_string(),
)),
to_npk: None,
to_ipk: None,
amount: 7,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("=================== SETUP FINISHED ===============");
// Create new AMM
// Setup accounts
// Create new account for the user holding lp
let SubcommandReturnValue::RegisterAccount {
account_id: user_holding_lp,
} = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Send creation tx
let subcommand = AmmProgramAgnosticSubcommand::New {
user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()),
user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()),
user_holding_lp: format_public_account_id(&user_holding_lp.to_string()),
balance_a: 3,
balance_b: 3,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let user_holding_a_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_1.to_string())
.await?
.account;
let user_holding_b_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_2.to_string())
.await?
.account;
let user_holding_lp_acc = ctx
.sequencer_client()
.get_account(user_holding_lp.to_string())
.await?
.account;
assert_eq!(
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
4
);
assert_eq!(
u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()),
4
);
assert_eq!(
u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()),
3
);
info!("=================== AMM DEFINITION FINISHED ===============");
// Make swap
let subcommand = AmmProgramAgnosticSubcommand::Swap {
user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()),
user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()),
amount_in: 2,
min_amount_out: 1,
token_definition: definition_account_id_1.to_string(),
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let user_holding_a_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_1.to_string())
.await?
.account;
let user_holding_b_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_2.to_string())
.await?
.account;
let user_holding_lp_acc = ctx
.sequencer_client()
.get_account(user_holding_lp.to_string())
.await?
.account;
assert_eq!(
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
2
);
assert_eq!(
u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()),
5
);
assert_eq!(
u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()),
3
);
info!("=================== FIRST SWAP FINISHED ===============");
// Make swap
let subcommand = AmmProgramAgnosticSubcommand::Swap {
user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()),
user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()),
amount_in: 2,
min_amount_out: 1,
token_definition: definition_account_id_2.to_string(),
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let user_holding_a_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_1.to_string())
.await?
.account;
let user_holding_b_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_2.to_string())
.await?
.account;
let user_holding_lp_acc = ctx
.sequencer_client()
.get_account(user_holding_lp.to_string())
.await?
.account;
assert_eq!(
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
4
);
assert_eq!(
u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()),
3
);
assert_eq!(
u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()),
3
);
info!("=================== SECOND SWAP FINISHED ===============");
// Add liquidity
let subcommand = AmmProgramAgnosticSubcommand::AddLiquidity {
user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()),
user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()),
user_holding_lp: format_public_account_id(&user_holding_lp.to_string()),
min_amount_lp: 1,
max_amount_a: 2,
max_amount_b: 2,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let user_holding_a_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_1.to_string())
.await?
.account;
let user_holding_b_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_2.to_string())
.await?
.account;
let user_holding_lp_acc = ctx
.sequencer_client()
.get_account(user_holding_lp.to_string())
.await?
.account;
assert_eq!(
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
3
);
assert_eq!(
u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()),
1
);
assert_eq!(
u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()),
4
);
info!("=================== ADD LIQ FINISHED ===============");
// Remove liquidity
let subcommand = AmmProgramAgnosticSubcommand::RemoveLiquidity {
user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()),
user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()),
user_holding_lp: format_public_account_id(&user_holding_lp.to_string()),
balance_lp: 2,
min_amount_a: 1,
min_amount_b: 1,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let user_holding_a_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_1.to_string())
.await?
.account;
let user_holding_b_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_2.to_string())
.await?
.account;
let user_holding_lp_acc = ctx
.sequencer_client()
.get_account(user_holding_lp.to_string())
.await?
.account;
assert_eq!(
u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()),
5
);
assert_eq!(
u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()),
4
);
assert_eq!(
u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()),
2
);
info!("Success!");
Ok(())
}

View File

@ -0,0 +1,2 @@
mod private;
mod public;

View File

@ -0,0 +1,417 @@
use std::time::Duration;
use anyhow::{Context as _, Result};
use integration_tests::{
ACC_RECEIVER, ACC_RECEIVER_PRIVATE, ACC_SENDER, ACC_SENDER_PRIVATE,
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, fetch_privacy_preserving_tx,
format_private_account_id, format_public_account_id, verify_commitment_is_in_state,
};
use log::info;
use nssa::{AccountId, program::Program};
use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point};
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::native_token_transfer::AuthTransferSubcommand,
};
#[test]
async fn private_transfer_to_owned_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let from: AccountId = ACC_SENDER_PRIVATE.parse()?;
let to: AccountId = ACC_RECEIVER_PRIVATE.parse()?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(&from.to_string()),
to: Some(format_private_account_id(&to.to_string())),
to_npk: None,
to_ipk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment1 = ctx
.wallet()
.get_private_account_commitment(&from)
.context("Failed to get private account commitment for sender")?;
assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await);
let new_commitment2 = ctx
.wallet()
.get_private_account_commitment(&to)
.context("Failed to get private account commitment for receiver")?;
assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await);
info!("Successfully transferred privately to owned account");
Ok(())
}
#[test]
async fn private_transfer_to_foreign_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let from: AccountId = ACC_SENDER_PRIVATE.parse()?;
let to_npk = NullifierPublicKey([42; 32]);
let to_npk_string = hex::encode(to_npk.0);
let to_ipk = Secp256k1Point::from_scalar(to_npk.0);
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(&from.to_string()),
to: None,
to_npk: Some(to_npk_string),
to_ipk: Some(hex::encode(to_ipk.0)),
amount: 100,
});
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = result else {
anyhow::bail!("Expected PrivacyPreservingTransfer return value");
};
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment1 = ctx
.wallet()
.get_private_account_commitment(&from)
.context("Failed to get private account commitment for sender")?;
let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash.clone()).await;
assert_eq!(tx.message.new_commitments[0], new_commitment1);
assert_eq!(tx.message.new_commitments.len(), 2);
for commitment in tx.message.new_commitments.into_iter() {
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
}
info!("Successfully transferred privately to foreign account");
Ok(())
}
#[test]
async fn deshielded_transfer_to_public_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let from: AccountId = ACC_SENDER_PRIVATE.parse()?;
let to: AccountId = ACC_RECEIVER.parse()?;
// Check initial balance of the private sender
let from_acc = ctx
.wallet()
.get_account_private(&from)
.context("Failed to get sender's private account")?;
assert_eq!(from_acc.balance, 10000);
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(&from.to_string()),
to: Some(format_public_account_id(&to.to_string())),
to_npk: None,
to_ipk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let from_acc = ctx
.wallet()
.get_account_private(&from)
.context("Failed to get sender's private account")?;
let new_commitment = ctx
.wallet()
.get_private_account_commitment(&from)
.context("Failed to get private account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
let acc_2_balance = ctx
.sequencer_client()
.get_account_balance(to.to_string())
.await?;
assert_eq!(from_acc.balance, 9900);
assert_eq!(acc_2_balance.balance, 20100);
info!("Successfully deshielded transfer to public account");
Ok(())
}
#[test]
async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> {
let mut ctx = TestContext::new().await?;
let from: AccountId = ACC_SENDER_PRIVATE.parse()?;
// Create a new private account
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None }));
let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount {
account_id: to_account_id,
} = sub_ret
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Get the keys for the newly created account
let (to_keys, _) = ctx
.wallet()
.storage()
.user_data
.get_private_account(&to_account_id)
.cloned()
.context("Failed to get private account")?;
// Send to this account using claiming path (using npk and ipk instead of account ID)
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(&from.to_string()),
to: None,
to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)),
to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)),
amount: 100,
});
let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else {
anyhow::bail!("Expected PrivacyPreservingTransfer return value");
};
let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash.clone()).await;
// Sync the wallet to claim the new account
let command = Command::Account(AccountSubcommand::SyncPrivate {});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let new_commitment1 = ctx
.wallet()
.get_private_account_commitment(&from)
.context("Failed to get private account commitment for sender")?;
assert_eq!(tx.message.new_commitments[0], new_commitment1);
assert_eq!(tx.message.new_commitments.len(), 2);
for commitment in tx.message.new_commitments.into_iter() {
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
}
let to_res_acc = ctx
.wallet()
.get_account_private(&to_account_id)
.context("Failed to get recipient's private account")?;
assert_eq!(to_res_acc.balance, 100);
info!("Successfully transferred using claiming path");
Ok(())
}
#[test]
async fn shielded_transfer_to_owned_private_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let from: AccountId = ACC_SENDER.parse()?;
let to: AccountId = ACC_RECEIVER_PRIVATE.parse()?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(&from.to_string()),
to: Some(format_private_account_id(&to.to_string())),
to_npk: None,
to_ipk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let acc_to = ctx
.wallet()
.get_account_private(&to)
.context("Failed to get receiver's private account")?;
let new_commitment = ctx
.wallet()
.get_private_account_commitment(&to)
.context("Failed to get receiver's commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
let acc_from_balance = ctx
.sequencer_client()
.get_account_balance(from.to_string())
.await?;
assert_eq!(acc_from_balance.balance, 9900);
assert_eq!(acc_to.balance, 20100);
info!("Successfully shielded transfer to owned private account");
Ok(())
}
#[test]
async fn shielded_transfer_to_foreign_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let to_npk = NullifierPublicKey([42; 32]);
let to_npk_string = hex::encode(to_npk.0);
let to_ipk = Secp256k1Point::from_scalar(to_npk.0);
let from: AccountId = ACC_SENDER.parse()?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(&from.to_string()),
to: None,
to_npk: Some(to_npk_string),
to_ipk: Some(hex::encode(to_ipk.0)),
amount: 100,
});
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = result else {
anyhow::bail!("Expected PrivacyPreservingTransfer return value");
};
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash).await;
let acc_1_balance = ctx
.sequencer_client()
.get_account_balance(from.to_string())
.await?;
assert!(
verify_commitment_is_in_state(
tx.message.new_commitments[0].clone(),
ctx.sequencer_client()
)
.await
);
assert_eq!(acc_1_balance.balance, 9900);
info!("Successfully shielded transfer to foreign account");
Ok(())
}
#[test]
#[ignore = "Flaky, TODO: #197"]
async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> {
let mut ctx = TestContext::new().await?;
// NOTE: This test needs refactoring - continuous run mode doesn't work well with TestContext
// The original implementation spawned wallet::cli::execute_continuous_run() in background
// but this conflicts with TestContext's wallet management
let from: AccountId = ACC_SENDER_PRIVATE.parse()?;
// Create a new private account
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None }));
let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount {
account_id: to_account_id,
} = sub_ret
else {
anyhow::bail!("Failed to register account");
};
// Get the newly created account's keys
let (to_keys, _) = ctx
.wallet()
.storage()
.user_data
.get_private_account(&to_account_id)
.cloned()
.context("Failed to get private account")?;
// Send transfer using nullifier and incoming viewing public keys
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(&from.to_string()),
to: None,
to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)),
to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)),
amount: 100,
});
let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else {
anyhow::bail!("Failed to send transaction");
};
let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash.clone()).await;
info!("Waiting for next blocks to check if continuous run fetches account");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Verify commitments are in state
assert_eq!(tx.message.new_commitments.len(), 2);
for commitment in tx.message.new_commitments.into_iter() {
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
}
// Verify receiver account balance
let to_res_acc = ctx
.wallet()
.get_account_private(&to_account_id)
.context("Failed to get receiver account")?;
assert_eq!(to_res_acc.balance, 100);
Ok(())
}
#[test]
async fn initialize_private_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None }));
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount { account_id } = result else {
anyhow::bail!("Expected RegisterAccount return value");
};
let command = Command::AuthTransfer(AuthTransferSubcommand::Init {
account_id: format_private_account_id(&account_id.to_string()),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("Syncing private accounts");
let command = Command::Account(AccountSubcommand::SyncPrivate {});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let new_commitment = ctx
.wallet()
.get_private_account_commitment(&account_id)
.context("Failed to get private account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
let account = ctx
.wallet()
.get_account_private(&account_id)
.context("Failed to get private account")?;
assert_eq!(
account.program_owner,
Program::authenticated_transfer_program().id()
);
assert_eq!(account.balance, 0);
assert!(account.data.is_empty());
info!("Successfully initialized private account");
Ok(())
}

View File

@ -0,0 +1,248 @@
use std::time::Duration;
use anyhow::Result;
use integration_tests::{
ACC_RECEIVER, ACC_SENDER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id,
};
use log::info;
use nssa::program::Program;
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::native_token_transfer::AuthTransferSubcommand,
};
#[test]
async fn successful_transfer_to_existing_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(ACC_SENDER),
to: Some(format_public_account_id(ACC_RECEIVER)),
to_npk: None,
to_ipk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("Checking correct balance move");
let acc_1_balance = ctx
.sequencer_client()
.get_account_balance(ACC_SENDER.to_string())
.await?;
let acc_2_balance = ctx
.sequencer_client()
.get_account_balance(ACC_RECEIVER.to_string())
.await?;
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance.balance, 9900);
assert_eq!(acc_2_balance.balance, 20100);
Ok(())
}
#[test]
pub async fn successful_transfer_to_new_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None }));
wallet::cli::execute_subcommand(ctx.wallet_mut(), command)
.await
.unwrap();
let new_persistent_account_id = ctx
.wallet()
.storage()
.user_data
.account_ids()
.map(ToString::to_string)
.find(|acc_id| acc_id != ACC_SENDER && acc_id != ACC_RECEIVER)
.expect("Failed to find newly created account in the wallet storage");
if new_persistent_account_id == String::new() {
panic!("Failed to produce new account, not present in persistent accounts");
}
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(ACC_SENDER),
to: Some(format_public_account_id(&new_persistent_account_id)),
to_npk: None,
to_ipk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("Checking correct balance move");
let acc_1_balance = ctx
.sequencer_client()
.get_account_balance(ACC_SENDER.to_string())
.await?;
let acc_2_balance = ctx
.sequencer_client()
.get_account_balance(new_persistent_account_id)
.await?;
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance.balance, 9900);
assert_eq!(acc_2_balance.balance, 100);
Ok(())
}
#[test]
async fn failed_transfer_with_insufficient_balance() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(ACC_SENDER),
to: Some(format_public_account_id(ACC_RECEIVER)),
to_npk: None,
to_ipk: None,
amount: 1000000,
});
let failed_send = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await;
assert!(failed_send.is_err());
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("Checking balances unchanged");
let acc_1_balance = ctx
.sequencer_client()
.get_account_balance(ACC_SENDER.to_string())
.await?;
let acc_2_balance = ctx
.sequencer_client()
.get_account_balance(ACC_RECEIVER.to_string())
.await?;
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance.balance, 10000);
assert_eq!(acc_2_balance.balance, 20000);
Ok(())
}
#[test]
async fn two_consecutive_successful_transfers() -> Result<()> {
let mut ctx = TestContext::new().await?;
// First transfer
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(ACC_SENDER),
to: Some(format_public_account_id(ACC_RECEIVER)),
to_npk: None,
to_ipk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("Checking correct balance move after first transfer");
let acc_1_balance = ctx
.sequencer_client()
.get_account_balance(ACC_SENDER.to_string())
.await?;
let acc_2_balance = ctx
.sequencer_client()
.get_account_balance(ACC_RECEIVER.to_string())
.await?;
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance.balance, 9900);
assert_eq!(acc_2_balance.balance, 20100);
info!("First TX Success!");
// Second transfer
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(ACC_SENDER),
to: Some(format_public_account_id(ACC_RECEIVER)),
to_npk: None,
to_ipk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("Checking correct balance move after second transfer");
let acc_1_balance = ctx
.sequencer_client()
.get_account_balance(ACC_SENDER.to_string())
.await?;
let acc_2_balance = ctx
.sequencer_client()
.get_account_balance(ACC_RECEIVER.to_string())
.await?;
info!("Balance of sender: {acc_1_balance:#?}");
info!("Balance of receiver: {acc_2_balance:#?}");
assert_eq!(acc_1_balance.balance, 9800);
assert_eq!(acc_2_balance.balance, 20200);
info!("Second TX Success!");
Ok(())
}
#[test]
async fn initialize_public_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None }));
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount { account_id } = result else {
anyhow::bail!("Expected RegisterAccount return value");
};
let command = Command::AuthTransfer(AuthTransferSubcommand::Init {
account_id: format_public_account_id(&account_id.to_string()),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Checking correct execution");
let account = ctx
.sequencer_client()
.get_account(account_id.to_string())
.await?
.account;
assert_eq!(
account.program_owner,
Program::authenticated_transfer_program().id()
);
assert_eq!(account.balance, 0);
assert_eq!(account.nonce, 1);
assert!(account.data.is_empty());
info!("Successfully initialized public account");
Ok(())
}

View File

@ -0,0 +1,33 @@
use anyhow::Result;
use integration_tests::TestContext;
use log::info;
use tokio::test;
use wallet::cli::{Command, config::ConfigSubcommand};
#[test]
async fn modify_config_field() -> Result<()> {
let mut ctx = TestContext::new().await?;
let old_seq_poll_timeout_millis = ctx.wallet().config().seq_poll_timeout_millis;
// Change config field
let command = Command::Config(ConfigSubcommand::Set {
key: "seq_poll_timeout_millis".to_string(),
value: "1000".to_string(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let new_seq_poll_timeout_millis = ctx.wallet().config().seq_poll_timeout_millis;
assert_eq!(new_seq_poll_timeout_millis, 1000);
// Return how it was at the beginning
let command = Command::Config(ConfigSubcommand::Set {
key: "seq_poll_timeout_millis".to_string(),
value: old_seq_poll_timeout_millis.to_string(),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Successfully modified and restored config field");
Ok(())
}

View File

@ -0,0 +1,217 @@
use std::{str::FromStr, time::Duration};
use anyhow::Result;
use integration_tests::{
ACC_SENDER, ACC_SENDER_PRIVATE, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext,
format_private_account_id, format_public_account_id, verify_commitment_is_in_state,
};
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use log::info;
use nssa::{AccountId, program::Program};
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::native_token_transfer::AuthTransferSubcommand,
};
#[test]
async fn restore_keys_from_seed() -> Result<()> {
let mut ctx = TestContext::new().await?;
let from: AccountId = ACC_SENDER_PRIVATE.parse()?;
// Create first private account at root
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
cci: Some(ChainIndex::root()),
}));
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount {
account_id: to_account_id1,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create second private account at /0
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
cci: Some(ChainIndex::from_str("/0")?),
}));
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount {
account_id: to_account_id2,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Send to first private account
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(&from.to_string()),
to: Some(format_private_account_id(&to_account_id1.to_string())),
to_npk: None,
to_ipk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
// Send to second private account
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(&from.to_string()),
to: Some(format_private_account_id(&to_account_id2.to_string())),
to_npk: None,
to_ipk: None,
amount: 101,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let from: AccountId = ACC_SENDER.parse()?;
// Create first public account at root
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: Some(ChainIndex::root()),
}));
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount {
account_id: to_account_id3,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create second public account at /0
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: Some(ChainIndex::from_str("/0")?),
}));
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount {
account_id: to_account_id4,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Send to first public account
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(&from.to_string()),
to: Some(format_public_account_id(&to_account_id3.to_string())),
to_npk: None,
to_ipk: None,
amount: 102,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
// Send to second public account
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(&from.to_string()),
to: Some(format_public_account_id(&to_account_id4.to_string())),
to_npk: None,
to_ipk: None,
amount: 103,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Preparation complete, performing keys restoration");
// Restore keys from seed
wallet::cli::execute_keys_restoration(ctx.wallet_mut(), 10).await?;
// Verify restored private accounts
let acc1 = ctx
.wallet()
.storage()
.user_data
.private_key_tree
.get_node(to_account_id1)
.expect("Acc 1 should be restored");
let acc2 = ctx
.wallet()
.storage()
.user_data
.private_key_tree
.get_node(to_account_id2)
.expect("Acc 2 should be restored");
// Verify restored public accounts
let _acc3 = ctx
.wallet()
.storage()
.user_data
.public_key_tree
.get_node(to_account_id3)
.expect("Acc 3 should be restored");
let _acc4 = ctx
.wallet()
.storage()
.user_data
.public_key_tree
.get_node(to_account_id4)
.expect("Acc 4 should be restored");
assert_eq!(
acc1.value.1.program_owner,
Program::authenticated_transfer_program().id()
);
assert_eq!(
acc2.value.1.program_owner,
Program::authenticated_transfer_program().id()
);
assert_eq!(acc1.value.1.balance, 100);
assert_eq!(acc2.value.1.balance, 101);
info!("Tree checks passed, testing restored accounts can transact");
// Test that restored accounts can send transactions
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(&to_account_id1.to_string()),
to: Some(format_private_account_id(&to_account_id2.to_string())),
to_npk: None,
to_ipk: None,
amount: 10,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(&to_account_id3.to_string()),
to: Some(format_public_account_id(&to_account_id4.to_string())),
to_npk: None,
to_ipk: None,
amount: 11,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Verify commitments exist for private accounts
let comm1 = ctx
.wallet()
.get_private_account_commitment(&to_account_id1)
.expect("Acc 1 commitment should exist");
let comm2 = ctx
.wallet()
.get_private_account_commitment(&to_account_id2)
.expect("Acc 2 commitment should exist");
assert!(verify_commitment_is_in_state(comm1, ctx.sequencer_client()).await);
assert!(verify_commitment_is_in_state(comm2, ctx.sequencer_client()).await);
// Verify public account balances
let acc3 = ctx
.sequencer_client()
.get_account_balance(to_account_id3.to_string())
.await?;
let acc4 = ctx
.sequencer_client()
.get_account_balance(to_account_id4.to_string())
.await?;
assert_eq!(acc3.balance, 91); // 102 - 11
assert_eq!(acc4.balance, 114); // 103 + 11
info!("Successfully restored keys and verified transactions");
Ok(())
}

View File

@ -0,0 +1,155 @@
use std::time::Duration;
use anyhow::{Context as _, Result};
use common::PINATA_BASE58;
use integration_tests::{
ACC_SENDER, ACC_SENDER_PRIVATE, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext,
format_private_account_id, format_public_account_id, verify_commitment_is_in_state,
};
use log::info;
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::pinata::PinataProgramAgnosticSubcommand,
};
#[test]
async fn claim_pinata_to_public_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let pinata_prize = 150;
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: format_public_account_id(ACC_SENDER),
});
let pinata_balance_pre = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.to_string())
.await?
.balance;
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("Checking correct balance move");
let pinata_balance_post = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.to_string())
.await?
.balance;
let winner_balance_post = ctx
.sequencer_client()
.get_account_balance(ACC_SENDER.to_string())
.await?
.balance;
assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize);
assert_eq!(winner_balance_post, 10000 + pinata_prize);
info!("Successfully claimed pinata to public account");
Ok(())
}
#[test]
async fn claim_pinata_to_existing_private_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let pinata_prize = 150;
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: format_private_account_id(ACC_SENDER_PRIVATE),
});
let pinata_balance_pre = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.to_string())
.await?
.balance;
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = result else {
anyhow::bail!("Expected PrivacyPreservingTransfer return value");
};
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("Syncing private accounts");
let command = Command::Account(AccountSubcommand::SyncPrivate {});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let new_commitment = ctx
.wallet()
.get_private_account_commitment(&ACC_SENDER_PRIVATE.parse()?)
.context("Failed to get private account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
let pinata_balance_post = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.to_string())
.await?
.balance;
assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize);
info!("Successfully claimed pinata to existing private account");
Ok(())
}
#[test]
async fn claim_pinata_to_new_private_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let pinata_prize = 150;
// Create new private account
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: winner_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim {
to: format_private_account_id(&winner_account_id.to_string()),
});
let pinata_balance_pre = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.to_string())
.await?
.balance;
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment = ctx
.wallet()
.get_private_account_commitment(&winner_account_id)
.context("Failed to get private account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
let pinata_balance_post = ctx
.sequencer_client()
.get_account_balance(PINATA_BASE58.to_string())
.await?
.balance;
assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize);
info!("Successfully claimed pinata to new private account");
Ok(())
}

View File

@ -0,0 +1,64 @@
use std::{path::PathBuf, time::Duration};
use anyhow::Result;
use integration_tests::{
NSSA_PROGRAM_FOR_TEST_DATA_CHANGER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext,
};
use log::info;
use nssa::{AccountId, program::Program};
use tokio::test;
use wallet::cli::Command;
#[test]
async fn deploy_and_execute_program() -> Result<()> {
let mut ctx = TestContext::new().await?;
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let binary_filepath: PathBuf = PathBuf::from(manifest_dir)
.join("../artifacts/test_program_methods")
.join(NSSA_PROGRAM_FOR_TEST_DATA_CHANGER);
let command = Command::DeployProgram {
binary_filepath: binary_filepath.clone(),
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// The program is the data changer and takes one account as input.
// We pass an uninitialized account and we expect after execution to be owned by the data
// changer program (NSSA account claiming mechanism) with data equal to [0] (due to program
// logic)
let bytecode = std::fs::read(binary_filepath)?;
let data_changer = Program::new(bytecode)?;
let account_id: AccountId = "11".repeat(16).parse()?;
let message = nssa::public_transaction::Message::try_new(
data_changer.id(),
vec![account_id],
vec![],
vec![0],
)?;
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
let transaction = nssa::PublicTransaction::new(message, witness_set);
let _response = ctx.sequencer_client().send_tx_public(transaction).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let post_state_account = ctx
.sequencer_client()
.get_account(account_id.to_string())
.await?
.account;
assert_eq!(post_state_account.program_owner, data_changer.id());
assert_eq!(post_state_account.balance, 0);
assert_eq!(post_state_account.data.as_ref(), &[0]);
assert_eq!(post_state_account.nonce, 0);
info!("Successfully deployed and executed program");
Ok(())
}

View File

@ -0,0 +1,968 @@
use std::time::Duration;
use anyhow::{Context as _, Result};
use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_private_account_id,
format_public_account_id, verify_commitment_is_in_state,
};
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use log::info;
use nssa::program::Program;
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::token::TokenProgramAgnosticSubcommand,
};
#[test]
async fn create_and_transfer_public_token() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create new account for the token definition
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create new account for the token supply holder
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create new account for receiving a token transaction
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create new token
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(&definition_account_id.to_string()),
supply_account_id: format_public_account_id(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Check the status of the token definition account
let definition_acc = ctx
.sequencer_client()
.get_account(definition_account_id.to_string())
.await?
.account;
assert_eq!(definition_acc.program_owner, Program::token().id());
// The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 bytes)]
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
);
// Check the status of the token holding account with the total supply
let supply_acc = ctx
.sequencer_client()
.get_account(supply_account_id.to_string())
.await?
.account;
// The account must be owned by the token program
assert_eq!(supply_acc.program_owner, Program::token().id());
// The data of a token holding account has the following layout:
// [ 0x01 || corresponding_token_definition_id (32 bytes) || balance (little endian 16 bytes) ]
// First byte of the data equal to 1 means it's a token holding account
assert_eq!(supply_acc.data.as_ref()[0], 1);
// Bytes from 1 to 33 represent the id of the token this account is associated with
assert_eq!(
&supply_acc.data.as_ref()[1..33],
definition_account_id.to_bytes()
);
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37);
// Transfer 7 tokens from supply_acc to recipient_account_id
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_public_account_id(&supply_account_id.to_string()),
to: Some(format_public_account_id(&recipient_account_id.to_string())),
to_npk: None,
to_ipk: None,
amount: 7,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Check the status of the supply account after transfer
let supply_acc = ctx
.sequencer_client()
.get_account(supply_account_id.to_string())
.await?
.account;
assert_eq!(supply_acc.program_owner, Program::token().id());
assert_eq!(supply_acc.data[0], 1);
assert_eq!(&supply_acc.data[1..33], definition_account_id.to_bytes());
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30);
// Check the status of the recipient account after transfer
let recipient_acc = ctx
.sequencer_client()
.get_account(recipient_account_id.to_string())
.await?
.account;
assert_eq!(recipient_acc.program_owner, Program::token().id());
assert_eq!(recipient_acc.data[0], 1);
assert_eq!(&recipient_acc.data[1..33], definition_account_id.to_bytes());
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7);
// Burn 3 tokens from recipient_acc
let subcommand = TokenProgramAgnosticSubcommand::Burn {
definition: format_public_account_id(&definition_account_id.to_string()),
holder: format_public_account_id(&recipient_account_id.to_string()),
amount: 3,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Check the status of the token definition account after burn
let definition_acc = ctx
.sequencer_client()
.get_account(definition_account_id.to_string())
.await?
.account;
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
);
// Check the status of the recipient account after burn
let recipient_acc = ctx
.sequencer_client()
.get_account(recipient_account_id.to_string())
.await?
.account;
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 4);
// Mint 10 tokens at recipient_acc
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: format_public_account_id(&definition_account_id.to_string()),
holder: Some(format_public_account_id(&recipient_account_id.to_string())),
holder_npk: None,
holder_ipk: None,
amount: 10,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Check the status of the token definition account after mint
let definition_acc = ctx
.sequencer_client()
.get_account(definition_account_id.to_string())
.await?
.account;
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
);
// Check the status of the recipient account after mint
let recipient_acc = ctx
.sequencer_client()
.get_account(recipient_account_id.to_string())
.await?
.account;
assert_eq!(
u128::from_le_bytes(recipient_acc.data[33..].try_into()?),
14
);
info!("Successfully created and transferred public token");
Ok(())
}
#[test]
async fn create_and_transfer_token_with_private_supply() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create new account for the token definition (public)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create new account for the token supply holder (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create new account for receiving a token transaction (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create new token
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(&definition_account_id.to_string()),
supply_account_id: format_private_account_id(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Check the status of the token definition account
let definition_acc = ctx
.sequencer_client()
.get_account(definition_account_id.to_string())
.await?
.account;
assert_eq!(definition_acc.program_owner, Program::token().id());
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
);
let new_commitment1 = ctx
.wallet()
.get_private_account_commitment(&supply_account_id)
.context("Failed to get supply account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await);
// Transfer 7 tokens from supply_acc to recipient_account_id
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_private_account_id(&supply_account_id.to_string()),
to: Some(format_private_account_id(&recipient_account_id.to_string())),
to_npk: None,
to_ipk: None,
amount: 7,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment1 = ctx
.wallet()
.get_private_account_commitment(&supply_account_id)
.context("Failed to get supply account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await);
let new_commitment2 = ctx
.wallet()
.get_private_account_commitment(&recipient_account_id)
.context("Failed to get recipient account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await);
// Burn 3 tokens from recipient_acc
let subcommand = TokenProgramAgnosticSubcommand::Burn {
definition: format_public_account_id(&definition_account_id.to_string()),
holder: format_private_account_id(&recipient_account_id.to_string()),
amount: 3,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Check the token definition account after burn
let definition_acc = ctx
.sequencer_client()
.get_account(definition_account_id.to_string())
.await?
.account;
assert_eq!(
definition_acc.data.as_ref(),
&[
0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
);
let new_commitment2 = ctx
.wallet()
.get_private_account_commitment(&recipient_account_id)
.context("Failed to get recipient account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await);
// Check the recipient account balance after burn
let recipient_acc = ctx
.wallet()
.get_account_private(&recipient_account_id)
.context("Failed to get recipient account")?;
assert_eq!(
u128::from_le_bytes(recipient_acc.data[33..].try_into()?),
4 // 7 - 3
);
info!("Successfully created and transferred token with private supply");
Ok(())
}
#[test]
async fn create_token_with_private_definition() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create token definition account (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
cci: Some(ChainIndex::root()),
})),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create supply account (public)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
cci: Some(ChainIndex::root()),
})),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create token with private definition
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_private_account_id(&definition_account_id.to_string()),
supply_account_id: format_public_account_id(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Verify private definition commitment
let new_commitment = ctx
.wallet()
.get_private_account_commitment(&definition_account_id)
.context("Failed to get definition commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
// Verify supply account
let supply_acc = ctx
.sequencer_client()
.get_account(supply_account_id.to_string())
.await?
.account;
assert_eq!(supply_acc.program_owner, Program::token().id());
assert_eq!(supply_acc.data.as_ref()[0], 1);
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37);
// Create private recipient account
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id_private,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create public recipient account
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id_public,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Mint to public account
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: format_private_account_id(&definition_account_id.to_string()),
holder: Some(format_public_account_id(
&recipient_account_id_public.to_string(),
)),
holder_npk: None,
holder_ipk: None,
amount: 10,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Verify definition account has updated supply
let definition_acc = ctx
.wallet()
.get_account_private(&definition_account_id)
.context("Failed to get definition account")?;
assert_eq!(
u128::from_le_bytes(definition_acc.data[7..23].try_into()?),
47 // 37 + 10
);
// Verify public recipient received tokens
let recipient_acc = ctx
.sequencer_client()
.get_account(recipient_account_id_public.to_string())
.await?
.account;
assert_eq!(
u128::from_le_bytes(recipient_acc.data[33..].try_into()?),
10
);
// Mint to private account
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: format_private_account_id(&definition_account_id.to_string()),
holder: Some(format_private_account_id(
&recipient_account_id_private.to_string(),
)),
holder_npk: None,
holder_ipk: None,
amount: 5,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Verify private recipient commitment
let new_commitment = ctx
.wallet()
.get_private_account_commitment(&recipient_account_id_private)
.context("Failed to get recipient commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
// Verify private recipient balance
let recipient_acc_private = ctx
.wallet()
.get_account_private(&recipient_account_id_private)
.context("Failed to get private recipient account")?;
assert_eq!(
u128::from_le_bytes(recipient_acc_private.data[33..].try_into()?),
5
);
info!("Successfully created token with private definition and minted to both account types");
Ok(())
}
#[test]
async fn create_token_with_private_definition_and_supply() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create token definition account (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create supply account (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create token with both private definition and supply
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_private_account_id(&definition_account_id.to_string()),
supply_account_id: format_private_account_id(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Verify definition commitment
let definition_commitment = ctx
.wallet()
.get_private_account_commitment(&definition_account_id)
.context("Failed to get definition commitment")?;
assert!(verify_commitment_is_in_state(definition_commitment, ctx.sequencer_client()).await);
// Verify supply commitment
let supply_commitment = ctx
.wallet()
.get_private_account_commitment(&supply_account_id)
.context("Failed to get supply commitment")?;
assert!(verify_commitment_is_in_state(supply_commitment, ctx.sequencer_client()).await);
// Verify supply balance
let supply_acc = ctx
.wallet()
.get_account_private(&supply_account_id)
.context("Failed to get supply account")?;
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37);
// Create recipient account
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Transfer tokens
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_private_account_id(&supply_account_id.to_string()),
to: Some(format_private_account_id(&recipient_account_id.to_string())),
to_npk: None,
to_ipk: None,
amount: 7,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Verify both commitments updated
let supply_commitment = ctx
.wallet()
.get_private_account_commitment(&supply_account_id)
.context("Failed to get supply commitment")?;
assert!(verify_commitment_is_in_state(supply_commitment, ctx.sequencer_client()).await);
let recipient_commitment = ctx
.wallet()
.get_private_account_commitment(&recipient_account_id)
.context("Failed to get recipient commitment")?;
assert!(verify_commitment_is_in_state(recipient_commitment, ctx.sequencer_client()).await);
// Verify balances
let supply_acc = ctx
.wallet()
.get_account_private(&supply_account_id)
.context("Failed to get supply account")?;
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30);
let recipient_acc = ctx
.wallet()
.get_account_private(&recipient_account_id)
.context("Failed to get recipient account")?;
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7);
info!("Successfully created and transferred token with both private definition and supply");
Ok(())
}
#[test]
async fn shielded_token_transfer() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create token definition account (public)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create supply account (public)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create recipient account (private) for shielded transfer
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create token
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(&definition_account_id.to_string()),
supply_account_id: format_public_account_id(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Perform shielded transfer: public supply -> private recipient
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_public_account_id(&supply_account_id.to_string()),
to: Some(format_private_account_id(&recipient_account_id.to_string())),
to_npk: None,
to_ipk: None,
amount: 7,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Verify supply account balance
let supply_acc = ctx
.sequencer_client()
.get_account(supply_account_id.to_string())
.await?
.account;
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30);
// Verify recipient commitment exists
let new_commitment = ctx
.wallet()
.get_private_account_commitment(&recipient_account_id)
.context("Failed to get recipient commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
// Verify recipient balance
let recipient_acc = ctx
.wallet()
.get_account_private(&recipient_account_id)
.context("Failed to get recipient account")?;
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7);
info!("Successfully performed shielded token transfer");
Ok(())
}
#[test]
async fn deshielded_token_transfer() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create token definition account (public)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create supply account (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create recipient account (public) for deshielded transfer
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create token with private supply
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_public_account_id(&definition_account_id.to_string()),
supply_account_id: format_private_account_id(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Perform deshielded transfer: private supply -> public recipient
let subcommand = TokenProgramAgnosticSubcommand::Send {
from: format_private_account_id(&supply_account_id.to_string()),
to: Some(format_public_account_id(&recipient_account_id.to_string())),
to_npk: None,
to_ipk: None,
amount: 7,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Verify supply account commitment exists
let new_commitment = ctx
.wallet()
.get_private_account_commitment(&supply_account_id)
.context("Failed to get supply commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
// Verify supply balance
let supply_acc = ctx
.wallet()
.get_account_private(&supply_account_id)
.context("Failed to get supply account")?;
assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30);
// Verify recipient balance
let recipient_acc = ctx
.sequencer_client()
.get_account(recipient_account_id.to_string())
.await?
.account;
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7);
info!("Successfully performed deshielded token transfer");
Ok(())
}
#[test]
async fn token_claiming_path_with_private_accounts() -> Result<()> {
let mut ctx = TestContext::new().await?;
// Create token definition account (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: definition_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create supply account (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: supply_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Create token
let subcommand = TokenProgramAgnosticSubcommand::New {
definition_account_id: format_private_account_id(&definition_account_id.to_string()),
supply_account_id: format_private_account_id(&supply_account_id.to_string()),
name: "A NAME".to_string(),
total_supply: 37,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Create new private account for claiming path
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })),
)
.await?;
let SubcommandReturnValue::RegisterAccount {
account_id: recipient_account_id,
} = result
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Get keys for foreign mint (claiming path)
let (holder_keys, _) = ctx
.wallet()
.storage()
.user_data
.get_private_account(&recipient_account_id)
.cloned()
.context("Failed to get private account keys")?;
// Mint using claiming path (foreign account)
let subcommand = TokenProgramAgnosticSubcommand::Mint {
definition: format_private_account_id(&definition_account_id.to_string()),
holder: None,
holder_npk: Some(hex::encode(holder_keys.nullifer_public_key.0)),
holder_ipk: Some(hex::encode(holder_keys.incoming_viewing_public_key.0)),
amount: 9,
};
wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Sync to claim the account
let command = Command::Account(AccountSubcommand::SyncPrivate {});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
// Verify commitment exists
let recipient_commitment = ctx
.wallet()
.get_private_account_commitment(&recipient_account_id)
.context("Failed to get recipient commitment")?;
assert!(verify_commitment_is_in_state(recipient_commitment, ctx.sequencer_client()).await);
// Verify balance
let recipient_acc = ctx
.wallet()
.get_account_private(&recipient_account_id)
.context("Failed to get recipient account")?;
assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 9);
info!("Successfully minted tokens using claiming path");
Ok(())
}

View File

@ -1,6 +1,9 @@
use std::time::Duration;
use std::time::{Duration, Instant};
use anyhow::Result;
use integration_tests::TestContext;
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
use log::info;
use nssa::{
Account, AccountId, PrivacyPreservingTransaction, PrivateKey, PublicKey, PublicTransaction,
privacy_preserving_transaction::{self as pptx, circuit},
@ -13,6 +16,78 @@ use nssa_core::{
encryption::IncomingViewingPublicKey,
};
use sequencer_core::config::{AccountInitialData, CommitmentsInitialData, SequencerConfig};
use tokio::test;
// TODO: Make a proper benchmark instead of an ad-hoc test
#[test]
pub async fn tps_test() -> Result<()> {
let num_transactions = 300 * 5;
let target_tps = 12;
let tps_test = TpsTestManager::new(target_tps, num_transactions);
let ctx = TestContext::new_with_sequencer_config(tps_test.generate_sequencer_config()).await?;
let target_time = tps_test.target_time();
info!(
"TPS test begin. Target time is {target_time:?} for {num_transactions} transactions ({target_tps} TPS)"
);
let txs = tps_test.build_public_txs();
let now = Instant::now();
let mut tx_hashes = vec![];
for (i, tx) in txs.into_iter().enumerate() {
let tx_hash = ctx
.sequencer_client()
.send_tx_public(tx)
.await
.unwrap()
.tx_hash;
info!("Sent tx {i}");
tx_hashes.push(tx_hash);
}
for (i, tx_hash) in tx_hashes.iter().enumerate() {
loop {
if now.elapsed().as_millis() > target_time.as_millis() {
panic!("TPS test failed by timeout");
}
let tx_obj = ctx
.sequencer_client()
.get_transaction_by_hash(tx_hash.clone())
.await
.inspect_err(|err| {
log::warn!(
"Failed to get transaction by hash {tx_hash:#?} with error: {err:#?}"
)
});
if let Ok(tx_obj) = tx_obj
&& tx_obj.transaction.is_some()
{
info!("Found tx {i} with hash {tx_hash}");
break;
}
}
}
let time_elapsed = now.elapsed().as_secs();
let tx_processed = tx_hashes.len();
let actual_tps = tx_processed as u64 / time_elapsed;
info!("Processed {tx_processed} transactions in {time_elapsed:?} ({actual_tps} TPS)",);
assert_eq!(tx_processed, num_transactions);
assert!(
time_elapsed <= target_time.as_secs(),
"Elapsed time {time_elapsed:?} exceeded target time {target_time:?}"
);
info!("TPS test finished successfully");
Ok(())
}
pub(crate) struct TpsTestManager {
public_keypairs: Vec<(PrivateKey, AccountId)>,
@ -32,7 +107,7 @@ impl TpsTestManager {
let account_id = AccountId::from(&public_key);
(private_key, account_id)
})
.collect::<Vec<_>>();
.collect();
Self {
public_keypairs,
target_tps,
@ -72,7 +147,7 @@ impl TpsTestManager {
/// Generates a sequencer configuration with initial balance in a number of public accounts.
/// The transactions generated with the function `build_public_txs` will be valid in a node
/// started with the config from this method.
pub(crate) fn generate_tps_test_config(&self) -> SequencerConfig {
pub(crate) fn generate_sequencer_config(&self) -> SequencerConfig {
// Create public public keypairs
let initial_public_accounts = self
.public_keypairs
@ -118,7 +193,7 @@ impl TpsTestManager {
/// it may take a while to run. In normal execution of the node this transaction will be accepted
/// only once. Disabling the node's nullifier uniqueness check allows to submit this transaction
/// multiple times with the purpose of testing the node's processing performance.
#[allow(unused)]
#[expect(dead_code, reason = "No idea if we need this, should we remove it?")]
fn build_privacy_transaction() -> PrivacyPreservingTransaction {
let program = Program::authenticated_transfer_program();
let sender_nsk = [1; 32];

View File

@ -165,6 +165,14 @@ impl NSSAUserData {
.map(Into::into)
}
}
pub fn account_ids(&self) -> impl Iterator<Item = &nssa::AccountId> {
self.default_pub_account_signing_keys
.keys()
.chain(self.public_key_tree.account_id_map.keys())
.chain(self.default_user_private_accounts.keys())
.chain(self.private_key_tree.account_id_map.keys())
}
}
impl Default for NSSAUserData {

View File

@ -13,6 +13,7 @@ mempool.workspace = true
base58.workspace = true
anyhow.workspace = true
serde.workspace = true
serde_json.workspace = true
tempfile.workspace = true
chrono.workspace = true
log.workspace = true

View File

@ -1,5 +1,10 @@
use std::path::PathBuf;
use std::{
fs::File,
io::BufReader,
path::{Path, PathBuf},
};
use anyhow::Result;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
@ -43,3 +48,12 @@ pub struct SequencerConfig {
/// Sequencer own signing key
pub signing_key: [u8; 32],
}
impl SequencerConfig {
pub fn from_path(config_home: &Path) -> Result<SequencerConfig> {
let file = File::open(config_home)?;
let reader = BufReader::new(file);
Ok(serde_json::from_reader(reader)?)
}
}

View File

@ -1,4 +1,4 @@
use std::{io, sync::Arc};
use std::{io, net::SocketAddr, sync::Arc};
use actix_cors::Cors;
use actix_web::{App, Error as HttpError, HttpResponse, HttpServer, http, middleware, web};
@ -42,25 +42,24 @@ fn get_cors(cors_allowed_origins: &[String]) -> Cors {
.max_age(3600)
}
#[allow(clippy::too_many_arguments)]
pub fn new_http_server(
config: RpcConfig,
seuquencer_core: Arc<Mutex<SequencerCore>>,
mempool_handle: MemPoolHandle<EncodedTransaction>,
) -> io::Result<actix_web::dev::Server> {
) -> io::Result<(actix_web::dev::Server, SocketAddr)> {
let RpcConfig {
addr,
cors_allowed_origins,
limits_config,
} = config;
info!(target:NETWORK, "Starting http server at {addr}");
info!(target:NETWORK, "Starting HTTP server at {addr}");
let handler = web::Data::new(JsonHandler {
sequencer_state: seuquencer_core.clone(),
mempool_handle,
});
// HTTP server
Ok(HttpServer::new(move || {
let http_server = HttpServer::new(move || {
App::new()
.wrap(get_cors(&cors_allowed_origins))
.app_data(handler.clone())
@ -70,6 +69,14 @@ pub fn new_http_server(
})
.bind(addr)?
.shutdown_timeout(SHUTDOWN_TIMEOUT_SECS)
.disable_signals()
.run())
.disable_signals();
let [addr] = http_server
.addrs()
.try_into()
.expect("Exactly one address bound is expected for sequencer HTTP server");
info!(target:NETWORK, "HTTP server started at {addr}");
Ok((http_server.run(), addr))
}

View File

@ -1,11 +0,0 @@
use std::{fs::File, io::BufReader, path::PathBuf};
use anyhow::Result;
use sequencer_core::config::SequencerConfig;
pub fn from_file(config_home: PathBuf) -> Result<SequencerConfig> {
let file = File::open(config_home)?;
let reader = BufReader::new(file);
Ok(serde_json::from_reader(reader)?)
}

View File

@ -1,4 +1,4 @@
use std::{path::PathBuf, sync::Arc};
use std::{net::SocketAddr, path::PathBuf, sync::Arc};
use actix_web::dev::ServerHandle;
use anyhow::Result;
@ -9,8 +9,6 @@ use sequencer_core::{SequencerCore, config::SequencerConfig};
use sequencer_rpc::new_http_server;
use tokio::{sync::Mutex, task::JoinHandle};
pub mod config;
pub const RUST_LOG: &str = "RUST_LOG";
#[derive(Parser, Debug)]
@ -22,7 +20,7 @@ struct Args {
pub async fn startup_sequencer(
app_config: SequencerConfig,
) -> Result<(ServerHandle, JoinHandle<Result<()>>)> {
) -> Result<(ServerHandle, SocketAddr, JoinHandle<Result<()>>)> {
let block_timeout = app_config.block_create_timeout_millis;
let port = app_config.port;
@ -32,7 +30,7 @@ pub async fn startup_sequencer(
let seq_core_wrapped = Arc::new(Mutex::new(sequencer_core));
let http_server = new_http_server(
let (http_server, addr) = new_http_server(
RpcConfig::with_port(port),
Arc::clone(&seq_core_wrapped),
mempool_handle,
@ -61,7 +59,7 @@ pub async fn startup_sequencer(
}
});
Ok((http_server_handle, main_loop_handle))
Ok((http_server_handle, addr, main_loop_handle))
}
pub async fn main_runner() -> Result<()> {
@ -70,7 +68,7 @@ pub async fn main_runner() -> Result<()> {
let args = Args::parse();
let Args { home_dir } = args;
let app_config = config::from_file(home_dir.join("sequencer_config.json"))?;
let app_config = SequencerConfig::from_path(&home_dir.join("sequencer_config.json"))?;
if let Some(ref rust_log) = app_config.override_rust_log {
info!("RUST_LOG env var set to {rust_log:?}");
@ -81,7 +79,7 @@ pub async fn main_runner() -> Result<()> {
}
// ToDo: Add restart on failures
let (_, main_loop_handle) = startup_sequencer(app_config).await?;
let (_, _, main_loop_handle) = startup_sequencer(app_config).await?;
main_loop_handle.await??;

View File

@ -25,6 +25,7 @@ rand.workspace = true
itertools.workspace = true
sha2.workspace = true
futures.workspace = true
risc0-zkvm.workspace = true
async-stream = "0.3.6"
indicatif = { version = "0.18.3", features = ["improved_unicode"] }
risc0-zkvm.workspace = true
optfield = "0.4.0"

View File

@ -64,9 +64,7 @@ impl WalletSubcommand for NewSubcommand {
"Generated new account with account_id Public/{account_id} at path {chain_index}"
);
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::RegisterAccount { account_id })
}
@ -89,9 +87,7 @@ impl WalletSubcommand for NewSubcommand {
hex::encode(key.incoming_viewing_public_key.to_bytes())
);
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::RegisterAccount { account_id })
}
@ -243,9 +239,7 @@ impl WalletSubcommand for AccountSubcommand {
{
wallet_core.last_synced_block = curr_last_block;
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent data at {path:#?}");
wallet_core.store_persistent_data().await?;
} else {
wallet_core.sync_to_block(curr_last_block).await?;
}

View File

@ -108,9 +108,7 @@ impl WalletSubcommand for ConfigSubcommand {
}
}
let path = wallet_core.store_config_changes().await?;
println!("Stored changed config at {path:#?}");
wallet_core.store_config_changes().await?
}
ConfigSubcommand::Description { key } => match key.as_str() {
"override_rust_log" => {

View File

@ -15,7 +15,6 @@ use crate::{
pinata::PinataProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand,
},
},
helperfunctions::{fetch_config, fetch_persistent_storage, merge_auth_config},
};
pub mod account;
@ -97,43 +96,22 @@ pub enum SubcommandReturnValue {
SyncedToBlock(u64),
}
pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValue> {
execute_subcommand_with_auth(command, None).await
}
pub async fn execute_subcommand_with_auth(
pub async fn execute_subcommand(
wallet_core: &mut WalletCore,
command: Command,
auth: Option<String>,
) -> Result<SubcommandReturnValue> {
if fetch_persistent_storage().await.is_err() {
println!("Persistent storage not found, need to execute setup");
let password = read_password_from_stdin()?;
execute_setup_with_auth(password, auth.clone()).await?;
}
let wallet_config = fetch_config().await?;
let wallet_config = merge_auth_config(wallet_config, auth.clone())?;
let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?;
let subcommand_ret = match command {
Command::AuthTransfer(transfer_subcommand) => {
transfer_subcommand
.handle_subcommand(&mut wallet_core)
.await?
transfer_subcommand.handle_subcommand(wallet_core).await?
}
Command::ChainInfo(chain_subcommand) => {
chain_subcommand.handle_subcommand(&mut wallet_core).await?
chain_subcommand.handle_subcommand(wallet_core).await?
}
Command::Account(account_subcommand) => {
account_subcommand
.handle_subcommand(&mut wallet_core)
.await?
account_subcommand.handle_subcommand(wallet_core).await?
}
Command::Pinata(pinata_subcommand) => {
pinata_subcommand
.handle_subcommand(&mut wallet_core)
.await?
pinata_subcommand.handle_subcommand(wallet_core).await?
}
Command::CheckHealth {} => {
let remote_program_ids = wallet_core
@ -165,18 +143,15 @@ pub async fn execute_subcommand_with_auth(
SubcommandReturnValue::Empty
}
Command::Token(token_subcommand) => {
token_subcommand.handle_subcommand(&mut wallet_core).await?
}
Command::AMM(amm_subcommand) => amm_subcommand.handle_subcommand(&mut wallet_core).await?,
Command::Token(token_subcommand) => token_subcommand.handle_subcommand(wallet_core).await?,
Command::AMM(amm_subcommand) => amm_subcommand.handle_subcommand(wallet_core).await?,
Command::Config(config_subcommand) => {
config_subcommand
.handle_subcommand(&mut wallet_core)
.await?
config_subcommand.handle_subcommand(wallet_core).await?
}
Command::RestoreKeys { depth } => {
let password = read_password_from_stdin()?;
execute_keys_restoration_with_auth(password, depth, auth).await?;
wallet_core.reset_storage(password)?;
execute_keys_restoration(wallet_core, depth).await?;
SubcommandReturnValue::Empty
}
@ -200,14 +175,7 @@ pub async fn execute_subcommand_with_auth(
Ok(subcommand_ret)
}
pub async fn execute_continuous_run() -> Result<()> {
execute_continuous_run_with_auth(None).await
}
pub async fn execute_continuous_run_with_auth(auth: Option<String>) -> Result<()> {
let config = fetch_config().await?;
let config = merge_auth_config(config, auth)?;
let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?;
pub async fn execute_continuous_run(wallet_core: &mut WalletCore) -> Result<()> {
loop {
let latest_block_num = wallet_core
.sequencer_client
@ -217,7 +185,7 @@ pub async fn execute_continuous_run_with_auth(auth: Option<String>) -> Result<()
wallet_core.sync_to_block(latest_block_num).await?;
tokio::time::sleep(std::time::Duration::from_millis(
config.seq_poll_timeout_millis,
wallet_core.config().seq_poll_timeout_millis,
))
.await;
}
@ -233,34 +201,7 @@ pub fn read_password_from_stdin() -> Result<String> {
Ok(password.trim().to_string())
}
pub async fn execute_setup(password: String) -> Result<()> {
execute_setup_with_auth(password, None).await
}
pub async fn execute_setup_with_auth(password: String, auth: Option<String>) -> Result<()> {
let config = fetch_config().await?;
let config = merge_auth_config(config, auth)?;
let wallet_core = WalletCore::start_from_config_new_storage(config.clone(), password).await?;
wallet_core.store_persistent_data().await?;
Ok(())
}
pub async fn execute_keys_restoration(password: String, depth: u32) -> Result<()> {
execute_keys_restoration_with_auth(password, depth, None).await
}
pub async fn execute_keys_restoration_with_auth(
password: String,
depth: u32,
auth: Option<String>,
) -> Result<()> {
let config = fetch_config().await?;
let config = merge_auth_config(config, auth)?;
let mut wallet_core =
WalletCore::start_from_config_new_storage(config.clone(), password.clone()).await?;
pub async fn execute_keys_restoration(wallet_core: &mut WalletCore, depth: u32) -> Result<()> {
wallet_core
.storage
.user_data

View File

@ -69,9 +69,7 @@ impl WalletSubcommand for AuthTransferSubcommand {
println!("Transaction data is {transfer_tx:?}");
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
}
AccountPrivacyKind::Private => {
let account_id = account_id.parse()?;
@ -96,9 +94,7 @@ impl WalletSubcommand for AuthTransferSubcommand {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
}
}
@ -337,9 +333,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -381,9 +375,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -421,9 +413,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -454,9 +444,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded {
let tx_hash = res.tx_hash;
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -500,9 +488,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -520,9 +506,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand {
println!("Transaction data is {transfer_tx:?}");
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::Empty)
}

View File

@ -168,9 +168,7 @@ impl WalletSubcommand for PinataProgramSubcommandPrivate {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}

View File

@ -740,9 +740,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -790,9 +788,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -831,9 +827,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -872,9 +866,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -923,9 +915,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -971,9 +961,7 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -1009,9 +997,7 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -1047,9 +1033,7 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -1102,9 +1086,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
println!("Transaction data is {:?}", tx.message);
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -1140,9 +1122,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -1178,9 +1158,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -1216,9 +1194,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -1262,9 +1238,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded {
println!("Transaction data is {:?}", tx.message);
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -1323,9 +1297,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -1371,9 +1343,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}
@ -1419,9 +1389,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand {
)?;
}
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
wallet_core.store_persistent_data().await?;
Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash })
}

View File

@ -1,11 +1,17 @@
use std::str::FromStr;
use std::{
io::{BufReader, Write as _},
path::Path,
str::FromStr,
};
use anyhow::{Context as _, Result};
use key_protocol::key_management::{
KeyChain,
key_tree::{
chain_index::ChainIndex, keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic,
},
};
use log::warn;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -105,6 +111,25 @@ pub struct PersistentStorage {
pub last_synced_block: u64,
}
impl PersistentStorage {
pub fn from_path(path: &Path) -> Result<Self> {
match std::fs::File::open(path) {
Ok(file) => {
let storage_content = BufReader::new(file);
Ok(serde_json::from_reader(storage_content)?)
}
Err(err) => match err.kind() {
std::io::ErrorKind::NotFound => {
anyhow::bail!("Not found, please setup roots from config command beforehand");
}
_ => {
anyhow::bail!("IO error {err:#?}");
}
},
}
}
}
impl InitialAccountData {
pub fn account_id(&self) -> nssa::AccountId {
match &self {
@ -172,9 +197,11 @@ pub struct GasConfig {
pub gas_limit_runtime: u64,
}
#[optfield::optfield(pub WalletConfigOverrides, rewrap, attrs = (derive(Debug, Default)))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WalletConfig {
/// Override rust log (env var logging level)
#[serde(skip_serializing_if = "Option::is_none")]
pub override_rust_log: Option<String>,
/// Sequencer URL
pub sequencer_addr: String,
@ -189,6 +216,7 @@ pub struct WalletConfig {
/// Initial accounts for wallet
pub initial_accounts: Vec<InitialAccountData>,
/// Basic authentication credentials
#[serde(skip_serializing_if = "Option::is_none")]
pub basic_auth: Option<BasicAuth>,
}
@ -748,3 +776,98 @@ impl Default for WalletConfig {
}
}
}
impl WalletConfig {
pub fn from_path_or_initialize_default(config_path: &Path) -> Result<WalletConfig> {
match std::fs::File::open(config_path) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
Ok(serde_json::from_reader(reader)?)
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
println!("Config not found, setting up default config");
let config_home = config_path.parent().ok_or_else(|| {
anyhow::anyhow!(
"Could not get parent directory of config file at {config_path:#?}"
)
})?;
std::fs::create_dir_all(config_home)?;
println!("Created configs dir at path {config_home:#?}");
let mut file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(config_path)?;
let config = WalletConfig::default();
let default_config_serialized = serde_json::to_vec_pretty(&config).unwrap();
file.write_all(&default_config_serialized)?;
println!("Configs set up");
Ok(config)
}
Err(err) => Err(err).context("IO error"),
}
}
pub fn apply_overrides(&mut self, overrides: WalletConfigOverrides) {
let WalletConfig {
override_rust_log,
sequencer_addr,
seq_poll_timeout_millis,
seq_tx_poll_max_blocks,
seq_poll_max_retries,
seq_block_poll_max_amount,
initial_accounts,
basic_auth,
} = self;
let WalletConfigOverrides {
override_rust_log: o_override_rust_log,
sequencer_addr: o_sequencer_addr,
seq_poll_timeout_millis: o_seq_poll_timeout_millis,
seq_tx_poll_max_blocks: o_seq_tx_poll_max_blocks,
seq_poll_max_retries: o_seq_poll_max_retries,
seq_block_poll_max_amount: o_seq_block_poll_max_amount,
initial_accounts: o_initial_accounts,
basic_auth: o_basic_auth,
} = overrides;
if let Some(v) = o_override_rust_log {
warn!("Overriding wallet config 'override_rust_log' to {v:#?}");
*override_rust_log = v;
}
if let Some(v) = o_sequencer_addr {
warn!("Overriding wallet config 'sequencer_addr' to {v}");
*sequencer_addr = v;
}
if let Some(v) = o_seq_poll_timeout_millis {
warn!("Overriding wallet config 'seq_poll_timeout_millis' to {v}");
*seq_poll_timeout_millis = v;
}
if let Some(v) = o_seq_tx_poll_max_blocks {
warn!("Overriding wallet config 'seq_tx_poll_max_blocks' to {v}");
*seq_tx_poll_max_blocks = v;
}
if let Some(v) = o_seq_poll_max_retries {
warn!("Overriding wallet config 'seq_poll_max_retries' to {v}");
*seq_poll_max_retries = v;
}
if let Some(v) = o_seq_block_poll_max_amount {
warn!("Overriding wallet config 'seq_block_poll_max_amount' to {v}");
*seq_block_poll_max_amount = v;
}
if let Some(v) = o_initial_accounts {
warn!("Overriding wallet config 'initial_accounts' to {v:#?}");
*initial_accounts = v;
}
if let Some(v) = o_basic_auth {
warn!("Overriding wallet config 'basic_auth' to {v:#?}");
*basic_auth = v;
}
}
}

View File

@ -7,23 +7,22 @@ use nssa::Account;
use nssa_core::account::Nonce;
use rand::{RngCore, rngs::OsRng};
use serde::Serialize;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::{
HOME_DIR_ENV_VAR,
config::{
BasicAuth, InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic,
PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, WalletConfig,
InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic,
PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage,
},
};
/// Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed.
pub fn get_home_nssa_var() -> Result<PathBuf> {
fn get_home_nssa_var() -> Result<PathBuf> {
Ok(PathBuf::from_str(&std::env::var(HOME_DIR_ENV_VAR)?)?)
}
/// Get home dir for wallet. Env var `HOME` must be set before execution to succeed.
pub fn get_home_default_path() -> Result<PathBuf> {
fn get_home_default_path() -> Result<PathBuf> {
std::env::home_dir()
.map(|path| path.join(".nssa").join("wallet"))
.ok_or(anyhow::anyhow!("Failed to get HOME"))
@ -38,96 +37,20 @@ pub fn get_home() -> Result<PathBuf> {
}
}
/// Fetch config from default home
pub async fn fetch_config() -> Result<WalletConfig> {
let config_home = get_home()?;
let mut config_needs_setup = false;
let config = match tokio::fs::OpenOptions::new()
.read(true)
.open(config_home.join("wallet_config.json"))
.await
{
Ok(mut file) => {
let mut config_contents = vec![];
file.read_to_end(&mut config_contents).await?;
serde_json::from_slice(&config_contents)?
}
Err(err) => match err.kind() {
std::io::ErrorKind::NotFound => {
config_needs_setup = true;
println!("Config not found, setting up default config");
WalletConfig::default()
}
_ => anyhow::bail!("IO error {err:#?}"),
},
};
if config_needs_setup {
tokio::fs::create_dir_all(&config_home).await?;
println!("Created configs dir at path {config_home:#?}");
let mut file = tokio::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(config_home.join("wallet_config.json"))
.await?;
let default_config_serialized =
serde_json::to_vec_pretty(&WalletConfig::default()).unwrap();
file.write_all(&default_config_serialized).await?;
println!("Configs setted up");
}
Ok(config)
/// Fetch config path from default home
pub fn fetch_config_path() -> Result<PathBuf> {
let home = get_home()?;
let config_path = home.join("wallet_config.json");
Ok(config_path)
}
/// Parse CLI auth string and merge with config auth, prioritizing CLI
pub fn merge_auth_config(
mut config: WalletConfig,
cli_auth: Option<String>,
) -> Result<WalletConfig> {
if let Some(auth_str) = cli_auth {
let cli_auth_config: BasicAuth = auth_str.parse()?;
if config.basic_auth.is_some() {
println!("Warning: CLI auth argument takes precedence over config basic-auth");
}
config.basic_auth = Some(cli_auth_config);
}
Ok(config)
}
/// Fetch data stored at home
/// Fetch path to data storage from default home
///
/// File must be created through setup beforehand.
pub async fn fetch_persistent_storage() -> Result<PersistentStorage> {
pub fn fetch_persistent_storage_path() -> Result<PathBuf> {
let home = get_home()?;
let accs_path = home.join("storage.json");
let mut storage_content = vec![];
match tokio::fs::File::open(accs_path).await {
Ok(mut file) => {
file.read_to_end(&mut storage_content).await?;
Ok(serde_json::from_slice(&storage_content)?)
}
Err(err) => match err.kind() {
std::io::ErrorKind::NotFound => {
anyhow::bail!("Not found, please setup roots from config command beforehand");
}
_ => {
anyhow::bail!("IO error {err:#?}");
}
},
}
Ok(accs_path)
}
/// Produces data for storage

View File

@ -1,6 +1,6 @@
use std::{path::PathBuf, sync::Arc};
use anyhow::Result;
use anyhow::{Context, Result};
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
use chain_storage::WalletChainStore;
use common::{
@ -25,10 +25,8 @@ pub use privacy_preserving_tx::PrivacyPreservingAccount;
use tokio::io::AsyncWriteExt;
use crate::{
config::PersistentStorage,
helperfunctions::{
fetch_persistent_storage, get_home, produce_data_for_storage, produce_random_nonces,
},
config::{PersistentStorage, WalletConfigOverrides},
helperfunctions::{produce_data_for_storage, produce_random_nonces},
poller::TxPoller,
};
@ -124,91 +122,133 @@ impl TokenHolding {
}
pub struct WalletCore {
pub storage: WalletChainStore,
pub poller: TxPoller,
config_path: PathBuf,
storage: WalletChainStore,
storage_path: PathBuf,
poller: TxPoller,
// TODO: Make all fields private
pub sequencer_client: Arc<SequencerClient>,
pub last_synced_block: u64,
}
impl WalletCore {
pub async fn start_from_config_update_chain(config: WalletConfig) -> Result<Self> {
let basic_auth = config
.basic_auth
.as_ref()
.map(|auth| (auth.username.clone(), auth.password.clone()));
let client = Arc::new(SequencerClient::new_with_auth(
config.sequencer_addr.clone(),
basic_auth,
)?);
let tx_poller = TxPoller::new(config.clone(), client.clone());
/// Construct wallet using [`HOME_DIR_ENV_VAR`] env var for paths or user home dir if not set.
pub fn from_env() -> Result<Self> {
let config_path = helperfunctions::fetch_config_path()?;
let storage_path = helperfunctions::fetch_persistent_storage_path()?;
Self::new_update_chain(config_path, storage_path, None)
}
pub fn new_update_chain(
config_path: PathBuf,
storage_path: PathBuf,
config_overrides: Option<WalletConfigOverrides>,
) -> Result<Self> {
let PersistentStorage {
accounts: persistent_accounts,
last_synced_block,
} = fetch_persistent_storage().await?;
} = PersistentStorage::from_path(&storage_path)
.with_context(|| format!("Failed to read persistent storage at {storage_path:#?}"))?;
let storage = WalletChainStore::new(config, persistent_accounts)?;
Ok(Self {
storage,
poller: tx_poller,
sequencer_client: client.clone(),
Self::new(
config_path,
storage_path,
config_overrides,
|config| WalletChainStore::new(config, persistent_accounts),
last_synced_block,
})
)
}
pub async fn start_from_config_new_storage(
config: WalletConfig,
pub fn new_init_storage(
config_path: PathBuf,
storage_path: PathBuf,
config_overrides: Option<WalletConfigOverrides>,
password: String,
) -> Result<Self> {
Self::new(
config_path,
storage_path,
config_overrides,
|config| WalletChainStore::new_storage(config, password),
0,
)
}
fn new(
config_path: PathBuf,
storage_path: PathBuf,
config_overrides: Option<WalletConfigOverrides>,
storage_ctor: impl FnOnce(WalletConfig) -> Result<WalletChainStore>,
last_synced_block: u64,
) -> Result<Self> {
let mut config = WalletConfig::from_path_or_initialize_default(&config_path)
.with_context(|| format!("Failed to deserialize wallet config at {config_path:#?}"))?;
if let Some(config_overrides) = config_overrides {
config.apply_overrides(config_overrides);
}
let basic_auth = config
.basic_auth
.as_ref()
.map(|auth| (auth.username.clone(), auth.password.clone()));
let client = Arc::new(SequencerClient::new_with_auth(
let sequencer_client = Arc::new(SequencerClient::new_with_auth(
config.sequencer_addr.clone(),
basic_auth,
)?);
let tx_poller = TxPoller::new(config.clone(), client.clone());
let tx_poller = TxPoller::new(config.clone(), Arc::clone(&sequencer_client));
let storage = WalletChainStore::new_storage(config, password)?;
let storage = storage_ctor(config)?;
Ok(Self {
config_path,
storage_path,
storage,
poller: tx_poller,
sequencer_client: client.clone(),
last_synced_block: 0,
sequencer_client,
last_synced_block,
})
}
/// Store persistent data at home
pub async fn store_persistent_data(&self) -> Result<PathBuf> {
let home = get_home()?;
let storage_path = home.join("storage.json");
/// Get configuration with applied overrides
pub fn config(&self) -> &WalletConfig {
&self.storage.wallet_config
}
let data = produce_data_for_storage(&self.storage.user_data, self.last_synced_block);
let storage = serde_json::to_vec_pretty(&data)?;
/// Get storage
pub fn storage(&self) -> &WalletChainStore {
&self.storage
}
let mut storage_file = tokio::fs::File::create(storage_path.as_path()).await?;
storage_file.write_all(&storage).await?;
info!("Stored data at {storage_path:#?}");
Ok(storage_path)
/// Reset storage
pub fn reset_storage(&mut self, password: String) -> Result<()> {
self.storage = WalletChainStore::new_storage(self.storage.wallet_config.clone(), password)?;
Ok(())
}
/// Store persistent data at home
pub async fn store_config_changes(&self) -> Result<PathBuf> {
let home = get_home()?;
let config_path = home.join("wallet_config.json");
pub async fn store_persistent_data(&self) -> Result<()> {
let data = produce_data_for_storage(&self.storage.user_data, self.last_synced_block);
let storage = serde_json::to_vec_pretty(&data)?;
let mut storage_file = tokio::fs::File::create(&self.storage_path).await?;
storage_file.write_all(&storage).await?;
println!("Stored persistent accounts at {:#?}", self.storage_path);
Ok(())
}
/// Store persistent data at home
pub async fn store_config_changes(&self) -> Result<()> {
let config = serde_json::to_vec_pretty(&self.storage.wallet_config)?;
let mut config_file = tokio::fs::File::create(config_path.as_path()).await?;
let mut config_file = tokio::fs::File::create(&self.config_path).await?;
config_file.write_all(&config).await?;
info!("Stored data at {config_path:#?}");
info!("Stored data at {:#?}", self.config_path);
Ok(config_path)
Ok(())
}
pub fn create_new_account_public(

View File

@ -1,36 +1,65 @@
use anyhow::Result;
use anyhow::{Context as _, Result};
use clap::{CommandFactory as _, Parser as _};
use tokio::runtime::Builder;
use wallet::cli::{Args, execute_continuous_run_with_auth, execute_subcommand_with_auth};
pub const NUM_THREADS: usize = 2;
use wallet::{
WalletCore,
cli::{Args, execute_continuous_run, execute_subcommand, read_password_from_stdin},
config::WalletConfigOverrides,
helperfunctions::{fetch_config_path, fetch_persistent_storage_path},
};
// TODO #169: We have sample configs for sequencer, but not for wallet
// TODO #168: Why it requires config as a directory? Maybe better to deduce directory from config
// file path?
// TODO #172: Why it requires config as env var while sequencer_runner accepts as
// argument?
fn main() -> Result<()> {
let runtime = Builder::new_multi_thread()
.worker_threads(NUM_THREADS)
.enable_all()
.build()
.unwrap();
let args = Args::parse();
#[tokio::main]
async fn main() -> Result<()> {
let Args {
continuous_run,
auth,
command,
} = Args::parse();
env_logger::init();
runtime.block_on(async move {
if let Some(command) = args.command {
let _output = execute_subcommand_with_auth(command, args.auth).await?;
Ok(())
} else if args.continuous_run {
execute_continuous_run_with_auth(args.auth).await
let config_path = fetch_config_path().context("Could not fetch config path")?;
let storage_path =
fetch_persistent_storage_path().context("Could not fetch persistent storage path")?;
// Override basic auth if provided via CLI
let config_overrides = WalletConfigOverrides {
basic_auth: auth.map(|auth| auth.parse()).transpose()?.map(Some),
..Default::default()
};
if let Some(command) = command {
let mut wallet = if !storage_path.exists() {
// TODO: Maybe move to `WalletCore::from_env()` or similar?
println!("Persistent storage not found, need to execute setup");
let password = read_password_from_stdin()?;
let wallet = WalletCore::new_init_storage(
config_path,
storage_path,
Some(config_overrides),
password,
)?;
wallet.store_persistent_data().await?;
wallet
} else {
let help = Args::command().render_long_help();
println!("{help}");
Ok(())
}
})
WalletCore::new_update_chain(config_path, storage_path, Some(config_overrides))?
};
let _output = execute_subcommand(&mut wallet, command).await?;
Ok(())
} else if continuous_run {
let mut wallet =
WalletCore::new_update_chain(config_path, storage_path, Some(config_overrides))?;
execute_continuous_run(&mut wallet).await
} else {
let help = Args::command().render_long_help();
println!("{help}");
Ok(())
}
}

View File

@ -15,11 +15,10 @@ impl NativeTokenTransfer<'_> {
let instruction: u128 = 0;
self.0
.send_privacy_preserving_tx_with_pre_check(
.send_privacy_preserving_tx(
vec![PrivacyPreservingAccount::PrivateOwned(from)],
&Program::serialize_instruction(instruction).unwrap(),
&Program::authenticated_transfer_program().into(),
|_| Ok(()),
)
.await
.map(|(resp, secrets)| {