spl-stake-pool 0.6.4

Solana Program Library Stake Pool
Documentation
#![cfg(feature = "test-bpf")]

mod helpers;

use {
    bincode::deserialize,
    borsh::BorshSerialize,
    helpers::*,
    solana_program::{
        borsh::try_from_slice_unchecked,
        instruction::{AccountMeta, Instruction, InstructionError},
        pubkey::Pubkey,
        stake, sysvar,
    },
    solana_program_test::*,
    solana_sdk::{
        signature::{Keypair, Signer},
        transaction::Transaction,
        transaction::TransactionError,
        transport::TransportError,
    },
    spl_stake_pool::{error::StakePoolError, id, instruction, minimum_stake_lamports, state},
    spl_token::error as token_error,
};

async fn setup() -> (
    ProgramTestContext,
    StakePoolAccounts,
    ValidatorStakeAccount,
    Keypair,
    Pubkey,
    Pubkey,
    u64,
) {
    let mut context = program_test().start_with_context().await;

    let stake_pool_accounts = StakePoolAccounts::new();
    stake_pool_accounts
        .initialize_stake_pool(
            &mut context.banks_client,
            &context.payer,
            &context.last_blockhash,
            1,
        )
        .await
        .unwrap();

    let validator_stake_account = simple_add_validator_to_pool(
        &mut context.banks_client,
        &context.payer,
        &context.last_blockhash,
        &stake_pool_accounts,
    )
    .await;

    let first_normal_slot = context.genesis_config().epoch_schedule.first_normal_slot;
    let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
    let mut slot = first_normal_slot;
    context.warp_to_slot(slot).unwrap();

    let user = Keypair::new();
    // make stake account
    let deposit_stake = Keypair::new();
    let lockup = stake::state::Lockup::default();

    let authorized = stake::state::Authorized {
        staker: user.pubkey(),
        withdrawer: user.pubkey(),
    };

    let stake_lamports = create_independent_stake_account(
        &mut context.banks_client,
        &context.payer,
        &context.last_blockhash,
        &deposit_stake,
        &authorized,
        &lockup,
        TEST_STAKE_AMOUNT,
    )
    .await;

    delegate_stake_account(
        &mut context.banks_client,
        &context.payer,
        &context.last_blockhash,
        &deposit_stake.pubkey(),
        &user,
        &validator_stake_account.vote.pubkey(),
    )
    .await;

    slot += slots_per_epoch;
    context.warp_to_slot(slot).unwrap();
    stake_pool_accounts
        .update_all(
            &mut context.banks_client,
            &context.payer,
            &context.last_blockhash,
            &[validator_stake_account.vote.pubkey()],
            false,
        )
        .await;

    // make pool token account
    let pool_token_account = Keypair::new();
    create_token_account(
        &mut context.banks_client,
        &context.payer,
        &context.last_blockhash,
        &pool_token_account,
        &stake_pool_accounts.pool_mint.pubkey(),
        &user.pubkey(),
    )
    .await
    .unwrap();

    (
        context,
        stake_pool_accounts,
        validator_stake_account,
        user,
        deposit_stake.pubkey(),
        pool_token_account.pubkey(),
        stake_lamports,
    )
}

#[tokio::test]
async fn success() {
    let (
        mut context,
        stake_pool_accounts,
        validator_stake_account,
        user,
        deposit_stake,
        pool_token_account,
        stake_lamports,
    ) = setup().await;

    let rent = context.banks_client.get_rent().await.unwrap();
    let stake_rent = rent.minimum_balance(std::mem::size_of::<stake::state::StakeState>());

    // Save stake pool state before depositing
    let pre_stake_pool = get_account(
        &mut context.banks_client,
        &stake_pool_accounts.stake_pool.pubkey(),
    )
    .await;
    let pre_stake_pool =
        try_from_slice_unchecked::<state::StakePool>(pre_stake_pool.data.as_slice()).unwrap();

    // Save validator stake account record before depositing
    let validator_list = get_account(
        &mut context.banks_client,
        &stake_pool_accounts.validator_list.pubkey(),
    )
    .await;
    let validator_list =
        try_from_slice_unchecked::<state::ValidatorList>(validator_list.data.as_slice()).unwrap();
    let pre_validator_stake_item = validator_list
        .find(&validator_stake_account.vote.pubkey())
        .unwrap();

    // Save reserve state before depositing
    let pre_reserve_lamports = get_account(
        &mut context.banks_client,
        &stake_pool_accounts.reserve_stake.pubkey(),
    )
    .await
    .lamports;

    let error = stake_pool_accounts
        .deposit_stake(
            &mut context.banks_client,
            &context.payer,
            &context.last_blockhash,
            &deposit_stake,
            &pool_token_account,
            &validator_stake_account.stake_account,
            &user,
        )
        .await;
    assert!(error.is_none());

    // Original stake account should be drained
    assert!(context
        .banks_client
        .get_account(deposit_stake)
        .await
        .expect("get_account")
        .is_none());

    let tokens_issued = stake_lamports; // For now tokens are 1:1 to stake

    // Stake pool should add its balance to the pool balance
    let post_stake_pool = get_account(
        &mut context.banks_client,
        &stake_pool_accounts.stake_pool.pubkey(),
    )
    .await;
    let post_stake_pool =
        try_from_slice_unchecked::<state::StakePool>(post_stake_pool.data.as_slice()).unwrap();
    assert_eq!(
        post_stake_pool.total_lamports,
        pre_stake_pool.total_lamports + stake_lamports
    );
    assert_eq!(
        post_stake_pool.pool_token_supply,
        pre_stake_pool.pool_token_supply + tokens_issued
    );

    // Check minted tokens
    let user_token_balance =
        get_token_balance(&mut context.banks_client, &pool_token_account).await;
    let tokens_issued_user = tokens_issued
        - post_stake_pool
            .calc_pool_tokens_sol_deposit_fee(stake_rent)
            .unwrap()
        - post_stake_pool
            .calc_pool_tokens_stake_deposit_fee(stake_lamports - stake_rent)
            .unwrap();
    assert_eq!(user_token_balance, tokens_issued_user);

    // Check balances in validator stake account list storage
    let validator_list = get_account(
        &mut context.banks_client,
        &stake_pool_accounts.validator_list.pubkey(),
    )
    .await;
    let validator_list =
        try_from_slice_unchecked::<state::ValidatorList>(validator_list.data.as_slice()).unwrap();
    let post_validator_stake_item = validator_list
        .find(&validator_stake_account.vote.pubkey())
        .unwrap();
    assert_eq!(
        post_validator_stake_item.stake_lamports(),
        pre_validator_stake_item.stake_lamports() + stake_lamports - stake_rent,
    );

    // Check validator stake account actual SOL balance
    let validator_stake_account = get_account(
        &mut context.banks_client,
        &validator_stake_account.stake_account,
    )
    .await;
    let stake_state =
        deserialize::<stake::state::StakeState>(&validator_stake_account.data).unwrap();
    let meta = stake_state.meta().unwrap();
    assert_eq!(
        validator_stake_account.lamports - minimum_stake_lamports(&meta),
        post_validator_stake_item.stake_lamports()
    );
    assert_eq!(post_validator_stake_item.transient_stake_lamports, 0);

    // Check reserve
    let post_reserve_lamports = get_account(
        &mut context.banks_client,
        &stake_pool_accounts.reserve_stake.pubkey(),
    )
    .await
    .lamports;
    assert_eq!(post_reserve_lamports, pre_reserve_lamports + stake_rent);
}

