parent
3a616de47b
commit
bc88180058
|
@ -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);
|
||||||
|
|
|
@ -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(*stake_pubkey, false),
|
||||||
AccountMeta::new(sysvar::rent::id(), 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(
|
||||||
|
|
|
@ -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) => {
|
||||||
|
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
||||||
let stake = Stake::new(
|
let stake = Stake::new(
|
||||||
self.account.lamports,
|
self.account
|
||||||
|
.lamports
|
||||||
|
.saturating_sub(meta.rent_exempt_reserve), // can't stake the rent ;)
|
||||||
vote_account.unsigned_key(),
|
vote_account.unsigned_key(),
|
||||||
&vote_account.state()?,
|
&vote_account.state()?,
|
||||||
clock.epoch,
|
clock.epoch,
|
||||||
config,
|
config,
|
||||||
);
|
);
|
||||||
|
self.set_state(&StakeState::Stake(meta, stake))
|
||||||
self.set_state(&StakeState::Stake(authorized, lockup, stake))
|
}
|
||||||
} else if let StakeState::Stake(authorized, lockup, mut stake) = self.state()? {
|
StakeState::Stake(meta, mut stake) => {
|
||||||
authorized.check(signers, StakeAuthorize::Staker)?;
|
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
||||||
stake.redelegate(vote_account.unsigned_key(), &vote_account.state()?, &clock)?;
|
stake.redelegate(vote_account.unsigned_key(), &vote_account.state()?, &clock)?;
|
||||||
self.set_state(&StakeState::Stake(authorized, lockup, stake))
|
self.set_state(&StakeState::Stake(meta, stake))
|
||||||
} else {
|
}
|
||||||
Err(InstructionError::InvalidAccountData)
|
_ => 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(meta) => {
|
||||||
}
|
meta.authorized.check(signers, StakeAuthorize::Withdrawer)?;
|
||||||
StakeState::Initialized(authorized, lockup) => {
|
|
||||||
authorized.check(signers, StakeAuthorize::Withdrawer)?;
|
(meta.lockup, meta.rent_exempt_reserve, false)
|
||||||
lockup
|
|
||||||
}
|
}
|
||||||
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 {
|
||||||
|
rent_exempt_reserve: Rent::default()
|
||||||
|
.minimum_balance(std::mem::size_of::<StakeState>()),
|
||||||
|
authorized: Authorized {
|
||||||
staker: *authorized,
|
staker: *authorized,
|
||||||
withdrawer: *authorized,
|
withdrawer: *authorized,
|
||||||
},
|
},
|
||||||
Lockup::default(),
|
lockup: 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(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue