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:
Steven Luscher 2022-06-13 20:36:44 -07:00 committed by GitHub
parent 7b786ff331
commit 45d11f3d26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 870 additions and 5 deletions

View File

@ -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`.

View File

@ -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,

View File

@ -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(

View File

@ -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,

View File

@ -82,6 +82,8 @@ fn parse_vote_instruction_data(
}
VoteInstruction::Authorize(_, _)
| VoteInstruction::AuthorizeChecked(_)
| VoteInstruction::AuthorizeWithSeed(_)
| VoteInstruction::AuthorizeCheckedWithSeed(_)
| VoteInstruction::InitializeAccount(_)
| VoteInstruction::UpdateCommission(_)
| VoteInstruction::UpdateValidatorIdentity

View File

@ -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()

View File

@ -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,
&current_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,
&current_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]);