use {
crate::{helpers::*, id, PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH, PSEUDO_RENT_EXEMPT_RESERVE},
solana_account_info::{next_account_info, AccountInfo},
solana_clock::Clock,
solana_cpi::set_return_data,
solana_msg::msg,
solana_program_error::{ProgramError, ProgramResult},
solana_pubkey::Pubkey,
solana_rent::Rent,
solana_stake_interface::{
error::StakeError,
instruction::{
AuthorizeCheckedWithSeedArgs, AuthorizeWithSeedArgs, LockupArgs, LockupCheckedArgs,
StakeInstruction,
},
stake_flags::StakeFlags,
state::{Authorized, Lockup, Meta, StakeAuthorize, StakeStateV2},
sysvar::stake_history::StakeHistorySysvar,
tools::{acceptable_reference_epoch_credits, eligible_for_deactivate_delinquent},
},
solana_sysvar::{epoch_rewards::EpochRewards, Sysvar},
solana_sysvar_id::SysvarId,
solana_vote_interface::{program as solana_vote_program, state::VoteStateV4},
std::{collections::HashSet, mem::MaybeUninit},
};
fn get_vote_state(vote_account_info: &AccountInfo) -> Result<Box<VoteStateV4>, ProgramError> {
if *vote_account_info.owner != solana_vote_program::id() {
return Err(ProgramError::IncorrectProgramId);
}
let mut vote_state = Box::new(MaybeUninit::uninit());
VoteStateV4::deserialize_into_uninit(
&vote_account_info.try_borrow_data()?,
vote_state.as_mut(),
vote_account_info.key,
)
.map_err(|_| ProgramError::InvalidAccountData)?;
let vote_state = unsafe { vote_state.assume_init() };
Ok(vote_state)
}
fn get_stake_state(stake_account_info: &AccountInfo) -> Result<StakeStateV2, ProgramError> {
if *stake_account_info.owner != id() {
return Err(ProgramError::InvalidAccountOwner);
}
stake_account_info
.deserialize_data()
.map_err(|_| ProgramError::InvalidAccountData)
}
fn set_stake_state(stake_account_info: &AccountInfo, new_state: &StakeStateV2) -> ProgramResult {
let serialized_size =
bincode::serialized_size(new_state).map_err(|_| ProgramError::InvalidAccountData)?;
if serialized_size > stake_account_info.data_len() as u64 {
return Err(ProgramError::AccountDataTooSmall);
}
bincode::serialize_into(&mut stake_account_info.data.borrow_mut()[..], new_state)
.map_err(|_| ProgramError::InvalidAccountData)
}
fn relocate_lamports(
source_account_info: &AccountInfo,
destination_account_info: &AccountInfo,
lamports: u64,
) -> ProgramResult {
{
let mut source_lamports = source_account_info.try_borrow_mut_lamports()?;
**source_lamports = source_lamports
.checked_sub(lamports)
.ok_or(ProgramError::InsufficientFunds)?;
}
{
let mut destination_lamports = destination_account_info.try_borrow_mut_lamports()?;
**destination_lamports = destination_lamports
.checked_add(lamports)
.ok_or(ProgramError::ArithmeticOverflow)?;
}
Ok(())
}
fn collect_signers(accounts: &[AccountInfo]) -> HashSet<Pubkey> {
let mut signers = HashSet::new();
for account in accounts {
if account.is_signer {
signers.insert(*account.key);
}
}
signers
}
fn collect_signers_checked<'a>(
authority_info: Option<&'a AccountInfo>,
custodian_info: Option<&'a AccountInfo>,
) -> Result<(HashSet<Pubkey>, Option<&'a Pubkey>), ProgramError> {
let mut signers = HashSet::new();
if let Some(authority_info) = authority_info {
if authority_info.is_signer {
signers.insert(*authority_info.key);
} else {
return Err(ProgramError::MissingRequiredSignature);
}
}
let custodian = if let Some(custodian_info) = custodian_info {
if custodian_info.is_signer {
signers.insert(*custodian_info.key);
Some(custodian_info.key)
} else {
return Err(ProgramError::MissingRequiredSignature);
}
} else {
None
};
Ok((signers, custodian))
}
fn do_initialize(
stake_account_info: &AccountInfo,
authorized: Authorized,
lockup: Lockup,
) -> ProgramResult {
if stake_account_info.data_len() != StakeStateV2::size_of() {
return Err(ProgramError::InvalidAccountData);
}
if let StakeStateV2::Uninitialized = get_stake_state(stake_account_info)? {
let rent = Rent::get()?;
let rent_exempt_reserve = rent.minimum_balance(stake_account_info.data_len());
if stake_account_info.lamports() >= rent_exempt_reserve {
let stake_state = StakeStateV2::Initialized(Meta {
authorized,
lockup,
rent_exempt_reserve: PSEUDO_RENT_EXEMPT_RESERVE,
});
set_stake_state(stake_account_info, &stake_state)
} else {
Err(ProgramError::InsufficientFunds)
}
} else {
Err(ProgramError::InvalidAccountData)
}
}
fn do_authorize(
stake_account_info: &AccountInfo,
signers: &HashSet<Pubkey>,
new_authority: &Pubkey,
authority_type: StakeAuthorize,
custodian: Option<&Pubkey>,
) -> ProgramResult {
let clock = &Clock::get()?;
match get_stake_state(stake_account_info)? {
StakeStateV2::Initialized(mut meta) => {
meta.authorized
.authorize(
signers,
new_authority,
authority_type,
Some((&meta.lockup, clock, custodian)),
)
.map_err(to_program_error)?;
set_stake_state(stake_account_info, &StakeStateV2::Initialized(meta))
}
StakeStateV2::Stake(mut meta, stake, stake_flags) => {
meta.authorized
.authorize(
signers,
new_authority,
authority_type,
Some((&meta.lockup, clock, custodian)),
)
.map_err(to_program_error)?;
set_stake_state(
stake_account_info,
&StakeStateV2::Stake(meta, stake, stake_flags),
)
}
_ => Err(ProgramError::InvalidAccountData),
}
}
fn do_set_lockup(
stake_account_info: &AccountInfo,
signers: &HashSet<Pubkey>,
lockup: &LockupArgs,
clock: &Clock,
) -> ProgramResult {
match get_stake_state(stake_account_info)? {
StakeStateV2::Initialized(mut meta) => {
meta.set_lockup(lockup, signers, clock)
.map_err(to_program_error)?;
set_stake_state(stake_account_info, &StakeStateV2::Initialized(meta))
}
StakeStateV2::Stake(mut meta, stake, stake_flags) => {
meta.set_lockup(lockup, signers, clock)
.map_err(to_program_error)?;
set_stake_state(
stake_account_info,
&StakeStateV2::Stake(meta, stake, stake_flags),
)
}
_ => Err(ProgramError::InvalidAccountData),
}
}
fn move_stake_or_lamports_shared_checks(
source_stake_account_info: &AccountInfo,
move_amount: u64,
destination_stake_account_info: &AccountInfo,
stake_authority_info: &AccountInfo,
) -> Result<(MergeKind, MergeKind), ProgramError> {
let (signers, _) = collect_signers_checked(Some(stake_authority_info), None)?;
if *source_stake_account_info.key == *destination_stake_account_info.key {
return Err(ProgramError::InvalidInstructionData);
}
if !source_stake_account_info.is_writable || !destination_stake_account_info.is_writable {
return Err(ProgramError::InvalidInstructionData);
}
if move_amount == 0 {
return Err(ProgramError::InvalidArgument);
}
let clock = Clock::get()?;
let stake_history = StakeHistorySysvar(clock.epoch);
let source_merge_kind = MergeKind::get_if_mergeable(
&get_stake_state(source_stake_account_info)?,
source_stake_account_info.lamports(),
&clock,
&stake_history,
)?;
source_merge_kind
.meta()
.authorized
.check(&signers, StakeAuthorize::Staker)
.map_err(to_program_error)?;
let destination_merge_kind = MergeKind::get_if_mergeable(
&get_stake_state(destination_stake_account_info)?,
destination_stake_account_info.lamports(),
&clock,
&stake_history,
)?;
MergeKind::metas_can_merge(
source_merge_kind.meta(),
destination_merge_kind.meta(),
&clock,
)?;
Ok((source_merge_kind, destination_merge_kind))
}
pub struct Processor {}
impl Processor {
fn process_initialize(
accounts: &[AccountInfo],
authorized: Authorized,
lockup: Lockup,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let stake_account_info = next_account_info(account_info_iter)?;
do_initialize(stake_account_info, authorized, lockup)?;
Ok(())
}
fn process_authorize(
accounts: &[AccountInfo],
new_authority: Pubkey,
authority_type: StakeAuthorize,
) -> ProgramResult {
let signers = collect_signers(accounts);
let account_info_iter = &mut accounts.iter();
let stake_account_info = next_account_info(account_info_iter)?;
{
let branch_account = next_account_info(account_info_iter)?;
if Clock::check_id(branch_account.key) {
let _stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
} else {
let stake_or_withdraw_authority_info = branch_account;
if !stake_or_withdraw_authority_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
}
}
let option_lockup_authority_info = next_account_info(account_info_iter).ok();
let custodian = option_lockup_authority_info
.filter(|a| a.is_signer)
.map(|a| a.key);
do_authorize(
stake_account_info,
&signers,
&new_authority,
authority_type,
custodian,
)?;
Ok(())
}
fn process_delegate(accounts: &[AccountInfo]) -> ProgramResult {
let signers = collect_signers(accounts);
let account_info_iter = &mut accounts.iter();
let stake_account_info = next_account_info(account_info_iter)?;
let vote_account_info = next_account_info(account_info_iter)?;
{
let branch_account = next_account_info(account_info_iter)?;
if Clock::check_id(branch_account.key) {
let _stake_history_info = next_account_info(account_info_iter)?;
let _stake_config_info = next_account_info(account_info_iter)?;
} else {
let stake_authority_info = branch_account;
if !stake_authority_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
}
};
let rent = &Rent::get()?;
let clock = &Clock::get()?;
let stake_history = &StakeHistorySysvar(clock.epoch);
let vote_state = get_vote_state(vote_account_info)?;
let rent_exempt_reserve = rent.minimum_balance(stake_account_info.data_len());
match get_stake_state(stake_account_info)? {
StakeStateV2::Initialized(meta) => {
meta.authorized
.check(&signers, StakeAuthorize::Staker)
.map_err(to_program_error)?;
let ValidatedDelegatedInfo { stake_amount } =
validate_delegated_amount(stake_account_info, rent_exempt_reserve)?;
let stake = new_stake(
stake_amount,
vote_account_info.key,
vote_state.credits(),
clock.epoch,
);
set_stake_state(
stake_account_info,
&StakeStateV2::Stake(meta, stake, StakeFlags::empty()),
)
}
StakeStateV2::Stake(meta, mut stake, flags) => {
meta.authorized
.check(&signers, StakeAuthorize::Staker)
.map_err(to_program_error)?;
let ValidatedDelegatedInfo { stake_amount } =
validate_delegated_amount(stake_account_info, rent_exempt_reserve)?;
let effective_stake = stake.delegation.stake(
clock.epoch,
stake_history,
PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
);
if effective_stake == 0 {
stake = new_stake(
stake_amount,
vote_account_info.key,
vote_state.credits(),
clock.epoch,
);
} else if clock.epoch == stake.delegation.deactivation_epoch
&& stake.delegation.voter_pubkey == *vote_account_info.key
{
if stake_amount < stake.delegation.stake {
return Err(StakeError::InsufficientDelegation.into());
}
stake.delegation.deactivation_epoch = u64::MAX;
} else {
return Err(StakeError::TooSoonToRedelegate.into());
}
set_stake_state(stake_account_info, &StakeStateV2::Stake(meta, stake, flags))
}
_ => Err(ProgramError::InvalidAccountData),
}?;
Ok(())
}
fn process_split(accounts: &[AccountInfo], split_lamports: u64) -> ProgramResult {
let signers = collect_signers(accounts);
let account_info_iter = &mut accounts.iter();
let source_stake_account_info = next_account_info(account_info_iter)?;
let destination_stake_account_info = next_account_info(account_info_iter)?;
let rent = Rent::get()?;
let clock = Clock::get()?;
let stake_history = &StakeHistorySysvar(clock.epoch);
let minimum_delegation = crate::get_minimum_delegation();
if source_stake_account_info.key == destination_stake_account_info.key {
return Err(ProgramError::InvalidArgument);
}
if let StakeStateV2::Uninitialized = get_stake_state(destination_stake_account_info)? {
} else {
return Err(ProgramError::InvalidAccountData);
}
let source_lamport_balance = source_stake_account_info.lamports();
let destination_lamport_balance = destination_stake_account_info.lamports();
if split_lamports > source_lamport_balance {
return Err(ProgramError::InsufficientFunds);
}
if split_lamports == 0 {
return Err(ProgramError::InsufficientFunds);
}
let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
let destination_data_len = destination_stake_account_info.data_len();
if destination_data_len != StakeStateV2::size_of() {
return Err(ProgramError::InvalidAccountData);
}
let destination_rent_exempt_reserve = rent.minimum_balance(destination_data_len);
let source_stake_state = get_stake_state(source_stake_account_info)?;
let (is_active_or_activating, option_dest_meta) = match source_stake_state {
StakeStateV2::Stake(source_meta, source_stake, _) => {
source_meta
.authorized
.check(&signers, StakeAuthorize::Staker)
.map_err(to_program_error)?;
let source_status = source_stake.delegation.stake_activating_and_deactivating(
clock.epoch,
stake_history,
PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
);
let is_active_or_activating =
source_status.effective > 0 || source_status.activating > 0;
let mut dest_meta = source_meta;
dest_meta.rent_exempt_reserve = PSEUDO_RENT_EXEMPT_RESERVE;
(is_active_or_activating, Some(dest_meta))
}
StakeStateV2::Initialized(source_meta) => {
source_meta
.authorized
.check(&signers, StakeAuthorize::Staker)
.map_err(to_program_error)?;
let mut dest_meta = source_meta;
dest_meta.rent_exempt_reserve = PSEUDO_RENT_EXEMPT_RESERVE;
(false, Some(dest_meta))
}
StakeStateV2::Uninitialized => {
if !source_stake_account_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
(false, None)
}
StakeStateV2::RewardsPool => return Err(ProgramError::InvalidAccountData),
};
if split_lamports == source_lamport_balance {
let mut destination_stake_state = source_stake_state;
let delegation = match (&mut destination_stake_state, option_dest_meta) {
(StakeStateV2::Stake(meta, stake, _), Some(dest_meta)) => {
*meta = dest_meta;
if is_active_or_activating {
stake.delegation.stake
} else {
0
}
}
(StakeStateV2::Initialized(meta), Some(dest_meta)) => {
*meta = dest_meta;
0
}
(StakeStateV2::Uninitialized, None) => 0,
_ => unreachable!(),
};
if destination_lamport_balance
.saturating_add(split_lamports)
.saturating_sub(delegation)
< destination_rent_exempt_reserve
{
return Err(ProgramError::InsufficientFunds);
}
if is_active_or_activating && delegation < minimum_delegation {
return Err(StakeError::InsufficientDelegation.into());
}
set_stake_state(destination_stake_account_info, &destination_stake_state)?;
source_stake_account_info.resize(0)?;
relocate_lamports(
source_stake_account_info,
destination_stake_account_info,
split_lamports,
)?;
return Ok(());
}
if !is_active_or_activating {
let mut destination_stake_state = source_stake_state;
match (&mut destination_stake_state, option_dest_meta) {
(StakeStateV2::Stake(meta, _, _), Some(dest_meta))
| (StakeStateV2::Initialized(meta), Some(dest_meta)) => {
*meta = dest_meta;
}
(StakeStateV2::Uninitialized, None) => (),
_ => unreachable!(),
}
let post_source_lamports = source_lamport_balance
.checked_sub(split_lamports)
.ok_or(ProgramError::InsufficientFunds)?;
let post_destination_lamports = destination_lamport_balance
.checked_add(split_lamports)
.ok_or(ProgramError::ArithmeticOverflow)?;
if post_source_lamports < source_rent_exempt_reserve
|| post_destination_lamports < destination_rent_exempt_reserve
{
return Err(ProgramError::InsufficientFunds);
}
set_stake_state(destination_stake_account_info, &destination_stake_state)?;
relocate_lamports(
source_stake_account_info,
destination_stake_account_info,
split_lamports,
)?;
return Ok(());
}
match (source_stake_state, option_dest_meta) {
(StakeStateV2::Stake(source_meta, mut source_stake, stake_flags), Some(dest_meta)) => {
if destination_lamport_balance < destination_rent_exempt_reserve {
return Err(ProgramError::InsufficientFunds);
}
let mut dest_stake = source_stake;
source_stake.delegation.stake = source_stake
.delegation
.stake
.checked_sub(split_lamports)
.ok_or::<ProgramError>(StakeError::InsufficientDelegation.into())?;
if source_stake.delegation.stake < minimum_delegation {
return Err(StakeError::InsufficientDelegation.into());
}
if source_lamport_balance
.saturating_sub(split_lamports)
.saturating_sub(source_stake.delegation.stake)
< source_rent_exempt_reserve
{
return Err(ProgramError::InsufficientFunds);
}
dest_stake.delegation.stake = split_lamports;
if dest_stake.delegation.stake < minimum_delegation {
return Err(StakeError::InsufficientDelegation.into());
}
set_stake_state(
source_stake_account_info,
&StakeStateV2::Stake(source_meta, source_stake, stake_flags),
)?;
set_stake_state(
destination_stake_account_info,
&StakeStateV2::Stake(dest_meta, dest_stake, stake_flags),
)?;
relocate_lamports(
source_stake_account_info,
destination_stake_account_info,
split_lamports,
)?;
}
_ => unreachable!(),
}
Ok(())
}
fn process_withdraw(accounts: &[AccountInfo], withdraw_lamports: u64) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let source_stake_account_info = next_account_info(account_info_iter)?;
let destination_info = next_account_info(account_info_iter)?;
let withdraw_authority_info = {
let branch_account = next_account_info(account_info_iter)?;
if Clock::check_id(branch_account.key) {
let _stake_history_info = next_account_info(account_info_iter)?;
next_account_info(account_info_iter)?
} else {
let withdraw_authority_info = branch_account;
if !withdraw_authority_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
withdraw_authority_info
}
};
let option_lockup_authority_info = next_account_info(account_info_iter).ok();
let rent = &Rent::get()?;
let clock = &Clock::get()?;
let stake_history = &StakeHistorySysvar(clock.epoch);
if source_stake_account_info.key == destination_info.key {
return Err(ProgramError::InvalidArgument);
}
let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
let (signers, custodian) =
collect_signers_checked(Some(withdraw_authority_info), option_lockup_authority_info)?;
let (lockup, reserve, is_staked) = match get_stake_state(source_stake_account_info) {
Ok(StakeStateV2::Stake(meta, stake, _stake_flag)) => {
meta.authorized
.check(&signers, StakeAuthorize::Withdrawer)
.map_err(to_program_error)?;
let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
stake.delegation.stake(
clock.epoch,
stake_history,
PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
)
} else {
stake.delegation.stake
};
let staked_and_reserve = checked_add(staked, source_rent_exempt_reserve)?;
(meta.lockup, staked_and_reserve, staked != 0)
}
Ok(StakeStateV2::Initialized(meta)) => {
meta.authorized
.check(&signers, StakeAuthorize::Withdrawer)
.map_err(to_program_error)?;
(meta.lockup, source_rent_exempt_reserve, false)
}
Ok(StakeStateV2::Uninitialized) => {
if !signers.contains(source_stake_account_info.key) {
return Err(ProgramError::MissingRequiredSignature);
}
(Lockup::default(), 0, false) }
Err(e)
if e == ProgramError::InvalidAccountData
&& source_stake_account_info.data_len() == 0 =>
{
if !signers.contains(source_stake_account_info.key) {
return Err(ProgramError::MissingRequiredSignature);
}
(Lockup::default(), 0, false) }
Ok(StakeStateV2::RewardsPool) => return Err(ProgramError::InvalidAccountData),
Err(e) => return Err(e),
};
if lockup.is_in_force(clock, custodian) {
return Err(StakeError::LockupInForce.into());
}
let stake_account_lamports = source_stake_account_info.lamports();
if withdraw_lamports == stake_account_lamports {
if is_staked {
return Err(ProgramError::InsufficientFunds);
}
source_stake_account_info.resize(0)?;
} else {
let withdraw_lamports_and_reserve = checked_add(withdraw_lamports, reserve)?;
if withdraw_lamports_and_reserve > stake_account_lamports {
return Err(ProgramError::InsufficientFunds);
}
}
relocate_lamports(
source_stake_account_info,
destination_info,
withdraw_lamports,
)?;
Ok(())
}
fn process_deactivate(accounts: &[AccountInfo]) -> ProgramResult {
let signers = collect_signers(accounts);
let account_info_iter = &mut accounts.iter();
let stake_account_info = next_account_info(account_info_iter)?;
{
let branch_account = next_account_info(account_info_iter)?;
if Clock::check_id(branch_account.key) {
} else {
let stake_authority_info = branch_account;
if !stake_authority_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
}
}
let clock = &Clock::get()?;
match get_stake_state(stake_account_info)? {
StakeStateV2::Stake(meta, mut stake, stake_flags) => {
meta.authorized
.check(&signers, StakeAuthorize::Staker)
.map_err(to_program_error)?;
stake.deactivate(clock.epoch)?;
set_stake_state(
stake_account_info,
&StakeStateV2::Stake(meta, stake, stake_flags),
)
}
_ => Err(ProgramError::InvalidAccountData),
}?;
Ok(())
}
fn process_set_lockup(accounts: &[AccountInfo], lockup: LockupArgs) -> ProgramResult {
let signers = collect_signers(accounts);
let account_info_iter = &mut accounts.iter();
let stake_account_info = next_account_info(account_info_iter)?;
let clock = Clock::get()?;
do_set_lockup(stake_account_info, &signers, &lockup, &clock)?;
Ok(())
}
fn process_merge(accounts: &[AccountInfo]) -> ProgramResult {
let signers = collect_signers(accounts);
let account_info_iter = &mut accounts.iter();
let destination_stake_account_info = next_account_info(account_info_iter)?;
let source_stake_account_info = next_account_info(account_info_iter)?;
{
let branch_account = next_account_info(account_info_iter)?;
if Clock::check_id(branch_account.key) {
let _stake_history_info = next_account_info(account_info_iter)?;
} else {
let stake_authority_info = branch_account;
if !stake_authority_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
}
}
let clock = &Clock::get()?;
let stake_history = &StakeHistorySysvar(clock.epoch);
if source_stake_account_info.key == destination_stake_account_info.key {
return Err(ProgramError::InvalidArgument);
}
let source_lamports = source_stake_account_info.lamports();
msg!("Checking if destination stake is mergeable");
let destination_merge_kind = MergeKind::get_if_mergeable(
&get_stake_state(destination_stake_account_info)?,
destination_stake_account_info.lamports(),
clock,
stake_history,
)?;
destination_merge_kind
.meta()
.authorized
.check(&signers, StakeAuthorize::Staker)
.map_err(|_| ProgramError::MissingRequiredSignature)?;
msg!("Checking if source stake is mergeable");
let source_merge_kind = MergeKind::get_if_mergeable(
&get_stake_state(source_stake_account_info)?,
source_lamports,
clock,
stake_history,
)?;
msg!("Merging stake accounts");
if let Some(merged_state) = destination_merge_kind.merge(source_merge_kind, clock)? {
set_stake_state(destination_stake_account_info, &merged_state)?;
}
source_stake_account_info.resize(0)?;
relocate_lamports(
source_stake_account_info,
destination_stake_account_info,
source_lamports,
)?;
Ok(())
}
fn process_authorize_with_seed(
accounts: &[AccountInfo],
authorize_args: AuthorizeWithSeedArgs,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let stake_account_info = next_account_info(account_info_iter)?;
let stake_or_withdraw_authority_base_info = next_account_info(account_info_iter)?;
let option_lockup_authority_info = {
let branch_account = next_account_info(account_info_iter).ok();
if branch_account.is_some_and(|account| Clock::check_id(account.key)) {
next_account_info(account_info_iter).ok()
} else {
branch_account
}
};
let (mut signers, custodian) = collect_signers_checked(None, option_lockup_authority_info)?;
if stake_or_withdraw_authority_base_info.is_signer {
signers.insert(Pubkey::create_with_seed(
stake_or_withdraw_authority_base_info.key,
&authorize_args.authority_seed,
&authorize_args.authority_owner,
)?);
}
do_authorize(
stake_account_info,
&signers,
&authorize_args.new_authorized_pubkey,
authorize_args.stake_authorize,
custodian,
)?;
Ok(())
}
fn process_initialize_checked(accounts: &[AccountInfo]) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let stake_account_info = next_account_info(account_info_iter)?;
let stake_authority_info = {
let branch_account = next_account_info(account_info_iter)?;
if Rent::check_id(branch_account.key) {
next_account_info(account_info_iter)?
} else {
branch_account
}
};
let withdraw_authority_info = next_account_info(account_info_iter)?;
if !withdraw_authority_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
let authorized = Authorized {
staker: *stake_authority_info.key,
withdrawer: *withdraw_authority_info.key,
};
do_initialize(stake_account_info, authorized, Lockup::default())?;
Ok(())
}
fn process_authorize_checked(
accounts: &[AccountInfo],
authority_type: StakeAuthorize,
) -> ProgramResult {
let signers = collect_signers(accounts);
let account_info_iter = &mut accounts.iter();
let stake_account_info = next_account_info(account_info_iter)?;
{
let branch_account = next_account_info(account_info_iter)?;
if Clock::check_id(branch_account.key) {
let _old_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
} else {
let old_stake_or_withdraw_authority_info = branch_account;
if !old_stake_or_withdraw_authority_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
}
}
let new_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
let option_lockup_authority_info = next_account_info(account_info_iter).ok();
if !new_stake_or_withdraw_authority_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
let custodian = option_lockup_authority_info
.filter(|a| a.is_signer)
.map(|a| a.key);
do_authorize(
stake_account_info,
&signers,
new_stake_or_withdraw_authority_info.key,
authority_type,
custodian,
)?;
Ok(())
}
fn process_authorize_checked_with_seed(
accounts: &[AccountInfo],
authorize_args: AuthorizeCheckedWithSeedArgs,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let stake_account_info = next_account_info(account_info_iter)?;
let old_stake_or_withdraw_authority_base_info = next_account_info(account_info_iter)?;
let new_stake_or_withdraw_authority_info = {
let branch_account = next_account_info(account_info_iter)?;
if Clock::check_id(branch_account.key) {
next_account_info(account_info_iter)?
} else {
let new_stake_or_withdraw_authority_info = branch_account;
if !new_stake_or_withdraw_authority_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
new_stake_or_withdraw_authority_info
}
};
let option_lockup_authority_info = next_account_info(account_info_iter).ok();
let (mut signers, custodian) = collect_signers_checked(
Some(new_stake_or_withdraw_authority_info),
option_lockup_authority_info,
)?;
if old_stake_or_withdraw_authority_base_info.is_signer {
signers.insert(Pubkey::create_with_seed(
old_stake_or_withdraw_authority_base_info.key,
&authorize_args.authority_seed,
&authorize_args.authority_owner,
)?);
}
do_authorize(
stake_account_info,
&signers,
new_stake_or_withdraw_authority_info.key,
authorize_args.stake_authorize,
custodian,
)?;
Ok(())
}
fn process_set_lockup_checked(
accounts: &[AccountInfo],
lockup_checked: LockupCheckedArgs,
) -> ProgramResult {
let signers = collect_signers(accounts);
let account_info_iter = &mut accounts.iter();
let stake_account_info = next_account_info(account_info_iter)?;
let _old_withdraw_or_lockup_authority_info = next_account_info(account_info_iter);
let option_new_lockup_authority_info = next_account_info(account_info_iter).ok();
let clock = Clock::get()?;
let custodian = match option_new_lockup_authority_info {
Some(new_lockup_authority_info) if new_lockup_authority_info.is_signer => {
Some(new_lockup_authority_info.key)
}
Some(_) => return Err(ProgramError::MissingRequiredSignature),
None => None,
};
let lockup = LockupArgs {
unix_timestamp: lockup_checked.unix_timestamp,
epoch: lockup_checked.epoch,
custodian: custodian.copied(),
};
do_set_lockup(stake_account_info, &signers, &lockup, &clock)?;
Ok(())
}
fn process_deactivate_delinquent(accounts: &[AccountInfo]) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let stake_account_info = next_account_info(account_info_iter)?;
let delinquent_vote_account_info = next_account_info(account_info_iter)?;
let reference_vote_account_info = next_account_info(account_info_iter)?;
let clock = Clock::get()?;
let delinquent_vote_state = get_vote_state(delinquent_vote_account_info)?;
let reference_vote_state = get_vote_state(reference_vote_account_info)?;
if !acceptable_reference_epoch_credits(&reference_vote_state.epoch_credits, clock.epoch) {
return Err(StakeError::InsufficientReferenceVotes.into());
}
if let StakeStateV2::Stake(meta, mut stake, stake_flags) =
get_stake_state(stake_account_info)?
{
if stake.delegation.voter_pubkey != *delinquent_vote_account_info.key {
return Err(StakeError::VoteAddressMismatch.into());
}
if eligible_for_deactivate_delinquent(&delinquent_vote_state.epoch_credits, clock.epoch)
{
stake.deactivate(clock.epoch)?;
set_stake_state(
stake_account_info,
&StakeStateV2::Stake(meta, stake, stake_flags),
)
} else {
Err(StakeError::MinimumDelinquentEpochsForDeactivationNotMet.into())
}
} else {
Err(ProgramError::InvalidAccountData)
}?;
Ok(())
}
fn process_move_stake(accounts: &[AccountInfo], move_amount: u64) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let source_stake_account_info = next_account_info(account_info_iter)?;
let destination_stake_account_info = next_account_info(account_info_iter)?;
let stake_authority_info = next_account_info(account_info_iter)?;
let rent = &Rent::get()?;
let (source_merge_kind, destination_merge_kind) = move_stake_or_lamports_shared_checks(
source_stake_account_info,
move_amount,
destination_stake_account_info,
stake_authority_info,
)?;
let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
let destination_rent_exempt_reserve =
rent.minimum_balance(destination_stake_account_info.data_len());
if source_stake_account_info.data_len() != StakeStateV2::size_of()
|| destination_stake_account_info.data_len() != StakeStateV2::size_of()
{
return Err(ProgramError::InvalidAccountData);
}
let MergeKind::FullyActive(source_meta, mut source_stake) = source_merge_kind else {
return Err(ProgramError::InvalidAccountData);
};
let minimum_delegation = crate::get_minimum_delegation();
let source_effective_stake = source_stake.delegation.stake;
let source_final_stake = source_effective_stake
.checked_sub(move_amount)
.ok_or(ProgramError::InvalidArgument)?;
if source_final_stake != 0 && source_final_stake < minimum_delegation {
return Err(ProgramError::InvalidArgument);
}
match destination_merge_kind {
MergeKind::FullyActive(destination_meta, mut destination_stake) => {
if source_stake.delegation.voter_pubkey != destination_stake.delegation.voter_pubkey
{
return Err(StakeError::VoteAddressMismatch.into());
}
let destination_effective_stake = destination_stake.delegation.stake;
let destination_final_stake = destination_effective_stake
.checked_add(move_amount)
.ok_or(ProgramError::ArithmeticOverflow)?;
if destination_final_stake < minimum_delegation {
return Err(ProgramError::InvalidArgument);
}
merge_delegation_stake_and_credits_observed(
&mut destination_stake,
move_amount,
source_stake.credits_observed,
)?;
set_stake_state(
destination_stake_account_info,
&StakeStateV2::Stake(destination_meta, destination_stake, StakeFlags::empty()),
)?;
}
MergeKind::Inactive(destination_meta, _, _) => {
if move_amount < minimum_delegation {
return Err(ProgramError::InvalidArgument);
}
let mut destination_stake = source_stake;
destination_stake.delegation.stake = move_amount;
set_stake_state(
destination_stake_account_info,
&StakeStateV2::Stake(destination_meta, destination_stake, StakeFlags::empty()),
)?;
}
_ => return Err(ProgramError::InvalidAccountData),
}
if source_final_stake == 0 {
set_stake_state(
source_stake_account_info,
&StakeStateV2::Initialized(source_meta),
)?;
} else {
source_stake.delegation.stake = source_final_stake;
set_stake_state(
source_stake_account_info,
&StakeStateV2::Stake(source_meta, source_stake, StakeFlags::empty()),
)?;
}
relocate_lamports(
source_stake_account_info,
destination_stake_account_info,
move_amount,
)?;
if source_stake_account_info.lamports() < source_rent_exempt_reserve
|| destination_stake_account_info.lamports() < destination_rent_exempt_reserve
{
msg!("Delegation calculations violated lamport balance assumptions");
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
fn process_move_lamports(accounts: &[AccountInfo], move_amount: u64) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let source_stake_account_info = next_account_info(account_info_iter)?;
let destination_stake_account_info = next_account_info(account_info_iter)?;
let stake_authority_info = next_account_info(account_info_iter)?;
let rent = &Rent::get()?;
let (source_merge_kind, _) = move_stake_or_lamports_shared_checks(
source_stake_account_info,
move_amount,
destination_stake_account_info,
stake_authority_info,
)?;
let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
let source_free_lamports = match source_merge_kind {
MergeKind::FullyActive(_, source_stake) => source_stake_account_info
.lamports()
.saturating_sub(source_stake.delegation.stake)
.saturating_sub(source_rent_exempt_reserve),
MergeKind::Inactive(_, source_lamports, _) => {
source_lamports.saturating_sub(source_rent_exempt_reserve)
}
_ => return Err(ProgramError::InvalidAccountData),
};
if move_amount > source_free_lamports {
return Err(ProgramError::InvalidArgument);
}
relocate_lamports(
source_stake_account_info,
destination_stake_account_info,
move_amount,
)?;
Ok(())
}
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
if *program_id != id() {
return Err(ProgramError::IncorrectProgramId);
}
let epoch_rewards_active = EpochRewards::get()
.map(|epoch_rewards| epoch_rewards.active)
.unwrap_or(false);
let instruction =
bincode::deserialize(data).map_err(|_| ProgramError::InvalidInstructionData)?;
if epoch_rewards_active && !matches!(instruction, StakeInstruction::GetMinimumDelegation) {
return Err(StakeError::EpochRewardsActive.into());
}
match instruction {
StakeInstruction::Initialize(authorize, lockup) => {
msg!("Instruction: Initialize");
Self::process_initialize(accounts, authorize, lockup)
}
StakeInstruction::Authorize(new_authority, authority_type) => {
msg!("Instruction: Authorize");
Self::process_authorize(accounts, new_authority, authority_type)
}
StakeInstruction::DelegateStake => {
msg!("Instruction: DelegateStake");
Self::process_delegate(accounts)
}
StakeInstruction::Split(lamports) => {
msg!("Instruction: Split");
Self::process_split(accounts, lamports)
}
StakeInstruction::Withdraw(lamports) => {
msg!("Instruction: Withdraw");
Self::process_withdraw(accounts, lamports)
}
StakeInstruction::Deactivate => {
msg!("Instruction: Deactivate");
Self::process_deactivate(accounts)
}
StakeInstruction::SetLockup(lockup) => {
msg!("Instruction: SetLockup");
Self::process_set_lockup(accounts, lockup)
}
StakeInstruction::Merge => {
msg!("Instruction: Merge");
Self::process_merge(accounts)
}
StakeInstruction::AuthorizeWithSeed(args) => {
msg!("Instruction: AuthorizeWithSeed");
Self::process_authorize_with_seed(accounts, args)
}
StakeInstruction::InitializeChecked => {
msg!("Instruction: InitializeChecked");
Self::process_initialize_checked(accounts)
}
StakeInstruction::AuthorizeChecked(authority_type) => {
msg!("Instruction: AuthorizeChecked");
Self::process_authorize_checked(accounts, authority_type)
}
StakeInstruction::AuthorizeCheckedWithSeed(args) => {
msg!("Instruction: AuthorizeCheckedWithSeed");
Self::process_authorize_checked_with_seed(accounts, args)
}
StakeInstruction::SetLockupChecked(lockup_checked) => {
msg!("Instruction: SetLockupChecked");
Self::process_set_lockup_checked(accounts, lockup_checked)
}
StakeInstruction::GetMinimumDelegation => {
msg!("Instruction: GetMinimumDelegation");
let minimum_delegation = crate::get_minimum_delegation();
set_return_data(&minimum_delegation.to_le_bytes());
Ok(())
}
StakeInstruction::DeactivateDelinquent => {
msg!("Instruction: DeactivateDelinquent");
Self::process_deactivate_delinquent(accounts)
}
#[allow(deprecated)]
StakeInstruction::Redelegate => Err(ProgramError::InvalidInstructionData),
StakeInstruction::MoveStake(lamports) => {
msg!("Instruction: MoveStake");
Self::process_move_stake(accounts, lamports)
}
StakeInstruction::MoveLamports(lamports) => {
msg!("Instruction: MoveLamports");
Self::process_move_lamports(accounts, lamports)
}
}
}
}