redelegate stake (#5868)

* redelegate stake

* boil this down to just delegate(), which can be offered any number of times
This commit is contained in:
Rob Walker 2019-09-11 09:48:29 -07:00 committed by GitHub
parent 1853771930
commit 92d2452f33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 173 additions and 114 deletions

View File

@ -24,8 +24,11 @@ pub const DEFAULT_SLASH_PENALTY: u8 = ((5 * std::u8::MAX as usize) / 100) as u8;
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
pub struct Config { pub struct Config {
/// how much stake we can activate per-epoch as a fraction of currently effective stake
pub warmup_rate: f64, pub warmup_rate: f64,
/// how much stake we can deactivate as a fraction of currently effective stake
pub cooldown_rate: f64, pub cooldown_rate: f64,
/// percentage of stake lost when slash, expressed as a portion of std::u8::MAX
pub slash_penalty: u8, pub slash_penalty: u8,
} }

View File

@ -41,27 +41,28 @@ pub enum StakeInstruction {
/// Expects 1 Account: /// Expects 1 Account:
/// 0 - Uninitialized StakeAccount to be lockup'd /// 0 - Uninitialized StakeAccount to be lockup'd
/// ///
/// The u64 is the portion of the Stake account balance to be activated, /// The Slot parameter denotes slot height at which this stake
/// must be less than StakeAccount.lamports /// will allow withdrawal from the stake account.
/// ///
Lockup(Slot), Lockup(Slot),
/// `Delegate` a stake to a particular node /// `Delegate` a stake to a particular vote account
/// ///
/// Expects 3 Accounts: /// Expects 4 Accounts:
/// 0 - Lockup'd StakeAccount to be delegated <= must have this signature /// 0 - Lockup'd StakeAccount to be delegated <= transaction must have this signature
/// 1 - VoteAccount to which this Stake will be delegated /// 1 - VoteAccount to which this Stake will be delegated
/// 2 - Clock sysvar Account that carries clock bank epoch /// 2 - Clock sysvar Account that carries clock bank epoch
/// 3 - Config Account that carries stake config /// 3 - Config Account that carries stake config
/// ///
/// The u64 is the portion of the Stake account balance to be activated, /// The entire balance of the staking account is staked. DelegateStake
/// must be less than StakeAccount.lamports /// can be called multiple times, but re-delegation is delayed
/// by one epoch
/// ///
DelegateStake(u64), DelegateStake,
/// Redeem credits in the stake account /// Redeem credits in the stake account
/// ///
/// Expects 4 Accounts: /// Expects 5 Accounts:
/// 0 - Delegate StakeAccount to be updated with rewards /// 0 - Delegate StakeAccount to be updated with rewards
/// 1 - VoteAccount to which the Stake is delegated, /// 1 - VoteAccount to which the Stake is delegated,
/// 2 - RewardsPool Stake Account from which to redeem credits /// 2 - RewardsPool Stake Account from which to redeem credits
@ -71,8 +72,8 @@ pub enum StakeInstruction {
/// Withdraw unstaked lamports from the stake account /// Withdraw unstaked lamports from the stake account
/// ///
/// Expects 3 Accounts: /// Expects 4 Accounts:
/// 0 - Delegate StakeAccount /// 0 - Delegate StakeAccount <= transaction must have this signature
/// 1 - System account to which the lamports will be transferred, /// 1 - System account to which the lamports will be transferred,
/// 2 - Syscall Account that carries epoch /// 2 - Syscall Account that carries epoch
/// 3 - StakeHistory sysvar that carries stake warmup/cooldown history /// 3 - StakeHistory sysvar that carries stake warmup/cooldown history
@ -83,10 +84,11 @@ pub enum StakeInstruction {
/// Deactivates the stake in the account /// Deactivates the stake in the account
/// ///
/// Expects 2 Accounts: /// Expects 3 Accounts:
/// 0 - Delegate StakeAccount /// 0 - Delegate StakeAccount <= transaction must have this signature
/// 1 - VoteAccount to which the Stake is delegated /// 1 - VoteAccount to which the Stake is delegated
/// 2 - Syscall Account that carries epoch /// 2 - Syscall Account that carries epoch
///
Deactivate, Deactivate,
} }
@ -127,7 +129,7 @@ pub fn create_stake_account_and_delegate_stake(
lamports: u64, lamports: u64,
) -> Vec<Instruction> { ) -> Vec<Instruction> {
let mut instructions = create_stake_account(from_pubkey, stake_pubkey, lamports); let mut instructions = create_stake_account(from_pubkey, stake_pubkey, lamports);
instructions.push(delegate_stake(stake_pubkey, vote_pubkey, lamports)); instructions.push(delegate_stake(stake_pubkey, vote_pubkey));
instructions instructions
} }
@ -142,14 +144,14 @@ pub fn redeem_vote_credits(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instr
Instruction::new(id(), &StakeInstruction::RedeemVoteCredits, account_metas) Instruction::new(id(), &StakeInstruction::RedeemVoteCredits, account_metas)
} }
pub fn delegate_stake(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey, stake: u64) -> Instruction { pub fn delegate_stake(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instruction {
let account_metas = vec![ let account_metas = vec![
AccountMeta::new(*stake_pubkey, true), AccountMeta::new(*stake_pubkey, true),
AccountMeta::new_credit_only(*vote_pubkey, false), AccountMeta::new_credit_only(*vote_pubkey, false),
AccountMeta::new_credit_only(sysvar::clock::id(), false), AccountMeta::new_credit_only(sysvar::clock::id(), false),
AccountMeta::new_credit_only(crate::config::id(), false), AccountMeta::new_credit_only(crate::config::id(), false),
]; ];
Instruction::new(id(), &StakeInstruction::DelegateStake(stake), account_metas) Instruction::new(id(), &StakeInstruction::DelegateStake, account_metas)
} }
pub fn withdraw(stake_pubkey: &Pubkey, to_pubkey: &Pubkey, lamports: u64) -> Instruction { pub fn withdraw(stake_pubkey: &Pubkey, to_pubkey: &Pubkey, lamports: u64) -> Instruction {
@ -191,7 +193,7 @@ pub fn process_instruction(
// TODO: data-driven unpack and dispatch of KeyedAccounts // TODO: data-driven unpack and dispatch of KeyedAccounts
match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? { match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? {
StakeInstruction::Lockup(slot) => me.lockup(slot), StakeInstruction::Lockup(slot) => me.lockup(slot),
StakeInstruction::DelegateStake(stake) => { StakeInstruction::DelegateStake => {
if rest.len() != 3 { if rest.len() != 3 {
Err(InstructionError::InvalidInstructionData)?; Err(InstructionError::InvalidInstructionData)?;
} }
@ -199,7 +201,6 @@ pub fn process_instruction(
me.delegate_stake( me.delegate_stake(
vote, vote,
stake,
&sysvar::clock::from_keyed_account(&rest[1])?, &sysvar::clock::from_keyed_account(&rest[1])?,
&config::from_keyed_account(&rest[2])?, &config::from_keyed_account(&rest[2])?,
) )
@ -290,7 +291,7 @@ mod tests {
Err(InstructionError::InvalidAccountData), Err(InstructionError::InvalidAccountData),
); );
assert_eq!( assert_eq!(
process_instruction(&delegate_stake(&Pubkey::default(), &Pubkey::default(), 0)), process_instruction(&delegate_stake(&Pubkey::default(), &Pubkey::default())),
Err(InstructionError::InvalidAccountData), Err(InstructionError::InvalidAccountData),
); );
assert_eq!( assert_eq!(
@ -307,7 +308,17 @@ mod tests {
fn test_stake_process_instruction_decode_bail() { fn test_stake_process_instruction_decode_bail() {
// these will not call stake_state, have bogus contents // these will not call stake_state, have bogus contents
// gets the first check // gets the "is_empty()" check
assert_eq!(
super::process_instruction(
&Pubkey::default(),
&mut [],
&serialize(&StakeInstruction::Lockup(0)).unwrap(),
),
Err(InstructionError::InvalidInstructionData),
);
// gets the first check in delegate, wrong number of accounts
assert_eq!( assert_eq!(
super::process_instruction( super::process_instruction(
&Pubkey::default(), &Pubkey::default(),
@ -316,7 +327,7 @@ mod tests {
false, false,
&mut Account::default(), &mut Account::default(),
)], )],
&serialize(&StakeInstruction::DelegateStake(0)).unwrap(), &serialize(&StakeInstruction::DelegateStake).unwrap(),
), ),
Err(InstructionError::InvalidInstructionData), Err(InstructionError::InvalidInstructionData),
); );
@ -330,11 +341,12 @@ mod tests {
false, false,
&mut Account::default() &mut Account::default()
),], ),],
&serialize(&StakeInstruction::DelegateStake(0)).unwrap(), &serialize(&StakeInstruction::DelegateStake).unwrap(),
), ),
Err(InstructionError::InvalidInstructionData), Err(InstructionError::InvalidInstructionData),
); );
// catches the number of args check
assert_eq!( assert_eq!(
super::process_instruction( super::process_instruction(
&Pubkey::default(), &Pubkey::default(),
@ -347,6 +359,22 @@ mod tests {
Err(InstructionError::InvalidInstructionData), Err(InstructionError::InvalidInstructionData),
); );
// catches the type of args check
assert_eq!(
super::process_instruction(
&Pubkey::default(),
&mut [
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
],
&serialize(&StakeInstruction::RedeemVoteCredits).unwrap(),
),
Err(InstructionError::InvalidArgument),
);
// gets the check non-deserialize-able account in delegate_stake // gets the check non-deserialize-able account in delegate_stake
assert_eq!( assert_eq!(
super::process_instruction( super::process_instruction(
@ -365,7 +393,7 @@ mod tests {
&mut config::create_account(1, &config::Config::default()) &mut config::create_account(1, &config::Config::default())
), ),
], ],
&serialize(&StakeInstruction::DelegateStake(0)).unwrap(), &serialize(&StakeInstruction::DelegateStake).unwrap(),
), ),
Err(InstructionError::InvalidAccountData), Err(InstructionError::InvalidAccountData),
); );

View File

@ -76,7 +76,7 @@ pub struct Stake {
pub prior_delegates_idx: usize, pub prior_delegates_idx: usize,
} }
const MAX_PRIOR_DELEGATES: usize = 32; const MAX_PRIOR_DELEGATES: usize = 32; // this is how many epochs a stake is exposed to a slashing condition
impl Default for Stake { impl Default for Stake {
fn default() -> Self { fn default() -> Self {
@ -90,7 +90,7 @@ impl Default for Stake {
config: Config::default(), config: Config::default(),
lockup: 0, lockup: 0,
prior_delegates: <[(Pubkey, Epoch, Epoch); MAX_PRIOR_DELEGATES]>::default(), prior_delegates: <[(Pubkey, Epoch, Epoch); MAX_PRIOR_DELEGATES]>::default(),
prior_delegates_idx: 0, prior_delegates_idx: MAX_PRIOR_DELEGATES - 1,
} }
} }
} }
@ -104,8 +104,15 @@ impl Stake {
self.stake_activating_and_deactivating(epoch, history).0 self.stake_activating_and_deactivating(epoch, history).0
} }
pub fn voter_pubkey(&self, _epoch: Epoch) -> &Pubkey { pub fn voter_pubkey(&self, epoch: Epoch) -> &Pubkey {
&self.voter_pubkey let prior_delegate_pubkey = &self.prior_delegates[self.prior_delegates_idx].0;
// next epoch from re-delegation, or no redelegations
if epoch > self.voter_pubkey_epoch || *prior_delegate_pubkey == Pubkey::default() {
&self.voter_pubkey
} else {
assert!(epoch <= self.prior_delegates[self.prior_delegates_idx].2);
prior_delegate_pubkey
}
} }
fn stake_activating_and_deactivating( fn stake_activating_and_deactivating(
@ -287,6 +294,26 @@ impl Stake {
) )
} }
fn redelegate(
&mut self,
voter_pubkey: &Pubkey,
vote_state: &VoteState,
epoch: Epoch,
) -> Result<(), StakeError> {
// remember old delegate,
if epoch != self.voter_pubkey_epoch {
self.prior_delegates_idx += 1;
self.prior_delegates_idx %= MAX_PRIOR_DELEGATES;
self.prior_delegates[self.prior_delegates_idx] =
(self.voter_pubkey, self.voter_pubkey_epoch, epoch);
}
self.voter_pubkey = *voter_pubkey;
self.voter_pubkey_epoch = epoch;
self.credits_observed = vote_state.credits();
Ok(())
}
fn new( fn new(
stake: u64, stake: u64,
voter_pubkey: &Pubkey, voter_pubkey: &Pubkey,
@ -299,6 +326,7 @@ impl Stake {
stake, stake,
activation_epoch, activation_epoch,
voter_pubkey: *voter_pubkey, voter_pubkey: *voter_pubkey,
voter_pubkey_epoch: activation_epoch,
credits_observed: vote_state.credits(), credits_observed: vote_state.credits(),
config: *config, config: *config,
lockup, lockup,
@ -316,7 +344,6 @@ pub trait StakeAccount {
fn delegate_stake( fn delegate_stake(
&mut self, &mut self,
vote_account: &KeyedAccount, vote_account: &KeyedAccount,
stake: u64,
clock: &sysvar::clock::Clock, clock: &sysvar::clock::Clock,
config: &Config, config: &Config,
) -> Result<(), InstructionError>; ) -> Result<(), InstructionError>;
@ -352,7 +379,6 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
fn delegate_stake( fn delegate_stake(
&mut self, &mut self,
vote_account: &KeyedAccount, vote_account: &KeyedAccount,
new_stake: u64,
clock: &sysvar::clock::Clock, clock: &sysvar::clock::Clock,
config: &Config, config: &Config,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
@ -360,13 +386,9 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
return Err(InstructionError::MissingRequiredSignature); return Err(InstructionError::MissingRequiredSignature);
} }
if new_stake > self.account.lamports {
return Err(InstructionError::InsufficientFunds);
}
if let StakeState::Lockup(lockup) = self.state()? { if let StakeState::Lockup(lockup) = self.state()? {
let stake = Stake::new( let stake = Stake::new(
new_stake, self.account.lamports,
vote_account.unsigned_key(), vote_account.unsigned_key(),
&vote_account.state()?, &vote_account.state()?,
clock.epoch, clock.epoch,
@ -374,6 +396,13 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
lockup, lockup,
); );
self.set_state(&StakeState::Stake(stake))
} else if let StakeState::Stake(mut stake) = self.state()? {
stake.redelegate(
vote_account.unsigned_key(),
&vote_account.state()?,
clock.epoch,
)?;
self.set_state(&StakeState::Stake(stake)) self.set_state(&StakeState::Stake(stake))
} else { } else {
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
@ -538,10 +567,7 @@ pub fn create_account(voter_pubkey: &Pubkey, vote_account: &Account, lamports: u
mod tests { mod tests {
use super::*; use super::*;
use crate::id; use crate::id;
use solana_sdk::account::Account; use solana_sdk::{account::Account, pubkey::Pubkey, system_program};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_program;
use solana_vote_api::vote_state; use solana_vote_api::vote_state;
#[test] #[test]
@ -582,13 +608,12 @@ mod tests {
..sysvar::clock::Clock::default() ..sysvar::clock::Clock::default()
}; };
let vote_keypair = Keypair::new(); let vote_pubkey = Pubkey::new_rand();
let mut vote_state = VoteState::default(); let mut vote_state = VoteState::default();
for i in 0..1000 { for i in 0..1000 {
vote_state.process_slot_vote_unchecked(i); vote_state.process_slot_vote_unchecked(i);
} }
let vote_pubkey = vote_keypair.pubkey();
let mut vote_account = let mut vote_account =
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100); vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
@ -613,66 +638,88 @@ mod tests {
} }
assert_eq!( assert_eq!(
stake_keyed_account.delegate_stake(&vote_keyed_account, 0, &clock, &Config::default()), stake_keyed_account.delegate_stake(&vote_keyed_account, &clock, &Config::default()),
Err(InstructionError::MissingRequiredSignature) Err(InstructionError::MissingRequiredSignature)
); );
// signed keyed account // signed keyed account
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert!(stake_keyed_account assert!(stake_keyed_account
.delegate_stake( .delegate_stake(&vote_keyed_account, &clock, &Config::default())
&vote_keyed_account,
stake_lamports,
&clock,
&Config::default()
)
.is_ok()); .is_ok());
// verify that delegate_stake() looks right, compare against hand-rolled // verify that delegate_stake() looks right, compare against hand-rolled
let stake_state: StakeState = stake_keyed_account.state().unwrap(); let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap();
assert_eq!( assert_eq!(
stake_state, stake,
StakeState::Stake(Stake { Stake {
voter_pubkey: vote_keypair.pubkey(), voter_pubkey: vote_pubkey,
voter_pubkey_epoch: clock.epoch,
credits_observed: vote_state.credits(), credits_observed: vote_state.credits(),
stake: stake_lamports, stake: stake_lamports,
activation_epoch: clock.epoch, activation_epoch: clock.epoch,
deactivation_epoch: std::u64::MAX, deactivation_epoch: std::u64::MAX,
..Stake::default() ..Stake::default()
}) }
);
// verify that delegate_stake can't be called twice StakeState::default()
// signed keyed account
assert_eq!(
stake_keyed_account.delegate_stake(
&vote_keyed_account,
stake_lamports,
&clock,
&Config::default()
),
Err(InstructionError::InvalidAccountData)
); );
// verify can only stake up to account lamports // verify that voter_pubkey() is right for all epochs, even ones that don't count (like 0)
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); for epoch in 0..=clock.epoch + 1 {
assert_eq!( assert_eq!(stake.voter_pubkey(epoch), &vote_pubkey);
stake_keyed_account.delegate_stake( }
&vote_keyed_account,
stake_lamports + 1,
&clock,
&Config::default()
),
Err(InstructionError::InsufficientFunds)
);
// verify that delegate_stake can be called twice, 2nd is redelegate
assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, &clock, &Config::default())
.is_ok());
// verify that non-stakes fail delegate_stake()
let stake_state = StakeState::RewardsPool; let stake_state = StakeState::RewardsPool;
stake_keyed_account.set_state(&stake_state).unwrap(); stake_keyed_account.set_state(&stake_state).unwrap();
assert!(stake_keyed_account assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, 0, &clock, &Config::default()) .delegate_stake(&vote_keyed_account, &clock, &Config::default())
.is_err()); .is_err());
} }
#[test]
fn test_stake_redelegate() {
// what a freshly delegated stake looks like
let mut stake = Stake {
voter_pubkey: Pubkey::new_rand(),
voter_pubkey_epoch: 0,
..Stake::default()
};
// verify that redelegation any number of times since first delegation works just fine,
// and that the stake is delegated to the most recent vote account
for epoch in 0..=MAX_PRIOR_DELEGATES + 1 {
let voter_pubkey = Pubkey::new_rand();
let _ignored = stake.redelegate(&voter_pubkey, &VoteState::default(), 0);
assert_eq!(stake.voter_pubkey(epoch as u64), &voter_pubkey);
}
// get a new voter_pubkey
let voter_pubkey = Pubkey::new_rand();
// save off old voter_pubkey
let prior_voter_pubkey = stake.voter_pubkey;
// actually redelegate in epoch 1
let _ignored = stake.redelegate(&voter_pubkey, &VoteState::default(), 1);
// verify that delegation is delayed
assert_eq!(stake.voter_pubkey(0 as u64), &prior_voter_pubkey);
assert_eq!(stake.voter_pubkey(1 as u64), &prior_voter_pubkey);
assert_eq!(stake.voter_pubkey(2 as u64), &voter_pubkey);
// verify that prior_delegates wraps around safely...
for epoch in 0..=MAX_PRIOR_DELEGATES + 1 {
let voter_pubkey = Pubkey::new_rand();
let prior_voter_pubkey = stake.voter_pubkey;
let _ignored = stake.redelegate(&voter_pubkey, &VoteState::default(), epoch as u64);
assert_eq!(stake.voter_pubkey(epoch as u64), &prior_voter_pubkey);
assert_eq!(stake.voter_pubkey((epoch + 1) as u64), &voter_pubkey);
}
}
fn create_stake_history_from_stakes( fn create_stake_history_from_stakes(
bootstrap: Option<u64>, bootstrap: Option<u64>,
epochs: std::ops::Range<Epoch>, epochs: std::ops::Range<Epoch>,
@ -986,12 +1033,7 @@ mod tests {
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&VoteState::default()).unwrap(); vote_keyed_account.set_state(&VoteState::default()).unwrap();
assert_eq!( assert_eq!(
stake_keyed_account.delegate_stake( stake_keyed_account.delegate_stake(&vote_keyed_account, &clock, &Config::default()),
&vote_keyed_account,
stake_lamports,
&clock,
&Config::default()
),
Ok(()) Ok(())
); );
@ -1005,10 +1047,9 @@ mod tests {
#[test] #[test]
fn test_withdraw_stake() { fn test_withdraw_stake() {
let stake_pubkey = Pubkey::new_rand(); let stake_pubkey = Pubkey::new_rand();
let total_lamports = 100;
let stake_lamports = 42; let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space( let mut stake_account = Account::new_data_with_space(
total_lamports, stake_lamports,
&StakeState::Lockup(0), &StakeState::Lockup(0),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
@ -1025,7 +1066,7 @@ mod tests {
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
assert_eq!( assert_eq!(
stake_keyed_account.withdraw( stake_keyed_account.withdraw(
total_lamports, stake_lamports,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default()
@ -1037,7 +1078,7 @@ mod tests {
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!( assert_eq!(
stake_keyed_account.withdraw( stake_keyed_account.withdraw(
total_lamports, stake_lamports,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default()
@ -1047,13 +1088,13 @@ mod tests {
assert_eq!(stake_account.lamports, 0); assert_eq!(stake_account.lamports, 0);
// reset balance // reset balance
stake_account.lamports = total_lamports; stake_account.lamports = stake_lamports;
// signed keyed account and uninitialized, more than available should fail // signed keyed account and uninitialized, more than available should fail
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!( assert_eq!(
stake_keyed_account.withdraw( stake_keyed_account.withdraw(
total_lamports + 1, stake_lamports + 1,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default()
@ -1061,41 +1102,38 @@ mod tests {
Err(InstructionError::InsufficientFunds) Err(InstructionError::InsufficientFunds)
); );
// Stake some lamports (available lampoorts for withdrawals will reduce) // Stake some lamports (available lamports for withdrawals will reduce to zero)
let vote_pubkey = Pubkey::new_rand(); let vote_pubkey = Pubkey::new_rand();
let mut vote_account = let mut vote_account =
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100); vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&VoteState::default()).unwrap(); vote_keyed_account.set_state(&VoteState::default()).unwrap();
assert_eq!( assert_eq!(
stake_keyed_account.delegate_stake( stake_keyed_account.delegate_stake(&vote_keyed_account, &clock, &Config::default()),
&vote_keyed_account,
stake_lamports,
&clock,
&Config::default()
),
Ok(()) Ok(())
); );
// withdrawal before deactivate works for some portion // simulate rewards
stake_account.lamports += 10;
// withdrawal before deactivate works for rewards amount
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!( assert_eq!(
stake_keyed_account.withdraw( stake_keyed_account.withdraw(
total_lamports - stake_lamports, 10,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default()
), ),
Ok(()) Ok(())
); );
// reset balance
stake_account.lamports = total_lamports;
// withdrawal before deactivate fails if not in excess of stake // simulate rewards
stake_account.lamports += 10;
// withdrawal of rewards fails if not in excess of stake
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!( assert_eq!(
stake_keyed_account.withdraw( stake_keyed_account.withdraw(
total_lamports - stake_lamports + 1, 10 + 1,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default()
@ -1114,7 +1152,7 @@ mod tests {
// Try to withdraw more than what's available // Try to withdraw more than what's available
assert_eq!( assert_eq!(
stake_keyed_account.withdraw( stake_keyed_account.withdraw(
total_lamports + 1, stake_lamports + 10 + 1,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default()
@ -1125,7 +1163,7 @@ mod tests {
// Try to withdraw all lamports // Try to withdraw all lamports
assert_eq!( assert_eq!(
stake_keyed_account.withdraw( stake_keyed_account.withdraw(
total_lamports, stake_lamports + 10,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default()
@ -1165,12 +1203,7 @@ mod tests {
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
vote_keyed_account.set_state(&VoteState::default()).unwrap(); vote_keyed_account.set_state(&VoteState::default()).unwrap();
assert_eq!( assert_eq!(
stake_keyed_account.delegate_stake( stake_keyed_account.delegate_stake(&vote_keyed_account, &future, &Config::default()),
&vote_keyed_account,
stake_lamports,
&future,
&Config::default()
),
Ok(()) Ok(())
); );
@ -1382,12 +1415,7 @@ mod tests {
// delegate the stake // delegate the stake
assert!(stake_keyed_account assert!(stake_keyed_account
.delegate_stake( .delegate_stake(&vote_keyed_account, &clock, &Config::default())
&vote_keyed_account,
stake_lamports,
&clock,
&Config::default()
)
.is_ok()); .is_ok());
let stake_history = create_stake_history_from_stakes( let stake_history = create_stake_history_from_stakes(