From 7296088005d87424da063f1b2d6cbc6307ce715a Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Wed, 31 Dec 2025 04:02:25 +0300 Subject: [PATCH 1/3] feat: introduce parallel integration tests, wallet without global vars and etc --- .github/workflows/ci.yml | 28 +- Cargo.lock | 21 +- Cargo.toml | 1 - common/src/sequencer_client.rs | 8 +- .../src/bin/run_hello_world.rs | 9 +- .../src/bin/run_hello_world_private.rs | 9 +- .../bin/run_hello_world_through_tail_call.rs | 9 +- ...n_hello_world_through_tail_call_private.rs | 9 +- .../bin/run_hello_world_with_authorization.rs | 11 +- ...uthorization_through_tail_call_with_pda.rs | 9 +- .../bin/run_hello_world_with_move_function.rs | 9 +- integration_tests/Cargo.toml | 6 +- .../sequencer/sequencer_config.json | 4 +- .../{debug => }/wallet/wallet_config.json | 6 +- .../proc_macro_test_attribute/Cargo.toml | 9 - .../proc_macro_test_attribute/src/lib.rs | 49 - integration_tests/src/lib.rs | 296 +- integration_tests/src/main.rs | 15 - integration_tests/src/test_suite_map.rs | 3154 ----------------- integration_tests/tests/account.rs | 28 + integration_tests/tests/amm.rs | 405 +++ integration_tests/tests/auth_transfer/main.rs | 2 + .../tests/auth_transfer/private.rs | 417 +++ .../tests/auth_transfer/public.rs | 248 ++ integration_tests/tests/config.rs | 33 + integration_tests/tests/keys_restoration.rs | 217 ++ integration_tests/tests/pinata.rs | 155 + integration_tests/tests/program_deployment.rs | 64 + integration_tests/tests/token.rs | 968 +++++ .../{src/tps_test_utils.rs => tests/tps.rs} | 83 +- key_protocol/src/key_protocol_core/mod.rs | 8 + sequencer_core/Cargo.toml | 1 + sequencer_core/src/config.rs | 16 +- sequencer_rpc/src/net_utils.rs | 21 +- sequencer_runner/src/config.rs | 11 - sequencer_runner/src/lib.rs | 14 +- wallet/Cargo.toml | 3 +- wallet/src/cli/account.rs | 12 +- wallet/src/cli/config.rs | 4 +- wallet/src/cli/mod.rs | 87 +- .../src/cli/programs/native_token_transfer.rs | 32 +- wallet/src/cli/programs/pinata.rs | 4 +- wallet/src/cli/programs/token.rs | 64 +- wallet/src/config.rs | 125 +- wallet/src/helperfunctions.rs | 101 +- wallet/src/lib.rs | 140 +- wallet/src/main.rs | 77 +- .../native_token_transfer/private.rs | 3 +- 48 files changed, 3218 insertions(+), 3787 deletions(-) rename integration_tests/configs/{debug => }/sequencer/sequencer_config.json (98%) rename integration_tests/configs/{debug => }/wallet/wallet_config.json (99%) delete mode 100644 integration_tests/proc_macro_test_attribute/Cargo.toml delete mode 100644 integration_tests/proc_macro_test_attribute/src/lib.rs delete mode 100644 integration_tests/src/main.rs delete mode 100644 integration_tests/src/test_suite_map.rs create mode 100644 integration_tests/tests/account.rs create mode 100644 integration_tests/tests/amm.rs create mode 100644 integration_tests/tests/auth_transfer/main.rs create mode 100644 integration_tests/tests/auth_transfer/private.rs create mode 100644 integration_tests/tests/auth_transfer/public.rs create mode 100644 integration_tests/tests/config.rs create mode 100644 integration_tests/tests/keys_restoration.rs create mode 100644 integration_tests/tests/pinata.rs create mode 100644 integration_tests/tests/program_deployment.rs create mode 100644 integration_tests/tests/token.rs rename integration_tests/{src/tps_test_utils.rs => tests/tps.rs} (74%) delete mode 100644 sequencer_runner/src/config.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21c73d2..ace2d07 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/Cargo.lock b/Cargo.lock index eeb8f5b..5c378ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 14856d0..ef1b881 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/common/src/sequencer_client.rs b/common/src/sequencer_client.rs index 622c0c1..0cb03f6 100644 --- a/common/src/sequencer_client.rs +++ b/common/src/sequencer_client.rs @@ -44,8 +44,10 @@ impl SequencerClient { ) -> Result { 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 { diff --git a/examples/program_deployment/src/bin/run_hello_world.rs b/examples/program_deployment/src/bin/run_hello_world.rs index a7dc0fc..3c0c903 100644 --- a/examples/program_deployment/src/bin/run_hello_world.rs +++ b/examples/program_deployment/src/bin/run_hello_world.rs @@ -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 diff --git a/examples/program_deployment/src/bin/run_hello_world_private.rs b/examples/program_deployment/src/bin/run_hello_world_private.rs index dcbe59a..cb66d42 100644 --- a/examples/program_deployment/src/bin/run_hello_world_private.rs +++ b/examples/program_deployment/src/bin/run_hello_world_private.rs @@ -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 diff --git a/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs b/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs index d7c91f8..56d2808 100644 --- a/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs +++ b/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs @@ -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 diff --git a/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs b/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs index 5a014f2..bd67cfe 100644 --- a/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs +++ b/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs @@ -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 diff --git a/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs b/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs index 21740ae..09fd621 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs @@ -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"); diff --git a/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs b/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs index 6c673d8..43839ba 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs @@ -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 diff --git a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs index 4307315..d879ab6 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs @@ -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 = 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 { diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 52c6e38..b888c17 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -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 diff --git a/integration_tests/configs/debug/sequencer/sequencer_config.json b/integration_tests/configs/sequencer/sequencer_config.json similarity index 98% rename from integration_tests/configs/debug/sequencer/sequencer_config.json rename to integration_tests/configs/sequencer/sequencer_config.json index db1c7f2..1548bb5 100644 --- a/integration_tests/configs/debug/sequencer/sequencer_config.json +++ b/integration_tests/configs/sequencer/sequencer_config.json @@ -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", diff --git a/integration_tests/configs/debug/wallet/wallet_config.json b/integration_tests/configs/wallet/wallet_config.json similarity index 99% rename from integration_tests/configs/debug/wallet/wallet_config.json rename to integration_tests/configs/wallet/wallet_config.json index ad7b279..2abab83 100644 --- a/integration_tests/configs/debug/wallet/wallet_config.json +++ b/integration_tests/configs/wallet/wallet_config.json @@ -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 + ] } \ No newline at end of file diff --git a/integration_tests/proc_macro_test_attribute/Cargo.toml b/integration_tests/proc_macro_test_attribute/Cargo.toml deleted file mode 100644 index a50c3f3..0000000 --- a/integration_tests/proc_macro_test_attribute/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "proc_macro_test_attribute" -version = "0.1.0" -edition = "2024" - -[dependencies] - -[lib] -proc-macro = true diff --git a/integration_tests/proc_macro_test_attribute/src/lib.rs b/integration_tests/proc_macro_test_attribute/src/lib.rs deleted file mode 100644 index 29852a0..0000000 --- a/integration_tests/proc_macro_test_attribute/src/lib.rs +++ /dev/null @@ -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() -} diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 401238f..12d718e 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -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>, + sequencer_client: SequencerClient, + wallet: WalletCore, + _temp_sequencer_dir: TempDir, + _temp_wallet_dir: TempDir, +} + +impl TestContext { + /// Create new test context. + pub async fn new() -> Result { + 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 { + // 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: + // but clients need to connect to 127.0.0.1: 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>, 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>, 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>, 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()); diff --git a/integration_tests/src/main.rs b/integration_tests/src/main.rs deleted file mode 100644 index 583df2a..0000000 --- a/integration_tests/src/main.rs +++ /dev/null @@ -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()) -} diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs deleted file mode 100644 index e722cc5..0000000 --- a/integration_tests/src/test_suite_map.rs +++ /dev/null @@ -1,3154 +0,0 @@ -use std::{ - collections::HashMap, - path::PathBuf, - pin::Pin, - str::FromStr, - time::{Duration, Instant}, -}; - -use actix_web::dev::ServerHandle; -use anyhow::Result; -use common::{PINATA_BASE58, sequencer_client::SequencerClient}; -use key_protocol::key_management::key_tree::chain_index::ChainIndex; -use log::info; -use nssa::{AccountId, program::Program}; -use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point}; -use sequencer_runner::startup_sequencer; -use tempfile::TempDir; -use tokio::task::JoinHandle; -use wallet::{ - WalletCore, - cli::{ - Command, SubcommandReturnValue, - account::{AccountSubcommand, NewSubcommand}, - config::ConfigSubcommand, - programs::{ - amm::AmmProgramAgnosticSubcommand, native_token_transfer::AuthTransferSubcommand, - pinata::PinataProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand, - }, - }, - config::PersistentStorage, - helperfunctions::{fetch_config, fetch_persistent_storage}, -}; - -use crate::{ - ACC_RECEIVER, ACC_RECEIVER_PRIVATE, ACC_SENDER, ACC_SENDER_PRIVATE, - NSSA_PROGRAM_FOR_TEST_DATA_CHANGER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, - fetch_privacy_preserving_tx, make_private_account_input_from_str, - make_public_account_input_from_str, post_test, pre_test, - replace_home_dir_with_temp_dir_in_configs, tps_test_utils::TpsTestManager, - verify_commitment_is_in_state, -}; - -type TestFunction = fn(PathBuf) -> Pin>>; - -pub fn prepare_function_map() -> HashMap { - let mut function_map: HashMap = HashMap::new(); - - #[nssa_integration_test] - pub async fn test_success() { - info!("########## test_success ##########"); - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - 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 = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - 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!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_move_to_another_account() { - info!("########## test_success_move_to_another_account ##########"); - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let PersistentStorage { - accounts: persistent_accounts, - last_synced_block: _, - } = fetch_persistent_storage().await.unwrap(); - - let mut new_persistent_account_id = String::new(); - - for per_acc in persistent_accounts { - if (per_acc.account_id().to_string() != ACC_RECEIVER) - && (per_acc.account_id().to_string() != ACC_SENDER) - { - new_persistent_account_id = per_acc.account_id().to_string(); - } - } - - 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: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str( - &new_persistent_account_id, - )), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - 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 = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(new_persistent_account_id) - .await - .unwrap(); - - 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); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_failure() { - info!("########## test_failure ##########"); - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 1000000, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let failed_send = wallet::cli::execute_subcommand(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 correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - 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); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_two_transactions() { - info!("########## test_success_two_transactions ##########"); - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - 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 = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - 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!"); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - 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 = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - 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!"); - } - - #[nssa_integration_test] - pub async fn test_get_account() { - info!("########## test_get_account ##########"); - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let account = seq_client - .get_account(ACC_SENDER.to_string()) - .await - .unwrap() - .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); - } - - /// This test creates a new token using the token program. After creating the token, the test - /// executes a token transfer to a new account. - #[nssa_integration_test] - pub async fn test_success_token_program() { - info!("########## test_success_token_program ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .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 is the expected after - // the execution - let supply_acc = seq_client - .get_account(supply_account_id.to_string()) - .await - .unwrap() - .account; - - // The account must be owned by the token program - assert_eq!(supply_acc.program_owner, Program::token().id()); - // The data of a token definition 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. - // In this example, this is a token account of the newly created token, so it is expected - // to be equal to the account_id of the token definition account. - 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().unwrap()), - 37 - ); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Check the status of the account at `supply_account_id` is the expected after the - // execution - let supply_acc = seq_client - .get_account(supply_account_id.to_string()) - .await - .unwrap() - .account; - // The account must be owned by the token program - assert_eq!(supply_acc.program_owner, Program::token().id()); - // First byte equal to 1 means it's a token holding account - assert_eq!(supply_acc.data[0], 1); - // Bytes from 1 to 33 represent the id of the token this account is associated with. - assert_eq!(&supply_acc.data[1..33], definition_account_id.to_bytes()); - assert_eq!( - u128::from_le_bytes(supply_acc.data[33..].try_into().unwrap()), - 30 - ); - - // Check the status of the account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = seq_client - .get_account(recipient_account_id.to_string()) - .await - .unwrap() - .account; - - // The account must be owned by the token program - assert_eq!(recipient_acc.program_owner, Program::token().id()); - // First byte equal to 1 means it's a token holding account - assert_eq!(recipient_acc.data[0], 1); - // Bytes from 1 to 33 represent the id of the token this account is associated with. - assert_eq!(&recipient_acc.data[1..33], definition_account_id.to_bytes()); - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 7 - ); - - // Burn 3 tokens from `recipient_acc` - let subcommand = TokenProgramAgnosticSubcommand::Burn { - definition: make_public_account_input_from_str(&definition_account_id.to_string()), - holder: make_public_account_input_from_str(&recipient_account_id.to_string()), - amount: 3, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - 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 is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .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 account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = seq_client - .get_account(recipient_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 4 - ); - - // Mint 10 tokens at `recipient_acc` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_public_account_input_from_str(&definition_account_id.to_string()), - holder: Some(make_public_account_input_from_str( - &recipient_account_id.to_string(), - )), - holder_npk: None, - holder_ipk: None, - amount: 10, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - 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 is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .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 account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = seq_client - .get_account(recipient_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 14 - ); - } - - /// This test creates a new private token using the token program. After creating the token, the - /// test executes a private token transfer to a new account. All accounts are private owned - /// except definition which is public. - #[nssa_integration_test] - pub async fn test_success_token_program_private_owned_supply() { - info!("########## test_success_token_program_private_owned_supply ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .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 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Transfer additional 7 tokens from `supply_acc` to the account at account_id - // `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Burn 3 tokens from `recipient_acc` - let subcommand = TokenProgramAgnosticSubcommand::Burn { - definition: make_public_account_input_from_str(&definition_account_id.to_string()), - holder: make_private_account_input_from_str(&recipient_account_id.to_string()), - amount: 3, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - 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 is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .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 wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = wallet_storage - .get_account_private(&recipient_account_id) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 11 - ); - - // Mint 10 tokens at `recipient_acc` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_public_account_input_from_str(&definition_account_id.to_string()), - holder: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - holder_npk: None, - holder_ipk: None, - amount: 10, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - 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 is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .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 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = wallet_storage - .get_account_private(&recipient_account_id) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 21 - ); - - // Now the same mint, but in foreign way - - // Create new account for receiving a mint transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id2, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let (holder_keys, _) = wallet_storage - .storage - .user_data - .get_private_account(&recipient_account_id2) - .unwrap(); - - // Mint 9 tokens at `recipient_acc2` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_public_account_input_from_str(&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.clone(), - )), - amount: 9, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Sync to claim holder - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 53, 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 wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id2) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id2` is the expected after the - // execution - let recipient_acc = wallet_storage - .get_account_private(&recipient_account_id2) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 9 - ); - } - - /// This test creates a new private token using the token program. All accounts are private - /// owned except supply which is public. - #[nssa_integration_test] - pub async fn test_success_token_program_private_owned_definition() { - info!("########## test_success_token_program_private_owned_definition ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (private) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (public) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_private_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&definition_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Check the status of the token definition account is the expected after the execution - let supply_acc = seq_client - .get_account(supply_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(supply_acc.program_owner, Program::token().id()); - // The data of a token holding account has the following layout: - // [ 0x01 || definition id (32 bytes) || balance (little endian 16 bytes) ] - assert_eq!( - supply_acc.data.as_ref(), - &[ - 1, 128, 101, 5, 31, 43, 36, 97, 108, 164, 92, 25, 157, 173, 5, 14, 194, 121, 239, - 84, 19, 160, 243, 47, 193, 2, 250, 247, 232, 253, 191, 232, 173, 37, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - - // Create new account for receiving a mint transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id_pr, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new account for receiving a mint transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id_pub, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Mint 10 tokens at `recipient_acc_pub` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_private_account_input_from_str(&definition_account_id.to_string()), - holder: Some(make_public_account_input_from_str( - &recipient_account_id_pub.to_string(), - )), - holder_npk: None, - holder_ipk: None, - amount: 10, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 47, 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 account at `recipient_account_id_pub` is the expected after the - // execution - let recipient_acc_pub = seq_client - .get_account(recipient_account_id_pub.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(recipient_acc_pub.data[33..].try_into().unwrap()), - 10 - ); - - let (holder_keys, _) = wallet_storage - .storage - .user_data - .get_private_account(&recipient_account_id_pr) - .unwrap(); - - // Mint 5 tokens at `recipient_acc_pr` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_private_account_input_from_str(&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.clone(), - )), - amount: 5, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Sync to claim holder - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 52, 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 = wallet_storage - .get_private_account_commitment(&recipient_account_id_pr) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id_pr` is the expected after the - // execution - let recipient_acc_pr = wallet_storage - .get_account_private(&recipient_account_id_pr) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc_pr.data[33..].try_into().unwrap()), - 5 - ); - - // Mint 5 tokens at `recipient_acc_pr` - let subcommand = TokenProgramAgnosticSubcommand::Mint { - definition: make_private_account_input_from_str(&definition_account_id.to_string()), - holder: Some(make_private_account_input_from_str( - &recipient_account_id_pr.to_string(), - )), - holder_npk: None, - holder_ipk: None, - amount: 5, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 57, 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 = wallet_storage - .get_private_account_commitment(&recipient_account_id_pr) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id_pr` is the expected after the - // execution - let recipient_acc = wallet_storage - .get_account_private(&recipient_account_id_pr) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 10 - ); - - // Burn 5 tokens at `recipient_acc_pub` - let subcommand = TokenProgramAgnosticSubcommand::Burn { - definition: make_private_account_input_from_str(&definition_account_id.to_string()), - holder: make_public_account_input_from_str(&recipient_account_id_pub.to_string()), - amount: 5, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 52, 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 account at `recipient_account_id_pub` is the expected after the - // execution - let recipient_acc_pub = seq_client - .get_account(recipient_account_id_pub.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - u128::from_le_bytes(recipient_acc_pub.data[33..].try_into().unwrap()), - 5 - ); - - // Burn 5 tokens at `recipient_acc_pr` - let subcommand = TokenProgramAgnosticSubcommand::Burn { - definition: make_private_account_input_from_str(&definition_account_id.to_string()), - holder: make_private_account_input_from_str(&recipient_account_id_pr.to_string()), - amount: 5, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 47, 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 = wallet_storage - .get_private_account_commitment(&recipient_account_id_pr) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Check the status of the account at `recipient_account_id_pr` is the expected after the - // execution - let recipient_acc = wallet_storage - .get_account_private(&recipient_account_id_pr) - .unwrap(); - - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 5 - ); - } - - /// This test creates a new private token using the token program. All accounts are private - /// owned. - #[nssa_integration_test] - pub async fn test_success_token_program_private_owned_definition_and_supply() { - info!( - "########## test_success_token_program_private_owned_definition_and_supply ##########" - ); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (private) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_private_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&definition_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - let supply_acc = wallet_storage - .get_account_private(&supply_account_id) - .unwrap(); - - 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 - ] - ); - - assert_eq!(supply_acc.program_owner, Program::token().id()); - // The data of a token holding account has the following layout: - // [ 0x01 || definition id (32 bytes) || balance (little endian 16 bytes) ] - assert_eq!( - supply_acc.data.as_ref(), - &[ - 1, 128, 101, 5, 31, 43, 36, 97, 108, 164, 92, 25, 157, 173, 5, 14, 194, 121, 239, - 84, 19, 160, 243, 47, 193, 2, 250, 247, 232, 253, 191, 232, 173, 37, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - } - - /// This test creates a new private token using the token program. After creating the token, the - /// test executes a private token transfer to a new account. - #[nssa_integration_test] - pub async fn test_success_token_program_private_claiming_path() { - info!("########## test_success_token_program_private_claiming_path ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .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 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let (recipient_keys, _) = wallet_storage - .storage - .user_data - .get_private_account(&recipient_account_id) - .unwrap(); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: None, - to_npk: Some(hex::encode(recipient_keys.nullifer_public_key.0)), - to_ipk: Some(hex::encode( - recipient_keys.incoming_viewing_public_key.0.clone(), - )), - amount: 7, - }; - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - } - - /// This test creates a new public token using the token program. After creating the token, the - /// test executes a shielded token transfer to a new account. All accounts are owned except - /// definition. - #[nssa_integration_test] - pub async fn test_success_token_program_shielded_owned() { - info!("########## test_success_token_program_shielded_owned ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (public) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .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 - ] - ); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Transfer additional 7 tokens from `supply_acc` to the account at account_id - // `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - } - - /// This test creates a new private token using the token program. After creating the token, the - /// test executes a deshielded token transfer to a new account. All accounts are owned - /// except definition. - #[nssa_integration_test] - pub async fn test_success_token_program_deshielded_owned() { - info!("########## test_success_token_program_deshielded_owned ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .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 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Transfer additional 7 tokens from `supply_acc` to the account at account_id - // `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - } - - #[nssa_integration_test] - pub async fn test_success_private_transfer_to_another_owned_account() { - info!("########## test_success_private_transfer_to_another_owned_account ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - let to: AccountId = ACC_RECEIVER_PRIVATE.parse().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str(&to.to_string())), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage.get_private_account_commitment(&to).unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_private_transfer_to_another_foreign_account() { - info!("########## test_success_private_transfer_to_another_foreign_account ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - 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: make_private_account_input_from_str(&from.to_string()), - to: None, - to_npk: Some(to_npk_string), - to_ipk: Some(hex::encode(to_ipk.0)), - amount: 100, - }); - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("invalid subcommand return value"); - }; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - - let tx = fetch_privacy_preserving_tx(&seq_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, &seq_client).await); - } - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_private_transfer_to_another_owned_account_claiming_path() { - info!( - "########## test_success_private_transfer_to_another_owned_account_claiming_path ##########" - ); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - - let command = - Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let (to_keys, _) = wallet_storage - .storage - .user_data - .get_private_account(&to_account_id) - .cloned() - .unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&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(command).await.unwrap(); - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { - panic!("FAILED TO SEND TX"); - }; - - let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await; - - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - wallet::cli::execute_subcommand(command).await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - 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, &seq_client).await); - } - - let to_res_acc = wallet_storage.get_account_private(&to_account_id).unwrap(); - - assert_eq!(to_res_acc.balance, 100); - - info!("Success!"); - } - - // #[nssa_integration_test] - // pub async fn test_success_private_transfer_to_another_owned_account_cont_run_path() { - // info!( - // "########## test_success_private_transfer_to_another_owned_account_cont_run_path - // ##########" ); - // let continious_run_handle = tokio::spawn(wallet::cli::execute_continious_run()); - - // let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - - // let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {})); - - // let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - // let SubcommandReturnValue::RegisterAccount { - // account_id: to_account_id, - // } = sub_ret - // else { - // panic!("FAILED TO REGISTER ACCOUNT"); - // }; - - // let wallet_config = fetch_config().await.unwrap(); - // let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - // let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - // .await - // .unwrap(); - - // let (to_keys, _) = wallet_storage - // .storage - // .user_data - // .user_private_accounts - // .get(&to_account_id) - // .cloned() - // .unwrap(); - - // let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - // from: make_private_account_input_from_str(&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(command).await.unwrap(); - // let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { - // panic!("FAILED TO SEND TX"); - // }; - - // let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await; - // println!("Waiting for next blocks to check if continoius run fetch 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; - - // let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - // .await - // .unwrap(); - - // assert_eq!(tx.message.new_commitments.len(), 2); - // for commitment in tx.message.new_commitments.into_iter() { - // assert!(verify_commitment_is_in_state(commitment, &seq_client).await); - // } - - // let to_res_acc = wallet_storage.get_account_private(&to_account_id).unwrap(); - - // assert_eq!(to_res_acc.balance, 100); - - // continious_run_handle.abort(); - - // info!("Success!"); - // } - - #[nssa_integration_test] - pub async fn test_success_deshielded_transfer_to_another_account() { - info!("########## test_success_deshielded_transfer_to_another_account ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - let to: AccountId = ACC_RECEIVER.parse().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_public_account_input_from_str(&to.to_string())), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let from_acc = wallet_storage.get_account_private(&from).unwrap(); - assert_eq!(from_acc.balance, 10000); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let from_acc = wallet_storage.get_account_private(&from).unwrap(); - let new_commitment = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment, &seq_client).await); - - let acc_2_balance = seq_client - .get_account_balance(to.to_string()) - .await - .unwrap(); - - assert_eq!(from_acc.balance, 10000 - 100); - assert_eq!(acc_2_balance.balance, 20100); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_shielded_transfer_to_another_owned_account() { - info!("########## test_success_shielded_transfer_to_another_owned_account ##########"); - let from: AccountId = ACC_SENDER.parse().unwrap(); - let to: AccountId = ACC_RECEIVER_PRIVATE.parse().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str(&to.to_string())), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let acc_to = wallet_storage.get_account_private(&to).unwrap(); - let new_commitment = wallet_storage.get_private_account_commitment(&to).unwrap(); - assert!(verify_commitment_is_in_state(new_commitment, &seq_client).await); - - let acc_from_balance = seq_client - .get_account_balance(from.to_string()) - .await - .unwrap(); - - assert_eq!(acc_from_balance.balance, 9900); - assert_eq!(acc_to.balance, 20000 + 100); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_shielded_transfer_to_another_foreign_account() { - info!("########## test_success_shielded_transfer_to_another_foreign_account ##########"); - 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().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: None, - to_npk: Some(to_npk_string), - to_ipk: Some(hex::encode(to_ipk.0)), - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("invalid subcommand 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(&seq_client, tx_hash).await; - - let acc_1_balance = seq_client - .get_account_balance(from.to_string()) - .await - .unwrap(); - - assert!( - verify_commitment_is_in_state(tx.message.new_commitments[0].clone(), &seq_client).await - ); - - assert_eq!(acc_1_balance.balance, 9900); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_pinata() { - info!("########## test_pinata ##########"); - let pinata_account_id = PINATA_BASE58; - - let pinata_prize = 150; - let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to: make_public_account_input_from_str(ACC_SENDER), - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let pinata_balance_pre = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - wallet::cli::execute_subcommand(command).await.unwrap(); - - 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 = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let winner_balance_post = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap() - .balance; - - assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); - assert_eq!(winner_balance_post, 10000 + pinata_prize); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_program_deployment() { - info!("########## test program deployment ##########"); - - 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(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - 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).unwrap(); - let data_changer = Program::new(bytecode).unwrap(); - let account_id: AccountId = "11".repeat(16).parse().unwrap(); - let message = nssa::public_transaction::Message::try_new( - data_changer.id(), - vec![account_id], - vec![], - vec![0], - ) - .unwrap(); - let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); - let transaction = nssa::PublicTransaction::new(message, witness_set); - let _response = seq_client.send_tx_public(transaction).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let post_state_account = seq_client - .get_account(account_id.to_string()) - .await - .unwrap() - .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!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_authenticated_transfer_initialize_function() { - info!("########## test initialize account for authenticated transfer ##########"); - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); - let SubcommandReturnValue::RegisterAccount { account_id } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("Error creating account"); - }; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Init { - account_id: make_public_account_input_from_str(&account_id.to_string()), - }); - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Checking correct execution"); - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let account = seq_client - .get_account(account_id.to_string()) - .await - .unwrap() - .account; - - let expected_program_owner = Program::authenticated_transfer_program().id(); - let expected_nonce = 1; - let expected_balance = 0; - - assert_eq!(account.program_owner, expected_program_owner); - assert_eq!(account.balance, expected_balance); - assert_eq!(account.nonce, expected_nonce); - assert!(account.data.is_empty()); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_authenticated_transfer_initialize_function_private() { - info!("########## test initialize private account for authenticated transfer ##########"); - let command = - Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); - let SubcommandReturnValue::RegisterAccount { account_id } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("Error creating account"); - }; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Init { - account_id: make_private_account_input_from_str(&account_id.to_string()), - }); - wallet::cli::execute_subcommand(command).await.unwrap(); - - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct execution"); - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let account = wallet_storage.get_account_private(&account_id).unwrap(); - - let expected_program_owner = Program::authenticated_transfer_program().id(); - let expected_balance = 0; - - assert_eq!(account.program_owner, expected_program_owner); - assert_eq!(account.balance, expected_balance); - assert!(account.data.is_empty()); - } - - #[nssa_integration_test] - pub async fn test_pinata_private_receiver() { - info!("########## test_pinata_private_receiver ##########"); - let pinata_account_id = PINATA_BASE58; - let pinata_prize = 150; - - let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to: make_private_account_input_from_str(ACC_SENDER_PRIVATE), - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let pinata_balance_pre = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("invalid subcommand return value"); - }; - - 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 = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&ACC_SENDER_PRIVATE.parse().unwrap()) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_pinata_private_receiver_new_account() { - info!("########## test_pinata_private_receiver_new_account ##########"); - let pinata_account_id = PINATA_BASE58; - let pinata_prize = 150; - - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: winner_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to: make_private_account_input_from_str(&winner_account_id.to_string()), - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let pinata_balance_pre = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - wallet::cli::execute_subcommand(command).await.unwrap(); - - 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 = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&winner_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_modify_config_fields() { - info!("########## test_modify_config_fields ##########"); - - let wallet_config = fetch_config().await.unwrap(); - let old_seq_poll_timeout_millis = 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(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - - assert_eq!(wallet_config.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(command).await.unwrap(); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_keys_restoration() { - info!("########## test_keys_restoration ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { - cci: Some(ChainIndex::root()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id1, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { - cci: Some(ChainIndex::from_str("/0").unwrap()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id2, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str( - &to_account_id1.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str( - &to_account_id2.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 101, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let from: AccountId = ACC_SENDER.parse().unwrap(); - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { - cci: Some(ChainIndex::root()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id3, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { - cci: Some(ChainIndex::from_str("/0").unwrap()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id4, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: Some(make_public_account_input_from_str( - &to_account_id3.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 102, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: Some(make_public_account_input_from_str( - &to_account_id4.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 103, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("########## PREPARATION END ##########"); - - wallet::cli::execute_keys_restoration("test_pass".to_string(), 10) - .await - .unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let acc1 = wallet_storage - .storage - .user_data - .private_key_tree - .get_node(to_account_id1) - .expect("Acc 1 should be restored"); - - let acc2 = wallet_storage - .storage - .user_data - .private_key_tree - .get_node(to_account_id2) - .expect("Acc 2 should be restored"); - - let _ = wallet_storage - .storage - .user_data - .public_key_tree - .get_node(to_account_id3) - .expect("Acc 3 should be restored"); - - let _ = wallet_storage - .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 END ##########"); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&to_account_id1.to_string()), - to: Some(make_private_account_input_from_str( - &to_account_id2.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 10, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&to_account_id3.to_string()), - to: Some(make_public_account_input_from_str( - &to_account_id4.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 11, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let comm1 = wallet_storage - .get_private_account_commitment(&to_account_id1) - .expect("Acc 1 commitment should exist"); - let comm2 = wallet_storage - .get_private_account_commitment(&to_account_id2) - .expect("Acc 2 commitment should exist"); - - assert!(verify_commitment_is_in_state(comm1, &seq_client).await); - assert!(verify_commitment_is_in_state(comm2, &seq_client).await); - - let acc3 = seq_client - .get_account_balance(to_account_id3.to_string()) - .await - .expect("Acc 3 must be present in public state"); - let acc4 = seq_client - .get_account_balance(to_account_id4.to_string()) - .await - .expect("Acc 4 must be present in public state"); - - assert_eq!(acc3.balance, 91); - assert_eq!(acc4.balance, 114); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_amm_public() { - info!("########## test_amm_public ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id_1, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id_1, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id_1, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token definition - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id_2, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id_2, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id_2, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id_1.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id_1.to_string()), - name: "A NAM1".to_string(), - total_supply: 37, - }; - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - 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: make_public_account_input_from_str(&supply_account_id_1.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id_1.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - 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: make_public_account_input_from_str( - &definition_account_id_2.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id_2.to_string()), - name: "A NAM2".to_string(), - total_supply: 37, - }; - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_1` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id_2.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id_2.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - 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(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Send creation tx - let subcommand = AmmProgramAgnosticSubcommand::New { - user_holding_a: make_public_account_input_from_str(&recipient_account_id_1.to_string()), - user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()), - user_holding_lp: make_public_account_input_from_str(&user_holding_lp.to_string()), - balance_a: 3, - balance_b: 3, - }; - - wallet::cli::execute_subcommand(Command::AMM(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let user_holding_a_acc = seq_client - .get_account(recipient_account_id_1.to_string()) - .await - .unwrap() - .account; - - let user_holding_b_acc = seq_client - .get_account(recipient_account_id_2.to_string()) - .await - .unwrap() - .account; - - let user_holding_lp_acc = seq_client - .get_account(user_holding_lp.to_string()) - .await - .unwrap() - .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: make_public_account_input_from_str(&recipient_account_id_1.to_string()), - user_holding_b: make_public_account_input_from_str(&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(Command::AMM(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let user_holding_a_acc = seq_client - .get_account(recipient_account_id_1.to_string()) - .await - .unwrap() - .account; - - let user_holding_b_acc = seq_client - .get_account(recipient_account_id_2.to_string()) - .await - .unwrap() - .account; - - let user_holding_lp_acc = seq_client - .get_account(user_holding_lp.to_string()) - .await - .unwrap() - .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: make_public_account_input_from_str(&recipient_account_id_1.to_string()), - user_holding_b: make_public_account_input_from_str(&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(Command::AMM(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let user_holding_a_acc = seq_client - .get_account(recipient_account_id_1.to_string()) - .await - .unwrap() - .account; - - let user_holding_b_acc = seq_client - .get_account(recipient_account_id_2.to_string()) - .await - .unwrap() - .account; - - let user_holding_lp_acc = seq_client - .get_account(user_holding_lp.to_string()) - .await - .unwrap() - .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: make_public_account_input_from_str(&recipient_account_id_1.to_string()), - user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()), - user_holding_lp: make_public_account_input_from_str(&user_holding_lp.to_string()), - min_amount_lp: 1, - max_amount_a: 2, - max_amount_b: 2, - }; - - wallet::cli::execute_subcommand(Command::AMM(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let user_holding_a_acc = seq_client - .get_account(recipient_account_id_1.to_string()) - .await - .unwrap() - .account; - - let user_holding_b_acc = seq_client - .get_account(recipient_account_id_2.to_string()) - .await - .unwrap() - .account; - - let user_holding_lp_acc = seq_client - .get_account(user_holding_lp.to_string()) - .await - .unwrap() - .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: make_public_account_input_from_str(&recipient_account_id_1.to_string()), - user_holding_b: make_public_account_input_from_str(&recipient_account_id_2.to_string()), - user_holding_lp: make_public_account_input_from_str(&user_holding_lp.to_string()), - balance_lp: 2, - min_amount_a: 1, - min_amount_b: 1, - }; - - wallet::cli::execute_subcommand(Command::AMM(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let user_holding_a_acc = seq_client - .get_account(recipient_account_id_1.to_string()) - .await - .unwrap() - .account; - - let user_holding_b_acc = seq_client - .get_account(recipient_account_id_2.to_string()) - .await - .unwrap() - .account; - - let user_holding_lp_acc = seq_client - .get_account(user_holding_lp.to_string()) - .await - .unwrap() - .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!"); - } - - println!("{function_map:#?}"); - - function_map -} - -#[allow(clippy::type_complexity)] -async fn pre_tps_test( - test: &TpsTestManager, -) -> Result<(ServerHandle, JoinHandle>, TempDir)> { - info!("Generating tps test config"); - let mut sequencer_config = test.generate_tps_test_config(); - info!("Done"); - - 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 async fn tps_test() { - let num_transactions = 300 * 5; - let target_tps = 12; - let tps_test = TpsTestManager::new(target_tps, num_transactions); - - let target_time = tps_test.target_time(); - info!("Target time: {:?} seconds", target_time.as_secs()); - let res = pre_tps_test(&tps_test).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - info!("TPS test begin"); - 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 = seq_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 = seq_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(); - - info!("TPS test finished successfully"); - info!("Target TPS: {}", target_tps); - info!( - "Processed {} transactions in {}s", - tx_hashes.len(), - time_elapsed - ); - info!("Target time: {:?}s", target_time.as_secs()); - - post_test(res).await; -} diff --git a/integration_tests/tests/account.rs b/integration_tests/tests/account.rs new file mode 100644 index 0000000..e5e700d --- /dev/null +++ b/integration_tests/tests/account.rs @@ -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(()) +} diff --git a/integration_tests/tests/amm.rs b/integration_tests/tests/amm.rs new file mode 100644 index 0000000..073cd03 --- /dev/null +++ b/integration_tests/tests/amm.rs @@ -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(()) +} diff --git a/integration_tests/tests/auth_transfer/main.rs b/integration_tests/tests/auth_transfer/main.rs new file mode 100644 index 0000000..c97008b --- /dev/null +++ b/integration_tests/tests/auth_transfer/main.rs @@ -0,0 +1,2 @@ +mod private; +mod public; diff --git a/integration_tests/tests/auth_transfer/private.rs b/integration_tests/tests/auth_transfer/private.rs new file mode 100644 index 0000000..4674ed0 --- /dev/null +++ b/integration_tests/tests/auth_transfer/private.rs @@ -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(()) +} diff --git a/integration_tests/tests/auth_transfer/public.rs b/integration_tests/tests/auth_transfer/public.rs new file mode 100644 index 0000000..467e3eb --- /dev/null +++ b/integration_tests/tests/auth_transfer/public.rs @@ -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(()) +} diff --git a/integration_tests/tests/config.rs b/integration_tests/tests/config.rs new file mode 100644 index 0000000..ca800d0 --- /dev/null +++ b/integration_tests/tests/config.rs @@ -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(()) +} diff --git a/integration_tests/tests/keys_restoration.rs b/integration_tests/tests/keys_restoration.rs new file mode 100644 index 0000000..9076c87 --- /dev/null +++ b/integration_tests/tests/keys_restoration.rs @@ -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(()) +} diff --git a/integration_tests/tests/pinata.rs b/integration_tests/tests/pinata.rs new file mode 100644 index 0000000..c627cea --- /dev/null +++ b/integration_tests/tests/pinata.rs @@ -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(()) +} diff --git a/integration_tests/tests/program_deployment.rs b/integration_tests/tests/program_deployment.rs new file mode 100644 index 0000000..25771ec --- /dev/null +++ b/integration_tests/tests/program_deployment.rs @@ -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(()) +} diff --git a/integration_tests/tests/token.rs b/integration_tests/tests/token.rs new file mode 100644 index 0000000..9a8b714 --- /dev/null +++ b/integration_tests/tests/token.rs @@ -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(()) +} diff --git a/integration_tests/src/tps_test_utils.rs b/integration_tests/tests/tps.rs similarity index 74% rename from integration_tests/src/tps_test_utils.rs rename to integration_tests/tests/tps.rs index 154253c..b62a3ce 100644 --- a/integration_tests/src/tps_test_utils.rs +++ b/integration_tests/tests/tps.rs @@ -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::>(); + .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]; diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index b46c46c..4814552 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -165,6 +165,14 @@ impl NSSAUserData { .map(Into::into) } } + + pub fn account_ids(&self) -> impl Iterator { + 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 { diff --git a/sequencer_core/Cargo.toml b/sequencer_core/Cargo.toml index 06164e9..a844c52 100644 --- a/sequencer_core/Cargo.toml +++ b/sequencer_core/Cargo.toml @@ -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 diff --git a/sequencer_core/src/config.rs b/sequencer_core/src/config.rs index 2f4ee3b..4ef0880 100644 --- a/sequencer_core/src/config.rs +++ b/sequencer_core/src/config.rs @@ -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 { + let file = File::open(config_home)?; + let reader = BufReader::new(file); + + Ok(serde_json::from_reader(reader)?) + } +} diff --git a/sequencer_rpc/src/net_utils.rs b/sequencer_rpc/src/net_utils.rs index 33eacae..8b9b7e6 100644 --- a/sequencer_rpc/src/net_utils.rs +++ b/sequencer_rpc/src/net_utils.rs @@ -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>, mempool_handle: MemPoolHandle, -) -> io::Result { +) -> 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)) } diff --git a/sequencer_runner/src/config.rs b/sequencer_runner/src/config.rs deleted file mode 100644 index 58f539b..0000000 --- a/sequencer_runner/src/config.rs +++ /dev/null @@ -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 { - let file = File::open(config_home)?; - let reader = BufReader::new(file); - - Ok(serde_json::from_reader(reader)?) -} diff --git a/sequencer_runner/src/lib.rs b/sequencer_runner/src/lib.rs index e9f4a84..5c1ab92 100644 --- a/sequencer_runner/src/lib.rs +++ b/sequencer_runner/src/lib.rs @@ -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<(ServerHandle, SocketAddr, JoinHandle>)> { 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??; diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 9e4b078..0f88af2 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -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" diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index d10c8c5..028a953 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -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?; } diff --git a/wallet/src/cli/config.rs b/wallet/src/cli/config.rs index bfebf9b..1546716 100644 --- a/wallet/src/cli/config.rs +++ b/wallet/src/cli/config.rs @@ -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" => { diff --git a/wallet/src/cli/mod.rs b/wallet/src/cli/mod.rs index cf3b2f1..c742ecb 100644 --- a/wallet/src/cli/mod.rs +++ b/wallet/src/cli/mod.rs @@ -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 { - 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, ) -> Result { - 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) -> 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) -> 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 { 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) -> 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, -) -> 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 diff --git a/wallet/src/cli/programs/native_token_transfer.rs b/wallet/src/cli/programs/native_token_transfer.rs index 7868a7c..0cfc0fb 100644 --- a/wallet/src/cli/programs/native_token_transfer.rs +++ b/wallet/src/cli/programs/native_token_transfer.rs @@ -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) } diff --git a/wallet/src/cli/programs/pinata.rs b/wallet/src/cli/programs/pinata.rs index e0b6570..192d3f4 100644 --- a/wallet/src/cli/programs/pinata.rs +++ b/wallet/src/cli/programs/pinata.rs @@ -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 }) } diff --git a/wallet/src/cli/programs/token.rs b/wallet/src/cli/programs/token.rs index 25d61ff..b5ea1b3 100644 --- a/wallet/src/cli/programs/token.rs +++ b/wallet/src/cli/programs/token.rs @@ -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 }) } diff --git a/wallet/src/config.rs b/wallet/src/config.rs index c06ccc4..45407b6 100644 --- a/wallet/src/config.rs +++ b/wallet/src/config.rs @@ -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 { + 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, /// Sequencer URL pub sequencer_addr: String, @@ -189,6 +216,7 @@ pub struct WalletConfig { /// Initial accounts for wallet pub initial_accounts: Vec, /// Basic authentication credentials + #[serde(skip_serializing_if = "Option::is_none")] pub basic_auth: Option, } @@ -748,3 +776,98 @@ impl Default for WalletConfig { } } } + +impl WalletConfig { + pub fn from_path_or_initialize_default(config_path: &Path) -> Result { + 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; + } + } +} diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 5f1dcf7..23bf4bb 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -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 { +fn get_home_nssa_var() -> Result { 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 { +fn get_home_default_path() -> Result { 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 { } } -/// Fetch config from default home -pub async fn fetch_config() -> Result { - 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 { + 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, -) -> Result { - 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 { +pub fn fetch_persistent_storage_path() -> Result { 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 diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index bad6435..7c3da73 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -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, pub last_synced_block: u64, } impl WalletCore { - pub async fn start_from_config_update_chain(config: WalletConfig) -> Result { - 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 { + 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, + ) -> Result { 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, password: String, ) -> Result { + 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, + storage_ctor: impl FnOnce(WalletConfig) -> Result, + last_synced_block: u64, + ) -> Result { + 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 { - 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 { - 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( diff --git a/wallet/src/main.rs b/wallet/src/main.rs index 708b1fc..045b1b8 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -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(()) + } } diff --git a/wallet/src/program_facades/native_token_transfer/private.rs b/wallet/src/program_facades/native_token_transfer/private.rs index 0baeeac..da98aed 100644 --- a/wallet/src/program_facades/native_token_transfer/private.rs +++ b/wallet/src/program_facades/native_token_transfer/private.rs @@ -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)| { From e7ee114d9f6d485e2bdd09bf5c7e7d047667aeb0 Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Wed, 31 Dec 2025 04:33:18 +0300 Subject: [PATCH 2/3] chore: fix cargo machete --- Cargo.lock | 1 - sequencer_runner/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c378ec..40434dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3952,7 +3952,6 @@ dependencies = [ "log", "sequencer_core", "sequencer_rpc", - "serde_json", "tokio", ] diff --git a/sequencer_runner/Cargo.toml b/sequencer_runner/Cargo.toml index 7200145..55f56de 100644 --- a/sequencer_runner/Cargo.toml +++ b/sequencer_runner/Cargo.toml @@ -10,7 +10,6 @@ sequencer_rpc.workspace = true clap = { workspace = true, features = ["derive", "env"] } anyhow.workspace = true -serde_json.workspace = true env_logger.workspace = true log.workspace = true actix.workspace = true From f98c6456422912e820099cf5a7d091a2de6e358f Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Wed, 31 Dec 2025 13:50:11 +0300 Subject: [PATCH 3/3] fix: skip tps_test in ci --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ace2d07..38161a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,11 +101,11 @@ jobs: - name: Install nextest run: cargo install cargo-nextest - - name: Run unit tests + - name: Run tests env: RISC0_DEV_MODE: "1" RUST_LOG: "info" - run: cargo nextest run --no-fail-fast + run: cargo nextest run --no-fail-fast -- --skip tps_test valid-proof-test: runs-on: ubuntu-latest