#[tokio::test]
async fn success_with_extra_stake_lamports() {
    let (
        mut context,
        stake_pool_accounts,
        validator_stake_account,
        user,
        deposit_stake,
        pool_token_account,
        stake_lamports,
    ) = setup().await;

    let extra_lamports = TEST_STAKE_AMOUNT * 3 + 1;

    transfer(
        &mut context.banks_client,
        &context.payer,
        &context.last_blockhash,
        &deposit_stake,
        extra_lamports,
    )
    .await;

    let referrer = Keypair::new();
    let referrer_token_account = Keypair::new();
    create_token_account(
        &mut context.banks_client,
        &context.payer,
        &context.last_blockhash,
        &referrer_token_account,
        &stake_pool_accounts.pool_mint.pubkey(),
        &referrer.pubkey(),
    )
    .await
    .unwrap();

    let referrer_balance_pre =
        get_token_balance(&mut context.banks_client, &referrer_token_account.pubkey()).await;

    let manager_pool_balance_pre = get_token_balance(
        &mut context.banks_client,
        &stake_pool_accounts.pool_fee_account.pubkey(),
    )
    .await;

    let rent = context.banks_client.get_rent().await.unwrap();
    let stake_rent = rent.minimum_balance(std::mem::size_of::<stake::state::StakeState>());

    // Save stake pool state before depositing
    let pre_stake_pool = get_account(
        &mut context.banks_client,
        &stake_pool_accounts.stake_pool.pubkey(),
    )
    .await;
    let pre_stake_pool =
        try_from_slice_unchecked::<state::StakePool>(pre_stake_pool.data.as_slice()).unwrap();

    // Save validator stake account record before depositing
    let validator_list = get_account(
        &mut context.banks_client,
        &stake_pool_accounts.validator_list.pubkey(),
    )
    .await;
    let validator_list =
        try_from_slice_unchecked::<state::ValidatorList>(validator_list.data.as_slice()).unwrap();
    let pre_validator_stake_item = validator_list
        .find(&validator_stake_account.vote.pubkey())
        .unwrap();

    // Save reserve state before depositing
    let pre_reserve_lamports = get_account(
        &mut context.banks_client,
        &stake_pool_accounts.reserve_stake.pubkey(),
    )
    .await
    .lamports;

    let error = stake_pool_accounts
        .deposit_stake_with_referral(
            &mut context.banks_client,
            &context.payer,
            &context.last_blockhash,
            &deposit_stake,
            &pool_token_account,
            &validator_stake_account.stake_account,
            &user,
            &referrer_token_account.pubkey(),
        )
        .await;
    assert!(error.is_none());

    // Original stake account should be drained
    assert!(context
        .banks_client
        .get_account(deposit_stake)
        .await
        .expect("get_account")
        .is_none());

    let tokens_issued = stake_lamports + extra_lamports;
    // For now tokens are 1:1 to stake

    // Stake pool should add its balance to the pool balance

    // The extra lamports will not get recorded in total stake lamports unless
    // update_stake_pool_balance is called
    let post_stake_pool = get_account(
        &mut context.banks_client,
        &stake_pool_accounts.stake_pool.pubkey(),
    )
    .await;

    let post_stake_pool =
        try_from_slice_unchecked::<state::StakePool>(post_stake_pool.data.as_slice()).unwrap();
    assert_eq!(
        post_stake_pool.total_lamports,
        pre_stake_pool.total_lamports + extra_lamports + stake_lamports
    );
    assert_eq!(
        post_stake_pool.pool_token_supply,
        pre_stake_pool.pool_token_supply + tokens_issued
    );

    // Check minted tokens
    let user_token_balance =
        get_token_balance(&mut context.banks_client, &pool_token_account).await;

    let fee_tokens = post_stake_pool
        .calc_pool_tokens_sol_deposit_fee(extra_lamports + stake_rent)
        .unwrap()
        + post_stake_pool
            .calc_pool_tokens_stake_deposit_fee(stake_lamports - stake_rent)
            .unwrap();
    let tokens_issued_user = tokens_issued - fee_tokens;
    assert_eq!(user_token_balance, tokens_issued_user);

    let referrer_balance_post =
        get_token_balance(&mut context.banks_client, &referrer_token_account.pubkey()).await;

    let referral_fee = stake_pool_accounts.calculate_referral_fee(fee_tokens);
    let manager_fee = fee_tokens - referral_fee;

    assert_eq!(referrer_balance_post - referrer_balance_pre, referral_fee);

    let manager_pool_balance_post = get_token_balance(
        &mut context.banks_client,
        &stake_pool_accounts.pool_fee_account.pubkey(),
    )
    .await;
    assert_eq!(
        manager_pool_balance_post - manager_pool_balance_pre,
        manager_fee
    );

    // Check balances in validator stake account list storage
    let validator_list = get_account(
        &mut context.banks_client,
        &stake_pool_accounts.validator_list.pubkey(),
    )
    .await;
    let validator_list =
        try_from_slice_unchecked::<state::ValidatorList>(validator_list.data.as_slice()).unwrap();
    let post_validator_stake_item = validator_list
        .find(&validator_stake_account.vote.pubkey())
        .unwrap();
    assert_eq!(
        post_validator_stake_item.stake_lamports(),
        pre_validator_stake_item.stake_lamports() + stake_lamports - stake_rent,
    );

    // Check validator stake account actual SOL balance
    let validator_stake_account = get_account(
        &mut context.banks_client,
        &validator_stake_account.stake_account,
    )
    .await;
    let stake_state =
        deserialize::<stake::state::StakeState>(&validator_stake_account.data).unwrap();
    let meta = stake_state.meta().unwrap();
    assert_eq!(
        validator_stake_account.lamports - minimum_stake_lamports(&meta),
        post_validator_stake_item.stake_lamports()
    );
    assert_eq!(post_validator_stake_item.transient_stake_lamports, 0);

    // Check reserve
    let post_reserve_lamports = get_account(
        &mut context.banks_client,
        &stake_pool_accounts.reserve_stake.pubkey(),
    )
    .await
    .lamports;
    assert_eq!(
        post_reserve_lamports,
        pre_reserve_lamports + stake_rent + extra_lamports
    );
}

#[tokio::test]
async fn fail_with_wrong_stake_program_id() {
    let (
        mut context,
        stake_pool_accounts,
        validator_stake_account,
        _user,
        deposit_stake,
        pool_token_account,
        _stake_lamports,
    ) = setup().await;

    let wrong_stake_program = Pubkey::new_unique();

    let accounts = vec![
        AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false),
        AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false),
        AccountMeta::new_readonly(stake_pool_accounts.stake_deposit_authority, false),
        AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false),
        AccountMeta::new(deposit_stake, false),
        AccountMeta::new(validator_stake_account.stake_account, false),
        AccountMeta::new(stake_pool_accounts.reserve_stake.pubkey(), false),
        AccountMeta::new(pool_token_account, false),
        AccountMeta::new(stake_pool_accounts.pool_fee_account.pubkey(), false),
        AccountMeta::new(stake_pool_accounts.pool_fee_account.pubkey(), false),
        AccountMeta::new(stake_pool_accounts.pool_mint.pubkey(), false),
        AccountMeta::new_readonly(sysvar::clock::id(), false),
        AccountMeta::new_readonly(sysvar::rent::id(), false),
        AccountMeta::new_readonly(sysvar::stake_history::id(), false),
        AccountMeta::new_readonly(spl_token::id(), false),
        AccountMeta::new_readonly(wrong_stake_program, false),
    ];
    let instruction = Instruction {
        program_id: id(),
        accounts,
        data: instruction::StakePoolInstruction::DepositStake
            .try_to_vec()
            .unwrap(),
    };

    let mut transaction =
        Transaction::new_with_payer(&[instruction], Some(&context.payer.pubkey()));
    transaction.sign(&[&context.payer], context.last_blockhash);
    #[allow(clippy::useless_conversion)] // Remove during upgrade to 1.10
    let transaction_error = context
        .banks_client
        .process_transaction(transaction)
        .await
        .err()
        .unwrap()
        .into();

    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 fail_with_wrong_token_program_id() {
    let (
        mut context,
        stake_pool_accounts,
        validator_stake_account,
        user,
        deposit_stake,
        pool_token_account,
        _stake_lamports,
    ) = setup().await;

    let wrong_token_program = Keypair::new();

    let mut transaction = Transaction::new_with_payer(
        &instruction::deposit_stake(
            &id(),
            &stake_pool_accounts.stake_pool.pubkey(),
            &stake_pool_accounts.validator_list.pubkey(),
            &stake_pool_accounts.withdraw_authority,
            &deposit_stake,
            &user.pubkey(),
            &validator_stake_account.stake_account,
            &stake_pool_accounts.reserve_stake.pubkey(),
            &pool_token_account,
            &stake_pool_accounts.pool_fee_account.pubkey(),
            &stake_pool_accounts.pool_fee_account.pubkey(),
            &stake_pool_accounts.pool_mint.pubkey(),
            &wrong_token_program.pubkey(),
        ),
        Some(&context.payer.pubkey()),
    );
    transaction.sign(&[&context.payer, &user], context.last_blockhash);
    #[allow(clippy::useless_conversion)] // Remove during upgrade to 1.10
    let transaction_error = context
        .banks_client
        .process_transaction(transaction)
        .await
        .err()
        .unwrap()
        .into();

    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 fail_with_wrong_validator_list_account() {
    let (
        mut context,
        mut stake_pool_accounts,
        validator_stake_account,
        user,
        deposit_stake,
        pool_token_account,
        _stake_lamports,
    ) = setup().await;

    let wrong_validator_list = Keypair::new();
    stake_pool_accounts.validator_list = wrong_validator_list;

    let transaction_error = stake_pool_accounts
        .deposit_stake(
            &mut context.banks_client,
            &context.payer,
            &context.last_blockhash,
            &deposit_stake,
            &pool_token_account,
            &validator_stake_account.stake_account,
            &user,
        )
        .await
        .unwrap()
        .unwrap();

    match transaction_error {
        TransactionError::InstructionError(
            _,
            InstructionError::Custom(error_index),
        ) => {
            let program_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 fail_with_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, 1)
        .await
        .unwrap();

    let unknown_stake = create_unknown_validator_stake(
        &mut banks_client,
        &payer,
        &recent_blockhash,
        &stake_pool_accounts.stake_pool.pubkey(),
    )
    .await;

    let user = Keypair::new();
    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();

    // make stake account
    let user_stake = Keypair::new();
    let lockup = stake::state::Lockup::default();
    let authorized = stake::state::Authorized {
        staker: user.pubkey(),
        withdrawer: user.pubkey(),
    };
    create_independent_stake_account(
        &mut banks_client,
        &payer,
        &recent_blockhash,
        &user_stake,
        &authorized,
        &lockup,
        TEST_STAKE_AMOUNT,
    )
    .await;
    delegate_stake_account(
        &mut banks_client,
        &payer,
        &recent_blockhash,
        &user_stake.pubkey(),
        &user,
        &unknown_stake.vote.pubkey(),
    )
    .await;

    let error = stake_pool_accounts
        .deposit_stake(
            &mut banks_client,
            &payer,
            &recent_blockhash,
            &user_stake.pubkey(),
            &user_pool_account.pubkey(),
            &unknown_stake.stake_account,
            &user,
        )
        .await
        .unwrap()
        .unwrap();

    assert_eq!(
        error,
        TransactionError::InstructionError(
            2,
            InstructionError::Custom(StakePoolError::InvalidStakeAccountAddress as u32)
        )
    );
}

#[tokio::test]
async fn fail_with_wrong_withdraw_authority() {
    let (
        mut context,
        mut stake_pool_accounts,
        validator_stake_account,
        user,
        deposit_stake,
        pool_token_account,
        _stake_lamports,
    ) = setup().await;

    stake_pool_accounts.withdraw_authority = Pubkey::new_unique();

    let transaction_error = stake_pool_accounts
        .deposit_stake(
            &mut context.banks_client,
            &context.payer,
            &context.last_blockhash,
            &deposit_stake,
            &pool_token_account,
            &validator_stake_account.stake_account,
            &user,
        )
        .await
        .unwrap()
        .unwrap();

    match transaction_error {
        TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
            let program_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 fail_with_wrong_mint_for_receiver_acc() {
    let (
        mut context,
        stake_pool_accounts,
        validator_stake_account,
        user,
        deposit_stake,
        _pool_token_account,
        _stake_lamports,
    ) = setup().await;

    let outside_mint = Keypair::new();
    let outside_withdraw_auth = Keypair::new();
    let outside_manager = Keypair::new();
    let outside_pool_fee_acc = Keypair::new();

    create_mint(
        &mut context.banks_client,
        &context.payer,
        &context.last_blockhash,
        &outside_mint,
        &outside_withdraw_auth.pubkey(),
    )
    .await
    .unwrap();

    create_token_account(
        &mut context.banks_client,
        &context.payer,
        &context.last_blockhash,
        &outside_pool_fee_acc,
        &outside_mint.pubkey(),
        &outside_manager.pubkey(),
    )
    .await
    .unwrap();

    let transaction_error = stake_pool_accounts
        .deposit_stake(
            &mut context.banks_client,
            &context.payer,
            &context.last_blockhash,
            &deposit_stake,
            &outside_pool_fee_acc.pubkey(),
            &validator_stake_account.stake_account,
            &user,
        )
        .await
        .unwrap()
        .unwrap();

    match transaction_error {
        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 from receiver account")
        }
    }
}

#[tokio::test]
async fn fail_with_uninitialized_validator_list() {} // TODO

#[tokio::test]
async fn fail_with_out_of_dated_pool_balances() {} // TODO

#[tokio::test]
async fn success_with_preferred_deposit() {
    let (
        mut context,
        stake_pool_accounts,
        validator_stake,
        user,
        deposit_stake,
        pool_token_account,
        _stake_lamports,
    ) = setup().await;

    stake_pool_accounts
        .set_preferred_validator(
            &mut context.banks_client,
            &context.payer,
            &context.last_blockhash,
            instruction::PreferredValidatorType::Deposit,
            Some(validator_stake.vote.pubkey()),
        )
        .await;

    let error = stake_pool_accounts
        .deposit_stake(
            &mut context.banks_client,
            &context.payer,
            &context.last_blockhash,
            &deposit_stake,
            &pool_token_account,
            &validator_stake.stake_account,
            &user,
        )
        .await;
    assert!(error.is_none());
}

#[tokio::test]
async fn fail_with_wrong_preferred_deposit() {
    let (
        mut context,
        stake_pool_accounts,
        validator_stake,
        user,
        deposit_stake,
        pool_token_account,
        _stake_lamports,
    ) = setup().await;

    let preferred_validator = simple_add_validator_to_pool(
        &mut context.banks_client,
        &context.payer,
        &context.last_blockhash,
        &stake_pool_accounts,
    )
    .await;

    stake_pool_accounts
        .set_preferred_validator(
            &mut context.banks_client,
            &context.payer,
            &context.last_blockhash,
            instruction::PreferredValidatorType::Deposit,
            Some(preferred_validator.vote.pubkey()),
        )
        .await;

    let error = stake_pool_accounts
        .deposit_stake(
            &mut context.banks_client,
            &context.payer,
            &context.last_blockhash,
            &deposit_stake,
            &pool_token_account,
            &validator_stake.stake_account,
            &user,
        )
        .await
        .unwrap()
        .unwrap();
    match error {
        TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
            assert_eq!(
                error_index,
                StakePoolError::IncorrectDepositVoteAddress as u32
            );
        }
        _ => panic!("Wrong error occurs while try to make a deposit with wrong stake program ID"),
    }
}

#[tokio::test]
async fn success_with_referral_fee() {
    let (
        mut context,
        stake_pool_accounts,
        validator_stake_account,
        user,
        deposit_stake,
        pool_token_account,
        stake_lamports,
    ) = setup().await;

    let referrer = Keypair::new();
    let referrer_token_account = Keypair::new();
    create_token_account(
        &mut context.banks_client,
        &context.payer,
        &context.last_blockhash,
        &referrer_token_account,
        &stake_pool_accounts.pool_mint.pubkey(),
        &referrer.pubkey(),
    )
    .await
    .unwrap();

    let referrer_balance_pre =
        get_token_balance(&mut context.banks_client, &referrer_token_account.pubkey()).await;

    let mut transaction = Transaction::new_with_payer(
        &instruction::deposit_stake(
            &id(),
            &stake_pool_accounts.stake_pool.pubkey(),
            &stake_pool_accounts.validator_list.pubkey(),
            &stake_pool_accounts.withdraw_authority,
            &deposit_stake,
            &user.pubkey(),
            &validator_stake_account.stake_account,
            &stake_pool_accounts.reserve_stake.pubkey(),
            &pool_token_account,
            &stake_pool_accounts.pool_fee_account.pubkey(),
            &referrer_token_account.pubkey(),
            &stake_pool_accounts.pool_mint.pubkey(),
            &spl_token::id(),
        ),
        Some(&context.payer.pubkey()),
    );
    transaction.sign(&[&context.payer, &user], context.last_blockhash);
    context
        .banks_client
        .process_transaction(transaction)
        .await
        .unwrap();

    let referrer_balance_post =
        get_token_balance(&mut context.banks_client, &referrer_token_account.pubkey()).await;
    let stake_pool = get_account(
        &mut context.banks_client,
        &stake_pool_accounts.stake_pool.pubkey(),
    )
    .await;
    let stake_pool =
        try_from_slice_unchecked::<state::StakePool>(stake_pool.data.as_slice()).unwrap();
    let rent = context.banks_client.get_rent().await.unwrap();
    let stake_rent = rent.minimum_balance(std::mem::size_of::<stake::state::StakeState>());
    let fee_tokens = stake_pool
        .calc_pool_tokens_sol_deposit_fee(stake_rent)
        .unwrap()
        + stake_pool
            .calc_pool_tokens_stake_deposit_fee(stake_lamports - stake_rent)
            .unwrap();
    let referral_fee = stake_pool_accounts.calculate_referral_fee(fee_tokens);
    assert!(referral_fee > 0);
    assert_eq!(referrer_balance_pre + referral_fee, referrer_balance_post);
}

#[tokio::test]
async fn fail_with_invalid_referrer() {
    let (
        mut context,
        stake_pool_accounts,
        validator_stake_account,
        user,
        deposit_stake,
        pool_token_account,
        _stake_lamports,
    ) = setup().await;

    let invalid_token_account = Keypair::new();

    let mut transaction = Transaction::new_with_payer(
        &instruction::deposit_stake(
            &id(),
            &stake_pool_accounts.stake_pool.pubkey(),
            &stake_pool_accounts.validator_list.pubkey(),
            &stake_pool_accounts.withdraw_authority,
            &deposit_stake,
            &user.pubkey(),
            &validator_stake_account.stake_account,
            &stake_pool_accounts.reserve_stake.pubkey(),
            &pool_token_account,
            &stake_pool_accounts.pool_fee_account.pubkey(),
            &invalid_token_account.pubkey(),
            &stake_pool_accounts.pool_mint.pubkey(),
            &spl_token::id(),
        ),
        Some(&context.payer.pubkey()),
    );
    transaction.sign(&[&context.payer, &user], context.last_blockhash);
    let transaction_error = context
        .banks_client
        .process_transaction(transaction)
        .await
        .err()
        .unwrap()
        .unwrap();

    match transaction_error {
        TransactionError::InstructionError(_, InstructionError::InvalidAccountData) => (),
        _ => panic!(
            "Wrong error occurs while try to make a deposit with an invalid referrer account"
        ),
    }
}