redelegate stake (#5868)
* redelegate stake * boil this down to just delegate(), which can be offered any number of times
This commit is contained in:
parent
1853771930
commit
92d2452f33
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Reference in New Issue