// Mark this test as SBF-only due to current `ProgramTest` limitations when CPIing into the system program #![cfg(feature = "test-sbf")] use { solana_program_test::{ processor, tokio::{self, sync::Mutex}, ProgramTest, ProgramTestContext, }, solana_sdk::{ instruction::{AccountMeta, InstructionError}, pubkey::Pubkey, signature::Signer, signer::keypair::Keypair, transaction::{Transaction, TransactionError}, }, spl_token_client::{ client::{ ProgramBanksClient, ProgramBanksClientProcessTransaction, ProgramClient, SendTransaction, }, token::Token, }, spl_token_upgrade::{ error::TokenUpgradeError, get_token_upgrade_authority_address, instruction::exchange, }, std::sync::Arc, test_case::test_case, }; fn keypair_clone(kp: &Keypair) -> Keypair { Keypair::from_bytes(&kp.to_bytes()).expect("failed to copy keypair") } async fn setup() -> ( Arc>, Arc>, Arc, ) { let mut program_test = ProgramTest::new( "spl_token_upgrade", spl_token_upgrade::id(), processor!(spl_token_upgrade::processor::process), ); program_test.prefer_bpf(false); // simplicity in the build program_test.add_program( "spl_token_2022", spl_token_2022::id(), processor!(spl_token_2022::processor::Processor::process), ); program_test.add_program( "spl_token", spl_token::id(), processor!(spl_token::processor::Processor::process), ); let context = program_test.start_with_context().await; let payer = Arc::new(keypair_clone(&context.payer)); let context = Arc::new(Mutex::new(context)); let client: Arc> = Arc::new(ProgramBanksClient::new_from_context( Arc::clone(&context), ProgramBanksClientProcessTransaction, )); (context, client, payer) } async fn setup_mint( program_id: &Pubkey, mint_authority: &Pubkey, decimals: u8, payer: Arc, client: Arc>, ) -> Token { let mint_account = Keypair::new(); let token = Token::new( client, program_id, &mint_account.pubkey(), Some(decimals), payer, ); token .create_mint(mint_authority, None, vec![], &[&mint_account]) .await .unwrap(); token } #[test_case(spl_token::id(), spl_token_2022::id() ; "upgrade to token-2022")] #[test_case(spl_token_2022::id(), spl_token::id() ; "downgrade to token")] #[test_case(spl_token::id(), spl_token::id() ; "token to token")] #[test_case(spl_token_2022::id(), spl_token_2022::id() ; "token-2022 to token-2022")] #[tokio::test] async fn success(original_program_id: Pubkey, new_program_id: Pubkey) { let (context, client, payer) = setup().await; let wallet = Keypair::new(); let mint_authority = Keypair::new(); let mint_authority_pubkey = mint_authority.pubkey(); let decimals = 2; let original_token = setup_mint( &original_program_id, &mint_authority_pubkey, decimals, payer.clone(), client.clone(), ) .await; let new_token = setup_mint( &new_program_id, &mint_authority_pubkey, decimals, payer.clone(), client.clone(), ) .await; let program_escrow = get_token_upgrade_authority_address( original_token.get_address(), new_token.get_address(), &spl_token_upgrade::id(), ); original_token .create_associated_token_account(&wallet.pubkey()) .await .unwrap(); let original_account = original_token.get_associated_token_address(&wallet.pubkey()); let token_amount = 1_000_000_000_000; original_token .mint_to( &original_account, &mint_authority_pubkey, token_amount, &[&mint_authority], ) .await .unwrap(); new_token .create_associated_token_account(&wallet.pubkey()) .await .unwrap(); let new_account = new_token.get_associated_token_address(&wallet.pubkey()); new_token .create_associated_token_account(&program_escrow) .await .unwrap(); let escrow_account = new_token.get_associated_token_address(&program_escrow); new_token .mint_to( &escrow_account, &mint_authority_pubkey, token_amount, &[&mint_authority], ) .await .unwrap(); { let mut context = context.lock().await; let transaction = Transaction::new_signed_with_payer( &[exchange( &spl_token_upgrade::id(), &original_account, original_token.get_address(), &escrow_account, &new_account, new_token.get_address(), &original_program_id, &new_program_id, &wallet.pubkey(), &[], )], Some(&context.payer.pubkey()), &[&context.payer, &wallet], context.last_blockhash, ); context .banks_client .process_transaction(transaction) .await .unwrap(); } let original_mint = original_token.get_mint_info().await.unwrap(); assert_eq!(original_mint.base.supply, 0); let original_account_info = original_token .get_account_info(&original_account) .await .unwrap(); assert_eq!(original_account_info.base.amount, 0); let new_account_info = new_token.get_account_info(&new_account).await.unwrap(); assert_eq!(new_account_info.base.amount, token_amount); let escrow_info = new_token.get_account_info(&escrow_account).await.unwrap(); assert_eq!(escrow_info.base.amount, 0); } #[test_case(spl_token::id(), spl_token_2022::id() ; "fail upgrade to token-2022")] #[tokio::test] async fn fail_incorrect_escrow_derivation(original_program_id: Pubkey, new_program_id: Pubkey) { let (context, client, payer) = setup().await; let wallet = Keypair::new(); let mint_authority = Keypair::new(); let mint_authority_pubkey = mint_authority.pubkey(); let decimals = 2; let original_token = setup_mint( &original_program_id, &mint_authority_pubkey, decimals, payer.clone(), client.clone(), ) .await; let new_token = setup_mint( &new_program_id, &mint_authority_pubkey, decimals, payer.clone(), client.clone(), ) .await; // backwards derivation let program_escrow = get_token_upgrade_authority_address( new_token.get_address(), original_token.get_address(), &spl_token_upgrade::id(), ); original_token .create_associated_token_account(&wallet.pubkey()) .await .unwrap(); let original_account = original_token.get_associated_token_address(&wallet.pubkey()); let token_amount = 1_000_000_000_000; original_token .mint_to( &original_account, &mint_authority_pubkey, token_amount, &[&mint_authority], ) .await .unwrap(); new_token .create_associated_token_account(&wallet.pubkey()) .await .unwrap(); let new_account = new_token.get_associated_token_address(&wallet.pubkey()); new_token .create_associated_token_account(&program_escrow) .await .unwrap(); let escrow_account = new_token.get_associated_token_address(&program_escrow); new_token .mint_to( &escrow_account, &mint_authority_pubkey, token_amount, &[&mint_authority], ) .await .unwrap(); let mut instruction = exchange( &spl_token_upgrade::id(), &original_account, original_token.get_address(), &escrow_account, &new_account, new_token.get_address(), &original_program_id, &new_program_id, &wallet.pubkey(), &[], ); instruction.accounts[5] = AccountMeta::new_readonly(program_escrow, false); let mut context = context.lock().await; let transaction = Transaction::new_signed_with_payer( &[instruction], Some(&context.payer.pubkey()), &[&context.payer, &wallet], context.last_blockhash, ); let error = context .banks_client .process_transaction(transaction) .await .unwrap_err() .unwrap(); assert_eq!( error, TransactionError::InstructionError( 0, InstructionError::Custom(TokenUpgradeError::InvalidOwner as u32) ) ); } #[test_case(spl_token::id(), spl_token_2022::id() ; "fail upgrade to token-2022")] #[tokio::test] async fn fail_decimals_mismatch(original_program_id: Pubkey, new_program_id: Pubkey) { let (context, client, payer) = setup().await; let wallet = Keypair::new(); let mint_authority = Keypair::new(); let mint_authority_pubkey = mint_authority.pubkey(); // different decimals let original_decimals = 2; let new_decimals = 3; let original_token = setup_mint( &original_program_id, &mint_authority_pubkey, original_decimals, payer.clone(), client.clone(), ) .await; let new_token = setup_mint( &new_program_id, &mint_authority_pubkey, new_decimals, payer.clone(), client.clone(), ) .await; let program_escrow = get_token_upgrade_authority_address( original_token.get_address(), new_token.get_address(), &spl_token_upgrade::id(), ); original_token .create_associated_token_account(&wallet.pubkey()) .await .unwrap(); let original_account = original_token.get_associated_token_address(&wallet.pubkey()); let token_amount = 1_000_000_000_000; original_token .mint_to( &original_account, &mint_authority_pubkey, token_amount, &[&mint_authority], ) .await .unwrap(); new_token .create_associated_token_account(&wallet.pubkey()) .await .unwrap(); let new_account = new_token.get_associated_token_address(&wallet.pubkey()); new_token .create_associated_token_account(&program_escrow) .await .unwrap(); let escrow_account = new_token.get_associated_token_address(&program_escrow); new_token .mint_to( &escrow_account, &mint_authority_pubkey, token_amount, &[&mint_authority], ) .await .unwrap(); let mut context = context.lock().await; let transaction = Transaction::new_signed_with_payer( &[exchange( &spl_token_upgrade::id(), &original_account, original_token.get_address(), &escrow_account, &new_account, new_token.get_address(), &original_program_id, &new_program_id, &wallet.pubkey(), &[], )], Some(&context.payer.pubkey()), &[&context.payer, &wallet], context.last_blockhash, ); let error = context .banks_client .process_transaction(transaction) .await .unwrap_err() .unwrap(); assert_eq!( error, TransactionError::InstructionError( 0, InstructionError::Custom(TokenUpgradeError::DecimalsMismatch as u32) ) ); }