stake-pool: Rework add / remove validator to not use pool tokens (#1581)

* Rework remove

* Add tests

* Transition to checked math

* Update CLI for new types / instructions

* Cargo fmt

* Rename voter_pubkey -> vote_account_address

* Remove max check

* Update validator balance test
This commit is contained in:
Jon Cinque 2021-04-21 13:06:43 +02:00 committed by GitHub
parent cf8eeb0720
commit 30671aa5b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 651 additions and 825 deletions

View File

@ -264,12 +264,7 @@ fn command_vsa_create(
Ok(())
}
fn command_vsa_add(
config: &Config,
stake_pool_address: &Pubkey,
stake: &Pubkey,
token_receiver: &Option<Pubkey>,
) -> CommandResult {
fn command_vsa_add(config: &Config, stake_pool_address: &Pubkey, stake: &Pubkey) -> CommandResult {
if config.rpc_client.get_stake_activation(*stake, None)?.state != StakeActivationState::Active {
return Err("Stake account is not active.".into());
}
@ -280,26 +275,9 @@ fn command_vsa_add(
let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?;
let mut total_rent_free_balances: u64 = 0;
let token_receiver_account = Keypair::new();
let mut instructions: Vec<Instruction> = vec![];
let mut signers = vec![config.fee_payer.as_ref(), config.staker.as_ref()];
// Create token account if not specified
let token_receiver = unwrap_create_token_account(
&config,
&token_receiver,
&token_receiver_account,
&stake_pool.pool_mint,
&mut instructions,
|balance| {
signers.push(&token_receiver_account);
total_rent_free_balances += balance;
},
)?;
// Calculate Deposit and Withdraw stake pool authorities
let pool_deposit_authority =
find_deposit_authority_program_address(&spl_stake_pool::id(), stake_pool_address).0;
@ -331,9 +309,6 @@ fn command_vsa_add(
&pool_withdraw_authority,
&stake_pool.validator_list,
&stake,
&token_receiver,
&stake_pool.pool_mint,
&spl_token::id(),
)?,
]);
@ -341,10 +316,7 @@ fn command_vsa_add(
Transaction::new_with_payer(&instructions, Some(&config.fee_payer.pubkey()));
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance(
config,
total_rent_free_balances + fee_calculator.calculate_fee(&transaction.message()),
)?;
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
unique_signers!(signers);
transaction.sign(&signers, recent_blockhash);
send_transaction(&config, transaction)?;
@ -355,7 +327,6 @@ fn command_vsa_remove(
config: &Config,
stake_pool_address: &Pubkey,
stake: &Pubkey,
withdraw_from: &Pubkey,
new_authority: &Option<Pubkey>,
) -> CommandResult {
if !config.no_update {
@ -369,35 +340,8 @@ fn command_vsa_remove(
let staker_pubkey = config.staker.pubkey();
let new_authority = new_authority.as_ref().unwrap_or(&staker_pubkey);
// Calculate amount of tokens to withdraw
let stake_account = config.rpc_client.get_account(&stake)?;
let tokens_to_withdraw = stake_pool
.calc_pool_tokens_for_withdraw(stake_account.lamports)
.unwrap();
// Check balance and mint
let token_account =
get_token_account(&config.rpc_client, &withdraw_from, &stake_pool.pool_mint)?;
if token_account.amount < tokens_to_withdraw {
let pool_mint = get_token_mint(&config.rpc_client, &stake_pool.pool_mint)?;
return Err(format!(
"Not enough balance to burn to remove validator stake account from the pool. {} pool tokens needed.",
spl_token::amount_to_ui_amount(tokens_to_withdraw, pool_mint.decimals)
).into());
}
let mut transaction = Transaction::new_with_payer(
&[
// Approve spending token
spl_token::instruction::approve(
&spl_token::id(),
&withdraw_from,
&pool_withdraw_authority,
&config.token_owner.pubkey(),
&[],
tokens_to_withdraw,
)?,
// Create new validator stake account address
spl_stake_pool::instruction::remove_validator_from_pool(
&spl_stake_pool::id(),
@ -407,9 +351,6 @@ fn command_vsa_remove(
&new_authority,
&stake_pool.validator_list,
&stake,
&withdraw_from,
&stake_pool.pool_mint,
&spl_token::id(),
)?,
],
Some(&config.fee_payer.pubkey()),
@ -589,7 +530,7 @@ fn command_list(config: &Config, stake_pool_address: &Pubkey) -> CommandResult {
for validator in validator_list.validators {
println!(
"Validator Vote Account: {}\tBalance: {}\tLast Update Epoch: {}{}",
validator.vote_account,
validator.vote_account_address,
Sol(validator.stake_lamports),
validator.last_update_epoch,
if validator.last_update_epoch != epoch_info.epoch {
@ -669,7 +610,7 @@ fn command_update(config: &Config, stake_pool_address: &Pubkey) -> CommandResult
} else {
let (stake_account, _) = find_stake_program_address(
&spl_stake_pool::id(),
&item.vote_account,
&item.vote_account_address,
&stake_pool_address,
);
Some(stake_account)
@ -1439,26 +1380,13 @@ fn main() {
("add-validator", Some(arg_matches)) => {
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();
let stake_account = pubkey_of(arg_matches, "stake_account").unwrap();
let token_receiver: Option<Pubkey> = pubkey_of(arg_matches, "token_receiver");
command_vsa_add(
&config,
&stake_pool_address,
&stake_account,
&token_receiver,
)
command_vsa_add(&config, &stake_pool_address, &stake_account)
}
("remove-validator", Some(arg_matches)) => {
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();
let stake_account = pubkey_of(arg_matches, "stake_account").unwrap();
let withdraw_from = pubkey_of(arg_matches, "withdraw_from").unwrap();
let new_authority: Option<Pubkey> = pubkey_of(arg_matches, "new_authority");
command_vsa_remove(
&config,
&stake_pool_address,
&stake_account,
&withdraw_from,
&new_authority,
)
command_vsa_remove(&config, &stake_pool_address, &stake_account, &new_authority)
}
("deposit", Some(arg_matches)) => {
let stake_pool_address = pubkey_of(arg_matches, "pool").unwrap();

View File

@ -85,6 +85,9 @@ pub enum StakePoolError {
/// Pool token supply is not zero on initialization
#[error("NonZeroPoolTokenSupply")]
NonZeroPoolTokenSupply,
/// The lamports in the validator stake account is not equal to the minimum
#[error("StakeLamportsNotEqualToMinimum")]
StakeLamportsNotEqualToMinimum,
}
impl From<StakePoolError> for ProgramError {
fn from(e: StakePoolError) -> Self {

View File

@ -64,7 +64,16 @@ pub enum StakePoolInstruction {
CreateValidatorStakeAccount,
/// (Staker only) Adds stake account delegated to validator to the pool's
/// list of managed validators
/// list of managed validators.
///
/// The stake account must have the rent-exempt amount plus at least 1 SOL,
/// and at most 1.001 SOL.
///
/// Once we delegate even 1 SOL, it will accrue rewards one epoch later,
/// so we'll have more than 1 active SOL at this point.
/// At 10% annualized rewards, 1 epoch of 2 days will accrue
/// 0.000547945 SOL, so we check that it is at least 1 SOL, and at most
/// 1.001 SOL.
///
/// 0. `[w]` Stake pool
/// 1. `[s]` Staker
@ -72,26 +81,23 @@ pub enum StakePoolInstruction {
/// 3. `[]` Stake pool withdraw authority
/// 4. `[w]` Validator stake list storage account
/// 5. `[w]` Stake account to add to the pool, its withdraw authority should be set to stake pool deposit
/// 6. `[w]` User account to receive pool tokens
/// 7. `[w]` Pool token mint account
/// 8. `[]` Clock sysvar (required)
/// 9. '[]' Sysvar stake history account
/// 10. `[]` Pool token program id,
/// 11. `[]` Stake program id,
/// 6. `[]` Clock sysvar
/// 7. '[]' Sysvar stake history account
/// 8. `[]` Stake program
AddValidatorToPool,
/// (Staker only) Removes validator from the pool
///
/// Only succeeds if the validator stake account has the minimum of 1 SOL
/// plus the rent-exempt amount.
///
/// 0. `[w]` Stake pool
/// 1. `[s]` Staker
/// 2. `[]` Stake pool withdraw authority
/// 3. `[]` New withdraw/staker authority to set in the stake account
/// 4. `[w]` Validator stake list storage account
/// 5. `[w]` Stake account to remove from the pool
/// 6. `[w]` User account with pool tokens to burn from
/// 7. `[w]` Pool token mint account
/// 8. '[]' Sysvar clock account (required)
/// 9. `[]` Pool token program id
/// 8. '[]' Sysvar clock
/// 10. `[]` Stake program id,
RemoveValidatorFromPool,
@ -197,7 +203,10 @@ pub enum StakePoolInstruction {
Deposit,
/// Withdraw the token from the pool at the current ratio.
/// The amount withdrawn is the MIN(u64, stake size)
///
/// Succeeds if the stake account has enough SOL to cover the desired amount
/// of pool tokens, and if the withdrawal keeps the total staked amount
/// above the minimum of rent-exempt amount + 1 SOL.
///
/// A validator stake account can be withdrawn from freely, and the reserve
/// can only be drawn from if there is no active stake left, where all
@ -307,9 +316,6 @@ pub fn add_validator_to_pool(
stake_pool_withdraw: &Pubkey,
validator_list: &Pubkey,
stake_account: &Pubkey,
pool_token_receiver: &Pubkey,
pool_mint: &Pubkey,
token_program_id: &Pubkey,
) -> Result<Instruction, ProgramError> {
let accounts = vec![
AccountMeta::new(*stake_pool, false),
@ -318,11 +324,8 @@ pub fn add_validator_to_pool(
AccountMeta::new_readonly(*stake_pool_withdraw, false),
AccountMeta::new(*validator_list, false),
AccountMeta::new(*stake_account, false),
AccountMeta::new(*pool_token_receiver, false),
AccountMeta::new(*pool_mint, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
AccountMeta::new_readonly(*token_program_id, false),
AccountMeta::new_readonly(stake_program::id(), false),
];
Ok(Instruction {
@ -341,9 +344,6 @@ pub fn remove_validator_from_pool(
new_stake_authority: &Pubkey,
validator_list: &Pubkey,
stake_account: &Pubkey,
burn_from: &Pubkey,
pool_mint: &Pubkey,
token_program_id: &Pubkey,
) -> Result<Instruction, ProgramError> {
let accounts = vec![
AccountMeta::new(*stake_pool, false),
@ -352,10 +352,7 @@ pub fn remove_validator_from_pool(
AccountMeta::new_readonly(*new_stake_authority, false),
AccountMeta::new(*validator_list, false),
AccountMeta::new(*stake_account, false),
AccountMeta::new(*burn_from, false),
AccountMeta::new(*pool_mint, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(*token_program_id, false),
AccountMeta::new_readonly(stake_program::id(), false),
];
Ok(Instruction {

View File

@ -14,7 +14,10 @@ pub mod entrypoint;
// Export current sdk types for downstream users building with a different sdk version
pub use solana_program;
use solana_program::pubkey::Pubkey;
use {
crate::stake_program::Meta,
solana_program::{native_token::LAMPORTS_PER_SOL, pubkey::Pubkey},
};
/// Seed for deposit authority seed
const AUTHORITY_DEPOSIT: &[u8] = b"deposit";
@ -22,6 +25,17 @@ const AUTHORITY_DEPOSIT: &[u8] = b"deposit";
/// Seed for withdraw authority seed
const AUTHORITY_WITHDRAW: &[u8] = b"withdraw";
/// Minimum amount of staked SOL required in a validator stake account to allow
/// for merges without a mismatch on credits observed
pub const MINIMUM_ACTIVE_STAKE: u64 = LAMPORTS_PER_SOL;
/// Get the stake amount under consideration when calculating pool token
/// conversions
pub fn minimum_stake_lamports(meta: &Meta) -> u64 {
meta.rent_exempt_reserve
.saturating_add(MINIMUM_ACTIVE_STAKE)
}
/// Generates the deposit authority program address for the stake pool
pub fn find_deposit_authority_program_address(
program_id: &Pubkey,

View File

@ -5,9 +5,9 @@ use {
borsh::try_from_slice_unchecked,
error::StakePoolError,
instruction::{Fee, StakePoolInstruction},
stake_program,
minimum_stake_lamports, stake_program,
state::{AccountType, StakePool, ValidatorList, ValidatorStakeInfo},
AUTHORITY_DEPOSIT, AUTHORITY_WITHDRAW,
AUTHORITY_DEPOSIT, AUTHORITY_WITHDRAW, MINIMUM_ACTIVE_STAKE,
},
bincode::deserialize,
borsh::{BorshDeserialize, BorshSerialize},
@ -19,7 +19,6 @@ use {
decode_error::DecodeError,
entrypoint::ProgramResult,
msg,
native_token::sol_to_lamports,
program::{invoke, invoke_signed},
program_error::PrintProgramError,
program_error::ProgramError,
@ -27,57 +26,59 @@ use {
pubkey::Pubkey,
rent::Rent,
stake_history::StakeHistory,
system_instruction,
system_instruction, system_program,
sysvar::Sysvar,
},
spl_token::state::Mint,
};
/// Deserialize the stake state from AccountInfo
fn get_stake_state(
stake_account_info: &AccountInfo,
) -> Result<(stake_program::Meta, stake_program::Stake), ProgramError> {
let stake_state: stake_program::StakeState =
deserialize(&stake_account_info.data.borrow()).or(Err(ProgramError::InvalidAccountData))?;
match stake_state {
stake_program::StakeState::Stake(meta, stake) => Ok((meta, stake)),
_ => Err(StakePoolError::WrongStakeState.into()),
}
}
/// Checks if validator stake account is a proper program address
fn is_validator_stake_address(
program_id: &Pubkey,
stake_pool_address: &Pubkey,
stake_account_address: &Pubkey,
vote_address: &Pubkey,
) -> bool {
// Check stake account address validity
let (stake_address, _) =
crate::find_stake_program_address(&program_id, &vote_address, &stake_pool_address);
stake_address == *stake_account_address
}
/// Check validity of vote address for a particular stake account
fn check_validator_stake_address(
program_id: &Pubkey,
stake_pool_address: &Pubkey,
stake_account_address: &Pubkey,
vote_address: &Pubkey,
) -> Result<(), ProgramError> {
if !is_validator_stake_address(
program_id,
stake_pool_address,
stake_account_address,
vote_address,
) {
Err(StakePoolError::InvalidStakeAccountAddress.into())
} else {
Ok(())
}
}
/// Program state handler.
pub struct Processor {}
impl Processor {
/// Returns validator address for a particular stake account
fn get_validator(stake_account_info: &AccountInfo) -> Result<Pubkey, ProgramError> {
let stake_state: stake_program::StakeState = deserialize(&stake_account_info.data.borrow())
.or(Err(ProgramError::InvalidAccountData))?;
match stake_state {
stake_program::StakeState::Stake(_, stake) => Ok(stake.delegation.voter_pubkey),
_ => Err(StakePoolError::WrongStakeState.into()),
}
}
/// Checks if validator stake account is a proper program address
fn is_validator_stake_address(
vote_account: &Pubkey,
program_id: &Pubkey,
stake_pool_info: &AccountInfo,
stake_account_info: &AccountInfo,
) -> bool {
// Check stake account address validity
let (stake_address, _) =
crate::find_stake_program_address(&program_id, &vote_account, &stake_pool_info.key);
stake_address == *stake_account_info.key
}
/// Returns validator address for a particular stake account and checks its validity
fn get_validator_checked(
program_id: &Pubkey,
stake_pool_info: &AccountInfo,
stake_account_info: &AccountInfo,
) -> Result<Pubkey, ProgramError> {
let vote_account = Self::get_validator(stake_account_info)?;
if !Self::is_validator_stake_address(
&vote_account,
program_id,
stake_pool_info,
stake_account_info,
) {
return Err(StakePoolError::InvalidStakeAccountAddress.into());
}
Ok(vote_account)
}
/// Issue a stake_split instruction.
fn stake_split<'a>(
stake_pool: &Pubkey,
@ -359,7 +360,7 @@ impl Processor {
}
stake_pool.check_staker(staker_info)?;
if *system_program_info.key != solana_program::system_program::id() {
if *system_program_info.key != system_program::id() {
return Err(ProgramError::IncorrectProgramId);
}
if *stake_program_info.key != stake_program::id() {
@ -382,7 +383,7 @@ impl Processor {
];
// Fund the stake account with 1 SOL + rent-exempt balance
let required_lamports = sol_to_lamports(1.0)
let required_lamports = MINIMUM_ACTIVE_STAKE
+ rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
// Create new stake account
@ -443,20 +444,20 @@ impl Processor {
let withdraw_info = next_account_info(account_info_iter)?;
let validator_list_info = next_account_info(account_info_iter)?;
let stake_account_info = next_account_info(account_info_iter)?;
let dest_user_info = next_account_info(account_info_iter)?;
let pool_mint_info = next_account_info(account_info_iter)?;
let clock_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(clock_info)?;
let stake_history_info = next_account_info(account_info_iter)?;
let stake_history = &StakeHistory::from_account_info(stake_history_info)?;
let token_program_info = next_account_info(account_info_iter)?;
let stake_program_info = next_account_info(account_info_iter)?;
if *stake_program_info.key != stake_program::id() {
return Err(ProgramError::IncorrectProgramId);
}
let mut stake_pool = StakePool::try_from_slice(&stake_pool_info.data.borrow())?;
if stake_pool_info.owner != program_id {
return Err(ProgramError::IncorrectProgramId);
}
let stake_pool = StakePool::try_from_slice(&stake_pool_info.data.borrow())?;
if !stake_pool.is_valid() {
return Err(StakePoolError::InvalidState.into());
}
@ -465,16 +466,11 @@ impl Processor {
stake_pool.check_authority_deposit(deposit_info.key, program_id, stake_pool_info.key)?;
stake_pool.check_staker(staker_info)?;
stake_pool.check_mint(pool_mint_info)?;
if stake_pool.last_update_epoch < clock.epoch {
return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
}
if stake_pool.token_program_id != *token_program_info.key {
return Err(ProgramError::IncorrectProgramId);
}
if *validator_list_info.key != stake_pool.validator_list {
return Err(StakePoolError::InvalidValidatorStakeList.into());
}
@ -488,13 +484,34 @@ impl Processor {
return Err(ProgramError::AccountDataTooSmall);
}
let vote_account =
Self::get_validator_checked(program_id, stake_pool_info, stake_account_info)?;
let (meta, stake) = get_stake_state(stake_account_info)?;
let vote_account_address = stake.delegation.voter_pubkey;
check_validator_stake_address(
program_id,
stake_pool_info.key,
stake_account_info.key,
&vote_account_address,
)?;
if validator_list.contains(&vote_account) {
if validator_list.contains(&vote_account_address) {
return Err(StakePoolError::ValidatorAlreadyAdded.into());
}
// Check amount of lamports
let stake_lamports = **stake_account_info.lamports.borrow();
let minimum_lamport_amount = minimum_stake_lamports(&meta);
if stake_lamports != minimum_lamport_amount {
msg!(
"Error: attempting to add stake with {} lamports, must have {} lamports",
stake_lamports,
minimum_lamport_amount
);
return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into());
}
// Check if stake is warmed up
Self::check_stake_activation(stake_account_info, clock, stake_history)?;
// Update Withdrawer and Staker authority to the program withdraw authority
for authority in &[
stake_program::StakeAuthorize::Withdrawer,
@ -513,36 +530,13 @@ impl Processor {
)?;
}
// Calculate and mint tokens
let stake_lamports = **stake_account_info.lamports.borrow();
let pool_tokens = stake_pool
.calc_pool_tokens_for_deposit(stake_lamports)
.ok_or(StakePoolError::CalculationFailure)?;
Self::token_mint_to(
stake_pool_info.key,
token_program_info.clone(),
pool_mint_info.clone(),
dest_user_info.clone(),
withdraw_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
pool_tokens,
)?;
// Check if stake is warmed up
Self::check_stake_activation(stake_account_info, clock, stake_history)?;
validator_list.validators.push(ValidatorStakeInfo {
vote_account,
stake_lamports,
vote_account_address,
stake_lamports: stake_lamports.saturating_sub(minimum_lamport_amount),
last_update_epoch: clock.epoch,
});
validator_list.serialize(&mut *validator_list_info.data.borrow_mut())?;
stake_pool.pool_token_supply += pool_tokens;
stake_pool.total_stake_lamports += stake_lamports;
stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?;
Ok(())
}
@ -558,18 +552,18 @@ impl Processor {
let new_stake_authority_info = next_account_info(account_info_iter)?;
let validator_list_info = next_account_info(account_info_iter)?;
let stake_account_info = next_account_info(account_info_iter)?;
let burn_from_info = next_account_info(account_info_iter)?;
let pool_mint_info = next_account_info(account_info_iter)?;
let clock_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(clock_info)?;
let token_program_info = next_account_info(account_info_iter)?;
let stake_program_info = next_account_info(account_info_iter)?;
if *stake_program_info.key != stake_program::id() {
return Err(ProgramError::IncorrectProgramId);
}
let mut stake_pool = StakePool::try_from_slice(&stake_pool_info.data.borrow())?;
if stake_pool_info.owner != program_id {
return Err(ProgramError::IncorrectProgramId);
}
let stake_pool = StakePool::try_from_slice(&stake_pool_info.data.borrow())?;
if !stake_pool.is_valid() {
return Err(StakePoolError::InvalidState.into());
}
@ -581,11 +575,6 @@ impl Processor {
return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
}
if stake_pool.token_program_id != *token_program_info.key {
return Err(ProgramError::IncorrectProgramId);
}
stake_pool.check_mint(pool_mint_info)?;
if *validator_list_info.key != stake_pool.validator_list {
return Err(StakePoolError::InvalidValidatorStakeList.into());
}
@ -596,13 +585,30 @@ impl Processor {
return Err(StakePoolError::InvalidState.into());
}
let vote_account =
Self::get_validator_checked(program_id, stake_pool_info, stake_account_info)?;
let (meta, stake) = get_stake_state(stake_account_info)?;
let vote_account_address = stake.delegation.voter_pubkey;
check_validator_stake_address(
program_id,
stake_pool_info.key,
stake_account_info.key,
&vote_account_address,
)?;
if !validator_list.contains(&vote_account) {
if !validator_list.contains(&vote_account_address) {
return Err(StakePoolError::ValidatorNotFound.into());
}
let stake_lamports = **stake_account_info.lamports.borrow();
let required_lamports = minimum_stake_lamports(&meta);
if stake_lamports != required_lamports {
msg!(
"Attempting to remove validator account with {} lamports, must have {} lamports",
stake_lamports,
required_lamports
);
return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into());
}
for authority in &[
stake_program::StakeAuthorize::Withdrawer,
stake_program::StakeAuthorize::Staker,
@ -620,31 +626,11 @@ impl Processor {
)?;
}
// Calculate and burn tokens
let stake_lamports = **stake_account_info.lamports.borrow();
let pool_tokens = stake_pool
.calc_pool_tokens_for_withdraw(stake_lamports)
.ok_or(StakePoolError::CalculationFailure)?;
Self::token_burn(
stake_pool_info.key,
token_program_info.clone(),
burn_from_info.clone(),
pool_mint_info.clone(),
withdraw_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
pool_tokens,
)?;
validator_list
.validators
.retain(|item| item.vote_account != vote_account);
.retain(|item| item.vote_account_address != vote_account_address);
validator_list.serialize(&mut *validator_list_info.data.borrow_mut())?;
stake_pool.pool_token_supply -= pool_tokens;
stake_pool.total_stake_lamports -= stake_lamports;
stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?;
Ok(())
}
@ -665,10 +651,11 @@ impl Processor {
return Err(StakePoolError::InvalidState.into());
}
let vote_accounts: Vec<Option<Pubkey>> = validator_stake_accounts
.iter()
.map(|stake| Self::get_validator(stake).ok())
.collect();
let stake_states: Vec<(stake_program::Meta, stake_program::Stake)> =
validator_stake_accounts
.iter()
.filter_map(|stake| get_stake_state(stake).ok())
.collect();
let mut changes = false;
// Do a brute iteration through the list, optimize if necessary
@ -676,16 +663,17 @@ impl Processor {
if validator_stake_record.last_update_epoch >= clock.epoch {
continue;
}
for (validator_stake_account, vote_account) in
validator_stake_accounts.iter().zip(vote_accounts.iter())
for (validator_stake_account, (meta, stake)) in
validator_stake_accounts.iter().zip(stake_states.iter())
{
if validator_stake_record.vote_account
!= vote_account.ok_or(StakePoolError::WrongStakeState)?
{
if validator_stake_record.vote_account_address != stake.delegation.voter_pubkey {
continue;
}
validator_stake_record.last_update_epoch = clock.epoch;
validator_stake_record.stake_lamports = **validator_stake_account.lamports.borrow();
validator_stake_record.stake_lamports = validator_stake_account
.lamports
.borrow()
.saturating_sub(minimum_stake_lamports(&meta));
changes = true;
}
}
@ -736,16 +724,16 @@ impl Processor {
}
let previous_lamports = stake_pool.total_stake_lamports;
let mut total_stake_lamports = 0;
let mut total_stake_lamports: u64 = 0;
for validator_stake_record in validator_list.validators {
if validator_stake_record.last_update_epoch < clock.epoch {
return Err(StakePoolError::StakeListOutOfDate.into());
}
total_stake_lamports += validator_stake_record.stake_lamports;
total_stake_lamports = total_stake_lamports
.checked_add(validator_stake_record.stake_lamports)
.ok_or(StakePoolError::CalculationFailure)?;
}
stake_pool.total_stake_lamports = total_stake_lamports;
let reward_lamports = total_stake_lamports.saturating_sub(previous_lamports);
let fee = stake_pool
.calc_fee_amount(reward_lamports)
@ -768,6 +756,7 @@ impl Processor {
.checked_add(fee)
.ok_or(StakePoolError::CalculationFailure)?;
}
stake_pool.total_stake_lamports = total_stake_lamports;
stake_pool.last_update_epoch = clock.epoch;
stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?;
@ -855,11 +844,17 @@ impl Processor {
return Err(StakePoolError::InvalidState.into());
}
let vote_account =
Self::get_validator_checked(program_id, stake_pool_info, validator_stake_account_info)?;
let (meta, stake) = get_stake_state(validator_stake_account_info)?;
let vote_account_address = stake.delegation.voter_pubkey;
check_validator_stake_address(
program_id,
stake_pool_info.key,
validator_stake_account_info.key,
&vote_account_address,
)?;
let validator_list_item = validator_list
.find_mut(&vote_account)
.find_mut(&vote_account_address)
.ok_or(StakePoolError::ValidatorNotFound)?;
let stake_lamports = **stake_info.lamports.borrow();
@ -914,11 +909,21 @@ impl Processor {
new_pool_tokens,
)?;
stake_pool.pool_token_supply += new_pool_tokens;
stake_pool.total_stake_lamports += stake_lamports;
stake_pool.pool_token_supply = stake_pool
.pool_token_supply
.checked_add(new_pool_tokens)
.ok_or(StakePoolError::CalculationFailure)?;
stake_pool.total_stake_lamports = stake_pool
.total_stake_lamports
.checked_add(stake_lamports)
.ok_or(StakePoolError::CalculationFailure)?;
stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?;
validator_list_item.stake_lamports = **validator_stake_account_info.lamports.borrow();
validator_list_item.stake_lamports = validator_stake_account_info
.lamports
.borrow()
.checked_sub(minimum_stake_lamports(&meta))
.ok_or(StakePoolError::CalculationFailure)?;
validator_list.serialize(&mut *validator_list_info.data.borrow_mut())?;
Ok(())
@ -974,24 +979,49 @@ impl Processor {
return Err(StakePoolError::InvalidState.into());
}
let vote_account =
Self::get_validator_checked(program_id, stake_pool_info, stake_split_from)?;
let (meta, stake) = get_stake_state(stake_split_from)?;
let vote_account_address = stake.delegation.voter_pubkey;
check_validator_stake_address(
program_id,
stake_pool_info.key,
stake_split_from.key,
&vote_account_address,
)?;
let validator_list_item = validator_list
.find_mut(&vote_account)
.find_mut(&vote_account_address)
.ok_or(StakePoolError::ValidatorNotFound)?;
let stake_lamports = stake_pool
let withdraw_lamports = stake_pool
.calc_lamports_withdraw_amount(pool_tokens)
.ok_or(StakePoolError::CalculationFailure)?;
let required_lamports = minimum_stake_lamports(&meta);
let current_lamports = **stake_split_from.lamports.borrow();
let remaining_lamports = current_lamports.saturating_sub(withdraw_lamports);
if remaining_lamports < required_lamports {
msg!("Attempting to withdraw {} lamports from validator account with {} lamports, {} must remain", withdraw_lamports, current_lamports, required_lamports);
return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into());
}
Self::token_burn(
stake_pool_info.key,
token_program_info.clone(),
burn_from_info.clone(),
pool_mint_info.clone(),
withdraw_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
pool_tokens,
)?;
Self::stake_split(
stake_pool_info.key,
stake_split_from.clone(),
withdraw_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
stake_lamports,
withdraw_lamports,
stake_split_to.clone(),
)?;
@ -1019,22 +1049,20 @@ impl Processor {
stake_program_info.clone(),
)?;
Self::token_burn(
stake_pool_info.key,
token_program_info.clone(),
burn_from_info.clone(),
pool_mint_info.clone(),
withdraw_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
pool_tokens,
)?;
stake_pool.pool_token_supply -= pool_tokens;
stake_pool.total_stake_lamports -= stake_lamports;
stake_pool.pool_token_supply = stake_pool
.pool_token_supply
.checked_sub(pool_tokens)
.ok_or(StakePoolError::CalculationFailure)?;
stake_pool.total_stake_lamports = stake_pool
.total_stake_lamports
.checked_sub(withdraw_lamports)
.ok_or(StakePoolError::CalculationFailure)?;
stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?;
validator_list_item.stake_lamports = **stake_split_from.lamports.borrow();
validator_list_item.stake_lamports = validator_list_item
.stake_lamports
.checked_sub(withdraw_lamports)
.ok_or(StakePoolError::CalculationFailure)?;
validator_list.serialize(&mut *validator_list_info.data.borrow_mut())?;
Ok(())
@ -1180,6 +1208,7 @@ impl PrintProgramError for StakePoolError {
StakePoolError::UnexpectedValidatorListAccountSize=> msg!("Error: The size of the given validator stake list does match the expected amount"),
StakePoolError::WrongStaker=> msg!("Error: Wrong pool staker account"),
StakePoolError::NonZeroPoolTokenSupply => msg!("Error: Pool token supply is not zero on initialization"),
StakePoolError::StakeLamportsNotEqualToMinimum => msg!("Error: The lamports in the validator stake account is not equal to the minimum"),
}
}
}

View File

@ -200,6 +200,14 @@ impl StakeState {
_ => None,
}
}
/// Get meta
pub fn meta(&self) -> Option<&Meta> {
match self {
StakeState::Initialized(meta) => Some(meta),
StakeState::Stake(meta, _) => Some(meta),
_ => None,
}
}
}
/// FIXME copied from the stake program

View File

@ -79,7 +79,7 @@ pub struct StakePool {
impl StakePool {
/// calculate the pool tokens that should be minted for a deposit of `stake_lamports`
pub fn calc_pool_tokens_for_deposit(&self, stake_lamports: u64) -> Option<u64> {
if self.total_stake_lamports == 0 {
if self.total_stake_lamports == 0 || self.pool_token_supply == 0 {
return Some(stake_lamports);
}
u64::try_from(
@ -236,7 +236,7 @@ pub struct ValidatorList {
#[derive(Clone, Copy, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct ValidatorStakeInfo {
/// Validator vote account address
pub vote_account: Pubkey,
pub vote_account_address: Pubkey,
/// Amount of stake delegated to this validator
/// Note that if `last_update_epoch` does not match the current epoch then this field may not
@ -264,23 +264,23 @@ impl ValidatorList {
}
/// Check if contains validator with particular pubkey
pub fn contains(&self, vote_account: &Pubkey) -> bool {
pub fn contains(&self, vote_account_address: &Pubkey) -> bool {
self.validators
.iter()
.any(|x| x.vote_account == *vote_account)
.any(|x| x.vote_account_address == *vote_account_address)
}
/// Check if contains validator with particular pubkey
pub fn find_mut(&mut self, vote_account: &Pubkey) -> Option<&mut ValidatorStakeInfo> {
pub fn find_mut(&mut self, vote_account_address: &Pubkey) -> Option<&mut ValidatorStakeInfo> {
self.validators
.iter_mut()
.find(|x| x.vote_account == *vote_account)
.find(|x| x.vote_account_address == *vote_account_address)
}
/// Check if contains validator with particular pubkey
pub fn find(&self, vote_account: &Pubkey) -> Option<&ValidatorStakeInfo> {
pub fn find(&self, vote_account_address: &Pubkey) -> Option<&ValidatorStakeInfo> {
self.validators
.iter()
.find(|x| x.vote_account == *vote_account)
.find(|x| x.vote_account_address == *vote_account_address)
}
/// Check if validator stake list is actually initialized as a validator stake list
@ -337,17 +337,17 @@ mod test {
max_validators,
validators: vec![
ValidatorStakeInfo {
vote_account: Pubkey::new_from_array([1; 32]),
vote_account_address: Pubkey::new_from_array([1; 32]),
stake_lamports: 123456789,
last_update_epoch: 987654321,
},
ValidatorStakeInfo {
vote_account: Pubkey::new_from_array([2; 32]),
vote_account_address: Pubkey::new_from_array([2; 32]),
stake_lamports: 998877665544,
last_update_epoch: 11223445566,
},
ValidatorStakeInfo {
vote_account: Pubkey::new_from_array([3; 32]),
vote_account_address: Pubkey::new_from_array([3; 32]),
stake_lamports: 0,
last_update_epoch: 999999999999999,
},

View File

@ -3,6 +3,7 @@
mod helpers;
use {
bincode::deserialize,
borsh::{BorshDeserialize, BorshSerialize},
helpers::*,
solana_program::{
@ -19,7 +20,8 @@ use {
transport::TransportError,
},
spl_stake_pool::{
borsh::try_from_slice_unchecked, error, id, instruction, stake_program, state,
borsh::try_from_slice_unchecked, error, id, instruction, minimum_stake_lamports,
stake_program, state,
},
spl_token::error as token_error,
};
@ -208,8 +210,11 @@ async fn test_stake_pool_deposit() {
// Check validator stake account actual SOL balance
let validator_stake_account =
get_account(&mut banks_client, &validator_stake_account.stake_account).await;
let stake_state =
deserialize::<stake_program::StakeState>(&validator_stake_account.data).unwrap();
let meta = stake_state.meta().unwrap();
assert_eq!(
validator_stake_account.lamports,
validator_stake_account.lamports - minimum_stake_lamports(&meta),
validator_stake_item.stake_lamports
);
}

View File

@ -79,15 +79,16 @@ pub async fn transfer(
recipient: &Pubkey,
amount: u64,
) {
let mut transaction = Transaction::new_with_payer(
let transaction = Transaction::new_signed_with_payer(
&[system_instruction::transfer(
&payer.pubkey(),
recipient,
amount,
)],
Some(&payer.pubkey()),
&[payer],
*recent_blockhash,
);
transaction.sign(&[payer], *recent_blockhash);
banks_client.process_transaction(transaction).await.unwrap();
}
@ -287,7 +288,7 @@ pub async fn create_independent_stake_account(
let lamports =
rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>()) + TEST_STAKE_AMOUNT;
let mut transaction = Transaction::new_with_payer(
let transaction = Transaction::new_signed_with_payer(
&stake_program::create_account(
&payer.pubkey(),
&stake.pubkey(),
@ -296,8 +297,9 @@ pub async fn create_independent_stake_account(
lamports,
),
Some(&payer.pubkey()),
&[payer, stake],
*recent_blockhash,
);
transaction.sign(&[payer, stake], *recent_blockhash);
banks_client.process_transaction(transaction).await.unwrap();
lamports
@ -312,7 +314,7 @@ pub async fn create_blank_stake_account(
let rent = banks_client.get_rent().await.unwrap();
let lamports = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>()) + 1;
let mut transaction = Transaction::new_with_payer(
let transaction = Transaction::new_signed_with_payer(
&[system_instruction::create_account(
&payer.pubkey(),
&stake.pubkey(),
@ -321,8 +323,9 @@ pub async fn create_blank_stake_account(
&stake_program::id(),
)],
Some(&payer.pubkey()),
&[payer, stake],
*recent_blockhash,
);
transaction.sign(&[payer, stake], *recent_blockhash);
banks_client.process_transaction(transaction).await.unwrap();
lamports
@ -337,22 +340,20 @@ pub async fn create_validator_stake_account(
stake_account: &Pubkey,
validator: &Pubkey,
) {
let mut transaction = Transaction::new_with_payer(
&[
instruction::create_validator_stake_account(
&id(),
&stake_pool,
&staker.pubkey(),
&payer.pubkey(),
&stake_account,
&validator,
)
.unwrap(),
system_instruction::transfer(&payer.pubkey(), &stake_account, TEST_STAKE_AMOUNT),
],
let transaction = Transaction::new_signed_with_payer(
&[instruction::create_validator_stake_account(
&id(),
&stake_pool,
&staker.pubkey(),
&payer.pubkey(),
&stake_account,
&validator,
)
.unwrap()],
Some(&payer.pubkey()),
&[payer, staker],
*recent_blockhash,
);
transaction.sign(&[payer, staker], *recent_blockhash);
banks_client.process_transaction(transaction).await.unwrap();
}
@ -594,8 +595,8 @@ impl StakePoolAccounts {
validator_stake_account: &Pubkey,
recipient_new_authority: &Pubkey,
amount: u64,
) -> Result<(), TransportError> {
let mut transaction = Transaction::new_with_payer(
) -> Option<TransportError> {
let transaction = Transaction::new_signed_with_payer(
&[instruction::withdraw(
&id(),
&self.stake_pool.pubkey(),
@ -611,10 +612,10 @@ impl StakePoolAccounts {
)
.unwrap()],
Some(&payer.pubkey()),
&[payer],
*recent_blockhash,
);
transaction.sign(&[payer], *recent_blockhash);
banks_client.process_transaction(transaction).await?;
Ok(())
banks_client.process_transaction(transaction).await.err()
}
pub async fn update_validator_list_balance(
@ -667,9 +668,8 @@ impl StakePoolAccounts {
payer: &Keypair,
recent_blockhash: &Hash,
stake: &Pubkey,
pool_account: &Pubkey,
) -> Option<TransportError> {
let mut transaction = Transaction::new_with_payer(
let transaction = Transaction::new_signed_with_payer(
&[instruction::add_validator_to_pool(
&id(),
&self.stake_pool.pubkey(),
@ -678,14 +678,12 @@ impl StakePoolAccounts {
&self.withdraw_authority,
&self.validator_list.pubkey(),
stake,
pool_account,
&self.pool_mint.pubkey(),
&spl_token::id(),
)
.unwrap()],
Some(&payer.pubkey()),
&[payer, &self.staker],
*recent_blockhash,
);
transaction.sign(&[payer, &self.staker], *recent_blockhash);
banks_client.process_transaction(transaction).await.err()
}
@ -695,7 +693,6 @@ impl StakePoolAccounts {
payer: &Keypair,
recent_blockhash: &Hash,
stake: &Pubkey,
pool_account: &Pubkey,
new_authority: &Pubkey,
) -> Option<TransportError> {
let mut transaction = Transaction::new_with_payer(
@ -707,9 +704,6 @@ impl StakePoolAccounts {
&new_authority,
&self.validator_list.pubkey(),
stake,
pool_account,
&self.pool_mint.pubkey(),
&spl_token::id(),
)
.unwrap()],
Some(&payer.pubkey()),
@ -738,25 +732,12 @@ pub async fn simple_add_validator_to_pool(
)
.await;
let user_pool_account = Keypair::new();
let user = Keypair::new();
create_token_account(
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_to_pool(
banks_client,
&payer,
&recent_blockhash,
&user_stake.stake_account,
&user_pool_account.pubkey(),
)
.await;
assert!(error.is_none());
@ -764,6 +745,7 @@ pub async fn simple_add_validator_to_pool(
user_stake
}
#[derive(Debug)]
pub struct DepositInfo {
pub user: Keypair,
pub user_pool_account: Pubkey,

View File

@ -34,15 +34,24 @@ async fn setup() -> (
let mut stake_accounts: Vec<ValidatorStakeAccount> = vec![];
const STAKE_ACCOUNTS: u64 = 3;
for _ in 0..STAKE_ACCOUNTS {
stake_accounts.push(
simple_add_validator_to_pool(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
&stake_pool_accounts,
)
.await,
);
let validator_stake_account = simple_add_validator_to_pool(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
&stake_pool_accounts,
)
.await;
let _deposit_info = simple_deposit(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
&stake_pool_accounts,
&validator_stake_account,
)
.await;
stake_accounts.push(validator_stake_account);
}
(context, stake_pool_accounts, stake_accounts)
@ -52,6 +61,18 @@ async fn setup() -> (
async fn success() {
let (mut context, stake_pool_accounts, stake_accounts) = setup().await;
let before_balance = get_validator_list_sum(
&mut context.banks_client,
&stake_pool_accounts.validator_list.pubkey(),
)
.await;
let before_token_supply = get_token_supply(
&mut context.banks_client,
&stake_pool_accounts.pool_mint.pubkey(),
)
.await;
let error = stake_pool_accounts
.update_stake_pool_balance(
&mut context.banks_client,
@ -74,12 +95,6 @@ async fn success() {
.await;
}
let before_balance = get_validator_list_sum(
&mut context.banks_client,
&stake_pool_accounts.validator_list.pubkey(),
)
.await;
// Update epoch
context.warp_to_slot(50_000).unwrap();
@ -130,9 +145,9 @@ async fn success() {
)
.await;
let stake_pool = StakePool::try_from_slice(&stake_pool_info.data).unwrap();
let expected_fee = stake_pool
.calc_fee_amount(after_balance - before_balance)
.unwrap();
let expected_fee = (after_balance - before_balance) * before_token_supply / before_balance
* stake_pool.fee.numerator
/ stake_pool.fee.denominator;
assert_eq!(actual_fee, expected_fee);
assert_eq!(pool_token_supply, stake_pool.pool_token_supply);
}

View File

@ -3,12 +3,8 @@
mod helpers;
use {
crate::helpers::TEST_STAKE_AMOUNT,
helpers::*,
solana_program::{native_token, pubkey::Pubkey},
solana_program_test::*,
helpers::*, solana_program::pubkey::Pubkey, solana_program_test::*,
solana_sdk::signature::Signer,
spl_stake_pool::stake_program,
};
#[tokio::test]
@ -39,10 +35,6 @@ async fn success() {
);
}
let rent = context.banks_client.get_rent().await.unwrap();
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>())
+ native_token::sol_to_lamports(1.0);
// Check current balance in the list
assert_eq!(
get_validator_list_sum(
@ -50,7 +42,7 @@ async fn success() {
&stake_pool_accounts.validator_list.pubkey()
)
.await,
STAKE_ACCOUNTS * (stake_rent + TEST_STAKE_AMOUNT)
0,
);
// Add extra funds, simulating rewards
@ -90,12 +82,12 @@ async fn success() {
&stake_pool_accounts.validator_list.pubkey()
)
.await,
STAKE_ACCOUNTS * (stake_rent + TEST_STAKE_AMOUNT + EXTRA_STAKE_AMOUNT)
STAKE_ACCOUNTS * EXTRA_STAKE_AMOUNT
);
}
#[tokio::test]
async fn test_update_validator_list_balance_with_uninitialized_validator_list() {} // TODO
async fn fail_with_uninitialized_validator_list() {} // TODO
#[tokio::test]
async fn test_update_validator_list_balance_with_wrong_stake_state() {} // TODO
async fn fail_with_wrong_stake_state() {} // TODO

View File

@ -19,7 +19,8 @@ use {
transport::TransportError,
},
spl_stake_pool::{
borsh::try_from_slice_unchecked, error, id, instruction, stake_program, state,
borsh::try_from_slice_unchecked, error::StakePoolError, id, instruction, stake_program,
state,
},
};
@ -29,7 +30,6 @@ async fn setup() -> (
Hash,
StakePoolAccounts,
ValidatorStakeAccount,
Keypair,
) {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let stake_pool_accounts = StakePoolAccounts::new();
@ -38,8 +38,6 @@ async fn setup() -> (
.await
.unwrap();
let user = Keypair::new();
let user_stake = ValidatorStakeAccount::new_with_target_authority(
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.stake_pool.pubkey(),
@ -53,39 +51,19 @@ async fn setup() -> (
)
.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();
(
banks_client,
payer,
recent_blockhash,
stake_pool_accounts,
user_stake,
user_pool_account,
)
}
#[tokio::test]
async fn test_add_validator_to_pool() {
let (
mut banks_client,
payer,
recent_blockhash,
stake_pool_accounts,
user_stake,
user_pool_account,
) = setup().await;
async fn success() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
setup().await;
let error = stake_pool_accounts
.add_validator_to_pool(
@ -93,28 +71,10 @@ async fn test_add_validator_to_pool() {
&payer,
&recent_blockhash,
&user_stake.stake_account,
&user_pool_account.pubkey(),
)
.await;
assert!(error.is_none());
let stake_lamports = banks_client
.get_account(user_stake.stake_account)
.await
.unwrap()
.unwrap()
.lamports;
let deposit_tokens = stake_lamports; // For now 1:1 math
// Check token account balance
let token_balance = get_token_balance(&mut banks_client, &user_pool_account.pubkey()).await;
assert_eq!(token_balance, deposit_tokens);
let pool_fee_token_balance = get_token_balance(
&mut banks_client,
&stake_pool_accounts.pool_fee_account.pubkey(),
)
.await;
assert_eq!(pool_fee_token_balance, 0); // No fee when adding validator stake accounts
// Check if validator account was added to the list
let validator_list = get_account(
&mut banks_client,
@ -129,9 +89,9 @@ async fn test_add_validator_to_pool() {
account_type: state::AccountType::ValidatorList,
max_validators: stake_pool_accounts.max_validators,
validators: vec![state::ValidatorStakeInfo {
vote_account: user_stake.vote.pubkey(),
vote_account_address: user_stake.vote.pubkey(),
last_update_epoch: 0,
stake_lamports,
stake_lamports: 0,
}]
}
);
@ -155,105 +115,9 @@ async fn test_add_validator_to_pool() {
}
#[tokio::test]
async fn test_add_validator_to_pool_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_to_pool(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.staker.pubkey(),
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.withdraw_authority,
&stake_pool_accounts.validator_list.pubkey(),
&user_stake.stake_account,
&user_pool_account.pubkey(),
&stake_pool_accounts.pool_mint.pubkey(),
&stake_program::id(),
)
.unwrap()],
Some(&payer.pubkey()),
);
transaction.sign(&[&payer, &stake_pool_accounts.staker], 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_to_pool_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_to_pool(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.staker.pubkey(),
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.withdraw_authority,
&stake_pool_accounts.validator_list.pubkey(),
&user_stake.stake_account,
&user_pool_account.pubkey(),
&wrong_pool_mint.pubkey(),
&spl_token::id(),
)
.unwrap()],
Some(&payer.pubkey()),
);
transaction.sign(&[&payer, &stake_pool_accounts.staker], 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_to_pool_with_wrong_validator_list_account() {
let (
mut banks_client,
payer,
recent_blockhash,
stake_pool_accounts,
user_stake,
user_pool_account,
) = setup().await;
async fn fail_with_wrong_validator_list_account() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
setup().await;
let wrong_validator_list = Keypair::new();
@ -266,9 +130,6 @@ async fn test_add_validator_to_pool_with_wrong_validator_list_account() {
&stake_pool_accounts.withdraw_authority,
&wrong_validator_list.pubkey(),
&user_stake.stake_account,
&user_pool_account.pubkey(),
&stake_pool_accounts.pool_mint.pubkey(),
&spl_token::id(),
)
.unwrap()],
Some(&payer.pubkey()),
@ -285,7 +146,7 @@ async fn test_add_validator_to_pool_with_wrong_validator_list_account() {
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::InvalidValidatorStakeList as u32;
let program_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"),
@ -293,15 +154,149 @@ async fn test_add_validator_to_pool_with_wrong_validator_list_account() {
}
#[tokio::test]
async fn test_try_to_add_already_added_validator_stake_account() {
let (
mut banks_client,
payer,
async fn fail_too_little_stake() {
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_stake = ValidatorStakeAccount::new_with_target_authority(
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.stake_pool.pubkey(),
);
create_vote(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake.vote,
)
.await;
create_validator_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake.stake_pool,
&stake_pool_accounts.staker,
&user_stake.stake_account,
&user_stake.vote.pubkey(),
)
.await;
// Create stake account to withdraw to
let split = Keypair::new();
create_blank_stake_account(&mut banks_client, &payer, &recent_blockhash, &split).await;
let transaction = Transaction::new_signed_with_payer(
&[stake_program::split_only(
&user_stake.stake_account,
&stake_pool_accounts.staker.pubkey(),
1,
&split.pubkey(),
)],
Some(&payer.pubkey()),
&[&payer, &stake_pool_accounts.staker],
recent_blockhash,
stake_pool_accounts,
user_stake,
user_pool_account,
) = setup().await;
);
banks_client.process_transaction(transaction).await.unwrap();
authorize_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake.stake_account,
&stake_pool_accounts.staker,
&user_stake.target_authority,
stake_program::StakeAuthorize::Staker,
)
.await;
authorize_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake.stake_account,
&stake_pool_accounts.staker,
&user_stake.target_authority,
stake_program::StakeAuthorize::Withdrawer,
)
.await;
let error = stake_pool_accounts
.add_validator_to_pool(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake.stake_account,
)
.await
.unwrap()
.unwrap();
assert_eq!(
error,
TransactionError::InstructionError(
0,
InstructionError::Custom(StakePoolError::StakeLamportsNotEqualToMinimum as u32)
),
);
}
#[tokio::test]
async fn fail_too_much_stake() {
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_stake = ValidatorStakeAccount::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,
&stake_pool_accounts.staker,
)
.await;
transfer(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake.stake_account,
1,
)
.await;
let error = stake_pool_accounts
.add_validator_to_pool(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake.stake_account,
)
.await
.unwrap()
.unwrap();
assert_eq!(
error,
TransactionError::InstructionError(
0,
InstructionError::Custom(StakePoolError::StakeLamportsNotEqualToMinimum as u32)
),
);
}
#[tokio::test]
async fn fail_double_add() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
setup().await;
stake_pool_accounts
.add_validator_to_pool(
@ -309,7 +304,6 @@ async fn test_try_to_add_already_added_validator_stake_account() {
&payer,
&recent_blockhash,
&user_stake.stake_account,
&user_pool_account.pubkey(),
)
.await;
@ -321,7 +315,6 @@ async fn test_try_to_add_already_added_validator_stake_account() {
&payer,
&latest_blockhash,
&user_stake.stake_account,
&user_pool_account.pubkey(),
)
.await
.unwrap();
@ -331,7 +324,7 @@ async fn test_try_to_add_already_added_validator_stake_account() {
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::ValidatorAlreadyAdded as u32;
let program_error = StakePoolError::ValidatorAlreadyAdded as u32;
assert_eq!(error_index, program_error);
}
_ => panic!("Wrong error occurs while try to add already added validator stake account"),
@ -339,15 +332,9 @@ async fn test_try_to_add_already_added_validator_stake_account() {
}
#[tokio::test]
async fn test_not_staker_try_to_add_validator_to_pool() {
let (
mut banks_client,
payer,
recent_blockhash,
stake_pool_accounts,
user_stake,
user_pool_account,
) = setup().await;
async fn fail_wrong_staker() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
setup().await;
let malicious = Keypair::new();
@ -360,9 +347,6 @@ async fn test_not_staker_try_to_add_validator_to_pool() {
&stake_pool_accounts.withdraw_authority,
&stake_pool_accounts.validator_list.pubkey(),
&user_stake.stake_account,
&user_pool_account.pubkey(),
&stake_pool_accounts.pool_mint.pubkey(),
&spl_token::id(),
)
.unwrap()],
Some(&payer.pubkey()),
@ -379,7 +363,7 @@ async fn test_not_staker_try_to_add_validator_to_pool() {
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::WrongStaker as u32;
let program_error = StakePoolError::WrongStaker as u32;
assert_eq!(error_index, program_error);
}
_ => panic!("Wrong error occurs while malicious try to add validator stake account"),
@ -387,15 +371,9 @@ async fn test_not_staker_try_to_add_validator_to_pool() {
}
#[tokio::test]
async fn test_not_staker_try_to_add_validator_to_pool_without_signature() {
let (
mut banks_client,
payer,
recent_blockhash,
stake_pool_accounts,
user_stake,
user_pool_account,
) = setup().await;
async fn fail_without_signature() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
setup().await;
let accounts = vec![
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false),
@ -404,11 +382,8 @@ async fn test_not_staker_try_to_add_validator_to_pool_without_signature() {
AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false),
AccountMeta::new(stake_pool_accounts.validator_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(sysvar::stake_history::id(), false),
AccountMeta::new_readonly(spl_token::id(), false),
AccountMeta::new_readonly(stake_program::id(), false),
];
let instruction = Instruction {
@ -432,7 +407,7 @@ async fn test_not_staker_try_to_add_validator_to_pool_without_signature() {
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::SignatureMissing as u32;
let program_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"),
@ -440,15 +415,9 @@ async fn test_not_staker_try_to_add_validator_to_pool_without_signature() {
}
#[tokio::test]
async fn test_add_validator_to_pool_with_wrong_stake_program_id() {
let (
mut banks_client,
payer,
recent_blockhash,
stake_pool_accounts,
user_stake,
user_pool_account,
) = setup().await;
async fn fail_with_wrong_stake_program_id() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
setup().await;
let wrong_stake_program = Pubkey::new_unique();
@ -459,11 +428,8 @@ async fn test_add_validator_to_pool_with_wrong_stake_program_id() {
AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false),
AccountMeta::new(stake_pool_accounts.validator_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(sysvar::stake_history::id(), false),
AccountMeta::new_readonly(spl_token::id(), false),
AccountMeta::new_readonly(wrong_stake_program, false),
];
let instruction = Instruction {
@ -492,7 +458,7 @@ async fn test_add_validator_to_pool_with_wrong_stake_program_id() {
}
#[tokio::test]
async fn test_add_too_many_validator_stake_accounts() {
async fn fail_add_too_many_validator_stake_accounts() {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let mut stake_pool_accounts = StakePoolAccounts::new();
stake_pool_accounts.max_validators = 1;
@ -501,8 +467,6 @@ async fn test_add_too_many_validator_stake_accounts() {
.await
.unwrap();
let user = Keypair::new();
let user_stake = ValidatorStakeAccount::new_with_target_authority(
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.stake_pool.pubkey(),
@ -516,26 +480,12 @@ async fn test_add_too_many_validator_stake_accounts() {
)
.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_to_pool(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake.stake_account,
&user_pool_account.pubkey(),
)
.await;
assert!(error.is_none());
@ -558,7 +508,6 @@ async fn test_add_too_many_validator_stake_accounts() {
&payer,
&recent_blockhash,
&user_stake.stake_account,
&user_pool_account.pubkey(),
)
.await
.unwrap()
@ -570,7 +519,7 @@ async fn test_add_too_many_validator_stake_accounts() {
}
#[tokio::test]
async fn test_add_validator_to_pool_to_unupdated_stake_pool() {} // TODO
async fn fail_with_unupdated_stake_pool() {} // TODO
#[tokio::test]
async fn test_add_validator_to_pool_with_uninitialized_validator_list_account() {} // TODO
async fn fail_with_uninitialized_validator_list_account() {} // TODO

View File

@ -19,7 +19,8 @@ use {
transport::TransportError,
},
spl_stake_pool::{
borsh::try_from_slice_unchecked, error, id, instruction, stake_program, state,
borsh::try_from_slice_unchecked, error::StakePoolError, id, instruction, stake_program,
state,
},
};
@ -29,8 +30,6 @@ async fn setup() -> (
Hash,
StakePoolAccounts,
ValidatorStakeAccount,
Keypair,
Keypair,
) {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let stake_pool_accounts = StakePoolAccounts::new();
@ -39,8 +38,6 @@ async fn setup() -> (
.await
.unwrap();
let user = Keypair::new();
let user_stake = ValidatorStakeAccount::new_with_target_authority(
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.stake_pool.pubkey(),
@ -54,26 +51,12 @@ async fn setup() -> (
)
.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_to_pool(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake.stake_account,
&user_pool_account.pubkey(),
)
.await;
assert!(error.is_none());
@ -84,34 +67,13 @@ async fn setup() -> (
recent_blockhash,
stake_pool_accounts,
user_stake,
user_pool_account,
user,
)
}
#[tokio::test]
async fn test_remove_validator_from_pool() {
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;
async fn success() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
setup().await;
let new_authority = Pubkey::new_unique();
let error = stake_pool_accounts
@ -120,16 +82,11 @@ async fn test_remove_validator_from_pool() {
&payer,
&recent_blockhash,
&user_stake.stake_account,
&user_pool_account.pubkey(),
&new_authority,
)
.await;
assert!(error.is_none());
// Check if all tokens were burned
let tokens_left = get_token_balance(&mut banks_client, &user_pool_account.pubkey()).await;
assert_eq!(tokens_left, 0);
// Check if account was removed from the list of stake accounts
let validator_list = get_account(
&mut banks_client,
@ -160,16 +117,9 @@ async fn test_remove_validator_from_pool() {
}
#[tokio::test]
async fn test_remove_validator_from_pool_with_wrong_stake_program_id() {
let (
mut banks_client,
payer,
recent_blockhash,
stake_pool_accounts,
user_stake,
user_pool_account,
_,
) = setup().await;
async fn fail_with_wrong_stake_program_id() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
setup().await;
let wrong_stake_program = Pubkey::new_unique();
@ -181,10 +131,7 @@ async fn test_remove_validator_from_pool_with_wrong_stake_program_id() {
AccountMeta::new_readonly(new_authority, false),
AccountMeta::new(stake_pool_accounts.validator_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(wrong_stake_program, false),
];
let instruction = Instruction {
@ -215,112 +162,9 @@ async fn test_remove_validator_from_pool_with_wrong_stake_program_id() {
}
#[tokio::test]
async fn test_remove_validator_from_pool_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_from_pool(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.staker.pubkey(),
&stake_pool_accounts.withdraw_authority,
&new_authority,
&stake_pool_accounts.validator_list.pubkey(),
&user_stake.stake_account,
&user_pool_account.pubkey(),
&stake_pool_accounts.pool_mint.pubkey(),
&wrong_token_program.pubkey(),
)
.unwrap()],
Some(&payer.pubkey()),
);
transaction.sign(&[&payer, &stake_pool_accounts.staker], 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_from_pool_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_from_pool(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.staker.pubkey(),
&stake_pool_accounts.withdraw_authority,
&new_authority,
&stake_pool_accounts.validator_list.pubkey(),
&user_stake.stake_account,
&user_pool_account.pubkey(),
&wrong_pool_mint.pubkey(),
&spl_token::id(),
)
.unwrap()],
Some(&payer.pubkey()),
);
transaction.sign(&[&payer, &stake_pool_accounts.staker], 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_from_pool_with_wrong_validator_list_account() {
let (
mut banks_client,
payer,
recent_blockhash,
stake_pool_accounts,
user_stake,
user_pool_account,
_,
) = setup().await;
async fn fail_with_wrong_validator_list_account() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
setup().await;
let wrong_validator_list = Keypair::new();
@ -334,9 +178,6 @@ async fn test_remove_validator_from_pool_with_wrong_validator_list_account() {
&new_authority,
&wrong_validator_list.pubkey(),
&user_stake.stake_account,
&user_pool_account.pubkey(),
&stake_pool_accounts.pool_mint.pubkey(),
&spl_token::id(),
)
.unwrap()],
Some(&payer.pubkey()),
@ -353,7 +194,7 @@ async fn test_remove_validator_from_pool_with_wrong_validator_list_account() {
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::InvalidValidatorStakeList as u32;
let program_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"),
@ -361,26 +202,16 @@ async fn test_remove_validator_from_pool_with_wrong_validator_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;
async fn fail_not_at_minimum() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
setup().await;
let tokens_to_burn = get_token_balance(&mut banks_client, &user_pool_account.pubkey()).await;
delegate_tokens(
transfer(
&mut banks_client,
&payer,
&recent_blockhash,
&user_pool_account.pubkey(),
&user,
&stake_pool_accounts.withdraw_authority,
tokens_to_burn,
&user_stake.stake_account,
1_000_001,
)
.await;
@ -391,7 +222,32 @@ async fn test_remove_already_removed_validator_stake_account() {
&payer,
&recent_blockhash,
&user_stake.stake_account,
&user_pool_account.pubkey(),
&new_authority,
)
.await
.unwrap()
.unwrap();
assert_eq!(
error,
TransactionError::InstructionError(
0,
InstructionError::Custom(StakePoolError::StakeLamportsNotEqualToMinimum as u32)
),
);
}
#[tokio::test]
async fn fail_double_remove() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
setup().await;
let new_authority = Pubkey::new_unique();
let error = stake_pool_accounts
.remove_validator_from_pool(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake.stake_account,
&new_authority,
)
.await;
@ -405,7 +261,6 @@ async fn test_remove_already_removed_validator_stake_account() {
&payer,
&latest_blockhash,
&user_stake.stake_account,
&user_pool_account.pubkey(),
&new_authority,
)
.await
@ -416,7 +271,7 @@ async fn test_remove_already_removed_validator_stake_account() {
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::ValidatorNotFound as u32;
let program_error = StakePoolError::ValidatorNotFound as u32;
assert_eq!(error_index, program_error);
}
_ => {
@ -426,16 +281,9 @@ async fn test_remove_already_removed_validator_stake_account() {
}
#[tokio::test]
async fn test_not_staker_try_to_remove_validator_from_pool() {
let (
mut banks_client,
payer,
recent_blockhash,
stake_pool_accounts,
user_stake,
user_pool_account,
_,
) = setup().await;
async fn fail_wrong_staker() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
setup().await;
let malicious = Keypair::new();
@ -449,9 +297,6 @@ async fn test_not_staker_try_to_remove_validator_from_pool() {
&new_authority,
&stake_pool_accounts.validator_list.pubkey(),
&user_stake.stake_account,
&user_pool_account.pubkey(),
&stake_pool_accounts.pool_mint.pubkey(),
&spl_token::id(),
)
.unwrap()],
Some(&payer.pubkey()),
@ -468,7 +313,7 @@ async fn test_not_staker_try_to_remove_validator_from_pool() {
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::WrongStaker as u32;
let program_error = StakePoolError::WrongStaker as u32;
assert_eq!(error_index, program_error);
}
_ => {
@ -478,16 +323,9 @@ async fn test_not_staker_try_to_remove_validator_from_pool() {
}
#[tokio::test]
async fn test_not_staker_try_to_remove_validator_from_pool_without_signature() {
let (
mut banks_client,
payer,
recent_blockhash,
stake_pool_accounts,
user_stake,
user_pool_account,
_,
) = setup().await;
async fn fail_no_signature() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
setup().await;
let new_authority = Pubkey::new_unique();
@ -498,10 +336,7 @@ async fn test_not_staker_try_to_remove_validator_from_pool_without_signature() {
AccountMeta::new_readonly(new_authority, false),
AccountMeta::new(stake_pool_accounts.validator_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_program::id(), false),
];
let instruction = Instruction {
@ -512,8 +347,12 @@ async fn test_not_staker_try_to_remove_validator_from_pool_without_signature() {
.unwrap(),
};
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
transaction.sign(&[&payer], recent_blockhash);
let transaction = Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[&payer],
recent_blockhash,
);
let transaction_error = banks_client
.process_transaction(transaction)
.await
@ -525,7 +364,7 @@ async fn test_not_staker_try_to_remove_validator_from_pool_without_signature() {
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::SignatureMissing as u32;
let program_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"),
@ -533,7 +372,7 @@ async fn test_not_staker_try_to_remove_validator_from_pool_without_signature() {
}
#[tokio::test]
async fn test_remove_validator_from_pool_from_unupdated_stake_pool() {} // TODO
async fn fail_not_updated_stake_pool() {} // TODO
#[tokio::test]
async fn test_remove_validator_from_pool_with_uninitialized_validator_list_account() {} // TODO
async fn fail_with_uninitialized_validator_list_account() {} // TODO

View File

@ -3,6 +3,7 @@
mod helpers;
use {
bincode::deserialize,
borsh::{BorshDeserialize, BorshSerialize},
helpers::*,
solana_program::{
@ -18,7 +19,8 @@ use {
transport::TransportError,
},
spl_stake_pool::{
borsh::try_from_slice_unchecked, error, id, instruction, stake_program, state,
borsh::try_from_slice_unchecked, error::StakePoolError, id, instruction,
minimum_stake_lamports, stake_program, state,
},
spl_token::error::TokenError,
};
@ -39,7 +41,7 @@ async fn setup() -> (
.await
.unwrap();
let validator_stake_account: ValidatorStakeAccount = simple_add_validator_to_pool(
let validator_stake_account = simple_add_validator_to_pool(
&mut banks_client,
&payer,
&recent_blockhash,
@ -47,7 +49,7 @@ async fn setup() -> (
)
.await;
let deposit_info: DepositInfo = simple_deposit(
let deposit_info = simple_deposit(
&mut banks_client,
&payer,
&recent_blockhash,
@ -82,7 +84,7 @@ async fn setup() -> (
}
#[tokio::test]
async fn test_stake_pool_withdraw() {
async fn success() {
let (
mut banks_client,
payer,
@ -126,7 +128,7 @@ async fn test_stake_pool_withdraw() {
get_token_balance(&mut banks_client, &deposit_info.user_pool_account).await;
let new_authority = Pubkey::new_unique();
stake_pool_accounts
let error = stake_pool_accounts
.withdraw_stake(
&mut banks_client,
&payer,
@ -137,8 +139,8 @@ async fn test_stake_pool_withdraw() {
&new_authority,
tokens_to_burn,
)
.await
.unwrap();
.await;
assert!(error.is_none());
// Check pool stats
let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
@ -179,8 +181,11 @@ async fn test_stake_pool_withdraw() {
// Check validator stake account balance
let validator_stake_account =
get_account(&mut banks_client, &validator_stake_account.stake_account).await;
let stake_state =
deserialize::<stake_program::StakeState>(&validator_stake_account.data).unwrap();
let meta = stake_state.meta().unwrap();
assert_eq!(
validator_stake_account.lamports,
validator_stake_account.lamports - minimum_stake_lamports(&meta),
validator_stake_item.stake_lamports
);
@ -194,7 +199,7 @@ async fn test_stake_pool_withdraw() {
}
#[tokio::test]
async fn test_stake_pool_withdraw_with_wrong_stake_program() {
async fn fail_with_wrong_stake_program() {
let (
mut banks_client,
payer,
@ -249,7 +254,7 @@ async fn test_stake_pool_withdraw_with_wrong_stake_program() {
}
#[tokio::test]
async fn test_stake_pool_withdraw_with_wrong_withdraw_authority() {
async fn fail_with_wrong_withdraw_authority() {
let (
mut banks_client,
payer,
@ -278,7 +283,6 @@ async fn test_stake_pool_withdraw_with_wrong_withdraw_authority() {
tokens_to_burn,
)
.await
.err()
.unwrap();
match transaction_error {
@ -286,7 +290,7 @@ async fn test_stake_pool_withdraw_with_wrong_withdraw_authority() {
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::InvalidProgramAddress as u32;
let program_error = StakePoolError::InvalidProgramAddress as u32;
assert_eq!(error_index, program_error);
}
_ => panic!("Wrong error occurs while try to withdraw with wrong withdraw authority"),
@ -294,7 +298,7 @@ async fn test_stake_pool_withdraw_with_wrong_withdraw_authority() {
}
#[tokio::test]
async fn test_stake_pool_withdraw_with_wrong_token_program_id() {
async fn fail_with_wrong_token_program_id() {
let (
mut banks_client,
payer,
@ -344,7 +348,7 @@ async fn test_stake_pool_withdraw_with_wrong_token_program_id() {
}
#[tokio::test]
async fn test_stake_pool_withdraw_with_wrong_validator_list() {
async fn fail_with_wrong_validator_list() {
let (
mut banks_client,
payer,
@ -373,7 +377,6 @@ async fn test_stake_pool_withdraw_with_wrong_validator_list() {
tokens_to_burn,
)
.await
.err()
.unwrap();
match transaction_error {
@ -381,7 +384,7 @@ async fn test_stake_pool_withdraw_with_wrong_validator_list() {
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::InvalidValidatorStakeList as u32;
let program_error = StakePoolError::InvalidValidatorStakeList as u32;
assert_eq!(error_index, program_error);
}
_ => panic!(
@ -391,7 +394,7 @@ async fn test_stake_pool_withdraw_with_wrong_validator_list() {
}
#[tokio::test]
async fn test_stake_pool_withdraw_from_unknown_validator() {
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
@ -502,7 +505,6 @@ async fn test_stake_pool_withdraw_from_unknown_validator() {
tokens_to_burn,
)
.await
.err()
.unwrap();
match transaction_error {
@ -510,7 +512,7 @@ async fn test_stake_pool_withdraw_from_unknown_validator() {
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::ValidatorNotFound as u32;
let program_error = StakePoolError::ValidatorNotFound as u32;
assert_eq!(error_index, program_error);
}
_ => panic!("Wrong error occurs while try to do withdraw from unknown validator"),
@ -518,7 +520,7 @@ async fn test_stake_pool_withdraw_from_unknown_validator() {
}
#[tokio::test]
async fn test_stake_pool_double_withdraw_to_the_same_account() {
async fn fail_double_withdraw_to_the_same_account() {
let (
mut banks_client,
payer,
@ -540,7 +542,7 @@ async fn test_stake_pool_double_withdraw_to_the_same_account() {
.await;
let new_authority = Pubkey::new_unique();
stake_pool_accounts
let error = stake_pool_accounts
.withdraw_stake(
&mut banks_client,
&payer,
@ -551,11 +553,23 @@ async fn test_stake_pool_double_withdraw_to_the_same_account() {
&new_authority,
tokens_to_burn,
)
.await
.unwrap();
.await;
assert!(error.is_none());
let latest_blockhash = banks_client.get_recent_blockhash().await.unwrap();
// Delegate tokens for burning
delegate_tokens(
&mut banks_client,
&payer,
&latest_blockhash,
&deposit_info.user_pool_account,
&deposit_info.user,
&stake_pool_accounts.withdraw_authority,
tokens_to_burn,
)
.await;
let transaction_error = stake_pool_accounts
.withdraw_stake(
&mut banks_client,
@ -568,7 +582,6 @@ async fn test_stake_pool_double_withdraw_to_the_same_account() {
tokens_to_burn,
)
.await
.err()
.unwrap();
match transaction_error {
@ -580,7 +593,7 @@ async fn test_stake_pool_double_withdraw_to_the_same_account() {
}
#[tokio::test]
async fn test_stake_pool_withdraw_token_delegate_was_not_setup() {
async fn fail_without_token_approval() {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let stake_pool_accounts = StakePoolAccounts::new();
stake_pool_accounts
@ -588,7 +601,7 @@ async fn test_stake_pool_withdraw_token_delegate_was_not_setup() {
.await
.unwrap();
let validator_stake_account: ValidatorStakeAccount = simple_add_validator_to_pool(
let validator_stake_account = simple_add_validator_to_pool(
&mut banks_client,
&payer,
&recent_blockhash,
@ -596,7 +609,7 @@ async fn test_stake_pool_withdraw_token_delegate_was_not_setup() {
)
.await;
let deposit_info: DepositInfo = simple_deposit(
let deposit_info = simple_deposit(
&mut banks_client,
&payer,
&recent_blockhash,
@ -630,7 +643,6 @@ async fn test_stake_pool_withdraw_token_delegate_was_not_setup() {
tokens_to_burn,
)
.await
.err()
.unwrap();
match transaction_error {
@ -648,7 +660,7 @@ async fn test_stake_pool_withdraw_token_delegate_was_not_setup() {
}
#[tokio::test]
async fn test_stake_pool_withdraw_with_low_delegation() {
async fn fail_with_low_delegation() {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let stake_pool_accounts = StakePoolAccounts::new();
stake_pool_accounts
@ -656,7 +668,7 @@ async fn test_stake_pool_withdraw_with_low_delegation() {
.await
.unwrap();
let validator_stake_account: ValidatorStakeAccount = simple_add_validator_to_pool(
let validator_stake_account = simple_add_validator_to_pool(
&mut banks_client,
&payer,
&recent_blockhash,
@ -664,7 +676,7 @@ async fn test_stake_pool_withdraw_with_low_delegation() {
)
.await;
let deposit_info: DepositInfo = simple_deposit(
let deposit_info = simple_deposit(
&mut banks_client,
&payer,
&recent_blockhash,
@ -710,7 +722,6 @@ async fn test_stake_pool_withdraw_with_low_delegation() {
tokens_to_burn,
)
.await
.err()
.unwrap();
match transaction_error {
@ -726,3 +737,57 @@ async fn test_stake_pool_withdraw_with_low_delegation() {
),
}
}
#[tokio::test]
async fn fail_overdraw_validator() {
let (
mut banks_client,
payer,
recent_blockhash,
stake_pool_accounts,
_validator_stake_account,
deposit_info,
tokens_to_burn,
) = setup().await;
// Create stake account to withdraw to
let user_stake_recipient = Keypair::new();
let _initial_stake_lamports = create_blank_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake_recipient,
)
.await;
let validator_stake_account = simple_add_validator_to_pool(
&mut banks_client,
&payer,
&recent_blockhash,
&stake_pool_accounts,
)
.await;
let new_authority = Pubkey::new_unique();
let 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,
tokens_to_burn,
)
.await
.unwrap()
.unwrap();
assert_eq!(
error,
TransactionError::InstructionError(
0,
InstructionError::Custom(StakePoolError::StakeLamportsNotEqualToMinimum as u32)
),
);
}