Add authorize_staker functionality (#5880)

* Add authorized_staker functionality

* Generalize authorize names; implement for Lockup

* Fix authorize() usage and improve tests
This commit is contained in:
Tyera Eulberg 2019-09-12 20:03:28 -06:00 committed by GitHub
parent 8f5a1535af
commit 5dceeec1ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 420 additions and 66 deletions

View File

@ -45,7 +45,11 @@ pub enum StakeInstruction {
/// will allow withdrawal from the stake account. /// will allow withdrawal from the stake account.
/// ///
Lockup(Slot), Lockup(Slot),
/// Authorize a system account to manage stake
///
/// Expects 1 Account:
/// 0 - Locked-up or delegated StakeAccount to be updated with authorized staker
Authorize(Pubkey),
/// `Delegate` a stake to a particular vote account /// `Delegate` a stake to a particular vote account
/// ///
/// Expects 4 Accounts: /// Expects 4 Accounts:
@ -133,6 +137,42 @@ pub fn create_stake_account_and_delegate_stake(
instructions instructions
} }
fn metas_for_authorized_staker(
stake_pubkey: &Pubkey,
authorized_pubkey: &Pubkey, // currently authorized
other_params: &[AccountMeta],
) -> Vec<AccountMeta> {
let is_own_signer = authorized_pubkey == stake_pubkey;
// stake account
let mut account_metas = vec![AccountMeta::new(*stake_pubkey, is_own_signer)];
for meta in other_params {
account_metas.push(meta.clone());
}
// append signer at the end
if !is_own_signer {
account_metas.push(AccountMeta::new_credit_only(*authorized_pubkey, true)) // signer
}
account_metas
}
pub fn authorize(
stake_pubkey: &Pubkey,
authorized_pubkey: &Pubkey,
new_authorized_pubkey: &Pubkey,
) -> Instruction {
let account_metas = metas_for_authorized_staker(stake_pubkey, authorized_pubkey, &[]);
Instruction::new(
id(),
&StakeInstruction::Authorize(*new_authorized_pubkey),
account_metas,
)
}
pub fn redeem_vote_credits(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instruction { pub fn redeem_vote_credits(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instruction {
let account_metas = vec![ let account_metas = vec![
AccountMeta::new(*stake_pubkey, false), AccountMeta::new(*stake_pubkey, false),
@ -193,8 +233,9 @@ 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::Authorize(authorized_pubkey) => me.authorize(&authorized_pubkey, &rest),
StakeInstruction::DelegateStake => { StakeInstruction::DelegateStake => {
if rest.len() != 3 { if rest.len() < 3 {
Err(InstructionError::InvalidInstructionData)?; Err(InstructionError::InvalidInstructionData)?;
} }
let vote = &rest[0]; let vote = &rest[0];
@ -203,6 +244,7 @@ pub fn process_instruction(
vote, vote,
&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])?,
&rest[3..],
) )
} }
StakeInstruction::RedeemVoteCredits => { StakeInstruction::RedeemVoteCredits => {
@ -222,28 +264,32 @@ pub fn process_instruction(
) )
} }
StakeInstruction::Withdraw(lamports) => { StakeInstruction::Withdraw(lamports) => {
if rest.len() != 3 { if rest.len() < 3 {
Err(InstructionError::InvalidInstructionData)?; Err(InstructionError::InvalidInstructionData)?;
} }
let (to, sysvar) = &mut rest.split_at_mut(1); let (to, rest) = &mut rest.split_at_mut(1);
let mut to = &mut to[0]; let mut to = &mut to[0];
me.withdraw( me.withdraw(
lamports, lamports,
&mut to, &mut to,
&sysvar::clock::from_keyed_account(&sysvar[0])?, &sysvar::clock::from_keyed_account(&rest[0])?,
&sysvar::stake_history::from_keyed_account(&sysvar[1])?, &sysvar::stake_history::from_keyed_account(&rest[1])?,
&rest[2..],
) )
} }
StakeInstruction::Deactivate => { StakeInstruction::Deactivate => {
if rest.len() != 2 { if rest.len() < 2 {
Err(InstructionError::InvalidInstructionData)?; Err(InstructionError::InvalidInstructionData)?;
} }
let (vote, rest) = rest.split_at_mut(1); let (vote, rest) = rest.split_at_mut(1);
let vote = &mut vote[0]; let vote = &mut vote[0];
let clock = &rest[0];
me.deactivate_stake(vote, &sysvar::clock::from_keyed_account(&clock)?) me.deactivate_stake(
vote,
&sysvar::clock::from_keyed_account(&rest[0])?,
&rest[1..],
)
} }
} }
} }

