* Add vote/stake checked instructions (cherry picked from commitee219ffa47
) # Conflicts: # programs/stake/src/stake_instruction.rs # sdk/program/src/stake/instruction.rs # sdk/src/feature_set.rs * Fix set-lockup custodian index (cherry picked from commit544f62c92f
) * Add parsing for new stake instructions; clean up confusing test args (cherry picked from commit9b302ac0b5
) # Conflicts: # transaction-status/src/parse_stake.rs * Add parsing for new vote instructions (cherry picked from commit39bac256ab
) * Add VoteInstruction::AuthorizeChecked test (cherry picked from commitb8ca2250fd
) * Add Stake checked tests (cherry picked from commit74e89a3e3e
) # Conflicts: # programs/stake/src/stake_instruction.rs * Fix conflicts and accommodate old apis in backport Co-authored-by: Michael Vines <mvines@gmail.com> Co-authored-by: Tyera Eulberg <tyera@solana.com>
This commit is contained in:
@ -169,6 +169,61 @@ pub enum StakeInstruction {
|
||||
/// 3. Optional: [SIGNER] Lockup authority, if updating StakeAuthorize::Withdrawer before
|
||||
/// lockup expiration
|
||||
AuthorizeWithSeed(AuthorizeWithSeedArgs),
|
||||
|
||||
/// Initialize a stake with authorization information
|
||||
///
|
||||
/// This instruction is similar to `Initialize` except that the withdraw authority
|
||||
/// must be a signer, and no lockup is applied to the account.
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE] Uninitialized stake account
|
||||
/// 1. [] Rent sysvar
|
||||
/// 2. [] The stake authority
|
||||
/// 3. [SIGNER] The withdraw authority
|
||||
///
|
||||
InitializeChecked,
|
||||
|
||||
/// Authorize a key to manage stake or withdrawal
|
||||
///
|
||||
/// This instruction behaves like `Authorize` with the additional requirement that the new
|
||||
/// stake or withdraw authority must also be a signer.
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE] Stake account to be updated
|
||||
/// 1. [] Clock sysvar
|
||||
/// 2. [SIGNER] The stake or withdraw authority
|
||||
/// 3. [SIGNER] The new stake or withdraw authority
|
||||
/// 4. Optional: [SIGNER] Lockup authority, if updating StakeAuthorize::Withdrawer before
|
||||
/// lockup expiration
|
||||
AuthorizeChecked(StakeAuthorize),
|
||||
|
||||
/// Authorize a key to manage stake or withdrawal with a derived key
|
||||
///
|
||||
/// This instruction behaves like `AuthorizeWithSeed` with the additional requirement that
|
||||
/// the new stake or withdraw authority must also be a signer.
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE] Stake account to be updated
|
||||
/// 1. [SIGNER] Base key of stake or withdraw authority
|
||||
/// 2. [] Clock sysvar
|
||||
/// 3. [SIGNER] The new stake or withdraw authority
|
||||
/// 4. Optional: [SIGNER] Lockup authority, if updating StakeAuthorize::Withdrawer before
|
||||
/// lockup expiration
|
||||
AuthorizeCheckedWithSeed(AuthorizeCheckedWithSeedArgs),
|
||||
|
||||
/// Set stake lockup
|
||||
///
|
||||
/// This instruction behaves like `SetLockup` with the additional requirement that
|
||||
/// the new lockup authority also be a signer.
|
||||
///
|
||||
/// If a lockup is not active, the withdraw authority may set a new lockup
|
||||
/// If a lockup is active, the lockup custodian may update the lockup parameters
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE] Initialized stake account
|
||||
/// 1. [SIGNER] Lockup authority or withdraw authority
|
||||
/// 2. Optional: [SIGNER] New lockup authority
|
||||
SetLockupChecked(LockupCheckedArgs),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
||||
@ -178,6 +233,12 @@ pub struct LockupArgs {
|
||||
pub custodian: Option<Pubkey>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
||||
pub struct LockupCheckedArgs {
|
||||
pub unix_timestamp: Option<UnixTimestamp>,
|
||||
pub epoch: Option<Epoch>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct AuthorizeWithSeedArgs {
|
||||
pub new_authorized_pubkey: Pubkey,
|
||||
@ -186,6 +247,13 @@ pub struct AuthorizeWithSeedArgs {
|
||||
pub authority_owner: Pubkey,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct AuthorizeCheckedWithSeedArgs {
|
||||
pub stake_authorize: StakeAuthorize,
|
||||
pub authority_seed: String,
|
||||
pub authority_owner: Pubkey,
|
||||
}
|
||||
|
||||
pub fn initialize(stake_pubkey: &Pubkey, authorized: &Authorized, lockup: &Lockup) -> Instruction {
|
||||
Instruction::new_with_bincode(
|
||||
id(),
|
||||
@ -197,6 +265,19 @@ pub fn initialize(stake_pubkey: &Pubkey, authorized: &Authorized, lockup: &Locku
|
||||
)
|
||||
}
|
||||
|
||||
pub fn initialize_checked(stake_pubkey: &Pubkey, authorized: &Authorized) -> Instruction {
|
||||
Instruction::new_with_bincode(
|
||||
id(),
|
||||
&StakeInstruction::InitializeChecked,
|
||||
vec![
|
||||
AccountMeta::new(*stake_pubkey, false),
|
||||
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||
AccountMeta::new_readonly(authorized.staker, false),
|
||||
AccountMeta::new_readonly(authorized.withdrawer, true),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_account_with_seed(
|
||||
from_pubkey: &Pubkey,
|
||||
stake_pubkey: &Pubkey,
|
||||
@ -220,6 +301,46 @@ pub fn create_account_with_seed(
|
||||
]
|
||||
}
|
||||
|
||||
pub fn create_account_with_seed_checked(
|
||||
from_pubkey: &Pubkey,
|
||||
stake_pubkey: &Pubkey,
|
||||
base: &Pubkey,
|
||||
seed: &str,
|
||||
authorized: &Authorized,
|
||||
lamports: u64,
|
||||
) -> Vec<Instruction> {
|
||||
vec![
|
||||
system_instruction::create_account_with_seed(
|
||||
from_pubkey,
|
||||
stake_pubkey,
|
||||
base,
|
||||
seed,
|
||||
lamports,
|
||||
std::mem::size_of::<StakeState>() as u64,
|
||||
&id(),
|
||||
),
|
||||
initialize_checked(stake_pubkey, authorized),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn create_account_checked(
|
||||
from_pubkey: &Pubkey,
|
||||
stake_pubkey: &Pubkey,
|
||||
authorized: &Authorized,
|
||||
lamports: u64,
|
||||
) -> Vec<Instruction> {
|
||||
vec![
|
||||
system_instruction::create_account(
|
||||
from_pubkey,
|
||||
stake_pubkey,
|
||||
lamports,
|
||||
std::mem::size_of::<StakeState>() as u64,
|
||||
&id(),
|
||||
),
|
||||
initialize_checked(stake_pubkey, authorized),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn create_account(
|
||||
from_pubkey: &Pubkey,
|
||||
stake_pubkey: &Pubkey,
|
||||
@ -385,6 +506,31 @@ pub fn authorize(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn authorize_checked(
|
||||
stake_pubkey: &Pubkey,
|
||||
authorized_pubkey: &Pubkey,
|
||||
new_authorized_pubkey: &Pubkey,
|
||||
stake_authorize: StakeAuthorize,
|
||||
custodian_pubkey: Option<&Pubkey>,
|
||||
) -> Instruction {
|
||||
let mut account_metas = vec![
|
||||
AccountMeta::new(*stake_pubkey, false),
|
||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||
AccountMeta::new_readonly(*authorized_pubkey, true),
|
||||
AccountMeta::new_readonly(*new_authorized_pubkey, true),
|
||||
];
|
||||
|
||||
if let Some(custodian_pubkey) = custodian_pubkey {
|
||||
account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
|
||||
}
|
||||
|
||||
Instruction::new_with_bincode(
|
||||
id(),
|
||||
&StakeInstruction::AuthorizeChecked(stake_authorize),
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn authorize_with_seed(
|
||||
stake_pubkey: &Pubkey,
|
||||
authority_base: &Pubkey,
|
||||
@ -418,6 +564,39 @@ pub fn authorize_with_seed(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn authorize_checked_with_seed(
|
||||
stake_pubkey: &Pubkey,
|
||||
authority_base: &Pubkey,
|
||||
authority_seed: String,
|
||||
authority_owner: &Pubkey,
|
||||
new_authorized_pubkey: &Pubkey,
|
||||
stake_authorize: StakeAuthorize,
|
||||
custodian_pubkey: Option<&Pubkey>,
|
||||
) -> Instruction {
|
||||
let mut account_metas = vec![
|
||||
AccountMeta::new(*stake_pubkey, false),
|
||||
AccountMeta::new_readonly(*authority_base, true),
|
||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||
AccountMeta::new_readonly(*new_authorized_pubkey, true),
|
||||
];
|
||||
|
||||
if let Some(custodian_pubkey) = custodian_pubkey {
|
||||
account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
|
||||
}
|
||||
|
||||
let args = AuthorizeCheckedWithSeedArgs {
|
||||
stake_authorize,
|
||||
authority_seed,
|
||||
authority_owner: *authority_owner,
|
||||
};
|
||||
|
||||
Instruction::new_with_bincode(
|
||||
id(),
|
||||
&StakeInstruction::AuthorizeCheckedWithSeed(args),
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn delegate_stake(
|
||||
stake_pubkey: &Pubkey,
|
||||
authorized_pubkey: &Pubkey,
|
||||
@ -477,6 +656,30 @@ pub fn set_lockup(
|
||||
Instruction::new_with_bincode(id(), &StakeInstruction::SetLockup(*lockup), account_metas)
|
||||
}
|
||||
|
||||
pub fn set_lockup_checked(
|
||||
stake_pubkey: &Pubkey,
|
||||
lockup: &LockupArgs,
|
||||
custodian_pubkey: &Pubkey,
|
||||
) -> Instruction {
|
||||
let mut account_metas = vec![
|
||||
AccountMeta::new(*stake_pubkey, false),
|
||||
AccountMeta::new_readonly(*custodian_pubkey, true),
|
||||
];
|
||||
|
||||
let lockup_checked = LockupCheckedArgs {
|
||||
unix_timestamp: lockup.unix_timestamp,
|
||||
epoch: lockup.epoch,
|
||||
};
|
||||
if let Some(new_custodian) = lockup.custodian {
|
||||
account_metas.push(AccountMeta::new_readonly(new_custodian, true));
|
||||
}
|
||||
Instruction::new_with_bincode(
|
||||
id(),
|
||||
&StakeInstruction::SetLockupChecked(lockup_checked),
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn process_instruction(
|
||||
_program_id: &Pubkey,
|
||||
keyed_accounts: &[KeyedAccount],
|
||||
@ -598,7 +801,6 @@ pub fn process_instruction(
|
||||
can_merge_expired_lockups,
|
||||
)
|
||||
}
|
||||
|
||||
StakeInstruction::Withdraw(lamports) => {
|
||||
let to = &next_keyed_account(keyed_accounts)?;
|
||||
me.withdraw(
|
||||
@ -615,7 +817,6 @@ pub fn process_instruction(
|
||||
&from_keyed_account::<Clock>(next_keyed_account(keyed_accounts)?)?,
|
||||
&signers,
|
||||
),
|
||||
|
||||
StakeInstruction::SetLockup(lockup) => {
|
||||
let clock = if invoke_context.is_feature_active(&feature_set::stake_program_v4::id()) {
|
||||
Some(get_sysvar::<Clock>(invoke_context, &sysvar::clock::id())?)
|
||||
@ -624,12 +825,107 @@ pub fn process_instruction(
|
||||
};
|
||||
me.set_lockup(&lockup, &signers, clock.as_ref())
|
||||
}
|
||||
StakeInstruction::InitializeChecked => {
|
||||
if invoke_context.is_feature_active(&feature_set::vote_stake_checked_instructions::id())
|
||||
{
|
||||
let rent = next_keyed_account(keyed_accounts)?;
|
||||
let staker = next_keyed_account(keyed_accounts)?;
|
||||
let withdrawer = next_keyed_account(keyed_accounts)?;
|
||||
let authorized = Authorized {
|
||||
staker: *staker.unsigned_key(),
|
||||
withdrawer: *withdrawer
|
||||
.signer_key()
|
||||
.ok_or(InstructionError::MissingRequiredSignature)?,
|
||||
};
|
||||
|
||||
me.initialize(
|
||||
&authorized,
|
||||
&Lockup::default(),
|
||||
&from_keyed_account::<Rent>(rent)?,
|
||||
)
|
||||
} else {
|
||||
Err(InstructionError::InvalidInstructionData)
|
||||
}
|
||||
}
|
||||
StakeInstruction::AuthorizeChecked(stake_authorize) => {
|
||||
if invoke_context.is_feature_active(&feature_set::vote_stake_checked_instructions::id())
|
||||
{
|
||||
let clock = from_keyed_account::<Clock>(next_keyed_account(keyed_accounts)?)?;
|
||||
let _current_authority = next_keyed_account(keyed_accounts)?;
|
||||
let authorized_pubkey = &next_keyed_account(keyed_accounts)?
|
||||
.signer_key()
|
||||
.ok_or(InstructionError::MissingRequiredSignature)?;
|
||||
let custodian = keyed_accounts.next().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.is_feature_active(&feature_set::vote_stake_checked_instructions::id())
|
||||
{
|
||||
let authority_base = next_keyed_account(keyed_accounts)?;
|
||||
let clock = from_keyed_account::<Clock>(next_keyed_account(keyed_accounts)?)?;
|
||||
let authorized_pubkey = &next_keyed_account(keyed_accounts)?
|
||||
.signer_key()
|
||||
.ok_or(InstructionError::MissingRequiredSignature)?;
|
||||
let custodian = keyed_accounts.next().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.is_feature_active(&feature_set::vote_stake_checked_instructions::id())
|
||||
{
|
||||
let _current_authority = next_keyed_account(keyed_accounts)?;
|
||||
|
||||
let custodian = if let Some(custodian) = keyed_accounts.next() {
|
||||
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(get_sysvar::<Clock>(invoke_context, &sysvar::clock::id())?);
|
||||
me.set_lockup(&lockup, &signers, clock.as_ref())
|
||||
} else {
|
||||
Err(InstructionError::InvalidInstructionData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::stake_state::{Meta, StakeState};
|
||||
use bincode::serialize;
|
||||
use solana_sdk::{
|
||||
account::{self, Account, AccountSharedData},
|
||||
@ -637,8 +933,7 @@ mod tests {
|
||||
rent::Rent,
|
||||
sysvar::stake_history::StakeHistory,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::str::FromStr;
|
||||
use std::{cell::RefCell, rc::Rc, str::FromStr};
|
||||
|
||||
fn create_default_account() -> RefCell<AccountSharedData> {
|
||||
RefCell::new(AccountSharedData::default())
|
||||
@ -1173,4 +1468,276 @@ mod tests {
|
||||
pretty_err::<StakeError>(StakeError::NoCreditsToRedeem.into())
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stake_checked_instructions() {
|
||||
let stake_address = Pubkey::new_unique();
|
||||
let staker = Pubkey::new_unique();
|
||||
let withdrawer = Pubkey::new_unique();
|
||||
|
||||
// Test InitializeChecked with non-signing withdrawer
|
||||
let mut instruction =
|
||||
initialize_checked(&stake_address, &Authorized { staker, withdrawer });
|
||||
instruction.accounts[3] = AccountMeta::new_readonly(withdrawer, false);
|
||||
assert_eq!(
|
||||
process_instruction(&instruction),
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
|
||||
// Test InitializeChecked with withdrawer signer
|
||||
let stake_account = AccountSharedData::new_ref(
|
||||
1_000_000_000,
|
||||
std::mem::size_of::<crate::stake_state::StakeState>(),
|
||||
&id(),
|
||||
);
|
||||
let rent_address = sysvar::rent::id();
|
||||
let rent_account = RefCell::new(account::create_account_shared_data_for_test(
|
||||
&Rent::default(),
|
||||
));
|
||||
let staker_account = create_default_account();
|
||||
let withdrawer_account = create_default_account();
|
||||
|
||||
let keyed_accounts = vec![
|
||||
KeyedAccount::new(&stake_address, false, &stake_account),
|
||||
KeyedAccount::new(&rent_address, false, &rent_account),
|
||||
KeyedAccount::new(&staker, false, &staker_account),
|
||||
KeyedAccount::new(&withdrawer, true, &withdrawer_account),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
super::process_instruction(
|
||||
&Pubkey::default(),
|
||||
&keyed_accounts,
|
||||
&serialize(&StakeInstruction::InitializeChecked).unwrap(),
|
||||
&mut MockInvokeContext::default()
|
||||
),
|
||||
Ok(()),
|
||||
);
|
||||
|
||||
// Test AuthorizeChecked with non-signing authority
|
||||
let authorized_address = Pubkey::new_unique();
|
||||
let mut instruction = authorize_checked(
|
||||
&stake_address,
|
||||
&authorized_address,
|
||||
&staker,
|
||||
StakeAuthorize::Staker,
|
||||
None,
|
||||
);
|
||||
instruction.accounts[3] = AccountMeta::new_readonly(staker, false);
|
||||
assert_eq!(
|
||||
process_instruction(&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);
|
||||
assert_eq!(
|
||||
process_instruction(&instruction),
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
|
||||
// Test AuthorizeChecked with authority signer
|
||||
let stake_account = AccountSharedData::new_ref_data_with_space(
|
||||
42,
|
||||
&StakeState::Initialized(Meta::auto(&authorized_address)),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.unwrap();
|
||||
let clock_address = sysvar::clock::id();
|
||||
let clock_account = RefCell::new(account::create_account_shared_data_for_test(
|
||||
&Clock::default(),
|
||||
));
|
||||
let authorized_account = create_default_account();
|
||||
let new_authorized_account = create_default_account();
|
||||
|
||||
let keyed_accounts = vec![
|
||||
KeyedAccount::new(&stake_address, false, &stake_account),
|
||||
KeyedAccount::new(&clock_address, false, &clock_account),
|
||||
KeyedAccount::new(&authorized_address, true, &authorized_account),
|
||||
KeyedAccount::new(&staker, true, &new_authorized_account),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
super::process_instruction(
|
||||
&Pubkey::default(),
|
||||
&keyed_accounts,
|
||||
&serialize(&StakeInstruction::AuthorizeChecked(StakeAuthorize::Staker)).unwrap(),
|
||||
&mut MockInvokeContext::default()
|
||||
),
|
||||
Ok(()),
|
||||
);
|
||||
|
||||
let keyed_accounts = vec![
|
||||
KeyedAccount::new(&stake_address, false, &stake_account),
|
||||
KeyedAccount::new(&clock_address, false, &clock_account),
|
||||
KeyedAccount::new(&authorized_address, true, &authorized_account),
|
||||
KeyedAccount::new(&withdrawer, true, &new_authorized_account),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
super::process_instruction(
|
||||
&Pubkey::default(),
|
||||
&keyed_accounts,
|
||||
&serialize(&StakeInstruction::AuthorizeChecked(
|
||||
StakeAuthorize::Withdrawer
|
||||
))
|
||||
.unwrap(),
|
||||
&mut MockInvokeContext::default()
|
||||
),
|
||||
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);
|
||||
assert_eq!(
|
||||
process_instruction(&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);
|
||||
assert_eq!(
|
||||
process_instruction(&instruction),
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
|
||||
// Test AuthorizeCheckedWithSeed with authority signer
|
||||
let stake_account = AccountSharedData::new_ref_data_with_space(
|
||||
42,
|
||||
&StakeState::Initialized(Meta::auto(&address_with_seed)),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.unwrap();
|
||||
let keyed_accounts = vec![
|
||||
KeyedAccount::new(&address_with_seed, false, &stake_account),
|
||||
KeyedAccount::new(&authorized_owner, true, &authorized_account),
|
||||
KeyedAccount::new(&clock_address, false, &clock_account),
|
||||
KeyedAccount::new(&staker, true, &new_authorized_account),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
super::process_instruction(
|
||||
&Pubkey::default(),
|
||||
&keyed_accounts,
|
||||
&serialize(&StakeInstruction::AuthorizeCheckedWithSeed(
|
||||
AuthorizeCheckedWithSeedArgs {
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority_seed: seed.to_string(),
|
||||
authority_owner: authorized_owner,
|
||||
}
|
||||
))
|
||||
.unwrap(),
|
||||
&mut MockInvokeContext::default()
|
||||
),
|
||||
Ok(()),
|
||||
);
|
||||
|
||||
let keyed_accounts = vec![
|
||||
KeyedAccount::new(&address_with_seed, false, &stake_account),
|
||||
KeyedAccount::new(&authorized_owner, true, &authorized_account),
|
||||
KeyedAccount::new(&clock_address, false, &clock_account),
|
||||
KeyedAccount::new(&withdrawer, true, &new_authorized_account),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
super::process_instruction(
|
||||
&Pubkey::default(),
|
||||
&keyed_accounts,
|
||||
&serialize(&StakeInstruction::AuthorizeCheckedWithSeed(
|
||||
AuthorizeCheckedWithSeedArgs {
|
||||
stake_authorize: StakeAuthorize::Withdrawer,
|
||||
authority_seed: seed.to_string(),
|
||||
authority_owner: authorized_owner,
|
||||
}
|
||||
))
|
||||
.unwrap(),
|
||||
&mut MockInvokeContext::default()
|
||||
),
|
||||
Ok(()),
|
||||
);
|
||||
|
||||
// Test SetLockupChecked with non-signing lockup custodian
|
||||
let custodian = Pubkey::new_unique();
|
||||
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);
|
||||
assert_eq!(
|
||||
process_instruction(&instruction),
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
|
||||
// Test SetLockupChecked with lockup custodian signer
|
||||
let stake_account = AccountSharedData::new_ref_data_with_space(
|
||||
42,
|
||||
&StakeState::Initialized(Meta::auto(&withdrawer)),
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
)
|
||||
.unwrap();
|
||||
let custodian_account = create_default_account();
|
||||
|
||||
let keyed_accounts = vec![
|
||||
KeyedAccount::new(&stake_address, false, &stake_account),
|
||||
KeyedAccount::new(&withdrawer, true, &withdrawer_account),
|
||||
KeyedAccount::new(&custodian, true, &custodian_account),
|
||||
];
|
||||
|
||||
let mut invoke_context = MockInvokeContext::default();
|
||||
let clock = Clock::default();
|
||||
let mut data = vec![];
|
||||
bincode::serialize_into(&mut data, &clock).unwrap();
|
||||
invoke_context
|
||||
.sysvars
|
||||
.push((sysvar::clock::id(), Some(Rc::new(data))));
|
||||
|
||||
assert_eq!(
|
||||
super::process_instruction(
|
||||
&Pubkey::default(),
|
||||
&keyed_accounts,
|
||||
&serialize(&StakeInstruction::SetLockupChecked(LockupCheckedArgs {
|
||||
unix_timestamp: None,
|
||||
epoch: Some(1),
|
||||
}))
|
||||
.unwrap(),
|
||||
&mut invoke_context
|
||||
),
|
||||
Ok(()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -111,6 +111,18 @@ pub enum VoteInstruction {
|
||||
/// 2. [] Clock sysvar
|
||||
/// 3. [SIGNER] Vote authority
|
||||
VoteSwitch(Vote, Hash),
|
||||
|
||||
/// Authorize a key to send votes or issue a withdrawal
|
||||
///
|
||||
/// This instruction behaves like `Authorize` with the additional requirement that the new vote
|
||||
/// or withdraw authority must also be a signer.
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. [WRITE] Vote account to be updated with the Pubkey for authorization
|
||||
/// 1. [] Clock sysvar
|
||||
/// 2. [SIGNER] Vote or withdraw authority
|
||||
/// 3. [SIGNER] New vote or withdraw authority
|
||||
AuthorizeChecked(VoteAuthorize),
|
||||
}
|
||||
|
||||
fn initialize_account(vote_pubkey: &Pubkey, vote_init: &VoteInit) -> Instruction {
|
||||
@ -182,6 +194,26 @@ pub fn authorize(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn authorize_checked(
|
||||
vote_pubkey: &Pubkey,
|
||||
authorized_pubkey: &Pubkey, // currently authorized
|
||||
new_authorized_pubkey: &Pubkey,
|
||||
vote_authorize: VoteAuthorize,
|
||||
) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*vote_pubkey, false),
|
||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||
AccountMeta::new_readonly(*authorized_pubkey, true),
|
||||
AccountMeta::new_readonly(*new_authorized_pubkey, true),
|
||||
];
|
||||
|
||||
Instruction::new_with_bincode(
|
||||
id(),
|
||||
&VoteInstruction::AuthorizeChecked(vote_authorize),
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update_validator_identity(
|
||||
vote_pubkey: &Pubkey,
|
||||
authorized_withdrawer_pubkey: &Pubkey,
|
||||
@ -335,12 +367,32 @@ pub fn process_instruction(
|
||||
let to = next_keyed_account(keyed_accounts)?;
|
||||
vote_state::withdraw(me, lamports, to, &signers)
|
||||
}
|
||||
VoteInstruction::AuthorizeChecked(vote_authorize) => {
|
||||
if invoke_context.is_feature_active(&feature_set::vote_stake_checked_instructions::id())
|
||||
{
|
||||
let clock = next_keyed_account(keyed_accounts)?;
|
||||
let _current_authority = next_keyed_account(keyed_accounts)?;
|
||||
let voter_pubkey = &next_keyed_account(keyed_accounts)?
|
||||
.signer_key()
|
||||
.ok_or(InstructionError::MissingRequiredSignature)?;
|
||||
vote_state::authorize(
|
||||
me,
|
||||
voter_pubkey,
|
||||
vote_authorize,
|
||||
&signers,
|
||||
&from_keyed_account::<Clock>(clock)?,
|
||||
)
|
||||
} else {
|
||||
Err(InstructionError::InvalidInstructionData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bincode::serialize;
|
||||
use solana_sdk::{
|
||||
account::{self, Account, AccountSharedData},
|
||||
process_instruction::MockInvokeContext,
|
||||
@ -349,6 +401,10 @@ mod tests {
|
||||
use std::cell::RefCell;
|
||||
use std::str::FromStr;
|
||||
|
||||
fn create_default_account() -> RefCell<AccountSharedData> {
|
||||
RefCell::new(AccountSharedData::default())
|
||||
}
|
||||
|
||||
// these are for 100% coverage in this file
|
||||
#[test]
|
||||
fn test_vote_process_instruction_decode_bail() {
|
||||
@ -491,6 +547,107 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vote_authorize_checked() {
|
||||
let vote_pubkey = Pubkey::new_unique();
|
||||
let authorized_pubkey = Pubkey::new_unique();
|
||||
let new_authorized_pubkey = Pubkey::new_unique();
|
||||
|
||||
// Test with vanilla authorize accounts
|
||||
let mut instruction = authorize_checked(
|
||||
&vote_pubkey,
|
||||
&authorized_pubkey,
|
||||
&new_authorized_pubkey,
|
||||
VoteAuthorize::Voter,
|
||||
);
|
||||
instruction.accounts = instruction.accounts[0..2].to_vec();
|
||||
assert_eq!(
|
||||
process_instruction(&instruction),
|
||||
Err(InstructionError::NotEnoughAccountKeys),
|
||||
);
|
||||
|
||||
let mut instruction = authorize_checked(
|
||||
&vote_pubkey,
|
||||
&authorized_pubkey,
|
||||
&new_authorized_pubkey,
|
||||
VoteAuthorize::Withdrawer,
|
||||
);
|
||||
instruction.accounts = instruction.accounts[0..2].to_vec();
|
||||
assert_eq!(
|
||||
process_instruction(&instruction),
|
||||
Err(InstructionError::NotEnoughAccountKeys),
|
||||
);
|
||||
|
||||
// Test with non-signing new_authorized_pubkey
|
||||
let mut instruction = authorize_checked(
|
||||
&vote_pubkey,
|
||||
&authorized_pubkey,
|
||||
&new_authorized_pubkey,
|
||||
VoteAuthorize::Voter,
|
||||
);
|
||||
instruction.accounts[3] = AccountMeta::new_readonly(new_authorized_pubkey, false);
|
||||
assert_eq!(
|
||||
process_instruction(&instruction),
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
|
||||
let mut instruction = authorize_checked(
|
||||
&vote_pubkey,
|
||||
&authorized_pubkey,
|
||||
&new_authorized_pubkey,
|
||||
VoteAuthorize::Withdrawer,
|
||||
);
|
||||
instruction.accounts[3] = AccountMeta::new_readonly(new_authorized_pubkey, false);
|
||||
assert_eq!(
|
||||
process_instruction(&instruction),
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
|
||||
// Test with new_authorized_pubkey signer
|
||||
let vote_account = AccountSharedData::new_ref(100, VoteState::size_of(), &id());
|
||||
let clock_address = sysvar::clock::id();
|
||||
let clock_account = RefCell::new(account::create_account_shared_data_for_test(
|
||||
&Clock::default(),
|
||||
));
|
||||
let default_authorized_pubkey = Pubkey::default();
|
||||
let authorized_account = create_default_account();
|
||||
let new_authorized_account = create_default_account();
|
||||
let keyed_accounts = vec![
|
||||
KeyedAccount::new(&vote_pubkey, false, &vote_account),
|
||||
KeyedAccount::new(&clock_address, false, &clock_account),
|
||||
KeyedAccount::new(&default_authorized_pubkey, true, &authorized_account),
|
||||
KeyedAccount::new(&new_authorized_pubkey, true, &new_authorized_account),
|
||||
];
|
||||
assert_eq!(
|
||||
super::process_instruction(
|
||||
&Pubkey::default(),
|
||||
&keyed_accounts,
|
||||
&serialize(&VoteInstruction::AuthorizeChecked(VoteAuthorize::Voter)).unwrap(),
|
||||
&mut MockInvokeContext::default()
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
let keyed_accounts = vec![
|
||||
KeyedAccount::new(&vote_pubkey, false, &vote_account),
|
||||
KeyedAccount::new(&clock_address, false, &clock_account),
|
||||
KeyedAccount::new(&default_authorized_pubkey, true, &authorized_account),
|
||||
KeyedAccount::new(&new_authorized_pubkey, true, &new_authorized_account),
|
||||
];
|
||||
assert_eq!(
|
||||
super::process_instruction(
|
||||
&Pubkey::default(),
|
||||
&keyed_accounts,
|
||||
&serialize(&VoteInstruction::AuthorizeChecked(
|
||||
VoteAuthorize::Withdrawer
|
||||
))
|
||||
.unwrap(),
|
||||
&mut MockInvokeContext::default()
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_minimum_balance() {
|
||||
let rent = solana_sdk::rent::Rent::default();
|
||||
|
@ -154,6 +154,10 @@ pub mod dedupe_config_program_signers {
|
||||
solana_sdk::declare_id!("8kEuAshXLsgkUEdcFVLqrjCGGHVWFW99ZZpxvAzzMtBp");
|
||||
}
|
||||
|
||||
pub mod vote_stake_checked_instructions {
|
||||
solana_sdk::declare_id!("BcWknVcgvonN8sL4HE4XFuEVgfcee5MwxWPAgP6ZV89X");
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// Map of feature identifiers to user-visible description
|
||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
||||
@ -191,6 +195,7 @@ lazy_static! {
|
||||
(system_transfer_zero_check::id(), "perform all checks for transfers of 0 lamports"),
|
||||
(memory_ops_syscalls::id(), "add syscalls for memory operations"),
|
||||
(dedupe_config_program_signers::id(), "dedupe config program signers"),
|
||||
(vote_stake_checked_instructions::id(), "vote/state program checked instructions #18345"),
|
||||
/*************** ADD NEW FEATURES HERE ***************/
|
||||
]
|
||||
.iter()
|
||||
|
@ -185,6 +185,86 @@ pub fn parse_stake(
|
||||
info: value,
|
||||
})
|
||||
}
|
||||
StakeInstruction::InitializeChecked => {
|
||||
check_num_stake_accounts(&instruction.accounts, 4)?;
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "initializeChecked".to_string(),
|
||||
info: json!({
|
||||
"stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"rentSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
"staker": account_keys[instruction.accounts[2] as usize].to_string(),
|
||||
"withdrawer": account_keys[instruction.accounts[3] as usize].to_string(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
StakeInstruction::AuthorizeChecked(authority_type) => {
|
||||
check_num_stake_accounts(&instruction.accounts, 4)?;
|
||||
let mut value = json!({
|
||||
"stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"clockSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
"authority": account_keys[instruction.accounts[2] as usize].to_string(),
|
||||
"newAuthority": account_keys[instruction.accounts[3] as usize].to_string(),
|
||||
"authorityType": authority_type,
|
||||
});
|
||||
let map = value.as_object_mut().unwrap();
|
||||
if instruction.accounts.len() >= 5 {
|
||||
map.insert(
|
||||
"custodian".to_string(),
|
||||
json!(account_keys[instruction.accounts[4] as usize].to_string()),
|
||||
);
|
||||
}
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "authorizeChecked".to_string(),
|
||||
info: value,
|
||||
})
|
||||
}
|
||||
StakeInstruction::AuthorizeCheckedWithSeed(args) => {
|
||||
check_num_stake_accounts(&instruction.accounts, 4)?;
|
||||
let mut value = json!({
|
||||
"stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"authorityBase": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
"clockSysvar": account_keys[instruction.accounts[2] as usize].to_string(),
|
||||
"newAuthorized": account_keys[instruction.accounts[3] as usize].to_string(),
|
||||
"authorityType": args.stake_authorize,
|
||||
"authoritySeed": args.authority_seed,
|
||||
"authorityOwner": args.authority_owner.to_string(),
|
||||
});
|
||||
let map = value.as_object_mut().unwrap();
|
||||
if instruction.accounts.len() >= 5 {
|
||||
map.insert(
|
||||
"custodian".to_string(),
|
||||
json!(account_keys[instruction.accounts[4] as usize].to_string()),
|
||||
);
|
||||
}
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "authorizeCheckedWithSeed".to_string(),
|
||||
info: value,
|
||||
})
|
||||
}
|
||||
StakeInstruction::SetLockupChecked(lockup_args) => {
|
||||
check_num_stake_accounts(&instruction.accounts, 2)?;
|
||||
let mut lockup_map = Map::new();
|
||||
if let Some(timestamp) = lockup_args.unix_timestamp {
|
||||
lockup_map.insert("unixTimestamp".to_string(), json!(timestamp));
|
||||
}
|
||||
if let Some(epoch) = lockup_args.epoch {
|
||||
lockup_map.insert("epoch".to_string(), json!(epoch));
|
||||
}
|
||||
if instruction.accounts.len() >= 3 {
|
||||
lockup_map.insert(
|
||||
"custodian".to_string(),
|
||||
json!(account_keys[instruction.accounts[2] as usize].to_string()),
|
||||
);
|
||||
}
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "setLockupChecked".to_string(),
|
||||
info: json!({
|
||||
"stakeAccount": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"custodian": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
"lockup": lockup_map,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,22 +348,22 @@ mod test {
|
||||
assert!(parse_stake(&message.instructions[0], &keys[0..2]).is_err());
|
||||
|
||||
let instruction = stake_instruction::authorize(
|
||||
&keys[1],
|
||||
&keys[2],
|
||||
&keys[0],
|
||||
&keys[3],
|
||||
&keys[4],
|
||||
StakeAuthorize::Withdrawer,
|
||||
Some(&keys[1]),
|
||||
);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_stake(&message.instructions[0], &keys[0..3]).unwrap(),
|
||||
parse_stake(&message.instructions[0], &keys[0..4]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "authorize".to_string(),
|
||||
info: json!({
|
||||
"stakeAccount": keys[1].to_string(),
|
||||
"clockSysvar": keys[2].to_string(),
|
||||
"stakeAccount": keys[2].to_string(),
|
||||
"clockSysvar": keys[3].to_string(),
|
||||
"authority": keys[0].to_string(),
|
||||
"newAuthority": keys[3].to_string(),
|
||||
"newAuthority": keys[4].to_string(),
|
||||
"authorityType": StakeAuthorize::Withdrawer,
|
||||
"custodian": keys[1].to_string(),
|
||||
}),
|
||||
@ -402,7 +482,7 @@ mod test {
|
||||
&keys[1],
|
||||
&keys[0],
|
||||
seed.to_string(),
|
||||
&keys[2],
|
||||
&keys[0],
|
||||
&keys[3],
|
||||
StakeAuthorize::Staker,
|
||||
None,
|
||||
@ -414,7 +494,7 @@ mod test {
|
||||
instruction_type: "authorizeWithSeed".to_string(),
|
||||
info: json!({
|
||||
"stakeAccount": keys[1].to_string(),
|
||||
"authorityOwner": keys[2].to_string(),
|
||||
"authorityOwner": keys[0].to_string(),
|
||||
"newAuthorized": keys[3].to_string(),
|
||||
"authorityBase": keys[0].to_string(),
|
||||
"authoritySeed": seed,
|
||||
@ -423,26 +503,26 @@ mod test {
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_stake(&message.instructions[0], &keys[0..1]).is_err());
|
||||
assert!(parse_stake(&message.instructions[0], &keys[0..2]).is_err());
|
||||
|
||||
let instruction = stake_instruction::authorize_with_seed(
|
||||
&keys[1],
|
||||
&keys[2],
|
||||
&keys[0],
|
||||
seed.to_string(),
|
||||
&keys[2],
|
||||
&keys[3],
|
||||
&keys[0],
|
||||
&keys[4],
|
||||
StakeAuthorize::Withdrawer,
|
||||
Some(&keys[4]),
|
||||
Some(&keys[1]),
|
||||
);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_stake(&message.instructions[0], &keys[0..5]).unwrap(),
|
||||
parse_stake(&message.instructions[0], &keys[0..4]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "authorizeWithSeed".to_string(),
|
||||
info: json!({
|
||||
"stakeAccount": keys[2].to_string(),
|
||||
"authorityOwner": keys[2].to_string(),
|
||||
"newAuthorized": keys[3].to_string(),
|
||||
"authorityOwner": keys[0].to_string(),
|
||||
"newAuthorized": keys[4].to_string(),
|
||||
"authorityBase": keys[0].to_string(),
|
||||
"authoritySeed": seed,
|
||||
"authorityType": StakeAuthorize::Withdrawer,
|
||||
@ -451,14 +531,14 @@ mod test {
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_stake(&message.instructions[0], &keys[0..1]).is_err());
|
||||
assert!(parse_stake(&message.instructions[0], &keys[0..3]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::same_item_push)]
|
||||
fn test_parse_set_lockup() {
|
||||
fn test_parse_stake_set_lockup() {
|
||||
let mut keys: Vec<Pubkey> = vec![];
|
||||
for _ in 0..2 {
|
||||
for _ in 0..3 {
|
||||
keys.push(Pubkey::new_unique());
|
||||
}
|
||||
let unix_timestamp = 1_234_567_890;
|
||||
@ -532,5 +612,208 @@ mod test {
|
||||
);
|
||||
|
||||
assert!(parse_stake(&message.instructions[0], &keys[0..1]).is_err());
|
||||
|
||||
let lockup = LockupArgs {
|
||||
unix_timestamp: Some(unix_timestamp),
|
||||
epoch: None,
|
||||
custodian: None,
|
||||
};
|
||||
let instruction = stake_instruction::set_lockup_checked(&keys[1], &lockup, &keys[0]);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_stake(&message.instructions[0], &keys[0..2]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "setLockupChecked".to_string(),
|
||||
info: json!({
|
||||
"stakeAccount": keys[1].to_string(),
|
||||
"custodian": keys[0].to_string(),
|
||||
"lockup": {
|
||||
"unixTimestamp": unix_timestamp
|
||||
}
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
let lockup = LockupArgs {
|
||||
unix_timestamp: Some(unix_timestamp),
|
||||
epoch: Some(epoch),
|
||||
custodian: None,
|
||||
};
|
||||
let instruction = stake_instruction::set_lockup_checked(&keys[1], &lockup, &keys[0]);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_stake(&message.instructions[0], &keys[0..2]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "setLockupChecked".to_string(),
|
||||
info: json!({
|
||||
"stakeAccount": keys[1].to_string(),
|
||||
"custodian": keys[0].to_string(),
|
||||
"lockup": {
|
||||
"unixTimestamp": unix_timestamp,
|
||||
"epoch": epoch,
|
||||
}
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_stake(&message.instructions[0], &keys[0..1]).is_err());
|
||||
|
||||
let lockup = LockupArgs {
|
||||
unix_timestamp: Some(unix_timestamp),
|
||||
epoch: Some(epoch),
|
||||
custodian: Some(keys[1]),
|
||||
};
|
||||
let instruction = stake_instruction::set_lockup_checked(&keys[2], &lockup, &keys[0]);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_stake(&message.instructions[0], &keys[0..3]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "setLockupChecked".to_string(),
|
||||
info: json!({
|
||||
"stakeAccount": keys[2].to_string(),
|
||||
"custodian": keys[0].to_string(),
|
||||
"lockup": {
|
||||
"unixTimestamp": unix_timestamp,
|
||||
"epoch": epoch,
|
||||
"custodian": keys[1].to_string(),
|
||||
}
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_stake(&message.instructions[0], &keys[0..2]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::same_item_push)]
|
||||
fn test_parse_stake_checked_instructions() {
|
||||
let mut keys: Vec<Pubkey> = vec![];
|
||||
for _ in 0..6 {
|
||||
keys.push(Pubkey::new_unique());
|
||||
}
|
||||
|
||||
let authorized = Authorized {
|
||||
staker: keys[3],
|
||||
withdrawer: keys[0],
|
||||
};
|
||||
let lamports = 55;
|
||||
|
||||
let instructions =
|
||||
stake_instruction::create_account_checked(&keys[0], &keys[1], &authorized, lamports);
|
||||
let message = Message::new(&instructions, None);
|
||||
assert_eq!(
|
||||
parse_stake(&message.instructions[1], &keys[0..4]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "initializeChecked".to_string(),
|
||||
info: json!({
|
||||
"stakeAccount": keys[1].to_string(),
|
||||
"rentSysvar": keys[2].to_string(),
|
||||
"staker": keys[3].to_string(),
|
||||
"withdrawer": keys[0].to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_stake(&message.instructions[1], &keys[0..3]).is_err());
|
||||
|
||||
let instruction = stake_instruction::authorize_checked(
|
||||
&keys[2],
|
||||
&keys[0],
|
||||
&keys[1],
|
||||
StakeAuthorize::Staker,
|
||||
None,
|
||||
);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_stake(&message.instructions[0], &keys[0..4]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "authorizeChecked".to_string(),
|
||||
info: json!({
|
||||
"stakeAccount": keys[2].to_string(),
|
||||
"clockSysvar": keys[3].to_string(),
|
||||
"authority": keys[0].to_string(),
|
||||
"newAuthority": keys[1].to_string(),
|
||||
"authorityType": StakeAuthorize::Staker,
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_stake(&message.instructions[0], &keys[0..3]).is_err());
|
||||
|
||||
let instruction = stake_instruction::authorize_checked(
|
||||
&keys[3],
|
||||
&keys[0],
|
||||
&keys[1],
|
||||
StakeAuthorize::Withdrawer,
|
||||
Some(&keys[2]),
|
||||
);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_stake(&message.instructions[0], &keys[0..5]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "authorizeChecked".to_string(),
|
||||
info: json!({
|
||||
"stakeAccount": keys[3].to_string(),
|
||||
"clockSysvar": keys[4].to_string(),
|
||||
"authority": keys[0].to_string(),
|
||||
"newAuthority": keys[1].to_string(),
|
||||
"authorityType": StakeAuthorize::Withdrawer,
|
||||
"custodian": keys[2].to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_stake(&message.instructions[0], &keys[0..4]).is_err());
|
||||
|
||||
let seed = "test_seed";
|
||||
let instruction = stake_instruction::authorize_checked_with_seed(
|
||||
&keys[2],
|
||||
&keys[0],
|
||||
seed.to_string(),
|
||||
&keys[0],
|
||||
&keys[1],
|
||||
StakeAuthorize::Staker,
|
||||
None,
|
||||
);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_stake(&message.instructions[0], &keys[0..4]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "authorizeCheckedWithSeed".to_string(),
|
||||
info: json!({
|
||||
"stakeAccount": keys[2].to_string(),
|
||||
"authorityOwner": keys[0].to_string(),
|
||||
"newAuthorized": keys[1].to_string(),
|
||||
"authorityBase": keys[0].to_string(),
|
||||
"authoritySeed": seed,
|
||||
"authorityType": StakeAuthorize::Staker,
|
||||
"clockSysvar": keys[3].to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_stake(&message.instructions[0], &keys[0..3]).is_err());
|
||||
|
||||
let instruction = stake_instruction::authorize_checked_with_seed(
|
||||
&keys[3],
|
||||
&keys[0],
|
||||
seed.to_string(),
|
||||
&keys[0],
|
||||
&keys[1],
|
||||
StakeAuthorize::Withdrawer,
|
||||
Some(&keys[2]),
|
||||
);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_stake(&message.instructions[0], &keys[0..5]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "authorizeCheckedWithSeed".to_string(),
|
||||
info: json!({
|
||||
"stakeAccount": keys[3].to_string(),
|
||||
"authorityOwner": keys[0].to_string(),
|
||||
"newAuthorized": keys[1].to_string(),
|
||||
"authorityBase": keys[0].to_string(),
|
||||
"authoritySeed": seed,
|
||||
"authorityType": StakeAuthorize::Withdrawer,
|
||||
"clockSysvar": keys[4].to_string(),
|
||||
"custodian": keys[2].to_string(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_stake(&message.instructions[0], &keys[0..4]).is_err());
|
||||
}
|
||||
}
|
||||
|
@ -121,6 +121,19 @@ pub fn parse_vote(
|
||||
}),
|
||||
})
|
||||
}
|
||||
VoteInstruction::AuthorizeChecked(authority_type) => {
|
||||
check_num_vote_accounts(&instruction.accounts, 4)?;
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "authorizeChecked".to_string(),
|
||||
info: json!({
|
||||
"voteAccount": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"clockSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
"authority": account_keys[instruction.accounts[2] as usize].to_string(),
|
||||
"newAuthority": account_keys[instruction.accounts[3] as usize].to_string(),
|
||||
"authorityType": authority_type,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,5 +307,24 @@ mod test {
|
||||
}
|
||||
);
|
||||
assert!(parse_vote(&message.instructions[0], &keys[0..3]).is_err());
|
||||
|
||||
let authority_type = VoteAuthorize::Voter;
|
||||
let instruction =
|
||||
vote_instruction::authorize_checked(&keys[1], &keys[0], &keys[3], authority_type);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_vote(&message.instructions[0], &keys[0..4]).unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "authorizeChecked".to_string(),
|
||||
info: json!({
|
||||
"voteAccount": keys[2].to_string(),
|
||||
"clockSysvar": keys[3].to_string(),
|
||||
"authority": keys[0].to_string(),
|
||||
"newAuthority": keys[1].to_string(),
|
||||
"authorityType": authority_type,
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_vote(&message.instructions[0], &keys[0..3]).is_err());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user