parent
3a616de47b
commit
bc88180058
|
@ -20,7 +20,7 @@ use solana_sdk::{
|
|||
};
|
||||
use solana_stake_api::{
|
||||
stake_instruction::{self, StakeError},
|
||||
stake_state::{Authorized, Lockup, StakeAuthorize, StakeState},
|
||||
stake_state::{Authorized, Lockup, Meta, StakeAuthorize, StakeState},
|
||||
};
|
||||
use solana_vote_api::vote_state::VoteState;
|
||||
use std::ops::Deref;
|
||||
|
@ -548,7 +548,12 @@ pub fn process_show_stake_account(
|
|||
println!("lockup custodian: {}", lockup.custodian);
|
||||
}
|
||||
match stake_account.state() {
|
||||
Ok(StakeState::Stake(authorized, lockup, stake)) => {
|
||||
Ok(StakeState::Stake(
|
||||
Meta {
|
||||
authorized, lockup, ..
|
||||
},
|
||||
stake,
|
||||
)) => {
|
||||
println!(
|
||||
"total stake: {}",
|
||||
build_balance_message(stake_account.lamports, use_lamports_unit, true)
|
||||
|
@ -577,7 +582,9 @@ pub fn process_show_stake_account(
|
|||
}
|
||||
Ok(StakeState::RewardsPool) => Ok("Stake account is a rewards pool".to_string()),
|
||||
Ok(StakeState::Uninitialized) => Ok("Stake account is uninitialized".to_string()),
|
||||
Ok(StakeState::Initialized(authorized, lockup)) => {
|
||||
Ok(StakeState::Initialized(Meta {
|
||||
authorized, lockup, ..
|
||||
})) => {
|
||||
println!("Stake account is undelegated");
|
||||
show_authorized(&authorized);
|
||||
show_lockup(&lockup);
|
||||
|
|
|
@ -11,7 +11,6 @@ use solana_sdk::{
|
|||
instruction_processor_utils::{limited_deserialize, next_keyed_account, DecodeError},
|
||||
pubkey::Pubkey,
|
||||
system_instruction, sysvar,
|
||||
sysvar::rent,
|
||||
};
|
||||
|
||||
/// Reasons the stake might have had an error
|
||||
|
@ -21,6 +20,7 @@ pub enum StakeError {
|
|||
LockupInForce,
|
||||
AlreadyDeactivated,
|
||||
TooSoonToRedelegate,
|
||||
InsufficientStake,
|
||||
}
|
||||
impl<E> DecodeError<E> for StakeError {
|
||||
fn type_of() -> &'static str {
|
||||
|
@ -33,9 +33,8 @@ impl std::fmt::Display for StakeError {
|
|||
StakeError::NoCreditsToRedeem => write!(f, "not enough credits to redeem"),
|
||||
StakeError::LockupInForce => write!(f, "lockup has not yet expired"),
|
||||
StakeError::AlreadyDeactivated => write!(f, "stake already deactivated"),
|
||||
StakeError::TooSoonToRedelegate => {
|
||||
write!(f, "only one redelegation permitted per epoch")
|
||||
}
|
||||
StakeError::TooSoonToRedelegate => write!(f, "one re-delegation permitted per epoch"),
|
||||
StakeError::InsufficientStake => write!(f, "split amount is more than is staked"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,8 +44,9 @@ impl std::error::Error for StakeError {}
|
|||
pub enum StakeInstruction {
|
||||
/// `Initialize` a stake with Lockup and Authorized information
|
||||
///
|
||||
/// Expects 1 Account:
|
||||
/// Expects 2 Accounts:
|
||||
/// 0 - Uninitialized StakeAccount
|
||||
/// 1 - Rent sysvar
|
||||
///
|
||||
/// Authorized carries pubkeys that must sign staker transactions
|
||||
/// and withdrawer transactions.
|
||||
|
@ -87,8 +87,24 @@ pub enum StakeInstruction {
|
|||
/// 2 - RewardsPool Stake Account from which to redeem credits
|
||||
/// 3 - Rewards sysvar Account that carries points values
|
||||
/// 4 - StakeHistory sysvar that carries stake warmup/cooldown history
|
||||
///
|
||||
RedeemVoteCredits,
|
||||
|
||||
/// Split u64 tokens and stake off a stake account into another stake
|
||||
/// account. Requires Authorized::staker signature.
|
||||
///
|
||||
/// The split-off stake account must be Initialized and carry the
|
||||
/// the same values for Lockup and Authorized as the source
|
||||
/// or this instruction will fail.
|
||||
///
|
||||
/// The source stake must be either Initialized or a Stake.
|
||||
///
|
||||
/// Expects 2 Accounts:
|
||||
/// 0 - StakeAccount to be split
|
||||
/// 1 - Initialized StakeAcount that will take the split-off amount
|
||||
///
|
||||
Split(u64),
|
||||
|
||||
/// Withdraw unstaked lamports from the stake account
|
||||
/// requires Authorized::withdrawer signature
|
||||
///
|
||||
|
@ -112,6 +128,17 @@ pub enum StakeInstruction {
|
|||
Deactivate,
|
||||
}
|
||||
|
||||
pub fn initialize(stake_pubkey: &Pubkey, authorized: &Authorized, lockup: &Lockup) -> Instruction {
|
||||
Instruction::new(
|
||||
id(),
|
||||
&StakeInstruction::Initialize(*authorized, *lockup),
|
||||
vec![
|
||||
AccountMeta::new(*stake_pubkey, false),
|
||||
AccountMeta::new_credit_only(sysvar::rent::id(), false),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_stake_account_with_lockup(
|
||||
from_pubkey: &Pubkey,
|
||||
stake_pubkey: &Pubkey,
|
||||
|
@ -127,13 +154,34 @@ pub fn create_stake_account_with_lockup(
|
|||
std::mem::size_of::<StakeState>() as u64,
|
||||
&id(),
|
||||
),
|
||||
initialize(stake_pubkey, authorized, lockup),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn split(
|
||||
stake_pubkey: &Pubkey,
|
||||
authorized_pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
split_stake_pubkey: &Pubkey,
|
||||
) -> Vec<Instruction> {
|
||||
vec![
|
||||
system_instruction::create_account(
|
||||
stake_pubkey,
|
||||
split_stake_pubkey,
|
||||
0, // creates an ephemeral, uninitialized Stake
|
||||
std::mem::size_of::<StakeState>() as u64,
|
||||
&id(),
|
||||
),
|
||||
Instruction::new(
|
||||
id(),
|
||||
&StakeInstruction::Initialize(*authorized, *lockup),
|
||||
vec![
|
||||
AccountMeta::new(*stake_pubkey, false),
|
||||
AccountMeta::new(sysvar::rent::id(), false),
|
||||
],
|
||||
&StakeInstruction::Split(lamports),
|
||||
metas_with_signer(
|
||||
&[
|
||||
AccountMeta::new(*stake_pubkey, false),
|
||||
AccountMeta::new(*split_stake_pubkey, false),
|
||||
],
|
||||
authorized_pubkey,
|
||||
),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
@ -279,10 +327,11 @@ pub fn process_instruction(
|
|||
|
||||
// TODO: data-driven unpack and dispatch of KeyedAccounts
|
||||
match limited_deserialize(data)? {
|
||||
StakeInstruction::Initialize(authorized, lockup) => {
|
||||
rent::verify_rent_exemption(me, next_keyed_account(keyed_accounts)?)?;
|
||||
me.initialize(&authorized, &lockup)
|
||||
}
|
||||
StakeInstruction::Initialize(authorized, lockup) => me.initialize(
|
||||
&authorized,
|
||||
&lockup,
|
||||
&sysvar::rent::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
||||
),
|
||||
StakeInstruction::Authorize(authorized_pubkey, stake_authorize) => {
|
||||
me.authorize(&authorized_pubkey, stake_authorize, &signers)
|
||||
}
|
||||
|
@ -307,6 +356,11 @@ pub fn process_instruction(
|
|||
&sysvar::stake_history::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
|
||||
)
|
||||
}
|
||||
StakeInstruction::Split(lamports) => {
|
||||
let split_stake = &mut next_keyed_account(keyed_accounts)?;
|
||||
me.split(lamports, split_stake, &signers)
|
||||
}
|
||||
|
||||
StakeInstruction::Withdraw(lamports) => {
|
||||
let to = &mut next_keyed_account(keyed_accounts)?;
|
||||
me.withdraw(
|
||||
|
@ -364,10 +418,38 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_stake_process_instruction() {
|
||||
assert_eq!(
|
||||
process_instruction(&initialize(
|
||||
&Pubkey::default(),
|
||||
&Authorized::default(),
|
||||
&Lockup::default()
|
||||
)),
|
||||
Err(InstructionError::InvalidAccountData),
|
||||
);
|
||||
assert_eq!(
|
||||
process_instruction(&redeem_vote_credits(&Pubkey::default(), &Pubkey::default())),
|
||||
Err(InstructionError::InvalidAccountData),
|
||||
);
|
||||
assert_eq!(
|
||||
process_instruction(&authorize(
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default(),
|
||||
StakeAuthorize::Staker
|
||||
)),
|
||||
Err(InstructionError::InvalidAccountData),
|
||||
);
|
||||
assert_eq!(
|
||||
process_instruction(
|
||||
&split(
|
||||
&Pubkey::default(),
|
||||
&Pubkey::default(),
|
||||
100,
|
||||
&Pubkey::default()
|
||||
)[1]
|
||||
),
|
||||
Err(InstructionError::InvalidAccountData),
|
||||
);
|
||||
assert_eq!(
|
||||
process_instruction(&delegate_stake(
|
||||
&Pubkey::default(),
|
||||
|
@ -409,6 +491,62 @@ mod tests {
|
|||
Err(InstructionError::NotEnoughAccountKeys),
|
||||
);
|
||||
|
||||
// no account for rent
|
||||
assert_eq!(
|
||||
super::process_instruction(
|
||||
&Pubkey::default(),
|
||||
&mut [KeyedAccount::new(
|
||||
&Pubkey::default(),
|
||||
false,
|
||||
&mut Account::default(),
|
||||
)],
|
||||
&serialize(&StakeInstruction::Initialize(
|
||||
Authorized::default(),
|
||||
Lockup::default()
|
||||
))
|
||||
.unwrap(),
|
||||
),
|
||||
Err(InstructionError::NotEnoughAccountKeys),
|
||||
);
|
||||
|
||||
// rent fails to deserialize
|
||||
assert_eq!(
|
||||
super::process_instruction(
|
||||
&Pubkey::default(),
|
||||
&mut [
|
||||
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default(),),
|
||||
KeyedAccount::new(&sysvar::rent::id(), false, &mut Account::default(),)
|
||||
],
|
||||
&serialize(&StakeInstruction::Initialize(
|
||||
Authorized::default(),
|
||||
Lockup::default()
|
||||
))
|
||||
.unwrap(),
|
||||
),
|
||||
Err(InstructionError::InvalidArgument),
|
||||
);
|
||||
|
||||
// fails to deserialize stake state
|
||||
assert_eq!(
|
||||
super::process_instruction(
|
||||
&Pubkey::default(),
|
||||
&mut [
|
||||
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default(),),
|
||||
KeyedAccount::new(
|
||||
&sysvar::rent::id(),
|
||||
false,
|
||||
&mut sysvar::rent::create_account(0, &Rent::default())
|
||||
)
|
||||
],
|
||||
&serialize(&StakeInstruction::Initialize(
|
||||
Authorized::default(),
|
||||
Lockup::default()
|
||||
))
|
||||
.unwrap(),
|
||||
),
|
||||
Err(InstructionError::InvalidAccountData),
|
||||
);
|
||||
|
||||
// gets the first check in delegate, wrong number of accounts
|
||||
assert_eq!(
|
||||
super::process_instruction(
|
||||
|
|
|
@ -8,9 +8,10 @@ use serde_derive::{Deserialize, Serialize};
|
|||
use solana_sdk::{
|
||||
account::{Account, KeyedAccount},
|
||||
account_utils::State,
|
||||
clock::{Epoch, Slot},
|
||||
clock::{Clock, Epoch, Slot},
|
||||
instruction::InstructionError,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
sysvar::{
|
||||
self,
|
||||
stake_history::{StakeHistory, StakeHistoryEntry},
|
||||
|
@ -23,8 +24,8 @@ use std::collections::HashSet;
|
|||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum StakeState {
|
||||
Uninitialized,
|
||||
Initialized(Authorized, Lockup),
|
||||
Stake(Authorized, Lockup, Stake),
|
||||
Initialized(Meta),
|
||||
Stake(Meta, Stake),
|
||||
RewardsPool,
|
||||
}
|
||||
|
||||
|
@ -50,13 +51,14 @@ impl StakeState {
|
|||
|
||||
pub fn stake(&self) -> Option<Stake> {
|
||||
match self {
|
||||
StakeState::Stake(_authorized, _lockup, stake) => Some(*stake),
|
||||
StakeState::Stake(_meta, stake) => Some(*stake),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn authorized(&self) -> Option<Authorized> {
|
||||
match self {
|
||||
StakeState::Stake(authorized, _lockup, _stake) => Some(*authorized),
|
||||
StakeState::Stake(meta, _stake) => Some(meta.authorized),
|
||||
StakeState::Initialized(meta) => Some(meta.authorized),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -85,6 +87,23 @@ pub struct Authorized {
|
|||
pub withdrawer: Pubkey,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
||||
pub struct Meta {
|
||||
pub rent_exempt_reserve: u64,
|
||||
pub authorized: Authorized,
|
||||
pub lockup: Lockup,
|
||||
}
|
||||
|
||||
impl Meta {
|
||||
pub fn auto(authorized: &Pubkey) -> Self {
|
||||
Self {
|
||||
authorized: Authorized::auto(authorized),
|
||||
rent_exempt_reserve: Rent::default().minimum_balance(std::mem::size_of::<StakeState>()),
|
||||
..Meta::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
||||
pub struct Stake {
|
||||
/// most recently delegated vote account pubkey
|
||||
|
@ -351,7 +370,7 @@ impl Stake {
|
|||
&mut self,
|
||||
voter_pubkey: &Pubkey,
|
||||
vote_state: &VoteState,
|
||||
clock: &sysvar::clock::Clock,
|
||||
clock: &Clock,
|
||||
) -> Result<(), StakeError> {
|
||||
// only one re-delegation supported per epoch
|
||||
if self.voter_pubkey_epoch == clock.epoch {
|
||||
|
@ -375,6 +394,18 @@ impl Stake {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn split(&mut self, lamports: u64) -> Result<Self, StakeError> {
|
||||
if lamports > self.stake {
|
||||
return Err(StakeError::InsufficientStake);
|
||||
}
|
||||
self.stake -= lamports;
|
||||
let new = Self {
|
||||
stake: lamports,
|
||||
..*self
|
||||
};
|
||||
Ok(new)
|
||||
}
|
||||
|
||||
fn new(
|
||||
stake: u64,
|
||||
voter_pubkey: &Pubkey,
|
||||
|
@ -408,6 +439,7 @@ pub trait StakeAccount {
|
|||
&mut self,
|
||||
authorized: &Authorized,
|
||||
lockup: &Lockup,
|
||||
rent: &Rent,
|
||||
) -> Result<(), InstructionError>;
|
||||
fn authorize(
|
||||
&mut self,
|
||||
|
@ -434,6 +466,12 @@ pub trait StakeAccount {
|
|||
rewards: &sysvar::rewards::Rewards,
|
||||
stake_history: &sysvar::stake_history::StakeHistory,
|
||||
) -> Result<(), InstructionError>;
|
||||
fn split(
|
||||
&mut self,
|
||||
lamports: u64,
|
||||
split_stake: &mut KeyedAccount,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError>;
|
||||
fn withdraw(
|
||||
&mut self,
|
||||
lamports: u64,
|
||||
|
@ -449,13 +487,25 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
|||
&mut self,
|
||||
authorized: &Authorized,
|
||||
lockup: &Lockup,
|
||||
rent: &Rent,
|
||||
) -> Result<(), InstructionError> {
|
||||
if let StakeState::Uninitialized = self.state()? {
|
||||
self.set_state(&StakeState::Initialized(*authorized, *lockup))
|
||||
let rent_exempt_reserve = rent.minimum_balance(self.account.data.len());
|
||||
|
||||
if rent_exempt_reserve < self.account.lamports {
|
||||
self.set_state(&StakeState::Initialized(Meta {
|
||||
rent_exempt_reserve,
|
||||
authorized: *authorized,
|
||||
lockup: *lockup,
|
||||
}))
|
||||
} else {
|
||||
Err(InstructionError::InsufficientFunds)
|
||||
}
|
||||
} 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.
|
||||
|
@ -465,16 +515,18 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
|||
stake_authorize: StakeAuthorize,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError> {
|
||||
let stake_state = self.state()?;
|
||||
|
||||
if let StakeState::Stake(mut authorized, lockup, stake) = stake_state {
|
||||
authorized.authorize(signers, authority, stake_authorize)?;
|
||||
self.set_state(&StakeState::Stake(authorized, lockup, stake))
|
||||
} else if let StakeState::Initialized(mut authorized, lockup) = stake_state {
|
||||
authorized.authorize(signers, authority, stake_authorize)?;
|
||||
self.set_state(&StakeState::Initialized(authorized, lockup))
|
||||
} else {
|
||||
Err(InstructionError::InvalidAccountData)
|
||||
match self.state()? {
|
||||
StakeState::Stake(mut meta, stake) => {
|
||||
meta.authorized
|
||||
.authorize(signers, authority, stake_authorize)?;
|
||||
self.set_state(&StakeState::Stake(meta, stake))
|
||||
}
|
||||
StakeState::Initialized(mut meta) => {
|
||||
meta.authorized
|
||||
.authorize(signers, authority, stake_authorize)?;
|
||||
self.set_state(&StakeState::Initialized(meta))
|
||||
}
|
||||
_ => Err(InstructionError::InvalidAccountData),
|
||||
}
|
||||
}
|
||||
fn delegate_stake(
|
||||
|
@ -484,23 +536,26 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
|||
config: &Config,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError> {
|
||||
if let StakeState::Initialized(authorized, lockup) = self.state()? {
|
||||
authorized.check(signers, StakeAuthorize::Staker)?;
|
||||
let stake = Stake::new(
|
||||
self.account.lamports,
|
||||
vote_account.unsigned_key(),
|
||||
&vote_account.state()?,
|
||||
clock.epoch,
|
||||
config,
|
||||
);
|
||||
|
||||
self.set_state(&StakeState::Stake(authorized, lockup, stake))
|
||||
} else if let StakeState::Stake(authorized, lockup, mut stake) = self.state()? {
|
||||
authorized.check(signers, StakeAuthorize::Staker)?;
|
||||
stake.redelegate(vote_account.unsigned_key(), &vote_account.state()?, &clock)?;
|
||||
self.set_state(&StakeState::Stake(authorized, lockup, stake))
|
||||
} else {
|
||||
Err(InstructionError::InvalidAccountData)
|
||||
match self.state()? {
|
||||
StakeState::Initialized(meta) => {
|
||||
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
||||
let stake = Stake::new(
|
||||
self.account
|
||||
.lamports
|
||||
.saturating_sub(meta.rent_exempt_reserve), // can't stake the rent ;)
|
||||
vote_account.unsigned_key(),
|
||||
&vote_account.state()?,
|
||||
clock.epoch,
|
||||
config,
|
||||
);
|
||||
self.set_state(&StakeState::Stake(meta, stake))
|
||||
}
|
||||
StakeState::Stake(meta, mut stake) => {
|
||||
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
||||
stake.redelegate(vote_account.unsigned_key(), &vote_account.state()?, &clock)?;
|
||||
self.set_state(&StakeState::Stake(meta, stake))
|
||||
}
|
||||
_ => Err(InstructionError::InvalidAccountData),
|
||||
}
|
||||
}
|
||||
fn deactivate_stake(
|
||||
|
@ -508,11 +563,11 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
|||
clock: &sysvar::clock::Clock,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError> {
|
||||
if let StakeState::Stake(authorized, lockup, mut stake) = self.state()? {
|
||||
authorized.check(signers, StakeAuthorize::Staker)?;
|
||||
if let StakeState::Stake(meta, mut stake) = self.state()? {
|
||||
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
||||
stake.deactivate(clock.epoch)?;
|
||||
|
||||
self.set_state(&StakeState::Stake(authorized, lockup, stake))
|
||||
self.set_state(&StakeState::Stake(meta, stake))
|
||||
} else {
|
||||
Err(InstructionError::InvalidAccountData)
|
||||
}
|
||||
|
@ -524,7 +579,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
|||
rewards: &sysvar::rewards::Rewards,
|
||||
stake_history: &sysvar::stake_history::StakeHistory,
|
||||
) -> Result<(), InstructionError> {
|
||||
if let (StakeState::Stake(authorized, lockup, mut stake), StakeState::RewardsPool) =
|
||||
if let (StakeState::Stake(meta, mut stake), StakeState::RewardsPool) =
|
||||
(self.state()?, rewards_account.state()?)
|
||||
{
|
||||
let vote_state: VoteState = vote_account.state()?;
|
||||
|
@ -553,7 +608,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
|||
stake.credits_observed = credits_observed;
|
||||
stake.stake += stakers_reward;
|
||||
|
||||
self.set_state(&StakeState::Stake(authorized, lockup, stake))
|
||||
self.set_state(&StakeState::Stake(meta, stake))
|
||||
} else {
|
||||
// not worth collecting
|
||||
Err(StakeError::NoCreditsToRedeem.into())
|
||||
|
@ -562,6 +617,75 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
|||
Err(InstructionError::InvalidAccountData)
|
||||
}
|
||||
}
|
||||
|
||||
fn split(
|
||||
&mut self,
|
||||
lamports: u64,
|
||||
split: &mut KeyedAccount,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError> {
|
||||
if let StakeState::Uninitialized = split.state()? {
|
||||
// verify enough account lamports
|
||||
if lamports > self.account.lamports {
|
||||
return Err(InstructionError::InsufficientFunds);
|
||||
}
|
||||
|
||||
match self.state()? {
|
||||
StakeState::Stake(meta, mut stake) => {
|
||||
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
||||
|
||||
// verify enough lamports for rent in new stake with the split
|
||||
if split.account.lamports + lamports < meta.rent_exempt_reserve
|
||||
// verify enough lamports left in previous stake
|
||||
|| lamports + meta.rent_exempt_reserve > self.account.lamports
|
||||
{
|
||||
return Err(InstructionError::InsufficientFunds);
|
||||
}
|
||||
|
||||
// split the stake, subtract rent_exempt_balance unless
|
||||
// the destination account already has those lamports
|
||||
// in place.
|
||||
// this could represent a small loss of staked lamports
|
||||
// if the split account starts out with a zero balance
|
||||
let split_stake = stake.split(
|
||||
lamports
|
||||
- meta
|
||||
.rent_exempt_reserve
|
||||
.saturating_sub(split.account.lamports),
|
||||
)?;
|
||||
|
||||
self.set_state(&StakeState::Stake(meta, stake))?;
|
||||
split.set_state(&StakeState::Stake(meta, split_stake))?;
|
||||
}
|
||||
StakeState::Initialized(meta) => {
|
||||
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
||||
|
||||
// enough lamports for rent in new stake
|
||||
if lamports < meta.rent_exempt_reserve
|
||||
// verify enough lamports left in previous stake
|
||||
|| lamports + meta.rent_exempt_reserve > self.account.lamports
|
||||
{
|
||||
return Err(InstructionError::InsufficientFunds);
|
||||
}
|
||||
|
||||
split.set_state(&StakeState::Initialized(meta))?;
|
||||
}
|
||||
StakeState::Uninitialized => {
|
||||
if !signers.contains(&self.unsigned_key()) {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
}
|
||||
_ => return Err(InstructionError::InvalidAccountData),
|
||||
}
|
||||
|
||||
split.account.lamports += lamports;
|
||||
self.account.lamports -= lamports;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(InstructionError::InvalidAccountData)
|
||||
}
|
||||
}
|
||||
|
||||
fn withdraw(
|
||||
&mut self,
|
||||
lamports: u64,
|
||||
|
@ -570,9 +694,9 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
|||
stake_history: &sysvar::stake_history::StakeHistory,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError> {
|
||||
let lockup = match self.state()? {
|
||||
StakeState::Stake(authorized, lockup, stake) => {
|
||||
authorized.check(signers, StakeAuthorize::Withdrawer)?;
|
||||
let (lockup, reserve, is_staked) = match self.state()? {
|
||||
StakeState::Stake(meta, stake) => {
|
||||
meta.authorized.check(signers, StakeAuthorize::Withdrawer)?;
|
||||
// if we have a deactivation epoch and we're in cooldown
|
||||
let staked = if clock.epoch >= stake.deactivation_epoch {
|
||||
stake.stake(clock.epoch, Some(stake_history))
|
||||
|
@ -583,28 +707,39 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
|
|||
stake.stake
|
||||
};
|
||||
|
||||
if lamports > self.account.lamports.saturating_sub(staked) {
|
||||
return Err(InstructionError::InsufficientFunds);
|
||||
}
|
||||
lockup
|
||||
(meta.lockup, staked + meta.rent_exempt_reserve, staked != 0)
|
||||
}
|
||||
StakeState::Initialized(authorized, lockup) => {
|
||||
authorized.check(signers, StakeAuthorize::Withdrawer)?;
|
||||
lockup
|
||||
StakeState::Initialized(meta) => {
|
||||
meta.authorized.check(signers, StakeAuthorize::Withdrawer)?;
|
||||
|
||||
(meta.lockup, meta.rent_exempt_reserve, false)
|
||||
}
|
||||
StakeState::Uninitialized => {
|
||||
if self.signer_key().is_none() {
|
||||
if !signers.contains(&self.unsigned_key()) {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
Lockup::default() // no lockup
|
||||
(Lockup::default(), 0, false) // no lockup, no restrictions
|
||||
}
|
||||
_ => return Err(InstructionError::InvalidAccountData),
|
||||
};
|
||||
|
||||
// verify that lockup has expired or that the withdrawal is going back
|
||||
// to the custodian
|
||||
if lockup.slot > clock.slot && lockup.custodian != *to.unsigned_key() {
|
||||
return Err(StakeError::LockupInForce.into());
|
||||
}
|
||||
if lamports > self.account.lamports {
|
||||
|
||||
// if the stake is active, we mustn't allow the account to go away
|
||||
if is_staked // line coverage for branch coverage
|
||||
&& lamports + reserve > self.account.lamports
|
||||
{
|
||||
return Err(InstructionError::InsufficientFunds);
|
||||
}
|
||||
|
||||
if lamports != self.account.lamports // not a full withdrawal
|
||||
&& lamports + reserve > self.account.lamports
|
||||
{
|
||||
assert!(!is_staked);
|
||||
return Err(InstructionError::InsufficientFunds);
|
||||
}
|
||||
|
||||
|
@ -652,11 +787,15 @@ pub fn create_account(
|
|||
|
||||
stake_account
|
||||
.set_state(&StakeState::Stake(
|
||||
Authorized {
|
||||
staker: *authorized,
|
||||
withdrawer: *authorized,
|
||||
Meta {
|
||||
rent_exempt_reserve: Rent::default()
|
||||
.minimum_balance(std::mem::size_of::<StakeState>()),
|
||||
authorized: Authorized {
|
||||
staker: *authorized,
|
||||
withdrawer: *authorized,
|
||||
},
|
||||
lockup: Lockup::default(),
|
||||
},
|
||||
Lockup::default(),
|
||||
Stake::new_bootstrap(lamports, voter_pubkey, &vote_state),
|
||||
))
|
||||
.expect("set_state");
|
||||
|
@ -724,13 +863,13 @@ mod tests {
|
|||
let stake_lamports = 42;
|
||||
let mut stake_account = Account::new_data_with_space(
|
||||
stake_lamports,
|
||||
&StakeState::Initialized(
|
||||
Authorized {
|
||||
&StakeState::Initialized(Meta {
|
||||
authorized: Authorized {
|
||||
staker: stake_pubkey,
|
||||
withdrawer: stake_pubkey,
|
||||
},
|
||||
Lockup::default(),
|
||||
),
|
||||
..Meta::default()
|
||||
}),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
|
@ -743,13 +882,13 @@ mod tests {
|
|||
let stake_state: StakeState = stake_keyed_account.state().unwrap();
|
||||
assert_eq!(
|
||||
stake_state,
|
||||
StakeState::Initialized(
|
||||
Authorized {
|
||||
StakeState::Initialized(Meta {
|
||||
authorized: Authorized {
|
||||
staker: stake_pubkey,
|
||||
withdrawer: stake_pubkey,
|
||||
},
|
||||
Lockup::default(),
|
||||
)
|
||||
..Meta::default()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1162,7 +1301,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_stake_lockup() {
|
||||
fn test_stake_initialize() {
|
||||
let stake_pubkey = Pubkey::new_rand();
|
||||
let stake_lamports = 42;
|
||||
let mut stake_account =
|
||||
|
@ -1171,32 +1310,45 @@ mod tests {
|
|||
// unsigned keyed account
|
||||
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
|
||||
let custodian = Pubkey::new_rand();
|
||||
|
||||
// not enough balance for rent...
|
||||
assert_eq!(
|
||||
stake_keyed_account.initialize(
|
||||
&Authorized {
|
||||
staker: stake_pubkey,
|
||||
withdrawer: stake_pubkey
|
||||
},
|
||||
&Lockup { slot: 1, custodian }
|
||||
&Authorized::default(),
|
||||
&Lockup::default(),
|
||||
&Rent {
|
||||
lamports_per_byte_year: 42,
|
||||
..Rent::default()
|
||||
}
|
||||
),
|
||||
Err(InstructionError::InsufficientFunds)
|
||||
);
|
||||
|
||||
// this one works, as is uninit
|
||||
assert_eq!(
|
||||
stake_keyed_account.initialize(
|
||||
&Authorized::auto(&stake_pubkey),
|
||||
&Lockup { slot: 1, custodian },
|
||||
&Rent::default(),
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
// first time works, as is uninit
|
||||
// check that we see what we expect
|
||||
assert_eq!(
|
||||
StakeState::from(&stake_keyed_account.account).unwrap(),
|
||||
StakeState::Initialized(
|
||||
Authorized {
|
||||
staker: stake_pubkey,
|
||||
withdrawer: stake_pubkey
|
||||
},
|
||||
Lockup { slot: 1, custodian }
|
||||
)
|
||||
StakeState::Initialized(Meta {
|
||||
lockup: Lockup { slot: 1, custodian },
|
||||
..Meta::auto(&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->init
|
||||
assert_eq!(
|
||||
stake_keyed_account.initialize(&Authorized::default(), &Lockup::default()),
|
||||
stake_keyed_account.initialize(
|
||||
&Authorized::default(),
|
||||
&Lockup::default(),
|
||||
&Rent::default()
|
||||
),
|
||||
Err(InstructionError::InvalidAccountData)
|
||||
);
|
||||
}
|
||||
|
@ -1207,7 +1359,7 @@ mod tests {
|
|||
let stake_lamports = 42;
|
||||
let mut stake_account = Account::new_data_with_space(
|
||||
stake_lamports,
|
||||
&StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()),
|
||||
&StakeState::Initialized(Meta::auto(&stake_pubkey)),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
|
@ -1320,6 +1472,7 @@ mod tests {
|
|||
.initialize(
|
||||
&Authorized::auto(&stake_pubkey),
|
||||
&Lockup { slot: 0, custodian },
|
||||
&Rent::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -1428,7 +1581,7 @@ mod tests {
|
|||
let stake_lamports = 42;
|
||||
let mut stake_account = Account::new_data_with_space(
|
||||
total_lamports,
|
||||
&StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()),
|
||||
&StakeState::Initialized(Meta::auto(&stake_pubkey)),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
|
@ -1516,10 +1669,10 @@ mod tests {
|
|||
let total_lamports = 100;
|
||||
let mut stake_account = Account::new_data_with_space(
|
||||
total_lamports,
|
||||
&StakeState::Initialized(
|
||||
Authorized::auto(&stake_pubkey),
|
||||
Lockup { slot: 1, custodian },
|
||||
),
|
||||
&StakeState::Initialized(Meta {
|
||||
lockup: Lockup { slot: 1, custodian },
|
||||
..Meta::auto(&stake_pubkey)
|
||||
}),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
|
@ -1674,7 +1827,7 @@ mod tests {
|
|||
let stake_lamports = 100;
|
||||
let mut stake_account = Account::new_data_with_space(
|
||||
stake_lamports,
|
||||
&StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()),
|
||||
&StakeState::Initialized(Meta::auto(&stake_pubkey)),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
|
@ -1799,13 +1952,33 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_authorize_uninit() {
|
||||
let stake_pubkey = Pubkey::new_rand();
|
||||
let stake_lamports = 42;
|
||||
let mut stake_account = Account::new_data_with_space(
|
||||
stake_lamports,
|
||||
&StakeState::default(),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
|
||||
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
||||
let signers = vec![stake_pubkey].into_iter().collect();
|
||||
assert_eq!(
|
||||
stake_keyed_account.authorize(&stake_pubkey, StakeAuthorize::Staker, &signers),
|
||||
Err(InstructionError::InvalidAccountData)
|
||||
);
|
||||
}
|
||||
|
||||
#[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::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()),
|
||||
&StakeState::Initialized(Meta::auto(&stake_pubkey)),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
|
@ -1828,7 +2001,7 @@ mod tests {
|
|||
stake_keyed_account.authorize(&stake_pubkey0, StakeAuthorize::Withdrawer, &signers),
|
||||
Ok(())
|
||||
);
|
||||
if let StakeState::Initialized(authorized, _lockup) =
|
||||
if let StakeState::Initialized(Meta { authorized, .. }) =
|
||||
StakeState::from(&stake_keyed_account.account).unwrap()
|
||||
{
|
||||
assert_eq!(authorized.staker, stake_pubkey0);
|
||||
|
@ -1852,7 +2025,7 @@ mod tests {
|
|||
stake_keyed_account.authorize(&stake_pubkey2, StakeAuthorize::Staker, &signers0),
|
||||
Ok(())
|
||||
);
|
||||
if let StakeState::Initialized(authorized, _lockup) =
|
||||
if let StakeState::Initialized(Meta { authorized, .. }) =
|
||||
StakeState::from(&stake_keyed_account.account).unwrap()
|
||||
{
|
||||
assert_eq!(authorized.staker, stake_pubkey2);
|
||||
|
@ -1862,7 +2035,7 @@ mod tests {
|
|||
stake_keyed_account.authorize(&stake_pubkey2, StakeAuthorize::Withdrawer, &signers0,),
|
||||
Ok(())
|
||||
);
|
||||
if let StakeState::Initialized(authorized, _lockup) =
|
||||
if let StakeState::Initialized(Meta { authorized, .. }) =
|
||||
StakeState::from(&stake_keyed_account.account).unwrap()
|
||||
{
|
||||
assert_eq!(authorized.staker, stake_pubkey2);
|
||||
|
@ -1896,13 +2069,337 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_source_uninitialized() {
|
||||
let stake_pubkey = Pubkey::new_rand();
|
||||
let stake_lamports = 42;
|
||||
let mut stake_account = Account::new_data_with_space(
|
||||
stake_lamports,
|
||||
&StakeState::Uninitialized,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
|
||||
let split_stake_pubkey = Pubkey::new_rand();
|
||||
let mut split_stake_account = Account::new_data_with_space(
|
||||
0,
|
||||
&StakeState::Uninitialized,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
|
||||
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
|
||||
let mut split_stake_keyed_account =
|
||||
KeyedAccount::new(&split_stake_pubkey, false, &mut split_stake_account);
|
||||
|
||||
// no signers should fail
|
||||
assert_eq!(
|
||||
stake_keyed_account.split(
|
||||
stake_lamports / 2,
|
||||
&mut split_stake_keyed_account,
|
||||
&HashSet::default() // no signers
|
||||
),
|
||||
Err(InstructionError::MissingRequiredSignature)
|
||||
);
|
||||
|
||||
// this should work
|
||||
let signers = vec![stake_pubkey].into_iter().collect();
|
||||
assert_eq!(
|
||||
stake_keyed_account.split(stake_lamports / 2, &mut split_stake_keyed_account, &signers),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
stake_keyed_account.account.lamports,
|
||||
split_stake_keyed_account.account.lamports
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_split_not_uninitialized() {
|
||||
let stake_pubkey = Pubkey::new_rand();
|
||||
let stake_lamports = 42;
|
||||
let mut stake_account = Account::new_data_with_space(
|
||||
stake_lamports,
|
||||
&StakeState::Stake(
|
||||
Meta::auto(&stake_pubkey),
|
||||
Stake {
|
||||
stake: stake_lamports,
|
||||
..Stake::default()
|
||||
},
|
||||
),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
|
||||
let split_stake_pubkey = Pubkey::new_rand();
|
||||
let mut split_stake_account = Account::new_data_with_space(
|
||||
0,
|
||||
&StakeState::Initialized(Meta::auto(&stake_pubkey)),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
|
||||
let signers = vec![stake_pubkey].into_iter().collect();
|
||||
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
||||
let mut split_stake_keyed_account =
|
||||
KeyedAccount::new(&split_stake_pubkey, true, &mut split_stake_account);
|
||||
assert_eq!(
|
||||
stake_keyed_account.split(stake_lamports / 2, &mut split_stake_keyed_account, &signers),
|
||||
Err(InstructionError::InvalidAccountData)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_more_than_staked() {
|
||||
let stake_pubkey = Pubkey::new_rand();
|
||||
let stake_lamports = 42;
|
||||
let mut stake_account = Account::new_data_with_space(
|
||||
stake_lamports,
|
||||
&StakeState::Stake(
|
||||
Meta::auto(&stake_pubkey),
|
||||
Stake {
|
||||
stake: stake_lamports / 2 - 1,
|
||||
..Stake::default()
|
||||
},
|
||||
),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
|
||||
let split_stake_pubkey = Pubkey::new_rand();
|
||||
let mut split_stake_account = Account::new_data_with_space(
|
||||
0,
|
||||
&StakeState::Uninitialized,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
|
||||
let signers = vec![stake_pubkey].into_iter().collect();
|
||||
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
||||
let mut split_stake_keyed_account =
|
||||
KeyedAccount::new(&split_stake_pubkey, true, &mut split_stake_account);
|
||||
assert_eq!(
|
||||
stake_keyed_account.split(stake_lamports / 2, &mut split_stake_keyed_account, &signers),
|
||||
Err(StakeError::InsufficientStake.into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_with_rent() {
|
||||
let stake_pubkey = Pubkey::new_rand();
|
||||
let split_stake_pubkey = Pubkey::new_rand();
|
||||
let stake_lamports = 42;
|
||||
let rent_exempt_reserve = 10;
|
||||
let signers = vec![stake_pubkey].into_iter().collect();
|
||||
|
||||
let meta = Meta {
|
||||
authorized: Authorized::auto(&stake_pubkey),
|
||||
rent_exempt_reserve,
|
||||
..Meta::default()
|
||||
};
|
||||
|
||||
// test splitting both an Initialized stake and a Staked stake
|
||||
for state in &[
|
||||
StakeState::Initialized(meta),
|
||||
StakeState::Stake(
|
||||
meta,
|
||||
Stake {
|
||||
stake: stake_lamports,
|
||||
..Stake::default()
|
||||
},
|
||||
),
|
||||
] {
|
||||
let mut stake_account = Account::new_data_with_space(
|
||||
stake_lamports,
|
||||
state,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
|
||||
let mut stake_keyed_account =
|
||||
KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
||||
|
||||
let mut split_stake_account = Account::new_data_with_space(
|
||||
0,
|
||||
&StakeState::Uninitialized,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
|
||||
let mut split_stake_keyed_account =
|
||||
KeyedAccount::new(&split_stake_pubkey, true, &mut split_stake_account);
|
||||
|
||||
// not enough to make a stake account
|
||||
assert_eq!(
|
||||
stake_keyed_account.split(
|
||||
rent_exempt_reserve - 1,
|
||||
&mut split_stake_keyed_account,
|
||||
&signers
|
||||
),
|
||||
Err(InstructionError::InsufficientFunds)
|
||||
);
|
||||
|
||||
// doesn't leave enough for initial stake
|
||||
assert_eq!(
|
||||
stake_keyed_account.split(
|
||||
(stake_lamports - rent_exempt_reserve) + 1,
|
||||
&mut split_stake_keyed_account,
|
||||
&signers
|
||||
),
|
||||
Err(InstructionError::InsufficientFunds)
|
||||
);
|
||||
|
||||
// split account already has way enough lamports
|
||||
split_stake_keyed_account.account.lamports = 1_000;
|
||||
assert_eq!(
|
||||
stake_keyed_account.split(
|
||||
stake_lamports - rent_exempt_reserve,
|
||||
&mut split_stake_keyed_account,
|
||||
&signers
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
// verify no stake leakage in the case of a stake
|
||||
if let StakeState::Stake(meta, stake) = state {
|
||||
assert_eq!(
|
||||
split_stake_keyed_account.state(),
|
||||
Ok(StakeState::Stake(
|
||||
*meta,
|
||||
Stake {
|
||||
stake: stake_lamports - rent_exempt_reserve,
|
||||
..*stake
|
||||
}
|
||||
))
|
||||
);
|
||||
// assert_eq!(
|
||||
// stake_keyed_account.state(),
|
||||
// Ok(StakeState::Stake(*meta, Stake { stake: 0, ..*stake }))
|
||||
// );
|
||||
assert_eq!(stake_keyed_account.account.lamports, rent_exempt_reserve);
|
||||
assert_eq!(
|
||||
split_stake_keyed_account.account.lamports,
|
||||
1_000 + stake_lamports - rent_exempt_reserve
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split() {
|
||||
let stake_pubkey = Pubkey::new_rand();
|
||||
let stake_lamports = 42;
|
||||
|
||||
let split_stake_pubkey = Pubkey::new_rand();
|
||||
let signers = vec![stake_pubkey].into_iter().collect();
|
||||
|
||||
// test splitting both an Initialized stake and a Staked stake
|
||||
for state in &[
|
||||
StakeState::Initialized(Meta::auto(&stake_pubkey)),
|
||||
StakeState::Stake(
|
||||
Meta::auto(&stake_pubkey),
|
||||
Stake {
|
||||
stake: stake_lamports,
|
||||
..Stake::default()
|
||||
},
|
||||
),
|
||||
] {
|
||||
let mut split_stake_account = Account::new_data_with_space(
|
||||
0,
|
||||
&StakeState::Uninitialized,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
|
||||
let mut split_stake_keyed_account =
|
||||
KeyedAccount::new(&split_stake_pubkey, true, &mut split_stake_account);
|
||||
|
||||
let mut stake_account = Account::new_data_with_space(
|
||||
stake_lamports,
|
||||
state,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.expect("stake_account");
|
||||
let mut stake_keyed_account =
|
||||
KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
||||
|
||||
// split more than available fails
|
||||
assert_eq!(
|
||||
stake_keyed_account.split(
|
||||
stake_lamports + 1,
|
||||
&mut split_stake_keyed_account,
|
||||
&signers
|
||||
),
|
||||
Err(InstructionError::InsufficientFunds)
|
||||
);
|
||||
|
||||
// should work
|
||||
assert_eq!(
|
||||
stake_keyed_account.split(
|
||||
stake_lamports / 2,
|
||||
&mut split_stake_keyed_account,
|
||||
&signers
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
// no lamport leakage
|
||||
assert_eq!(
|
||||
stake_keyed_account.account.lamports + split_stake_keyed_account.account.lamports,
|
||||
stake_lamports
|
||||
);
|
||||
|
||||
match state {
|
||||
StakeState::Initialized(_) => {
|
||||
assert_eq!(Ok(*state), split_stake_keyed_account.state());
|
||||
assert_eq!(Ok(*state), stake_keyed_account.state());
|
||||
}
|
||||
StakeState::Stake(meta, stake) => {
|
||||
assert_eq!(
|
||||
Ok(StakeState::Stake(
|
||||
*meta,
|
||||
Stake {
|
||||
stake: stake_lamports / 2,
|
||||
..*stake
|
||||
}
|
||||
)),
|
||||
split_stake_keyed_account.state()
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(StakeState::Stake(
|
||||
*meta,
|
||||
Stake {
|
||||
stake: stake_lamports / 2,
|
||||
..*stake
|
||||
}
|
||||
)),
|
||||
stake_keyed_account.state()
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// reset
|
||||
stake_keyed_account.account.lamports = stake_lamports;
|
||||
}
|
||||
}
|
||||
|
||||
#[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::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()),
|
||||
&StakeState::Initialized(Meta::auto(&stake_pubkey)),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
|
|
|
@ -111,7 +111,7 @@ fn test_stake_account_delegate() {
|
|||
// Test that correct lamports are staked
|
||||
let account = bank.get_account(&staker_pubkey).expect("account not found");
|
||||
let stake_state = account.state().expect("couldn't unpack account data");
|
||||
if let StakeState::Stake(_authorized, _lockup, stake) = stake_state {
|
||||
if let StakeState::Stake(_meta, stake) = stake_state {
|
||||
assert_eq!(stake.stake, 1_000_000);
|
||||
} else {
|
||||
assert!(false, "wrong account type found")
|
||||
|
@ -134,7 +134,7 @@ fn test_stake_account_delegate() {
|
|||
// Test that lamports are still staked
|
||||
let account = bank.get_account(&staker_pubkey).expect("account not found");
|
||||
let stake_state = account.state().expect("couldn't unpack account data");
|
||||
if let StakeState::Stake(_authorized, _lockup, stake) = stake_state {
|
||||
if let StakeState::Stake(_meta, stake) = stake_state {
|
||||
assert_eq!(stake.stake, 1_000_000);
|
||||
} else {
|
||||
assert!(false, "wrong account type found")
|
||||
|
|
|
@ -72,6 +72,15 @@ pub type Segment = u64;
|
|||
/// some number of Slots.
|
||||
pub type Epoch = u64;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
|
||||
pub struct Clock {
|
||||
pub slot: Slot,
|
||||
pub segment: Segment,
|
||||
pub epoch: Epoch,
|
||||
pub stakers_epoch: Epoch,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
//! This account contains the clock slot, epoch, and stakers_epoch
|
||||
//!
|
||||
pub use crate::clock::Clock;
|
||||
|
||||
use crate::account::Account;
|
||||
use crate::account_info::AccountInfo;
|
||||
use crate::clock::{Epoch, Segment, Slot};
|
||||
use crate::sysvar;
|
||||
use crate::{
|
||||
account::Account,
|
||||
account_info::AccountInfo,
|
||||
clock::{Epoch, Segment, Slot},
|
||||
sysvar,
|
||||
};
|
||||
use bincode::serialized_size;
|
||||
|
||||
const ID: [u8; 32] = [
|
||||
|
@ -14,15 +17,6 @@ const ID: [u8; 32] = [
|
|||
|
||||
crate::solana_sysvar_id!(ID, "SysvarC1ock11111111111111111111111111111111");
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
|
||||
pub struct Clock {
|
||||
pub slot: Slot,
|
||||
pub segment: Segment,
|
||||
pub epoch: Epoch,
|
||||
pub stakers_epoch: Epoch,
|
||||
}
|
||||
|
||||
impl Clock {
|
||||
pub fn size_of() -> usize {
|
||||
serialized_size(&Self::default()).unwrap() as usize
|
||||
|
|
Loading…
Reference in New Issue