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:
parent
cf8eeb0720
commit
30671aa5b3
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue