#[deprecated( since = "1.8.0", note = "Please use `solana_sdk::stake::instruction` or `solana_program::stake::instruction` instead" )] pub use solana_sdk::stake::instruction::*; use { crate::{config, stake_state::StakeAccount}, log::*, solana_program_runtime::invoke_context::InvokeContext, solana_sdk::{ feature_set, instruction::InstructionError, keyed_account::{from_keyed_account, get_signers, keyed_account_at_index}, program_utils::limited_deserialize, stake::{ instruction::StakeInstruction, program::id, state::{Authorized, Lockup}, }, sysvar::{clock::Clock, rent::Rent, stake_history::StakeHistory}, }, }; pub fn process_instruction( first_instruction_account: usize, data: &[u8], invoke_context: &mut InvokeContext, ) -> Result<(), InstructionError> { 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() { return Err(InstructionError::InvalidAccountOwner); } let signers = get_signers(&keyed_accounts[first_instruction_account..]); match limited_deserialize(data)? { StakeInstruction::Initialize(authorized, lockup) => me.initialize( &authorized, &lockup, &from_keyed_account::(keyed_account_at_index( keyed_accounts, first_instruction_account + 1, )?)?, ), StakeInstruction::Authorize(authorized_pubkey, stake_authorize) => { 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 = from_keyed_account::(keyed_account_at_index( keyed_accounts, first_instruction_account + 1, )?)?; let _current_authority = keyed_account_at_index(keyed_accounts, first_instruction_account + 2)?; let custodian = keyed_account_at_index(keyed_accounts, first_instruction_account + 3) .ok() .map(|ka| ka.unsigned_key()); me.authorize( &signers, &authorized_pubkey, stake_authorize, require_custodian_for_locked_stake_authorize, &clock, custodian, ) } else { me.authorize( &signers, &authorized_pubkey, stake_authorize, require_custodian_for_locked_stake_authorize, &Clock::default(), None, ) } } StakeInstruction::AuthorizeWithSeed(args) => { 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 = from_keyed_account::(keyed_account_at_index( keyed_accounts, first_instruction_account + 2, )?)?; let custodian = keyed_account_at_index(keyed_accounts, first_instruction_account + 3) .ok() .map(|ka| ka.unsigned_key()); me.authorize_with_seed( authority_base, &args.authority_seed, &args.authority_owner, &args.new_authorized_pubkey, args.stake_authorize, require_custodian_for_locked_stake_authorize, &clock, custodian, ) } else { me.authorize_with_seed( authority_base, &args.authority_seed, &args.authority_owner, &args.new_authorized_pubkey, args.stake_authorize, require_custodian_for_locked_stake_authorize, &Clock::default(), None, ) } } StakeInstruction::DelegateStake => { let can_reverse_deactivation = invoke_context .feature_set .is_active(&feature_set::stake_program_v4::id()); let vote = keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; me.delegate( vote, &from_keyed_account::(keyed_account_at_index( keyed_accounts, first_instruction_account + 2, )?)?, &from_keyed_account::(keyed_account_at_index( keyed_accounts, first_instruction_account + 3, )?)?, &config::from_keyed_account(keyed_account_at_index( keyed_accounts, first_instruction_account + 4, )?)?, &signers, can_reverse_deactivation, ) } StakeInstruction::Split(lamports) => { let split_stake = &keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; me.split(lamports, split_stake, &signers) } StakeInstruction::Merge => { let source_stake = &keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; let can_merge_expired_lockups = invoke_context .feature_set .is_active(&feature_set::stake_program_v4::id()); me.merge( invoke_context, source_stake, &from_keyed_account::(keyed_account_at_index( keyed_accounts, first_instruction_account + 2, )?)?, &from_keyed_account::(keyed_account_at_index( keyed_accounts, first_instruction_account + 3, )?)?, &signers, can_merge_expired_lockups, ) } StakeInstruction::Withdraw(lamports) => { let to = &keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; me.withdraw( lamports, to, &from_keyed_account::(keyed_account_at_index( keyed_accounts, first_instruction_account + 2, )?)?, &from_keyed_account::(keyed_account_at_index( keyed_accounts, first_instruction_account + 3, )?)?, keyed_account_at_index(keyed_accounts, first_instruction_account + 4)?, keyed_account_at_index(keyed_accounts, first_instruction_account + 5).ok(), invoke_context .feature_set .is_active(&feature_set::stake_program_v4::id()), ) } StakeInstruction::Deactivate => me.deactivate( &from_keyed_account::(keyed_account_at_index( keyed_accounts, first_instruction_account + 1, )?)?, &signers, ), StakeInstruction::SetLockup(lockup) => { let clock = if invoke_context .feature_set .is_active(&feature_set::stake_program_v4::id()) { Some(invoke_context.get_sysvar_cache().get_clock()?) } else { None }; me.set_lockup(&lockup, &signers, clock.as_deref()) } StakeInstruction::InitializeChecked => { if invoke_context .feature_set .is_active(&feature_set::vote_stake_checked_instructions::id()) { 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)?, }; me.initialize( &authorized, &Lockup::default(), &from_keyed_account::(keyed_account_at_index( keyed_accounts, first_instruction_account + 1, )?)?, ) } else { Err(InstructionError::InvalidInstructionData) } } StakeInstruction::AuthorizeChecked(stake_authorize) => { if invoke_context .feature_set .is_active(&feature_set::vote_stake_checked_instructions::id()) { let clock = from_keyed_account::(keyed_account_at_index( keyed_accounts, first_instruction_account + 1, )?)?; 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()); me.authorize( &signers, authorized_pubkey, stake_authorize, true, &clock, custodian, ) } else { Err(InstructionError::InvalidInstructionData) } } StakeInstruction::AuthorizeCheckedWithSeed(args) => { if invoke_context .feature_set .is_active(&feature_set::vote_stake_checked_instructions::id()) { let authority_base = keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?; let clock = from_keyed_account::(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()); me.authorize_with_seed( authority_base, &args.authority_seed, &args.authority_owner, authorized_pubkey, args.stake_authorize, true, &clock, custodian, ) } else { Err(InstructionError::InvalidInstructionData) } } StakeInstruction::SetLockupChecked(lockup_checked) => { if invoke_context .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 lockup = LockupArgs { unix_timestamp: lockup_checked.unix_timestamp, epoch: lockup_checked.epoch, custodian, }; let clock = Some(invoke_context.get_sysvar_cache().get_clock()?); me.set_lockup(&lockup, &signers, clock.as_deref()) } else { Err(InstructionError::InvalidInstructionData) } } } } #[cfg(test)] mod tests { use { super::*, crate::stake_state::{Meta, StakeState}, bincode::serialize, solana_program_runtime::{ invoke_context::{mock_process_instruction, mock_process_instruction_with_sysvars}, sysvar_cache::SysvarCache, }, solana_sdk::{ account::{self, AccountSharedData}, instruction::{AccountMeta, Instruction}, pubkey::Pubkey, rent::Rent, stake::{ config as stake_config, instruction::{self, LockupArgs}, state::{Authorized, Lockup, StakeAuthorize}, }, sysvar::{self, stake_history::StakeHistory}, }, std::str::FromStr, }; fn create_default_account() -> AccountSharedData { AccountSharedData::new(0, 0, &Pubkey::new_unique()) } fn create_default_stake_account() -> AccountSharedData { AccountSharedData::new(0, 0, &id()) } fn invalid_stake_state_pubkey() -> Pubkey { Pubkey::from_str("BadStake11111111111111111111111111111111111").unwrap() } fn invalid_vote_state_pubkey() -> Pubkey { Pubkey::from_str("BadVote111111111111111111111111111111111111").unwrap() } fn spoofed_stake_state_pubkey() -> Pubkey { Pubkey::from_str("SpoofedStake1111111111111111111111111111111").unwrap() } fn spoofed_stake_program_id() -> Pubkey { Pubkey::from_str("Spoofed111111111111111111111111111111111111").unwrap() } fn process_instruction( instruction_data: &[u8], transaction_accounts: Vec<(Pubkey, AccountSharedData)>, instruction_accounts: Vec, expected_result: Result<(), InstructionError>, ) -> Vec { mock_process_instruction( &id(), Vec::new(), instruction_data, transaction_accounts, instruction_accounts, expected_result, super::process_instruction, ) } fn process_instruction_as_one_arg( instruction: &Instruction, expected_result: Result<(), InstructionError>, ) -> Vec { let transaction_accounts = instruction .accounts .iter() .map(|meta| { ( meta.pubkey, if sysvar::clock::check_id(&meta.pubkey) { account::create_account_shared_data_for_test( &sysvar::clock::Clock::default(), ) } else if sysvar::rewards::check_id(&meta.pubkey) { account::create_account_shared_data_for_test( &sysvar::rewards::Rewards::new(0.0), ) } else if sysvar::stake_history::check_id(&meta.pubkey) { account::create_account_shared_data_for_test(&StakeHistory::default()) } else if stake_config::check_id(&meta.pubkey) { config::create_account(0, &stake_config::Config::default()) } else if sysvar::rent::check_id(&meta.pubkey) { account::create_account_shared_data_for_test(&Rent::default()) } else if meta.pubkey == invalid_stake_state_pubkey() { AccountSharedData::new(0, 0, &id()) } else if meta.pubkey == invalid_vote_state_pubkey() { AccountSharedData::new(0, 0, &solana_vote_program::id()) } else if meta.pubkey == spoofed_stake_state_pubkey() { AccountSharedData::new(0, 0, &spoofed_stake_program_id()) } else { AccountSharedData::new(0, 0, &id()) }, ) }) .collect(); let mut sysvar_cache = SysvarCache::default(); sysvar_cache.set_clock(Clock::default()); mock_process_instruction_with_sysvars( &id(), Vec::new(), &instruction.data, transaction_accounts, instruction.accounts.clone(), expected_result, &sysvar_cache, super::process_instruction, ) } #[test] fn test_stake_process_instruction() { process_instruction_as_one_arg( &instruction::initialize( &Pubkey::new_unique(), &Authorized::default(), &Lockup::default(), ), Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( &instruction::authorize( &Pubkey::new_unique(), &Pubkey::new_unique(), &Pubkey::new_unique(), StakeAuthorize::Staker, None, ), Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( &instruction::split( &Pubkey::new_unique(), &Pubkey::new_unique(), 100, &invalid_stake_state_pubkey(), )[2], Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( &instruction::merge( &Pubkey::new_unique(), &invalid_stake_state_pubkey(), &Pubkey::new_unique(), )[0], Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( &instruction::split_with_seed( &Pubkey::new_unique(), &Pubkey::new_unique(), 100, &invalid_stake_state_pubkey(), &Pubkey::new_unique(), "seed", )[1], Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( &instruction::delegate_stake( &Pubkey::new_unique(), &Pubkey::new_unique(), &invalid_vote_state_pubkey(), ), Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( &instruction::withdraw( &Pubkey::new_unique(), &Pubkey::new_unique(), &Pubkey::new_unique(), 100, None, ), Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( &instruction::deactivate_stake(&Pubkey::new_unique(), &Pubkey::new_unique()), Err(InstructionError::InvalidAccountData), ); process_instruction_as_one_arg( &instruction::set_lockup( &Pubkey::new_unique(), &LockupArgs::default(), &Pubkey::new_unique(), ), Err(InstructionError::InvalidAccountData), ); } #[test] fn test_spoofed_stake_accounts() { process_instruction_as_one_arg( &instruction::initialize( &spoofed_stake_state_pubkey(), &Authorized::default(), &Lockup::default(), ), Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( &instruction::authorize( &spoofed_stake_state_pubkey(), &Pubkey::new_unique(), &Pubkey::new_unique(), StakeAuthorize::Staker, None, ), Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( &instruction::split( &spoofed_stake_state_pubkey(), &Pubkey::new_unique(), 100, &Pubkey::new_unique(), )[2], Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( &instruction::split( &Pubkey::new_unique(), &Pubkey::new_unique(), 100, &spoofed_stake_state_pubkey(), )[2], Err(InstructionError::IncorrectProgramId), ); process_instruction_as_one_arg( &instruction::merge( &spoofed_stake_state_pubkey(), &Pubkey::new_unique(), &Pubkey::new_unique(), )[0], Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( &instruction::merge( &Pubkey::new_unique(), &spoofed_stake_state_pubkey(), &Pubkey::new_unique(), )[0], Err(InstructionError::IncorrectProgramId), ); process_instruction_as_one_arg( &instruction::split_with_seed( &spoofed_stake_state_pubkey(), &Pubkey::new_unique(), 100, &Pubkey::new_unique(), &Pubkey::new_unique(), "seed", )[1], Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( &instruction::delegate_stake( &spoofed_stake_state_pubkey(), &Pubkey::new_unique(), &Pubkey::new_unique(), ), Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( &instruction::withdraw( &spoofed_stake_state_pubkey(), &Pubkey::new_unique(), &Pubkey::new_unique(), 100, None, ), Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( &instruction::deactivate_stake(&spoofed_stake_state_pubkey(), &Pubkey::new_unique()), Err(InstructionError::InvalidAccountOwner), ); process_instruction_as_one_arg( &instruction::set_lockup( &spoofed_stake_state_pubkey(), &LockupArgs::default(), &Pubkey::new_unique(), ), Err(InstructionError::InvalidAccountOwner), ); } #[test] fn test_stake_process_instruction_decode_bail() { // these will not call stake_state, have bogus contents let stake_address = Pubkey::new_unique(); let stake_account = create_default_stake_account(); let rent_address = sysvar::rent::id(); let rent_account = account::create_account_shared_data_for_test(&Rent::default()); let rewards_address = sysvar::rewards::id(); let rewards_account = account::create_account_shared_data_for_test(&sysvar::rewards::Rewards::new(0.0)); let stake_history_address = sysvar::stake_history::id(); let stake_history_account = account::create_account_shared_data_for_test(&StakeHistory::default()); let vote_address = Pubkey::new_unique(); let vote_account = AccountSharedData::new(0, 0, &solana_vote_program::id()); let clock_address = sysvar::clock::id(); let clock_account = account::create_account_shared_data_for_test(&sysvar::clock::Clock::default()); let config_address = stake_config::id(); let config_account = config::create_account(0, &stake_config::Config::default()); // gets the "is_empty()" check process_instruction( &serialize(&StakeInstruction::Initialize( Authorized::default(), Lockup::default(), )) .unwrap(), Vec::new(), Vec::new(), Err(InstructionError::NotEnoughAccountKeys), ); // no account for rent process_instruction( &serialize(&StakeInstruction::Initialize( Authorized::default(), Lockup::default(), )) .unwrap(), vec![(stake_address, stake_account.clone())], vec![AccountMeta { pubkey: stake_address, is_signer: false, is_writable: false, }], Err(InstructionError::NotEnoughAccountKeys), ); // rent fails to deserialize process_instruction( &serialize(&StakeInstruction::Initialize( Authorized::default(), Lockup::default(), )) .unwrap(), vec![ (stake_address, stake_account.clone()), (rent_address, create_default_account()), ], vec![ AccountMeta { pubkey: stake_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: rent_address, is_signer: false, is_writable: false, }, ], Err(InstructionError::InvalidArgument), ); // fails to deserialize stake state process_instruction( &serialize(&StakeInstruction::Initialize( Authorized::default(), Lockup::default(), )) .unwrap(), vec![ (stake_address, stake_account.clone()), (rent_address, rent_account), ], vec![ AccountMeta { pubkey: stake_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: rent_address, is_signer: false, is_writable: false, }, ], Err(InstructionError::InvalidAccountData), ); // gets the first check in delegate, wrong number of accounts process_instruction( &serialize(&StakeInstruction::DelegateStake).unwrap(), vec![(stake_address, stake_account.clone())], vec![AccountMeta { pubkey: stake_address, is_signer: false, is_writable: false, }], Err(InstructionError::NotEnoughAccountKeys), ); // gets the sub-check for number of args process_instruction( &serialize(&StakeInstruction::DelegateStake).unwrap(), vec![(stake_address, stake_account.clone())], vec![AccountMeta { pubkey: stake_address, is_signer: false, is_writable: false, }], Err(InstructionError::NotEnoughAccountKeys), ); // gets the check non-deserialize-able account in delegate_stake process_instruction( &serialize(&StakeInstruction::DelegateStake).unwrap(), vec![ (stake_address, stake_account.clone()), (vote_address, vote_account.clone()), (clock_address, clock_account), (stake_history_address, stake_history_account.clone()), (config_address, config_account), ], vec![ AccountMeta { pubkey: stake_address, is_signer: true, is_writable: false, }, AccountMeta { pubkey: vote_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: clock_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: stake_history_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: config_address, is_signer: false, is_writable: false, }, ], Err(InstructionError::InvalidAccountData), ); // Tests 3rd keyed account is of correct type (Clock instead of rewards) in withdraw process_instruction( &serialize(&StakeInstruction::Withdraw(42)).unwrap(), vec![ (stake_address, stake_account.clone()), (vote_address, vote_account), (rewards_address, rewards_account.clone()), (stake_history_address, stake_history_account), ], vec![ AccountMeta { pubkey: stake_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: vote_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: rewards_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: stake_history_address, is_signer: false, is_writable: false, }, ], Err(InstructionError::InvalidArgument), ); // Tests correct number of accounts are provided in withdraw process_instruction( &serialize(&StakeInstruction::Withdraw(42)).unwrap(), vec![(stake_address, stake_account.clone())], vec![AccountMeta { pubkey: stake_address, is_signer: false, is_writable: false, }], Err(InstructionError::NotEnoughAccountKeys), ); // Tests 2nd keyed account is of correct type (Clock instead of rewards) in deactivate process_instruction( &serialize(&StakeInstruction::Deactivate).unwrap(), vec![ (stake_address, stake_account), (rewards_address, rewards_account), ], vec![ AccountMeta { pubkey: stake_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: rewards_address, is_signer: false, is_writable: false, }, ], Err(InstructionError::InvalidArgument), ); // Tests correct number of accounts are provided in deactivate process_instruction( &serialize(&StakeInstruction::Deactivate).unwrap(), Vec::new(), Vec::new(), Err(InstructionError::NotEnoughAccountKeys), ); } #[test] fn test_stake_checked_instructions() { let stake_address = Pubkey::new_unique(); let staker = Pubkey::new_unique(); let staker_account = create_default_account(); let withdrawer = Pubkey::new_unique(); let withdrawer_account = create_default_account(); let authorized_address = Pubkey::new_unique(); let authorized_account = create_default_account(); let new_authorized_account = create_default_account(); let clock_address = sysvar::clock::id(); let clock_account = account::create_account_shared_data_for_test(&Clock::default()); let custodian = Pubkey::new_unique(); let custodian_account = create_default_account(); // Test InitializeChecked with non-signing withdrawer let mut instruction = initialize_checked(&stake_address, &Authorized { staker, withdrawer }); instruction.accounts[3] = AccountMeta::new_readonly(withdrawer, false); process_instruction_as_one_arg( &instruction, Err(InstructionError::MissingRequiredSignature), ); // Test InitializeChecked with withdrawer signer let stake_account = AccountSharedData::new( 1_000_000_000, std::mem::size_of::(), &id(), ); let rent_address = sysvar::rent::id(); let rent_account = account::create_account_shared_data_for_test(&Rent::default()); process_instruction( &serialize(&StakeInstruction::InitializeChecked).unwrap(), vec![ (stake_address, stake_account), (rent_address, rent_account), (staker, staker_account), (withdrawer, withdrawer_account.clone()), ], vec![ AccountMeta { pubkey: stake_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: rent_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: staker, is_signer: false, is_writable: false, }, AccountMeta { pubkey: withdrawer, is_signer: true, is_writable: false, }, ], Ok(()), ); // Test AuthorizeChecked with non-signing authority let mut instruction = authorize_checked( &stake_address, &authorized_address, &staker, StakeAuthorize::Staker, None, ); instruction.accounts[3] = AccountMeta::new_readonly(staker, false); process_instruction_as_one_arg( &instruction, Err(InstructionError::MissingRequiredSignature), ); let mut instruction = authorize_checked( &stake_address, &authorized_address, &withdrawer, StakeAuthorize::Withdrawer, None, ); instruction.accounts[3] = AccountMeta::new_readonly(withdrawer, false); process_instruction_as_one_arg( &instruction, Err(InstructionError::MissingRequiredSignature), ); // Test AuthorizeChecked with authority signer let stake_account = AccountSharedData::new_data_with_space( 42, &StakeState::Initialized(Meta::auto(&authorized_address)), std::mem::size_of::(), &id(), ) .unwrap(); process_instruction( &serialize(&StakeInstruction::AuthorizeChecked(StakeAuthorize::Staker)).unwrap(), vec![ (stake_address, stake_account.clone()), (clock_address, clock_account.clone()), (authorized_address, authorized_account.clone()), (staker, new_authorized_account.clone()), ], vec![ AccountMeta { pubkey: stake_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: clock_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: authorized_address, is_signer: true, is_writable: false, }, AccountMeta { pubkey: staker, is_signer: true, is_writable: false, }, ], Ok(()), ); process_instruction( &serialize(&StakeInstruction::AuthorizeChecked( StakeAuthorize::Withdrawer, )) .unwrap(), vec![ (stake_address, stake_account), (clock_address, clock_account.clone()), (authorized_address, authorized_account.clone()), (withdrawer, new_authorized_account.clone()), ], vec![ AccountMeta { pubkey: stake_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: clock_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: authorized_address, is_signer: true, is_writable: false, }, AccountMeta { pubkey: withdrawer, is_signer: true, is_writable: false, }, ], Ok(()), ); // Test AuthorizeCheckedWithSeed with non-signing authority let authorized_owner = Pubkey::new_unique(); let seed = "test seed"; let address_with_seed = Pubkey::create_with_seed(&authorized_owner, seed, &authorized_owner).unwrap(); let mut instruction = authorize_checked_with_seed( &stake_address, &authorized_owner, seed.to_string(), &authorized_owner, &staker, StakeAuthorize::Staker, None, ); instruction.accounts[3] = AccountMeta::new_readonly(staker, false); process_instruction_as_one_arg( &instruction, Err(InstructionError::MissingRequiredSignature), ); let mut instruction = authorize_checked_with_seed( &stake_address, &authorized_owner, seed.to_string(), &authorized_owner, &staker, StakeAuthorize::Withdrawer, None, ); instruction.accounts[3] = AccountMeta::new_readonly(staker, false); process_instruction_as_one_arg( &instruction, Err(InstructionError::MissingRequiredSignature), ); // Test AuthorizeCheckedWithSeed with authority signer let stake_account = AccountSharedData::new_data_with_space( 42, &StakeState::Initialized(Meta::auto(&address_with_seed)), std::mem::size_of::(), &id(), ) .unwrap(); process_instruction( &serialize(&StakeInstruction::AuthorizeCheckedWithSeed( AuthorizeCheckedWithSeedArgs { stake_authorize: StakeAuthorize::Staker, authority_seed: seed.to_string(), authority_owner: authorized_owner, }, )) .unwrap(), vec![ (address_with_seed, stake_account.clone()), (authorized_owner, authorized_account.clone()), (clock_address, clock_account.clone()), (staker, new_authorized_account.clone()), ], vec![ AccountMeta { pubkey: address_with_seed, is_signer: false, is_writable: false, }, AccountMeta { pubkey: authorized_owner, is_signer: true, is_writable: false, }, AccountMeta { pubkey: clock_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: staker, is_signer: true, is_writable: false, }, ], Ok(()), ); process_instruction( &serialize(&StakeInstruction::AuthorizeCheckedWithSeed( AuthorizeCheckedWithSeedArgs { stake_authorize: StakeAuthorize::Withdrawer, authority_seed: seed.to_string(), authority_owner: authorized_owner, }, )) .unwrap(), vec![ (address_with_seed, stake_account), (authorized_owner, authorized_account), (clock_address, clock_account), (withdrawer, new_authorized_account), ], vec![ AccountMeta { pubkey: address_with_seed, is_signer: false, is_writable: false, }, AccountMeta { pubkey: authorized_owner, is_signer: true, is_writable: false, }, AccountMeta { pubkey: clock_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: withdrawer, is_signer: true, is_writable: false, }, ], Ok(()), ); // Test SetLockupChecked with non-signing lockup custodian let mut instruction = set_lockup_checked( &stake_address, &LockupArgs { unix_timestamp: None, epoch: Some(1), custodian: Some(custodian), }, &withdrawer, ); instruction.accounts[2] = AccountMeta::new_readonly(custodian, false); process_instruction_as_one_arg( &instruction, Err(InstructionError::MissingRequiredSignature), ); // Test SetLockupChecked with lockup custodian signer let stake_account = AccountSharedData::new_data_with_space( 42, &StakeState::Initialized(Meta::auto(&withdrawer)), std::mem::size_of::(), &id(), ) .unwrap(); let mut sysvar_cache = SysvarCache::default(); sysvar_cache.set_clock(Clock::default()); mock_process_instruction_with_sysvars( &id(), Vec::new(), &instruction.data, vec![ (stake_address, stake_account), (withdrawer, withdrawer_account), (custodian, custodian_account), ], vec![ AccountMeta { pubkey: stake_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: withdrawer, is_signer: true, is_writable: false, }, AccountMeta { pubkey: custodian, is_signer: true, is_writable: false, }, ], Ok(()), &sysvar_cache, super::process_instruction, ); } }