View File

@ -22,7 +22,7 @@ use solana_vote_api::vote_state::VoteState;
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum StakeState { pub enum StakeState {
Uninitialized, Uninitialized,
Lockup(Slot), Lockup(Lockup),
Stake(Stake), Stake(Stake),
RewardsPool, RewardsPool,
} }
@ -51,8 +51,18 @@ impl StakeState {
} }
} }
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct Lockup {
/// slot height at which this stake will allow withdrawal
pub slot: Slot,
/// alternate signer that is enabled to act on the Stake account after lockup
pub authorized_pubkey: Pubkey,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct Stake { pub struct Stake {
/// alternate signer that is enabled to act on the Stake account
pub authorized_pubkey: Pubkey,
/// most recently delegated vote account pubkey /// most recently delegated vote account pubkey
pub voter_pubkey: Pubkey, pub voter_pubkey: Pubkey,
/// the epoch when voter_pubkey was most recently set /// the epoch when voter_pubkey was most recently set
@ -81,6 +91,7 @@ const MAX_PRIOR_DELEGATES: usize = 32; // this is how many epochs a stake is exp
impl Default for Stake { impl Default for Stake {
fn default() -> Self { fn default() -> Self {
Self { Self {
authorized_pubkey: Pubkey::default(),
voter_pubkey: Pubkey::default(), voter_pubkey: Pubkey::default(),
voter_pubkey_epoch: 0, voter_pubkey_epoch: 0,
credits_observed: 0, credits_observed: 0,
@ -286,6 +297,7 @@ impl Stake {
fn new_bootstrap(stake: u64, voter_pubkey: &Pubkey, vote_state: &VoteState) -> Self { fn new_bootstrap(stake: u64, voter_pubkey: &Pubkey, vote_state: &VoteState) -> Self {
Self::new( Self::new(
stake, stake,
&Pubkey::default(),
voter_pubkey, voter_pubkey,
vote_state, vote_state,
std::u64::MAX, std::u64::MAX,
@ -316,6 +328,7 @@ impl Stake {
fn new( fn new(
stake: u64, stake: u64,
stake_pubkey: &Pubkey,
voter_pubkey: &Pubkey, voter_pubkey: &Pubkey,
vote_state: &VoteState, vote_state: &VoteState,
activation_epoch: Epoch, activation_epoch: Epoch,
@ -325,6 +338,7 @@ impl Stake {
Self { Self {
stake, stake,
activation_epoch, activation_epoch,
authorized_pubkey: *stake_pubkey,
voter_pubkey: *voter_pubkey, voter_pubkey: *voter_pubkey,
voter_pubkey_epoch: activation_epoch, voter_pubkey_epoch: activation_epoch,
credits_observed: vote_state.credits(), credits_observed: vote_state.credits(),
@ -339,18 +353,69 @@ impl Stake {
} }
} }
trait Authorized {
fn check_authorized(
&self,
stake_pubkey_signer: Option<&Pubkey>,
other_signers: &[KeyedAccount],
) -> Result<(), InstructionError>;
}
impl Authorized for Lockup {
fn check_authorized(
&self,
stake_pubkey_signer: Option<&Pubkey>,
other_signers: &[KeyedAccount],
) -> Result<(), InstructionError> {
let authorized = Some(&self.authorized_pubkey);
if stake_pubkey_signer != authorized
&& other_signers
.iter()
.all(|account| account.signer_key() != authorized)
{
return Err(InstructionError::MissingRequiredSignature);
}
Ok(())
}
}
impl Authorized for Stake {
fn check_authorized(
&self,
stake_pubkey_signer: Option<&Pubkey>,
other_signers: &[KeyedAccount],
) -> Result<(), InstructionError> {
let authorized = Some(&self.authorized_pubkey);
if stake_pubkey_signer != authorized
&& other_signers
.iter()
.all(|account| account.signer_key() != authorized)
{
return Err(InstructionError::MissingRequiredSignature);
}
Ok(())
}
}
pub trait StakeAccount { pub trait StakeAccount {
fn lockup(&mut self, slot: Slot) -> Result<(), InstructionError>; fn lockup(&mut self, slot: Slot) -> Result<(), InstructionError>;
fn authorize(
&mut self,
authorized_pubkey: &Pubkey,
other_signers: &[KeyedAccount],
) -> Result<(), InstructionError>;
fn delegate_stake( fn delegate_stake(
&mut self, &mut self,
vote_account: &KeyedAccount, vote_account: &KeyedAccount,
clock: &sysvar::clock::Clock, clock: &sysvar::clock::Clock,
config: &Config, config: &Config,
other_signers: &[KeyedAccount],
) -> Result<(), InstructionError>; ) -> Result<(), InstructionError>;
fn deactivate_stake( fn deactivate_stake(
&mut self, &mut self,
vote_account: &KeyedAccount, vote_account: &KeyedAccount,
clock: &sysvar::clock::Clock, clock: &sysvar::clock::Clock,
other_signers: &[KeyedAccount],
) -> Result<(), InstructionError>; ) -> Result<(), InstructionError>;
fn redeem_vote_credits( fn redeem_vote_credits(
&mut self, &mut self,
@ -365,12 +430,37 @@ pub trait StakeAccount {
to: &mut KeyedAccount, to: &mut KeyedAccount,
clock: &sysvar::clock::Clock, clock: &sysvar::clock::Clock,
stake_history: &sysvar::stake_history::StakeHistory, stake_history: &sysvar::stake_history::StakeHistory,
other_signers: &[KeyedAccount],
) -> Result<(), InstructionError>; ) -> Result<(), InstructionError>;
} }
impl<'a> StakeAccount for KeyedAccount<'a> { impl<'a> StakeAccount for KeyedAccount<'a> {
fn lockup(&mut self, lockup: Slot) -> Result<(), InstructionError> { fn lockup(&mut self, lockup: Slot) -> Result<(), InstructionError> {
if let StakeState::Uninitialized = self.state()? { if let StakeState::Uninitialized = self.state()? {
self.set_state(&StakeState::Lockup(Lockup {
slot: lockup,
authorized_pubkey: *self.unsigned_key(),
}))
} else {
Err(InstructionError::InvalidAccountData)
}
}
/// Authorize the given pubkey to manage stake (deactivate, withdraw). This may be called
/// multiple times, but will implicitly withdraw authorization from the previously authorized
/// staker. The default staker is the owner of the stake account's pubkey.
fn authorize(
&mut self,
authorized_pubkey: &Pubkey,
other_signers: &[KeyedAccount],
) -> Result<(), InstructionError> {
let stake_state = self.state()?;
if let StakeState::Stake(mut stake) = stake_state {
stake.check_authorized(self.signer_key(), other_signers)?;
stake.authorized_pubkey = *authorized_pubkey;
self.set_state(&StakeState::Stake(stake))
} else if let StakeState::Lockup(mut lockup) = stake_state {
lockup.check_authorized(self.signer_key(), other_signers)?;
lockup.authorized_pubkey = *authorized_pubkey;
self.set_state(&StakeState::Lockup(lockup)) self.set_state(&StakeState::Lockup(lockup))
} else { } else {
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
@ -381,23 +471,23 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
vote_account: &KeyedAccount, vote_account: &KeyedAccount,
clock: &sysvar::clock::Clock, clock: &sysvar::clock::Clock,
config: &Config, config: &Config,
other_signers: &[KeyedAccount],
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if self.signer_key().is_none() {
return Err(InstructionError::MissingRequiredSignature);
}
if let StakeState::Lockup(lockup) = self.state()? { if let StakeState::Lockup(lockup) = self.state()? {
lockup.check_authorized(self.signer_key(), other_signers)?;
let stake = Stake::new( let stake = Stake::new(
self.account.lamports, self.account.lamports,
&lockup.authorized_pubkey,
vote_account.unsigned_key(), vote_account.unsigned_key(),
&vote_account.state()?, &vote_account.state()?,
clock.epoch, clock.epoch,
config, config,
lockup, lockup.slot,
); );
self.set_state(&StakeState::Stake(stake)) self.set_state(&StakeState::Stake(stake))
} else if let StakeState::Stake(mut stake) = self.state()? { } else if let StakeState::Stake(mut stake) = self.state()? {
stake.check_authorized(self.signer_key(), other_signers)?;
stake.redelegate( stake.redelegate(
vote_account.unsigned_key(), vote_account.unsigned_key(),
&vote_account.state()?, &vote_account.state()?,
@ -412,12 +502,10 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
&mut self, &mut self,
_vote_account: &KeyedAccount, // TODO: used in slashing _vote_account: &KeyedAccount, // TODO: used in slashing
clock: &sysvar::clock::Clock, clock: &sysvar::clock::Clock,
other_signers: &[KeyedAccount],
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if self.signer_key().is_none() {
return Err(InstructionError::MissingRequiredSignature);
}
if let StakeState::Stake(mut stake) = self.state()? { if let StakeState::Stake(mut stake) = self.state()? {
stake.check_authorized(self.signer_key(), other_signers)?;
stake.deactivate(clock.epoch); stake.deactivate(clock.epoch);
self.set_state(&StakeState::Stake(stake)) self.set_state(&StakeState::Stake(stake))
@ -475,11 +563,8 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
to: &mut KeyedAccount, to: &mut KeyedAccount,
clock: &sysvar::clock::Clock, clock: &sysvar::clock::Clock,
stake_history: &sysvar::stake_history::StakeHistory, stake_history: &sysvar::stake_history::StakeHistory,
other_signers: &[KeyedAccount],
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if self.signer_key().is_none() {
return Err(InstructionError::MissingRequiredSignature);
}
fn transfer( fn transfer(
from: &mut Account, from: &mut Account,
to: &mut Account, to: &mut Account,
@ -495,6 +580,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
match self.state()? { match self.state()? {
StakeState::Stake(stake) => { StakeState::Stake(stake) => {
stake.check_authorized(self.signer_key(), other_signers)?;
// if we have a deactivation epoch and we're in cooldown // if we have a deactivation epoch and we're in cooldown
let staked = if clock.epoch >= stake.deactivation_epoch { let staked = if clock.epoch >= stake.deactivation_epoch {
stake.stake(clock.epoch, Some(stake_history)) stake.stake(clock.epoch, Some(stake_history))
@ -510,11 +596,16 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
} }
} }
StakeState::Lockup(lockup) => { StakeState::Lockup(lockup) => {
if lockup > clock.slot { lockup.check_authorized(self.signer_key(), other_signers)?;
if lockup.slot > clock.slot {
return Err(InstructionError::InsufficientFunds); return Err(InstructionError::InsufficientFunds);
} }
} }
StakeState::Uninitialized => {} StakeState::Uninitialized => {
if self.signer_key().is_none() {
return Err(InstructionError::MissingRequiredSignature);
}
}
_ => return Err(InstructionError::InvalidAccountData), _ => return Err(InstructionError::InvalidAccountData),
} }
transfer(&mut self.account, &mut to.account, lamports) transfer(&mut self.account, &mut to.account, lamports)
@ -623,7 +714,10 @@ mod tests {
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(
stake_lamports, stake_lamports,
&StakeState::Lockup(0), &StakeState::Lockup(Lockup {
slot: 0,
authorized_pubkey: stake_pubkey,
}),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@ -634,18 +728,29 @@ mod tests {
{ {
let stake_state: StakeState = stake_keyed_account.state().unwrap(); let stake_state: StakeState = stake_keyed_account.state().unwrap();
assert_eq!(stake_state, StakeState::Lockup(0)); assert_eq!(
stake_state,
StakeState::Lockup(Lockup {
slot: 0,
authorized_pubkey: stake_pubkey
})
);
} }
assert_eq!( assert_eq!(
stake_keyed_account.delegate_stake(&vote_keyed_account, &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(&vote_keyed_account, &clock, &Config::default()) .delegate_stake(&vote_keyed_account, &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
@ -653,6 +758,7 @@ mod tests {
assert_eq!( assert_eq!(
stake, stake,
Stake { Stake {
authorized_pubkey: stake_pubkey,
voter_pubkey: vote_pubkey, voter_pubkey: vote_pubkey,
voter_pubkey_epoch: clock.epoch, voter_pubkey_epoch: clock.epoch,
credits_observed: vote_state.credits(), credits_observed: vote_state.credits(),
@ -670,7 +776,7 @@ mod tests {
// verify that delegate_stake can be called twice, 2nd is redelegate // verify that delegate_stake can be called twice, 2nd is redelegate
assert!(stake_keyed_account assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, &clock, &Config::default()) .delegate_stake(&vote_keyed_account, &clock, &Config::default(), &[])
.is_ok()); .is_ok());
// verify that non-stakes fail delegate_stake() // verify that non-stakes fail delegate_stake()
@ -678,7 +784,7 @@ mod tests {
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, &clock, &Config::default()) .delegate_stake(&vote_keyed_account, &clock, &Config::default(), &[])
.is_err()); .is_err());
} }
@ -980,7 +1086,10 @@ mod tests {
// first time works, as is uninit // first time works, as is uninit
assert_eq!( assert_eq!(
StakeState::from(&stake_keyed_account.account).unwrap(), StakeState::from(&stake_keyed_account.account).unwrap(),
StakeState::Lockup(1) StakeState::Lockup(Lockup {
slot: 1,
authorized_pubkey: stake_pubkey
})
); );
// 2nd time fails, can't move it from anything other than uninit->lockup // 2nd time fails, can't move it from anything other than uninit->lockup
@ -996,7 +1105,10 @@ mod tests {
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(
stake_lamports, stake_lamports,
&StakeState::Lockup(0), &StakeState::Lockup(Lockup {
slot: 0,
authorized_pubkey: stake_pubkey,
}),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@ -1012,17 +1124,10 @@ mod tests {
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100); vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
// unsigned keyed account
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
assert_eq!(
stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock),
Err(InstructionError::MissingRequiredSignature)
);
// signed keyed account but not staked yet // signed keyed account but not staked yet
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.deactivate_stake(&vote_keyed_account, &clock), stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock, &[]),
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
); );
@ -1033,13 +1138,26 @@ 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(&vote_keyed_account, &clock, &Config::default()), stake_keyed_account.delegate_stake(
&vote_keyed_account,
&clock,
&Config::default(),
&[]
),
Ok(()) Ok(())
); );
// Deactivate after staking // unsigned keyed account
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
assert_eq!( assert_eq!(
stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock), stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock, &[]),
Err(InstructionError::MissingRequiredSignature)
);
// Deactivate after staking
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert_eq!(
stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock, &[]),
Ok(()) Ok(())
); );
} }
@ -1050,7 +1168,7 @@ mod tests {
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(
stake_lamports, stake_lamports,
&StakeState::Lockup(0), &StakeState::Uninitialized,
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@ -1069,7 +1187,8 @@ mod tests {
stake_lamports, stake_lamports,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default(),
&[],
), ),
Err(InstructionError::MissingRequiredSignature) Err(InstructionError::MissingRequiredSignature)
); );
@ -1081,7 +1200,8 @@ mod tests {
stake_lamports, stake_lamports,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default(),
&[],
), ),
Ok(()) Ok(())
); );
@ -1090,14 +1210,19 @@ mod tests {
// reset balance // reset balance
stake_account.lamports = stake_lamports; stake_account.lamports = stake_lamports;
// signed keyed account and uninitialized, more than available should fail // lockup
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
stake_keyed_account.lockup(0).unwrap();
// signed keyed account and locked up, 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(
stake_lamports + 1, stake_lamports + 1,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default(),
&[],
), ),
Err(InstructionError::InsufficientFunds) Err(InstructionError::InsufficientFunds)
); );
@ -1109,7 +1234,12 @@ 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(&vote_keyed_account, &clock, &Config::default()), stake_keyed_account.delegate_stake(
&vote_keyed_account,
&clock,
&Config::default(),
&[]
),
Ok(()) Ok(())
); );
@ -1122,7 +1252,8 @@ mod tests {
10, 10,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default(),
&[],
), ),
Ok(()) Ok(())
); );
@ -1136,14 +1267,15 @@ mod tests {
10 + 1, 10 + 1,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default(),
&[],
), ),
Err(InstructionError::InsufficientFunds) Err(InstructionError::InsufficientFunds)
); );
// deactivate the stake before withdrawal // deactivate the stake before withdrawal
assert_eq!( assert_eq!(
stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock), stake_keyed_account.deactivate_stake(&vote_keyed_account, &clock, &[]),
Ok(()) Ok(())
); );
// simulate time passing // simulate time passing
@ -1155,7 +1287,8 @@ mod tests {
stake_lamports + 10 + 1, stake_lamports + 10 + 1,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default(),
&[],
), ),
Err(InstructionError::InsufficientFunds) Err(InstructionError::InsufficientFunds)
); );
@ -1166,7 +1299,8 @@ mod tests {
stake_lamports + 10, stake_lamports + 10,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default(),
&[],
), ),
Ok(()) Ok(())
); );
@ -1180,7 +1314,10 @@ mod tests {
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, total_lamports,
&StakeState::Lockup(0), &StakeState::Lockup(Lockup {
slot: 0,
authorized_pubkey: stake_pubkey,
}),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@ -1203,7 +1340,12 @@ 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(&vote_keyed_account, &future, &Config::default()), stake_keyed_account.delegate_stake(
&vote_keyed_account,
&future,
&Config::default(),
&[]
),
Ok(()) Ok(())
); );
@ -1219,7 +1361,8 @@ mod tests {
total_lamports - stake_lamports + 1, total_lamports - stake_lamports + 1,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&stake_history &stake_history,
&[],
), ),
Err(InstructionError::InsufficientFunds) Err(InstructionError::InsufficientFunds)
); );
@ -1247,7 +1390,8 @@ mod tests {
total_lamports, total_lamports,
&mut to_keyed_account, &mut to_keyed_account,
&sysvar::clock::Clock::default(), &sysvar::clock::Clock::default(),
&StakeHistory::default() &StakeHistory::default(),
&[],
), ),
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
); );
@ -1259,7 +1403,10 @@ mod tests {
let total_lamports = 100; let total_lamports = 100;
let mut stake_account = Account::new_data_with_space( let mut stake_account = Account::new_data_with_space(
total_lamports, total_lamports,
&StakeState::Lockup(1), &StakeState::Lockup(Lockup {
slot: 1,
authorized_pubkey: stake_pubkey,
}),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@ -1277,7 +1424,8 @@ mod tests {
total_lamports, total_lamports,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default(),
&[],
), ),
Err(InstructionError::InsufficientFunds) Err(InstructionError::InsufficientFunds)
); );
@ -1288,7 +1436,8 @@ mod tests {
total_lamports, total_lamports,
&mut to_keyed_account, &mut to_keyed_account,
&clock, &clock,
&StakeHistory::default() &StakeHistory::default(),
&[],
), ),
Ok(()) Ok(())
); );
@ -1385,17 +1534,20 @@ mod tests {
let mut rewards_pool_keyed_account = let mut rewards_pool_keyed_account =
KeyedAccount::new(&rewards_pool_pubkey, false, &mut rewards_pool_account); KeyedAccount::new(&rewards_pool_pubkey, false, &mut rewards_pool_account);
let pubkey = Pubkey::default(); let stake_pubkey = Pubkey::default();
let stake_lamports = 100; let stake_lamports = 100;
let mut stake_account = Account::new_data_with_space( let mut stake_account = Account::new_data_with_space(
stake_lamports, stake_lamports,
&StakeState::Lockup(0), &StakeState::Lockup(Lockup {
slot: 0,
authorized_pubkey: stake_pubkey,
}),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
.expect("stake_account"); .expect("stake_account");
let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let vote_pubkey = Pubkey::new_rand(); let vote_pubkey = Pubkey::new_rand();
let mut vote_account = let mut vote_account =
@ -1415,7 +1567,7 @@ mod tests {
// delegate the stake // delegate the stake
assert!(stake_keyed_account assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account, &clock, &Config::default()) .delegate_stake(&vote_keyed_account, &clock, &Config::default(), &[])
.is_ok()); .is_ok());
let stake_history = create_stake_history_from_stakes( let stake_history = create_stake_history_from_stakes(
@ -1499,4 +1651,160 @@ mod tests {
); );
} }
#[test]
fn test_authorize_lockup() {
let stake_pubkey = Pubkey::new_rand();
let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space(
stake_lamports,
&StakeState::Lockup(Lockup {
slot: 0,
authorized_pubkey: stake_pubkey,
}),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let to = Pubkey::new_rand();
let mut to_account = Account::new(1, 0, &system_program::id());
let mut to_keyed_account = KeyedAccount::new(&to, false, &mut to_account);
let clock = sysvar::clock::Clock::default();
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
let stake_pubkey0 = Pubkey::new_rand();
assert_eq!(stake_keyed_account.authorize(&stake_pubkey0, &[]), Ok(()));
if let StakeState::Lockup(lockup) = StakeState::from(&stake_keyed_account.account).unwrap()
{
assert_eq!(lockup.authorized_pubkey, stake_pubkey0);
}
// A second authorization signed by the stake_keyed_account should fail
let stake_pubkey1 = Pubkey::new_rand();
assert_eq!(
stake_keyed_account.authorize(&stake_pubkey1, &[]),
Err(InstructionError::MissingRequiredSignature)
);
let mut staker_account0 = Account::new(1, 0, &system_program::id());
let staker_keyed_account0 = KeyedAccount::new(&stake_pubkey0, true, &mut staker_account0);
// Test a second authorization by the newly authorized pubkey
let stake_pubkey2 = Pubkey::new_rand();
assert_eq!(
stake_keyed_account.authorize(&stake_pubkey2, &[staker_keyed_account0]),
Ok(())
);
if let StakeState::Lockup(lockup) = StakeState::from(&stake_keyed_account.account).unwrap()
{
assert_eq!(lockup.authorized_pubkey, stake_pubkey2);
}
let mut staker_account2 = Account::new(1, 0, &system_program::id());
let staker_keyed_account2 = KeyedAccount::new(&stake_pubkey2, true, &mut staker_account2);
// Test an action by the currently authorized pubkey
assert_eq!(
stake_keyed_account.withdraw(
stake_lamports,
&mut to_keyed_account,
&clock,
&StakeHistory::default(),
&[staker_keyed_account2],
),
Ok(())
);
}
#[test]
fn test_authorize_delegated_stake() {
let stake_pubkey = Pubkey::new_rand();
let stake_lamports = 42;
let mut stake_account = Account::new_data_with_space(
stake_lamports,
&StakeState::Lockup(Lockup {
slot: 0,
authorized_pubkey: stake_pubkey,
}),
std::mem::size_of::<StakeState>(),
&id(),
)
.expect("stake_account");
let clock = sysvar::clock::Clock::default();
let vote_pubkey = Pubkey::new_rand();
let mut vote_account =
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
stake_keyed_account
.delegate_stake(&vote_keyed_account, &clock, &Config::default(), &[])
.unwrap();
let new_staker_pubkey = Pubkey::new_rand();
assert_eq!(
stake_keyed_account.authorize(&new_staker_pubkey, &[]),
Ok(())
);
let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap();
assert_eq!(stake.authorized_pubkey, new_staker_pubkey);
let other_pubkey = Pubkey::new_rand();
let mut other_account = Account::new(1, 0, &system_program::id());
let other_keyed_account = KeyedAccount::new(&other_pubkey, true, &mut other_account);
// Use unsigned stake_keyed_account to test other signers
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
let new_voter_pubkey = Pubkey::new_rand();
let vote_state = VoteState::default();
let mut new_vote_account =
vote_state::create_account(&new_voter_pubkey, &Pubkey::new_rand(), 0, 100);
let mut new_vote_keyed_account =
KeyedAccount::new(&new_voter_pubkey, false, &mut new_vote_account);
new_vote_keyed_account.set_state(&vote_state).unwrap();
// Random other account should fail
assert_eq!(
stake_keyed_account.delegate_stake(
&new_vote_keyed_account,
&clock,
&Config::default(),
&[other_keyed_account]
),
Err(InstructionError::MissingRequiredSignature)
);
let mut new_staker_account = Account::new(1, 0, &system_program::id());
let new_staker_keyed_account =
KeyedAccount::new(&new_staker_pubkey, true, &mut new_staker_account);
// Authorized staker should succeed
assert_eq!(
stake_keyed_account.delegate_stake(
&new_vote_keyed_account,
&clock,
&Config::default(),
&[new_staker_keyed_account]
),
Ok(())
);
let stake = StakeState::stake_from(&stake_keyed_account.account).unwrap();
assert_eq!(stake.voter_pubkey(0), &new_voter_pubkey);
// Test another staking action
let new_staker_keyed_account =
KeyedAccount::new(&new_staker_pubkey, true, &mut new_staker_account);
assert_eq!(
stake_keyed_account.deactivate_stake(
&vote_keyed_account,
&clock,
&[new_staker_keyed_account]
),
Ok(())
);
}
} }