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:
parent
beb4aa7e7f
commit
11df4aa5ec
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
];
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(¤t_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(¤t_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,
|
||||
|
|
|
@ -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]),
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue