stake-pool: Fixup accounting on increase transient account (#1985)

This commit is contained in:
Jon Cinque 2021-06-29 00:51:54 +02:00 committed by GitHub
parent 0b3552b892
commit ac2f9e09a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 74 additions and 31 deletions

View File

@ -105,9 +105,10 @@ fn command_create_pool(
max_validators: u32, max_validators: u32,
stake_pool_keypair: Option<Keypair>, stake_pool_keypair: Option<Keypair>,
mint_keypair: Option<Keypair>, mint_keypair: Option<Keypair>,
reserve_keypair: Option<Keypair>,
) -> CommandResult { ) -> CommandResult {
let reserve_stake = Keypair::new(); let reserve_keypair = reserve_keypair.unwrap_or_else(Keypair::new);
println!("Creating reserve stake {}", reserve_stake.pubkey()); println!("Creating reserve stake {}", reserve_keypair.pubkey());
let mint_keypair = mint_keypair.unwrap_or_else(Keypair::new); let mint_keypair = mint_keypair.unwrap_or_else(Keypair::new);
println!("Creating mint {}", mint_keypair.pubkey()); println!("Creating mint {}", mint_keypair.pubkey());
@ -163,13 +164,13 @@ fn command_create_pool(
// Account for the stake pool reserve // Account for the stake pool reserve
system_instruction::create_account( system_instruction::create_account(
&config.fee_payer.pubkey(), &config.fee_payer.pubkey(),
&reserve_stake.pubkey(), &reserve_keypair.pubkey(),
reserve_stake_balance, reserve_stake_balance,
STAKE_STATE_LEN as u64, STAKE_STATE_LEN as u64,
&stake_program::id(), &stake_program::id(),
), ),
stake_program::initialize( stake_program::initialize(
&reserve_stake.pubkey(), &reserve_keypair.pubkey(),
&stake_program::Authorized { &stake_program::Authorized {
staker: withdraw_authority, staker: withdraw_authority,
withdrawer: withdraw_authority, withdrawer: withdraw_authority,
@ -236,7 +237,7 @@ fn command_create_pool(
&config.manager.pubkey(), &config.manager.pubkey(),
&config.staker.pubkey(), &config.staker.pubkey(),
&validator_list.pubkey(), &validator_list.pubkey(),
&reserve_stake.pubkey(), &reserve_keypair.pubkey(),
&mint_keypair.pubkey(), &mint_keypair.pubkey(),
&pool_fee_account.pubkey(), &pool_fee_account.pubkey(),
&spl_token::id(), &spl_token::id(),
@ -259,7 +260,7 @@ fn command_create_pool(
config.fee_payer.as_ref(), config.fee_payer.as_ref(),
&mint_keypair, &mint_keypair,
&pool_fee_account, &pool_fee_account,
&reserve_stake, &reserve_keypair,
]; ];
unique_signers!(setup_signers); unique_signers!(setup_signers);
setup_transaction.sign(&setup_signers, recent_blockhash); setup_transaction.sign(&setup_signers, recent_blockhash);
@ -640,6 +641,13 @@ fn command_list(config: &Config, stake_pool_address: &Pubkey) -> CommandResult {
let pool_mint = get_token_mint(&config.rpc_client, &stake_pool.pool_mint)?; let pool_mint = get_token_mint(&config.rpc_client, &stake_pool.pool_mint)?;
let epoch_info = config.rpc_client.get_epoch_info()?; let epoch_info = config.rpc_client.get_epoch_info()?;
let reserve_stake = config.rpc_client.get_account(&stake_pool.reserve_stake)?;
println!(
"Reserve Account: {}\tBalance: {}",
stake_pool.reserve_stake,
Sol(reserve_stake.lamports),
);
for validator in validator_list.validators { for validator in validator_list.validators {
println!( println!(
"Validator Vote Account: {}\tBalance: {}\tLast Update Epoch: {}{}", "Validator Vote Account: {}\tBalance: {}\tLast Update Epoch: {}{}",
@ -1241,6 +1249,14 @@ fn main() {
.takes_value(true) .takes_value(true)
.help("Stake pool mint keypair [default: new keypair]"), .help("Stake pool mint keypair [default: new keypair]"),
) )
.arg(
Arg::with_name("reserve_keypair")
.long("reserve-keypair")
.validator(is_keypair_or_ask_keyword)
.value_name("PATH")
.takes_value(true)
.help("Stake pool reserve keypair [default: new keypair]"),
)
) )
.subcommand(SubCommand::with_name("create-validator-stake") .subcommand(SubCommand::with_name("create-validator-stake")
.about("Create a new stake account to use with the pool. Must be signed by the pool staker.") .about("Create a new stake account to use with the pool. Must be signed by the pool staker.")
@ -1710,6 +1726,7 @@ fn main() {
let max_validators = value_t_or_exit!(arg_matches, "max_validators", u32); let max_validators = value_t_or_exit!(arg_matches, "max_validators", u32);
let pool_keypair = keypair_of(arg_matches, "pool_keypair"); let pool_keypair = keypair_of(arg_matches, "pool_keypair");
let mint_keypair = keypair_of(arg_matches, "mint_keypair"); let mint_keypair = keypair_of(arg_matches, "mint_keypair");
let reserve_keypair = keypair_of(arg_matches, "reserve_keypair");
command_create_pool( command_create_pool(
&config, &config,
deposit_authority, deposit_authority,
@ -1720,6 +1737,7 @@ fn main() {
max_validators, max_validators,
pool_keypair, pool_keypair,
mint_keypair, mint_keypair,
reserve_keypair,
) )
} }
("create-validator-stake", Some(arg_matches)) => { ("create-validator-stake", Some(arg_matches)) => {

View File

@ -162,7 +162,11 @@ pub enum StakePoolInstruction {
/// 10. `[]` Stake Config sysvar /// 10. `[]` Stake Config sysvar
/// 11. `[]` System program /// 11. `[]` System program
/// 12. `[]` Stake program /// 12. `[]` Stake program
/// userdata: amount of lamports to split into the transient stake account /// userdata: amount of lamports to increase on the given validator.
/// The actual amount split into the transient stake account is:
/// `lamports + stake_rent_exemption`
/// The rent-exemption of the stake account is withdrawn back to the reserve
/// after it is merged.
IncreaseValidatorStake(u64), IncreaseValidatorStake(u64),
/// (Staker only) Set the preferred deposit or withdraw stake account for the /// (Staker only) Set the preferred deposit or withdraw stake account for the

View File

@ -33,7 +33,7 @@ pub const MINIMUM_ACTIVE_STAKE: u64 = LAMPORTS_PER_SOL;
/// Maximum amount of validator stake accounts to update per /// Maximum amount of validator stake accounts to update per
/// `UpdateValidatorListBalance` instruction, based on compute limits /// `UpdateValidatorListBalance` instruction, based on compute limits
pub const MAX_VALIDATORS_TO_UPDATE: usize = 10; pub const MAX_VALIDATORS_TO_UPDATE: usize = 9;
/// Get the stake amount under consideration when calculating pool token /// Get the stake amount under consideration when calculating pool token
/// conversions /// conversions

View File

@ -1150,19 +1150,21 @@ impl Processor {
} }
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>()); let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
let minimum_lamports = MINIMUM_ACTIVE_STAKE + stake_rent; if lamports < MINIMUM_ACTIVE_STAKE {
if lamports < minimum_lamports {
msg!( msg!(
"Need more than {} lamports for transient stake to be rent-exempt and mergeable, {} provided", "Need more than {} lamports for transient stake to be rent-exempt and mergeable, {} provided",
minimum_lamports, MINIMUM_ACTIVE_STAKE,
lamports lamports
); );
return Err(ProgramError::AccountNotRentExempt); return Err(ProgramError::AccountNotRentExempt);
} }
// the stake account rent exemption is withdrawn after the merge, so
let total_lamports = lamports.saturating_add(stake_rent);
if reserve_stake_account_info if reserve_stake_account_info
.lamports() .lamports()
.saturating_sub(lamports) .saturating_sub(total_lamports)
<= stake_rent <= stake_rent
{ {
let max_split_amount = reserve_stake_account_info let max_split_amount = reserve_stake_account_info
@ -1189,7 +1191,7 @@ impl Processor {
withdraw_authority_info.clone(), withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW, AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed, stake_pool.withdraw_bump_seed,
lamports, total_lamports,
transient_stake_account_info.clone(), transient_stake_account_info.clone(),
)?; )?;
@ -1206,7 +1208,7 @@ impl Processor {
stake_pool.withdraw_bump_seed, stake_pool.withdraw_bump_seed,
)?; )?;
validator_list_entry.transient_stake_lamports = lamports; validator_list_entry.transient_stake_lamports = total_lamports;
validator_list.serialize(&mut *validator_list_info.data.borrow_mut())?; validator_list.serialize(&mut *validator_list_info.data.borrow_mut())?;
Ok(()) Ok(())
@ -1404,6 +1406,9 @@ impl Processor {
if stake_program::active_stakes_can_merge(&stake, &validator_stake) if stake_program::active_stakes_can_merge(&stake, &validator_stake)
.is_ok() .is_ok()
{ {
let additional_lamports = transient_stake_info
.lamports()
.saturating_sub(stake.delegation.stake);
Self::stake_merge( Self::stake_merge(
stake_pool_info.key, stake_pool_info.key,
transient_stake_info.clone(), transient_stake_info.clone(),
@ -1415,6 +1420,23 @@ impl Processor {
stake_history_info.clone(), stake_history_info.clone(),
stake_program_info.clone(), stake_program_info.clone(),
)?; )?;
// post merge of two active stakes, withdraw
// the extra back to the reserve
if additional_lamports > 0 {
Self::stake_withdraw(
stake_pool_info.key,
validator_stake_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.withdraw_bump_seed,
reserve_stake_info.clone(),
clock_info.clone(),
stake_history_info.clone(),
stake_program_info.clone(),
additional_lamports,
)?;
}
} else { } else {
msg!("Stake activating or just active, not ready to merge"); msg!("Stake activating or just active, not ready to merge");
transient_stake_lamports = account_stake; transient_stake_lamports = account_stake;
@ -1436,6 +1458,10 @@ impl Processor {
// Status for validator stake // Status for validator stake
// * active -> do everything // * active -> do everything
// * any other state / not a stake -> error state, but account for transient stake // * any other state / not a stake -> error state, but account for transient stake
let validator_stake_state = try_from_slice_unchecked::<stake_program::StakeState>(
&validator_stake_info.data.borrow(),
)
.ok();
match validator_stake_state { match validator_stake_state {
Some(stake_program::StakeState::Stake(_, stake)) => { Some(stake_program::StakeState::Stake(_, stake)) => {
if validator_stake_record.status == StakeStatus::Active { if validator_stake_record.status == StakeStatus::Active {
@ -1782,8 +1808,8 @@ impl Processor {
/// Processes [Withdraw](enum.Instruction.html). /// Processes [Withdraw](enum.Instruction.html).
fn process_withdraw( fn process_withdraw(
program_id: &Pubkey, program_id: &Pubkey,
pool_tokens: u64,
accounts: &[AccountInfo], accounts: &[AccountInfo],
pool_tokens: u64,
) -> ProgramResult { ) -> ProgramResult {
let account_info_iter = &mut accounts.iter(); let account_info_iter = &mut accounts.iter();
let stake_pool_info = next_account_info(account_info_iter)?; let stake_pool_info = next_account_info(account_info_iter)?;
@ -2126,7 +2152,7 @@ impl Processor {
} }
StakePoolInstruction::Withdraw(amount) => { StakePoolInstruction::Withdraw(amount) => {
msg!("Instruction: Withdraw"); msg!("Instruction: Withdraw");
Self::process_withdraw(program_id, amount, accounts) Self::process_withdraw(program_id, accounts, amount)
} }
StakePoolInstruction::SetManager => { StakePoolInstruction::SetManager => {
msg!("Instruction: SetManager"); msg!("Instruction: SetManager");

View File

@ -93,8 +93,8 @@ async fn success() {
assert!(transient_account.is_none()); assert!(transient_account.is_none());
let rent = banks_client.get_rent().await.unwrap(); let rent = banks_client.get_rent().await.unwrap();
let lamports = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>()); let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
let reserve_lamports = reserve_lamports - lamports; let increase_amount = reserve_lamports - stake_rent - 1;
let error = stake_pool_accounts let error = stake_pool_accounts
.increase_validator_stake( .increase_validator_stake(
&mut banks_client, &mut banks_client,
@ -102,7 +102,7 @@ async fn success() {
&recent_blockhash, &recent_blockhash,
&validator_stake.transient_stake_account, &validator_stake.transient_stake_account,
&validator_stake.vote.pubkey(), &validator_stake.vote.pubkey(),
reserve_lamports, increase_amount,
) )
.await; .await;
assert!(error.is_none()); assert!(error.is_none());
@ -116,7 +116,7 @@ async fn success() {
let reserve_stake_state = let reserve_stake_state =
deserialize::<stake_program::StakeState>(&reserve_stake_account.data).unwrap(); deserialize::<stake_program::StakeState>(&reserve_stake_account.data).unwrap();
assert_eq!( assert_eq!(
pre_reserve_stake_account.lamports - reserve_lamports, pre_reserve_stake_account.lamports - increase_amount - stake_rent,
reserve_stake_account.lamports reserve_stake_account.lamports
); );
assert!(reserve_stake_state.delegation().is_none()); assert!(reserve_stake_state.delegation().is_none());
@ -126,7 +126,10 @@ async fn success() {
get_account(&mut banks_client, &validator_stake.transient_stake_account).await; get_account(&mut banks_client, &validator_stake.transient_stake_account).await;
let transient_stake_state = let transient_stake_state =
deserialize::<stake_program::StakeState>(&transient_stake_account.data).unwrap(); deserialize::<stake_program::StakeState>(&transient_stake_account.data).unwrap();
assert_eq!(transient_stake_account.lamports, reserve_lamports); assert_eq!(
transient_stake_account.lamports,
increase_amount + stake_rent
);
assert_ne!( assert_ne!(
transient_stake_state.delegation().unwrap().activation_epoch, transient_stake_state.delegation().unwrap().activation_epoch,
Epoch::MAX Epoch::MAX
@ -332,7 +335,7 @@ async fn fail_with_small_lamport_amount() {
) = setup().await; ) = setup().await;
let rent = banks_client.get_rent().await.unwrap(); let rent = banks_client.get_rent().await.unwrap();
let lamports = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>()); let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
let error = stake_pool_accounts let error = stake_pool_accounts
.increase_validator_stake( .increase_validator_stake(
@ -341,7 +344,7 @@ async fn fail_with_small_lamport_amount() {
&recent_blockhash, &recent_blockhash,
&validator_stake.transient_stake_account, &validator_stake.transient_stake_account,
&validator_stake.vote.pubkey(), &validator_stake.vote.pubkey(),
lamports, stake_rent,
) )
.await .await
.unwrap() .unwrap()

View File

@ -381,14 +381,6 @@ async fn merge_into_validator_stake() {
let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data).unwrap(); let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data).unwrap();
assert_eq!(expected_lamports, stake_pool.total_stake_lamports); assert_eq!(expected_lamports, stake_pool.total_stake_lamports);
let stake_pool_info = get_account(
&mut context.banks_client,
&stake_pool_accounts.stake_pool.pubkey(),
)
.await;
let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data).unwrap();
assert_eq!(expected_lamports, stake_pool.total_stake_lamports);
// Warp one more epoch so the stakes activate, ready to merge // Warp one more epoch so the stakes activate, ready to merge
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch; let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
slot += slots_per_epoch; slot += slots_per_epoch;