diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 65e8574d..5e0c7389 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -447,6 +447,136 @@ pub async fn test_success_token_program() { ); } +/// 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 owned. +pub async fn test_success_token_program_private_owned() { + let wallet_config = fetch_config().unwrap(); + + // Create new account for the token definition + let SubcommandReturnValue::RegisterAccount { + addr: definition_addr, + } = wallet::execute_subcommand(Command::RegisterAccountPrivate {}) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + // Create new account for the token supply holder + let SubcommandReturnValue::RegisterAccount { addr: supply_addr } = + wallet::execute_subcommand(Command::RegisterAccountPrivate {}) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + // Create new account for receiving a token transaction + let SubcommandReturnValue::RegisterAccount { + addr: recipient_addr, + } = wallet::execute_subcommand(Command::RegisterAccountPrivate {}) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; + + // Create new token + let command = Command::CreateNewTokenPrivateOwned { + 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], 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], 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], definition_addr.to_bytes()); + assert_eq!( + u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), + 7 + ); +} + pub async fn test_success_private_transfer_to_another_owned_account() { info!("test_success_private_transfer_to_another_owned_account"); let from: Address = ACC_SENDER_PRIVATE.parse().unwrap(); diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index dd396fd1..28373953 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -585,6 +585,17 @@ pub enum Command { #[arg(long)] solution: u128, }, + //Create a new token using the token program + CreateNewTokenPrivateOwned { + #[arg(short, long)] + definition_addr: String, + #[arg(short, long)] + supply_addr: String, + #[arg(short, long)] + name: String, + #[arg(short, long)] + total_supply: u128, + }, } ///To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config @@ -1025,6 +1036,81 @@ pub async fn execute_subcommand(command: Command) -> Result { + let name = name.as_bytes(); + if name.len() > 6 { + // TODO: return error + panic!("Name length mismatch"); + } + let mut name_bytes = [0; 6]; + name_bytes[..name.len()].copy_from_slice(name); + + let definition_addr: Address = definition_addr.parse().unwrap(); + let supply_addr: Address = supply_addr.parse().unwrap(); + + let (res, [secret_definition, secret_supply]) = wallet_core + .send_new_token_definition_private_owned( + definition_addr, + supply_addr, + name_bytes, + total_supply, + ) + .await?; + + println!("Results of tx send is {res:#?}"); + + let tx_hash = res.tx_hash; + let transfer_tx = wallet_core + .poll_native_token_transfer(tx_hash.clone()) + .await?; + + if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { + let definition_ebc = tx.message.encrypted_private_post_states[0].clone(); + let definition_comm = tx.message.new_commitments[0].clone(); + + let supply_ebc = tx.message.encrypted_private_post_states[1].clone(); + let supply_comm = tx.message.new_commitments[1].clone(); + + let res_acc_definition = nssa_core::EncryptionScheme::decrypt( + &definition_ebc.ciphertext, + &secret_definition, + &definition_comm, + 0, + ) + .unwrap(); + + let res_acc_supply = nssa_core::EncryptionScheme::decrypt( + &supply_ebc.ciphertext, + &secret_supply, + &supply_comm, + 1, + ) + .unwrap(); + + println!("Received new from acc {res_acc_definition:#?}"); + println!("Received new to acc {res_acc_supply:#?}"); + + println!("Transaction data is {:?}", tx.message); + + wallet_core + .storage + .insert_private_account_data(definition_addr, res_acc_definition); + wallet_core + .storage + .insert_private_account_data(supply_addr, res_acc_supply); + } + + let path = wallet_core.store_persistent_accounts()?; + + println!("Stored persistent accounts at {path:#?}"); + + SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } + } Command::TransferToken { sender_addr, recipient_addr,