Remove `KeyedAccount` in builtin program "stake" (#24210)
* Inline keyed_account_at_index() in all instructions of stake which have more than one KeyedAccount parameter, because these could cause a borrow collision. * Uses transaction_context.get_key_of_account_at_index() in stake. * Refactors stake::config::from to use BorrowedAccount instead of ReadableAccount. * Replaces KeyedAccount by BorrowedAccount in stake.
This commit is contained in:
parent
1882434c69
commit
bf13fb4c4b
|
@ -12,11 +12,12 @@ use {
|
|||
account::{AccountSharedData, ReadableAccount, WritableAccount},
|
||||
genesis_config::GenesisConfig,
|
||||
stake::config::{self, Config},
|
||||
transaction_context::BorrowedAccount,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn from<T: ReadableAccount>(account: &T) -> Option<Config> {
|
||||
get_config_data(account.data())
|
||||
pub fn from(account: &BorrowedAccount) -> Option<Config> {
|
||||
get_config_data(account.get_data())
|
||||
.ok()
|
||||
.and_then(|data| deserialize(data).ok())
|
||||
}
|
||||
|
@ -35,14 +36,3 @@ pub fn add_genesis_account(genesis_config: &mut GenesisConfig) -> u64 {
|
|||
|
||||
lamports
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {super::*, std::cell::RefCell};
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let account = RefCell::new(create_account(0, &Config::default()));
|
||||
assert_eq!(from(&account.borrow()), Some(Config::default()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,17 +13,38 @@ use {
|
|||
solana_sdk::{
|
||||
feature_set,
|
||||
instruction::InstructionError,
|
||||
keyed_account::keyed_account_at_index,
|
||||
program_utils::limited_deserialize,
|
||||
pubkey::Pubkey,
|
||||
stake::{
|
||||
instruction::{LockupArgs, StakeInstruction},
|
||||
program::id,
|
||||
state::{Authorized, Lockup},
|
||||
},
|
||||
sysvar::clock::Clock,
|
||||
transaction_context::{InstructionContext, TransactionContext},
|
||||
},
|
||||
};
|
||||
|
||||
fn get_optional_pubkey<'a>(
|
||||
transaction_context: &'a TransactionContext,
|
||||
instruction_context: &'a InstructionContext,
|
||||
index_in_instruction: usize,
|
||||
should_be_signer: bool,
|
||||
) -> Result<Option<&'a Pubkey>, InstructionError> {
|
||||
Ok(
|
||||
if instruction_context.get_number_of_accounts() > index_in_instruction {
|
||||
if should_be_signer && !instruction_context.is_signer(index_in_instruction)? {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
Some(transaction_context.get_key_of_account_at_index(
|
||||
instruction_context.get_index_in_transaction(index_in_instruction)?,
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn process_instruction(
|
||||
first_instruction_account: usize,
|
||||
invoke_context: &mut InvokeContext,
|
||||
|
@ -31,13 +52,11 @@ pub fn process_instruction(
|
|||
let transaction_context = &invoke_context.transaction_context;
|
||||
let instruction_context = transaction_context.get_current_instruction_context()?;
|
||||
let data = instruction_context.get_instruction_data();
|
||||
let keyed_accounts = invoke_context.get_keyed_accounts()?;
|
||||
|
||||
trace!("process_instruction: {:?}", data);
|
||||
trace!("keyed_accounts: {:?}", keyed_accounts);
|
||||
|
||||
let me = &keyed_account_at_index(keyed_accounts, first_instruction_account)?;
|
||||
if me.owner()? != id() {
|
||||
let mut me = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
|
||||
if *me.get_owner() != id() {
|
||||
return Err(InstructionError::InvalidAccountOwner);
|
||||
}
|
||||
|
||||
|
@ -45,7 +64,13 @@ pub fn process_instruction(
|
|||
match limited_deserialize(data)? {
|
||||
StakeInstruction::Initialize(authorized, lockup) => {
|
||||
let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?;
|
||||
initialize(me, &authorized, &lockup, &rent, &invoke_context.feature_set)
|
||||
initialize(
|
||||
&mut me,
|
||||
&authorized,
|
||||
&lockup,
|
||||
&rent,
|
||||
&invoke_context.feature_set,
|
||||
)
|
||||
}
|
||||
StakeInstruction::Authorize(authorized_pubkey, stake_authorize) => {
|
||||
let require_custodian_for_locked_stake_authorize = invoke_context
|
||||
|
@ -56,23 +81,25 @@ pub fn process_instruction(
|
|||
let clock =
|
||||
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?;
|
||||
instruction_context.check_number_of_instruction_accounts(3)?;
|
||||
let custodian =
|
||||
keyed_account_at_index(keyed_accounts, first_instruction_account + 3)
|
||||
.ok()
|
||||
.map(|ka| ka.unsigned_key());
|
||||
let custodian_pubkey = get_optional_pubkey(
|
||||
transaction_context,
|
||||
instruction_context,
|
||||
first_instruction_account + 3,
|
||||
false,
|
||||
)?;
|
||||
|
||||
authorize(
|
||||
me,
|
||||
&mut me,
|
||||
&signers,
|
||||
&authorized_pubkey,
|
||||
stake_authorize,
|
||||
require_custodian_for_locked_stake_authorize,
|
||||
&clock,
|
||||
custodian,
|
||||
custodian_pubkey,
|
||||
)
|
||||
} else {
|
||||
authorize(
|
||||
me,
|
||||
&mut me,
|
||||
&signers,
|
||||
&authorized_pubkey,
|
||||
stake_authorize,
|
||||
|
@ -84,35 +111,38 @@ pub fn process_instruction(
|
|||
}
|
||||
StakeInstruction::AuthorizeWithSeed(args) => {
|
||||
instruction_context.check_number_of_instruction_accounts(2)?;
|
||||
let authority_base =
|
||||
keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?;
|
||||
let require_custodian_for_locked_stake_authorize = invoke_context
|
||||
.feature_set
|
||||
.is_active(&feature_set::require_custodian_for_locked_stake_authorize::id());
|
||||
|
||||
if require_custodian_for_locked_stake_authorize {
|
||||
let clock =
|
||||
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?;
|
||||
let custodian =
|
||||
keyed_account_at_index(keyed_accounts, first_instruction_account + 3)
|
||||
.ok()
|
||||
.map(|ka| ka.unsigned_key());
|
||||
let custodian_pubkey = get_optional_pubkey(
|
||||
transaction_context,
|
||||
instruction_context,
|
||||
first_instruction_account + 3,
|
||||
false,
|
||||
)?;
|
||||
|
||||
authorize_with_seed(
|
||||
me,
|
||||
authority_base,
|
||||
transaction_context,
|
||||
instruction_context,
|
||||
&mut me,
|
||||
first_instruction_account + 1,
|
||||
&args.authority_seed,
|
||||
&args.authority_owner,
|
||||
&args.new_authorized_pubkey,
|
||||
args.stake_authorize,
|
||||
require_custodian_for_locked_stake_authorize,
|
||||
&clock,
|
||||
custodian,
|
||||
custodian_pubkey,
|
||||
)
|
||||
} else {
|
||||
authorize_with_seed(
|
||||
me,
|
||||
authority_base,
|
||||
transaction_context,
|
||||
instruction_context,
|
||||
&mut me,
|
||||
first_instruction_account + 1,
|
||||
&args.authority_seed,
|
||||
&args.authority_owner,
|
||||
&args.new_authorized_pubkey,
|
||||
|
@ -125,7 +155,6 @@ pub fn process_instruction(
|
|||
}
|
||||
StakeInstruction::DelegateStake => {
|
||||
instruction_context.check_number_of_instruction_accounts(2)?;
|
||||
let vote = keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?;
|
||||
let clock =
|
||||
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?;
|
||||
let stake_history = get_sysvar_with_account_check::stake_history(
|
||||
|
@ -134,25 +163,40 @@ pub fn process_instruction(
|
|||
3,
|
||||
)?;
|
||||
instruction_context.check_number_of_instruction_accounts(5)?;
|
||||
drop(me);
|
||||
let config_account =
|
||||
keyed_account_at_index(keyed_accounts, first_instruction_account + 4)?;
|
||||
if !config::check_id(config_account.unsigned_key()) {
|
||||
instruction_context.try_borrow_instruction_account(transaction_context, 4)?;
|
||||
if !config::check_id(config_account.get_key()) {
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
let config = config::from(&*config_account.try_account_ref()?)
|
||||
.ok_or(InstructionError::InvalidArgument)?;
|
||||
delegate(me, vote, &clock, &stake_history, &config, &signers)
|
||||
let config = config::from(&config_account).ok_or(InstructionError::InvalidArgument)?;
|
||||
drop(config_account);
|
||||
delegate(
|
||||
transaction_context,
|
||||
instruction_context,
|
||||
first_instruction_account,
|
||||
first_instruction_account + 1,
|
||||
&clock,
|
||||
&stake_history,
|
||||
&config,
|
||||
&signers,
|
||||
)
|
||||
}
|
||||
StakeInstruction::Split(lamports) => {
|
||||
instruction_context.check_number_of_instruction_accounts(2)?;
|
||||
let split_stake =
|
||||
&keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?;
|
||||
split(me, invoke_context, lamports, split_stake, &signers)
|
||||
drop(me);
|
||||
split(
|
||||
invoke_context,
|
||||
transaction_context,
|
||||
instruction_context,
|
||||
first_instruction_account,
|
||||
lamports,
|
||||
first_instruction_account + 1,
|
||||
&signers,
|
||||
)
|
||||
}
|
||||
StakeInstruction::Merge => {
|
||||
instruction_context.check_number_of_instruction_accounts(2)?;
|
||||
let source_stake =
|
||||
&keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?;
|
||||
let clock =
|
||||
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?;
|
||||
let stake_history = get_sysvar_with_account_check::stake_history(
|
||||
|
@ -160,10 +204,13 @@ pub fn process_instruction(
|
|||
instruction_context,
|
||||
3,
|
||||
)?;
|
||||
drop(me);
|
||||
merge(
|
||||
me,
|
||||
invoke_context,
|
||||
source_stake,
|
||||
transaction_context,
|
||||
instruction_context,
|
||||
first_instruction_account,
|
||||
first_instruction_account + 1,
|
||||
&clock,
|
||||
&stake_history,
|
||||
&signers,
|
||||
|
@ -171,7 +218,6 @@ pub fn process_instruction(
|
|||
}
|
||||
StakeInstruction::Withdraw(lamports) => {
|
||||
instruction_context.check_number_of_instruction_accounts(2)?;
|
||||
let to = &keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?;
|
||||
let clock =
|
||||
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?;
|
||||
let stake_history = get_sysvar_with_account_check::stake_history(
|
||||
|
@ -180,25 +226,32 @@ pub fn process_instruction(
|
|||
3,
|
||||
)?;
|
||||
instruction_context.check_number_of_instruction_accounts(5)?;
|
||||
drop(me);
|
||||
withdraw(
|
||||
me,
|
||||
transaction_context,
|
||||
instruction_context,
|
||||
first_instruction_account,
|
||||
lamports,
|
||||
to,
|
||||
first_instruction_account + 1,
|
||||
&clock,
|
||||
&stake_history,
|
||||
keyed_account_at_index(keyed_accounts, first_instruction_account + 4)?,
|
||||
keyed_account_at_index(keyed_accounts, first_instruction_account + 5).ok(),
|
||||
first_instruction_account + 4,
|
||||
if instruction_context.get_number_of_instruction_accounts() >= 6 {
|
||||
Some(first_instruction_account + 5)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
&invoke_context.feature_set,
|
||||
)
|
||||
}
|
||||
StakeInstruction::Deactivate => {
|
||||
let clock =
|
||||
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?;
|
||||
deactivate(me, &clock, &signers)
|
||||
deactivate(&mut me, &clock, &signers)
|
||||
}
|
||||
StakeInstruction::SetLockup(lockup) => {
|
||||
let clock = invoke_context.get_sysvar_cache().get_clock()?;
|
||||
set_lockup(me, &lockup, &signers, &clock)
|
||||
set_lockup(&mut me, &lockup, &signers, &clock)
|
||||
}
|
||||
StakeInstruction::InitializeChecked => {
|
||||
if invoke_context
|
||||
|
@ -206,21 +259,25 @@ pub fn process_instruction(
|
|||
.is_active(&feature_set::vote_stake_checked_instructions::id())
|
||||
{
|
||||
instruction_context.check_number_of_instruction_accounts(4)?;
|
||||
let staker_pubkey = transaction_context.get_key_of_account_at_index(
|
||||
instruction_context.get_index_in_transaction(first_instruction_account + 2)?,
|
||||
)?;
|
||||
let withdrawer_pubkey = transaction_context.get_key_of_account_at_index(
|
||||
instruction_context.get_index_in_transaction(first_instruction_account + 3)?,
|
||||
)?;
|
||||
if !instruction_context.is_signer(first_instruction_account + 3)? {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
|
||||
let authorized = Authorized {
|
||||
staker: *keyed_account_at_index(keyed_accounts, first_instruction_account + 2)?
|
||||
.unsigned_key(),
|
||||
withdrawer: *keyed_account_at_index(
|
||||
keyed_accounts,
|
||||
first_instruction_account + 3,
|
||||
)?
|
||||
.signer_key()
|
||||
.ok_or(InstructionError::MissingRequiredSignature)?,
|
||||
staker: *staker_pubkey,
|
||||
withdrawer: *withdrawer_pubkey,
|
||||
};
|
||||
|
||||
let rent =
|
||||
get_sysvar_with_account_check::rent(invoke_context, instruction_context, 1)?;
|
||||
initialize(
|
||||
me,
|
||||
&mut me,
|
||||
&authorized,
|
||||
&Lockup::default(),
|
||||
&rent,
|
||||
|
@ -238,25 +295,27 @@ pub fn process_instruction(
|
|||
let clock =
|
||||
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?;
|
||||
instruction_context.check_number_of_instruction_accounts(4)?;
|
||||
let _current_authority =
|
||||
keyed_account_at_index(keyed_accounts, first_instruction_account + 2)?;
|
||||
let authorized_pubkey =
|
||||
&keyed_account_at_index(keyed_accounts, first_instruction_account + 3)?
|
||||
.signer_key()
|
||||
.ok_or(InstructionError::MissingRequiredSignature)?;
|
||||
let custodian =
|
||||
keyed_account_at_index(keyed_accounts, first_instruction_account + 4)
|
||||
.ok()
|
||||
.map(|ka| ka.unsigned_key());
|
||||
let authorized_pubkey = transaction_context.get_key_of_account_at_index(
|
||||
instruction_context.get_index_in_transaction(first_instruction_account + 3)?,
|
||||
)?;
|
||||
if !instruction_context.is_signer(first_instruction_account + 3)? {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
let custodian_pubkey = get_optional_pubkey(
|
||||
transaction_context,
|
||||
instruction_context,
|
||||
first_instruction_account + 4,
|
||||
false,
|
||||
)?;
|
||||
|
||||
authorize(
|
||||
me,
|
||||
&mut me,
|
||||
&signers,
|
||||
authorized_pubkey,
|
||||
stake_authorize,
|
||||
true,
|
||||
&clock,
|
||||
custodian,
|
||||
custodian_pubkey,
|
||||
)
|
||||
} else {
|
||||
Err(InstructionError::InvalidInstructionData)
|
||||
|
@ -268,30 +327,34 @@ pub fn process_instruction(
|
|||
.is_active(&feature_set::vote_stake_checked_instructions::id())
|
||||
{
|
||||
instruction_context.check_number_of_instruction_accounts(2)?;
|
||||
let authority_base =
|
||||
keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?;
|
||||
let clock =
|
||||
get_sysvar_with_account_check::clock(invoke_context, instruction_context, 2)?;
|
||||
instruction_context.check_number_of_instruction_accounts(4)?;
|
||||
let authorized_pubkey =
|
||||
&keyed_account_at_index(keyed_accounts, first_instruction_account + 3)?
|
||||
.signer_key()
|
||||
.ok_or(InstructionError::MissingRequiredSignature)?;
|
||||
let custodian =
|
||||
keyed_account_at_index(keyed_accounts, first_instruction_account + 4)
|
||||
.ok()
|
||||
.map(|ka| ka.unsigned_key());
|
||||
let authorized_pubkey = transaction_context.get_key_of_account_at_index(
|
||||
instruction_context.get_index_in_transaction(first_instruction_account + 3)?,
|
||||
)?;
|
||||
if !instruction_context.is_signer(first_instruction_account + 3)? {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
let custodian_pubkey = get_optional_pubkey(
|
||||
transaction_context,
|
||||
instruction_context,
|
||||
first_instruction_account + 4,
|
||||
false,
|
||||
)?;
|
||||
|
||||
authorize_with_seed(
|
||||
me,
|
||||
authority_base,
|
||||
transaction_context,
|
||||
instruction_context,
|
||||
&mut me,
|
||||
first_instruction_account + 1,
|
||||
&args.authority_seed,
|
||||
&args.authority_owner,
|
||||
authorized_pubkey,
|
||||
args.stake_authorize,
|
||||
true,
|
||||
&clock,
|
||||
custodian,
|
||||
custodian_pubkey,
|
||||
)
|
||||
} else {
|
||||
Err(InstructionError::InvalidInstructionData)
|
||||
|
@ -302,25 +365,20 @@ pub fn process_instruction(
|
|||
.feature_set
|
||||
.is_active(&feature_set::vote_stake_checked_instructions::id())
|
||||
{
|
||||
let custodian = if let Ok(custodian) =
|
||||
keyed_account_at_index(keyed_accounts, first_instruction_account + 2)
|
||||
{
|
||||
Some(
|
||||
*custodian
|
||||
.signer_key()
|
||||
.ok_or(InstructionError::MissingRequiredSignature)?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let custodian_pubkey = get_optional_pubkey(
|
||||
transaction_context,
|
||||
instruction_context,
|
||||
first_instruction_account + 2,
|
||||
true,
|
||||
)?;
|
||||
|
||||
let lockup = LockupArgs {
|
||||
unix_timestamp: lockup_checked.unix_timestamp,
|
||||
epoch: lockup_checked.epoch,
|
||||
custodian,
|
||||
custodian: custodian_pubkey.cloned(),
|
||||
};
|
||||
let clock = invoke_context.get_sysvar_cache().get_clock()?;
|
||||
set_lockup(me, &lockup, &signers, &clock)
|
||||
set_lockup(&mut me, &lockup, &signers, &clock)
|
||||
} else {
|
||||
Err(InstructionError::InvalidInstructionData)
|
||||
}
|
||||
|
@ -335,6 +393,7 @@ pub fn process_instruction(
|
|||
|
||||
let minimum_delegation = crate::get_minimum_delegation(feature_set);
|
||||
let minimum_delegation = Vec::from(minimum_delegation.to_le_bytes());
|
||||
drop(me);
|
||||
invoke_context
|
||||
.transaction_context
|
||||
.set_return_data(id(), minimum_delegation)
|
||||
|
|
|
@ -12,13 +12,12 @@ use {
|
|||
solana_program_runtime::{ic_msg, invoke_context::InvokeContext},
|
||||
solana_sdk::{
|
||||
account::{AccountSharedData, ReadableAccount, WritableAccount},
|
||||
account_utils::{State, StateMut},
|
||||
account_utils::StateMut,
|
||||
clock::{Clock, Epoch},
|
||||
feature_set::{
|
||||
stake_merge_with_unmatched_credits_observed, stake_split_uses_rent_sysvar, FeatureSet,
|
||||
},
|
||||
instruction::{checked_add, InstructionError},
|
||||
keyed_account::KeyedAccount,
|
||||
pubkey::Pubkey,
|
||||
rent::{Rent, ACCOUNT_STORAGE_OVERHEAD},
|
||||
stake::{
|
||||
|
@ -27,6 +26,7 @@ use {
|
|||
program::id,
|
||||
},
|
||||
stake_history::{StakeHistory, StakeHistoryEntry},
|
||||
transaction_context::{BorrowedAccount, InstructionContext, TransactionContext},
|
||||
},
|
||||
solana_vote_program::vote_state::{VoteState, VoteStateVersions},
|
||||
std::{collections::HashSet, convert::TryFrom},
|
||||
|
@ -366,21 +366,21 @@ fn calculate_stake_rewards(
|
|||
}
|
||||
|
||||
pub fn initialize(
|
||||
stake_account: &KeyedAccount,
|
||||
stake_account: &mut BorrowedAccount,
|
||||
authorized: &Authorized,
|
||||
lockup: &Lockup,
|
||||
rent: &Rent,
|
||||
feature_set: &FeatureSet,
|
||||
) -> Result<(), InstructionError> {
|
||||
if stake_account.data_len()? != std::mem::size_of::<StakeState>() {
|
||||
if stake_account.get_data().len() != std::mem::size_of::<StakeState>() {
|
||||
return Err(InstructionError::InvalidAccountData);
|
||||
}
|
||||
if let StakeState::Uninitialized = stake_account.state()? {
|
||||
let rent_exempt_reserve = rent.minimum_balance(stake_account.data_len()?);
|
||||
if let StakeState::Uninitialized = stake_account.get_state()? {
|
||||
let rent_exempt_reserve = rent.minimum_balance(stake_account.get_data().len());
|
||||
let minimum_delegation = crate::get_minimum_delegation(feature_set);
|
||||
let minimum_balance = rent_exempt_reserve + minimum_delegation;
|
||||
|
||||
if stake_account.lamports()? >= minimum_balance {
|
||||
if stake_account.get_lamports() >= minimum_balance {
|
||||
stake_account.set_state(&StakeState::Initialized(Meta {
|
||||
rent_exempt_reserve,
|
||||
authorized: *authorized,
|
||||
|
@ -398,7 +398,7 @@ pub fn initialize(
|
|||
/// multiple times, but will implicitly withdraw authorization from the previously authorized
|
||||
/// staker. The default staker is the owner of the stake account's pubkey.
|
||||
pub fn authorize(
|
||||
stake_account: &KeyedAccount,
|
||||
stake_account: &mut BorrowedAccount,
|
||||
signers: &HashSet<Pubkey>,
|
||||
new_authority: &Pubkey,
|
||||
stake_authorize: StakeAuthorize,
|
||||
|
@ -406,7 +406,7 @@ pub fn authorize(
|
|||
clock: &Clock,
|
||||
custodian: Option<&Pubkey>,
|
||||
) -> Result<(), InstructionError> {
|
||||
match stake_account.state()? {
|
||||
match stake_account.get_state()? {
|
||||
StakeState::Stake(mut meta, stake) => {
|
||||
meta.authorized.authorize(
|
||||
signers,
|
||||
|
@ -437,9 +437,12 @@ pub fn authorize(
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn authorize_with_seed(
|
||||
stake_account: &KeyedAccount,
|
||||
authority_base: &KeyedAccount,
|
||||
transaction_context: &TransactionContext,
|
||||
instruction_context: &InstructionContext,
|
||||
stake_account: &mut BorrowedAccount,
|
||||
authority_base_index: usize,
|
||||
authority_seed: &str,
|
||||
authority_owner: &Pubkey,
|
||||
new_authority: &Pubkey,
|
||||
|
@ -449,7 +452,10 @@ pub fn authorize_with_seed(
|
|||
custodian: Option<&Pubkey>,
|
||||
) -> Result<(), InstructionError> {
|
||||
let mut signers = HashSet::default();
|
||||
if let Some(base_pubkey) = authority_base.signer_key() {
|
||||
if instruction_context.is_signer(authority_base_index)? {
|
||||
let base_pubkey = transaction_context.get_key_of_account_at_index(
|
||||
instruction_context.get_index_in_transaction(authority_base_index)?,
|
||||
)?;
|
||||
signers.insert(Pubkey::create_with_seed(
|
||||
base_pubkey,
|
||||
authority_seed,
|
||||
|
@ -468,26 +474,35 @@ pub fn authorize_with_seed(
|
|||
}
|
||||
|
||||
pub fn delegate(
|
||||
stake_account: &KeyedAccount,
|
||||
vote_account: &KeyedAccount,
|
||||
transaction_context: &TransactionContext,
|
||||
instruction_context: &InstructionContext,
|
||||
stake_account_index: usize,
|
||||
vote_account_index: usize,
|
||||
clock: &Clock,
|
||||
stake_history: &StakeHistory,
|
||||
config: &Config,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError> {
|
||||
if vote_account.owner()? != solana_vote_program::id() {
|
||||
let vote_account =
|
||||
instruction_context.try_borrow_account(transaction_context, vote_account_index)?;
|
||||
if *vote_account.get_owner() != solana_vote_program::id() {
|
||||
return Err(InstructionError::IncorrectProgramId);
|
||||
}
|
||||
let vote_pubkey = *vote_account.get_key();
|
||||
let vote_state = vote_account.get_state::<VoteStateVersions>();
|
||||
drop(vote_account);
|
||||
|
||||
match stake_account.state()? {
|
||||
let mut stake_account =
|
||||
instruction_context.try_borrow_account(transaction_context, stake_account_index)?;
|
||||
match stake_account.get_state()? {
|
||||
StakeState::Initialized(meta) => {
|
||||
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
||||
let ValidatedDelegatedInfo { stake_amount } =
|
||||
validate_delegated_amount(stake_account, &meta)?;
|
||||
validate_delegated_amount(&stake_account, &meta)?;
|
||||
let stake = new_stake(
|
||||
stake_amount,
|
||||
vote_account.unsigned_key(),
|
||||
&State::<VoteStateVersions>::state(vote_account)?.convert_to_current(),
|
||||
&vote_pubkey,
|
||||
&vote_state?.convert_to_current(),
|
||||
clock.epoch,
|
||||
config,
|
||||
);
|
||||
|
@ -496,12 +511,12 @@ pub fn delegate(
|
|||
StakeState::Stake(meta, mut stake) => {
|
||||
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
||||
let ValidatedDelegatedInfo { stake_amount } =
|
||||
validate_delegated_amount(stake_account, &meta)?;
|
||||
validate_delegated_amount(&stake_account, &meta)?;
|
||||
redelegate(
|
||||
&mut stake,
|
||||
stake_amount,
|
||||
vote_account.unsigned_key(),
|
||||
&State::<VoteStateVersions>::state(vote_account)?.convert_to_current(),
|
||||
&vote_pubkey,
|
||||
&vote_state?.convert_to_current(),
|
||||
clock,
|
||||
stake_history,
|
||||
config,
|
||||
|
@ -513,11 +528,11 @@ pub fn delegate(
|
|||
}
|
||||
|
||||
pub fn deactivate(
|
||||
stake_account: &KeyedAccount,
|
||||
stake_account: &mut BorrowedAccount,
|
||||
clock: &Clock,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError> {
|
||||
if let StakeState::Stake(meta, mut stake) = stake_account.state()? {
|
||||
if let StakeState::Stake(meta, mut stake) = stake_account.get_state()? {
|
||||
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
||||
stake.deactivate(clock.epoch)?;
|
||||
|
||||
|
@ -528,12 +543,12 @@ pub fn deactivate(
|
|||
}
|
||||
|
||||
pub fn set_lockup(
|
||||
stake_account: &KeyedAccount,
|
||||
stake_account: &mut BorrowedAccount,
|
||||
lockup: &LockupArgs,
|
||||
signers: &HashSet<Pubkey>,
|
||||
clock: &Clock,
|
||||
) -> Result<(), InstructionError> {
|
||||
match stake_account.state()? {
|
||||
match stake_account.get_state()? {
|
||||
StakeState::Initialized(mut meta) => {
|
||||
meta.set_lockup(lockup, signers, clock)?;
|
||||
stake_account.set_state(&StakeState::Initialized(meta))
|
||||
|
@ -547,32 +562,43 @@ pub fn set_lockup(
|
|||
}
|
||||
|
||||
pub fn split(
|
||||
stake_account: &KeyedAccount,
|
||||
invoke_context: &InvokeContext,
|
||||
transaction_context: &TransactionContext,
|
||||
instruction_context: &InstructionContext,
|
||||
stake_account_index: usize,
|
||||
lamports: u64,
|
||||
split: &KeyedAccount,
|
||||
split_index: usize,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError> {
|
||||
if split.owner()? != id() {
|
||||
let split = instruction_context.try_borrow_account(transaction_context, split_index)?;
|
||||
if *split.get_owner() != id() {
|
||||
return Err(InstructionError::IncorrectProgramId);
|
||||
}
|
||||
if split.data_len()? != std::mem::size_of::<StakeState>() {
|
||||
if split.get_data().len() != std::mem::size_of::<StakeState>() {
|
||||
return Err(InstructionError::InvalidAccountData);
|
||||
}
|
||||
if !matches!(split.state()?, StakeState::Uninitialized) {
|
||||
if !matches!(split.get_state()?, StakeState::Uninitialized) {
|
||||
return Err(InstructionError::InvalidAccountData);
|
||||
}
|
||||
if lamports > stake_account.lamports()? {
|
||||
let split_lamport_balance = split.get_lamports();
|
||||
drop(split);
|
||||
let stake_account =
|
||||
instruction_context.try_borrow_account(transaction_context, stake_account_index)?;
|
||||
if lamports > stake_account.get_lamports() {
|
||||
return Err(InstructionError::InsufficientFunds);
|
||||
}
|
||||
let stake_state = stake_account.get_state()?;
|
||||
drop(stake_account);
|
||||
|
||||
match stake_account.state()? {
|
||||
match stake_state {
|
||||
StakeState::Stake(meta, mut stake) => {
|
||||
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
||||
let validated_split_info = validate_split_amount(
|
||||
invoke_context,
|
||||
stake_account,
|
||||
split,
|
||||
transaction_context,
|
||||
instruction_context,
|
||||
stake_account_index,
|
||||
split_index,
|
||||
lamports,
|
||||
&meta,
|
||||
Some(&stake),
|
||||
|
@ -605,7 +631,7 @@ pub fn split(
|
|||
lamports.saturating_sub(
|
||||
validated_split_info
|
||||
.destination_rent_exempt_reserve
|
||||
.saturating_sub(split.lamports()?),
|
||||
.saturating_sub(split_lamport_balance),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
@ -613,19 +639,37 @@ pub fn split(
|
|||
let mut split_meta = meta;
|
||||
split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve;
|
||||
|
||||
let mut stake_account =
|
||||
instruction_context.try_borrow_account(transaction_context, stake_account_index)?;
|
||||
stake_account.set_state(&StakeState::Stake(meta, stake))?;
|
||||
drop(stake_account);
|
||||
let mut split =
|
||||
instruction_context.try_borrow_account(transaction_context, split_index)?;
|
||||
split.set_state(&StakeState::Stake(split_meta, split_stake))?;
|
||||
}
|
||||
StakeState::Initialized(meta) => {
|
||||
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
||||
let validated_split_info =
|
||||
validate_split_amount(invoke_context, stake_account, split, lamports, &meta, None)?;
|
||||
let validated_split_info = validate_split_amount(
|
||||
invoke_context,
|
||||
transaction_context,
|
||||
instruction_context,
|
||||
stake_account_index,
|
||||
split_index,
|
||||
lamports,
|
||||
&meta,
|
||||
None,
|
||||
)?;
|
||||
let mut split_meta = meta;
|
||||
split_meta.rent_exempt_reserve = validated_split_info.destination_rent_exempt_reserve;
|
||||
let mut split =
|
||||
instruction_context.try_borrow_account(transaction_context, split_index)?;
|
||||
split.set_state(&StakeState::Initialized(split_meta))?;
|
||||
}
|
||||
StakeState::Uninitialized => {
|
||||
if !signers.contains(stake_account.unsigned_key()) {
|
||||
let stake_pubkey = transaction_context.get_key_of_account_at_index(
|
||||
instruction_context.get_index_in_transaction(stake_account_index)?,
|
||||
)?;
|
||||
if !signers.contains(stake_pubkey) {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
}
|
||||
|
@ -633,54 +677,67 @@ pub fn split(
|
|||
}
|
||||
|
||||
// Deinitialize state upon zero balance
|
||||
if lamports == stake_account.lamports()? {
|
||||
let mut stake_account =
|
||||
instruction_context.try_borrow_account(transaction_context, stake_account_index)?;
|
||||
if lamports == stake_account.get_lamports() {
|
||||
stake_account.set_state(&StakeState::Uninitialized)?;
|
||||
}
|
||||
drop(stake_account);
|
||||
|
||||
split
|
||||
.try_account_ref_mut()?
|
||||
.checked_add_lamports(lamports)?;
|
||||
stake_account
|
||||
.try_account_ref_mut()?
|
||||
.checked_sub_lamports(lamports)?;
|
||||
let mut split = instruction_context.try_borrow_account(transaction_context, split_index)?;
|
||||
split.checked_add_lamports(lamports)?;
|
||||
drop(split);
|
||||
let mut stake_account =
|
||||
instruction_context.try_borrow_account(transaction_context, stake_account_index)?;
|
||||
stake_account.checked_sub_lamports(lamports)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn merge(
|
||||
stake_account: &KeyedAccount,
|
||||
invoke_context: &InvokeContext,
|
||||
source_account: &KeyedAccount,
|
||||
transaction_context: &TransactionContext,
|
||||
instruction_context: &InstructionContext,
|
||||
stake_account_index: usize,
|
||||
source_account_index: usize,
|
||||
clock: &Clock,
|
||||
stake_history: &StakeHistory,
|
||||
signers: &HashSet<Pubkey>,
|
||||
) -> Result<(), InstructionError> {
|
||||
let mut source_account =
|
||||
instruction_context.try_borrow_account(transaction_context, source_account_index)?;
|
||||
// Ensure source isn't spoofed
|
||||
if source_account.owner()? != id() {
|
||||
if *source_account.get_owner() != id() {
|
||||
return Err(InstructionError::IncorrectProgramId);
|
||||
}
|
||||
// Close the stake_account-reference loophole
|
||||
if source_account.unsigned_key() == stake_account.unsigned_key() {
|
||||
if instruction_context.get_index_in_transaction(stake_account_index)?
|
||||
== instruction_context.get_index_in_transaction(source_account_index)?
|
||||
{
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
let mut stake_account =
|
||||
instruction_context.try_borrow_account(transaction_context, stake_account_index)?;
|
||||
|
||||
ic_msg!(invoke_context, "Checking if destination stake is mergeable");
|
||||
let stake_merge_kind = MergeKind::get_if_mergeable(
|
||||
invoke_context,
|
||||
&stake_account.state()?,
|
||||
stake_account.lamports()?,
|
||||
&stake_account.get_state()?,
|
||||
stake_account.get_lamports(),
|
||||
clock,
|
||||
stake_history,
|
||||
)?;
|
||||
let meta = stake_merge_kind.meta();
|
||||
|
||||
// Authorized staker is allowed to split/merge accounts
|
||||
meta.authorized.check(signers, StakeAuthorize::Staker)?;
|
||||
stake_merge_kind
|
||||
.meta()
|
||||
.authorized
|
||||
.check(signers, StakeAuthorize::Staker)?;
|
||||
|
||||
ic_msg!(invoke_context, "Checking if source stake is mergeable");
|
||||
let source_merge_kind = MergeKind::get_if_mergeable(
|
||||
invoke_context,
|
||||
&source_account.state()?,
|
||||
source_account.lamports()?,
|
||||
&source_account.get_state()?,
|
||||
source_account.get_lamports(),
|
||||
clock,
|
||||
stake_history,
|
||||
)?;
|
||||
|
@ -694,33 +751,37 @@ pub fn merge(
|
|||
source_account.set_state(&StakeState::Uninitialized)?;
|
||||
|
||||
// Drain the source stake account
|
||||
let lamports = source_account.lamports()?;
|
||||
source_account
|
||||
.try_account_ref_mut()?
|
||||
.checked_sub_lamports(lamports)?;
|
||||
stake_account
|
||||
.try_account_ref_mut()?
|
||||
.checked_add_lamports(lamports)?;
|
||||
let lamports = source_account.get_lamports();
|
||||
source_account.checked_sub_lamports(lamports)?;
|
||||
stake_account.checked_add_lamports(lamports)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn withdraw(
|
||||
stake_account: &KeyedAccount,
|
||||
transaction_context: &TransactionContext,
|
||||
instruction_context: &InstructionContext,
|
||||
stake_account_index: usize,
|
||||
lamports: u64,
|
||||
to: &KeyedAccount,
|
||||
to_index: usize,
|
||||
clock: &Clock,
|
||||
stake_history: &StakeHistory,
|
||||
withdraw_authority: &KeyedAccount,
|
||||
custodian: Option<&KeyedAccount>,
|
||||
withdraw_authority_index: usize,
|
||||
custodian_index: Option<usize>,
|
||||
feature_set: &FeatureSet,
|
||||
) -> Result<(), InstructionError> {
|
||||
let withdraw_authority_pubkey = transaction_context.get_key_of_account_at_index(
|
||||
instruction_context.get_index_in_transaction(withdraw_authority_index)?,
|
||||
)?;
|
||||
if !instruction_context.is_signer(withdraw_authority_index)? {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
let mut signers = HashSet::new();
|
||||
let withdraw_authority_pubkey = withdraw_authority
|
||||
.signer_key()
|
||||
.ok_or(InstructionError::MissingRequiredSignature)?;
|
||||
signers.insert(*withdraw_authority_pubkey);
|
||||
|
||||
let (lockup, reserve, is_staked) = match stake_account.state()? {
|
||||
let mut stake_account =
|
||||
instruction_context.try_borrow_account(transaction_context, stake_account_index)?;
|
||||
let (lockup, reserve, is_staked) = match stake_account.get_state()? {
|
||||
StakeState::Stake(meta, stake) => {
|
||||
meta.authorized
|
||||
.check(&signers, StakeAuthorize::Withdrawer)?;
|
||||
|
@ -749,7 +810,7 @@ pub fn withdraw(
|
|||
(meta.lockup, reserve, false)
|
||||
}
|
||||
StakeState::Uninitialized => {
|
||||
if !signers.contains(stake_account.unsigned_key()) {
|
||||
if !signers.contains(stake_account.get_key()) {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
(Lockup::default(), 0, false) // no lockup, no restrictions
|
||||
|
@ -759,7 +820,17 @@ pub fn withdraw(
|
|||
|
||||
// verify that lockup has expired or that the withdrawal is signed by
|
||||
// the custodian, both epoch and unix_timestamp must have passed
|
||||
let custodian_pubkey = custodian.and_then(|keyed_account| keyed_account.signer_key());
|
||||
let custodian_pubkey = if let Some(custodian_index) = custodian_index {
|
||||
if instruction_context.is_signer(custodian_index)? {
|
||||
Some(transaction_context.get_key_of_account_at_index(
|
||||
instruction_context.get_index_in_transaction(custodian_index)?,
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if lockup.is_in_force(clock, custodian_pubkey) {
|
||||
return Err(StakeError::LockupInForce.into());
|
||||
}
|
||||
|
@ -767,27 +838,27 @@ pub fn withdraw(
|
|||
let lamports_and_reserve = checked_add(lamports, reserve)?;
|
||||
// if the stake is active, we mustn't allow the account to go away
|
||||
if is_staked // line coverage for branch coverage
|
||||
&& lamports_and_reserve > stake_account.lamports()?
|
||||
&& lamports_and_reserve > stake_account.get_lamports()
|
||||
{
|
||||
return Err(InstructionError::InsufficientFunds);
|
||||
}
|
||||
|
||||
if lamports != stake_account.lamports()? // not a full withdrawal
|
||||
&& lamports_and_reserve > stake_account.lamports()?
|
||||
if lamports != stake_account.get_lamports() // not a full withdrawal
|
||||
&& lamports_and_reserve > stake_account.get_lamports()
|
||||
{
|
||||
assert!(!is_staked);
|
||||
return Err(InstructionError::InsufficientFunds);
|
||||
}
|
||||
|
||||
// Deinitialize state upon zero balance
|
||||
if lamports == stake_account.lamports()? {
|
||||
if lamports == stake_account.get_lamports() {
|
||||
stake_account.set_state(&StakeState::Uninitialized)?;
|
||||
}
|
||||
|
||||
stake_account
|
||||
.try_account_ref_mut()?
|
||||
.checked_sub_lamports(lamports)?;
|
||||
to.try_account_ref_mut()?.checked_add_lamports(lamports)?;
|
||||
stake_account.checked_sub_lamports(lamports)?;
|
||||
drop(stake_account);
|
||||
let mut to = instruction_context.try_borrow_account(transaction_context, to_index)?;
|
||||
to.checked_add_lamports(lamports)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -800,10 +871,12 @@ struct ValidatedDelegatedInfo {
|
|||
/// Ensure the stake delegation amount is valid. This checks that the account meets the minimum
|
||||
/// balance requirements of delegated stake. If not, return an error.
|
||||
fn validate_delegated_amount(
|
||||
account: &KeyedAccount,
|
||||
account: &BorrowedAccount,
|
||||
meta: &Meta,
|
||||
) -> Result<ValidatedDelegatedInfo, InstructionError> {
|
||||
let stake_amount = account.lamports()?.saturating_sub(meta.rent_exempt_reserve); // can't stake the rent
|
||||
let stake_amount = account
|
||||
.get_lamports()
|
||||
.saturating_sub(meta.rent_exempt_reserve); // can't stake the rent
|
||||
Ok(ValidatedDelegatedInfo { stake_amount })
|
||||
}
|
||||
|
||||
|
@ -821,14 +894,24 @@ struct ValidatedSplitInfo {
|
|||
/// not, return an error.
|
||||
fn validate_split_amount(
|
||||
invoke_context: &InvokeContext,
|
||||
source_account: &KeyedAccount,
|
||||
destination_account: &KeyedAccount,
|
||||
transaction_context: &TransactionContext,
|
||||
instruction_context: &InstructionContext,
|
||||
source_account_index: usize,
|
||||
destination_account_index: usize,
|
||||
lamports: u64,
|
||||
source_meta: &Meta,
|
||||
source_stake: Option<&Stake>,
|
||||
) -> Result<ValidatedSplitInfo, InstructionError> {
|
||||
let source_lamports = source_account.lamports()?;
|
||||
let destination_lamports = destination_account.lamports()?;
|
||||
let source_account =
|
||||
instruction_context.try_borrow_account(transaction_context, source_account_index)?;
|
||||
let source_lamports = source_account.get_lamports();
|
||||
let source_data_len = source_account.get_data().len();
|
||||
drop(source_account);
|
||||
let destination_account =
|
||||
instruction_context.try_borrow_account(transaction_context, destination_account_index)?;
|
||||
let destination_lamports = destination_account.get_lamports();
|
||||
let destination_data_len = destination_account.get_data().len();
|
||||
drop(destination_account);
|
||||
|
||||
// Split amount has to be something
|
||||
if lamports == 0 {
|
||||
|
@ -869,12 +952,12 @@ fn validate_split_amount(
|
|||
.is_active(&stake_split_uses_rent_sysvar::ID)
|
||||
{
|
||||
let rent = invoke_context.get_sysvar_cache().get_rent()?;
|
||||
rent.minimum_balance(destination_account.data_len()?)
|
||||
rent.minimum_balance(destination_data_len)
|
||||
} else {
|
||||
calculate_split_rent_exempt_reserve(
|
||||
source_meta.rent_exempt_reserve,
|
||||
source_account.data_len()? as u64,
|
||||
destination_account.data_len()? as u64,
|
||||
source_data_len as u64,
|
||||
destination_data_len as u64,
|
||||
)
|
||||
};
|
||||
let destination_minimum_balance =
|
||||
|
|
Loading…
Reference in New Issue