diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index a01935f2..c3e26017 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -8,6 +8,7 @@ anyhow.workspace = true env_logger.workspace = true log.workspace = true actix.workspace = true +bytemuck = "1.23.2" actix-web.workspace = true tokio.workspace = true diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index cdf92f04..1646ee6b 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -172,47 +172,6 @@ pub async fn test_success_move_to_another_account() { info!("Success!"); } -pub async fn test_success_token_program() { - let wallet_config = fetch_config().unwrap(); - - wallet::execute_subcommand(Command::RegisterAccount {}) - .await - .unwrap(); - wallet::execute_subcommand(Command::RegisterAccount {}) - .await - .unwrap(); - wallet::execute_subcommand(Command::RegisterAccount {}) - .await - .unwrap(); - - let persistent_accounts = fetch_persistent_accounts().unwrap(); - - let mut new_persistent_accounts_addr = Vec::new(); - - for per_acc in persistent_accounts { - if (per_acc.address.to_string() != ACC_RECEIVER) - && (per_acc.address.to_string() != ACC_SENDER) - { - new_persistent_accounts_addr.push(per_acc.address.to_string()); - } - } - - let [definition_addr, supply_addr, other_addr] = new_persistent_accounts_addr - .try_into() - .expect("Failed to produce new account, not present in persistent accounts"); - - let command = Command::NewTokenDefinition { - definition_addr, - supply_addr, - name: "name".to_string(), - total_supply: 37, - }; - - wallet::execute_subcommand(command).await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); -} - pub async fn test_failure() { let command = Command::SendNativeTokenTransfer { from: ACC_SENDER.to_string(), @@ -344,6 +303,147 @@ pub async fn test_get_account_wallet_command() { 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. +pub async fn test_success_token_program() { + let wallet_config = fetch_config().unwrap(); + + // Create new account for the token definition + wallet::execute_subcommand(Command::RegisterAccount {}) + .await + .unwrap(); + // Create new account for the token supply holder + wallet::execute_subcommand(Command::RegisterAccount {}) + .await + .unwrap(); + // Create new account for receiving a token transaction + wallet::execute_subcommand(Command::RegisterAccount {}) + .await + .unwrap(); + + let persistent_accounts = fetch_persistent_accounts().unwrap(); + + let mut new_persistent_accounts_addr = Vec::new(); + + for per_acc in persistent_accounts { + if (per_acc.address.to_string() != ACC_RECEIVER) + && (per_acc.address.to_string() != ACC_SENDER) + { + new_persistent_accounts_addr.push(per_acc.address); + } + } + + let [definition_addr, supply_addr, recipient_addr] = new_persistent_accounts_addr + .try_into() + .expect("Failed to produce new account, not present in persistent accounts"); + + // Create new token + let command = Command::CreateNewToken { + definition_addr: definition_addr.to_string(), + supply_addr: supply_addr.to_string(), + name: "A NAME".to_string(), + total_supply: 37, + }; + wallet::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 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_addr.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) ] + assert_eq!( + definition_acc.data, + vec![ + 0, 65, 32, 78, 65, 77, 69, 37, 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_addr.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[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 address of the token definition account. + assert_eq!( + &supply_acc.data[1..33], + nssa::AccountId::from(&definition_addr).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 address `recipient_addr` + let command = Command::TransferToken { + sender_addr: supply_addr.to_string(), + recipient_addr: recipient_addr.to_string(), + balance_to_move: 7, + }; + wallet::execute_subcommand(command).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_addr` is the expected after the execution + let supply_acc = seq_client + .get_account(supply_addr.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], + nssa::AccountId::from(&definition_addr).to_bytes() + ); + assert_eq!( + u128::from_le_bytes(supply_acc.data[33..].try_into().unwrap()), + 30 + ); + + // Check the status of the account at `recipient_addr` is the expected after the execution + let recipient_acc = seq_client + .get_account(recipient_addr.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], + nssa::AccountId::from(&definition_addr).to_bytes() + ); + assert_eq!( + u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), + 7 + ); +} + macro_rules! test_cleanup_wrap { ($home_dir:ident, $test_func:ident) => {{ let res = pre_test($home_dir.clone()).await.unwrap(); @@ -367,6 +467,9 @@ pub async fn main_tests_runner() -> Result<()> { } = args; match test_name.as_str() { + "test_success_token_program" => { + test_cleanup_wrap!(home_dir, test_success_token_program); + } "test_success_move_to_another_account" => { test_cleanup_wrap!(home_dir, test_success_move_to_another_account); } @@ -388,6 +491,7 @@ pub async fn main_tests_runner() -> Result<()> { test_cleanup_wrap!(home_dir, test_failure); test_cleanup_wrap!(home_dir, test_success_two_transactions); test_cleanup_wrap!(home_dir, test_get_account_wallet_command); + test_cleanup_wrap!(home_dir, test_success_token_program); } _ => { anyhow::bail!("Unknown test name"); diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index 25a9368d..2a5682c3 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -8,7 +8,7 @@ mod signature; mod state; pub use address::Address; -pub use nssa_core::account::Account; +pub use nssa_core::account::{Account, AccountId}; pub use privacy_preserving_transaction::{ PrivacyPreservingTransaction, circuit::execute_and_prove, }; diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 4dbe8bf6..13522510 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -150,7 +150,7 @@ impl WalletCore { Ok(self.sequencer_client.send_tx(tx).await?) } - pub async fn send_token_transfer( + pub async fn send_transfer_token_transaction( &self, sender_address: Address, recipient_address: Address, @@ -258,7 +258,8 @@ pub enum Command { #[arg(short, long)] addr: String, }, - NewTokenDefinition { + //Create a new token using the token program + CreateNewToken { #[arg(short, long)] definition_addr: String, #[arg(short, long)] @@ -268,7 +269,8 @@ pub enum Command { #[arg(short, long)] total_supply: u128, }, - TokenTransfer { + //Transfer tokens using the token program + TransferToken { #[arg(short, long)] sender_addr: String, #[arg(short, long)] @@ -339,7 +341,7 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { let account: HumanReadableAccount = wallet_core.get_account(addr).await?.into(); println!("{}", serde_json::to_string(&account).unwrap()); } - Command::NewTokenDefinition { + Command::CreateNewToken { definition_addr, supply_addr, name, @@ -361,13 +363,13 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { ) .await?; } - Command::TokenTransfer { + Command::TransferToken { sender_addr, recipient_addr, balance_to_move, } => { wallet_core - .send_token_transfer( + .send_transfer_token_transaction( sender_addr.parse().unwrap(), recipient_addr.parse().unwrap(), balance_to_move,