From 13826294ef76b1f60fb0eb3f529b3146ea154946 Mon Sep 17 00:00:00 2001 From: Yuriy Savchenko Date: Thu, 28 Jan 2021 00:35:25 +0200 Subject: [PATCH] Stake pool: test coverage for most of the program errors (#1134) * Stake pool: test coverage for most of the program errors * Fixes to stake pool tests PR comments --- stake-pool/program/src/processor.rs | 8 +- stake-pool/program/tests/deposit.rs | 648 +++++++++++++++- stake-pool/program/tests/helpers/mod.rs | 47 +- stake-pool/program/tests/initialize.rs | 239 +++++- stake-pool/program/tests/set_owner.rs | 218 ++++++ .../program/tests/set_staking_authority.rs | 267 +++++++ .../program/tests/update_list_balance.rs | 9 +- .../program/tests/update_pool_balance.rs | 53 +- stake-pool/program/tests/vsa_add.rs | 455 +++++++++++- stake-pool/program/tests/vsa_create.rs | 168 ++++- stake-pool/program/tests/vsa_remove.rs | 496 ++++++++++++- stake-pool/program/tests/withdraw.rs | 690 +++++++++++++++++- 12 files changed, 3247 insertions(+), 51 deletions(-) create mode 100644 stake-pool/program/tests/set_owner.rs create mode 100644 stake-pool/program/tests/set_staking_authority.rs diff --git a/stake-pool/program/src/processor.rs b/stake-pool/program/src/processor.rs index a5366467..2203c8fa 100644 --- a/stake-pool/program/src/processor.rs +++ b/stake-pool/program/src/processor.rs @@ -509,7 +509,7 @@ impl Processor { } if stake_pool.token_program_id != *token_program_info.key { - return Err(StakePoolError::InvalidProgramAddress.into()); + return Err(ProgramError::IncorrectProgramId); } if stake_pool.pool_mint != *pool_mint_info.key { return Err(StakePoolError::WrongPoolMint.into()); @@ -637,7 +637,7 @@ impl Processor { } if stake_pool.token_program_id != *token_program_info.key { - return Err(StakePoolError::InvalidProgramAddress.into()); + return Err(ProgramError::IncorrectProgramId); } if stake_pool.pool_mint != *pool_mint_info.key { return Err(StakePoolError::WrongPoolMint.into()); @@ -855,7 +855,7 @@ impl Processor { return Err(StakePoolError::InvalidFeeAccount.into()); } if stake_pool.token_program_id != *token_program_info.key { - return Err(StakePoolError::InvalidProgramAddress.into()); + return Err(ProgramError::IncorrectProgramId); } // Check validator stake account list storage @@ -1004,7 +1004,7 @@ impl Processor { stake_pool.check_authority_withdraw(withdraw_info.key, program_id, stake_pool_info.key)?; if stake_pool.token_program_id != *token_program_info.key { - return Err(StakePoolError::InvalidProgramAddress.into()); + return Err(ProgramError::IncorrectProgramId); } // Check validator stake account list storage diff --git a/stake-pool/program/tests/deposit.rs b/stake-pool/program/tests/deposit.rs index fbb1f866..8f92e56c 100644 --- a/stake-pool/program/tests/deposit.rs +++ b/stake-pool/program/tests/deposit.rs @@ -4,16 +4,25 @@ mod helpers; use helpers::*; -use solana_sdk::signature::{Keypair, Signer}; +use solana_program::hash::Hash; +use solana_program_test::BanksClient; +use solana_sdk::{ + instruction::InstructionError, + signature::{Keypair, Signer}, + transaction::Transaction, + transaction::TransactionError, + transport::TransportError, +}; use spl_stake_pool::*; +use spl_token::error as token_error; -#[tokio::test] -async fn test_stake_pool_deposit() { +async fn setup() -> (BanksClient, Keypair, Hash, StakePoolAccounts, StakeAccount) { let (mut banks_client, payer, recent_blockhash) = program_test().start().await; let stake_pool_accounts = StakePoolAccounts::new(); stake_pool_accounts .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) - .await; + .await + .unwrap(); let validator_stake_account: StakeAccount = simple_add_validator_stake_account( &mut banks_client, @@ -23,6 +32,20 @@ async fn test_stake_pool_deposit() { ) .await; + ( + banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + validator_stake_account, + ) +} + +#[tokio::test] +async fn test_stake_pool_deposit() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake_account) = + setup().await; + let user = Keypair::new(); // make stake account let user_stake = Keypair::new(); @@ -50,7 +73,8 @@ async fn test_stake_pool_deposit() { &stake_pool_accounts.pool_mint.pubkey(), &user.pubkey(), ) - .await; + .await + .unwrap(); // Save stake pool state before depositing let stake_pool_before = @@ -81,7 +105,8 @@ async fn test_stake_pool_deposit() { &user_pool_account.pubkey(), &validator_stake_account.stake_account, ) - .await; + .await + .unwrap(); // Original stake account should be drained assert!(banks_client @@ -143,3 +168,614 @@ async fn test_stake_pool_deposit() { validator_stake_item.balance ); } + +#[tokio::test] +async fn test_stake_pool_deposit_with_wrong_stake_program_id() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake_account) = + setup().await; + + let user = Keypair::new(); + // make stake account + let user_stake = Keypair::new(); + + // make pool token account + let user_pool_account = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + let wrong_stake_program = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::deposit( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.validator_stake_list.pubkey(), + &stake_pool_accounts.deposit_authority, + &stake_pool_accounts.withdraw_authority, + &user_stake.pubkey(), + &validator_stake_account.stake_account, + &user_pool_account.pubkey(), + &stake_pool_accounts.pool_fee_account.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &spl_token::id(), + &wrong_stake_program.pubkey(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError(_, error)) => { + assert_eq!(error, InstructionError::IncorrectProgramId); + } + _ => panic!("Wrong error occurs while try to make a deposit with wrong stake program ID"), + } +} + +#[tokio::test] +async fn test_stake_pool_deposit_with_wrong_pool_fee_account() { + let ( + mut banks_client, + payer, + recent_blockhash, + mut stake_pool_accounts, + validator_stake_account, + ) = setup().await; + + let user = Keypair::new(); + // make stake account + let user_stake = Keypair::new(); + + // make pool token account + let user_pool_account = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + let wrong_pool_fee_acc = Keypair::new(); + stake_pool_accounts.pool_fee_account = wrong_pool_fee_acc; + + let transaction_error = stake_pool_accounts + .deposit_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.pubkey(), + &user_pool_account.pubkey(), + &validator_stake_account.stake_account, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::InvalidFeeAccount as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to make a deposit with wrong pool fee account"), + } +} + +#[tokio::test] +async fn test_stake_pool_deposit_with_wrong_token_program_id() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake_account) = + setup().await; + + let user = Keypair::new(); + // make stake account + let user_stake = Keypair::new(); + + // make pool token account + let user_pool_account = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + let wrong_token_program = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::deposit( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.validator_stake_list.pubkey(), + &stake_pool_accounts.deposit_authority, + &stake_pool_accounts.withdraw_authority, + &user_stake.pubkey(), + &validator_stake_account.stake_account, + &user_pool_account.pubkey(), + &stake_pool_accounts.pool_fee_account.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &wrong_token_program.pubkey(), + &stake::id(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError(_, error)) => { + assert_eq!(error, InstructionError::IncorrectProgramId); + } + _ => panic!("Wrong error occurs while try to make a deposit with wrong token program ID"), + } +} + +#[tokio::test] +async fn test_stake_pool_deposit_with_wrong_validator_stake_list_account() { + let ( + mut banks_client, + payer, + recent_blockhash, + mut stake_pool_accounts, + validator_stake_account, + ) = setup().await; + + let user = Keypair::new(); + // make stake account + let user_stake = Keypair::new(); + + // make pool token account + let user_pool_account = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + let wrong_validator_stake_list = Keypair::new(); + stake_pool_accounts.validator_stake_list = wrong_validator_stake_list; + + let transaction_error = stake_pool_accounts + .deposit_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.pubkey(), + &user_pool_account.pubkey(), + &validator_stake_account.stake_account, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::InvalidValidatorStakeList as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to make a deposit with wrong validator stake list account"), + } +} + +#[tokio::test] +async fn test_stake_pool_deposit_where_stake_acc_not_in_stake_state() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let validator_stake_account = StakeAccount::new_with_target_authority( + &stake_pool_accounts.deposit_authority, + &stake_pool_accounts.stake_pool.pubkey(), + ); + + let user_stake_authority = Keypair::new(); + create_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &validator_stake_account.stake_pool, + &validator_stake_account.stake_account, + &validator_stake_account.vote.pubkey(), + &user_stake_authority.pubkey(), + &validator_stake_account.target_authority, + ) + .await; + + let user_pool_account = Keypair::new(); + let user = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + let user_stake_acc = Keypair::new(); + let transaction_error = stake_pool_accounts + .deposit_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake_acc.pubkey(), + &user_pool_account.pubkey(), + &validator_stake_account.stake_account, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::WrongStakeState as u32; + assert_eq!(error_index, program_error); + } + _ => panic!( + "Wrong error occurs while try to make a deposit when stake acc not in stake state" + ), + } +} + +#[tokio::test] +async fn test_stake_pool_deposit_to_unknown_validator() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let validator_stake_account = StakeAccount::new_with_target_authority( + &stake_pool_accounts.deposit_authority, + &stake_pool_accounts.stake_pool.pubkey(), + ); + validator_stake_account + .create_and_delegate(&mut banks_client, &payer, &recent_blockhash) + .await; + + let user_pool_account = Keypair::new(); + let user = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + // make stake account + let user_stake = Keypair::new(); + let lockup = stake::Lockup::default(); + let authorized = stake::Authorized { + staker: stake_pool_accounts.deposit_authority, + withdrawer: stake_pool_accounts.deposit_authority, + }; + create_independent_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake, + &authorized, + &lockup, + ) + .await; + + let transaction_error = stake_pool_accounts + .deposit_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.pubkey(), + &user_pool_account.pubkey(), + &validator_stake_account.stake_account, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::ValidatorNotFound as u32; + assert_eq!(error_index, program_error); + } + _ => { + panic!("Wrong error occurs while try to make a deposit with unknown validator account") + } + } +} + +#[tokio::test] +async fn test_stake_pool_deposit_with_wrong_deposit_authority() { + let ( + mut banks_client, + payer, + recent_blockhash, + mut stake_pool_accounts, + validator_stake_account, + ) = setup().await; + + let user = Keypair::new(); + // make stake account + let user_stake = Keypair::new(); + + // make pool token account + let user_pool_account = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + stake_pool_accounts.deposit_authority = Keypair::new().pubkey(); + + let transaction_error = stake_pool_accounts + .deposit_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.pubkey(), + &user_pool_account.pubkey(), + &validator_stake_account.stake_account, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::InvalidProgramAddress as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to make a deposit with wrong deposit authority"), + } +} + +#[tokio::test] +async fn test_stake_pool_deposit_with_wrong_withdraw_authority() { + let ( + mut banks_client, + payer, + recent_blockhash, + mut stake_pool_accounts, + validator_stake_account, + ) = setup().await; + + let user = Keypair::new(); + // make stake account + let user_stake = Keypair::new(); + + // make pool token account + let user_pool_account = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + stake_pool_accounts.withdraw_authority = Keypair::new().pubkey(); + + let transaction_error = stake_pool_accounts + .deposit_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.pubkey(), + &user_pool_account.pubkey(), + &validator_stake_account.stake_account, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::InvalidProgramAddress as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to make a deposit with wrong withdraw authority"), + } +} + +#[tokio::test] +async fn test_stake_pool_deposit_with_wrong_set_deposit_authority() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake_account) = + setup().await; + + let user = Keypair::new(); + // make stake account + let user_stake = Keypair::new(); + let lockup = stake::Lockup::default(); + let authorized = stake::Authorized { + staker: Keypair::new().pubkey(), + withdrawer: stake_pool_accounts.deposit_authority, + }; + create_independent_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake, + &authorized, + &lockup, + ) + .await; + // make pool token account + let user_pool_account = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + let transaction_error = stake_pool_accounts + .deposit_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.pubkey(), + &user_pool_account.pubkey(), + &validator_stake_account.stake_account, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError(_, error)) => { + assert_eq!(error, InstructionError::MissingRequiredSignature); + } + _ => { + panic!("Wrong error occurs while try to make deposit with wrong set deposit authority") + } + } +} + +#[tokio::test] +async fn test_stake_pool_deposit_with_wrong_mint_for_receiver_acc() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake_account) = + setup().await; + + // make stake account + let user_stake = Keypair::new(); + let lockup = stake::Lockup::default(); + let authorized = stake::Authorized { + staker: stake_pool_accounts.deposit_authority, + withdrawer: stake_pool_accounts.deposit_authority, + }; + create_independent_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake, + &authorized, + &lockup, + ) + .await; + + let outside_mint = Keypair::new(); + let outside_withdraw_auth = Keypair::new(); + let outside_owner = Keypair::new(); + let outside_pool_fee_acc = Keypair::new(); + + create_mint( + &mut banks_client, + &payer, + &recent_blockhash, + &outside_mint, + &outside_withdraw_auth.pubkey(), + ) + .await + .unwrap(); + + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &outside_pool_fee_acc, + &outside_mint.pubkey(), + &outside_owner.pubkey(), + ) + .await + .unwrap(); + + let transaction_error = stake_pool_accounts + .deposit_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.pubkey(), + &outside_pool_fee_acc.pubkey(), + &validator_stake_account.stake_account, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = token_error::TokenError::MintMismatch as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to deposit with wrong mint fro receiver account"), + } +} + +#[tokio::test] +async fn test_deposit_with_uninitialized_validator_stake_list() {} // TODO + +#[tokio::test] +async fn test_deposit_with_out_of_dated_pool_balances() {} // TODO diff --git a/stake-pool/program/tests/helpers/mod.rs b/stake-pool/program/tests/helpers/mod.rs index 6de4df35..18cca7f8 100644 --- a/stake-pool/program/tests/helpers/mod.rs +++ b/stake-pool/program/tests/helpers/mod.rs @@ -29,13 +29,13 @@ pub async fn get_account(banks_client: &mut BanksClient, pubkey: &Pubkey) -> Acc .expect("account empty") } -async fn create_mint( +pub async fn create_mint( banks_client: &mut BanksClient, payer: &Keypair, recent_blockhash: &Hash, pool_mint: &Keypair, owner: &Pubkey, -) { +) -> Result<(), TransportError> { let rent = banks_client.get_rent().await.unwrap(); let mint_rent = rent.minimum_balance(spl_token::state::Mint::LEN); @@ -60,7 +60,8 @@ async fn create_mint( Some(&payer.pubkey()), ); transaction.sign(&[payer, pool_mint], *recent_blockhash); - banks_client.process_transaction(transaction).await.unwrap(); + banks_client.process_transaction(transaction).await?; + Ok(()) } pub async fn transfer( @@ -89,7 +90,7 @@ pub async fn create_token_account( account: &Keypair, pool_mint: &Pubkey, owner: &Pubkey, -) { +) -> Result<(), TransportError> { let rent = banks_client.get_rent().await.unwrap(); let account_rent = rent.minimum_balance(spl_token::state::Account::LEN); @@ -113,7 +114,8 @@ pub async fn create_token_account( Some(&payer.pubkey()), ); transaction.sign(&[payer, account], *recent_blockhash); - banks_client.process_transaction(transaction).await.unwrap(); + banks_client.process_transaction(transaction).await?; + Ok(()) } pub async fn get_token_balance(banks_client: &mut BanksClient, token: &Pubkey) -> u64 { @@ -153,7 +155,7 @@ pub async fn delegate_tokens( } #[allow(clippy::too_many_arguments)] -async fn create_stake_pool( +pub async fn create_stake_pool( banks_client: &mut BanksClient, payer: &Keypair, recent_blockhash: &Hash, @@ -163,7 +165,7 @@ async fn create_stake_pool( pool_token_account: &Pubkey, owner: &Pubkey, fee: &instruction::Fee, -) { +) -> Result<(), TransportError> { let rent = banks_client.get_rent().await.unwrap(); let rent_stake_pool = rent.minimum_balance(state::State::LEN); let rent_validator_stake_list = rent.minimum_balance(state::ValidatorStakeList::LEN); @@ -203,7 +205,8 @@ async fn create_stake_pool( &[payer, stake_pool, validator_stake_list], *recent_blockhash, ); - banks_client.process_transaction(transaction).await.unwrap(); + banks_client.process_transaction(transaction).await?; + Ok(()) } pub async fn create_vote( @@ -471,7 +474,7 @@ impl StakePoolAccounts { mut banks_client: &mut BanksClient, payer: &Keypair, recent_blockhash: &Hash, - ) { + ) -> Result<(), TransportError> { create_mint( &mut banks_client, &payer, @@ -479,7 +482,7 @@ impl StakePoolAccounts { &self.pool_mint, &self.withdraw_authority, ) - .await; + .await?; create_token_account( &mut banks_client, &payer, @@ -488,7 +491,7 @@ impl StakePoolAccounts { &self.pool_mint.pubkey(), &self.owner.pubkey(), ) - .await; + .await?; create_stake_pool( &mut banks_client, &payer, @@ -500,7 +503,8 @@ impl StakePoolAccounts { &self.owner.pubkey(), &self.fee, ) - .await; + .await?; + Ok(()) } pub async fn deposit_stake( @@ -511,7 +515,7 @@ impl StakePoolAccounts { stake: &Pubkey, pool_account: &Pubkey, validator_stake_account: &Pubkey, - ) { + ) -> Result<(), TransportError> { let mut transaction = Transaction::new_with_payer( &[instruction::deposit( &id(), @@ -531,7 +535,8 @@ impl StakePoolAccounts { Some(&payer.pubkey()), ); transaction.sign(&[payer], *recent_blockhash); - banks_client.process_transaction(transaction).await.unwrap(); + banks_client.process_transaction(transaction).await?; + Ok(()) } pub async fn withdraw_stake( @@ -544,7 +549,7 @@ impl StakePoolAccounts { validator_stake_account: &Pubkey, recipient_new_authority: &Pubkey, amount: u64, - ) { + ) -> Result<(), TransportError> { let mut transaction = Transaction::new_with_payer( &[instruction::withdraw( &id(), @@ -564,7 +569,8 @@ impl StakePoolAccounts { Some(&payer.pubkey()), ); transaction.sign(&[payer], *recent_blockhash); - banks_client.process_transaction(transaction).await.unwrap(); + banks_client.process_transaction(transaction).await?; + Ok(()) } pub async fn add_validator_stake_account( @@ -651,7 +657,8 @@ pub async fn simple_add_validator_stake_account( &stake_pool_accounts.pool_mint.pubkey(), &user.pubkey(), ) - .await; + .await + .unwrap(); let error = stake_pool_accounts .add_validator_stake_account( banks_client, @@ -707,7 +714,8 @@ pub async fn simple_deposit( &stake_pool_accounts.pool_mint.pubkey(), &user.pubkey(), ) - .await; + .await + .unwrap(); stake_pool_accounts .deposit_stake( @@ -718,7 +726,8 @@ pub async fn simple_deposit( &user_pool_account.pubkey(), &validator_stake_account.stake_account, ) - .await; + .await + .unwrap(); let user_pool_account = user_pool_account.pubkey(); let pool_tokens = get_token_balance(banks_client, &user_pool_account).await; diff --git a/stake-pool/program/tests/initialize.rs b/stake-pool/program/tests/initialize.rs index 03ceba1c..c15f2f04 100644 --- a/stake-pool/program/tests/initialize.rs +++ b/stake-pool/program/tests/initialize.rs @@ -3,7 +3,11 @@ mod helpers; use helpers::*; -use solana_sdk::signature::Signer; +use solana_program::system_instruction; +use solana_sdk::{ + instruction::InstructionError, signature::Keypair, signature::Signer, transaction::Transaction, + transaction::TransactionError, transport::TransportError, +}; use spl_stake_pool::*; #[tokio::test] @@ -12,7 +16,8 @@ async fn test_stake_pool_initialize() { let stake_pool_accounts = StakePoolAccounts::new(); stake_pool_accounts .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) - .await; + .await + .unwrap(); // Stake pool now exists let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await; @@ -29,3 +34,233 @@ async fn test_stake_pool_initialize() { state::ValidatorStakeList::deserialize(validator_stake_list.data.as_slice()).unwrap(); assert_eq!(validator_stake_list.is_initialized, true); } + +#[tokio::test] +async fn test_initialize_already_initialized_stake_pool() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let latest_blockhash = banks_client.get_recent_blockhash().await.unwrap(); + + let mut second_stake_pool_accounts = StakePoolAccounts::new(); + second_stake_pool_accounts.stake_pool = stake_pool_accounts.stake_pool; + + let transaction_error = second_stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &latest_blockhash) + .await + .err() + .unwrap(); + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::AlreadyInUse as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to initialize already initialized stake pool"), + } +} + +#[tokio::test] +async fn test_initialize_stake_pool_with_already_initialized_stake_list_storage() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let latest_blockhash = banks_client.get_recent_blockhash().await.unwrap(); + + let mut second_stake_pool_accounts = StakePoolAccounts::new(); + second_stake_pool_accounts.validator_stake_list = stake_pool_accounts.validator_stake_list; + + let transaction_error = second_stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &latest_blockhash) + .await + .err() + .unwrap(); + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::AlreadyInUse as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to initialize stake pool with already initialized stake list storage"), + } +} + +#[tokio::test] +async fn test_initialize_stake_pool_with_high_fee() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let mut stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts.fee = instruction::Fee { + numerator: 100001, + denominator: 100000, + }; + + let transaction_error = stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .err() + .unwrap(); + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::FeeTooHigh as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to initialize stake pool with high fee"), + } +} + +#[tokio::test] +async fn test_initialize_stake_pool_with_wrong_mint_authority() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + let wrong_mint = Keypair::new(); + + create_mint( + &mut banks_client, + &payer, + &recent_blockhash, + &stake_pool_accounts.pool_mint, + &stake_pool_accounts.withdraw_authority, + ) + .await + .unwrap(); + + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &stake_pool_accounts.pool_fee_account, + &stake_pool_accounts.pool_mint.pubkey(), + &stake_pool_accounts.owner.pubkey(), + ) + .await + .unwrap(); + + let transaction_error = create_stake_pool( + &mut banks_client, + &payer, + &recent_blockhash, + &stake_pool_accounts.stake_pool, + &stake_pool_accounts.validator_stake_list, + &wrong_mint.pubkey(), + &stake_pool_accounts.pool_fee_account.pubkey(), + &stake_pool_accounts.owner.pubkey(), + &stake_pool_accounts.fee, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::WrongAccountMint as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to initialize stake pool with wrong mint authority of pool fee account"), + } +} + +#[tokio::test] +async fn test_initialize_stake_pool_with_wrong_token_program_id() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + + create_mint( + &mut banks_client, + &payer, + &recent_blockhash, + &stake_pool_accounts.pool_mint, + &stake_pool_accounts.withdraw_authority, + ) + .await + .unwrap(); + + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &stake_pool_accounts.pool_fee_account, + &stake_pool_accounts.pool_mint.pubkey(), + &stake_pool_accounts.owner.pubkey(), + ) + .await + .unwrap(); + + let rent = banks_client.get_rent().await.unwrap(); + let rent_stake_pool = rent.minimum_balance(state::State::LEN); + let rent_validator_stake_list = rent.minimum_balance(state::ValidatorStakeList::LEN); + let init_args = instruction::InitArgs { + fee: stake_pool_accounts.fee, + }; + let wrong_token_program = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &[ + system_instruction::create_account( + &payer.pubkey(), + &stake_pool_accounts.stake_pool.pubkey(), + rent_stake_pool, + state::State::LEN as u64, + &id(), + ), + system_instruction::create_account( + &payer.pubkey(), + &stake_pool_accounts.validator_stake_list.pubkey(), + rent_validator_stake_list, + state::ValidatorStakeList::LEN as u64, + &id(), + ), + instruction::initialize( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.owner.pubkey(), + &stake_pool_accounts.validator_stake_list.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &stake_pool_accounts.pool_fee_account.pubkey(), + &wrong_token_program.pubkey(), + init_args, + ) + .unwrap(), + ], + Some(&payer.pubkey()), + ); + transaction.sign( + &[ + &payer, + &stake_pool_accounts.stake_pool, + &stake_pool_accounts.validator_stake_list, + ], + recent_blockhash, + ); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError(_, error)) => { + assert_eq!(error, InstructionError::IncorrectProgramId); + } + _ => panic!( + "Wrong error occurs while try to initialize stake pool with wrong token program ID" + ), + } +} diff --git a/stake-pool/program/tests/set_owner.rs b/stake-pool/program/tests/set_owner.rs new file mode 100644 index 00000000..8c2321b8 --- /dev/null +++ b/stake-pool/program/tests/set_owner.rs @@ -0,0 +1,218 @@ +#![cfg(feature = "test-bpf")] + +mod helpers; + +use helpers::*; +use solana_program::hash::Hash; +use solana_program::instruction::AccountMeta; +use solana_program::instruction::Instruction; +use solana_program_test::BanksClient; +use solana_sdk::{ + instruction::InstructionError, signature::Keypair, signature::Signer, transaction::Transaction, + transaction::TransactionError, transport::TransportError, +}; +use spl_stake_pool::*; + +async fn setup() -> ( + BanksClient, + Keypair, + Hash, + StakePoolAccounts, + Keypair, + Keypair, +) { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let new_pool_fee = Keypair::new(); + let new_owner = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &new_pool_fee, + &stake_pool_accounts.pool_mint.pubkey(), + &new_owner.pubkey(), + ) + .await + .unwrap(); + + ( + banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + new_pool_fee, + new_owner, + ) +} + +#[tokio::test] +async fn test_set_owner() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_pool_fee, new_owner) = + setup().await; + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_owner( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.owner.pubkey(), + &new_owner.pubkey(), + &new_pool_fee.pubkey(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + + let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await; + let stake_pool = state::State::deserialize(&stake_pool.data.as_slice()) + .unwrap() + .stake_pool() + .unwrap(); + + assert_eq!(stake_pool.owner, new_owner.pubkey()); +} + +#[tokio::test] +async fn test_set_owner_by_malicious() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_pool_fee, new_owner) = + setup().await; + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_owner( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &new_owner.pubkey(), + &new_owner.pubkey(), + &new_pool_fee.pubkey(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &new_owner], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::WrongOwner as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while malicious try to set owner"), + } +} + +#[tokio::test] +async fn test_set_owner_without_signature() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_pool_fee, new_owner) = + setup().await; + + let args = instruction::StakePoolInstruction::SetOwner; + let data = args.serialize().unwrap(); + let accounts = vec![ + AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false), + AccountMeta::new_readonly(stake_pool_accounts.owner.pubkey(), false), + AccountMeta::new_readonly(new_owner.pubkey(), false), + AccountMeta::new_readonly(new_pool_fee.pubkey(), false), + ]; + let instruction = Instruction { + program_id: id(), + accounts, + data, + }; + + let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey())); + transaction.sign(&[&payer], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::SignatureMissing as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to set new owner without signature"), + } +} + +#[tokio::test] +async fn test_set_owner_with_wrong_mint_for_pool_fee_acc() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let new_mint = Keypair::new(); + let new_withdraw_auth = Keypair::new(); + let new_pool_fee = Keypair::new(); + let new_owner = Keypair::new(); + + create_mint( + &mut banks_client, + &payer, + &recent_blockhash, + &new_mint, + &new_withdraw_auth.pubkey(), + ) + .await + .unwrap(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &new_pool_fee, + &new_mint.pubkey(), + &new_owner.pubkey(), + ) + .await + .unwrap(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_owner( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.owner.pubkey(), + &new_owner.pubkey(), + &new_pool_fee.pubkey(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::WrongAccountMint as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to set new owner with wrong mint"), + } +} diff --git a/stake-pool/program/tests/set_staking_authority.rs b/stake-pool/program/tests/set_staking_authority.rs new file mode 100644 index 00000000..4972e506 --- /dev/null +++ b/stake-pool/program/tests/set_staking_authority.rs @@ -0,0 +1,267 @@ +#![cfg(feature = "test-bpf")] + +mod helpers; + +use bincode::deserialize; +use helpers::*; +use solana_program::hash::Hash; +use solana_program::instruction::AccountMeta; +use solana_program::instruction::Instruction; +use solana_program::sysvar; +use solana_program_test::BanksClient; +use solana_sdk::{ + instruction::InstructionError, signature::Keypair, signature::Signer, transaction::Transaction, + transaction::TransactionError, transport::TransportError, +}; +use spl_stake_pool::*; + +async fn setup() -> (BanksClient, Keypair, Hash, StakePoolAccounts, StakeAccount) { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let user = Keypair::new(); + + let user_stake = StakeAccount::new_with_target_authority( + &stake_pool_accounts.deposit_authority, + &stake_pool_accounts.stake_pool.pubkey(), + ); + user_stake + .create_and_delegate(&mut banks_client, &payer, &recent_blockhash) + .await; + + // make pool token account + let user_pool_account = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + let error = stake_pool_accounts + .add_validator_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.stake_account, + &user_pool_account.pubkey(), + ) + .await; + assert!(error.is_none()); + + ( + banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + ) +} + +#[tokio::test] +async fn test_set_staking_authority() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) = + setup().await; + + let new_staking_pubkey = Keypair::new().pubkey(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_staking_authority( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.owner.pubkey(), + &stake_pool_accounts.withdraw_authority, + &user_stake.stake_account, + &new_staking_pubkey, + &stake::id(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + + // Check of stake account authority has changed + let stake = get_account(&mut banks_client, &user_stake.stake_account).await; + let stake_state = deserialize::(&stake.data).unwrap(); + match stake_state { + stake::StakeState::Stake(meta, _) => { + assert_eq!(&meta.authorized.staker, &new_staking_pubkey); + assert_eq!( + &meta.authorized.withdrawer, + &stake_pool_accounts.withdraw_authority + ); + } + _ => panic!(), + } +} + +#[tokio::test] +async fn test_set_staking_authority_with_wrong_stake_program_id() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) = + setup().await; + + let new_staking_pubkey = Keypair::new().pubkey(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_staking_authority( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.owner.pubkey(), + &stake_pool_accounts.withdraw_authority, + &user_stake.stake_account, + &new_staking_pubkey, + &Keypair::new().pubkey(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError(_, error)) => { + assert_eq!(error, InstructionError::IncorrectProgramId); + } + _ => panic!( + "Wrong error occurs while try to set staking authority with wrong stake program ID" + ), + } +} + +#[tokio::test] +async fn test_set_staking_authority_with_wrong_withdraw_authority() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) = + setup().await; + + let new_staking_pubkey = Keypair::new().pubkey(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_staking_authority( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.owner.pubkey(), + &Keypair::new().pubkey(), + &user_stake.stake_account, + &new_staking_pubkey, + &stake::id(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::InvalidProgramAddress as u32; + assert_eq!(error_index, program_error); + } + _ => panic!( + "Wrong error occurs while try to set staking authority with wrong withdraw authority" + ), + } +} + +#[tokio::test] +async fn test_set_staking_authority_with_wrong_owner() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) = + setup().await; + + let new_staking_pubkey = Keypair::new().pubkey(); + let wrong_owner = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::set_staking_authority( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &wrong_owner.pubkey(), + &stake_pool_accounts.withdraw_authority, + &user_stake.stake_account, + &new_staking_pubkey, + &stake::id(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &wrong_owner], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::WrongOwner as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to set staking authority with wrong owner"), + } +} + +#[tokio::test] +async fn test_set_staking_authority_without_signature() { + let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) = + setup().await; + + let new_staking_pubkey = Keypair::new().pubkey(); + + let args = instruction::StakePoolInstruction::SetStakingAuthority; + let data = args.serialize().unwrap(); + let accounts = vec![ + AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false), + AccountMeta::new_readonly(stake_pool_accounts.owner.pubkey(), false), + AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false), + AccountMeta::new(user_stake.stake_account, false), + AccountMeta::new_readonly(new_staking_pubkey, false), + AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(stake::id(), false), + ]; + let instruction = Instruction { + program_id: id(), + accounts, + data, + }; + + let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey())); + transaction.sign(&[&payer], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::SignatureMissing as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to set staking authority without signature"), + } +} diff --git a/stake-pool/program/tests/update_list_balance.rs b/stake-pool/program/tests/update_list_balance.rs index 12d0afff..f834ed64 100644 --- a/stake-pool/program/tests/update_list_balance.rs +++ b/stake-pool/program/tests/update_list_balance.rs @@ -31,7 +31,8 @@ async fn test_update_list_balance() { let stake_pool_accounts = StakePoolAccounts::new(); stake_pool_accounts .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) - .await; + .await + .unwrap(); // Add several accounts let mut stake_accounts: Vec = vec![]; @@ -77,3 +78,9 @@ async fn test_update_list_balance() { // TODO: Execute update list with updated clock } + +#[tokio::test] +async fn test_update_list_balance_with_uninitialized_validator_stake_list() {} // TODO + +#[tokio::test] +async fn test_update_list_balance_with_wrong_stake_state() {} // TODO diff --git a/stake-pool/program/tests/update_pool_balance.rs b/stake-pool/program/tests/update_pool_balance.rs index 049002e8..53bbd4d6 100644 --- a/stake-pool/program/tests/update_pool_balance.rs +++ b/stake-pool/program/tests/update_pool_balance.rs @@ -3,6 +3,11 @@ mod helpers; use helpers::*; +use solana_sdk::{ + instruction::InstructionError, signature::Keypair, signature::Signer, transaction::Transaction, + transaction::TransactionError, transport::TransportError, +}; +use spl_stake_pool::*; #[tokio::test] async fn test_update_pool_balance() { @@ -10,7 +15,53 @@ async fn test_update_pool_balance() { let stake_pool_accounts = StakePoolAccounts::new(); stake_pool_accounts .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) - .await; + .await + .unwrap(); // TODO: Waiting for the ability to advance clock (or modify account data) to finish the tests } + +#[tokio::test] +async fn test_update_pool_balance_with_wrong_validator_stake_list() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let wrong_stake_list_storage = Keypair::new(); + let mut transaction = Transaction::new_with_payer( + &[instruction::update_pool_balance( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &wrong_stake_list_storage.pubkey(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + + transaction.sign(&[&payer], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::InvalidValidatorStakeList as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to update pool balance with wrong validator stake list account"), + } +} + +#[tokio::test] +async fn test_update_pool_balance_with_uninitialized_validator_stake_list() {} // TODO + +#[tokio::test] +async fn test_update_pool_balance_with_out_of_dated_validators_balances() {} // TODO diff --git a/stake-pool/program/tests/vsa_add.rs b/stake-pool/program/tests/vsa_add.rs index 0dcaccb1..da933cb1 100644 --- a/stake-pool/program/tests/vsa_add.rs +++ b/stake-pool/program/tests/vsa_add.rs @@ -5,16 +5,34 @@ mod helpers; use helpers::*; use bincode::deserialize; -use solana_sdk::signature::{Keypair, Signer}; +use solana_program::hash::Hash; +use solana_program::instruction::AccountMeta; +use solana_program::instruction::Instruction; +use solana_program::sysvar; +use solana_program_test::BanksClient; +use solana_sdk::{ + instruction::InstructionError, + signature::{Keypair, Signer}, + transaction::Transaction, + transaction::TransactionError, + transport::TransportError, +}; use spl_stake_pool::*; -#[tokio::test] -async fn test_add_validator_stake_account() { +async fn setup() -> ( + BanksClient, + Keypair, + Hash, + StakePoolAccounts, + StakeAccount, + Keypair, +) { let (mut banks_client, payer, recent_blockhash) = program_test().start().await; let stake_pool_accounts = StakePoolAccounts::new(); stake_pool_accounts .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) - .await; + .await + .unwrap(); let user = Keypair::new(); @@ -36,7 +54,30 @@ async fn test_add_validator_stake_account() { &stake_pool_accounts.pool_mint.pubkey(), &user.pubkey(), ) - .await; + .await + .unwrap(); + + ( + banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + ) +} + +#[tokio::test] +async fn test_add_validator_stake_account() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + ) = setup().await; + let error = stake_pool_accounts .add_validator_stake_account( &mut banks_client, @@ -102,3 +143,407 @@ async fn test_add_validator_stake_account() { _ => panic!(), } } + +#[tokio::test] +async fn test_add_validator_stake_account_with_wrong_token_program_id() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + ) = setup().await; + + let mut transaction = Transaction::new_with_payer( + &[instruction::add_validator_stake_account( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.owner.pubkey(), + &stake_pool_accounts.deposit_authority, + &stake_pool_accounts.withdraw_authority, + &stake_pool_accounts.validator_stake_list.pubkey(), + &user_stake.stake_account, + &user_pool_account.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &stake::id(), + &stake::id(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError(_, error)) => { + assert_eq!(error, InstructionError::IncorrectProgramId); + } + _ => panic!("Wrong error occurs while try to add validator stake address with wrong token program ID"), + } +} + +#[tokio::test] +async fn test_add_validator_stake_account_with_wrong_pool_mint_account() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + ) = setup().await; + + let wrong_pool_mint = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::add_validator_stake_account( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.owner.pubkey(), + &stake_pool_accounts.deposit_authority, + &stake_pool_accounts.withdraw_authority, + &stake_pool_accounts.validator_stake_list.pubkey(), + &user_stake.stake_account, + &user_pool_account.pubkey(), + &wrong_pool_mint.pubkey(), + &spl_token::id(), + &stake::id(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::WrongPoolMint as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to add validator stake address with wrong pool mint account"), + } +} + +#[tokio::test] +async fn test_add_validator_stake_account_with_wrong_validator_stake_list_account() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + ) = setup().await; + + let wrong_validator_stake_list = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::add_validator_stake_account( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.owner.pubkey(), + &stake_pool_accounts.deposit_authority, + &stake_pool_accounts.withdraw_authority, + &wrong_validator_stake_list.pubkey(), + &user_stake.stake_account, + &user_pool_account.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &spl_token::id(), + &stake::id(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::InvalidValidatorStakeList as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to add validator stake address with wrong validator stake list account"), + } +} + +#[tokio::test] +async fn test_try_to_add_already_added_validator_stake_account() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + ) = setup().await; + + stake_pool_accounts + .add_validator_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.stake_account, + &user_pool_account.pubkey(), + ) + .await; + + let latest_blockhash = banks_client.get_recent_blockhash().await.unwrap(); + + let transaction_error = stake_pool_accounts + .add_validator_stake_account( + &mut banks_client, + &payer, + &latest_blockhash, + &user_stake.stake_account, + &user_pool_account.pubkey(), + ) + .await + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::ValidatorAlreadyAdded as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to add already added validator stake account"), + } +} + +#[tokio::test] +async fn test_not_owner_try_to_add_validator_stake_account() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + ) = setup().await; + + let malicious = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::add_validator_stake_account( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &malicious.pubkey(), + &stake_pool_accounts.deposit_authority, + &stake_pool_accounts.withdraw_authority, + &stake_pool_accounts.validator_stake_list.pubkey(), + &user_stake.stake_account, + &user_pool_account.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &spl_token::id(), + &stake::id(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &malicious], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::WrongOwner as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while malicious try to add validator stake account"), + } +} + +#[tokio::test] +async fn test_not_owner_try_to_add_validator_stake_account_without_signature() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + ) = setup().await; + + let accounts = vec![ + AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false), + AccountMeta::new_readonly(stake_pool_accounts.owner.pubkey(), false), + AccountMeta::new_readonly(stake_pool_accounts.deposit_authority, false), + AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false), + AccountMeta::new(stake_pool_accounts.validator_stake_list.pubkey(), false), + AccountMeta::new(user_stake.stake_account, false), + AccountMeta::new(user_pool_account.pubkey(), false), + AccountMeta::new(stake_pool_accounts.pool_mint.pubkey(), false), + AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(stake::id(), false), + ]; + let instruction = Instruction { + program_id: id(), + accounts, + data: instruction::StakePoolInstruction::AddValidatorStakeAccount + .serialize() + .unwrap(), + }; + + let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey())); + transaction.sign(&[&payer], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::SignatureMissing as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while malicious try to add validator stake account without signing transaction"), + } +} + +#[tokio::test] +async fn test_add_validator_stake_account_when_stake_acc_not_in_stake_state() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let user = Keypair::new(); + + let user_stake = StakeAccount::new_with_target_authority( + &stake_pool_accounts.deposit_authority, + &stake_pool_accounts.stake_pool.pubkey(), + ); + let user_stake_authority = Keypair::new(); + create_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.stake_pool, + &user_stake.stake_account, + &user_stake.vote.pubkey(), + &user_stake_authority.pubkey(), + &user_stake.target_authority, + ) + .await; + + let user_pool_account = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + let transaction_error = stake_pool_accounts + .add_validator_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.stake_account, + &user_pool_account.pubkey(), + ) + .await + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::WrongStakeState as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to add validator stake account when it isn't in stake state"), + } +} + +#[tokio::test] +async fn test_add_validator_stake_account_with_wrong_stake_program_id() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + ) = setup().await; + + let wrong_stake_program = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::add_validator_stake_account( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.owner.pubkey(), + &stake_pool_accounts.deposit_authority, + &stake_pool_accounts.withdraw_authority, + &stake_pool_accounts.validator_stake_list.pubkey(), + &user_stake.stake_account, + &user_pool_account.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &spl_token::id(), + &wrong_stake_program.pubkey(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError(_, error)) => { + assert_eq!(error, InstructionError::IncorrectProgramId); + } + _ => panic!( + "Wrong error occurs while try to add validator stake account with wrong stake program ID" + ), + } +} + +#[tokio::test] +async fn test_add_validator_stake_account_to_unupdated_stake_pool() {} // TODO + +#[tokio::test] +async fn test_add_validator_stake_account_with_uninitialized_validator_stake_list_account() {} // TODO diff --git a/stake-pool/program/tests/vsa_create.rs b/stake-pool/program/tests/vsa_create.rs index 6f08f13d..ab48b56e 100644 --- a/stake-pool/program/tests/vsa_create.rs +++ b/stake-pool/program/tests/vsa_create.rs @@ -6,8 +6,13 @@ use crate::solana_program::pubkey::Pubkey; use helpers::*; use bincode::deserialize; -use solana_sdk::signature::{Keypair, Signer}; -use solana_sdk::transaction::Transaction; +use solana_sdk::{ + instruction::InstructionError, + signature::{Keypair, Signer}, + transaction::Transaction, + transaction::TransactionError, + transport::TransportError, +}; use spl_stake_pool::*; #[tokio::test] @@ -16,7 +21,8 @@ async fn test_create_validator_stake_account() { let stake_pool_accounts = StakePoolAccounts::new(); stake_pool_accounts .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) - .await; + .await + .unwrap(); let validator = Pubkey::new_unique(); let user_stake_authority = Keypair::new(); @@ -60,3 +66,159 @@ async fn test_create_validator_stake_account() { _ => panic!(), } } + +#[tokio::test] +async fn test_create_validator_stake_account_with_incorrect_address() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let validator = Pubkey::new_unique(); + let user_stake_authority = Keypair::new(); + let user_withdraw_authority = Keypair::new(); + let stake_account = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::create_validator_stake_account( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &payer.pubkey(), + &stake_account.pubkey(), + &validator, + &user_stake_authority.pubkey(), + &user_withdraw_authority.pubkey(), + &solana_program::system_program::id(), + &stake::id(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::InvalidStakeAccountAddress as u32; + assert_eq!(error_index, program_error); + } + _ => panic!( + "Wrong error occurs while try to create validator stake account with incorrect address" + ), + } +} + +#[tokio::test] +async fn test_create_validator_stake_account_with_wrong_system_program() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let validator = Pubkey::new_unique(); + let user_stake_authority = Keypair::new(); + let user_withdraw_authority = Keypair::new(); + + let (stake_account, _) = processor::Processor::find_stake_address_for_validator( + &id(), + &validator, + &stake_pool_accounts.stake_pool.pubkey(), + ); + + let wrong_system_program = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::create_validator_stake_account( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &payer.pubkey(), + &stake_account, + &validator, + &user_stake_authority.pubkey(), + &user_withdraw_authority.pubkey(), + &wrong_system_program.pubkey(), + &stake::id(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError(_, error)) => { + assert_eq!(error, InstructionError::IncorrectProgramId); + } + _ => panic!( + "Wrong error occurs while try to create validator stake account with wrong token program ID" + ), + } +} + +#[tokio::test] +async fn test_create_validator_stake_account_with_wrong_stake_program() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let validator = Pubkey::new_unique(); + let user_stake_authority = Keypair::new(); + let user_withdraw_authority = Keypair::new(); + + let (stake_account, _) = processor::Processor::find_stake_address_for_validator( + &id(), + &validator, + &stake_pool_accounts.stake_pool.pubkey(), + ); + + let wrong_stake_program = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::create_validator_stake_account( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &payer.pubkey(), + &stake_account, + &validator, + &user_stake_authority.pubkey(), + &user_withdraw_authority.pubkey(), + &solana_program::system_program::id(), + &wrong_stake_program.pubkey(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError(_, error)) => { + assert_eq!(error, InstructionError::IncorrectProgramId); + } + _ => panic!( + "Wrong error occurs while try to create validator stake account with wrong stake program ID" + ), + } +} diff --git a/stake-pool/program/tests/vsa_remove.rs b/stake-pool/program/tests/vsa_remove.rs index 9ada8989..99aaeee1 100644 --- a/stake-pool/program/tests/vsa_remove.rs +++ b/stake-pool/program/tests/vsa_remove.rs @@ -4,17 +4,36 @@ mod helpers; use bincode::deserialize; use helpers::*; +use solana_program::hash::Hash; +use solana_program::instruction::AccountMeta; +use solana_program::instruction::Instruction; use solana_program::pubkey::Pubkey; -use solana_sdk::signature::{Keypair, Signer}; +use solana_program::sysvar; +use solana_program_test::BanksClient; +use solana_sdk::{ + instruction::InstructionError, + signature::{Keypair, Signer}, + transaction::Transaction, + transaction::TransactionError, + transport::TransportError, +}; use spl_stake_pool::*; -#[tokio::test] -async fn test_remove_validator_stake_account() { +async fn setup() -> ( + BanksClient, + Keypair, + Hash, + StakePoolAccounts, + StakeAccount, + Keypair, + Keypair, +) { let (mut banks_client, payer, recent_blockhash) = program_test().start().await; let stake_pool_accounts = StakePoolAccounts::new(); stake_pool_accounts .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) - .await; + .await + .unwrap(); let user = Keypair::new(); @@ -36,7 +55,9 @@ async fn test_remove_validator_stake_account() { &stake_pool_accounts.pool_mint.pubkey(), &user.pubkey(), ) - .await; + .await + .unwrap(); + let error = stake_pool_accounts .add_validator_stake_account( &mut banks_client, @@ -48,6 +69,29 @@ async fn test_remove_validator_stake_account() { .await; assert!(error.is_none()); + ( + banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + user, + ) +} + +#[tokio::test] +async fn test_remove_validator_stake_account() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + user, + ) = setup().await; + let tokens_to_burn = get_token_balance(&mut banks_client, &user_pool_account.pubkey()).await; delegate_tokens( &mut banks_client, @@ -104,3 +148,445 @@ async fn test_remove_validator_stake_account() { _ => panic!(), } } + +#[tokio::test] +async fn test_remove_validator_stake_account_with_wrong_stake_program_id() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + _, + ) = setup().await; + + let wrong_stake_program = Keypair::new(); + + let new_authority = Pubkey::new_unique(); + let mut transaction = Transaction::new_with_payer( + &[instruction::remove_validator_stake_account( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.owner.pubkey(), + &stake_pool_accounts.withdraw_authority, + &new_authority, + &stake_pool_accounts.validator_stake_list.pubkey(), + &user_stake.stake_account, + &user_pool_account.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &spl_token::id(), + &wrong_stake_program.pubkey(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + error, + )) => { + assert_eq!(error, InstructionError::IncorrectProgramId); + } + _ => panic!("Wrong error occurs while try to remove validator stake address with wrong stake program ID"), + } +} + +#[tokio::test] +async fn test_remove_validator_stake_account_with_wrong_token_program_id() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + _, + ) = setup().await; + + let wrong_token_program = Keypair::new(); + + let new_authority = Pubkey::new_unique(); + let mut transaction = Transaction::new_with_payer( + &[instruction::remove_validator_stake_account( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.owner.pubkey(), + &stake_pool_accounts.withdraw_authority, + &new_authority, + &stake_pool_accounts.validator_stake_list.pubkey(), + &user_stake.stake_account, + &user_pool_account.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &wrong_token_program.pubkey(), + &stake::id(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError(_, error)) => { + assert_eq!(error, InstructionError::IncorrectProgramId); + } + _ => panic!("Wrong error occurs while try to remove validator stake address with wrong token program ID"), + } +} + +#[tokio::test] +async fn test_remove_validator_stake_account_with_wrong_pool_mint_account() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + _, + ) = setup().await; + + let wrong_pool_mint = Keypair::new(); + + let new_authority = Pubkey::new_unique(); + let mut transaction = Transaction::new_with_payer( + &[instruction::remove_validator_stake_account( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.owner.pubkey(), + &stake_pool_accounts.withdraw_authority, + &new_authority, + &stake_pool_accounts.validator_stake_list.pubkey(), + &user_stake.stake_account, + &user_pool_account.pubkey(), + &wrong_pool_mint.pubkey(), + &spl_token::id(), + &stake::id(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::WrongPoolMint as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to remove validator stake address with wrong pool mint account"), + } +} + +#[tokio::test] +async fn test_remove_validator_stake_account_with_wrong_validator_stake_list_account() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + _, + ) = setup().await; + + let wrong_validator_stake_list = Keypair::new(); + + let new_authority = Pubkey::new_unique(); + let mut transaction = Transaction::new_with_payer( + &[instruction::remove_validator_stake_account( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.owner.pubkey(), + &stake_pool_accounts.withdraw_authority, + &new_authority, + &wrong_validator_stake_list.pubkey(), + &user_stake.stake_account, + &user_pool_account.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &spl_token::id(), + &stake::id(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::InvalidValidatorStakeList as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to remove validator stake address with wrong validator stake list account"), + } +} + +#[tokio::test] +async fn test_remove_already_removed_validator_stake_account() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + user, + ) = setup().await; + + let tokens_to_burn = get_token_balance(&mut banks_client, &user_pool_account.pubkey()).await; + delegate_tokens( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account.pubkey(), + &user, + &stake_pool_accounts.withdraw_authority, + tokens_to_burn, + ) + .await; + + let new_authority = Pubkey::new_unique(); + let error = stake_pool_accounts + .remove_validator_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.stake_account, + &user_pool_account.pubkey(), + &new_authority, + ) + .await; + assert!(error.is_none()); + + let latest_blockhash = banks_client.get_recent_blockhash().await.unwrap(); + + let transaction_error = stake_pool_accounts + .remove_validator_stake_account( + &mut banks_client, + &payer, + &latest_blockhash, + &user_stake.stake_account, + &user_pool_account.pubkey(), + &new_authority, + ) + .await + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::ValidatorNotFound as u32; + assert_eq!(error_index, program_error); + } + _ => { + panic!("Wrong error occurs while try to remove already removed validator stake address") + } + } +} + +#[tokio::test] +async fn test_not_owner_try_to_remove_validator_stake_account() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + _, + ) = setup().await; + + let malicious = Keypair::new(); + + let new_authority = Pubkey::new_unique(); + let mut transaction = Transaction::new_with_payer( + &[instruction::remove_validator_stake_account( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &malicious.pubkey(), + &stake_pool_accounts.withdraw_authority, + &new_authority, + &stake_pool_accounts.validator_stake_list.pubkey(), + &user_stake.stake_account, + &user_pool_account.pubkey(), + &stake_pool_accounts.pool_mint.pubkey(), + &spl_token::id(), + &stake::id(), + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer, &malicious], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::WrongOwner as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while not an owner try to remove validator stake address"), + } +} + +#[tokio::test] +async fn test_not_owner_try_to_remove_validator_stake_account_without_signature() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + user_stake, + user_pool_account, + _, + ) = setup().await; + + let new_authority = Pubkey::new_unique(); + + let accounts = vec![ + AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false), + AccountMeta::new_readonly(stake_pool_accounts.owner.pubkey(), false), + AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false), + AccountMeta::new_readonly(new_authority, false), + AccountMeta::new(stake_pool_accounts.validator_stake_list.pubkey(), false), + AccountMeta::new(user_stake.stake_account, false), + AccountMeta::new(user_pool_account.pubkey(), false), + AccountMeta::new(stake_pool_accounts.pool_mint.pubkey(), false), + AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(stake::id(), false), + ]; + let instruction = Instruction { + program_id: id(), + accounts, + data: instruction::StakePoolInstruction::RemoveValidatorStakeAccount + .serialize() + .unwrap(), + }; + + let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey())); + transaction.sign(&[&payer], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::SignatureMissing as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while malicious try to remove validator stake account without signing transaction"), + } +} + +#[tokio::test] +async fn test_remove_validator_stake_account_when_stake_acc_not_in_stake_state() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let user = Keypair::new(); + + let user_stake = StakeAccount::new_with_target_authority( + &stake_pool_accounts.deposit_authority, + &stake_pool_accounts.stake_pool.pubkey(), + ); + let user_stake_authority = Keypair::new(); + create_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.stake_pool, + &user_stake.stake_account, + &user_stake.vote.pubkey(), + &user_stake_authority.pubkey(), + &user_stake.target_authority, + ) + .await; + + let user_pool_account = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + let new_authority = Pubkey::new_unique(); + + let transaction_error = stake_pool_accounts + .remove_validator_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake.stake_account, + &user_pool_account.pubkey(), + &new_authority, + ) + .await + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::WrongStakeState as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to add validator stake account when it isn't in stake state"), + } +} + +#[tokio::test] +async fn test_remove_validator_stake_account_from_unupdated_stake_pool() {} // TODO + +#[tokio::test] +async fn test_remove_validator_stake_account_with_uninitialized_validator_stake_list_account() {} // TODO diff --git a/stake-pool/program/tests/withdraw.rs b/stake-pool/program/tests/withdraw.rs index 46059399..ed805da1 100644 --- a/stake-pool/program/tests/withdraw.rs +++ b/stake-pool/program/tests/withdraw.rs @@ -5,16 +5,34 @@ mod helpers; use helpers::*; use solana_program::pubkey::Pubkey; -use solana_sdk::signature::{Keypair, Signer}; +use solana_program::hash::Hash; +use solana_program_test::BanksClient; +use solana_sdk::{ + instruction::InstructionError, + signature::{Keypair, Signer}, + transaction::Transaction, + transaction::TransactionError, + transport::TransportError, +}; use spl_stake_pool::*; +use spl_token::error::TokenError; -#[tokio::test] -async fn test_stake_pool_withdraw() { +async fn setup() -> ( + BanksClient, + Keypair, + Hash, + StakePoolAccounts, + StakeAccount, + DepositInfo, + u64, + u64, +) { let (mut banks_client, payer, recent_blockhash) = program_test().start().await; let stake_pool_accounts = StakePoolAccounts::new(); stake_pool_accounts .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) - .await; + .await + .unwrap(); let validator_stake_account: StakeAccount = simple_add_validator_stake_account( &mut banks_client, @@ -48,6 +66,31 @@ async fn test_stake_pool_withdraw() { ) .await; + ( + banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + validator_stake_account, + deposit_info, + tokens_to_burn, + lamports_to_withdraw, + ) +} + +#[tokio::test] +async fn test_stake_pool_withdraw() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + validator_stake_account, + deposit_info, + tokens_to_burn, + lamports_to_withdraw, + ) = setup().await; + // Create stake account to withdraw to let user_stake_recipient = Keypair::new(); let initial_stake_lamports = create_blank_stake_account( @@ -94,7 +137,8 @@ async fn test_stake_pool_withdraw() { &new_authority, lamports_to_withdraw, ) - .await; + .await + .unwrap(); // Check pool stats let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await; @@ -151,3 +195,639 @@ async fn test_stake_pool_withdraw() { initial_stake_lamports + lamports_to_withdraw ); } + +#[tokio::test] +async fn test_stake_pool_withdraw_with_wrong_stake_program() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + validator_stake_account, + deposit_info, + _, + lamports_to_withdraw, + ) = setup().await; + + // Create stake account to withdraw to + let user_stake_recipient = Keypair::new(); + + let new_authority = Pubkey::new_unique(); + let wrong_stake_program = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::withdraw( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.validator_stake_list.pubkey(), + &stake_pool_accounts.withdraw_authority, + &validator_stake_account.stake_account, + &user_stake_recipient.pubkey(), + &new_authority, + &deposit_info.user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &spl_token::id(), + &wrong_stake_program.pubkey(), + lamports_to_withdraw, + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError(_, error)) => { + assert_eq!(error, InstructionError::IncorrectProgramId); + } + _ => panic!("Wrong error occurs while try to withdraw with wrong stake program ID"), + } +} + +#[tokio::test] +async fn test_stake_pool_withdraw_with_wrong_withdraw_authority() { + let ( + mut banks_client, + payer, + recent_blockhash, + mut stake_pool_accounts, + validator_stake_account, + deposit_info, + _, + lamports_to_withdraw, + ) = setup().await; + + // Create stake account to withdraw to + let user_stake_recipient = Keypair::new(); + + let new_authority = Pubkey::new_unique(); + stake_pool_accounts.withdraw_authority = Keypair::new().pubkey(); + + let transaction_error = stake_pool_accounts + .withdraw_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake_recipient.pubkey(), + &deposit_info.user_pool_account, + &validator_stake_account.stake_account, + &new_authority, + lamports_to_withdraw, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::InvalidProgramAddress as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to withdraw with wrong withdraw authority"), + } +} + +#[tokio::test] +async fn test_stake_pool_withdraw_with_wrong_token_program_id() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + validator_stake_account, + deposit_info, + _, + lamports_to_withdraw, + ) = setup().await; + + // Create stake account to withdraw to + let user_stake_recipient = Keypair::new(); + + let new_authority = Pubkey::new_unique(); + let wrong_token_program = Keypair::new(); + + let mut transaction = Transaction::new_with_payer( + &[instruction::withdraw( + &id(), + &stake_pool_accounts.stake_pool.pubkey(), + &stake_pool_accounts.validator_stake_list.pubkey(), + &stake_pool_accounts.withdraw_authority, + &validator_stake_account.stake_account, + &user_stake_recipient.pubkey(), + &new_authority, + &deposit_info.user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &wrong_token_program.pubkey(), + &stake::id(), + lamports_to_withdraw, + ) + .unwrap()], + Some(&payer.pubkey()), + ); + transaction.sign(&[&payer], recent_blockhash); + let transaction_error = banks_client + .process_transaction(transaction) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError(_, error)) => { + assert_eq!(error, InstructionError::IncorrectProgramId); + } + _ => panic!("Wrong error occurs while try to withdraw with wrong token program ID"), + } +} + +#[tokio::test] +async fn test_stake_pool_withdraw_with_wrong_validator_stake_list() { + let ( + mut banks_client, + payer, + recent_blockhash, + mut stake_pool_accounts, + validator_stake_account, + deposit_info, + _, + lamports_to_withdraw, + ) = setup().await; + + // Create stake account to withdraw to + let user_stake_recipient = Keypair::new(); + + let new_authority = Pubkey::new_unique(); + stake_pool_accounts.validator_stake_list = Keypair::new(); + + let transaction_error = stake_pool_accounts + .withdraw_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake_recipient.pubkey(), + &deposit_info.user_pool_account, + &validator_stake_account.stake_account, + &new_authority, + lamports_to_withdraw, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::InvalidValidatorStakeList as u32; + assert_eq!(error_index, program_error); + } + _ => panic!( + "Wrong error occurs while try to withdraw with wrong validator stake list account" + ), + } +} + +#[tokio::test] +async fn test_stake_pool_withdraw_when_stake_acc_not_in_stake_state() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let validator_stake_account = StakeAccount::new_with_target_authority( + &stake_pool_accounts.deposit_authority, + &stake_pool_accounts.stake_pool.pubkey(), + ); + + let user_stake_authority = Keypair::new(); + create_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &validator_stake_account.stake_pool, + &validator_stake_account.stake_account, + &validator_stake_account.vote.pubkey(), + &user_stake_authority.pubkey(), + &validator_stake_account.target_authority, + ) + .await; + + let user = Keypair::new(); + // make stake account + let user_stake = Keypair::new(); + let lockup = stake::Lockup::default(); + let authorized = stake::Authorized { + staker: stake_pool_accounts.deposit_authority, + withdrawer: stake_pool_accounts.deposit_authority, + }; + create_independent_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake, + &authorized, + &lockup, + ) + .await; + // make pool token account + let user_pool_account = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + let user_pool_account = user_pool_account.pubkey(); + let pool_tokens = get_token_balance(&mut banks_client, &user_pool_account).await; + + let tokens_to_burn = pool_tokens / 4; + let lamports_to_withdraw = tokens_to_burn; // For now math is 1:1 + + // Delegate tokens for burning + delegate_tokens( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &user, + &stake_pool_accounts.withdraw_authority, + tokens_to_burn, + ) + .await; + + // Create stake account to withdraw to + let user_stake_recipient = Keypair::new(); + + let new_authority = Pubkey::new_unique(); + + let transaction_error = stake_pool_accounts + .withdraw_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake_recipient.pubkey(), + &user_pool_account, + &validator_stake_account.stake_account, + &new_authority, + lamports_to_withdraw, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::WrongStakeState as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to withdraw when stake acc not in stake state"), + } +} + +#[tokio::test] +async fn test_stake_pool_withdraw_from_unknown_validator() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let validator_stake_account = StakeAccount::new_with_target_authority( + &stake_pool_accounts.deposit_authority, + &stake_pool_accounts.stake_pool.pubkey(), + ); + validator_stake_account + .create_and_delegate(&mut banks_client, &payer, &recent_blockhash) + .await; + + let user_stake = StakeAccount::new_with_target_authority( + &stake_pool_accounts.deposit_authority, + &stake_pool_accounts.stake_pool.pubkey(), + ); + user_stake + .create_and_delegate(&mut banks_client, &payer, &recent_blockhash) + .await; + + let user_pool_account = Keypair::new(); + let user = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + let user = Keypair::new(); + // make stake account + let user_stake = Keypair::new(); + let lockup = stake::Lockup::default(); + let authorized = stake::Authorized { + staker: stake_pool_accounts.deposit_authority, + withdrawer: stake_pool_accounts.deposit_authority, + }; + create_independent_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake, + &authorized, + &lockup, + ) + .await; + // make pool token account + let user_pool_account = Keypair::new(); + create_token_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &stake_pool_accounts.pool_mint.pubkey(), + &user.pubkey(), + ) + .await + .unwrap(); + + let user_pool_account = user_pool_account.pubkey(); + let pool_tokens = get_token_balance(&mut banks_client, &user_pool_account).await; + + let tokens_to_burn = pool_tokens / 4; + let lamports_to_withdraw = tokens_to_burn; // For now math is 1:1 + + // Delegate tokens for burning + delegate_tokens( + &mut banks_client, + &payer, + &recent_blockhash, + &user_pool_account, + &user, + &stake_pool_accounts.withdraw_authority, + tokens_to_burn, + ) + .await; + + // Create stake account to withdraw to + let user_stake_recipient = Keypair::new(); + + let new_authority = Pubkey::new_unique(); + + let transaction_error = stake_pool_accounts + .withdraw_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake_recipient.pubkey(), + &user_pool_account, + &validator_stake_account.stake_account, + &new_authority, + lamports_to_withdraw, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = error::StakePoolError::ValidatorNotFound as u32; + assert_eq!(error_index, program_error); + } + _ => panic!("Wrong error occurs while try to do withdraw from unknown validator"), + } +} + +#[tokio::test] +async fn test_stake_pool_double_withdraw_to_the_same_account() { + let ( + mut banks_client, + payer, + recent_blockhash, + stake_pool_accounts, + validator_stake_account, + deposit_info, + _, + lamports_to_withdraw, + ) = setup().await; + + // Create stake account to withdraw to + let user_stake_recipient = Keypair::new(); + create_blank_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake_recipient, + ) + .await; + + let new_authority = Pubkey::new_unique(); + stake_pool_accounts + .withdraw_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake_recipient.pubkey(), + &deposit_info.user_pool_account, + &validator_stake_account.stake_account, + &new_authority, + lamports_to_withdraw, + ) + .await + .unwrap(); + + let latest_blockhash = banks_client.get_recent_blockhash().await.unwrap(); + + let transaction_error = stake_pool_accounts + .withdraw_stake( + &mut banks_client, + &payer, + &latest_blockhash, + &user_stake_recipient.pubkey(), + &deposit_info.user_pool_account, + &validator_stake_account.stake_account, + &new_authority, + lamports_to_withdraw, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError(_, error)) => { + assert_eq!(error, InstructionError::InvalidAccountData); + } + _ => panic!("Wrong error occurs while try to do double withdraw"), + } +} + +#[tokio::test] +async fn test_stake_pool_withdraw_token_delegate_was_not_setup() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let validator_stake_account: StakeAccount = simple_add_validator_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &stake_pool_accounts, + ) + .await; + + let deposit_info: DepositInfo = simple_deposit( + &mut banks_client, + &payer, + &recent_blockhash, + &stake_pool_accounts, + &validator_stake_account, + ) + .await; + + let tokens_to_burn = deposit_info.pool_tokens / 4; + let lamports_to_withdraw = tokens_to_burn; // For now math is 1:1 + + // Create stake account to withdraw to + let user_stake_recipient = Keypair::new(); + create_blank_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake_recipient, + ) + .await; + + let new_authority = Pubkey::new_unique(); + let transaction_error = stake_pool_accounts + .withdraw_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake_recipient.pubkey(), + &deposit_info.user_pool_account, + &validator_stake_account.stake_account, + &new_authority, + lamports_to_withdraw, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = TokenError::OwnerMismatch as u32; + assert_eq!(error_index, program_error); + } + _ => panic!( + "Wrong error occurs while try to do withdraw without token delegation for burn before" + ), + } +} + +#[tokio::test] +async fn test_stake_pool_withdraw_with_low_delegation() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + let stake_pool_accounts = StakePoolAccounts::new(); + stake_pool_accounts + .initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash) + .await + .unwrap(); + + let validator_stake_account: StakeAccount = simple_add_validator_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &stake_pool_accounts, + ) + .await; + + let deposit_info: DepositInfo = simple_deposit( + &mut banks_client, + &payer, + &recent_blockhash, + &stake_pool_accounts, + &validator_stake_account, + ) + .await; + + let tokens_to_burn = deposit_info.pool_tokens / 4; + let lamports_to_withdraw = tokens_to_burn; // For now math is 1:1 + + // Delegate tokens for burning + delegate_tokens( + &mut banks_client, + &payer, + &recent_blockhash, + &deposit_info.user_pool_account, + &deposit_info.user, + &stake_pool_accounts.withdraw_authority, + 1, + ) + .await; + + // Create stake account to withdraw to + let user_stake_recipient = Keypair::new(); + create_blank_stake_account( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake_recipient, + ) + .await; + + let new_authority = Pubkey::new_unique(); + let transaction_error = stake_pool_accounts + .withdraw_stake( + &mut banks_client, + &payer, + &recent_blockhash, + &user_stake_recipient.pubkey(), + &deposit_info.user_pool_account, + &validator_stake_account.stake_account, + &new_authority, + lamports_to_withdraw, + ) + .await + .err() + .unwrap(); + + match transaction_error { + TransportError::TransactionError(TransactionError::InstructionError( + _, + InstructionError::Custom(error_index), + )) => { + let program_error = TokenError::InsufficientFunds as u32; + assert_eq!(error_index, program_error); + } + _ => panic!( + "Wrong error occurs while try to do withdraw with not enough delegated tokens to burn" + ), + } +}