diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index a304191..c5cb7c1 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -1297,6 +1297,37 @@ pub async fn test_program_deployment() { info!("Success!"); } +pub async fn test_authenticated_transfer_initialize_function() { + info!("test initialize account for authenticated transfer"); + let command = Command::AuthenticatedTransferInitializePublicAccount {}; + + let SubcommandReturnValue::RegisterAccount { addr } = + wallet::execute_subcommand(command).await.unwrap() + else { + panic!("Error creating account"); + }; + + info!("Checking correct execution"); + let wallet_config = fetch_config().unwrap(); + let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); + let account = seq_client + .get_account(addr.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!"); +} + pub async fn test_pinata_private_receiver() { info!("test_pinata_private_receiver"); let pinata_addr = "cafe".repeat(16); @@ -1496,6 +1527,9 @@ pub async fn main_tests_runner() -> Result<()> { "test_program_deployment" => { test_cleanup_wrap!(home_dir, test_program_deployment); } + "test_authenticated_transfer_initialize_function" => { + test_cleanup_wrap!(home_dir, test_authenticated_transfer_initialize_function); + } "test_pinata_private_receiver" => { test_cleanup_wrap!(home_dir, test_pinata_private_receiver); } @@ -1518,6 +1552,7 @@ pub async fn main_tests_runner() -> Result<()> { test_cleanup_wrap!(home_dir, test_success_move_to_another_account); test_cleanup_wrap!(home_dir, test_success); test_cleanup_wrap!(home_dir, test_failure); + test_cleanup_wrap!(home_dir, test_get_account); test_cleanup_wrap!(home_dir, test_success_two_transactions); test_cleanup_wrap!(home_dir, test_success_token_program); test_cleanup_wrap!( @@ -1544,10 +1579,13 @@ pub async fn main_tests_runner() -> Result<()> { home_dir, test_success_private_transfer_to_another_owned_account_claiming_path ); + test_cleanup_wrap!(home_dir, test_success_token_program_shielded_owned); test_cleanup_wrap!(home_dir, test_pinata); test_cleanup_wrap!(home_dir, test_program_deployment); + test_cleanup_wrap!(home_dir, test_authenticated_transfer_initialize_function); test_cleanup_wrap!(home_dir, test_pinata_private_receiver); test_cleanup_wrap!(home_dir, test_success_token_program_private_owned); + test_cleanup_wrap!(home_dir, test_success_token_program_deshielded_owned); test_cleanup_wrap!(home_dir, test_success_token_program_private_claiming_path); test_cleanup_wrap!(home_dir, test_pinata_private_receiver_new_account); } diff --git a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs index 210e3f0..df8a38e 100644 --- a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/nssa/program_methods/guest/src/bin/authenticated_transfer.rs @@ -1,21 +1,30 @@ -use nssa_core::program::{ProgramInput, read_nssa_inputs, write_nssa_outputs}; +use nssa_core::{ + account::{Account, AccountWithMetadata}, + program::{ProgramInput, read_nssa_inputs, write_nssa_outputs}, +}; -/// A transfer of balance program. -/// To be used both in public and private contexts. -fn main() { - // Read input accounts. - // It is expected to receive only two accounts: [sender_account, receiver_account] - let ProgramInput { - pre_states, - instruction: balance_to_move, - } = read_nssa_inputs(); +/// Initializes a default account under the ownership of this program. +/// This is achieved by a noop. +fn initialize_account(pre_state: AccountWithMetadata) { + let account_to_claim = pre_state.account.clone(); + let is_authorized = pre_state.is_authorized; - // Continue only if input_accounts is an array of two elements - let [sender, receiver] = match pre_states.try_into() { - Ok(array) => array, - Err(_) => return, - }; + // Continue only if the account to claim has default values + if account_to_claim != Account::default() { + return; + } + // Continue only if the owner authorized this operation + if !is_authorized { + return; + } + + // Noop will result in account being claimed for this program + write_nssa_outputs(vec![pre_state], vec![account_to_claim]); +} + +/// Transfers `balance_to_move` native balance from `sender` to `recipient`. +fn transfer(sender: AccountWithMetadata, recipient: AccountWithMetadata, balance_to_move: u128) { // Continue only if the sender has authorized this operation if !sender.is_authorized { return; @@ -28,10 +37,27 @@ fn main() { // Create accounts post states, with updated balances let mut sender_post = sender.account.clone(); - let mut receiver_post = receiver.account.clone(); + let mut recipient_post = recipient.account.clone(); sender_post.balance -= balance_to_move; - receiver_post.balance += balance_to_move; + recipient_post.balance += balance_to_move; - write_nssa_outputs(vec![sender, receiver], vec![sender_post, receiver_post]); + write_nssa_outputs(vec![sender, recipient], vec![sender_post, recipient_post]); } +/// A transfer of balance program. +/// To be used both in public and private contexts. +fn main() { + // Read input accounts. + let ProgramInput { + pre_states, + instruction: balance_to_move, + } = read_nssa_inputs(); + + match (pre_states.as_slice(), balance_to_move) { + ([account_to_claim], 0) => initialize_account(account_to_claim.clone()), + ([sender, recipient], balance_to_move) => { + transfer(sender.clone(), recipient.clone(), balance_to_move) + } + _ => panic!("invalid params"), + } +} diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 31cf483..fd9f4c0 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -326,6 +326,7 @@ pub enum Command { #[arg(long)] solution: u128, }, + AuthenticatedTransferInitializePublicAccount {}, // Check the wallet can connect to the node and builtin local programs // match the remote versions CheckHealth {}, @@ -665,7 +666,8 @@ pub async fn execute_subcommand(command: Command) -> Result { let addr: Address = addr.parse()?; if let Some(account) = wallet_core.get_account_private(&addr) { - println!("{}", serde_json::to_string(&account).unwrap()); + let account_hr: HumanReadableAccount = account.into(); + println!("{}", serde_json::to_string(&account_hr).unwrap()); } else { println!("Private account not found."); } @@ -768,6 +770,25 @@ pub async fn execute_subcommand(command: Command) -> Result { + let addr = wallet_core.create_new_account_public(); + + println!("Generated new account with addr {addr}"); + + let path = wallet_core.store_persistent_accounts()?; + + println!("Stored persistent accounts at {path:#?}"); + + let res = wallet_core + .register_account_under_authenticated_transfers_programs(addr) + .await?; + + println!("Results of tx send is {res:#?}"); + + let _transfer_tx = wallet_core.poll_native_token_transfer(res.tx_hash).await?; + + SubcommandReturnValue::RegisterAccount { addr } + } Command::TokenProgram(token_subcommand) => { token_subcommand.handle_subcommand(&mut wallet_core).await? } diff --git a/wallet/src/token_transfers/public.rs b/wallet/src/token_transfers/public.rs index 22d50eb..3e6df44 100644 --- a/wallet/src/token_transfers/public.rs +++ b/wallet/src/token_transfers/public.rs @@ -42,4 +42,30 @@ impl WalletCore { Err(ExecutionFailureKind::InsufficientFundsError) } } + + pub async fn register_account_under_authenticated_transfers_programs( + &self, + from: Address, + ) -> Result { + let Ok(nonces) = self.get_accounts_nonces(vec![from]).await else { + return Err(ExecutionFailureKind::SequencerError); + }; + + let instruction: u128 = 0; + let addresses = vec![from]; + let program_id = Program::authenticated_transfer_program().id(); + let message = Message::try_new(program_id, addresses, nonces, instruction).unwrap(); + + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); + + let Some(signing_key) = signing_key else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let witness_set = WitnessSet::for_message(&message, &[signing_key]); + + let tx = PublicTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_tx_public(tx).await?) + } }