From 473a5fd98b25ccbc6ab55d351ef4697fc918a631 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 15 Sep 2025 18:09:28 -0300 Subject: [PATCH] add integratin tests wip --- integration_tests/src/lib.rs | 41 ++++++++ nssa/program_methods/guest/src/bin/token.rs | 29 ++++-- wallet/src/lib.rs | 110 ++++++++++++++++++++ 3 files changed, 173 insertions(+), 7 deletions(-) diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 5c14ea2..cdf92f0 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -172,6 +172,47 @@ 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(), diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index 784e651..a8c2a0a 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -3,14 +3,27 @@ use nssa_core::{ program::{ProgramInput, read_nssa_inputs, write_nssa_outputs}, }; -/// [type (1) || amount (16) || name (6)] -type Instruction = [u8; 23]; +// The token program has two functions: +// 1. New token definition. +// Arguments to this function are: +// * Two **default** accounts: [definition_account, holding_account]. +// The first default account will be populated with the token definition account values. The second account will +// be set to a token holding account for the new token, holding the entire total supply. +// * An instruction data of 23-bytes, indicating the total supply and the token name, with +// the following layout: +// [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] +// The name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] +// 2. Token transfer +// Arguments to this function are: +// * Two accounts: [sender_account, recipient_account]. +// * An instruction data byte string of length 23, indicating the total supply and the token name, with the following layout +// [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. const TOKEN_DEFINITION_TYPE: u8 = 0; -const TOKEN_DEFINITION_SIZE: usize = 23; +const TOKEN_DEFINITION_DATA_SIZE: usize = 23; const TOKEN_HOLDING_TYPE: u8 = 1; -const TOKEN_HOLDING_SIZE: usize = 49; +const TOKEN_HOLDING_DATA_SIZE: usize = 49; struct TokenDefinition { account_type: u8, @@ -26,7 +39,7 @@ struct TokenHolding { impl TokenDefinition { fn into_data(self) -> Vec { - let mut bytes = [0; TOKEN_DEFINITION_SIZE]; + let mut bytes = [0; TOKEN_DEFINITION_DATA_SIZE]; bytes[0] = self.account_type; bytes[1..7].copy_from_slice(&self.name); bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes()); @@ -44,7 +57,7 @@ impl TokenHolding { } fn parse(data: &[u8]) -> Option { - if data.len() != TOKEN_HOLDING_SIZE && data[0] != TOKEN_HOLDING_TYPE { + if data.len() != TOKEN_HOLDING_DATA_SIZE && data[0] != TOKEN_HOLDING_TYPE { None } else { let account_type = data[0]; @@ -59,7 +72,7 @@ impl TokenHolding { } fn into_data(self) -> Data { - let mut bytes = [0; TOKEN_HOLDING_SIZE]; + let mut bytes = [0; TOKEN_HOLDING_DATA_SIZE]; bytes[0] = self.account_type; bytes[1..33].copy_from_slice(&self.definition_id.to_bytes()); bytes[33..].copy_from_slice(&self.balance.to_le_bytes()); @@ -147,6 +160,8 @@ fn new_definition(pre_states: Vec, name: [u8; 6], total_sup ); } +type Instruction = [u8; 23]; + fn main() { let ProgramInput { pre_states, diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 3bea629..4dbe8bf 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -126,6 +126,63 @@ impl WalletCore { } } + pub async fn send_new_token_definition( + &self, + definition_address: Address, + supply_address: Address, + name: [u8; 6], + total_supply: u128, + ) -> Result { + let addresses = vec![definition_address, supply_address]; + let program_id = nssa::program::Program::token().id(); + // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] + let mut instruction = [0; 23]; + instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); + instruction[17..].copy_from_slice(&name); + let message = + nssa::public_transaction::Message::try_new(program_id, addresses, vec![], instruction) + .unwrap(); + + let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx(tx).await?) + } + + pub async fn send_token_transfer( + &self, + sender_address: Address, + recipient_address: Address, + amount: u128, + ) -> Result { + let addresses = vec![sender_address, recipient_address]; + let program_id = nssa::program::Program::token().id(); + // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. + let mut instruction = [0; 23]; + instruction[0] = 0x01; + instruction[1..17].copy_from_slice(&amount.to_le_bytes()); + let Ok(nonces) = self.get_accounts_nonces(vec![sender_address]).await else { + return Err(ExecutionFailureKind::SequencerError); + }; + let message = + nssa::public_transaction::Message::try_new(program_id, addresses, nonces, instruction) + .unwrap(); + + let Some(signing_key) = self + .storage + .user_data + .get_account_signing_key(&sender_address) + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + let witness_set = + nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]); + + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx(tx).await?) + } ///Get account balance pub async fn get_account_balance(&self, acc: Address) -> Result { Ok(self @@ -201,6 +258,24 @@ pub enum Command { #[arg(short, long)] addr: String, }, + NewTokenDefinition { + #[arg(short, long)] + definition_addr: String, + #[arg(short, long)] + supply_addr: String, + #[arg(short, long)] + name: String, + #[arg(short, long)] + total_supply: u128, + }, + TokenTransfer { + #[arg(short, long)] + sender_addr: String, + #[arg(short, long)] + recipient_addr: String, + #[arg(short, long)] + balance_to_move: u128, + }, } ///To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config @@ -264,6 +339,41 @@ 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 { + definition_addr, + supply_addr, + name, + total_supply, + } => { + let name = name.as_bytes(); + if name.len() > 6 { + // TODO: return error + panic!(); + } + let mut name_bytes = [0; 6]; + name_bytes[..name.len()].copy_from_slice(name); + wallet_core + .send_new_token_definition( + definition_addr.parse().unwrap(), + supply_addr.parse().unwrap(), + name_bytes, + total_supply, + ) + .await?; + } + Command::TokenTransfer { + sender_addr, + recipient_addr, + balance_to_move, + } => { + wallet_core + .send_token_transfer( + sender_addr.parse().unwrap(), + recipient_addr.parse().unwrap(), + balance_to_move, + ) + .await?; + } } Ok(())