Implement `VoteInstruction::AuthorizeWithSeed` & `VoteInstruction::AuthorizeWithSeedChecked` (#25928)
* [vote_authorize_with_seed] Add `VoteInstruction::AuthorizeWithSeed` * [vote_authorize_with_seed] You can now update a vote account's authority if it's a derived key for which you control the base key * [vote_authorize_with_seed] Add test helper to create a vote account whose authorities are derived keys * [vote_authorize_with_seed] Write tests to assert the behavior of `VoteInstruction::AuthorizeWithSeed` * [vote_authorize_with_seed] Feature gate the `VoteInstruction::AuthorizeWithSeed` processor * [vote_authorize_with_seed] Add `VoteInstruction::AuthorizeWithSeed` to transaction status parser * [vote_authorize_with_seed] Add `VoteInstruction::AuthorizeWithSeed` to docs * [vote_authorize_with_seed] Add `VoteInstruction::AuthorizeCheckedWithSeed` * [vote_authorize_with_seed] You can now update a vote account's authority (while checking that the new authority has signed) if it's a derived key for which you control the base key * [vote_authorize_with_seed] Add `VoteInstruction::AuthorizeCheckedWithSeed` to transaction status parser * [vote_authorize_with_seed] Write tests to assert the behavior of `VoteInstruction::AuthorizeCheckedWithSeed`
This commit is contained in:
parent
7b786ff331
commit
45d11f3d26
|
@ -45,6 +45,13 @@ VoteState is the current state of all the votes the validator has submitted to t
|
|||
|
||||
Updates the account with a new authorized voter or withdrawer, according to the VoteAuthorize parameter \(`Voter` or `Withdrawer`\). The transaction must be signed by the Vote account's current `authorized_voter` or `authorized_withdrawer`.
|
||||
|
||||
- `account[0]` - RW - The VoteState.
|
||||
`VoteState::authorized_voter` or `authorized_withdrawer` is set to `Pubkey`.
|
||||
|
||||
### VoteInstruction::AuthorizeWithSeed\(VoteAuthorizeWithSeedArgs\)
|
||||
|
||||
Updates the account with a new authorized voter or withdrawer, according to the VoteAuthorize parameter \(`Voter` or `Withdrawer`\). Unlike `VoteInstruction::Authorize` this instruction is for use when the Vote account's current `authorized_voter` or `authorized_withdrawer` is a derived key. The transaction must be signed by someone who can sign for the base key of that derived key.
|
||||
|
||||
- `account[0]` - RW - The VoteState.
|
||||
`VoteState::authorized_voter` or `authorized_withdrawer` is set to `Pubkey`.
|
||||
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
use {
|
||||
crate::{
|
||||
id,
|
||||
vote_state::{Vote, VoteAuthorize, VoteInit, VoteState, VoteStateUpdate},
|
||||
vote_state::{
|
||||
Vote, VoteAuthorize, VoteAuthorizeCheckedWithSeedArgs, VoteAuthorizeWithSeedArgs,
|
||||
VoteInit, VoteState, VoteStateUpdate,
|
||||
},
|
||||
},
|
||||
serde_derive::{Deserialize, Serialize},
|
||||
solana_sdk::{
|
||||
|
@ -99,6 +102,30 @@ pub enum VoteInstruction {
|
|||
/// 0. `[Write]` Vote account to vote with
|
||||
/// 1. `[SIGNER]` Vote authority
|
||||
UpdateVoteStateSwitch(VoteStateUpdate, Hash),
|
||||
|
||||
/// Given that the current Voter or Withdrawer authority is a derived key,
|
||||
/// this instruction allows someone who can sign for that derived key's
|
||||
/// base key to authorize a new Voter or Withdrawer for a vote account.
|
||||
///
|
||||
/// # Account references
|
||||
/// 0. `[Write]` Vote account to be updated
|
||||
/// 1. `[]` Clock sysvar
|
||||
/// 2. `[SIGNER]` Base key of current Voter or Withdrawer authority's derived key
|
||||
AuthorizeWithSeed(VoteAuthorizeWithSeedArgs),
|
||||
|
||||
/// Given that the current Voter or Withdrawer authority is a derived key,
|
||||
/// this instruction allows someone who can sign for that derived key's
|
||||
/// base key to authorize a new Voter or Withdrawer for a vote account.
|
||||
///
|
||||
/// This instruction behaves like `AuthorizeWithSeed` 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
|
||||
/// 1. `[]` Clock sysvar
|
||||
/// 2. `[SIGNER]` Base key of current Voter or Withdrawer authority's derived key
|
||||
/// 3. `[SIGNER]` New vote or withdraw authority
|
||||
AuthorizeCheckedWithSeed(VoteAuthorizeCheckedWithSeedArgs),
|
||||
}
|
||||
|
||||
fn initialize_account(vote_pubkey: &Pubkey, vote_init: &VoteInit) -> Instruction {
|
||||
|
@ -190,6 +217,58 @@ pub fn authorize_checked(
|
|||
)
|
||||
}
|
||||
|
||||
pub fn authorize_with_seed(
|
||||
vote_pubkey: &Pubkey,
|
||||
current_authority_base_key: &Pubkey,
|
||||
current_authority_derived_key_owner: &Pubkey,
|
||||
current_authority_derived_key_seed: &str,
|
||||
new_authority: &Pubkey,
|
||||
authorization_type: VoteAuthorize,
|
||||
) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*vote_pubkey, false),
|
||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||
AccountMeta::new_readonly(*current_authority_base_key, true),
|
||||
];
|
||||
|
||||
Instruction::new_with_bincode(
|
||||
id(),
|
||||
&VoteInstruction::AuthorizeWithSeed(VoteAuthorizeWithSeedArgs {
|
||||
authorization_type,
|
||||
current_authority_derived_key_owner: *current_authority_derived_key_owner,
|
||||
current_authority_derived_key_seed: current_authority_derived_key_seed.to_string(),
|
||||
new_authority: *new_authority,
|
||||
}),
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn authorize_checked_with_seed(
|
||||
vote_pubkey: &Pubkey,
|
||||
current_authority_base_key: &Pubkey,
|
||||
current_authority_derived_key_owner: &Pubkey,
|
||||
current_authority_derived_key_seed: &str,
|
||||
new_authority: &Pubkey,
|
||||
authorization_type: VoteAuthorize,
|
||||
) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*vote_pubkey, false),
|
||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||
AccountMeta::new_readonly(*current_authority_base_key, true),
|
||||
AccountMeta::new_readonly(*new_authority, true),
|
||||
];
|
||||
|
||||
Instruction::new_with_bincode(
|
||||
id(),
|
||||
&VoteInstruction::AuthorizeCheckedWithSeed(VoteAuthorizeCheckedWithSeedArgs {
|
||||
authorization_type,
|
||||
current_authority_derived_key_owner: *current_authority_derived_key_owner,
|
||||
current_authority_derived_key_seed: current_authority_derived_key_seed.to_string(),
|
||||
}),
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update_validator_identity(
|
||||
vote_pubkey: &Pubkey,
|
||||
authorized_withdrawer_pubkey: &Pubkey,
|
||||
|
|
|
@ -1,14 +1,65 @@
|
|||
//! Vote program processor
|
||||
|
||||
use {
|
||||
crate::{id, vote_instruction::VoteInstruction, vote_state},
|
||||
crate::{
|
||||
id,
|
||||
vote_instruction::VoteInstruction,
|
||||
vote_state::{self, VoteAuthorize},
|
||||
},
|
||||
log::*,
|
||||
solana_program_runtime::{
|
||||
invoke_context::InvokeContext, sysvar_cache::get_sysvar_with_account_check,
|
||||
},
|
||||
solana_sdk::{feature_set, instruction::InstructionError, program_utils::limited_deserialize},
|
||||
solana_sdk::{
|
||||
feature_set,
|
||||
instruction::InstructionError,
|
||||
program_utils::limited_deserialize,
|
||||
pubkey::Pubkey,
|
||||
transaction_context::{BorrowedAccount, InstructionContext, TransactionContext},
|
||||
},
|
||||
std::collections::HashSet,
|
||||
};
|
||||
|
||||
fn process_authorize_with_seed_instruction(
|
||||
invoke_context: &InvokeContext,
|
||||
instruction_context: &InstructionContext,
|
||||
transaction_context: &TransactionContext,
|
||||
first_instruction_account: usize,
|
||||
vote_account: &mut BorrowedAccount,
|
||||
new_authority: &Pubkey,
|
||||
authorization_type: VoteAuthorize,
|
||||
current_authority_derived_key_owner: &Pubkey,
|
||||
current_authority_derived_key_seed: &str,
|
||||
) -> Result<(), InstructionError> {
|
||||
if !invoke_context
|
||||
.feature_set
|
||||
.is_active(&feature_set::vote_authorize_with_seed::id())
|
||||
{
|
||||
return Err(InstructionError::InvalidInstructionData);
|
||||
}
|
||||
let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?;
|
||||
let mut expected_authority_keys: HashSet<Pubkey> = HashSet::default();
|
||||
let authority_base_key_index = first_instruction_account + 2;
|
||||
if instruction_context.is_signer(authority_base_key_index)? {
|
||||
let base_pubkey = transaction_context.get_key_of_account_at_index(
|
||||
instruction_context.get_index_in_transaction(authority_base_key_index)?,
|
||||
)?;
|
||||
expected_authority_keys.insert(Pubkey::create_with_seed(
|
||||
base_pubkey,
|
||||
current_authority_derived_key_seed,
|
||||
current_authority_derived_key_owner,
|
||||
)?);
|
||||
};
|
||||
vote_state::authorize(
|
||||
vote_account,
|
||||
new_authority,
|
||||
authorization_type,
|
||||
&expected_authority_keys,
|
||||
&clock,
|
||||
&invoke_context.feature_set,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn process_instruction(
|
||||
first_instruction_account: usize,
|
||||
invoke_context: &mut InvokeContext,
|
||||
|
@ -48,6 +99,41 @@ pub fn process_instruction(
|
|||
&invoke_context.feature_set,
|
||||
)
|
||||
}
|
||||
VoteInstruction::AuthorizeWithSeed(args) => {
|
||||
instruction_context.check_number_of_instruction_accounts(3)?;
|
||||
process_authorize_with_seed_instruction(
|
||||
invoke_context,
|
||||
instruction_context,
|
||||
transaction_context,
|
||||
first_instruction_account,
|
||||
&mut me,
|
||||
&args.new_authority,
|
||||
args.authorization_type,
|
||||
&args.current_authority_derived_key_owner,
|
||||
args.current_authority_derived_key_seed.as_str(),
|
||||
)
|
||||
}
|
||||
VoteInstruction::AuthorizeCheckedWithSeed(args) => {
|
||||
instruction_context.check_number_of_instruction_accounts(4)?;
|
||||
let new_authority_index = first_instruction_account + 3;
|
||||
let new_authority = transaction_context.get_key_of_account_at_index(
|
||||
instruction_context.get_index_in_transaction(new_authority_index)?,
|
||||
)?;
|
||||
if !instruction_context.is_signer(new_authority_index)? {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
process_authorize_with_seed_instruction(
|
||||
invoke_context,
|
||||
instruction_context,
|
||||
transaction_context,
|
||||
first_instruction_account,
|
||||
&mut me,
|
||||
new_authority,
|
||||
args.authorization_type,
|
||||
&args.current_authority_derived_key_owner,
|
||||
args.current_authority_derived_key_seed.as_str(),
|
||||
)
|
||||
}
|
||||
VoteInstruction::UpdateValidatorIdentity => {
|
||||
instruction_context.check_number_of_instruction_accounts(2)?;
|
||||
let node_pubkey = transaction_context.get_key_of_account_at_index(
|
||||
|
@ -166,8 +252,8 @@ mod tests {
|
|||
vote_switch, withdraw, VoteInstruction,
|
||||
},
|
||||
vote_state::{
|
||||
Lockout, Vote, VoteAuthorize, VoteInit, VoteState, VoteStateUpdate,
|
||||
VoteStateVersions,
|
||||
Lockout, Vote, VoteAuthorize, VoteAuthorizeCheckedWithSeedArgs,
|
||||
VoteAuthorizeWithSeedArgs, VoteInit, VoteState, VoteStateUpdate, VoteStateVersions,
|
||||
},
|
||||
},
|
||||
bincode::serialize,
|
||||
|
@ -184,6 +270,17 @@ mod tests {
|
|||
std::{collections::HashSet, str::FromStr},
|
||||
};
|
||||
|
||||
struct VoteAccountTestFixtureWithAuthorities {
|
||||
vote_account: AccountSharedData,
|
||||
vote_pubkey: Pubkey,
|
||||
voter_base_key: Pubkey,
|
||||
voter_owner: Pubkey,
|
||||
voter_seed: String,
|
||||
withdrawer_base_key: Pubkey,
|
||||
withdrawer_owner: Pubkey,
|
||||
withdrawer_seed: String,
|
||||
}
|
||||
|
||||
fn create_default_account() -> AccountSharedData {
|
||||
AccountSharedData::new(0, 0, &Pubkey::new_unique())
|
||||
}
|
||||
|
@ -312,6 +409,41 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
fn create_test_account_with_authorized_from_seed() -> VoteAccountTestFixtureWithAuthorities {
|
||||
let vote_pubkey = Pubkey::new_unique();
|
||||
let voter_base_key = Pubkey::new_unique();
|
||||
let voter_owner = Pubkey::new_unique();
|
||||
let voter_seed = String::from("VOTER_SEED");
|
||||
let withdrawer_base_key = Pubkey::new_unique();
|
||||
let withdrawer_owner = Pubkey::new_unique();
|
||||
let withdrawer_seed = String::from("WITHDRAWER_SEED");
|
||||
let authorized_voter =
|
||||
Pubkey::create_with_seed(&voter_base_key, voter_seed.as_str(), &voter_owner).unwrap();
|
||||
let authorized_withdrawer = Pubkey::create_with_seed(
|
||||
&withdrawer_base_key,
|
||||
withdrawer_seed.as_str(),
|
||||
&withdrawer_owner,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
VoteAccountTestFixtureWithAuthorities {
|
||||
vote_account: vote_state::create_account_with_authorized(
|
||||
&Pubkey::new_unique(),
|
||||
&authorized_voter,
|
||||
&authorized_withdrawer,
|
||||
0,
|
||||
100,
|
||||
),
|
||||
vote_pubkey,
|
||||
voter_base_key,
|
||||
voter_owner,
|
||||
voter_seed,
|
||||
withdrawer_base_key,
|
||||
withdrawer_owner,
|
||||
withdrawer_seed,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_test_account_with_epoch_credits(
|
||||
credits_to_append: &[u64],
|
||||
) -> (Pubkey, AccountSharedData) {
|
||||
|
@ -1096,6 +1228,515 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
fn perform_authorize_with_seed_test(
|
||||
authorization_type: VoteAuthorize,
|
||||
vote_pubkey: Pubkey,
|
||||
vote_account: AccountSharedData,
|
||||
current_authority_base_key: Pubkey,
|
||||
current_authority_seed: String,
|
||||
current_authority_owner: Pubkey,
|
||||
new_authority_pubkey: Pubkey,
|
||||
) {
|
||||
let clock = Clock {
|
||||
epoch: 1,
|
||||
leader_schedule_epoch: 2,
|
||||
..Clock::default()
|
||||
};
|
||||
let clock_account = account::create_account_shared_data_for_test(&clock);
|
||||
let transaction_accounts = vec![
|
||||
(vote_pubkey, vote_account),
|
||||
(sysvar::clock::id(), clock_account),
|
||||
(current_authority_base_key, AccountSharedData::default()),
|
||||
];
|
||||
let mut instruction_accounts = vec![
|
||||
AccountMeta {
|
||||
pubkey: vote_pubkey,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::clock::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: current_authority_base_key,
|
||||
is_signer: true,
|
||||
is_writable: false,
|
||||
},
|
||||
];
|
||||
|
||||
// Can't change authority unless base key signs.
|
||||
instruction_accounts[2].is_signer = false;
|
||||
process_instruction(
|
||||
&serialize(&VoteInstruction::AuthorizeWithSeed(
|
||||
VoteAuthorizeWithSeedArgs {
|
||||
authorization_type,
|
||||
current_authority_derived_key_owner: current_authority_owner,
|
||||
current_authority_derived_key_seed: current_authority_seed.clone(),
|
||||
new_authority: new_authority_pubkey,
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
instruction_accounts[2].is_signer = true;
|
||||
|
||||
// Can't change authority if seed doesn't match.
|
||||
process_instruction(
|
||||
&serialize(&VoteInstruction::AuthorizeWithSeed(
|
||||
VoteAuthorizeWithSeedArgs {
|
||||
authorization_type,
|
||||
current_authority_derived_key_owner: current_authority_owner,
|
||||
current_authority_derived_key_seed: String::from("WRONG_SEED"),
|
||||
new_authority: new_authority_pubkey,
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
|
||||
// Can't change authority if owner doesn't match.
|
||||
process_instruction(
|
||||
&serialize(&VoteInstruction::AuthorizeWithSeed(
|
||||
VoteAuthorizeWithSeedArgs {
|
||||
authorization_type,
|
||||
current_authority_derived_key_owner: Pubkey::new_unique(), // Wrong owner.
|
||||
current_authority_derived_key_seed: current_authority_seed.clone(),
|
||||
new_authority: new_authority_pubkey,
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
|
||||
// Can change authority when base key signs for related derived key.
|
||||
process_instruction(
|
||||
&serialize(&VoteInstruction::AuthorizeWithSeed(
|
||||
VoteAuthorizeWithSeedArgs {
|
||||
authorization_type,
|
||||
current_authority_derived_key_owner: current_authority_owner,
|
||||
current_authority_derived_key_seed: current_authority_seed.clone(),
|
||||
new_authority: new_authority_pubkey,
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
Ok(()),
|
||||
);
|
||||
|
||||
// Should fail when the `vote_authorize_with_seed` feature is disabled
|
||||
process_instruction_disabled_features(
|
||||
&serialize(&VoteInstruction::AuthorizeWithSeed(
|
||||
VoteAuthorizeWithSeedArgs {
|
||||
authorization_type,
|
||||
current_authority_derived_key_owner: current_authority_owner,
|
||||
current_authority_derived_key_seed: current_authority_seed,
|
||||
new_authority: new_authority_pubkey,
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
transaction_accounts,
|
||||
instruction_accounts,
|
||||
Err(InstructionError::InvalidInstructionData),
|
||||
);
|
||||
}
|
||||
|
||||
fn perform_authorize_checked_with_seed_test(
|
||||
authorization_type: VoteAuthorize,
|
||||
vote_pubkey: Pubkey,
|
||||
vote_account: AccountSharedData,
|
||||
current_authority_base_key: Pubkey,
|
||||
current_authority_seed: String,
|
||||
current_authority_owner: Pubkey,
|
||||
new_authority_pubkey: Pubkey,
|
||||
) {
|
||||
let clock = Clock {
|
||||
epoch: 1,
|
||||
leader_schedule_epoch: 2,
|
||||
..Clock::default()
|
||||
};
|
||||
let clock_account = account::create_account_shared_data_for_test(&clock);
|
||||
let transaction_accounts = vec![
|
||||
(vote_pubkey, vote_account),
|
||||
(sysvar::clock::id(), clock_account),
|
||||
(current_authority_base_key, AccountSharedData::default()),
|
||||
(new_authority_pubkey, AccountSharedData::default()),
|
||||
];
|
||||
let mut instruction_accounts = vec![
|
||||
AccountMeta {
|
||||
pubkey: vote_pubkey,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::clock::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: current_authority_base_key,
|
||||
is_signer: true,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: new_authority_pubkey,
|
||||
is_signer: true,
|
||||
is_writable: false,
|
||||
},
|
||||
];
|
||||
|
||||
// Can't change authority unless base key signs.
|
||||
instruction_accounts[2].is_signer = false;
|
||||
process_instruction(
|
||||
&serialize(&VoteInstruction::AuthorizeCheckedWithSeed(
|
||||
VoteAuthorizeCheckedWithSeedArgs {
|
||||
authorization_type,
|
||||
current_authority_derived_key_owner: current_authority_owner,
|
||||
current_authority_derived_key_seed: current_authority_seed.clone(),
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
instruction_accounts[2].is_signer = true;
|
||||
|
||||
// Can't change authority unless new authority signs.
|
||||
instruction_accounts[3].is_signer = false;
|
||||
process_instruction(
|
||||
&serialize(&VoteInstruction::AuthorizeCheckedWithSeed(
|
||||
VoteAuthorizeCheckedWithSeedArgs {
|
||||
authorization_type,
|
||||
current_authority_derived_key_owner: current_authority_owner,
|
||||
current_authority_derived_key_seed: current_authority_seed.clone(),
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
instruction_accounts[3].is_signer = true;
|
||||
|
||||
// Can't change authority if seed doesn't match.
|
||||
process_instruction(
|
||||
&serialize(&VoteInstruction::AuthorizeCheckedWithSeed(
|
||||
VoteAuthorizeCheckedWithSeedArgs {
|
||||
authorization_type,
|
||||
current_authority_derived_key_owner: current_authority_owner,
|
||||
current_authority_derived_key_seed: String::from("WRONG_SEED"),
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
|
||||
// Can't change authority if owner doesn't match.
|
||||
process_instruction(
|
||||
&serialize(&VoteInstruction::AuthorizeCheckedWithSeed(
|
||||
VoteAuthorizeCheckedWithSeedArgs {
|
||||
authorization_type,
|
||||
current_authority_derived_key_owner: Pubkey::new_unique(), // Wrong owner.
|
||||
current_authority_derived_key_seed: current_authority_seed.clone(),
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
|
||||
// Can change authority when base key signs for related derived key and new authority signs.
|
||||
process_instruction(
|
||||
&serialize(&VoteInstruction::AuthorizeCheckedWithSeed(
|
||||
VoteAuthorizeCheckedWithSeedArgs {
|
||||
authorization_type,
|
||||
current_authority_derived_key_owner: current_authority_owner,
|
||||
current_authority_derived_key_seed: current_authority_seed.clone(),
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
transaction_accounts.clone(),
|
||||
instruction_accounts.clone(),
|
||||
Ok(()),
|
||||
);
|
||||
|
||||
// Should fail when the `vote_authorize_with_seed` feature is disabled
|
||||
process_instruction_disabled_features(
|
||||
&serialize(&VoteInstruction::AuthorizeCheckedWithSeed(
|
||||
VoteAuthorizeCheckedWithSeedArgs {
|
||||
authorization_type,
|
||||
current_authority_derived_key_owner: current_authority_owner,
|
||||
current_authority_derived_key_seed: current_authority_seed,
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
transaction_accounts,
|
||||
instruction_accounts,
|
||||
Err(InstructionError::InvalidInstructionData),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_voter_base_key_can_authorize_new_voter() {
|
||||
let VoteAccountTestFixtureWithAuthorities {
|
||||
vote_pubkey,
|
||||
voter_base_key,
|
||||
voter_owner,
|
||||
voter_seed,
|
||||
vote_account,
|
||||
..
|
||||
} = create_test_account_with_authorized_from_seed();
|
||||
let new_voter_pubkey = Pubkey::new_unique();
|
||||
perform_authorize_with_seed_test(
|
||||
VoteAuthorize::Voter,
|
||||
vote_pubkey,
|
||||
vote_account,
|
||||
voter_base_key,
|
||||
voter_seed,
|
||||
voter_owner,
|
||||
new_voter_pubkey,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_withdrawer_base_key_can_authorize_new_voter() {
|
||||
let VoteAccountTestFixtureWithAuthorities {
|
||||
vote_pubkey,
|
||||
withdrawer_base_key,
|
||||
withdrawer_owner,
|
||||
withdrawer_seed,
|
||||
vote_account,
|
||||
..
|
||||
} = create_test_account_with_authorized_from_seed();
|
||||
let new_voter_pubkey = Pubkey::new_unique();
|
||||
perform_authorize_with_seed_test(
|
||||
VoteAuthorize::Voter,
|
||||
vote_pubkey,
|
||||
vote_account,
|
||||
withdrawer_base_key,
|
||||
withdrawer_seed,
|
||||
withdrawer_owner,
|
||||
new_voter_pubkey,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_voter_base_key_can_not_authorize_new_withdrawer() {
|
||||
let VoteAccountTestFixtureWithAuthorities {
|
||||
vote_pubkey,
|
||||
voter_base_key,
|
||||
voter_owner,
|
||||
voter_seed,
|
||||
vote_account,
|
||||
..
|
||||
} = create_test_account_with_authorized_from_seed();
|
||||
let new_withdrawer_pubkey = Pubkey::new_unique();
|
||||
let clock = Clock {
|
||||
epoch: 1,
|
||||
leader_schedule_epoch: 2,
|
||||
..Clock::default()
|
||||
};
|
||||
let clock_account = account::create_account_shared_data_for_test(&clock);
|
||||
let transaction_accounts = vec![
|
||||
(vote_pubkey, vote_account),
|
||||
(sysvar::clock::id(), clock_account),
|
||||
(voter_base_key, AccountSharedData::default()),
|
||||
];
|
||||
let instruction_accounts = vec![
|
||||
AccountMeta {
|
||||
pubkey: vote_pubkey,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::clock::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: voter_base_key,
|
||||
is_signer: true,
|
||||
is_writable: false,
|
||||
},
|
||||
];
|
||||
// Despite having Voter authority, you may not change the Withdrawer authority.
|
||||
process_instruction(
|
||||
&serialize(&VoteInstruction::AuthorizeWithSeed(
|
||||
VoteAuthorizeWithSeedArgs {
|
||||
authorization_type: VoteAuthorize::Withdrawer,
|
||||
current_authority_derived_key_owner: voter_owner,
|
||||
current_authority_derived_key_seed: voter_seed,
|
||||
new_authority: new_withdrawer_pubkey,
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
transaction_accounts,
|
||||
instruction_accounts,
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_withdrawer_base_key_can_authorize_new_withdrawer() {
|
||||
let VoteAccountTestFixtureWithAuthorities {
|
||||
vote_pubkey,
|
||||
withdrawer_base_key,
|
||||
withdrawer_owner,
|
||||
withdrawer_seed,
|
||||
vote_account,
|
||||
..
|
||||
} = create_test_account_with_authorized_from_seed();
|
||||
let new_withdrawer_pubkey = Pubkey::new_unique();
|
||||
perform_authorize_with_seed_test(
|
||||
VoteAuthorize::Withdrawer,
|
||||
vote_pubkey,
|
||||
vote_account,
|
||||
withdrawer_base_key,
|
||||
withdrawer_seed,
|
||||
withdrawer_owner,
|
||||
new_withdrawer_pubkey,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_voter_base_key_can_authorize_new_voter_checked() {
|
||||
let VoteAccountTestFixtureWithAuthorities {
|
||||
vote_pubkey,
|
||||
voter_base_key,
|
||||
voter_owner,
|
||||
voter_seed,
|
||||
vote_account,
|
||||
..
|
||||
} = create_test_account_with_authorized_from_seed();
|
||||
let new_voter_pubkey = Pubkey::new_unique();
|
||||
perform_authorize_checked_with_seed_test(
|
||||
VoteAuthorize::Voter,
|
||||
vote_pubkey,
|
||||
vote_account,
|
||||
voter_base_key,
|
||||
voter_seed,
|
||||
voter_owner,
|
||||
new_voter_pubkey,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_withdrawer_base_key_can_authorize_new_voter_checked() {
|
||||
let VoteAccountTestFixtureWithAuthorities {
|
||||
vote_pubkey,
|
||||
withdrawer_base_key,
|
||||
withdrawer_owner,
|
||||
withdrawer_seed,
|
||||
vote_account,
|
||||
..
|
||||
} = create_test_account_with_authorized_from_seed();
|
||||
let new_voter_pubkey = Pubkey::new_unique();
|
||||
perform_authorize_checked_with_seed_test(
|
||||
VoteAuthorize::Voter,
|
||||
vote_pubkey,
|
||||
vote_account,
|
||||
withdrawer_base_key,
|
||||
withdrawer_seed,
|
||||
withdrawer_owner,
|
||||
new_voter_pubkey,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_voter_base_key_can_not_authorize_new_withdrawer_checked() {
|
||||
let VoteAccountTestFixtureWithAuthorities {
|
||||
vote_pubkey,
|
||||
voter_base_key,
|
||||
voter_owner,
|
||||
voter_seed,
|
||||
vote_account,
|
||||
..
|
||||
} = create_test_account_with_authorized_from_seed();
|
||||
let new_withdrawer_pubkey = Pubkey::new_unique();
|
||||
let clock = Clock {
|
||||
epoch: 1,
|
||||
leader_schedule_epoch: 2,
|
||||
..Clock::default()
|
||||
};
|
||||
let clock_account = account::create_account_shared_data_for_test(&clock);
|
||||
let transaction_accounts = vec![
|
||||
(vote_pubkey, vote_account),
|
||||
(sysvar::clock::id(), clock_account),
|
||||
(voter_base_key, AccountSharedData::default()),
|
||||
(new_withdrawer_pubkey, AccountSharedData::default()),
|
||||
];
|
||||
let instruction_accounts = vec![
|
||||
AccountMeta {
|
||||
pubkey: vote_pubkey,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: sysvar::clock::id(),
|
||||
is_signer: false,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: voter_base_key,
|
||||
is_signer: true,
|
||||
is_writable: false,
|
||||
},
|
||||
AccountMeta {
|
||||
pubkey: new_withdrawer_pubkey,
|
||||
is_signer: true,
|
||||
is_writable: false,
|
||||
},
|
||||
];
|
||||
// Despite having Voter authority, you may not change the Withdrawer authority.
|
||||
process_instruction(
|
||||
&serialize(&VoteInstruction::AuthorizeCheckedWithSeed(
|
||||
VoteAuthorizeCheckedWithSeedArgs {
|
||||
authorization_type: VoteAuthorize::Withdrawer,
|
||||
current_authority_derived_key_owner: voter_owner,
|
||||
current_authority_derived_key_seed: voter_seed,
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
transaction_accounts,
|
||||
instruction_accounts,
|
||||
Err(InstructionError::MissingRequiredSignature),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_withdrawer_base_key_can_authorize_new_withdrawer_checked() {
|
||||
let VoteAccountTestFixtureWithAuthorities {
|
||||
vote_pubkey,
|
||||
withdrawer_base_key,
|
||||
withdrawer_owner,
|
||||
withdrawer_seed,
|
||||
vote_account,
|
||||
..
|
||||
} = create_test_account_with_authorized_from_seed();
|
||||
let new_withdrawer_pubkey = Pubkey::new_unique();
|
||||
perform_authorize_checked_with_seed_test(
|
||||
VoteAuthorize::Withdrawer,
|
||||
vote_pubkey,
|
||||
vote_account,
|
||||
withdrawer_base_key,
|
||||
withdrawer_seed,
|
||||
withdrawer_owner,
|
||||
new_withdrawer_pubkey,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spoofed_vote() {
|
||||
process_instruction_as_one_arg(
|
||||
|
|
|
@ -240,6 +240,21 @@ pub enum VoteAuthorize {
|
|||
Withdrawer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||
pub struct VoteAuthorizeWithSeedArgs {
|
||||
pub authorization_type: VoteAuthorize,
|
||||
pub current_authority_derived_key_owner: Pubkey,
|
||||
pub current_authority_derived_key_seed: String,
|
||||
pub new_authority: Pubkey,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||
pub struct VoteAuthorizeCheckedWithSeedArgs {
|
||||
pub authorization_type: VoteAuthorize,
|
||||
pub current_authority_derived_key_owner: Pubkey,
|
||||
pub current_authority_derived_key_seed: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone, AbiExample)]
|
||||
pub struct BlockTimestamp {
|
||||
pub slot: Slot,
|
||||
|
|
|
@ -82,6 +82,8 @@ fn parse_vote_instruction_data(
|
|||
}
|
||||
VoteInstruction::Authorize(_, _)
|
||||
| VoteInstruction::AuthorizeChecked(_)
|
||||
| VoteInstruction::AuthorizeWithSeed(_)
|
||||
| VoteInstruction::AuthorizeCheckedWithSeed(_)
|
||||
| VoteInstruction::InitializeAccount(_)
|
||||
| VoteInstruction::UpdateCommission(_)
|
||||
| VoteInstruction::UpdateValidatorIdentity
|
||||
|
|
|
@ -440,6 +440,10 @@ pub mod nonce_must_be_advanceable {
|
|||
solana_sdk::declare_id!("3u3Er5Vc2jVcwz4xr2GJeSAXT3fAj6ADHZ4BJMZiScFd");
|
||||
}
|
||||
|
||||
pub mod vote_authorize_with_seed {
|
||||
solana_sdk::declare_id!("6tRxEYKuy2L5nnv5bgn7iT28MxUbYxp5h7F3Ncf1exrT");
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// Map of feature identifiers to user-visible description
|
||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
||||
|
@ -544,6 +548,7 @@ lazy_static! {
|
|||
(quick_bail_on_panic::id(), "quick bail on panic"),
|
||||
(nonce_must_be_authorized::id(), "nonce must be authorized"),
|
||||
(nonce_must_be_advanceable::id(), "durable nonces must be advanceable"),
|
||||
(vote_authorize_with_seed::id(), "An instruction you can use to change a vote accounts authority when the current authority is a derived key #25860"),
|
||||
/*************** ADD NEW FEATURES HERE ***************/
|
||||
]
|
||||
.iter()
|
||||
|
|
|
@ -52,6 +52,36 @@ pub fn parse_vote(
|
|||
}),
|
||||
})
|
||||
}
|
||||
VoteInstruction::AuthorizeWithSeed(args) => {
|
||||
check_num_vote_accounts(&instruction.accounts, 3)?;
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "authorizeWithSeed".to_string(),
|
||||
info: json!({
|
||||
"voteAccount": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"clockSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
"authorityBaseKey": account_keys[instruction.accounts[2] as usize].to_string(),
|
||||
"authorityOwner": args.current_authority_derived_key_owner.to_string(),
|
||||
"authoritySeed": args.current_authority_derived_key_seed,
|
||||
"newAuthority": args.new_authority.to_string(),
|
||||
"authorityType": args.authorization_type,
|
||||
}),
|
||||
})
|
||||
}
|
||||
VoteInstruction::AuthorizeCheckedWithSeed(args) => {
|
||||
check_num_vote_accounts(&instruction.accounts, 4)?;
|
||||
Ok(ParsedInstructionEnum {
|
||||
instruction_type: "authorizeCheckedWithSeed".to_string(),
|
||||
info: json!({
|
||||
"voteAccount": account_keys[instruction.accounts[0] as usize].to_string(),
|
||||
"clockSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
|
||||
"authorityBaseKey": account_keys[instruction.accounts[2] as usize].to_string(),
|
||||
"authorityOwner": args.current_authority_derived_key_owner.to_string(),
|
||||
"authoritySeed": args.current_authority_derived_key_seed,
|
||||
"newAuthority": account_keys[instruction.accounts[3] as usize].to_string(),
|
||||
"authorityType": args.authorization_type,
|
||||
}),
|
||||
})
|
||||
}
|
||||
VoteInstruction::Vote(vote) => {
|
||||
check_num_vote_accounts(&instruction.accounts, 4)?;
|
||||
let vote = json!({
|
||||
|
@ -285,6 +315,92 @@ mod test {
|
|||
assert!(parse_vote(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_vote_authorize_with_seed_ix() {
|
||||
let vote_pubkey = Pubkey::new_unique();
|
||||
let authorized_base_key = Pubkey::new_unique();
|
||||
let new_authorized_pubkey = Pubkey::new_unique();
|
||||
let authority_type = VoteAuthorize::Voter;
|
||||
let current_authority_owner = Pubkey::new_unique();
|
||||
let current_authority_seed = "AUTHORITY_SEED";
|
||||
let instruction = vote_instruction::authorize_with_seed(
|
||||
&vote_pubkey,
|
||||
&authorized_base_key,
|
||||
¤t_authority_owner,
|
||||
current_authority_seed,
|
||||
&new_authorized_pubkey,
|
||||
authority_type,
|
||||
);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_vote(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys, None)
|
||||
)
|
||||
.unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "authorizeWithSeed".to_string(),
|
||||
info: json!({
|
||||
"voteAccount": vote_pubkey.to_string(),
|
||||
"clockSysvar": sysvar::clock::ID.to_string(),
|
||||
"authorityBaseKey": authorized_base_key.to_string(),
|
||||
"authorityOwner": current_authority_owner.to_string(),
|
||||
"authoritySeed": current_authority_seed,
|
||||
"newAuthority": new_authorized_pubkey.to_string(),
|
||||
"authorityType": authority_type,
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_vote(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys[0..2], None)
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_vote_authorize_with_seed_checked_ix() {
|
||||
let vote_pubkey = Pubkey::new_unique();
|
||||
let authorized_base_key = Pubkey::new_unique();
|
||||
let new_authorized_pubkey = Pubkey::new_unique();
|
||||
let authority_type = VoteAuthorize::Voter;
|
||||
let current_authority_owner = Pubkey::new_unique();
|
||||
let current_authority_seed = "AUTHORITY_SEED";
|
||||
let instruction = vote_instruction::authorize_checked_with_seed(
|
||||
&vote_pubkey,
|
||||
&authorized_base_key,
|
||||
¤t_authority_owner,
|
||||
current_authority_seed,
|
||||
&new_authorized_pubkey,
|
||||
authority_type,
|
||||
);
|
||||
let message = Message::new(&[instruction], None);
|
||||
assert_eq!(
|
||||
parse_vote(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys, None)
|
||||
)
|
||||
.unwrap(),
|
||||
ParsedInstructionEnum {
|
||||
instruction_type: "authorizeCheckedWithSeed".to_string(),
|
||||
info: json!({
|
||||
"voteAccount": vote_pubkey.to_string(),
|
||||
"clockSysvar": sysvar::clock::ID.to_string(),
|
||||
"authorityBaseKey": authorized_base_key.to_string(),
|
||||
"authorityOwner": current_authority_owner.to_string(),
|
||||
"authoritySeed": current_authority_seed,
|
||||
"newAuthority": new_authorized_pubkey.to_string(),
|
||||
"authorityType": authority_type,
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert!(parse_vote(
|
||||
&message.instructions[0],
|
||||
&AccountKeys::new(&message.account_keys[0..3], None)
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_vote_ix() {
|
||||
let hash = Hash::new_from_array([1; 32]);
|
||||
|
|
Loading…
Reference in New Issue