Various postponed fixes and changes to the stake pool program (#1200)

* Various postponed fixes and changes to the stake pool program

* Fixed PR comments

* Fixed no-signature validator stake account add test

Co-authored-by: Yuriy Savchenko <yuriy.savchenko@gmail.com>
This commit is contained in:
Atticlab LLC 2021-02-10 18:42:28 +02:00 committed by GitHub
parent beb4aa7e7f
commit 11df4aa5ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1078 additions and 332 deletions

View File

@ -39,7 +39,6 @@ use spl_stake_pool::{
stake::StakeAuthorize,
stake::StakeState,
state::StakePool,
state::State as PoolState,
state::ValidatorStakeList,
};
use spl_token::{
@ -148,7 +147,7 @@ fn command_create_pool(config: &Config, fee: PoolFee) -> CommandResult {
.get_minimum_balance_for_rent_exemption(TokenAccount::LEN)?;
let pool_account_balance = config
.rpc_client
.get_minimum_balance_for_rent_exemption(PoolState::LEN)?;
.get_minimum_balance_for_rent_exemption(StakePool::LEN)?;
let validator_stake_list_balance = config
.rpc_client
.get_minimum_balance_for_rent_exemption(ValidatorStakeList::LEN)?;
@ -193,7 +192,7 @@ fn command_create_pool(config: &Config, fee: PoolFee) -> CommandResult {
&config.fee_payer.pubkey(),
&pool_account.pubkey(),
pool_account_balance,
PoolState::LEN as u64,
StakePool::LEN as u64,
&spl_stake_pool::id(),
),
// Validator stake account list storage
@ -245,6 +244,7 @@ fn command_create_pool(config: &Config, fee: PoolFee) -> CommandResult {
&validator_stake_list,
&mint_account,
&pool_fee_account,
config.owner.as_ref(),
];
unique_signers!(signers);
transaction.sign(&signers, recent_blockhash);
@ -289,10 +289,7 @@ fn command_vsa_add(
) -> CommandResult {
// Get stake pool state
let pool_data = config.rpc_client.get_account_data(&pool)?;
let pool_data: StakePool = PoolState::deserialize(pool_data.as_slice())
.unwrap()
.stake_pool()
.unwrap();
let pool_data: StakePool = StakePool::deserialize(pool_data.as_slice()).unwrap();
let mut total_rent_free_balances: u64 = 0;
@ -383,10 +380,7 @@ fn command_vsa_remove(
) -> CommandResult {
// Get stake pool state
let pool_data = config.rpc_client.get_account_data(&pool)?;
let pool_data: StakePool = PoolState::deserialize(pool_data.as_slice())
.unwrap()
.stake_pool()
.unwrap();
let pool_data: StakePool = StakePool::deserialize(pool_data.as_slice()).unwrap();
let pool_withdraw_authority: Pubkey = PoolProcessor::authority_id(
&spl_stake_pool::id(),
@ -512,10 +506,7 @@ fn command_deposit(
) -> CommandResult {
// Get stake pool state
let pool_data = config.rpc_client.get_account_data(&pool)?;
let pool_data: StakePool = PoolState::deserialize(pool_data.as_slice())
.unwrap()
.stake_pool()
.unwrap();
let pool_data: StakePool = StakePool::deserialize(pool_data.as_slice()).unwrap();
// Get stake account data
let stake_data = config.rpc_client.get_account_data(&stake)?;
@ -624,10 +615,7 @@ fn command_deposit(
fn command_list(config: &Config, pool: &Pubkey) -> CommandResult {
// Get stake pool state
let pool_data = config.rpc_client.get_account_data(&pool)?;
let pool_data: StakePool = PoolState::deserialize(pool_data.as_slice())
.unwrap()
.stake_pool()
.unwrap();
let pool_data: StakePool = StakePool::deserialize(pool_data.as_slice()).unwrap();
let pool_withdraw_authority: Pubkey = PoolProcessor::authority_id(
&spl_stake_pool::id(),
@ -657,10 +645,7 @@ fn command_list(config: &Config, pool: &Pubkey) -> CommandResult {
fn command_update(config: &Config, pool: &Pubkey) -> CommandResult {
// Get stake pool state
let pool_data = config.rpc_client.get_account_data(&pool)?;
let pool_data: StakePool = PoolState::deserialize(pool_data.as_slice())
.unwrap()
.stake_pool()
.unwrap();
let pool_data: StakePool = StakePool::deserialize(pool_data.as_slice()).unwrap();
let validator_stake_list_data = config
.rpc_client
.get_account_data(&pool_data.validator_stake_list)?;
@ -804,10 +789,7 @@ fn command_withdraw(
) -> CommandResult {
// Get stake pool state
let pool_data = config.rpc_client.get_account_data(&pool)?;
let pool_data: StakePool = PoolState::deserialize(pool_data.as_slice())
.unwrap()
.stake_pool()
.unwrap();
let pool_data: StakePool = StakePool::deserialize(pool_data.as_slice()).unwrap();
let pool_withdraw_authority: Pubkey = PoolProcessor::authority_id(
&spl_stake_pool::id(),
@ -827,19 +809,21 @@ fn command_withdraw(
}
// Check burn_from balance
let max_withdraw_amount = pool_tokens_to_stake_amount(&pool_data, account_data.amount);
if max_withdraw_amount < amount {
if account_data.amount < amount {
return Err(format!(
"Not enough token balance to withdraw {} SOL.\nMaximum withdraw amount is {} SOL.",
"Not enough token balance to withdraw {} pool tokens.\nMaximum withdraw amount is {} pool tokens.",
lamports_to_sol(amount),
lamports_to_sol(max_withdraw_amount)
lamports_to_sol(account_data.amount)
)
.into());
}
// Convert pool tokens amount to lamports
let sol_withdraw_amount = pool_tokens_to_stake_amount(&pool_data, amount);
// Get the list of accounts to withdraw from
let withdraw_from: Vec<WithdrawAccount> =
prepare_withdraw_accounts(config, &pool_withdraw_authority, amount)?;
prepare_withdraw_accounts(config, &pool_withdraw_authority, sol_withdraw_amount)?;
// Construct transaction to withdraw from withdraw_from account list
let mut instructions: Vec<Instruction> = vec![];
@ -848,9 +832,6 @@ fn command_withdraw(
let mut total_rent_free_balances: u64 = 0;
// Calculate amount of tokens to burn
let tokens_to_burn = stake_amount_to_pool_tokens(&pool_data, amount);
instructions.push(
// Approve spending token
approve_token(
@ -859,7 +840,7 @@ fn command_withdraw(
&pool_withdraw_authority,
&config.owner.pubkey(),
&[],
tokens_to_burn,
amount,
)?,
);
@ -940,10 +921,7 @@ fn command_set_staking_auth(
new_staker: &Pubkey,
) -> CommandResult {
let pool_data = config.rpc_client.get_account_data(&pool)?;
let pool_data: StakePool = PoolState::deserialize(pool_data.as_slice())
.unwrap()
.stake_pool()
.unwrap();
let pool_data: StakePool = StakePool::deserialize(pool_data.as_slice()).unwrap();
let pool_withdraw_authority: Pubkey = PoolProcessor::authority_id(
&spl_stake_pool::id(),
@ -981,10 +959,7 @@ fn command_set_owner(
new_fee_receiver: &Option<Pubkey>,
) -> CommandResult {
let pool_data = config.rpc_client.get_account_data(&pool)?;
let pool_data: StakePool = PoolState::deserialize(pool_data.as_slice())
.unwrap()
.stake_pool()
.unwrap();
let pool_data: StakePool = StakePool::deserialize(pool_data.as_slice()).unwrap();
// If new accounts are missing in the arguments use the old ones
let new_owner: Pubkey = match new_owner {
@ -1264,7 +1239,7 @@ fn main() {
.value_name("AMOUNT")
.takes_value(true)
.required(true)
.help("Amount in SOL to withdraw from the pool."),
.help("Amount of pool tokens to burn and get rewards."),
)
.arg(
Arg::with_name("burn_from")
@ -1444,6 +1419,7 @@ fn main() {
("withdraw", Some(arg_matches)) => {
let pool_account: Pubkey = pubkey_of(arg_matches, "pool").unwrap();
let burn_from: Pubkey = pubkey_of(arg_matches, "burn_from").unwrap();
// convert from float to int, using sol_to_lamports because they have the same precision as SOL
let amount: u64 = sol_to_lamports(value_t_or_exit!(arg_matches, "amount", f64));
let stake_receiver: Option<Pubkey> = pubkey_of(arg_matches, "stake_receiver");
command_withdraw(&config, &pool_account, amount, &burn_from, &stake_receiver)

View File

@ -13,7 +13,7 @@ pub enum StakePoolError {
/// The program address provided doesn't match the value generated by the program.
#[error("InvalidProgramAddress")]
InvalidProgramAddress,
/// The token swap state is invalid.
/// The stake pool state is invalid.
#[error("InvalidState")]
InvalidState,
/// The calculation failed.
@ -46,6 +46,9 @@ pub enum StakePoolError {
/// Stake account is not in the state expected by the program.
#[error("WrongStakeState")]
WrongStakeState,
/// User stake is not active
#[error("UserStakeNotActive")]
UserStakeNotActive,
/// Stake account voting for this validator already exists in the pool.
#[error("ValidatorAlreadyAdded")]
ValidatorAlreadyAdded,
@ -58,12 +61,18 @@ pub enum StakePoolError {
/// Identify validator stake accounts with old balances and update them.
#[error("StakeListOutOfDate")]
StakeListOutOfDate,
/// First udpate old validator stake account balances and then pool stake balance.
/// First update old validator stake account balances and then pool stake balance.
#[error("StakeListAndPoolOutOfDate")]
StakeListAndPoolOutOfDate,
/// Validator stake account is not found in the list storage.
#[error("UnknownValidatorStakeAccount")]
UnknownValidatorStakeAccount,
/// Wrong minting authority set for mint pool account
#[error("WrongMintingAuthority")]
WrongMintingAuthority,
/// Account is not rent-exempt
#[error("AccountNotRentExempt")]
AccountNotRentExempt,
}
impl From<StakePoolError> for ProgramError {
fn from(e: StakePoolError) -> Self {

View File

@ -35,11 +35,13 @@ pub enum StakePoolInstruction {
/// Initializes a new StakePool.
///
/// 0. `[w]` New StakePool to create.
/// 1. `[]` Owner
/// 1. `[s]` Owner
/// 2. `[w]` Uninitialized validator stake list storage account
/// 3. `[]` pool token Mint. Must be non zero, owned by withdraw authority.
/// 4. `[]` Pool Account to deposit the generated fee for owner.
/// 5. `[]` Token program id
/// 5. `[]` Clock sysvar
/// 6. `[]` Rent sysvar
/// 7. `[]` Token program id
Initialize(InitArgs),
/// Creates new program account for accumulating stakes for a particular validator
@ -66,8 +68,9 @@ pub enum StakePoolInstruction {
/// 6. `[w]` User account to receive pool tokens
/// 7. `[w]` Pool token mint account
/// 8. `[]` Clock sysvar (required)
/// 9. `[]` Pool token program id,
/// 10. `[]` Stake program id,
/// 9. '[]' Sysvar stake history account
/// 10. `[]` Pool token program id,
/// 11. `[]` Stake program id,
AddValidatorStakeAccount,
/// Removes validator stake account from the pool
@ -253,11 +256,12 @@ pub fn initialize(
let data = init_data.serialize()?;
let accounts = vec![
AccountMeta::new(*stake_pool, true),
AccountMeta::new_readonly(*owner, false),
AccountMeta::new_readonly(*owner, true),
AccountMeta::new(*validator_stake_list, false),
AccountMeta::new_readonly(*pool_mint, false),
AccountMeta::new_readonly(*owner_pool_account, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(*token_program_id, false),
];
Ok(Instruction {
@ -321,6 +325,7 @@ pub fn add_validator_stake_account(
AccountMeta::new(*pool_tokens_to, 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),
];

View File

@ -4,7 +4,7 @@ use crate::{
error::StakePoolError,
instruction::{InitArgs, StakePoolInstruction},
stake,
state::{StakePool, State, ValidatorStakeInfo, ValidatorStakeList},
state::{StakePool, ValidatorStakeInfo, ValidatorStakeList},
PROGRAM_VERSION,
};
use bincode::deserialize;
@ -22,9 +22,11 @@ use solana_program::{
program_pack::Pack,
pubkey::Pubkey,
rent::Rent,
stake_history::StakeHistory,
system_instruction,
sysvar::Sysvar,
};
use spl_token::state::Mint;
/// Program state handler.
pub struct Processor {}
@ -293,28 +295,60 @@ impl Processor {
// Clock sysvar account
let clock_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(clock_info)?;
// Rent sysvar account
let rent_info = next_account_info(account_info_iter)?;
let rent = &Rent::from_account_info(rent_info)?;
// Token program ID
let token_program_info = next_account_info(account_info_iter)?;
// Check if transaction was signed by owner
if !owner_info.is_signer {
return Err(StakePoolError::SignatureMissing.into());
}
let mut stake_pool = StakePool::deserialize(&stake_pool_info.data.borrow())?;
// Stake pool account should not be already initialized
if State::Unallocated != State::deserialize(&stake_pool_info.data.borrow())? {
if stake_pool.is_initialized() {
return Err(StakePoolError::AlreadyInUse.into());
}
// Check if validator stake list storage is unitialized
let mut validator_stake_list =
ValidatorStakeList::deserialize(&validator_stake_list_info.data.borrow())?;
if validator_stake_list.is_initialized {
if validator_stake_list.is_initialized() {
return Err(StakePoolError::AlreadyInUse.into());
}
validator_stake_list.is_initialized = true;
validator_stake_list.version = ValidatorStakeList::VALIDATOR_STAKE_LIST_VERSION;
validator_stake_list.validators.clear();
// Check if stake pool account is rent-exempt
if !rent.is_exempt(stake_pool_info.lamports(), stake_pool_info.data_len()) {
return Err(StakePoolError::AccountNotRentExempt.into());
}
// Check if validator stake list account is rent-exempt
if !rent.is_exempt(
validator_stake_list_info.lamports(),
validator_stake_list_info.data_len(),
) {
return Err(StakePoolError::AccountNotRentExempt.into());
}
// Numerator should be smaller than or equal to denominator (fee <= 1)
if init.fee.numerator > init.fee.denominator {
return Err(StakePoolError::FeeTooHigh.into());
}
// Check if fee account's owner the same as token program id
if owner_fee_info.owner != token_program_info.key {
return Err(StakePoolError::InvalidFeeAccount.into());
}
// Check pool mint program ID
if pool_mint_info.owner != token_program_info.key {
return Err(ProgramError::IncorrectProgramId);
}
// Check for owner fee account to have proper mint assigned
if *pool_mint_info.key
!= spl_token::state::Account::unpack_from_slice(&owner_fee_info.data.borrow())?.mint
@ -322,41 +356,39 @@ impl Processor {
return Err(StakePoolError::WrongAccountMint.into());
}
// Check pool mint program ID
if pool_mint_info.owner != token_program_info.key {
return Err(ProgramError::IncorrectProgramId);
}
let (_, deposit_bump_seed) = Self::find_authority_bump_seed(
program_id,
stake_pool_info.key,
Self::AUTHORITY_DEPOSIT,
);
let (_, withdraw_bump_seed) = Self::find_authority_bump_seed(
let (withdraw_authority_key, withdraw_bump_seed) = Self::find_authority_bump_seed(
program_id,
stake_pool_info.key,
Self::AUTHORITY_WITHDRAW,
);
let pool_mint = Mint::unpack_from_slice(&pool_mint_info.data.borrow())?;
if !pool_mint.mint_authority.contains(&withdraw_authority_key) {
return Err(StakePoolError::WrongMintingAuthority.into());
}
validator_stake_list.serialize(&mut validator_stake_list_info.data.borrow_mut())?;
msg!("Clock data: {:?}", clock_info.data.borrow());
msg!("Epoch: {}", clock.epoch);
let stake_pool = State::Init(StakePool {
version: PROGRAM_VERSION,
owner: *owner_info.key,
deposit_bump_seed,
withdraw_bump_seed,
validator_stake_list: *validator_stake_list_info.key,
pool_mint: *pool_mint_info.key,
owner_fee_account: *owner_fee_info.key,
token_program_id: *token_program_info.key,
stake_total: 0,
pool_total: 0,
last_update_epoch: clock.epoch,
fee: init.fee,
});
stake_pool.version = PROGRAM_VERSION;
stake_pool.owner = *owner_info.key;
stake_pool.deposit_bump_seed = deposit_bump_seed;
stake_pool.withdraw_bump_seed = withdraw_bump_seed;
stake_pool.validator_stake_list = *validator_stake_list_info.key;
stake_pool.pool_mint = *pool_mint_info.key;
stake_pool.owner_fee_account = *owner_fee_info.key;
stake_pool.token_program_id = *token_program_info.key;
stake_pool.last_update_epoch = clock.epoch;
stake_pool.fee = init.fee;
stake_pool.serialize(&mut stake_pool_info.data.borrow_mut())
}
@ -412,35 +444,20 @@ impl Processor {
// Fund the associated token account with the minimum balance to be rent exempt
let required_lamports = 1 + rent.minimum_balance(std::mem::size_of::<stake::StakeState>());
// Fund new account
invoke(
&system_instruction::transfer(
// Create new stake account
invoke_signed(
&system_instruction::create_account(
&funder_info.key,
stake_account_info.key,
&stake_account_info.key,
required_lamports,
),
&[
funder_info.clone(),
stake_account_info.clone(),
system_program_info.clone(),
],
)?;
// Allocate account space
invoke_signed(
&system_instruction::allocate(
stake_account_info.key,
std::mem::size_of::<stake::StakeState>() as u64,
&stake::id(),
),
&[stake_account_info.clone(), system_program_info.clone()],
&[funder_info.clone(), stake_account_info.clone()],
&[&stake_account_signer_seeds],
)?;
// Assign account to the stake program
invoke_signed(
&system_instruction::assign(stake_account_info.key, &stake::id()),
&[stake_account_info.clone(), system_program_info.clone()],
&[&stake_account_signer_seeds],
)?;
// Initialize stake account
invoke(
&stake::initialize(
&stake_account_info.key,
@ -483,6 +500,9 @@ impl Processor {
// Clock sysvar account
let clock_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(clock_info)?;
// Stake history sysvar account
let stake_history_info = next_account_info(account_info_iter)?;
let stake_history = &StakeHistory::from_account_info(stake_history_info)?;
// Pool token program id
let token_program_info = next_account_info(account_info_iter)?;
// Staking program id
@ -493,8 +513,11 @@ impl Processor {
return Err(ProgramError::IncorrectProgramId);
}
// Get stake pool stake (and check if it is iniaialized)
let mut stake_pool = State::deserialize(&stake_pool_info.data.borrow())?.stake_pool()?;
// Get stake pool stake (and check if it is initialized)
let mut stake_pool = StakePool::deserialize(&stake_pool_info.data.borrow())?;
if !stake_pool.is_initialized() {
return Err(StakePoolError::InvalidState.into());
}
// Check authority accounts
stake_pool.check_authority_withdraw(withdraw_info.key, program_id, stake_pool_info.key)?;
@ -523,7 +546,7 @@ impl Processor {
// Read validator stake list account and check if it is valid
let mut validator_stake_list =
ValidatorStakeList::deserialize(&validator_stake_list_info.data.borrow())?;
if !validator_stake_list.is_initialized {
if !validator_stake_list.is_initialized() {
return Err(StakePoolError::InvalidState.into());
}
@ -568,7 +591,8 @@ impl Processor {
token_amount,
)?;
// TODO: Check if stake is warmed up
// Check if stake is warmed up
Self::check_stake_activation(stake_account_info, clock, stake_history)?;
// Add validator to the list and save
validator_stake_list.validators.push(ValidatorStakeInfo {
@ -582,7 +606,7 @@ impl Processor {
stake_pool.pool_total += token_amount;
// Only update stake total if the last state update epoch is current
stake_pool.stake_total += stake_lamports;
State::Init(stake_pool).serialize(&mut stake_pool_info.data.borrow_mut())?;
stake_pool.serialize(&mut stake_pool_info.data.borrow_mut())?;
Ok(())
}
@ -622,8 +646,11 @@ impl Processor {
return Err(ProgramError::IncorrectProgramId);
}
// Get stake pool stake (and check if it is iniaialized)
let mut stake_pool = State::deserialize(&stake_pool_info.data.borrow())?.stake_pool()?;
// Get stake pool stake (and check if it is initialized)
let mut stake_pool = StakePool::deserialize(&stake_pool_info.data.borrow())?;
if !stake_pool.is_initialized() {
return Err(StakePoolError::InvalidState.into());
}
// Check authority account
stake_pool.check_authority_withdraw(withdraw_info.key, program_id, stake_pool_info.key)?;
@ -651,7 +678,7 @@ impl Processor {
// Read validator stake list account and check if it is valid
let mut validator_stake_list =
ValidatorStakeList::deserialize(&validator_stake_list_info.data.borrow())?;
if !validator_stake_list.is_initialized {
if !validator_stake_list.is_initialized() {
return Err(StakePoolError::InvalidState.into());
}
@ -706,7 +733,7 @@ impl Processor {
stake_pool.pool_total -= token_amount;
// Only update stake total if the last state update epoch is current
stake_pool.stake_total -= stake_lamports;
State::Init(stake_pool).serialize(&mut stake_pool_info.data.borrow_mut())?;
stake_pool.serialize(&mut stake_pool_info.data.borrow_mut())?;
Ok(())
}
@ -728,7 +755,7 @@ impl Processor {
// Read validator stake list account and check if it is valid
let mut validator_stake_list =
ValidatorStakeList::deserialize(&validator_stake_list_info.data.borrow())?;
if !validator_stake_list.is_initialized {
if !validator_stake_list.is_initialized() {
return Err(StakePoolError::InvalidState.into());
}
@ -779,8 +806,11 @@ impl Processor {
let clock_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(clock_info)?;
// Get stake pool stake (and check if it is iniaialized)
let mut stake_pool = State::deserialize(&stake_pool_info.data.borrow())?.stake_pool()?;
// Get stake pool stake (and check if it is initialized)
let mut stake_pool = StakePool::deserialize(&stake_pool_info.data.borrow())?;
if !stake_pool.is_initialized() {
return Err(StakePoolError::InvalidState.into());
}
// Check validator stake account list storage
if *validator_stake_list_info.key != stake_pool.validator_stake_list {
@ -790,7 +820,7 @@ impl Processor {
// Read validator stake list account and check if it is valid
let validator_stake_list =
ValidatorStakeList::deserialize(&validator_stake_list_info.data.borrow())?;
if !validator_stake_list.is_initialized {
if !validator_stake_list.is_initialized() {
return Err(StakePoolError::InvalidState.into());
}
@ -804,11 +834,40 @@ impl Processor {
stake_pool.stake_total = total_balance;
stake_pool.last_update_epoch = clock.epoch;
State::Init(stake_pool).serialize(&mut stake_pool_info.data.borrow_mut())?;
stake_pool.serialize(&mut stake_pool_info.data.borrow_mut())?;
Ok(())
}
/// Check stake activation status
pub fn check_stake_activation(
_stake_info: &AccountInfo,
_clock: &Clock,
_stake_history: &StakeHistory,
) -> ProgramResult {
// TODO: remove conditional compilation when time travel in tests is possible
//#[cfg(not(feature = "test-bpf"))]
// This check is commented to make tests run without special command line arguments
/*{
let stake_acc_state: stake::StakeState =
deserialize(&stake_info.data.borrow()).unwrap();
let delegation = stake_acc_state.delegation();
if let Some(delegation) = delegation {
let target_epoch = clock.epoch;
let history = Some(stake_history);
let fix_stake_deactivate = true;
let (effective, activating, deactivating) = delegation
.stake_activating_and_deactivating(target_epoch, history, fix_stake_deactivate);
if activating != 0 || deactivating != 0 || effective == 0 {
return Err(StakePoolError::UserStakeNotActive.into());
}
} else {
return Err(StakePoolError::WrongStakeState.into());
}
}*/
Ok(())
}
/// Processes [Deposit](enum.Instruction.html).
pub fn process_deposit(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
@ -822,7 +881,7 @@ impl Processor {
let withdraw_info = next_account_info(account_info_iter)?;
// Stake account to join the pool (withdraw should be set to stake pool deposit)
let stake_info = next_account_info(account_info_iter)?;
// Valdidator stake account to merge with
// Validator stake account to merge with
let validator_stake_account_info = next_account_info(account_info_iter)?;
// User account to receive pool tokens
let dest_user_info = next_account_info(account_info_iter)?;
@ -835,6 +894,7 @@ impl Processor {
let clock = &Clock::from_account_info(clock_info)?;
// Stake history sysvar account
let stake_history_info = next_account_info(account_info_iter)?;
let stake_history = &StakeHistory::from_account_info(stake_history_info)?;
// Pool token program id
let token_program_info = next_account_info(account_info_iter)?;
// Stake program id
@ -845,7 +905,13 @@ impl Processor {
return Err(ProgramError::IncorrectProgramId);
}
let mut stake_pool = State::deserialize(&stake_pool_info.data.borrow())?.stake_pool()?;
let mut stake_pool = StakePool::deserialize(&stake_pool_info.data.borrow())?;
if !stake_pool.is_initialized() {
return Err(StakePoolError::InvalidState.into());
}
// Check if stake is active
Self::check_stake_activation(stake_info, clock, stake_history)?;
// Check authority accounts
stake_pool.check_authority_withdraw(withdraw_info.key, program_id, stake_pool_info.key)?;
@ -871,7 +937,7 @@ impl Processor {
// Read validator stake list account and check if it is valid
let mut validator_stake_list =
ValidatorStakeList::deserialize(&validator_stake_list_info.data.borrow())?;
if !validator_stake_list.is_initialized {
if !validator_stake_list.is_initialized() {
return Err(StakePoolError::InvalidState.into());
}
@ -954,7 +1020,7 @@ impl Processor {
)?;
stake_pool.pool_total += pool_amount;
stake_pool.stake_total += stake_lamports;
State::Init(stake_pool).serialize(&mut stake_pool_info.data.borrow_mut())?;
stake_pool.serialize(&mut stake_pool_info.data.borrow_mut())?;
validator_list_item.balance = **validator_stake_account_info.lamports.borrow();
validator_stake_list.serialize(&mut validator_stake_list_info.data.borrow_mut())?;
@ -965,7 +1031,7 @@ impl Processor {
/// Processes [Withdraw](enum.Instruction.html).
pub fn process_withdraw(
program_id: &Pubkey,
stake_amount: u64,
pool_amount: u64,
accounts: &[AccountInfo],
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
@ -998,7 +1064,10 @@ impl Processor {
return Err(ProgramError::IncorrectProgramId);
}
let mut stake_pool = State::deserialize(&stake_pool_info.data.borrow())?.stake_pool()?;
let mut stake_pool = StakePool::deserialize(&stake_pool_info.data.borrow())?;
if !stake_pool.is_initialized() {
return Err(StakePoolError::InvalidState.into());
}
// Check authority account
stake_pool.check_authority_withdraw(withdraw_info.key, program_id, stake_pool_info.key)?;
@ -1020,7 +1089,7 @@ impl Processor {
// Read validator stake list account and check if it is valid
let mut validator_stake_list =
ValidatorStakeList::deserialize(&validator_stake_list_info.data.borrow())?;
if !validator_stake_list.is_initialized {
if !validator_stake_list.is_initialized() {
return Err(StakePoolError::InvalidState.into());
}
@ -1031,8 +1100,8 @@ impl Processor {
.find_mut(&validator_account)
.ok_or(StakePoolError::ValidatorNotFound)?;
let pool_amount = stake_pool
.calc_pool_withdraw_amount(stake_amount)
let stake_amount = stake_pool
.calc_lamports_amount(pool_amount)
.ok_or(StakePoolError::CalculationFailure)?;
Self::stake_split(
@ -1084,7 +1153,7 @@ impl Processor {
stake_pool.pool_total -= pool_amount;
stake_pool.stake_total -= stake_amount;
State::Init(stake_pool).serialize(&mut stake_pool_info.data.borrow_mut())?;
stake_pool.serialize(&mut stake_pool_info.data.borrow_mut())?;
validator_list_item.balance = **stake_split_from.lamports.borrow();
validator_stake_list.serialize(&mut validator_stake_list_info.data.borrow_mut())?;
@ -1112,7 +1181,10 @@ impl Processor {
return Err(ProgramError::IncorrectProgramId);
}
let stake_pool = State::deserialize(&stake_pool_info.data.borrow())?.stake_pool()?;
let stake_pool = StakePool::deserialize(&stake_pool_info.data.borrow())?;
if !stake_pool.is_initialized() {
return Err(StakePoolError::InvalidState.into());
}
// Check authority account
stake_pool.check_authority_withdraw(withdraw_info.key, program_id, stake_pool_info.key)?;
@ -1142,7 +1214,10 @@ impl Processor {
let new_owner_info = next_account_info(account_info_iter)?;
let new_owner_fee_info = next_account_info(account_info_iter)?;
let mut stake_pool = State::deserialize(&stake_pool_info.data.borrow())?.stake_pool()?;
let mut stake_pool = StakePool::deserialize(&stake_pool_info.data.borrow())?;
if !stake_pool.is_initialized() {
return Err(StakePoolError::InvalidState.into());
}
// Check owner validity and signature
stake_pool.check_owner(owner_info)?;
@ -1156,7 +1231,7 @@ impl Processor {
stake_pool.owner = *new_owner_info.key;
stake_pool.owner_fee_account = *new_owner_fee_info.key;
State::Init(stake_pool).serialize(&mut stake_pool_info.data.borrow_mut())?;
stake_pool.serialize(&mut stake_pool_info.data.borrow_mut())?;
Ok(())
}
/// Processes [Instruction](enum.Instruction.html).
@ -1213,27 +1288,30 @@ impl PrintProgramError for StakePoolError {
E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
{
match self {
StakePoolError::AlreadyInUse => msg!("Error: AlreadyInUse"),
StakePoolError::InvalidProgramAddress => msg!("Error: InvalidProgramAddress"),
StakePoolError::InvalidState => msg!("Error: InvalidState"),
StakePoolError::CalculationFailure => msg!("Error: CalculationFailure"),
StakePoolError::FeeTooHigh => msg!("Error: FeeTooHigh"),
StakePoolError::WrongAccountMint => msg!("Error: WrongAccountMint"),
StakePoolError::NonZeroBalance => msg!("Error: NonZeroBalance"),
StakePoolError::WrongOwner => msg!("Error: WrongOwner"),
StakePoolError::SignatureMissing => msg!("Error: SignatureMissing"),
StakePoolError::InvalidValidatorStakeList => msg!("Error: InvalidValidatorStakeList"),
StakePoolError::InvalidFeeAccount => msg!("Error: InvalidFeeAccount"),
StakePoolError::WrongPoolMint => msg!("Error: WrongPoolMint"),
StakePoolError::WrongStakeState => msg!("Error: WrongStakeState"),
StakePoolError::ValidatorAlreadyAdded => msg!("Error: ValidatorAlreadyAdded"),
StakePoolError::ValidatorNotFound => msg!("Error: ValidatorNotFound"),
StakePoolError::InvalidStakeAccountAddress => msg!("Error: InvalidStakeAccountAddress"),
StakePoolError::StakeListOutOfDate => msg!("Error: StakeListOutOfDate"),
StakePoolError::StakeListAndPoolOutOfDate => msg!("Error: StakeListAndPoolOutOfDate"),
StakePoolError::AlreadyInUse => msg!("Error: The account cannot be initialized because it is already being used"),
StakePoolError::InvalidProgramAddress => msg!("Error: The program address provided doesn't match the value generated by the program"),
StakePoolError::InvalidState => msg!("Error: The stake pool state is invalid"),
StakePoolError::CalculationFailure => msg!("Error: The calculation failed"),
StakePoolError::FeeTooHigh => msg!("Error: Stake pool fee > 1"),
StakePoolError::WrongAccountMint => msg!("Error: Token account is associated with the wrong mint"),
StakePoolError::NonZeroBalance => msg!("Error: Account balance should be zero"),
StakePoolError::WrongOwner => msg!("Error: Wrong pool owner account"),
StakePoolError::SignatureMissing => msg!("Error: Required signature is missing"),
StakePoolError::InvalidValidatorStakeList => msg!("Error: Invalid validator stake list account"),
StakePoolError::InvalidFeeAccount => msg!("Error: Invalid owner fee account"),
StakePoolError::WrongPoolMint => msg!("Error: Specified pool mint account is wrong"),
StakePoolError::WrongStakeState => msg!("Error: Stake account is not in the state expected by the program"),
StakePoolError::UserStakeNotActive => msg!("Error: User stake is not active"),
StakePoolError::ValidatorAlreadyAdded => msg!("Error: Stake account voting for this validator already exists in the pool"),
StakePoolError::ValidatorNotFound => msg!("Error: Stake account for this validator not found in the pool"),
StakePoolError::InvalidStakeAccountAddress => msg!("Error: Stake account address not properly derived from the validator address"),
StakePoolError::StakeListOutOfDate => msg!("Error: Identify validator stake accounts with old balances and update them"),
StakePoolError::StakeListAndPoolOutOfDate => msg!("Error: First update old validator stake account balances and then pool stake balance"),
StakePoolError::UnknownValidatorStakeAccount => {
msg!("Error: UnknownValidatorStakeAccount")
msg!("Error: Validator stake account is not found in the list storage")
}
StakePoolError::WrongMintingAuthority => msg!("Error: Wrong minting authority set for mint pool account"),
StakePoolError::AccountNotRentExempt => msg!("Error: Account is not rent-exempt"),
}
}
}

View File

@ -5,6 +5,7 @@ use solana_program::{
clock::{Epoch, UnixTimestamp},
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
stake_history::StakeHistory,
system_instruction, sysvar,
};
use std::str::FromStr;
@ -69,7 +70,7 @@ pub enum StakeInstruction {
/// 5. Optional: [SIGNER] Lockup authority, if before lockup expiration
///
/// The u64 is the portion of the stake account balance to be withdrawn,
/// must be `<= StakeAccount.lamports - staked_lamports`.
/// must be `<= ValidatorStakeAccount.lamports - staked_lamports`.
Withdraw(u64),
/// Deactivates the stake in the account
@ -186,6 +187,212 @@ pub struct Lockup {
pub custodian: Pubkey,
}
/// FIXME copied from the stake program
impl StakeState {
/// Get Delegation
pub fn delegation(&self) -> Option<Delegation> {
match self {
StakeState::Stake(_meta, stake) => Some(stake.delegation),
_ => None,
}
}
}
/// FIXME copied from the stake program
impl Delegation {
/// Create new Delegation
pub fn new(
voter_pubkey: &Pubkey,
stake: u64,
activation_epoch: Epoch,
warmup_cooldown_rate: f64,
) -> Self {
Self {
voter_pubkey: *voter_pubkey,
stake,
activation_epoch,
warmup_cooldown_rate,
..Delegation::default()
}
}
/// Check if it bootstrap
pub fn is_bootstrap(&self) -> bool {
self.activation_epoch == std::u64::MAX
}
/// Return tuple (effective, activating, deactivating) stake
#[allow(clippy::comparison_chain)]
pub fn stake_activating_and_deactivating(
&self,
target_epoch: Epoch,
history: Option<&StakeHistory>,
fix_stake_deactivate: bool,
) -> (u64, u64, u64) {
let delegated_stake = self.stake;
// first, calculate an effective and activating stake
let (effective_stake, activating_stake) =
self.stake_and_activating(target_epoch, history, fix_stake_deactivate);
// then de-activate some portion if necessary
if target_epoch < self.deactivation_epoch {
// not deactivated
(effective_stake, activating_stake, 0)
} else if target_epoch == self.deactivation_epoch {
// can only deactivate what's activated
(effective_stake, 0, effective_stake.min(delegated_stake))
} else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) =
history.and_then(|history| {
history
.get(&self.deactivation_epoch)
.map(|cluster_stake_at_deactivation_epoch| {
(
history,
self.deactivation_epoch,
cluster_stake_at_deactivation_epoch,
)
})
})
{
// target_epoch > self.deactivation_epoch
// loop from my deactivation epoch until the target epoch
// current effective stake is updated using its previous epoch's cluster stake
let mut current_epoch;
let mut current_effective_stake = effective_stake;
loop {
current_epoch = prev_epoch + 1;
// if there is no deactivating stake at prev epoch, we should have been
// fully undelegated at this moment
if prev_cluster_stake.deactivating == 0 {
break;
}
// I'm trying to get to zero, how much of the deactivation in stake
// this account is entitled to take
let weight =
current_effective_stake as f64 / prev_cluster_stake.deactivating as f64;
// portion of newly not-effective cluster stake I'm entitled to at current epoch
let newly_not_effective_cluster_stake =
prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate;
let newly_not_effective_stake =
((weight * newly_not_effective_cluster_stake) as u64).max(1);
current_effective_stake =
current_effective_stake.saturating_sub(newly_not_effective_stake);
if current_effective_stake == 0 {
break;
}
if current_epoch >= target_epoch {
break;
}
if let Some(current_cluster_stake) = history.get(&current_epoch) {
prev_epoch = current_epoch;
prev_cluster_stake = current_cluster_stake;
} else {
break;
}
}
// deactivating stake should equal to all of currently remaining effective stake
(current_effective_stake, 0, current_effective_stake)
} else {
// no history or I've dropped out of history, so assume fully deactivated
(0, 0, 0)
}
}
// returned tuple is (effective, activating) stake
fn stake_and_activating(
&self,
target_epoch: Epoch,
history: Option<&StakeHistory>,
fix_stake_deactivate: bool,
) -> (u64, u64) {
let delegated_stake = self.stake;
if self.is_bootstrap() {
// fully effective immediately
(delegated_stake, 0)
} else if fix_stake_deactivate && self.activation_epoch == self.deactivation_epoch {
// activated but instantly deactivated; no stake at all regardless of target_epoch
// this must be after the bootstrap check and before all-is-activating check
(0, 0)
} else if target_epoch == self.activation_epoch {
// all is activating
(0, delegated_stake)
} else if target_epoch < self.activation_epoch {
// not yet enabled
(0, 0)
} else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) =
history.and_then(|history| {
history
.get(&self.activation_epoch)
.map(|cluster_stake_at_activation_epoch| {
(
history,
self.activation_epoch,
cluster_stake_at_activation_epoch,
)
})
})
{
// target_epoch > self.activation_epoch
// loop from my activation epoch until the target epoch summing up my entitlement
// current effective stake is updated using its previous epoch's cluster stake
let mut current_epoch;
let mut current_effective_stake = 0;
loop {
current_epoch = prev_epoch + 1;
// if there is no activating stake at prev epoch, we should have been
// fully effective at this moment
if prev_cluster_stake.activating == 0 {
break;
}
// how much of the growth in stake this account is
// entitled to take
let remaining_activating_stake = delegated_stake - current_effective_stake;
let weight =
remaining_activating_stake as f64 / prev_cluster_stake.activating as f64;
// portion of newly effective cluster stake I'm entitled to at current epoch
let newly_effective_cluster_stake =
prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate;
let newly_effective_stake =
((weight * newly_effective_cluster_stake) as u64).max(1);
current_effective_stake += newly_effective_stake;
if current_effective_stake >= delegated_stake {
current_effective_stake = delegated_stake;
break;
}
if current_epoch >= target_epoch || current_epoch >= self.deactivation_epoch {
break;
}
if let Some(current_cluster_stake) = history.get(&current_epoch) {
prev_epoch = current_epoch;
prev_cluster_stake = current_cluster_stake;
} else {
break;
}
}
(
current_effective_stake,
delegated_stake - current_effective_stake,
)
} else {
// no history or I've dropped out of history, so assume fully effective
(delegated_stake, 0)
}
}
}
/// FIXME copied from the stake program
pub fn split_only(
stake_pubkey: &Pubkey,

View File

@ -1,7 +1,7 @@
//! State transition types
use crate::error::StakePoolError;
use crate::instruction::{unpack, Fee};
use crate::instruction::Fee;
use crate::processor::Processor;
use core::convert::TryInto;
use solana_program::{
@ -44,6 +44,8 @@ pub struct StakePool {
pub fee: Fee,
}
impl StakePool {
/// Length of state data when serialized
pub const LEN: usize = size_of::<StakePool>();
/// calculate the pool tokens that should be minted
pub fn calc_pool_deposit_amount(&self, stake_lamports: u64) -> Option<u64> {
if self.stake_total == 0 {
@ -60,6 +62,15 @@ impl StakePool {
)
.ok()
}
/// calculate lamports amount
pub fn calc_lamports_amount(&self, pool_tokens: u64) -> Option<u64> {
u64::try_from(
(pool_tokens as u128)
.checked_mul(self.stake_total as u128)?
.checked_div(self.pool_total as u128)?,
)
.ok()
}
/// calculate the fee in pool tokens that goes to the owner
pub fn calc_fee_amount(&self, pool_amount: u64) -> Option<u64> {
if self.fee.denominator == 0 {
@ -114,67 +125,34 @@ impl StakePool {
}
Ok(())
}
}
/// Program states.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum State {
/// Unallocated state, may be initialized into another state.
Unallocated,
/// Initialized state.
Init(StakePool),
}
impl State {
/// Length of state data when serialized
pub const LEN: usize = size_of::<u8>() + size_of::<StakePool>();
/// Deserializes a byte buffer into a [State](struct.State.html).
/// TODO efficient unpacking here
pub fn deserialize(input: &[u8]) -> Result<State, ProgramError> {
if input.len() < size_of::<u8>() {
return Err(ProgramError::InvalidAccountData);
}
Ok(match input[0] {
0 => State::Unallocated,
1 => {
// We send whole input here, because unpack skips the first byte
let swap: &StakePool = unpack(&input)?;
State::Init(*swap)
}
_ => return Err(ProgramError::InvalidAccountData),
})
/// Check if StakePool is initialized
pub fn is_initialized(&self) -> bool {
self.version > 0
}
/// Serializes [State](struct.State.html) into a byte buffer.
/// TODO efficient packing here
/// Deserializes a byte buffer into a [StakePool](struct.StakePool.html).
pub fn deserialize(input: &[u8]) -> Result<StakePool, ProgramError> {
if input.len() < size_of::<StakePool>() {
return Err(ProgramError::InvalidAccountData);
}
let stake_pool: &StakePool = unsafe { &*(&input[0] as *const u8 as *const StakePool) };
Ok(*stake_pool)
}
/// Serializes [StakePool](struct.StakePool.html) into a byte buffer.
pub fn serialize(&self, output: &mut [u8]) -> ProgramResult {
if output.len() < size_of::<u8>() {
if output.len() < size_of::<StakePool>() {
return Err(ProgramError::InvalidAccountData);
}
match self {
Self::Unallocated => output[0] = 0,
Self::Init(swap) => {
if output.len() < size_of::<u8>() + size_of::<StakePool>() {
return Err(ProgramError::InvalidAccountData);
}
output[0] = 1;
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe { &mut *(&mut output[1] as *mut u8 as *mut StakePool) };
*value = *swap;
}
}
#[allow(clippy::cast_ptr_alignment)]
let value = unsafe { &mut *(&mut output[0] as *mut u8 as *mut StakePool) };
*value = *self;
Ok(())
}
/// Gets the `StakePool` from `State`
pub fn stake_pool(&self) -> Result<StakePool, ProgramError> {
if let State::Init(swap) = &self {
Ok(*swap)
} else {
Err(StakePoolError::InvalidState.into())
}
}
}
const MAX_VALIDATOR_STAKE_ACCOUNTS: usize = 1000;
@ -183,8 +161,8 @@ const MAX_VALIDATOR_STAKE_ACCOUNTS: usize = 1000;
#[repr(C)]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ValidatorStakeList {
/// False if not yet initialized
pub is_initialized: bool,
/// Validator stake list version
pub version: u8,
/// List of all validator stake accounts and their info
pub validators: Vec<ValidatorStakeInfo>,
}
@ -211,6 +189,9 @@ impl ValidatorStakeList {
/// Header length
pub const HEADER_LEN: usize = size_of::<u8>() + size_of::<u16>();
/// Version of validator stake list
pub const VALIDATOR_STAKE_LIST_VERSION: u8 = 1;
/// Check if contains validator with particular pubkey
pub fn contains(&self, validator: &Pubkey) -> bool {
self.validators
@ -231,6 +212,11 @@ impl ValidatorStakeList {
.find(|x| x.validator_account == *validator)
}
/// Check if validator stake list is initialized
pub fn is_initialized(&self) -> bool {
self.version > 0
}
/// Deserializes a byte buffer into a ValidatorStakeList.
pub fn deserialize(input: &[u8]) -> Result<Self, ProgramError> {
if input.len() < Self::LEN {
@ -239,7 +225,7 @@ impl ValidatorStakeList {
if input[0] == 0 {
return Ok(ValidatorStakeList {
is_initialized: false,
version: 0,
validators: vec![],
});
}
@ -262,7 +248,7 @@ impl ValidatorStakeList {
to += ValidatorStakeInfo::LEN;
}
Ok(ValidatorStakeList {
is_initialized: true,
version: input[0],
validators,
})
}
@ -275,7 +261,7 @@ impl ValidatorStakeList {
if self.validators.len() > MAX_VALIDATOR_STAKE_ACCOUNTS {
return Err(ProgramError::InvalidAccountData);
}
output[0] = if self.is_initialized { 1 } else { 0 };
output[0] = self.version;
output[1..3].copy_from_slice(&u16::to_le_bytes(self.validators.len() as u16));
let mut from = Self::HEADER_LEN;
let mut to = from + ValidatorStakeInfo::LEN;
@ -324,7 +310,7 @@ mod test {
fn test_state_packing() {
// Not initialized
let stake_list = ValidatorStakeList {
is_initialized: false,
version: 0,
validators: vec![],
};
let mut bytes: [u8; ValidatorStakeList::LEN] = [0; ValidatorStakeList::LEN];
@ -334,7 +320,7 @@ mod test {
// Empty
let stake_list = ValidatorStakeList {
is_initialized: true,
version: ValidatorStakeList::VALIDATOR_STAKE_LIST_VERSION,
validators: vec![],
};
let mut bytes: [u8; ValidatorStakeList::LEN] = [0; ValidatorStakeList::LEN];
@ -344,7 +330,7 @@ mod test {
// With several accounts
let stake_list = ValidatorStakeList {
is_initialized: true,
version: ValidatorStakeList::VALIDATOR_STAKE_LIST_VERSION,
validators: vec![
ValidatorStakeInfo {
validator_account: Pubkey::new_from_array([1; 32]),

View File

@ -16,7 +16,13 @@ use solana_sdk::{
use spl_stake_pool::*;
use spl_token::error as token_error;
async fn setup() -> (BanksClient, Keypair, Hash, StakePoolAccounts, StakeAccount) {
async fn setup() -> (
BanksClient,
Keypair,
Hash,
StakePoolAccounts,
ValidatorStakeAccount,
) {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let stake_pool_accounts = StakePoolAccounts::new();
stake_pool_accounts
@ -24,7 +30,7 @@ async fn setup() -> (BanksClient, Keypair, Hash, StakePoolAccounts, StakeAccount
.await
.unwrap();
let validator_stake_account: StakeAccount = simple_add_validator_stake_account(
let validator_stake_account: ValidatorStakeAccount = simple_add_validator_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
@ -50,10 +56,13 @@ async fn test_stake_pool_deposit() {
// make stake account
let user_stake = Keypair::new();
let lockup = stake::Lockup::default();
let stake_authority = Keypair::new();
let authorized = stake::Authorized {
staker: stake_pool_accounts.deposit_authority,
withdrawer: stake_pool_accounts.deposit_authority,
staker: stake_authority.pubkey(),
withdrawer: stake_authority.pubkey(),
};
let stake_lamports = create_independent_stake_account(
&mut banks_client,
&payer,
@ -63,6 +72,46 @@ async fn test_stake_pool_deposit() {
&lockup,
)
.await;
create_vote(
&mut banks_client,
&payer,
&recent_blockhash,
&validator_stake_account.vote,
)
.await;
delegate_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake.pubkey(),
&stake_authority,
&validator_stake_account.vote.pubkey(),
)
.await;
// Change authority to the stake pool's deposit
authorize_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake.pubkey(),
&stake_authority,
&stake_pool_accounts.deposit_authority,
stake::StakeAuthorize::Withdrawer,
)
.await;
authorize_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake.pubkey(),
&stake_authority,
&stake_pool_accounts.deposit_authority,
stake::StakeAuthorize::Staker,
)
.await;
// make pool token account
let user_pool_account = Keypair::new();
create_token_account(
@ -79,10 +128,8 @@ async fn test_stake_pool_deposit() {
// Save stake pool state before depositing
let stake_pool_before =
get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
let stake_pool_before = state::State::deserialize(&stake_pool_before.data.as_slice())
.unwrap()
.stake_pool()
.unwrap();
let stake_pool_before =
state::StakePool::deserialize(&stake_pool_before.data.as_slice()).unwrap();
// Save validator stake account record before depositing
let validator_stake_list = get_account(
@ -120,10 +167,7 @@ async fn test_stake_pool_deposit() {
// Stake pool should add its balance to the pool balance
let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
let stake_pool = state::State::deserialize(&stake_pool.data.as_slice())
.unwrap()
.stake_pool()
.unwrap();
let stake_pool = state::StakePool::deserialize(&stake_pool.data.as_slice()).unwrap();
assert_eq!(
stake_pool.stake_total,
stake_pool_before.stake_total + stake_lamports
@ -239,6 +283,20 @@ async fn test_stake_pool_deposit_with_wrong_pool_fee_account() {
let user = Keypair::new();
// make stake account
let user_stake = Keypair::new();
let lockup = stake::Lockup::default();
let authorized = stake::Authorized {
staker: stake_pool_accounts.deposit_authority,
withdrawer: stake_pool_accounts.deposit_authority,
};
create_independent_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake,
&authorized,
&lockup,
)
.await;
// make pool token account
let user_pool_account = Keypair::new();
@ -289,6 +347,20 @@ async fn test_stake_pool_deposit_with_wrong_token_program_id() {
let user = Keypair::new();
// make stake account
let user_stake = Keypair::new();
let lockup = stake::Lockup::default();
let authorized = stake::Authorized {
staker: stake_pool_accounts.deposit_authority,
withdrawer: stake_pool_accounts.deposit_authority,
};
create_independent_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake,
&authorized,
&lockup,
)
.await;
// make pool token account
let user_pool_account = Keypair::new();
@ -351,6 +423,20 @@ async fn test_stake_pool_deposit_with_wrong_validator_stake_list_account() {
let user = Keypair::new();
// make stake account
let user_stake = Keypair::new();
let lockup = stake::Lockup::default();
let authorized = stake::Authorized {
staker: stake_pool_accounts.deposit_authority,
withdrawer: stake_pool_accounts.deposit_authority,
};
create_independent_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake,
&authorized,
&lockup,
)
.await;
// make pool token account
let user_pool_account = Keypair::new();
@ -402,13 +488,13 @@ async fn test_stake_pool_deposit_where_stake_acc_not_in_stake_state() {
.await
.unwrap();
let validator_stake_account = StakeAccount::new_with_target_authority(
let validator_stake_account = ValidatorStakeAccount::new_with_target_authority(
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.stake_pool.pubkey(),
);
let user_stake_authority = Keypair::new();
create_stake_account(
create_validator_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
@ -434,6 +520,20 @@ async fn test_stake_pool_deposit_where_stake_acc_not_in_stake_state() {
.unwrap();
let user_stake_acc = Keypair::new();
let lockup = stake::Lockup::default();
let authorized = stake::Authorized {
staker: stake_pool_accounts.deposit_authority,
withdrawer: stake_pool_accounts.deposit_authority,
};
create_independent_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake_acc,
&authorized,
&lockup,
)
.await;
let transaction_error = stake_pool_accounts
.deposit_stake(
&mut banks_client,
@ -470,7 +570,7 @@ async fn test_stake_pool_deposit_to_unknown_validator() {
.await
.unwrap();
let validator_stake_account = StakeAccount::new_with_target_authority(
let validator_stake_account = ValidatorStakeAccount::new_with_target_authority(
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.stake_pool.pubkey(),
);
@ -548,6 +648,20 @@ async fn test_stake_pool_deposit_with_wrong_deposit_authority() {
let user = Keypair::new();
// make stake account
let user_stake = Keypair::new();
let lockup = stake::Lockup::default();
let authorized = stake::Authorized {
staker: stake_pool_accounts.deposit_authority,
withdrawer: stake_pool_accounts.deposit_authority,
};
create_independent_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake,
&authorized,
&lockup,
)
.await;
// make pool token account
let user_pool_account = Keypair::new();
@ -602,6 +716,20 @@ async fn test_stake_pool_deposit_with_wrong_withdraw_authority() {
let user = Keypair::new();
// make stake account
let user_stake = Keypair::new();
let lockup = stake::Lockup::default();
let authorized = stake::Authorized {
staker: stake_pool_accounts.deposit_authority,
withdrawer: stake_pool_accounts.deposit_authority,
};
create_independent_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake,
&authorized,
&lockup,
)
.await;
// make pool token account
let user_pool_account = Keypair::new();

View File

@ -163,11 +163,11 @@ pub async fn create_stake_pool(
validator_stake_list: &Keypair,
pool_mint: &Pubkey,
pool_token_account: &Pubkey,
owner: &Pubkey,
owner: &Keypair,
fee: &instruction::Fee,
) -> Result<(), TransportError> {
let rent = banks_client.get_rent().await.unwrap();
let rent_stake_pool = rent.minimum_balance(state::State::LEN);
let rent_stake_pool = rent.minimum_balance(state::StakePool::LEN);
let rent_validator_stake_list = rent.minimum_balance(state::ValidatorStakeList::LEN);
let init_args = instruction::InitArgs { fee: *fee };
@ -177,7 +177,7 @@ pub async fn create_stake_pool(
&payer.pubkey(),
&stake_pool.pubkey(),
rent_stake_pool,
state::State::LEN as u64,
state::StakePool::LEN as u64,
&id(),
),
system_instruction::create_account(
@ -190,7 +190,7 @@ pub async fn create_stake_pool(
instruction::initialize(
&id(),
&stake_pool.pubkey(),
owner,
&owner.pubkey(),
&validator_stake_list.pubkey(),
pool_mint,
pool_token_account,
@ -202,7 +202,7 @@ pub async fn create_stake_pool(
Some(&payer.pubkey()),
);
transaction.sign(
&[payer, stake_pool, validator_stake_list],
&[payer, stake_pool, validator_stake_list, owner],
*recent_blockhash,
);
banks_client.process_transaction(transaction).await?;
@ -285,7 +285,7 @@ pub async fn create_blank_stake_account(
lamports
}
pub async fn create_stake_account(
pub async fn create_validator_stake_account(
banks_client: &mut BanksClient,
payer: &Keypair,
recent_blockhash: &Hash,
@ -355,14 +355,14 @@ pub async fn authorize_stake_account(
banks_client.process_transaction(transaction).await.unwrap();
}
pub struct StakeAccount {
pub struct ValidatorStakeAccount {
pub stake_account: Pubkey,
pub target_authority: Pubkey,
pub vote: Keypair,
pub stake_pool: Pubkey,
}
impl StakeAccount {
impl ValidatorStakeAccount {
pub fn new_with_target_authority(authority: &Pubkey, stake_pool: &Pubkey) -> Self {
let validator = Keypair::new();
let (stake_account, _) = processor::Processor::find_stake_address_for_validator(
@ -370,7 +370,7 @@ impl StakeAccount {
&validator.pubkey(),
stake_pool,
);
StakeAccount {
ValidatorStakeAccount {
stake_account,
target_authority: *authority,
vote: validator,
@ -386,7 +386,7 @@ impl StakeAccount {
) {
// make stake account
let user_stake_authority = Keypair::new();
create_stake_account(
create_validator_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
@ -500,7 +500,7 @@ impl StakePoolAccounts {
&self.validator_stake_list,
&self.pool_mint.pubkey(),
&self.pool_fee_account.pubkey(),
&self.owner.pubkey(),
&self.owner,
&self.fee,
)
.await?;
@ -638,8 +638,8 @@ pub async fn simple_add_validator_stake_account(
payer: &Keypair,
recent_blockhash: &Hash,
stake_pool_accounts: &StakePoolAccounts,
) -> StakeAccount {
let user_stake = StakeAccount::new_with_target_authority(
) -> ValidatorStakeAccount {
let user_stake = ValidatorStakeAccount::new_with_target_authority(
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.stake_pool.pubkey(),
);
@ -685,7 +685,7 @@ pub async fn simple_deposit(
payer: &Keypair,
recent_blockhash: &Hash,
stake_pool_accounts: &StakePoolAccounts,
validator_stake_account: &StakeAccount,
validator_stake_account: &ValidatorStakeAccount,
) -> DepositInfo {
let user = Keypair::new();
// make stake account

View File

@ -3,13 +3,47 @@
mod helpers;
use helpers::*;
use solana_program::system_instruction;
use solana_program::hash::Hash;
use solana_program::{
instruction::{AccountMeta, Instruction},
program_pack::Pack,
system_instruction, sysvar,
};
use solana_program_test::BanksClient;
use solana_sdk::{
instruction::InstructionError, signature::Keypair, signature::Signer, transaction::Transaction,
transaction::TransactionError, transport::TransportError,
};
use spl_stake_pool::*;
async fn create_mint_and_token_account(
banks_client: &mut BanksClient,
payer: &Keypair,
recent_blockhash: &Hash,
stake_pool_accounts: &StakePoolAccounts,
) {
create_mint(
banks_client,
payer,
recent_blockhash,
&stake_pool_accounts.pool_mint,
&stake_pool_accounts.withdraw_authority,
)
.await
.unwrap();
create_token_account(
banks_client,
payer,
recent_blockhash,
&stake_pool_accounts.pool_fee_account,
&stake_pool_accounts.pool_mint.pubkey(),
&stake_pool_accounts.owner.pubkey(),
)
.await
.unwrap();
}
#[tokio::test]
async fn test_stake_pool_initialize() {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
@ -21,7 +55,7 @@ async fn test_stake_pool_initialize() {
// Stake pool now exists
let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
assert_eq!(stake_pool.data.len(), state::State::LEN);
assert_eq!(stake_pool.data.len(), state::StakePool::LEN);
assert_eq!(stake_pool.owner, id());
// Validator stake list storage initialized
@ -32,7 +66,7 @@ async fn test_stake_pool_initialize() {
.await;
let validator_stake_list =
state::ValidatorStakeList::deserialize(validator_stake_list.data.as_slice()).unwrap();
assert_eq!(validator_stake_list.is_initialized, true);
assert_eq!(validator_stake_list.is_initialized(), true);
}
#[tokio::test]
@ -129,27 +163,25 @@ async fn test_initialize_stake_pool_with_wrong_mint_authority() {
let stake_pool_accounts = StakePoolAccounts::new();
let wrong_mint = Keypair::new();
create_mint_and_token_account(
&mut banks_client,
&payer,
&recent_blockhash,
&stake_pool_accounts,
)
.await;
// create wrong mint
create_mint(
&mut banks_client,
&payer,
&recent_blockhash,
&stake_pool_accounts.pool_mint,
&wrong_mint,
&stake_pool_accounts.withdraw_authority,
)
.await
.unwrap();
create_token_account(
&mut banks_client,
&payer,
&recent_blockhash,
&stake_pool_accounts.pool_fee_account,
&stake_pool_accounts.pool_mint.pubkey(),
&stake_pool_accounts.owner.pubkey(),
)
.await
.unwrap();
let transaction_error = create_stake_pool(
&mut banks_client,
&payer,
@ -158,7 +190,7 @@ async fn test_initialize_stake_pool_with_wrong_mint_authority() {
&stake_pool_accounts.validator_stake_list,
&wrong_mint.pubkey(),
&stake_pool_accounts.pool_fee_account.pubkey(),
&stake_pool_accounts.owner.pubkey(),
&stake_pool_accounts.owner,
&stake_pool_accounts.fee,
)
.await
@ -182,6 +214,8 @@ async fn test_initialize_stake_pool_with_wrong_token_program_id() {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let stake_pool_accounts = StakePoolAccounts::new();
let wrong_token_program = Keypair::new();
create_mint(
&mut banks_client,
&payer,
@ -192,24 +226,30 @@ async fn test_initialize_stake_pool_with_wrong_token_program_id() {
.await
.unwrap();
create_token_account(
&mut banks_client,
&payer,
&recent_blockhash,
&stake_pool_accounts.pool_fee_account,
&stake_pool_accounts.pool_mint.pubkey(),
&stake_pool_accounts.owner.pubkey(),
)
.await
.unwrap();
let rent = banks_client.get_rent().await.unwrap();
let rent_stake_pool = rent.minimum_balance(state::State::LEN);
let account_rent = rent.minimum_balance(spl_token::state::Account::LEN);
let mut transaction = Transaction::new_with_payer(
&[system_instruction::create_account(
&payer.pubkey(),
&stake_pool_accounts.pool_fee_account.pubkey(),
account_rent,
spl_token::state::Account::LEN as u64,
&wrong_token_program.pubkey(),
)],
Some(&payer.pubkey()),
);
transaction.sign(
&[&payer, &stake_pool_accounts.pool_fee_account],
recent_blockhash,
);
banks_client.process_transaction(transaction).await.unwrap();
let rent_stake_pool = rent.minimum_balance(state::StakePool::LEN);
let rent_validator_stake_list = rent.minimum_balance(state::ValidatorStakeList::LEN);
let init_args = instruction::InitArgs {
fee: stake_pool_accounts.fee,
};
let wrong_token_program = Keypair::new();
let mut transaction = Transaction::new_with_payer(
&[
@ -217,7 +257,7 @@ async fn test_initialize_stake_pool_with_wrong_token_program_id() {
&payer.pubkey(),
&stake_pool_accounts.stake_pool.pubkey(),
rent_stake_pool,
state::State::LEN as u64,
state::StakePool::LEN as u64,
&id(),
),
system_instruction::create_account(
@ -246,6 +286,7 @@ async fn test_initialize_stake_pool_with_wrong_token_program_id() {
&payer,
&stake_pool_accounts.stake_pool,
&stake_pool_accounts.validator_stake_list,
&stake_pool_accounts.owner,
],
recent_blockhash,
);
@ -264,3 +305,333 @@ async fn test_initialize_stake_pool_with_wrong_token_program_id() {
),
}
}
#[tokio::test]
async fn test_initialize_stake_pool_with_wrong_fee_accounts_owner() {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let stake_pool_accounts = StakePoolAccounts::new();
create_mint(
&mut banks_client,
&payer,
&recent_blockhash,
&stake_pool_accounts.pool_mint,
&stake_pool_accounts.withdraw_authority,
)
.await
.unwrap();
let rent = banks_client.get_rent().await.unwrap();
let account_rent = rent.minimum_balance(spl_token::state::Account::LEN);
let mut transaction = Transaction::new_with_payer(
&[system_instruction::create_account(
&payer.pubkey(),
&stake_pool_accounts.pool_fee_account.pubkey(),
account_rent,
spl_token::state::Account::LEN as u64,
&Keypair::new().pubkey(),
)],
Some(&payer.pubkey()),
);
transaction.sign(
&[&payer, &stake_pool_accounts.pool_fee_account],
recent_blockhash,
);
banks_client.process_transaction(transaction).await.unwrap();
let transaction_error = create_stake_pool(
&mut banks_client,
&payer,
&recent_blockhash,
&stake_pool_accounts.stake_pool,
&stake_pool_accounts.validator_stake_list,
&stake_pool_accounts.pool_mint.pubkey(),
&stake_pool_accounts.pool_fee_account.pubkey(),
&stake_pool_accounts.owner,
&stake_pool_accounts.fee,
)
.await
.err()
.unwrap();
match transaction_error {
TransportError::TransactionError(TransactionError::InstructionError(
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::InvalidFeeAccount as u32;
assert_eq!(error_index, program_error);
}
_ => panic!(
"Wrong error occurs while try to initialize stake pool with wrong fee account's owner"
),
}
}
#[tokio::test]
async fn test_initialize_stake_pool_with_wrong_withdraw_authority() {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let mut stake_pool_accounts = StakePoolAccounts::new();
stake_pool_accounts.withdraw_authority = Keypair::new().pubkey();
let transaction_error = stake_pool_accounts
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
.await
.err()
.unwrap();
match transaction_error {
TransportError::TransactionError(TransactionError::InstructionError(
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::WrongMintingAuthority as u32;
assert_eq!(error_index, program_error);
}
_ => panic!(
"Wrong error occurs while try to initialize stake pool with wrong withdraw authority"
),
}
}
#[tokio::test]
async fn test_initialize_stake_pool_with_not_rent_exempt_pool() {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let stake_pool_accounts = StakePoolAccounts::new();
create_mint_and_token_account(
&mut banks_client,
&payer,
&recent_blockhash,
&stake_pool_accounts,
)
.await;
let rent = banks_client.get_rent().await.unwrap();
let rent_validator_stake_list = rent.minimum_balance(state::ValidatorStakeList::LEN);
let init_args = instruction::InitArgs {
fee: stake_pool_accounts.fee,
};
let mut transaction = Transaction::new_with_payer(
&[
system_instruction::create_account(
&payer.pubkey(),
&stake_pool_accounts.stake_pool.pubkey(),
1,
state::StakePool::LEN as u64,
&id(),
),
system_instruction::create_account(
&payer.pubkey(),
&stake_pool_accounts.validator_stake_list.pubkey(),
rent_validator_stake_list,
state::ValidatorStakeList::LEN as u64,
&id(),
),
instruction::initialize(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.owner.pubkey(),
&stake_pool_accounts.validator_stake_list.pubkey(),
&stake_pool_accounts.pool_mint.pubkey(),
&stake_pool_accounts.pool_fee_account.pubkey(),
&spl_token::id(),
init_args,
)
.unwrap(),
],
Some(&payer.pubkey()),
);
transaction.sign(
&[
&payer,
&stake_pool_accounts.stake_pool,
&stake_pool_accounts.validator_stake_list,
&stake_pool_accounts.owner,
],
recent_blockhash,
);
let transaction_error = banks_client
.process_transaction(transaction)
.await
.err()
.unwrap();
match transaction_error {
TransportError::TransactionError(TransactionError::InstructionError(
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::AccountNotRentExempt as u32;
assert_eq!(error_index, program_error);
}
_ => panic!(
"Wrong error occurs while try to initialize stake pool with not rent exempt stake pool account"
),
}
}
#[tokio::test]
async fn test_initialize_stake_pool_with_not_rent_exempt_validator_stake_list() {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let stake_pool_accounts = StakePoolAccounts::new();
create_mint_and_token_account(
&mut banks_client,
&payer,
&recent_blockhash,
&stake_pool_accounts,
)
.await;
let rent = banks_client.get_rent().await.unwrap();
let rent_stake_pool = rent.minimum_balance(state::StakePool::LEN);
let init_args = instruction::InitArgs {
fee: stake_pool_accounts.fee,
};
let mut transaction = Transaction::new_with_payer(
&[
system_instruction::create_account(
&payer.pubkey(),
&stake_pool_accounts.stake_pool.pubkey(),
rent_stake_pool,
state::StakePool::LEN as u64,
&id(),
),
system_instruction::create_account(
&payer.pubkey(),
&stake_pool_accounts.validator_stake_list.pubkey(),
1,
state::ValidatorStakeList::LEN as u64,
&id(),
),
instruction::initialize(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.owner.pubkey(),
&stake_pool_accounts.validator_stake_list.pubkey(),
&stake_pool_accounts.pool_mint.pubkey(),
&stake_pool_accounts.pool_fee_account.pubkey(),
&spl_token::id(),
init_args,
)
.unwrap(),
],
Some(&payer.pubkey()),
);
transaction.sign(
&[
&payer,
&stake_pool_accounts.stake_pool,
&stake_pool_accounts.validator_stake_list,
&stake_pool_accounts.owner,
],
recent_blockhash,
);
let transaction_error = banks_client
.process_transaction(transaction)
.await
.err()
.unwrap();
match transaction_error {
TransportError::TransactionError(TransactionError::InstructionError(
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::AccountNotRentExempt as u32;
assert_eq!(error_index, program_error);
}
_ => panic!(
"Wrong error occurs while try to initialize stake pool with not rent exempt validator stake list account"
),
}
}
#[tokio::test]
async fn test_initialize_stake_pool_without_owner_signature() {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let stake_pool_accounts = StakePoolAccounts::new();
create_mint_and_token_account(
&mut banks_client,
&payer,
&recent_blockhash,
&stake_pool_accounts,
)
.await;
let rent = banks_client.get_rent().await.unwrap();
let rent_stake_pool = rent.minimum_balance(state::StakePool::LEN);
let init_args = instruction::InitArgs {
fee: stake_pool_accounts.fee,
};
let init_data = instruction::StakePoolInstruction::Initialize(init_args);
let data = init_data.serialize().unwrap();
let accounts = vec![
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), true),
AccountMeta::new_readonly(stake_pool_accounts.owner.pubkey(), false),
AccountMeta::new(stake_pool_accounts.validator_stake_list.pubkey(), false),
AccountMeta::new_readonly(stake_pool_accounts.pool_mint.pubkey(), false),
AccountMeta::new_readonly(stake_pool_accounts.pool_fee_account.pubkey(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(spl_token::id(), false),
];
let stake_pool_init_instruction = Instruction {
program_id: id(),
accounts,
data,
};
let mut transaction = Transaction::new_with_payer(
&[
system_instruction::create_account(
&payer.pubkey(),
&stake_pool_accounts.stake_pool.pubkey(),
rent_stake_pool,
state::StakePool::LEN as u64,
&id(),
),
system_instruction::create_account(
&payer.pubkey(),
&stake_pool_accounts.validator_stake_list.pubkey(),
state::ValidatorStakeList::LEN as u64,
state::ValidatorStakeList::LEN as u64,
&id(),
),
stake_pool_init_instruction,
],
Some(&payer.pubkey()),
);
transaction.sign(
&[
&payer,
&stake_pool_accounts.stake_pool,
&stake_pool_accounts.validator_stake_list,
],
recent_blockhash,
);
let transaction_error = banks_client
.process_transaction(transaction)
.await
.err()
.unwrap();
match transaction_error {
TransportError::TransactionError(TransactionError::InstructionError(
_,
InstructionError::Custom(error_index),
)) => {
let program_error = error::StakePoolError::SignatureMissing as u32;
assert_eq!(error_index, program_error);
}
_ => panic!(
"Wrong error occurs while try to initialize stake pool without owner's signature"
),
}
}

View File

@ -71,10 +71,7 @@ async fn test_set_owner() {
banks_client.process_transaction(transaction).await.unwrap();
let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
let stake_pool = state::State::deserialize(&stake_pool.data.as_slice())
.unwrap()
.stake_pool()
.unwrap();
let stake_pool = state::StakePool::deserialize(&stake_pool.data.as_slice()).unwrap();
assert_eq!(stake_pool.owner, new_owner.pubkey());
}

View File

@ -15,7 +15,13 @@ use solana_sdk::{
};
use spl_stake_pool::*;
async fn setup() -> (BanksClient, Keypair, Hash, StakePoolAccounts, StakeAccount) {
async fn setup() -> (
BanksClient,
Keypair,
Hash,
StakePoolAccounts,
ValidatorStakeAccount,
) {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let stake_pool_accounts = StakePoolAccounts::new();
stake_pool_accounts
@ -25,7 +31,7 @@ async fn setup() -> (BanksClient, Keypair, Hash, StakePoolAccounts, StakeAccount
let user = Keypair::new();
let user_stake = StakeAccount::new_with_target_authority(
let user_stake = ValidatorStakeAccount::new_with_target_authority(
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.stake_pool.pubkey(),
);

View File

@ -35,7 +35,7 @@ async fn test_update_list_balance() {
.unwrap();
// Add several accounts
let mut stake_accounts: Vec<StakeAccount> = vec![];
let mut stake_accounts: Vec<ValidatorStakeAccount> = vec![];
const STAKE_ACCOUNTS: u64 = 3;
for _ in 0..STAKE_ACCOUNTS {
stake_accounts.push(

View File

@ -24,7 +24,7 @@ async fn setup() -> (
Keypair,
Hash,
StakePoolAccounts,
StakeAccount,
ValidatorStakeAccount,
Keypair,
) {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
@ -36,7 +36,7 @@ async fn setup() -> (
let user = Keypair::new();
let user_stake = StakeAccount::new_with_target_authority(
let user_stake = ValidatorStakeAccount::new_with_target_authority(
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.stake_pool.pubkey(),
);
@ -117,7 +117,7 @@ async fn test_add_validator_stake_account() {
assert_eq!(
validator_stake_list,
state::ValidatorStakeList {
is_initialized: true,
version: state::ValidatorStakeList::VALIDATOR_STAKE_LIST_VERSION,
validators: vec![state::ValidatorStakeInfo {
validator_account: user_stake.vote.pubkey(),
last_update_epoch: 0,
@ -401,6 +401,7 @@ async fn test_not_owner_try_to_add_validator_stake_account_without_signature() {
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::id(), false),
];
@ -443,12 +444,12 @@ async fn test_add_validator_stake_account_when_stake_acc_not_in_stake_state() {
let user = Keypair::new();
let user_stake = StakeAccount::new_with_target_authority(
let user_stake = ValidatorStakeAccount::new_with_target_authority(
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.stake_pool.pubkey(),
);
let user_stake_authority = Keypair::new();
create_stake_account(
create_validator_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,

View File

@ -24,7 +24,7 @@ async fn setup() -> (
Keypair,
Hash,
StakePoolAccounts,
StakeAccount,
ValidatorStakeAccount,
Keypair,
Keypair,
) {
@ -37,7 +37,7 @@ async fn setup() -> (
let user = Keypair::new();
let user_stake = StakeAccount::new_with_target_authority(
let user_stake = ValidatorStakeAccount::new_with_target_authority(
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.stake_pool.pubkey(),
);
@ -132,7 +132,7 @@ async fn test_remove_validator_stake_account() {
assert_eq!(
validator_stake_list,
state::ValidatorStakeList {
is_initialized: true,
version: state::ValidatorStakeList::VALIDATOR_STAKE_LIST_VERSION,
validators: vec![]
}
);
@ -530,12 +530,12 @@ async fn test_remove_validator_stake_account_when_stake_acc_not_in_stake_state()
let user = Keypair::new();
let user_stake = StakeAccount::new_with_target_authority(
let user_stake = ValidatorStakeAccount::new_with_target_authority(
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.stake_pool.pubkey(),
);
let user_stake_authority = Keypair::new();
create_stake_account(
create_validator_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,

View File

@ -22,10 +22,9 @@ async fn setup() -> (
Keypair,
Hash,
StakePoolAccounts,
StakeAccount,
ValidatorStakeAccount,
DepositInfo,
u64,
u64,
) {
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
let stake_pool_accounts = StakePoolAccounts::new();
@ -34,7 +33,7 @@ async fn setup() -> (
.await
.unwrap();
let validator_stake_account: StakeAccount = simple_add_validator_stake_account(
let validator_stake_account: ValidatorStakeAccount = simple_add_validator_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
@ -52,7 +51,6 @@ async fn setup() -> (
.await;
let tokens_to_burn = deposit_info.pool_tokens / 4;
let lamports_to_withdraw = tokens_to_burn; // For now math is 1:1
// Delegate tokens for burning
delegate_tokens(
@ -74,7 +72,6 @@ async fn setup() -> (
validator_stake_account,
deposit_info,
tokens_to_burn,
lamports_to_withdraw,
)
}
@ -88,7 +85,6 @@ async fn test_stake_pool_withdraw() {
validator_stake_account,
deposit_info,
tokens_to_burn,
lamports_to_withdraw,
) = setup().await;
// Create stake account to withdraw to
@ -104,10 +100,8 @@ async fn test_stake_pool_withdraw() {
// Save stake pool state before withdrawal
let stake_pool_before =
get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
let stake_pool_before = state::State::deserialize(&stake_pool_before.data.as_slice())
.unwrap()
.stake_pool()
.unwrap();
let stake_pool_before =
state::StakePool::deserialize(&stake_pool_before.data.as_slice()).unwrap();
// Save validator stake account record before withdrawal
let validator_stake_list = get_account(
@ -135,20 +129,17 @@ async fn test_stake_pool_withdraw() {
&deposit_info.user_pool_account,
&validator_stake_account.stake_account,
&new_authority,
lamports_to_withdraw,
tokens_to_burn,
)
.await
.unwrap();
// Check pool stats
let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
let stake_pool = state::State::deserialize(&stake_pool.data.as_slice())
.unwrap()
.stake_pool()
.unwrap();
let stake_pool = state::StakePool::deserialize(&stake_pool.data.as_slice()).unwrap();
assert_eq!(
stake_pool.stake_total,
stake_pool_before.stake_total - lamports_to_withdraw
stake_pool_before.stake_total - tokens_to_burn
);
assert_eq!(
stake_pool.pool_total,
@ -168,7 +159,7 @@ async fn test_stake_pool_withdraw() {
.unwrap();
assert_eq!(
validator_stake_item.balance,
validator_stake_item_before.balance - lamports_to_withdraw
validator_stake_item_before.balance - tokens_to_burn
);
// Check tokens burned
@ -192,7 +183,7 @@ async fn test_stake_pool_withdraw() {
get_account(&mut banks_client, &user_stake_recipient.pubkey()).await;
assert_eq!(
user_stake_recipient_account.lamports,
initial_stake_lamports + lamports_to_withdraw
initial_stake_lamports + tokens_to_burn
);
}
@ -205,8 +196,7 @@ async fn test_stake_pool_withdraw_with_wrong_stake_program() {
stake_pool_accounts,
validator_stake_account,
deposit_info,
_,
lamports_to_withdraw,
tokens_to_burn,
) = setup().await;
// Create stake account to withdraw to
@ -228,7 +218,7 @@ async fn test_stake_pool_withdraw_with_wrong_stake_program() {
&stake_pool_accounts.pool_mint.pubkey(),
&spl_token::id(),
&wrong_stake_program.pubkey(),
lamports_to_withdraw,
tokens_to_burn,
)
.unwrap()],
Some(&payer.pubkey()),
@ -257,8 +247,7 @@ async fn test_stake_pool_withdraw_with_wrong_withdraw_authority() {
mut stake_pool_accounts,
validator_stake_account,
deposit_info,
_,
lamports_to_withdraw,
tokens_to_burn,
) = setup().await;
// Create stake account to withdraw to
@ -276,7 +265,7 @@ async fn test_stake_pool_withdraw_with_wrong_withdraw_authority() {
&deposit_info.user_pool_account,
&validator_stake_account.stake_account,
&new_authority,
lamports_to_withdraw,
tokens_to_burn,
)
.await
.err()
@ -303,8 +292,7 @@ async fn test_stake_pool_withdraw_with_wrong_token_program_id() {
stake_pool_accounts,
validator_stake_account,
deposit_info,
_,
lamports_to_withdraw,
tokens_to_burn,
) = setup().await;
// Create stake account to withdraw to
@ -326,7 +314,7 @@ async fn test_stake_pool_withdraw_with_wrong_token_program_id() {
&stake_pool_accounts.pool_mint.pubkey(),
&wrong_token_program.pubkey(),
&stake::id(),
lamports_to_withdraw,
tokens_to_burn,
)
.unwrap()],
Some(&payer.pubkey()),
@ -355,8 +343,7 @@ async fn test_stake_pool_withdraw_with_wrong_validator_stake_list() {
mut stake_pool_accounts,
validator_stake_account,
deposit_info,
_,
lamports_to_withdraw,
tokens_to_burn,
) = setup().await;
// Create stake account to withdraw to
@ -374,7 +361,7 @@ async fn test_stake_pool_withdraw_with_wrong_validator_stake_list() {
&deposit_info.user_pool_account,
&validator_stake_account.stake_account,
&new_authority,
lamports_to_withdraw,
tokens_to_burn,
)
.await
.err()
@ -403,13 +390,13 @@ async fn test_stake_pool_withdraw_when_stake_acc_not_in_stake_state() {
.await
.unwrap();
let validator_stake_account = StakeAccount::new_with_target_authority(
let validator_stake_account = ValidatorStakeAccount::new_with_target_authority(
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.stake_pool.pubkey(),
);
let user_stake_authority = Keypair::new();
create_stake_account(
create_validator_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
@ -455,7 +442,6 @@ async fn test_stake_pool_withdraw_when_stake_acc_not_in_stake_state() {
let pool_tokens = get_token_balance(&mut banks_client, &user_pool_account).await;
let tokens_to_burn = pool_tokens / 4;
let lamports_to_withdraw = tokens_to_burn; // For now math is 1:1
// Delegate tokens for burning
delegate_tokens(
@ -483,7 +469,7 @@ async fn test_stake_pool_withdraw_when_stake_acc_not_in_stake_state() {
&user_pool_account,
&validator_stake_account.stake_account,
&new_authority,
lamports_to_withdraw,
tokens_to_burn,
)
.await
.err()
@ -510,7 +496,7 @@ async fn test_stake_pool_withdraw_from_unknown_validator() {
.await
.unwrap();
let validator_stake_account = StakeAccount::new_with_target_authority(
let validator_stake_account = ValidatorStakeAccount::new_with_target_authority(
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.stake_pool.pubkey(),
);
@ -518,7 +504,7 @@ async fn test_stake_pool_withdraw_from_unknown_validator() {
.create_and_delegate(&mut banks_client, &payer, &recent_blockhash)
.await;
let user_stake = StakeAccount::new_with_target_authority(
let user_stake = ValidatorStakeAccount::new_with_target_authority(
&stake_pool_accounts.deposit_authority,
&stake_pool_accounts.stake_pool.pubkey(),
);
@ -573,7 +559,6 @@ async fn test_stake_pool_withdraw_from_unknown_validator() {
let pool_tokens = get_token_balance(&mut banks_client, &user_pool_account).await;
let tokens_to_burn = pool_tokens / 4;
let lamports_to_withdraw = tokens_to_burn; // For now math is 1:1
// Delegate tokens for burning
delegate_tokens(
@ -601,7 +586,7 @@ async fn test_stake_pool_withdraw_from_unknown_validator() {
&user_pool_account,
&validator_stake_account.stake_account,
&new_authority,
lamports_to_withdraw,
tokens_to_burn,
)
.await
.err()
@ -628,8 +613,7 @@ async fn test_stake_pool_double_withdraw_to_the_same_account() {
stake_pool_accounts,
validator_stake_account,
deposit_info,
_,
lamports_to_withdraw,
tokens_to_burn,
) = setup().await;
// Create stake account to withdraw to
@ -652,7 +636,7 @@ async fn test_stake_pool_double_withdraw_to_the_same_account() {
&deposit_info.user_pool_account,
&validator_stake_account.stake_account,
&new_authority,
lamports_to_withdraw,
tokens_to_burn,
)
.await
.unwrap();
@ -668,7 +652,7 @@ async fn test_stake_pool_double_withdraw_to_the_same_account() {
&deposit_info.user_pool_account,
&validator_stake_account.stake_account,
&new_authority,
lamports_to_withdraw,
tokens_to_burn,
)
.await
.err()
@ -691,7 +675,7 @@ async fn test_stake_pool_withdraw_token_delegate_was_not_setup() {
.await
.unwrap();
let validator_stake_account: StakeAccount = simple_add_validator_stake_account(
let validator_stake_account: ValidatorStakeAccount = simple_add_validator_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
@ -709,7 +693,6 @@ async fn test_stake_pool_withdraw_token_delegate_was_not_setup() {
.await;
let tokens_to_burn = deposit_info.pool_tokens / 4;
let lamports_to_withdraw = tokens_to_burn; // For now math is 1:1
// Create stake account to withdraw to
let user_stake_recipient = Keypair::new();
@ -731,7 +714,7 @@ async fn test_stake_pool_withdraw_token_delegate_was_not_setup() {
&deposit_info.user_pool_account,
&validator_stake_account.stake_account,
&new_authority,
lamports_to_withdraw,
tokens_to_burn,
)
.await
.err()
@ -760,7 +743,7 @@ async fn test_stake_pool_withdraw_with_low_delegation() {
.await
.unwrap();
let validator_stake_account: StakeAccount = simple_add_validator_stake_account(
let validator_stake_account: ValidatorStakeAccount = simple_add_validator_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
@ -778,7 +761,6 @@ async fn test_stake_pool_withdraw_with_low_delegation() {
.await;
let tokens_to_burn = deposit_info.pool_tokens / 4;
let lamports_to_withdraw = tokens_to_burn; // For now math is 1:1
// Delegate tokens for burning
delegate_tokens(
@ -812,7 +794,7 @@ async fn test_stake_pool_withdraw_with_low_delegation() {
&deposit_info.user_pool_account,
&validator_stake_account.stake_account,
&new_authority,
lamports_to_withdraw,
tokens_to_burn,
)
.await
.err()