418 lines
14 KiB
Rust

use std::time::Duration;
use anyhow::{Context as _, Result};
use integration_tests::{
ACC_RECEIVER, ACC_RECEIVER_PRIVATE, ACC_SENDER, ACC_SENDER_PRIVATE,
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, fetch_privacy_preserving_tx,
format_private_account_id, format_public_account_id, verify_commitment_is_in_state,
};
use log::info;
use nssa::{AccountId, program::Program};
use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point};
use tokio::test;
use wallet::cli::{
Command, SubcommandReturnValue,
account::{AccountSubcommand, NewSubcommand},
programs::native_token_transfer::AuthTransferSubcommand,
};
#[test]
async fn private_transfer_to_owned_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let from: AccountId = ACC_SENDER_PRIVATE.parse()?;
let to: AccountId = ACC_RECEIVER_PRIVATE.parse()?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(&from.to_string()),
to: Some(format_private_account_id(&to.to_string())),
to_npk: None,
to_ipk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment1 = ctx
.wallet()
.get_private_account_commitment(&from)
.context("Failed to get private account commitment for sender")?;
assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await);
let new_commitment2 = ctx
.wallet()
.get_private_account_commitment(&to)
.context("Failed to get private account commitment for receiver")?;
assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await);
info!("Successfully transferred privately to owned account");
Ok(())
}
#[test]
async fn private_transfer_to_foreign_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let from: AccountId = ACC_SENDER_PRIVATE.parse()?;
let to_npk = NullifierPublicKey([42; 32]);
let to_npk_string = hex::encode(to_npk.0);
let to_ipk = Secp256k1Point::from_scalar(to_npk.0);
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(&from.to_string()),
to: None,
to_npk: Some(to_npk_string),
to_ipk: Some(hex::encode(to_ipk.0)),
amount: 100,
});
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = result else {
anyhow::bail!("Expected PrivacyPreservingTransfer return value");
};
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment1 = ctx
.wallet()
.get_private_account_commitment(&from)
.context("Failed to get private account commitment for sender")?;
let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash.clone()).await;
assert_eq!(tx.message.new_commitments[0], new_commitment1);
assert_eq!(tx.message.new_commitments.len(), 2);
for commitment in tx.message.new_commitments.into_iter() {
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
}
info!("Successfully transferred privately to foreign account");
Ok(())
}
#[test]
async fn deshielded_transfer_to_public_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let from: AccountId = ACC_SENDER_PRIVATE.parse()?;
let to: AccountId = ACC_RECEIVER.parse()?;
// Check initial balance of the private sender
let from_acc = ctx
.wallet()
.get_account_private(&from)
.context("Failed to get sender's private account")?;
assert_eq!(from_acc.balance, 10000);
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(&from.to_string()),
to: Some(format_public_account_id(&to.to_string())),
to_npk: None,
to_ipk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let from_acc = ctx
.wallet()
.get_account_private(&from)
.context("Failed to get sender's private account")?;
let new_commitment = ctx
.wallet()
.get_private_account_commitment(&from)
.context("Failed to get private account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
let acc_2_balance = ctx
.sequencer_client()
.get_account_balance(to.to_string())
.await?;
assert_eq!(from_acc.balance, 9900);
assert_eq!(acc_2_balance.balance, 20100);
info!("Successfully deshielded transfer to public account");
Ok(())
}
#[test]
async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> {
let mut ctx = TestContext::new().await?;
let from: AccountId = ACC_SENDER_PRIVATE.parse()?;
// Create a new private account
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None }));
let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount {
account_id: to_account_id,
} = sub_ret
else {
anyhow::bail!("Expected RegisterAccount return value");
};
// Get the keys for the newly created account
let (to_keys, _) = ctx
.wallet()
.storage()
.user_data
.get_private_account(&to_account_id)
.cloned()
.context("Failed to get private account")?;
// Send to this account using claiming path (using npk and ipk instead of account ID)
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(&from.to_string()),
to: None,
to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)),
to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)),
amount: 100,
});
let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else {
anyhow::bail!("Expected PrivacyPreservingTransfer return value");
};
let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash.clone()).await;
// Sync the wallet to claim the new account
let command = Command::Account(AccountSubcommand::SyncPrivate {});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let new_commitment1 = ctx
.wallet()
.get_private_account_commitment(&from)
.context("Failed to get private account commitment for sender")?;
assert_eq!(tx.message.new_commitments[0], new_commitment1);
assert_eq!(tx.message.new_commitments.len(), 2);
for commitment in tx.message.new_commitments.into_iter() {
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
}
let to_res_acc = ctx
.wallet()
.get_account_private(&to_account_id)
.context("Failed to get recipient's private account")?;
assert_eq!(to_res_acc.balance, 100);
info!("Successfully transferred using claiming path");
Ok(())
}
#[test]
async fn shielded_transfer_to_owned_private_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let from: AccountId = ACC_SENDER.parse()?;
let to: AccountId = ACC_RECEIVER_PRIVATE.parse()?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(&from.to_string()),
to: Some(format_private_account_id(&to.to_string())),
to_npk: None,
to_ipk: None,
amount: 100,
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let acc_to = ctx
.wallet()
.get_account_private(&to)
.context("Failed to get receiver's private account")?;
let new_commitment = ctx
.wallet()
.get_private_account_commitment(&to)
.context("Failed to get receiver's commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
let acc_from_balance = ctx
.sequencer_client()
.get_account_balance(from.to_string())
.await?;
assert_eq!(acc_from_balance.balance, 9900);
assert_eq!(acc_to.balance, 20100);
info!("Successfully shielded transfer to owned private account");
Ok(())
}
#[test]
async fn shielded_transfer_to_foreign_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let to_npk = NullifierPublicKey([42; 32]);
let to_npk_string = hex::encode(to_npk.0);
let to_ipk = Secp256k1Point::from_scalar(to_npk.0);
let from: AccountId = ACC_SENDER.parse()?;
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_public_account_id(&from.to_string()),
to: None,
to_npk: Some(to_npk_string),
to_ipk: Some(hex::encode(to_ipk.0)),
amount: 100,
});
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = result else {
anyhow::bail!("Expected PrivacyPreservingTransfer return value");
};
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash).await;
let acc_1_balance = ctx
.sequencer_client()
.get_account_balance(from.to_string())
.await?;
assert!(
verify_commitment_is_in_state(
tx.message.new_commitments[0].clone(),
ctx.sequencer_client()
)
.await
);
assert_eq!(acc_1_balance.balance, 9900);
info!("Successfully shielded transfer to foreign account");
Ok(())
}
#[test]
#[ignore = "Flaky, TODO: #197"]
async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> {
let mut ctx = TestContext::new().await?;
// NOTE: This test needs refactoring - continuous run mode doesn't work well with TestContext
// The original implementation spawned wallet::cli::execute_continuous_run() in background
// but this conflicts with TestContext's wallet management
let from: AccountId = ACC_SENDER_PRIVATE.parse()?;
// Create a new private account
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None }));
let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount {
account_id: to_account_id,
} = sub_ret
else {
anyhow::bail!("Failed to register account");
};
// Get the newly created account's keys
let (to_keys, _) = ctx
.wallet()
.storage()
.user_data
.get_private_account(&to_account_id)
.cloned()
.context("Failed to get private account")?;
// Send transfer using nullifier and incoming viewing public keys
let command = Command::AuthTransfer(AuthTransferSubcommand::Send {
from: format_private_account_id(&from.to_string()),
to: None,
to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)),
to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)),
amount: 100,
});
let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else {
anyhow::bail!("Failed to send transaction");
};
let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash.clone()).await;
info!("Waiting for next blocks to check if continuous run fetches account");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
// Verify commitments are in state
assert_eq!(tx.message.new_commitments.len(), 2);
for commitment in tx.message.new_commitments.into_iter() {
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
}
// Verify receiver account balance
let to_res_acc = ctx
.wallet()
.get_account_private(&to_account_id)
.context("Failed to get receiver account")?;
assert_eq!(to_res_acc.balance, 100);
Ok(())
}
#[test]
async fn initialize_private_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None }));
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount { account_id } = result else {
anyhow::bail!("Expected RegisterAccount return value");
};
let command = Command::AuthTransfer(AuthTransferSubcommand::Init {
account_id: format_private_account_id(&account_id.to_string()),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
info!("Syncing private accounts");
let command = Command::Account(AccountSubcommand::SyncPrivate {});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let new_commitment = ctx
.wallet()
.get_private_account_commitment(&account_id)
.context("Failed to get private account commitment")?;
assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await);
let account = ctx
.wallet()
.get_account_private(&account_id)
.context("Failed to get private account")?;
assert_eq!(
account.program_owner,
Program::authenticated_transfer_program().id()
);
assert_eq!(account.balance, 0);
assert!(account.data.is_empty());
info!("Successfully initialized private account");
Ok(())
}