diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs index 06ba995..9e532df 100644 --- a/integration_tests/src/test_suite_map.rs +++ b/integration_tests/src/test_suite_map.rs @@ -437,6 +437,94 @@ pub fn prepare_function_map() -> HashMap { 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 @@ -596,6 +684,194 @@ pub fn prepare_function_map() -> HashMap { .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 @@ -679,6 +955,287 @@ pub fn prepare_function_map() -> HashMap { 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 diff --git a/wallet/src/cli/programs/native_token_transfer.rs b/wallet/src/cli/programs/native_token_transfer.rs index 9dc72ae..7868a7c 100644 --- a/wallet/src/cli/programs/native_token_transfer.rs +++ b/wallet/src/cli/programs/native_token_transfer.rs @@ -4,6 +4,7 @@ use common::transaction::NSSATransaction; use nssa::AccountId; use crate::{ + AccDecodeData::Decode, WalletCore, cli::{SubcommandReturnValue, WalletSubcommand}, helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix}, @@ -87,7 +88,7 @@ impl WalletSubcommand for AuthTransferSubcommand { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret, account_id)]; + let acc_decode_data = vec![Decode(secret, account_id)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -328,7 +329,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_from, from), (secret_to, to)]; + let acc_decode_data = vec![Decode(secret_from, from), Decode(secret_to, to)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -372,7 +373,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_from, from)]; + let acc_decode_data = vec![Decode(secret_from, from)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -412,7 +413,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret, to)]; + let acc_decode_data = vec![Decode(secret, to)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -491,7 +492,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret, from)]; + let acc_decode_data = vec![Decode(secret, from)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, diff --git a/wallet/src/cli/programs/pinata.rs b/wallet/src/cli/programs/pinata.rs index 7712a7c..e0b6570 100644 --- a/wallet/src/cli/programs/pinata.rs +++ b/wallet/src/cli/programs/pinata.rs @@ -3,6 +3,7 @@ use clap::Subcommand; use common::{PINATA_BASE58, transaction::NSSATransaction}; use crate::{ + AccDecodeData::Decode, WalletCore, cli::{SubcommandReturnValue, WalletSubcommand}, helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix}, @@ -159,7 +160,7 @@ impl WalletSubcommand for PinataProgramSubcommandPrivate { println!("Transaction data is {transfer_tx:?}"); if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_winner, winner_account_id)]; + let acc_decode_data = vec![Decode(secret_winner, winner_account_id)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, diff --git a/wallet/src/cli/programs/token.rs b/wallet/src/cli/programs/token.rs index 4480a1e..25d61ff 100644 --- a/wallet/src/cli/programs/token.rs +++ b/wallet/src/cli/programs/token.rs @@ -4,6 +4,7 @@ use common::transaction::NSSATransaction; use nssa::AccountId; use crate::{ + AccDecodeData::Decode, WalletCore, cli::{SubcommandReturnValue, WalletSubcommand}, helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix}, @@ -49,6 +50,48 @@ pub enum TokenProgramAgnosticSubcommand { #[arg(long)] amount: u128, }, + /// Burn tokens on `holder`, modify `definition`. + /// + /// `holder` is owned + /// + /// Also if `definition` is private then it is owned, because + /// we can not modify foreign accounts. + Burn { + /// definition - valid 32 byte base58 string with privacy prefix + #[arg(long)] + definition: String, + /// holder - valid 32 byte base58 string with privacy prefix + #[arg(long)] + holder: String, + /// amount - amount of balance to burn + #[arg(long)] + amount: u128, + }, + /// Mint tokens on `holder`, modify `definition`. + /// + /// `definition` is owned + /// + /// If `holder` is private, then `holder` and (`holder_npk` , `holder_ipk`) is a mutually + /// exclusive patterns. + /// + /// First is used for owned accounts, second otherwise. + Mint { + /// definition - valid 32 byte base58 string with privacy prefix + #[arg(long)] + definition: String, + /// holder - valid 32 byte base58 string with privacy prefix + #[arg(long)] + holder: Option, + /// holder_npk - valid 32 byte hex string + #[arg(long)] + holder_npk: Option, + /// to_ipk - valid 33 byte hex string + #[arg(long)] + holder_ipk: Option, + /// amount - amount of balance to mint + #[arg(long)] + amount: u128, + }, } impl WalletSubcommand for TokenProgramAgnosticSubcommand { @@ -201,6 +244,150 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand { } }; + underlying_subcommand.handle_subcommand(wallet_core).await + } + TokenProgramAgnosticSubcommand::Burn { + definition, + holder, + amount, + } => { + let underlying_subcommand = { + let (definition, definition_privacy) = + parse_addr_with_privacy_prefix(&definition)?; + let (holder, holder_privacy) = parse_addr_with_privacy_prefix(&holder)?; + + match (definition_privacy, holder_privacy) { + (AccountPrivacyKind::Public, AccountPrivacyKind::Public) => { + TokenProgramSubcommand::Public( + TokenProgramSubcommandPublic::BurnToken { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + (AccountPrivacyKind::Private, AccountPrivacyKind::Private) => { + TokenProgramSubcommand::Private( + TokenProgramSubcommandPrivate::BurnTokenPrivateOwned { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + (AccountPrivacyKind::Private, AccountPrivacyKind::Public) => { + TokenProgramSubcommand::Deshielded( + TokenProgramSubcommandDeshielded::BurnTokenDeshieldedOwned { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + (AccountPrivacyKind::Public, AccountPrivacyKind::Private) => { + TokenProgramSubcommand::Shielded( + TokenProgramSubcommandShielded::BurnTokenShielded { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + } + }; + + underlying_subcommand.handle_subcommand(wallet_core).await + } + TokenProgramAgnosticSubcommand::Mint { + definition, + holder, + holder_npk, + holder_ipk, + amount, + } => { + let underlying_subcommand = match (holder, holder_npk, holder_ipk) { + (None, None, None) => { + anyhow::bail!( + "Provide either account account_id of holder or their public keys" + ); + } + (Some(_), Some(_), Some(_)) => { + anyhow::bail!( + "Provide only one variant: either account_id of holder or their public keys" + ); + } + (_, Some(_), None) | (_, None, Some(_)) => { + anyhow::bail!("List of public keys is uncomplete"); + } + (Some(holder), None, None) => { + let (definition, definition_privacy) = + parse_addr_with_privacy_prefix(&definition)?; + let (holder, holder_privacy) = parse_addr_with_privacy_prefix(&holder)?; + + match (definition_privacy, holder_privacy) { + (AccountPrivacyKind::Public, AccountPrivacyKind::Public) => { + TokenProgramSubcommand::Public( + TokenProgramSubcommandPublic::MintToken { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + (AccountPrivacyKind::Private, AccountPrivacyKind::Private) => { + TokenProgramSubcommand::Private( + TokenProgramSubcommandPrivate::MintTokenPrivateOwned { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + (AccountPrivacyKind::Private, AccountPrivacyKind::Public) => { + TokenProgramSubcommand::Deshielded( + TokenProgramSubcommandDeshielded::MintTokenDeshielded { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + (AccountPrivacyKind::Public, AccountPrivacyKind::Private) => { + TokenProgramSubcommand::Shielded( + TokenProgramSubcommandShielded::MintTokenShieldedOwned { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + } + } + (None, Some(holder_npk), Some(holder_ipk)) => { + let (definition, definition_privacy) = + parse_addr_with_privacy_prefix(&definition)?; + + match definition_privacy { + AccountPrivacyKind::Private => TokenProgramSubcommand::Private( + TokenProgramSubcommandPrivate::MintTokenPrivateForeign { + definition_account_id: definition, + holder_npk, + holder_ipk, + amount, + }, + ), + AccountPrivacyKind::Public => TokenProgramSubcommand::Shielded( + TokenProgramSubcommandShielded::MintTokenShieldedForeign { + definition_account_id: definition, + holder_npk, + holder_ipk, + amount, + }, + ), + } + } + }; + underlying_subcommand.handle_subcommand(wallet_core).await } } @@ -239,6 +426,24 @@ pub enum TokenProgramSubcommandPublic { #[arg(short, long)] balance_to_move: u128, }, + // Burn tokens using the token program + BurnToken { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, + // Transfer tokens using the token program + MintToken { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, } /// Represents generic private CLI subcommand for a wallet working with token_program @@ -266,6 +471,35 @@ pub enum TokenProgramSubcommandPrivate { #[arg(short, long)] balance_to_move: u128, }, + // Burn tokens using the token program + BurnTokenPrivateOwned { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, + // Transfer tokens using the token program + MintTokenPrivateOwned { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, + // Transfer tokens using the token program + MintTokenPrivateForeign { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_npk: String, + #[arg(short, long)] + holder_ipk: String, + #[arg(short, long)] + amount: u128, + }, } /// Represents deshielded public CLI subcommand for a wallet working with token_program @@ -280,6 +514,24 @@ pub enum TokenProgramSubcommandDeshielded { #[arg(short, long)] balance_to_move: u128, }, + // Burn tokens using the token program + BurnTokenDeshieldedOwned { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, + // Transfer tokens using the token program + MintTokenDeshielded { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, } /// Represents generic shielded CLI subcommand for a wallet working with token_program @@ -307,6 +559,35 @@ pub enum TokenProgramSubcommandShielded { #[arg(short, long)] balance_to_move: u128, }, + // Burn tokens using the token program + BurnTokenShielded { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, + // Transfer tokens using the token program + MintTokenShieldedOwned { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, + // Transfer tokens using the token program + MintTokenShieldedForeign { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_npk: String, + #[arg(short, long)] + holder_ipk: String, + #[arg(short, long)] + amount: u128, + }, } /// Represents generic initialization subcommand for a wallet working with token_program @@ -386,6 +667,34 @@ impl WalletSubcommand for TokenProgramSubcommandPublic { .await?; Ok(SubcommandReturnValue::Empty) } + TokenProgramSubcommandPublic::BurnToken { + definition_account_id, + holder_account_id, + amount, + } => { + Token(wallet_core) + .send_burn_transaction( + definition_account_id.parse().unwrap(), + holder_account_id.parse().unwrap(), + amount, + ) + .await?; + Ok(SubcommandReturnValue::Empty) + } + TokenProgramSubcommandPublic::MintToken { + definition_account_id, + holder_account_id, + amount, + } => { + Token(wallet_core) + .send_mint_transaction( + definition_account_id.parse().unwrap(), + holder_account_id.parse().unwrap(), + amount, + ) + .await?; + Ok(SubcommandReturnValue::Empty) + } } } } @@ -421,8 +730,8 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { let acc_decode_data = vec![ - (secret_sender, sender_account_id), - (secret_recipient, recipient_account_id), + Decode(secret_sender, sender_account_id), + Decode(secret_recipient, recipient_account_id), ]; wallet_core.decode_insert_privacy_preserving_transaction_results( @@ -473,7 +782,140 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_sender, sender_account_id)]; + let acc_decode_data = vec![Decode(secret_sender, sender_account_id)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + let path = wallet_core.store_persistent_data().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandPrivate::BurnTokenPrivateOwned { + definition_account_id, + holder_account_id, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + let holder_account_id: AccountId = holder_account_id.parse().unwrap(); + + let (res, [secret_definition, secret_holder]) = Token(wallet_core) + .send_burn_transaction_private_owned_account( + definition_account_id, + holder_account_id, + amount, + ) + .await?; + + println!("Results of tx send are {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 acc_decode_data = vec![ + Decode(secret_definition, definition_account_id), + Decode(secret_holder, holder_account_id), + ]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + let path = wallet_core.store_persistent_data().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandPrivate::MintTokenPrivateOwned { + definition_account_id, + holder_account_id, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + let holder_account_id: AccountId = holder_account_id.parse().unwrap(); + + let (res, [secret_definition, secret_holder]) = Token(wallet_core) + .send_mint_transaction_private_owned_account( + definition_account_id, + holder_account_id, + amount, + ) + .await?; + + println!("Results of tx send are {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 acc_decode_data = vec![ + Decode(secret_definition, definition_account_id), + Decode(secret_holder, holder_account_id), + ]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + let path = wallet_core.store_persistent_data().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandPrivate::MintTokenPrivateForeign { + definition_account_id, + holder_npk, + holder_ipk, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + + let holder_npk_res = hex::decode(holder_npk)?; + let mut holder_npk = [0; 32]; + holder_npk.copy_from_slice(&holder_npk_res); + let holder_npk = nssa_core::NullifierPublicKey(holder_npk); + + let holder_ipk_res = hex::decode(holder_ipk)?; + let mut holder_ipk = [0u8; 33]; + holder_ipk.copy_from_slice(&holder_ipk_res); + let holder_ipk = nssa_core::encryption::shared_key_derivation::Secp256k1Point( + holder_ipk.to_vec(), + ); + + let (res, [secret_definition, _]) = Token(wallet_core) + .send_mint_transaction_private_foreign_account( + definition_account_id, + holder_npk, + holder_ipk, + amount, + ) + .await?; + + println!("Results of tx send are {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 acc_decode_data = vec![Decode(secret_definition, definition_account_id)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -521,7 +963,83 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_sender, sender_account_id)]; + let acc_decode_data = vec![Decode(secret_sender, sender_account_id)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + let path = wallet_core.store_persistent_data().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandDeshielded::BurnTokenDeshieldedOwned { + definition_account_id, + holder_account_id, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + let holder_account_id: AccountId = holder_account_id.parse().unwrap(); + + let (res, secret_definition) = Token(wallet_core) + .send_burn_transaction_deshielded_owned_account( + definition_account_id, + holder_account_id, + amount, + ) + .await?; + + println!("Results of tx send are {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 acc_decode_data = vec![Decode(secret_definition, definition_account_id)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + let path = wallet_core.store_persistent_data().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandDeshielded::MintTokenDeshielded { + definition_account_id, + holder_account_id, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + let holder_account_id: AccountId = holder_account_id.parse().unwrap(); + + let (res, secret_definition) = Token(wallet_core) + .send_mint_transaction_deshielded( + definition_account_id, + holder_account_id, + amount, + ) + .await?; + + println!("Results of tx send are {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 acc_decode_data = vec![Decode(secret_definition, definition_account_id)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -614,7 +1132,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_recipient, recipient_account_id)]; + let acc_decode_data = vec![Decode(secret_recipient, recipient_account_id)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -626,6 +1144,128 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { println!("Stored persistent accounts at {path:#?}"); + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandShielded::BurnTokenShielded { + definition_account_id, + holder_account_id, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + let holder_account_id: AccountId = holder_account_id.parse().unwrap(); + + let (res, secret_holder) = Token(wallet_core) + .send_burn_transaction_shielded( + definition_account_id, + holder_account_id, + amount, + ) + .await?; + + println!("Results of tx send are {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 acc_decode_data = vec![Decode(secret_holder, holder_account_id)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + let path = wallet_core.store_persistent_data().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandShielded::MintTokenShieldedOwned { + definition_account_id, + holder_account_id, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + let holder_account_id: AccountId = holder_account_id.parse().unwrap(); + + let (res, secret_holder) = Token(wallet_core) + .send_mint_transaction_shielded_owned_account( + definition_account_id, + holder_account_id, + amount, + ) + .await?; + + println!("Results of tx send are {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 acc_decode_data = vec![Decode(secret_holder, holder_account_id)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + let path = wallet_core.store_persistent_data().await?; + + println!("Stored persistent accounts at {path:#?}"); + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandShielded::MintTokenShieldedForeign { + definition_account_id, + holder_npk, + holder_ipk, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + + let holder_npk_res = hex::decode(holder_npk)?; + let mut holder_npk = [0; 32]; + holder_npk.copy_from_slice(&holder_npk_res); + let holder_npk = nssa_core::NullifierPublicKey(holder_npk); + + let holder_ipk_res = hex::decode(holder_ipk)?; + let mut holder_ipk = [0u8; 33]; + holder_ipk.copy_from_slice(&holder_ipk_res); + let holder_ipk = nssa_core::encryption::shared_key_derivation::Secp256k1Point( + holder_ipk.to_vec(), + ); + + let (res, _) = Token(wallet_core) + .send_mint_transaction_shielded_foreign_account( + definition_account_id, + holder_npk, + holder_ipk, + amount, + ) + .await?; + + println!("Results of tx send are {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 { + println!("Transaction data is {:?}", tx.message); + } + + let path = wallet_core.store_persistent_data().await?; + + println!("Stored persistent accounts at {path:#?}"); + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } } @@ -673,8 +1313,8 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { let acc_decode_data = vec![ - (secret_definition, definition_account_id), - (secret_supply, supply_account_id), + Decode(secret_definition, definition_account_id), + Decode(secret_supply, supply_account_id), ]; wallet_core.decode_insert_privacy_preserving_transaction_results( @@ -723,7 +1363,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_definition, definition_account_id)]; + let acc_decode_data = vec![Decode(secret_definition, definition_account_id)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -771,7 +1411,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_supply, supply_account_id)]; + let acc_decode_data = vec![Decode(secret_supply, supply_account_id)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 7cab837..c6bb8c2 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -40,6 +40,11 @@ pub mod poller; mod privacy_preserving_tx; pub mod program_facades; +pub enum AccDecodeData { + Skip, + Decode(nssa_core::SharedSecretKey, AccountId), +} + pub struct WalletCore { pub storage: WalletChainStore, pub poller: TxPoller, @@ -220,24 +225,29 @@ impl WalletCore { pub fn decode_insert_privacy_preserving_transaction_results( &mut self, tx: nssa::privacy_preserving_transaction::PrivacyPreservingTransaction, - acc_decode_data: &[(nssa_core::SharedSecretKey, AccountId)], + acc_decode_mask: &[AccDecodeData], ) -> Result<()> { - for (output_index, (secret, acc_account_id)) in acc_decode_data.iter().enumerate() { - let acc_ead = tx.message.encrypted_private_post_states[output_index].clone(); - let acc_comm = tx.message.new_commitments[output_index].clone(); + for (output_index, acc_decode_data) in acc_decode_mask.iter().enumerate() { + match acc_decode_data { + AccDecodeData::Decode(secret, acc_account_id) => { + let acc_ead = tx.message.encrypted_private_post_states[output_index].clone(); + let acc_comm = tx.message.new_commitments[output_index].clone(); - let res_acc = nssa_core::EncryptionScheme::decrypt( - &acc_ead.ciphertext, - secret, - &acc_comm, - output_index as u32, - ) - .unwrap(); + let res_acc = nssa_core::EncryptionScheme::decrypt( + &acc_ead.ciphertext, + secret, + &acc_comm, + output_index as u32, + ) + .unwrap(); - println!("Received new acc {res_acc:#?}"); + println!("Received new acc {res_acc:#?}"); - self.storage - .insert_private_account_data(*acc_account_id, res_acc); + self.storage + .insert_private_account_data(*acc_account_id, res_acc); + } + AccDecodeData::Skip => {} + } } println!("Transaction data is {:?}", tx.message); diff --git a/wallet/src/program_facades/token.rs b/wallet/src/program_facades/token.rs index 1c8a899..fc03a0a 100644 --- a/wallet/src/program_facades/token.rs +++ b/wallet/src/program_facades/token.rs @@ -1,9 +1,6 @@ use common::{error::ExecutionFailureKind, rpc_primitives::requests::SendTxResponse}; use nssa::{AccountId, program::Program}; -use nssa_core::{ - NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey, - program::InstructionData, -}; +use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey}; use crate::{PrivacyPreservingAccount, WalletCore}; @@ -45,7 +42,9 @@ impl Token<'_> { name: [u8; 6], total_supply: u128, ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_definition(name, total_supply); + let instruction = token_program_preparation_definition(name, total_supply); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -54,7 +53,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(supply_account_id), ], &instruction_data, - &program.into(), + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -73,7 +72,9 @@ impl Token<'_> { name: [u8; 6], total_supply: u128, ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_definition(name, total_supply); + let instruction = token_program_preparation_definition(name, total_supply); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -82,7 +83,7 @@ impl Token<'_> { PrivacyPreservingAccount::Public(supply_account_id), ], &instruction_data, - &program.into(), + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -101,7 +102,9 @@ impl Token<'_> { name: [u8; 6], total_supply: u128, ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_definition(name, total_supply); + let instruction = token_program_preparation_definition(name, total_supply); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -110,7 +113,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(supply_account_id), ], &instruction_data, - &program.into(), + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -167,7 +170,9 @@ impl Token<'_> { recipient_account_id: AccountId, amount: u128, ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_transfer(amount); + let instruction = token_program_preparation_transfer(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -176,7 +181,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(recipient_account_id), ], &instruction_data, - &program.into(), + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -194,7 +199,9 @@ impl Token<'_> { recipient_ipk: IncomingViewingPublicKey, amount: u128, ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_transfer(amount); + let instruction = token_program_preparation_transfer(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -206,7 +213,7 @@ impl Token<'_> { }, ], &instruction_data, - &program.into(), + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -223,7 +230,9 @@ impl Token<'_> { recipient_account_id: AccountId, amount: u128, ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_transfer(amount); + let instruction = token_program_preparation_transfer(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -232,7 +241,7 @@ impl Token<'_> { PrivacyPreservingAccount::Public(recipient_account_id), ], &instruction_data, - &program.into(), + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -250,7 +259,9 @@ impl Token<'_> { recipient_account_id: AccountId, amount: u128, ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_transfer(amount); + let instruction = token_program_preparation_transfer(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -259,7 +270,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(recipient_account_id), ], &instruction_data, - &program.into(), + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -278,7 +289,9 @@ impl Token<'_> { recipient_ipk: IncomingViewingPublicKey, amount: u128, ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_transfer(amount); + let instruction = token_program_preparation_transfer(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -290,7 +303,7 @@ impl Token<'_> { }, ], &instruction_data, - &program.into(), + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -301,30 +314,354 @@ impl Token<'_> { (resp, first) }) } + + pub async fn send_burn_transaction( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result { + let account_ids = vec![definition_account_id, holder_account_id]; + let instruction = token_program_preparation_burn(amount); + + let Ok(nonces) = self.0.get_accounts_nonces(vec![holder_account_id]).await else { + return Err(ExecutionFailureKind::SequencerError); + }; + let message = nssa::public_transaction::Message::try_new( + Program::token().id(), + account_ids, + nonces, + instruction, + ) + .expect("Instruction should serialize"); + + let signing_key = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&holder_account_id) + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; + let witness_set = + nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]); + + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.0.sequencer_client.send_tx_public(tx).await?) + } + + pub async fn send_burn_transaction_private_owned_account( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let instruction = token_program_preparation_burn(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(definition_account_id), + PrivacyPreservingAccount::PrivateOwned(holder_account_id), + ], + &instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let mut iter = secrets.into_iter(); + let first = iter.next().expect("expected definition's secret"); + let second = iter.next().expect("expected holder's secret"); + (resp, [first, second]) + }) + } + + pub async fn send_burn_transaction_deshielded_owned_account( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let instruction = token_program_preparation_burn(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(definition_account_id), + PrivacyPreservingAccount::Public(holder_account_id), + ], + &instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected definition's secret"); + (resp, first) + }) + } + + pub async fn send_burn_transaction_shielded( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let instruction = token_program_preparation_burn(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(definition_account_id), + PrivacyPreservingAccount::PrivateOwned(holder_account_id), + ], + &instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected holder's secret"); + (resp, first) + }) + } + + pub async fn send_mint_transaction( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result { + let account_ids = vec![definition_account_id, holder_account_id]; + let instruction = token_program_preparation_mint(amount); + + let Ok(nonces) = self + .0 + .get_accounts_nonces(vec![definition_account_id]) + .await + else { + return Err(ExecutionFailureKind::SequencerError); + }; + let message = nssa::public_transaction::Message::try_new( + Program::token().id(), + account_ids, + nonces, + instruction, + ) + .unwrap(); + + let Some(signing_key) = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&definition_account_id) + 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.0.sequencer_client.send_tx_public(tx).await?) + } + + pub async fn send_mint_transaction_private_owned_account( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let instruction = token_program_preparation_mint(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(definition_account_id), + PrivacyPreservingAccount::PrivateOwned(holder_account_id), + ], + &instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let mut iter = secrets.into_iter(); + let first = iter.next().expect("expected definition's secret"); + let second = iter.next().expect("expected holder's secret"); + (resp, [first, second]) + }) + } + + pub async fn send_mint_transaction_private_foreign_account( + &self, + definition_account_id: AccountId, + holder_npk: NullifierPublicKey, + holder_ipk: IncomingViewingPublicKey, + amount: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let instruction = token_program_preparation_mint(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(definition_account_id), + PrivacyPreservingAccount::PrivateForeign { + npk: holder_npk, + ipk: holder_ipk, + }, + ], + &instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let mut iter = secrets.into_iter(); + let first = iter.next().expect("expected definition's secret"); + let second = iter.next().expect("expected holder's secret"); + (resp, [first, second]) + }) + } + + pub async fn send_mint_transaction_deshielded( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let instruction = token_program_preparation_mint(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(definition_account_id), + PrivacyPreservingAccount::Public(holder_account_id), + ], + &instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected definition's secret"); + (resp, first) + }) + } + + pub async fn send_mint_transaction_shielded_owned_account( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let instruction = token_program_preparation_mint(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(definition_account_id), + PrivacyPreservingAccount::PrivateOwned(holder_account_id), + ], + &instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected holder's secret"); + (resp, first) + }) + } + + pub async fn send_mint_transaction_shielded_foreign_account( + &self, + definition_account_id: AccountId, + holder_npk: NullifierPublicKey, + holder_ipk: IncomingViewingPublicKey, + amount: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let instruction = token_program_preparation_mint(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(definition_account_id), + PrivacyPreservingAccount::PrivateForeign { + npk: holder_npk, + ipk: holder_ipk, + }, + ], + &instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected holder's secret"); + (resp, first) + }) + } } -fn token_program_preparation_transfer(amount: u128) -> (InstructionData, Program) { +fn token_program_preparation_transfer(amount: u128) -> Vec { // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || // 0x00 || 0x00 || 0x00]. let mut instruction = vec![0u8; 23]; instruction[0] = 0x01; instruction[1..17].copy_from_slice(&amount.to_le_bytes()); - let instruction_data = Program::serialize_instruction(instruction).unwrap(); - let program = Program::token(); - (instruction_data, program) + instruction } -fn token_program_preparation_definition( - name: [u8; 6], - total_supply: u128, -) -> (InstructionData, Program) { +fn token_program_preparation_definition(name: [u8; 6], total_supply: u128) -> Vec { // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] let mut instruction = vec![0u8; 23]; instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); instruction[17..].copy_from_slice(&name); - let instruction_data = Program::serialize_instruction(instruction).unwrap(); - let program = Program::token(); - (instruction_data, program) + instruction +} + +fn token_program_preparation_burn(amount: u128) -> Vec { + // Instruction must be: [0x03 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || + // 0x00 || 0x00 || 0x00]. + let mut instruction = vec![0; 23]; + instruction[0] = 0x03; + instruction[1..17].copy_from_slice(&amount.to_le_bytes()); + + instruction +} + +fn token_program_preparation_mint(amount: u128) -> Vec { + // Instruction must be: [0x04 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || + // 0x00 || 0x00 || 0x00]. + let mut instruction = vec![0; 23]; + instruction[0] = 0x04; + instruction[1..17].copy_from_slice(&amount.to_le_bytes()); + + instruction }