stake split (#6402)

* stake split

* stake split
This commit is contained in:
Rob Walker 2019-10-31 11:07:27 -07:00 committed by GitHub
parent 3a616de47b
commit bc88180058
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 770 additions and 125 deletions

View File

@ -20,7 +20,7 @@ use solana_sdk::{
}; };
use solana_stake_api::{ use solana_stake_api::{
stake_instruction::{self, StakeError}, 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 solana_vote_api::vote_state::VoteState;
use std::ops::Deref; use std::ops::Deref;
@ -548,7 +548,12 @@ pub fn process_show_stake_account(
println!("lockup custodian: {}", lockup.custodian); println!("lockup custodian: {}", lockup.custodian);
} }
match stake_account.state() { match stake_account.state() {
Ok(StakeState::Stake(authorized, lockup, stake)) => { Ok(StakeState::Stake(
Meta {
authorized, lockup, ..
},
stake,
)) => {
println!( println!(
"total stake: {}", "total stake: {}",
build_balance_message(stake_account.lamports, use_lamports_unit, true) 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::RewardsPool) => Ok("Stake account is a rewards pool".to_string()),
Ok(StakeState::Uninitialized) => Ok("Stake account is uninitialized".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"); println!("Stake account is undelegated");
show_authorized(&authorized); show_authorized(&authorized);
show_lockup(&lockup); show_lockup(&lockup);

View File

@ -11,7 +11,6 @@ use solana_sdk::{
instruction_processor_utils::{limited_deserialize, next_keyed_account, DecodeError}, instruction_processor_utils::{limited_deserialize, next_keyed_account, DecodeError},
pubkey::Pubkey, pubkey::Pubkey,
system_instruction, sysvar, system_instruction, sysvar,
sysvar::rent,
}; };
/// Reasons the stake might have had an error /// Reasons the stake might have had an error
@ -21,6 +20,7 @@ pub enum StakeError {
LockupInForce, LockupInForce,
AlreadyDeactivated, AlreadyDeactivated,
TooSoonToRedelegate, TooSoonToRedelegate,
InsufficientStake,
} }
impl<E> DecodeError<E> for StakeError { impl<E> DecodeError<E> for StakeError {
fn type_of() -> &'static str { 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::NoCreditsToRedeem => write!(f, "not enough credits to redeem"),
StakeError::LockupInForce => write!(f, "lockup has not yet expired"), StakeError::LockupInForce => write!(f, "lockup has not yet expired"),
StakeError::AlreadyDeactivated => write!(f, "stake already deactivated"), StakeError::AlreadyDeactivated => write!(f, "stake already deactivated"),
StakeError::TooSoonToRedelegate => { StakeError::TooSoonToRedelegate => write!(f, "one re-delegation permitted per epoch"),
write!(f, "only one redelegation 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 { pub enum StakeInstruction {
/// `Initialize` a stake with Lockup and Authorized information /// `Initialize` a stake with Lockup and Authorized information
/// ///
/// Expects 1 Account: /// Expects 2 Accounts:
/// 0 - Uninitialized StakeAccount /// 0 - Uninitialized StakeAccount
/// 1 - Rent sysvar
/// ///
/// Authorized carries pubkeys that must sign staker transactions /// Authorized carries pubkeys that must sign staker transactions
/// and withdrawer transactions. /// and withdrawer transactions.
@ -87,8 +87,24 @@ pub enum StakeInstruction {
/// 2 - RewardsPool Stake Account from which to redeem credits /// 2 - RewardsPool Stake Account from which to redeem credits
/// 3 - Rewards sysvar Account that carries points values /// 3 - Rewards sysvar Account that carries points values
/// 4 - StakeHistory sysvar that carries stake warmup/cooldown history /// 4 - StakeHistory sysvar that carries stake warmup/cooldown history
///
RedeemVoteCredits, 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 /// Withdraw unstaked lamports from the stake account
/// requires Authorized::withdrawer signature /// requires Authorized::withdrawer signature
/// ///
@ -112,6 +128,17 @@ pub enum StakeInstruction {
Deactivate, 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( pub fn create_stake_account_with_lockup(
from_pubkey: &Pubkey, from_pubkey: &Pubkey,
stake_pubkey: &Pubkey, stake_pubkey: &Pubkey,
@ -127,13 +154,34 @@ pub fn create_stake_account_with_lockup(
std::mem::size_of::<StakeState>() as u64, std::mem::size_of::<StakeState>() as u64,
&id(), &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( Instruction::new(
id(), id(),
&StakeInstruction::Initialize(*authorized, *lockup), &StakeInstruction::Split(lamports),
vec![ metas_with_signer(
AccountMeta::new(*stake_pubkey, false), &[
AccountMeta::new(sysvar::rent::id(), false), 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 // TODO: data-driven unpack and dispatch of KeyedAccounts
match limited_deserialize(data)? { match limited_deserialize(data)? {
StakeInstruction::Initialize(authorized, lockup) => { StakeInstruction::Initialize(authorized, lockup) => me.initialize(
rent::verify_rent_exemption(me, next_keyed_account(keyed_accounts)?)?; &authorized,
me.initialize(&authorized, &lockup) &lockup,
} &sysvar::rent::from_keyed_account(next_keyed_account(keyed_accounts)?)?,
),
StakeInstruction::Authorize(authorized_pubkey, stake_authorize) => { StakeInstruction::Authorize(authorized_pubkey, stake_authorize) => {
me.authorize(&authorized_pubkey, stake_authorize, &signers) 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)?)?, &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) => { StakeInstruction::Withdraw(lamports) => {
let to = &mut next_keyed_account(keyed_accounts)?; let to = &mut next_keyed_account(keyed_accounts)?;
me.withdraw( me.withdraw(
@ -364,10 +418,38 @@ mod tests {
#[test] #[test]
fn test_stake_process_instruction() { fn test_stake_process_instruction() {
assert_eq!(
process_instruction(&initialize(
&Pubkey::default(),
&Authorized::default(),
&Lockup::default()
)),
Err(InstructionError::InvalidAccountData),
);
assert_eq!( assert_eq!(
process_instruction(&redeem_vote_credits(&Pubkey::default(), &Pubkey::default())), process_instruction(&redeem_vote_credits(&Pubkey::default(), &Pubkey::default())),
Err(InstructionError::InvalidAccountData), 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!( assert_eq!(
process_instruction(&delegate_stake( process_instruction(&delegate_stake(
&Pubkey::default(), &Pubkey::default(),
@ -409,6 +491,62 @@ mod tests {
Err(InstructionError::NotEnoughAccountKeys), 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 // gets the first check in delegate, wrong number of accounts
assert_eq!( assert_eq!(
super::process_instruction( super::process_instruction(

View File

@ -8,9 +8,10 @@ use serde_derive::{Deserialize, Serialize};
use solana_sdk::{ use solana_sdk::{
account::{Account, KeyedAccount}, account::{Account, KeyedAccount},
account_utils::State, account_utils::State,
clock::{Epoch, Slot}, clock::{Clock, Epoch, Slot},
instruction::InstructionError, instruction::InstructionError,
pubkey::Pubkey, pubkey::Pubkey,
rent::Rent,
sysvar::{ sysvar::{
self, self,
stake_history::{StakeHistory, StakeHistoryEntry}, stake_history::{StakeHistory, StakeHistoryEntry},
@ -23,8 +24,8 @@ use std::collections::HashSet;
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum StakeState { pub enum StakeState {
Uninitialized, Uninitialized,
Initialized(Authorized, Lockup), Initialized(Meta),
Stake(Authorized, Lockup, Stake), Stake(Meta, Stake),
RewardsPool, RewardsPool,
} }
@ -50,13 +51,14 @@ impl StakeState {
pub fn stake(&self) -> Option<Stake> { pub fn stake(&self) -> Option<Stake> {
match self { match self {
StakeState::Stake(_authorized, _lockup, stake) => Some(*stake), StakeState::Stake(_meta, stake) => Some(*stake),
_ => None, _ => None,
} }
} }
pub fn authorized(&self) -> Option<Authorized> { pub fn authorized(&self) -> Option<Authorized> {
match self { match self {
StakeState::Stake(authorized, _lockup, _stake) => Some(*authorized), StakeState::Stake(meta, _stake) => Some(meta.authorized),
StakeState::Initialized(meta) => Some(meta.authorized),
_ => None, _ => None,
} }
} }
@ -85,6 +87,23 @@ pub struct Authorized {
pub withdrawer: Pubkey, 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)] #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
pub struct Stake { pub struct Stake {
/// most recently delegated vote account pubkey /// most recently delegated vote account pubkey
@ -351,7 +370,7 @@ impl Stake {
&mut self, &mut self,
voter_pubkey: &Pubkey, voter_pubkey: &Pubkey,
vote_state: &VoteState, vote_state: &VoteState,
clock: &sysvar::clock::Clock, clock: &Clock,
) -> Result<(), StakeError> { ) -> Result<(), StakeError> {
// only one re-delegation supported per epoch // only one re-delegation supported per epoch
if self.voter_pubkey_epoch == clock.epoch { if self.voter_pubkey_epoch == clock.epoch {
@ -375,6 +394,18 @@ impl Stake {
Ok(()) 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( fn new(
stake: u64, stake: u64,
voter_pubkey: &Pubkey, voter_pubkey: &Pubkey,
@ -408,6 +439,7 @@ pub trait StakeAccount {
&mut self, &mut self,
authorized: &Authorized, authorized: &Authorized,
lockup: &Lockup, lockup: &Lockup,
rent: &Rent,
) -> Result<(), InstructionError>; ) -> Result<(), InstructionError>;
fn authorize( fn authorize(
&mut self, &mut self,
@ -434,6 +466,12 @@ pub trait StakeAccount {
rewards: &sysvar::rewards::Rewards, rewards: &sysvar::rewards::Rewards,
stake_history: &sysvar::stake_history::StakeHistory, stake_history: &sysvar::stake_history::StakeHistory,
) -> Result<(), InstructionError>; ) -> Result<(), InstructionError>;
fn split(
&mut self,
lamports: u64,
split_stake: &mut KeyedAccount,
signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError>;
fn withdraw( fn withdraw(
&mut self, &mut self,
lamports: u64, lamports: u64,
@ -449,13 +487,25 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
&mut self, &mut self,
authorized: &Authorized, authorized: &Authorized,
lockup: &Lockup, lockup: &Lockup,
rent: &Rent,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if let StakeState::Uninitialized = self.state()? { 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 { } else {
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
} }
} }
/// Authorize the given pubkey to manage stake (deactivate, withdraw). This may be called /// Authorize the given pubkey to manage stake (deactivate, withdraw). This may be called
/// multiple times, but will implicitly withdraw authorization from the previously authorized /// multiple times, but will implicitly withdraw authorization from the previously authorized
/// staker. The default staker is the owner of the stake account's pubkey. /// 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, stake_authorize: StakeAuthorize,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let stake_state = self.state()?; match self.state()? {
StakeState::Stake(mut meta, stake) => {
if let StakeState::Stake(mut authorized, lockup, stake) = stake_state { meta.authorized
authorized.authorize(signers, authority, stake_authorize)?; .authorize(signers, authority, stake_authorize)?;
self.set_state(&StakeState::Stake(authorized, lockup, stake)) self.set_state(&StakeState::Stake(meta, stake))
} else if let StakeState::Initialized(mut authorized, lockup) = stake_state { }
authorized.authorize(signers, authority, stake_authorize)?; StakeState::Initialized(mut meta) => {
self.set_state(&StakeState::Initialized(authorized, lockup)) meta.authorized
} else { .authorize(signers, authority, stake_authorize)?;
Err(InstructionError::InvalidAccountData) self.set_state(&StakeState::Initialized(meta))
}
_ => Err(InstructionError::InvalidAccountData),
} }
} }
fn delegate_stake( fn delegate_stake(
@ -484,23 +536,26 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
config: &Config, config: &Config,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if let StakeState::Initialized(authorized, lockup) = self.state()? { match self.state()? {
authorized.check(signers, StakeAuthorize::Staker)?; StakeState::Initialized(meta) => {
let stake = Stake::new( meta.authorized.check(signers, StakeAuthorize::Staker)?;
self.account.lamports, let stake = Stake::new(
vote_account.unsigned_key(), self.account
&vote_account.state()?, .lamports
clock.epoch, .saturating_sub(meta.rent_exempt_reserve), // can't stake the rent ;)
config, vote_account.unsigned_key(),
); &vote_account.state()?,
clock.epoch,
self.set_state(&StakeState::Stake(authorized, lockup, stake)) config,
} else if let StakeState::Stake(authorized, lockup, mut stake) = self.state()? { );
authorized.check(signers, StakeAuthorize::Staker)?; self.set_state(&StakeState::Stake(meta, stake))
stake.redelegate(vote_account.unsigned_key(), &vote_account.state()?, &clock)?; }
self.set_state(&StakeState::Stake(authorized, lockup, stake)) StakeState::Stake(meta, mut stake) => {
} else { meta.authorized.check(signers, StakeAuthorize::Staker)?;
Err(InstructionError::InvalidAccountData) stake.redelegate(vote_account.unsigned_key(), &vote_account.state()?, &clock)?;
self.set_state(&StakeState::Stake(meta, stake))
}
_ => Err(InstructionError::InvalidAccountData),
} }
} }
fn deactivate_stake( fn deactivate_stake(
@ -508,11 +563,11 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
clock: &sysvar::clock::Clock, clock: &sysvar::clock::Clock,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if let StakeState::Stake(authorized, lockup, mut stake) = self.state()? { if let StakeState::Stake(meta, mut stake) = self.state()? {
authorized.check(signers, StakeAuthorize::Staker)?; meta.authorized.check(signers, StakeAuthorize::Staker)?;
stake.deactivate(clock.epoch)?; stake.deactivate(clock.epoch)?;
self.set_state(&StakeState::Stake(authorized, lockup, stake)) self.set_state(&StakeState::Stake(meta, stake))
} else { } else {
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
} }
@ -524,7 +579,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
rewards: &sysvar::rewards::Rewards, rewards: &sysvar::rewards::Rewards,
stake_history: &sysvar::stake_history::StakeHistory, stake_history: &sysvar::stake_history::StakeHistory,
) -> Result<(), InstructionError> { ) -> 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()?) (self.state()?, rewards_account.state()?)
{ {
let vote_state: VoteState = vote_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.credits_observed = credits_observed;
stake.stake += stakers_reward; stake.stake += stakers_reward;
self.set_state(&StakeState::Stake(authorized, lockup, stake)) self.set_state(&StakeState::Stake(meta, stake))
} else { } else {
// not worth collecting // not worth collecting
Err(StakeError::NoCreditsToRedeem.into()) Err(StakeError::NoCreditsToRedeem.into())
@ -562,6 +617,75 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
Err(InstructionError::InvalidAccountData) 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( fn withdraw(
&mut self, &mut self,
lamports: u64, lamports: u64,
@ -570,9 +694,9 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
stake_history: &sysvar::stake_history::StakeHistory, stake_history: &sysvar::stake_history::StakeHistory,
signers: &HashSet<Pubkey>, signers: &HashSet<Pubkey>,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let lockup = match self.state()? { let (lockup, reserve, is_staked) = match self.state()? {
StakeState::Stake(authorized, lockup, stake) => { StakeState::Stake(meta, stake) => {
authorized.check(signers, StakeAuthorize::Withdrawer)?; meta.authorized.check(signers, StakeAuthorize::Withdrawer)?;
// 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))
@ -583,28 +707,39 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
stake.stake stake.stake
}; };
if lamports > self.account.lamports.saturating_sub(staked) { (meta.lockup, staked + meta.rent_exempt_reserve, staked != 0)
return Err(InstructionError::InsufficientFunds);
}
lockup
} }
StakeState::Initialized(authorized, lockup) => { StakeState::Initialized(meta) => {
authorized.check(signers, StakeAuthorize::Withdrawer)?; meta.authorized.check(signers, StakeAuthorize::Withdrawer)?;
lockup
(meta.lockup, meta.rent_exempt_reserve, false)
} }
StakeState::Uninitialized => { StakeState::Uninitialized => {
if self.signer_key().is_none() { if !signers.contains(&self.unsigned_key()) {
return Err(InstructionError::MissingRequiredSignature); return Err(InstructionError::MissingRequiredSignature);
} }
Lockup::default() // no lockup (Lockup::default(), 0, false) // no lockup, no restrictions
} }
_ => return Err(InstructionError::InvalidAccountData), _ => 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() { if lockup.slot > clock.slot && lockup.custodian != *to.unsigned_key() {
return Err(StakeError::LockupInForce.into()); 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); return Err(InstructionError::InsufficientFunds);
} }
@ -652,11 +787,15 @@ pub fn create_account(
stake_account stake_account
.set_state(&StakeState::Stake( .set_state(&StakeState::Stake(
Authorized { Meta {
staker: *authorized, rent_exempt_reserve: Rent::default()
withdrawer: *authorized, .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), Stake::new_bootstrap(lamports, voter_pubkey, &vote_state),
)) ))
.expect("set_state"); .expect("set_state");
@ -724,13 +863,13 @@ 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::Initialized( &StakeState::Initialized(Meta {
Authorized { authorized: Authorized {
staker: stake_pubkey, staker: stake_pubkey,
withdrawer: stake_pubkey, withdrawer: stake_pubkey,
}, },
Lockup::default(), ..Meta::default()
), }),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@ -743,13 +882,13 @@ mod tests {
let stake_state: StakeState = stake_keyed_account.state().unwrap(); let stake_state: StakeState = stake_keyed_account.state().unwrap();
assert_eq!( assert_eq!(
stake_state, stake_state,
StakeState::Initialized( StakeState::Initialized(Meta {
Authorized { authorized: Authorized {
staker: stake_pubkey, staker: stake_pubkey,
withdrawer: stake_pubkey, withdrawer: stake_pubkey,
}, },
Lockup::default(), ..Meta::default()
) })
); );
} }
@ -1162,7 +1301,7 @@ mod tests {
} }
#[test] #[test]
fn test_stake_lockup() { fn test_stake_initialize() {
let stake_pubkey = Pubkey::new_rand(); let stake_pubkey = Pubkey::new_rand();
let stake_lamports = 42; let stake_lamports = 42;
let mut stake_account = let mut stake_account =
@ -1171,32 +1310,45 @@ mod tests {
// unsigned keyed account // unsigned keyed account
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);
let custodian = Pubkey::new_rand(); let custodian = Pubkey::new_rand();
// not enough balance for rent...
assert_eq!( assert_eq!(
stake_keyed_account.initialize( stake_keyed_account.initialize(
&Authorized { &Authorized::default(),
staker: stake_pubkey, &Lockup::default(),
withdrawer: stake_pubkey &Rent {
}, lamports_per_byte_year: 42,
&Lockup { slot: 1, custodian } ..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(()) Ok(())
); );
// check that we see what we expect
// 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::Initialized( StakeState::Initialized(Meta {
Authorized { lockup: Lockup { slot: 1, custodian },
staker: stake_pubkey, ..Meta::auto(&stake_pubkey)
withdrawer: stake_pubkey })
},
Lockup { slot: 1, custodian }
)
); );
// 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!( assert_eq!(
stake_keyed_account.initialize(&Authorized::default(), &Lockup::default()), stake_keyed_account.initialize(
&Authorized::default(),
&Lockup::default(),
&Rent::default()
),
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
); );
} }
@ -1207,7 +1359,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::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()), &StakeState::Initialized(Meta::auto(&stake_pubkey)),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@ -1320,6 +1472,7 @@ mod tests {
.initialize( .initialize(
&Authorized::auto(&stake_pubkey), &Authorized::auto(&stake_pubkey),
&Lockup { slot: 0, custodian }, &Lockup { slot: 0, custodian },
&Rent::default(),
) )
.unwrap(); .unwrap();
@ -1428,7 +1581,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(
total_lamports, total_lamports,
&StakeState::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()), &StakeState::Initialized(Meta::auto(&stake_pubkey)),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@ -1516,10 +1669,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::Initialized( &StakeState::Initialized(Meta {
Authorized::auto(&stake_pubkey), lockup: Lockup { slot: 1, custodian },
Lockup { slot: 1, custodian }, ..Meta::auto(&stake_pubkey)
), }),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@ -1674,7 +1827,7 @@ mod tests {
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::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()), &StakeState::Initialized(Meta::auto(&stake_pubkey)),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &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] #[test]
fn test_authorize_lockup() { fn test_authorize_lockup() {
let stake_pubkey = Pubkey::new_rand(); let stake_pubkey = Pubkey::new_rand();
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::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()), &StakeState::Initialized(Meta::auto(&stake_pubkey)),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )
@ -1828,7 +2001,7 @@ mod tests {
stake_keyed_account.authorize(&stake_pubkey0, StakeAuthorize::Withdrawer, &signers), stake_keyed_account.authorize(&stake_pubkey0, StakeAuthorize::Withdrawer, &signers),
Ok(()) Ok(())
); );
if let StakeState::Initialized(authorized, _lockup) = if let StakeState::Initialized(Meta { authorized, .. }) =
StakeState::from(&stake_keyed_account.account).unwrap() StakeState::from(&stake_keyed_account.account).unwrap()
{ {
assert_eq!(authorized.staker, stake_pubkey0); assert_eq!(authorized.staker, stake_pubkey0);
@ -1852,7 +2025,7 @@ mod tests {
stake_keyed_account.authorize(&stake_pubkey2, StakeAuthorize::Staker, &signers0), stake_keyed_account.authorize(&stake_pubkey2, StakeAuthorize::Staker, &signers0),
Ok(()) Ok(())
); );
if let StakeState::Initialized(authorized, _lockup) = if let StakeState::Initialized(Meta { authorized, .. }) =
StakeState::from(&stake_keyed_account.account).unwrap() StakeState::from(&stake_keyed_account.account).unwrap()
{ {
assert_eq!(authorized.staker, stake_pubkey2); assert_eq!(authorized.staker, stake_pubkey2);
@ -1862,7 +2035,7 @@ mod tests {
stake_keyed_account.authorize(&stake_pubkey2, StakeAuthorize::Withdrawer, &signers0,), stake_keyed_account.authorize(&stake_pubkey2, StakeAuthorize::Withdrawer, &signers0,),
Ok(()) Ok(())
); );
if let StakeState::Initialized(authorized, _lockup) = if let StakeState::Initialized(Meta { authorized, .. }) =
StakeState::from(&stake_keyed_account.account).unwrap() StakeState::from(&stake_keyed_account.account).unwrap()
{ {
assert_eq!(authorized.staker, stake_pubkey2); 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] #[test]
fn test_authorize_delegated_stake() { fn test_authorize_delegated_stake() {
let stake_pubkey = Pubkey::new_rand(); let stake_pubkey = Pubkey::new_rand();
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::Initialized(Authorized::auto(&stake_pubkey), Lockup::default()), &StakeState::Initialized(Meta::auto(&stake_pubkey)),
std::mem::size_of::<StakeState>(), std::mem::size_of::<StakeState>(),
&id(), &id(),
) )

View File

@ -111,7 +111,7 @@ fn test_stake_account_delegate() {
// Test that correct lamports are staked // Test that correct lamports are staked
let account = bank.get_account(&staker_pubkey).expect("account not found"); let account = bank.get_account(&staker_pubkey).expect("account not found");
let stake_state = account.state().expect("couldn't unpack account data"); 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); assert_eq!(stake.stake, 1_000_000);
} else { } else {
assert!(false, "wrong account type found") assert!(false, "wrong account type found")
@ -134,7 +134,7 @@ fn test_stake_account_delegate() {
// Test that lamports are still staked // Test that lamports are still staked
let account = bank.get_account(&staker_pubkey).expect("account not found"); let account = bank.get_account(&staker_pubkey).expect("account not found");
let stake_state = account.state().expect("couldn't unpack account data"); 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); assert_eq!(stake.stake, 1_000_000);
} else { } else {
assert!(false, "wrong account type found") assert!(false, "wrong account type found")

View File

@ -72,6 +72,15 @@ pub type Segment = u64;
/// some number of Slots. /// some number of Slots.
pub type Epoch = u64; 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -1,10 +1,13 @@
//! This account contains the clock slot, epoch, and stakers_epoch //! This account contains the clock slot, epoch, and stakers_epoch
//! //!
pub use crate::clock::Clock;
use crate::account::Account; use crate::{
use crate::account_info::AccountInfo; account::Account,
use crate::clock::{Epoch, Segment, Slot}; account_info::AccountInfo,
use crate::sysvar; clock::{Epoch, Segment, Slot},
sysvar,
};
use bincode::serialized_size; use bincode::serialized_size;
const ID: [u8; 32] = [ const ID: [u8; 32] = [
@ -14,15 +17,6 @@ const ID: [u8; 32] = [
crate::solana_sysvar_id!(ID, "SysvarC1ock11111111111111111111111111111111"); 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 { impl Clock {
pub fn size_of() -> usize { pub fn size_of() -> usize {
serialized_size(&Self::default()).unwrap() as usize serialized_size(&Self::default()).unwrap() as usize