|
|
@@ -1,676 +1,347 @@
|
|
|
-mod program_test;
|
|
|
-
|
|
|
use {
|
|
|
- program_test::{program_test, program_test_2022},
|
|
|
- solana_program::pubkey::Pubkey,
|
|
|
- solana_program_test::*,
|
|
|
- solana_sdk::{
|
|
|
- instruction::{AccountMeta, InstructionError},
|
|
|
- signature::Signer,
|
|
|
- signer::keypair::Keypair,
|
|
|
- transaction::{Transaction, TransactionError},
|
|
|
- },
|
|
|
- solana_system_interface::instruction as system_instruction,
|
|
|
+ ata_mollusk_harness::AtaTestHarness,
|
|
|
+ mollusk_svm::result::Check,
|
|
|
+ solana_instruction::AccountMeta,
|
|
|
+ solana_program_error::ProgramError,
|
|
|
+ solana_pubkey::Pubkey,
|
|
|
spl_associated_token_account_interface::{
|
|
|
address::get_associated_token_address_with_program_id, instruction,
|
|
|
},
|
|
|
- spl_token_2022_interface::{
|
|
|
- extension::{ExtensionType, StateWithExtensionsOwned},
|
|
|
- state::{Account, Mint},
|
|
|
- },
|
|
|
+ spl_token_2022_interface::extension::StateWithExtensionsOwned,
|
|
|
};
|
|
|
|
|
|
-async fn create_mint(context: &mut ProgramTestContext, program_id: &Pubkey) -> (Pubkey, Keypair) {
|
|
|
- let mint_account = Keypair::new();
|
|
|
- let token_mint_address = mint_account.pubkey();
|
|
|
- let mint_authority = Keypair::new();
|
|
|
- let space = ExtensionType::try_calculate_account_len::<Mint>(&[]).unwrap();
|
|
|
- let rent = context.banks_client.get_rent().await.unwrap();
|
|
|
- let transaction = Transaction::new_signed_with_payer(
|
|
|
+const TEST_MINT_AMOUNT: u64 = 100;
|
|
|
+
|
|
|
+fn test_recover_nested_same_mint(program_id: &Pubkey) {
|
|
|
+ let mut harness = AtaTestHarness::new(program_id)
|
|
|
+ .with_wallet(1_000_000)
|
|
|
+ .with_mint(0)
|
|
|
+ .with_ata();
|
|
|
+
|
|
|
+ let mint = harness.mint.unwrap();
|
|
|
+ let owner_ata = harness.ata_address.unwrap();
|
|
|
+
|
|
|
+ // Create nested ATA and mint tokens to it (not to the main, canonical ATA)
|
|
|
+ let nested_ata = harness.create_ata_for_owner(owner_ata, 1_000_000);
|
|
|
+ harness.mint_tokens_to(nested_ata, TEST_MINT_AMOUNT);
|
|
|
+
|
|
|
+ // Capture pre-state for lamports transfer validation
|
|
|
+ let wallet_pubkey = harness.wallet.unwrap();
|
|
|
+ let pre_wallet_lamports = {
|
|
|
+ let store = harness.ctx.account_store.borrow();
|
|
|
+ store.get(&wallet_pubkey).unwrap().lamports
|
|
|
+ };
|
|
|
+ let nested_lamports = harness.get_account(nested_ata).lamports;
|
|
|
+
|
|
|
+ // Build and execute recover instruction
|
|
|
+ let recover_instruction = harness.build_recover_nested_instruction(mint, mint);
|
|
|
+ harness.ctx.process_and_validate_instruction(
|
|
|
+ &recover_instruction,
|
|
|
&[
|
|
|
- system_instruction::create_account(
|
|
|
- &context.payer.pubkey(),
|
|
|
- &mint_account.pubkey(),
|
|
|
- rent.minimum_balance(space),
|
|
|
- space as u64,
|
|
|
- program_id,
|
|
|
- ),
|
|
|
- spl_token_2022_interface::instruction::initialize_mint(
|
|
|
- program_id,
|
|
|
- &token_mint_address,
|
|
|
- &mint_authority.pubkey(),
|
|
|
- Some(&mint_authority.pubkey()),
|
|
|
- 0,
|
|
|
- )
|
|
|
- .unwrap(),
|
|
|
+ Check::success(),
|
|
|
+ // Wallet received nested account lamports
|
|
|
+ Check::account(&wallet_pubkey)
|
|
|
+ .lamports(pre_wallet_lamports.checked_add(nested_lamports).unwrap())
|
|
|
+ .build(),
|
|
|
+ // Nested account has no lamports
|
|
|
+ Check::account(&nested_ata).lamports(0).build(),
|
|
|
+ // Nested account is closed
|
|
|
+ Check::account(&nested_ata).closed().build(),
|
|
|
],
|
|
|
- Some(&context.payer.pubkey()),
|
|
|
- &[&context.payer, &mint_account],
|
|
|
- context.last_blockhash,
|
|
|
);
|
|
|
- context
|
|
|
- .banks_client
|
|
|
- .process_transaction(transaction)
|
|
|
- .await
|
|
|
- .unwrap();
|
|
|
- (token_mint_address, mint_authority)
|
|
|
+
|
|
|
+ // Validate the recovery worked - tokens should be in the destination ATA (owner_ata)
|
|
|
+ let destination_account = harness.get_account(owner_ata);
|
|
|
+ let destination_amount =
|
|
|
+ StateWithExtensionsOwned::<spl_token_2022_interface::state::Account>::unpack(
|
|
|
+ destination_account.data,
|
|
|
+ )
|
|
|
+ .unwrap()
|
|
|
+ .base
|
|
|
+ .amount;
|
|
|
+ assert_eq!(destination_amount, TEST_MINT_AMOUNT);
|
|
|
}
|
|
|
|
|
|
-async fn create_associated_token_account(
|
|
|
- context: &mut ProgramTestContext,
|
|
|
- owner: &Pubkey,
|
|
|
- mint: &Pubkey,
|
|
|
- program_id: &Pubkey,
|
|
|
-) -> Pubkey {
|
|
|
- let transaction = Transaction::new_signed_with_payer(
|
|
|
- &[instruction::create_associated_token_account(
|
|
|
- &context.payer.pubkey(),
|
|
|
- owner,
|
|
|
- mint,
|
|
|
- program_id,
|
|
|
- )],
|
|
|
- Some(&context.payer.pubkey()),
|
|
|
- &[&context.payer],
|
|
|
- context.last_blockhash,
|
|
|
- );
|
|
|
- context
|
|
|
- .banks_client
|
|
|
- .process_transaction(transaction)
|
|
|
- .await
|
|
|
- .unwrap();
|
|
|
+fn test_fail_missing_wallet_signature(token_program_id: &Pubkey) {
|
|
|
+ let mut harness = AtaTestHarness::new(token_program_id)
|
|
|
+ .with_wallet(1_000_000)
|
|
|
+ .with_mint(0)
|
|
|
+ .with_ata();
|
|
|
|
|
|
- get_associated_token_address_with_program_id(owner, mint, program_id)
|
|
|
-}
|
|
|
+ let mint = harness.mint.unwrap();
|
|
|
+ let owner_ata = harness.ata_address.unwrap();
|
|
|
+ let nested_ata = harness.create_ata_for_owner(owner_ata, 1_000_000);
|
|
|
+ harness.mint_tokens_to(nested_ata, TEST_MINT_AMOUNT);
|
|
|
|
|
|
-#[allow(clippy::too_many_arguments)]
|
|
|
-async fn try_recover_nested(
|
|
|
- context: &mut ProgramTestContext,
|
|
|
- program_id: &Pubkey,
|
|
|
- nested_mint: Pubkey,
|
|
|
- nested_mint_authority: Keypair,
|
|
|
- nested_associated_token_address: Pubkey,
|
|
|
- destination_token_address: Pubkey,
|
|
|
- wallet: Keypair,
|
|
|
- recover_transaction: Transaction,
|
|
|
- expected_error: Option<InstructionError>,
|
|
|
-) {
|
|
|
- let nested_account = context
|
|
|
- .banks_client
|
|
|
- .get_account(nested_associated_token_address)
|
|
|
- .await
|
|
|
- .unwrap()
|
|
|
- .unwrap();
|
|
|
- let lamports = nested_account.lamports;
|
|
|
-
|
|
|
- // mint to nested account
|
|
|
- let amount = 100;
|
|
|
- let transaction = Transaction::new_signed_with_payer(
|
|
|
- &[spl_token_2022_interface::instruction::mint_to(
|
|
|
- program_id,
|
|
|
- &nested_mint,
|
|
|
- &nested_associated_token_address,
|
|
|
- &nested_mint_authority.pubkey(),
|
|
|
- &[],
|
|
|
- amount,
|
|
|
- )
|
|
|
- .unwrap()],
|
|
|
- Some(&context.payer.pubkey()),
|
|
|
- &[&context.payer, &nested_mint_authority],
|
|
|
- context.last_blockhash,
|
|
|
+ let mut recover_instruction = harness.build_recover_nested_instruction(mint, mint);
|
|
|
+ recover_instruction.accounts[5] = AccountMeta::new(harness.wallet.unwrap(), false);
|
|
|
+
|
|
|
+ harness.ctx.process_and_validate_instruction(
|
|
|
+ &recover_instruction,
|
|
|
+ &[Check::err(ProgramError::MissingRequiredSignature)],
|
|
|
);
|
|
|
- context
|
|
|
- .banks_client
|
|
|
- .process_transaction(transaction)
|
|
|
- .await
|
|
|
- .unwrap();
|
|
|
-
|
|
|
- // transfer / close nested account
|
|
|
- let result = context
|
|
|
- .banks_client
|
|
|
- .process_transaction(recover_transaction)
|
|
|
- .await;
|
|
|
-
|
|
|
- if let Some(expected_error) = expected_error {
|
|
|
- let error = result.unwrap_err().unwrap();
|
|
|
- assert_eq!(error, TransactionError::InstructionError(0, expected_error));
|
|
|
- } else {
|
|
|
- result.unwrap();
|
|
|
- // nested account is gone
|
|
|
- assert!(context
|
|
|
- .banks_client
|
|
|
- .get_account(nested_associated_token_address)
|
|
|
- .await
|
|
|
- .unwrap()
|
|
|
- .is_none());
|
|
|
- let destination_account = context
|
|
|
- .banks_client
|
|
|
- .get_account(destination_token_address)
|
|
|
- .await
|
|
|
- .unwrap()
|
|
|
- .unwrap();
|
|
|
- let destination_state =
|
|
|
- StateWithExtensionsOwned::<Account>::unpack(destination_account.data).unwrap();
|
|
|
- assert_eq!(destination_state.base.amount, amount);
|
|
|
- let wallet_account = context
|
|
|
- .banks_client
|
|
|
- .get_account(wallet.pubkey())
|
|
|
- .await
|
|
|
- .unwrap()
|
|
|
- .unwrap();
|
|
|
- assert_eq!(wallet_account.lamports, lamports);
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
-async fn check_same_mint(context: &mut ProgramTestContext, program_id: &Pubkey) {
|
|
|
- let wallet = Keypair::new();
|
|
|
- let (mint, mint_authority) = create_mint(context, program_id).await;
|
|
|
+fn test_fail_wrong_signer(token_program_id: &Pubkey) {
|
|
|
+ let mut harness = AtaTestHarness::new(token_program_id)
|
|
|
+ .with_wallet(1_000_000)
|
|
|
+ .with_mint(0)
|
|
|
+ .with_ata();
|
|
|
|
|
|
- let owner_associated_token_address =
|
|
|
- create_associated_token_account(context, &wallet.pubkey(), &mint, program_id).await;
|
|
|
- let nested_associated_token_address = create_associated_token_account(
|
|
|
- context,
|
|
|
- &owner_associated_token_address,
|
|
|
- &mint,
|
|
|
- program_id,
|
|
|
- )
|
|
|
- .await;
|
|
|
-
|
|
|
- context.last_blockhash = context
|
|
|
- .banks_client
|
|
|
- .get_new_latest_blockhash(&context.last_blockhash)
|
|
|
- .await
|
|
|
- .unwrap();
|
|
|
- let transaction = Transaction::new_signed_with_payer(
|
|
|
- &[instruction::recover_nested(
|
|
|
- &wallet.pubkey(),
|
|
|
- &mint,
|
|
|
- &mint,
|
|
|
- program_id,
|
|
|
- )],
|
|
|
- Some(&context.payer.pubkey()),
|
|
|
- &[&context.payer, &wallet],
|
|
|
- context.last_blockhash,
|
|
|
+ let mint = harness.mint.unwrap();
|
|
|
+ let owner_ata = harness.ata_address.unwrap();
|
|
|
+ let nested_ata = harness.create_ata_for_owner(owner_ata, 1_000_000);
|
|
|
+ harness.mint_tokens_to(nested_ata, TEST_MINT_AMOUNT);
|
|
|
+
|
|
|
+ let wrong_wallet = Pubkey::new_unique();
|
|
|
+ harness.create_ata_for_owner(wrong_wallet, 1_000_000);
|
|
|
+
|
|
|
+ let recover_instruction =
|
|
|
+ instruction::recover_nested(&wrong_wallet, &mint, &mint, token_program_id);
|
|
|
+
|
|
|
+ harness.ctx.process_and_validate_instruction(
|
|
|
+ &recover_instruction,
|
|
|
+ &[Check::err(ProgramError::IllegalOwner)],
|
|
|
);
|
|
|
- try_recover_nested(
|
|
|
- context,
|
|
|
- program_id,
|
|
|
- mint,
|
|
|
- mint_authority,
|
|
|
- nested_associated_token_address,
|
|
|
- owner_associated_token_address,
|
|
|
- wallet,
|
|
|
- transaction,
|
|
|
- None,
|
|
|
- )
|
|
|
- .await;
|
|
|
}
|
|
|
|
|
|
-#[tokio::test]
|
|
|
-async fn success_same_mint_2022() {
|
|
|
- let dummy_mint = Pubkey::new_unique();
|
|
|
- let pt = program_test_2022(dummy_mint);
|
|
|
- let mut context = pt.start_with_context().await;
|
|
|
- check_same_mint(&mut context, &spl_token_2022_interface::id()).await;
|
|
|
-}
|
|
|
+fn test_fail_not_nested(token_program_id: &Pubkey) {
|
|
|
+ let mut harness = AtaTestHarness::new(token_program_id)
|
|
|
+ .with_wallet(1_000_000)
|
|
|
+ .with_mint(0)
|
|
|
+ .with_ata();
|
|
|
|
|
|
-#[tokio::test]
|
|
|
-async fn success_same_mint() {
|
|
|
- let dummy_mint = Pubkey::new_unique();
|
|
|
- let pt = program_test(dummy_mint);
|
|
|
- let mut context = pt.start_with_context().await;
|
|
|
- check_same_mint(&mut context, &spl_token_interface::id()).await;
|
|
|
-}
|
|
|
+ let mint = harness.mint.unwrap();
|
|
|
+ let wrong_wallet = Pubkey::new_unique();
|
|
|
+
|
|
|
+ let nested_ata = harness.create_ata_for_owner(wrong_wallet, 0);
|
|
|
+ harness.mint_tokens_to(nested_ata, TEST_MINT_AMOUNT);
|
|
|
|
|
|
-async fn check_different_mints(context: &mut ProgramTestContext, program_id: &Pubkey) {
|
|
|
- let wallet = Keypair::new();
|
|
|
- let (owner_mint, _owner_mint_authority) = create_mint(context, program_id).await;
|
|
|
- let (nested_mint, nested_mint_authority) = create_mint(context, program_id).await;
|
|
|
-
|
|
|
- let owner_associated_token_address =
|
|
|
- create_associated_token_account(context, &wallet.pubkey(), &owner_mint, program_id).await;
|
|
|
- let nested_associated_token_address = create_associated_token_account(
|
|
|
- context,
|
|
|
- &owner_associated_token_address,
|
|
|
- &nested_mint,
|
|
|
- program_id,
|
|
|
- )
|
|
|
- .await;
|
|
|
- let destination_token_address =
|
|
|
- create_associated_token_account(context, &wallet.pubkey(), &nested_mint, program_id).await;
|
|
|
-
|
|
|
- context.last_blockhash = context
|
|
|
- .banks_client
|
|
|
- .get_new_latest_blockhash(&context.last_blockhash)
|
|
|
- .await
|
|
|
- .unwrap();
|
|
|
- let transaction = Transaction::new_signed_with_payer(
|
|
|
- &[instruction::recover_nested(
|
|
|
- &wallet.pubkey(),
|
|
|
- &owner_mint,
|
|
|
- &nested_mint,
|
|
|
- program_id,
|
|
|
- )],
|
|
|
- Some(&context.payer.pubkey()),
|
|
|
- &[&context.payer, &wallet],
|
|
|
- context.last_blockhash,
|
|
|
+ let recover_instruction = harness.build_recover_nested_instruction(mint, mint);
|
|
|
+ harness.ctx.process_and_validate_instruction(
|
|
|
+ &recover_instruction,
|
|
|
+ &[Check::err(ProgramError::IllegalOwner)],
|
|
|
);
|
|
|
- try_recover_nested(
|
|
|
- context,
|
|
|
- program_id,
|
|
|
- nested_mint,
|
|
|
- nested_mint_authority,
|
|
|
- nested_associated_token_address,
|
|
|
- destination_token_address,
|
|
|
- wallet,
|
|
|
- transaction,
|
|
|
- None,
|
|
|
- )
|
|
|
- .await;
|
|
|
}
|
|
|
|
|
|
-#[tokio::test]
|
|
|
-async fn success_different_mints() {
|
|
|
- let dummy_mint = Pubkey::new_unique();
|
|
|
- let pt = program_test(dummy_mint);
|
|
|
- let mut context = pt.start_with_context().await;
|
|
|
- check_different_mints(&mut context, &spl_token_interface::id()).await;
|
|
|
-}
|
|
|
+fn test_fail_wrong_address_derivation_owner(token_program_id: &Pubkey) {
|
|
|
+ let mut harness = AtaTestHarness::new(token_program_id)
|
|
|
+ .with_wallet(1_000_000)
|
|
|
+ .with_mint(0)
|
|
|
+ .with_ata();
|
|
|
|
|
|
-#[tokio::test]
|
|
|
-async fn success_different_mints_2022() {
|
|
|
- let dummy_mint = Pubkey::new_unique();
|
|
|
- let pt = program_test_2022(dummy_mint);
|
|
|
- let mut context = pt.start_with_context().await;
|
|
|
- check_different_mints(&mut context, &spl_token_2022_interface::id()).await;
|
|
|
-}
|
|
|
+ let mint = harness.mint.unwrap();
|
|
|
+ let owner_ata = harness.ata_address.unwrap();
|
|
|
+ let nested_ata = harness.create_ata_for_owner(owner_ata, 1_000_000);
|
|
|
+ harness.mint_tokens_to(nested_ata, TEST_MINT_AMOUNT);
|
|
|
|
|
|
-async fn check_missing_wallet_signature(context: &mut ProgramTestContext, program_id: &Pubkey) {
|
|
|
- let wallet = Keypair::new();
|
|
|
- let (mint, mint_authority) = create_mint(context, program_id).await;
|
|
|
+ let mut recover_instruction = harness.build_recover_nested_instruction(mint, mint);
|
|
|
+ let wrong_owner_address = Pubkey::new_unique();
|
|
|
+ recover_instruction.accounts[3] = AccountMeta::new_readonly(wrong_owner_address, false);
|
|
|
|
|
|
- let owner_associated_token_address =
|
|
|
- create_associated_token_account(context, &wallet.pubkey(), &mint, program_id).await;
|
|
|
+ harness.ensure_accounts_with_lamports(&[(wrong_owner_address, 1_000_000)]);
|
|
|
|
|
|
- let nested_associated_token_address = create_associated_token_account(
|
|
|
- context,
|
|
|
- &owner_associated_token_address,
|
|
|
- &mint,
|
|
|
- program_id,
|
|
|
- )
|
|
|
- .await;
|
|
|
-
|
|
|
- let mut recover = instruction::recover_nested(&wallet.pubkey(), &mint, &mint, program_id);
|
|
|
- recover.accounts[5] = AccountMeta::new(wallet.pubkey(), false);
|
|
|
- context.last_blockhash = context
|
|
|
- .banks_client
|
|
|
- .get_new_latest_blockhash(&context.last_blockhash)
|
|
|
- .await
|
|
|
- .unwrap();
|
|
|
- let transaction = Transaction::new_signed_with_payer(
|
|
|
- &[recover],
|
|
|
- Some(&context.payer.pubkey()),
|
|
|
- &[&context.payer],
|
|
|
- context.last_blockhash,
|
|
|
+ harness.ctx.process_and_validate_instruction(
|
|
|
+ &recover_instruction,
|
|
|
+ &[Check::err(ProgramError::InvalidSeeds)],
|
|
|
);
|
|
|
- try_recover_nested(
|
|
|
- context,
|
|
|
- program_id,
|
|
|
- mint,
|
|
|
- mint_authority,
|
|
|
- nested_associated_token_address,
|
|
|
- owner_associated_token_address,
|
|
|
- wallet,
|
|
|
- transaction,
|
|
|
- Some(InstructionError::MissingRequiredSignature),
|
|
|
- )
|
|
|
- .await;
|
|
|
}
|
|
|
|
|
|
-#[tokio::test]
|
|
|
-async fn fail_missing_wallet_signature_2022() {
|
|
|
- let dummy_mint = Pubkey::new_unique();
|
|
|
- let pt = program_test_2022(dummy_mint);
|
|
|
- let mut context = pt.start_with_context().await;
|
|
|
- check_missing_wallet_signature(&mut context, &spl_token_2022_interface::id()).await;
|
|
|
+#[test]
|
|
|
+fn success_same_mint_2022() {
|
|
|
+ test_recover_nested_same_mint(&spl_token_2022_interface::id());
|
|
|
}
|
|
|
|
|
|
-#[tokio::test]
|
|
|
-async fn fail_missing_wallet_signature() {
|
|
|
- let dummy_mint = Pubkey::new_unique();
|
|
|
- let pt = program_test(dummy_mint);
|
|
|
- let mut context = pt.start_with_context().await;
|
|
|
- check_missing_wallet_signature(&mut context, &spl_token_interface::id()).await;
|
|
|
+#[test]
|
|
|
+fn success_same_mint() {
|
|
|
+ test_recover_nested_same_mint(&spl_token_interface::id());
|
|
|
}
|
|
|
|
|
|
-async fn check_wrong_signer(context: &mut ProgramTestContext, program_id: &Pubkey) {
|
|
|
- let wallet = Keypair::new();
|
|
|
- let wrong_wallet = Keypair::new();
|
|
|
- let (mint, mint_authority) = create_mint(context, program_id).await;
|
|
|
-
|
|
|
- let owner_associated_token_address =
|
|
|
- create_associated_token_account(context, &wallet.pubkey(), &mint, program_id).await;
|
|
|
- let nested_associated_token_address = create_associated_token_account(
|
|
|
- context,
|
|
|
- &owner_associated_token_address,
|
|
|
- &mint,
|
|
|
- program_id,
|
|
|
- )
|
|
|
- .await;
|
|
|
-
|
|
|
- context.last_blockhash = context
|
|
|
- .banks_client
|
|
|
- .get_new_latest_blockhash(&context.last_blockhash)
|
|
|
- .await
|
|
|
- .unwrap();
|
|
|
- let transaction = Transaction::new_signed_with_payer(
|
|
|
- &[instruction::recover_nested(
|
|
|
- &wrong_wallet.pubkey(),
|
|
|
- &mint,
|
|
|
- &mint,
|
|
|
- program_id,
|
|
|
- )],
|
|
|
- Some(&context.payer.pubkey()),
|
|
|
- &[&context.payer, &wrong_wallet],
|
|
|
- context.last_blockhash,
|
|
|
+fn test_recover_nested_different_mints(program_id: &Pubkey) {
|
|
|
+ let harness = AtaTestHarness::new(program_id)
|
|
|
+ .with_wallet(1_000_000)
|
|
|
+ .with_mint(0)
|
|
|
+ .with_ata();
|
|
|
+
|
|
|
+ let owner_mint = harness.mint.unwrap();
|
|
|
+ let owner_ata = harness.ata_address.unwrap();
|
|
|
+
|
|
|
+ // Create a second mint for the nested token
|
|
|
+ let mut harness = harness.with_mint(0);
|
|
|
+ let nested_mint = harness.mint.unwrap();
|
|
|
+
|
|
|
+ // Create nested ATA and mint tokens to it
|
|
|
+ let nested_ata = harness.create_ata_for_owner(owner_ata, 1_000_000);
|
|
|
+ harness.mint_tokens_to(nested_ata, TEST_MINT_AMOUNT);
|
|
|
+
|
|
|
+ // Create destination ATA for the nested token
|
|
|
+ let destination_ata = harness.create_ata_for_owner(harness.wallet.unwrap(), 1_000_000);
|
|
|
+
|
|
|
+ // Capture pre-state for lamports transfer validation
|
|
|
+ let wallet_pubkey = harness.wallet.unwrap();
|
|
|
+ let pre_wallet_lamports = {
|
|
|
+ let store = harness.ctx.account_store.borrow();
|
|
|
+ store.get(&wallet_pubkey).unwrap().lamports
|
|
|
+ };
|
|
|
+ let nested_lamports = harness.get_account(nested_ata).lamports;
|
|
|
+
|
|
|
+ // Build and execute recover instruction
|
|
|
+ let recover_instruction = harness.build_recover_nested_instruction(owner_mint, nested_mint);
|
|
|
+ harness.ctx.process_and_validate_instruction(
|
|
|
+ &recover_instruction,
|
|
|
+ &[
|
|
|
+ Check::success(),
|
|
|
+ // Wallet received nested account lamports
|
|
|
+ Check::account(&wallet_pubkey)
|
|
|
+ .lamports(pre_wallet_lamports.checked_add(nested_lamports).unwrap())
|
|
|
+ .build(),
|
|
|
+ // Nested account has no lamports
|
|
|
+ Check::account(&nested_ata).lamports(0).build(),
|
|
|
+ // Nested account is closed
|
|
|
+ Check::account(&nested_ata).closed().build(),
|
|
|
+ ],
|
|
|
);
|
|
|
- try_recover_nested(
|
|
|
- context,
|
|
|
- program_id,
|
|
|
- mint,
|
|
|
- mint_authority,
|
|
|
- nested_associated_token_address,
|
|
|
- owner_associated_token_address,
|
|
|
- wrong_wallet,
|
|
|
- transaction,
|
|
|
- Some(InstructionError::IllegalOwner),
|
|
|
- )
|
|
|
- .await;
|
|
|
+
|
|
|
+ // Validate the recovery worked - tokens should be in the destination ATA
|
|
|
+ let destination_account = harness.get_account(destination_ata);
|
|
|
+ let destination_amount =
|
|
|
+ StateWithExtensionsOwned::<spl_token_2022_interface::state::Account>::unpack(
|
|
|
+ destination_account.data,
|
|
|
+ )
|
|
|
+ .unwrap()
|
|
|
+ .base
|
|
|
+ .amount;
|
|
|
+ assert_eq!(destination_amount, TEST_MINT_AMOUNT);
|
|
|
}
|
|
|
|
|
|
-#[tokio::test]
|
|
|
-async fn fail_wrong_signer_2022() {
|
|
|
- let dummy_mint = Pubkey::new_unique();
|
|
|
- let pt = program_test_2022(dummy_mint);
|
|
|
- let mut context = pt.start_with_context().await;
|
|
|
- check_wrong_signer(&mut context, &spl_token_2022_interface::id()).await;
|
|
|
+#[test]
|
|
|
+fn success_different_mints() {
|
|
|
+ test_recover_nested_different_mints(&spl_token_interface::id());
|
|
|
}
|
|
|
|
|
|
-#[tokio::test]
|
|
|
-async fn fail_wrong_signer() {
|
|
|
- let dummy_mint = Pubkey::new_unique();
|
|
|
- let pt = program_test(dummy_mint);
|
|
|
- let mut context = pt.start_with_context().await;
|
|
|
- check_wrong_signer(&mut context, &spl_token_interface::id()).await;
|
|
|
+#[test]
|
|
|
+fn success_different_mints_2022() {
|
|
|
+ test_recover_nested_different_mints(&spl_token_2022_interface::id());
|
|
|
}
|
|
|
|
|
|
-async fn check_not_nested(context: &mut ProgramTestContext, program_id: &Pubkey) {
|
|
|
- let wallet = Keypair::new();
|
|
|
- let wrong_wallet = Pubkey::new_unique();
|
|
|
- let (mint, mint_authority) = create_mint(context, program_id).await;
|
|
|
-
|
|
|
- let owner_associated_token_address =
|
|
|
- create_associated_token_account(context, &wallet.pubkey(), &mint, program_id).await;
|
|
|
- let nested_associated_token_address =
|
|
|
- create_associated_token_account(context, &wrong_wallet, &mint, program_id).await;
|
|
|
-
|
|
|
- context.last_blockhash = context
|
|
|
- .banks_client
|
|
|
- .get_new_latest_blockhash(&context.last_blockhash)
|
|
|
- .await
|
|
|
- .unwrap();
|
|
|
- let transaction = Transaction::new_signed_with_payer(
|
|
|
- &[instruction::recover_nested(
|
|
|
- &wallet.pubkey(),
|
|
|
- &mint,
|
|
|
- &mint,
|
|
|
- program_id,
|
|
|
- )],
|
|
|
- Some(&context.payer.pubkey()),
|
|
|
- &[&context.payer, &wallet],
|
|
|
- context.last_blockhash,
|
|
|
- );
|
|
|
- try_recover_nested(
|
|
|
- context,
|
|
|
- program_id,
|
|
|
- mint,
|
|
|
- mint_authority,
|
|
|
- nested_associated_token_address,
|
|
|
- owner_associated_token_address,
|
|
|
- wallet,
|
|
|
- transaction,
|
|
|
- Some(InstructionError::IllegalOwner),
|
|
|
- )
|
|
|
- .await;
|
|
|
+#[test]
|
|
|
+fn fail_missing_wallet_signature_2022() {
|
|
|
+ test_fail_missing_wallet_signature(&spl_token_2022_interface::id());
|
|
|
}
|
|
|
|
|
|
-#[tokio::test]
|
|
|
-async fn fail_not_nested_2022() {
|
|
|
- let dummy_mint = Pubkey::new_unique();
|
|
|
- let pt = program_test_2022(dummy_mint);
|
|
|
- let mut context = pt.start_with_context().await;
|
|
|
- check_not_nested(&mut context, &spl_token_2022_interface::id()).await;
|
|
|
+#[test]
|
|
|
+fn fail_missing_wallet_signature() {
|
|
|
+ test_fail_missing_wallet_signature(&spl_token_interface::id());
|
|
|
}
|
|
|
|
|
|
-#[tokio::test]
|
|
|
-async fn fail_not_nested() {
|
|
|
- let dummy_mint = Pubkey::new_unique();
|
|
|
- let pt = program_test(dummy_mint);
|
|
|
- let mut context = pt.start_with_context().await;
|
|
|
- check_not_nested(&mut context, &spl_token_interface::id()).await;
|
|
|
+#[test]
|
|
|
+fn fail_wrong_signer_2022() {
|
|
|
+ test_fail_wrong_signer(&spl_token_2022_interface::id());
|
|
|
}
|
|
|
|
|
|
-async fn check_wrong_address_derivation_owner(
|
|
|
- context: &mut ProgramTestContext,
|
|
|
- program_id: &Pubkey,
|
|
|
-) {
|
|
|
- let wallet = Keypair::new();
|
|
|
- let wrong_wallet = Pubkey::new_unique();
|
|
|
- let (mint, mint_authority) = create_mint(context, program_id).await;
|
|
|
+#[test]
|
|
|
+fn fail_wrong_signer() {
|
|
|
+ test_fail_wrong_signer(&spl_token_interface::id());
|
|
|
+}
|
|
|
|
|
|
- let owner_associated_token_address =
|
|
|
- create_associated_token_account(context, &wallet.pubkey(), &mint, program_id).await;
|
|
|
- let nested_associated_token_address = create_associated_token_account(
|
|
|
- context,
|
|
|
- &owner_associated_token_address,
|
|
|
- &mint,
|
|
|
- program_id,
|
|
|
- )
|
|
|
- .await;
|
|
|
-
|
|
|
- let wrong_owner_associated_token_address =
|
|
|
- get_associated_token_address_with_program_id(&mint, &wrong_wallet, program_id);
|
|
|
- let mut recover = instruction::recover_nested(&wallet.pubkey(), &mint, &mint, program_id);
|
|
|
- recover.accounts[3] = AccountMeta::new(wrong_owner_associated_token_address, false);
|
|
|
- context.last_blockhash = context
|
|
|
- .banks_client
|
|
|
- .get_new_latest_blockhash(&context.last_blockhash)
|
|
|
- .await
|
|
|
- .unwrap();
|
|
|
- let transaction = Transaction::new_signed_with_payer(
|
|
|
- &[recover],
|
|
|
- Some(&context.payer.pubkey()),
|
|
|
- &[&context.payer, &wallet],
|
|
|
- context.last_blockhash,
|
|
|
- );
|
|
|
- try_recover_nested(
|
|
|
- context,
|
|
|
- program_id,
|
|
|
- mint,
|
|
|
- mint_authority,
|
|
|
- nested_associated_token_address,
|
|
|
- wrong_owner_associated_token_address,
|
|
|
- wallet,
|
|
|
- transaction,
|
|
|
- Some(InstructionError::InvalidSeeds),
|
|
|
- )
|
|
|
- .await;
|
|
|
+#[test]
|
|
|
+fn fail_not_nested_2022() {
|
|
|
+ test_fail_not_nested(&spl_token_2022_interface::id());
|
|
|
}
|
|
|
|
|
|
-#[tokio::test]
|
|
|
-async fn fail_wrong_address_derivation_owner_2022() {
|
|
|
- let dummy_mint = Pubkey::new_unique();
|
|
|
- let pt = program_test_2022(dummy_mint);
|
|
|
- let mut context = pt.start_with_context().await;
|
|
|
- check_wrong_address_derivation_owner(&mut context, &spl_token_2022_interface::id()).await;
|
|
|
+#[test]
|
|
|
+fn fail_not_nested() {
|
|
|
+ test_fail_not_nested(&spl_token_interface::id());
|
|
|
+}
|
|
|
+#[test]
|
|
|
+fn fail_wrong_address_derivation_owner_2022() {
|
|
|
+ test_fail_wrong_address_derivation_owner(&spl_token_2022_interface::id());
|
|
|
}
|
|
|
|
|
|
-#[tokio::test]
|
|
|
-async fn fail_wrong_address_derivation_owner() {
|
|
|
- let dummy_mint = Pubkey::new_unique();
|
|
|
- let pt = program_test(dummy_mint);
|
|
|
- let mut context = pt.start_with_context().await;
|
|
|
- check_wrong_address_derivation_owner(&mut context, &spl_token_interface::id()).await;
|
|
|
+#[test]
|
|
|
+fn fail_wrong_address_derivation_owner() {
|
|
|
+ test_fail_wrong_address_derivation_owner(&spl_token_interface::id());
|
|
|
}
|
|
|
|
|
|
-async fn check_owner_account_does_not_exist(context: &mut ProgramTestContext, program_id: &Pubkey) {
|
|
|
- let wallet = Keypair::new();
|
|
|
- let (mint, mint_authority) = create_mint(context, program_id).await;
|
|
|
+#[test]
|
|
|
+fn fail_owner_account_does_not_exist() {
|
|
|
+ let mut harness = AtaTestHarness::new(&spl_token_2022_interface::id())
|
|
|
+ .with_wallet(1_000_000)
|
|
|
+ .with_mint(0);
|
|
|
+ // Note: deliberately NOT calling .with_ata() - owner ATA should not exist
|
|
|
+
|
|
|
+ let mint = harness.mint.unwrap();
|
|
|
+ let wallet_pubkey = harness.wallet.unwrap();
|
|
|
+ let owner_ata_address = get_associated_token_address_with_program_id(
|
|
|
+ &wallet_pubkey,
|
|
|
+ &mint,
|
|
|
+ &spl_token_2022_interface::id(),
|
|
|
+ );
|
|
|
|
|
|
- let owner_associated_token_address =
|
|
|
- get_associated_token_address_with_program_id(&wallet.pubkey(), &mint, program_id);
|
|
|
- let nested_associated_token_address = create_associated_token_account(
|
|
|
- context,
|
|
|
- &owner_associated_token_address,
|
|
|
+ // Create nested ATA using non-existent owner ATA address
|
|
|
+ let nested_ata = harness.create_ata_for_owner(owner_ata_address, 0);
|
|
|
+ harness.mint_tokens_to(nested_ata, TEST_MINT_AMOUNT);
|
|
|
+
|
|
|
+ let recover_instruction = instruction::recover_nested(
|
|
|
+ &wallet_pubkey,
|
|
|
+ &mint,
|
|
|
&mint,
|
|
|
- program_id,
|
|
|
- )
|
|
|
- .await;
|
|
|
-
|
|
|
- context.last_blockhash = context
|
|
|
- .banks_client
|
|
|
- .get_new_latest_blockhash(&context.last_blockhash)
|
|
|
- .await
|
|
|
- .unwrap();
|
|
|
- let transaction = Transaction::new_signed_with_payer(
|
|
|
- &[instruction::recover_nested(
|
|
|
- &wallet.pubkey(),
|
|
|
- &mint,
|
|
|
- &mint,
|
|
|
- program_id,
|
|
|
- )],
|
|
|
- Some(&context.payer.pubkey()),
|
|
|
- &[&context.payer, &wallet],
|
|
|
- context.last_blockhash,
|
|
|
+ &spl_token_2022_interface::id(),
|
|
|
);
|
|
|
- try_recover_nested(
|
|
|
- context,
|
|
|
- program_id,
|
|
|
- mint,
|
|
|
- mint_authority,
|
|
|
- nested_associated_token_address,
|
|
|
- owner_associated_token_address,
|
|
|
- wallet,
|
|
|
- transaction,
|
|
|
- Some(InstructionError::IllegalOwner),
|
|
|
- )
|
|
|
- .await;
|
|
|
-}
|
|
|
|
|
|
-#[tokio::test]
|
|
|
-async fn fail_owner_account_does_not_exist() {
|
|
|
- let dummy_mint = Pubkey::new_unique();
|
|
|
- let pt = program_test_2022(dummy_mint);
|
|
|
- let mut context = pt.start_with_context().await;
|
|
|
- check_owner_account_does_not_exist(&mut context, &spl_token_2022_interface::id()).await;
|
|
|
+ harness.ctx.process_and_validate_instruction(
|
|
|
+ &recover_instruction,
|
|
|
+ &[Check::err(ProgramError::IllegalOwner)],
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
-#[tokio::test]
|
|
|
-async fn fail_wrong_spl_token_program() {
|
|
|
- let wallet = Keypair::new();
|
|
|
- let dummy_mint = Pubkey::new_unique();
|
|
|
- let pt = program_test_2022(dummy_mint);
|
|
|
- let mut context = pt.start_with_context().await;
|
|
|
- let program_id = spl_token_2022_interface::id();
|
|
|
- let wrong_program_id = spl_token_interface::id();
|
|
|
- let (mint, mint_authority) = create_mint(&mut context, &program_id).await;
|
|
|
-
|
|
|
- let owner_associated_token_address =
|
|
|
- create_associated_token_account(&mut context, &wallet.pubkey(), &mint, &program_id).await;
|
|
|
- let nested_associated_token_address = create_associated_token_account(
|
|
|
- &mut context,
|
|
|
- &owner_associated_token_address,
|
|
|
+#[test]
|
|
|
+fn fail_wrong_spl_token_program() {
|
|
|
+ let mut harness = AtaTestHarness::new(&spl_token_2022_interface::id())
|
|
|
+ .with_wallet(1_000_000)
|
|
|
+ .with_mint(0)
|
|
|
+ .with_ata();
|
|
|
+
|
|
|
+ let mint = harness.mint.unwrap();
|
|
|
+ let owner_ata = harness.ata_address.unwrap();
|
|
|
+ let nested_ata = harness.create_ata_for_owner(owner_ata, 1_000_000);
|
|
|
+ harness.mint_tokens_to(nested_ata, TEST_MINT_AMOUNT);
|
|
|
+
|
|
|
+ // Use wrong program in instruction
|
|
|
+ let recover_instruction = instruction::recover_nested(
|
|
|
+ &harness.wallet.unwrap(),
|
|
|
+ &mint,
|
|
|
&mint,
|
|
|
- &program_id,
|
|
|
- )
|
|
|
- .await;
|
|
|
-
|
|
|
- context.last_blockhash = context
|
|
|
- .banks_client
|
|
|
- .get_new_latest_blockhash(&context.last_blockhash)
|
|
|
- .await
|
|
|
- .unwrap();
|
|
|
- let transaction = Transaction::new_signed_with_payer(
|
|
|
- &[instruction::recover_nested(
|
|
|
- &wallet.pubkey(),
|
|
|
- &mint,
|
|
|
- &mint,
|
|
|
- &wrong_program_id,
|
|
|
- )],
|
|
|
- Some(&context.payer.pubkey()),
|
|
|
- &[&context.payer, &wallet],
|
|
|
- context.last_blockhash,
|
|
|
+ &spl_token_interface::id(), // Wrong program ID
|
|
|
+ );
|
|
|
+
|
|
|
+ harness.ctx.process_and_validate_instruction(
|
|
|
+ &recover_instruction,
|
|
|
+ &[Check::err(ProgramError::IllegalOwner)],
|
|
|
);
|
|
|
- try_recover_nested(
|
|
|
- &mut context,
|
|
|
- &program_id,
|
|
|
- mint,
|
|
|
- mint_authority,
|
|
|
- nested_associated_token_address,
|
|
|
- owner_associated_token_address,
|
|
|
- wallet,
|
|
|
- transaction,
|
|
|
- Some(InstructionError::IllegalOwner),
|
|
|
- )
|
|
|
- .await;
|
|
|
}
|
|
|
|
|
|
-#[tokio::test]
|
|
|
-async fn fail_destination_not_wallet_ata() {
|
|
|
- let wallet = Keypair::new();
|
|
|
+#[test]
|
|
|
+fn fail_destination_not_wallet_ata() {
|
|
|
+ let mut harness = AtaTestHarness::new(&spl_token_2022_interface::id())
|
|
|
+ .with_wallet(1_000_000)
|
|
|
+ .with_mint(0)
|
|
|
+ .with_ata();
|
|
|
+
|
|
|
+ let mint = harness.mint.unwrap();
|
|
|
+ let owner_ata = harness.ata_address.unwrap();
|
|
|
+ let nested_ata = harness.create_ata_for_owner(owner_ata, 1_000_000);
|
|
|
+ harness.mint_tokens_to(nested_ata, TEST_MINT_AMOUNT);
|
|
|
+
|
|
|
+ // Create wrong destination ATA
|
|
|
let wrong_wallet = Pubkey::new_unique();
|
|
|
- let dummy_mint = Pubkey::new_unique();
|
|
|
- let pt = program_test_2022(dummy_mint);
|
|
|
- let program_id = spl_token_2022_interface::id();
|
|
|
- let mut context = pt.start_with_context().await;
|
|
|
- let (mint, mint_authority) = create_mint(&mut context, &program_id).await;
|
|
|
-
|
|
|
- let owner_associated_token_address =
|
|
|
- create_associated_token_account(&mut context, &wallet.pubkey(), &mint, &program_id).await;
|
|
|
- let nested_associated_token_address = create_associated_token_account(
|
|
|
- &mut context,
|
|
|
- &owner_associated_token_address,
|
|
|
- &mint,
|
|
|
- &program_id,
|
|
|
- )
|
|
|
- .await;
|
|
|
- let wrong_destination_associated_token_account_address =
|
|
|
- create_associated_token_account(&mut context, &wrong_wallet, &mint, &program_id).await;
|
|
|
-
|
|
|
- let mut recover = instruction::recover_nested(&wallet.pubkey(), &mint, &mint, &program_id);
|
|
|
- recover.accounts[2] =
|
|
|
- AccountMeta::new(wrong_destination_associated_token_account_address, false);
|
|
|
-
|
|
|
- context.last_blockhash = context
|
|
|
- .banks_client
|
|
|
- .get_new_latest_blockhash(&context.last_blockhash)
|
|
|
- .await
|
|
|
- .unwrap();
|
|
|
- let transaction = Transaction::new_signed_with_payer(
|
|
|
- &[recover],
|
|
|
- Some(&context.payer.pubkey()),
|
|
|
- &[&context.payer, &wallet],
|
|
|
- context.last_blockhash,
|
|
|
+ let wrong_destination_ata = harness.create_ata_for_owner(wrong_wallet, 1_000_000);
|
|
|
+
|
|
|
+ let mut recover_instruction = harness.build_recover_nested_instruction(mint, mint);
|
|
|
+ recover_instruction.accounts[2] = AccountMeta::new(wrong_destination_ata, false);
|
|
|
+
|
|
|
+ harness.ctx.process_and_validate_instruction(
|
|
|
+ &recover_instruction,
|
|
|
+ &[Check::err(ProgramError::InvalidSeeds)],
|
|
|
);
|
|
|
- try_recover_nested(
|
|
|
- &mut context,
|
|
|
- &program_id,
|
|
|
- mint,
|
|
|
- mint_authority,
|
|
|
- nested_associated_token_address,
|
|
|
- owner_associated_token_address,
|
|
|
- wallet,
|
|
|
- transaction,
|
|
|
- Some(InstructionError::InvalidSeeds),
|
|
|
- )
|
|
|
- .await;
|
|
|
}
|