From 8f065e487e50a89b37dfe988fd632d8686fce6d3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 10 Jun 2020 10:09:04 -0700 Subject: [PATCH] Add ability to change the commission of a vote account (bp #10493) (#10498) automerge --- cli/src/cli.rs | 11 +++ cli/src/vote.rs | 104 ++++++++++++++++++++++++++ programs/vote/src/vote_instruction.rs | 35 +++++++++ programs/vote/src/vote_state/mod.rs | 61 +++++++++++++++ 4 files changed, 211 insertions(+) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 6fcf7e16a9..999156fa53 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -397,6 +397,10 @@ pub enum CliCommand { vote_account_pubkey: Pubkey, new_identity_account: SignerIndex, }, + VoteUpdateCommission { + vote_account_pubkey: Pubkey, + commission: u8, + }, // Wallet Commands Address, Airdrop { @@ -710,6 +714,9 @@ pub fn parse_command( ("vote-update-validator", Some(matches)) => { parse_vote_update_validator(matches, default_signer_path, wallet_manager) } + ("vote-update-commission", Some(matches)) => { + parse_vote_update_commission(matches, default_signer_path, wallet_manager) + } ("vote-authorize-voter", Some(matches)) => parse_vote_authorize( matches, default_signer_path, @@ -2144,6 +2151,10 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { &vote_account_pubkey, *new_identity_account, ), + CliCommand::VoteUpdateCommission { + vote_account_pubkey, + commission, + } => process_vote_update_commission(&rpc_client, config, &vote_account_pubkey, *commission), // Wallet Commands diff --git a/cli/src/vote.rs b/cli/src/vote.rs index dc1da1b816..e13198d78f 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -161,6 +161,35 @@ impl VoteSubCommands for App<'_, '_> { .help("Authorized withdrawer keypair"), ) ) + .subcommand( + SubCommand::with_name("vote-update-commission") + .about("Update the vote account's commission") + .arg( + pubkey!(Arg::with_name("vote_account_pubkey") + .index(1) + .value_name("VOTE_ACCOUNT_ADDRESS") + .required(true), + "Vote account to update. "), + ) + .arg( + Arg::with_name("commission") + .index(2) + .value_name("PERCENTAGE") + .takes_value(true) + .required(true) + .validator(is_valid_percentage) + .help("The new commission") + ) + .arg( + Arg::with_name("authorized_withdrawer") + .index(3) + .value_name("AUTHORIZED_KEYPAIR") + .takes_value(true) + .required(true) + .validator(is_valid_signer) + .help("Authorized withdrawer keypair"), + ) + ) .subcommand( SubCommand::with_name("vote-account") .about("Show the contents of a vote account") @@ -309,6 +338,33 @@ pub fn parse_vote_update_validator( }) } +pub fn parse_vote_update_commission( + matches: &ArgMatches<'_>, + default_signer_path: &str, + wallet_manager: &mut Option>, +) -> Result { + let vote_account_pubkey = + pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap(); + let (authorized_withdrawer, _) = signer_of(matches, "authorized_withdrawer", wallet_manager)?; + let commission = value_t_or_exit!(matches, "commission", u8); + + let payer_provided = None; + let signer_info = generate_unique_signers( + vec![payer_provided, authorized_withdrawer], + matches, + default_signer_path, + wallet_manager, + )?; + + Ok(CliCommandInfo { + command: CliCommand::VoteUpdateCommission { + vote_account_pubkey, + commission, + }, + signers: signer_info.signers, + }) +} + pub fn parse_vote_get_account_command( matches: &ArgMatches<'_>, wallet_manager: &mut Option>, @@ -521,6 +577,33 @@ pub fn process_vote_update_validator( log_instruction_custom_error::(result, &config) } +pub fn process_vote_update_commission( + rpc_client: &RpcClient, + config: &CliConfig, + vote_account_pubkey: &Pubkey, + commission: u8, +) -> ProcessResult { + let authorized_withdrawer = config.signers[1]; + let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; + let ixs = vec![vote_instruction::update_commission( + vote_account_pubkey, + &authorized_withdrawer.pubkey(), + commission, + )]; + + let message = Message::new_with_payer(&ixs, Some(&config.signers[0].pubkey())); + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&config.signers, recent_blockhash)?; + check_account_for_fee( + rpc_client, + &config.signers[0].pubkey(), + &fee_calculator, + &tx.message, + )?; + let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx); + log_instruction_custom_error::(result, &config) +} + fn get_vote_account( rpc_client: &RpcClient, vote_account_pubkey: &Pubkey, @@ -842,6 +925,27 @@ mod tests { } ); + let test_update_commission = test_commands.clone().get_matches_from(vec![ + "test", + "vote-update-commission", + &pubkey_string, + "42", + &keypair_file, + ]); + assert_eq!( + parse_command(&test_update_commission, &default_keypair_file, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::VoteUpdateCommission { + vote_account_pubkey: pubkey, + commission: 42, + }, + signers: vec![ + read_keypair_file(&default_keypair_file).unwrap().into(), + Box::new(read_keypair_file(&keypair_file).unwrap()), + ], + } + ); + // Test WithdrawFromVoteAccount subcommand let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![ "test", diff --git a/programs/vote/src/vote_instruction.rs b/programs/vote/src/vote_instruction.rs index 18db807484..6404d90362 100644 --- a/programs/vote/src/vote_instruction.rs +++ b/programs/vote/src/vote_instruction.rs @@ -93,6 +93,13 @@ pub enum VoteInstruction { /// 2. [SIGNER] Withdraw authority UpdateValidatorIdentity, + /// Update the commission for the vote account + /// + /// # Account references + /// 0. [WRITE] Vote account to be updated + /// 1. [SIGNER] Withdraw authority + UpdateCommission(u8), + /// A Vote instruction with recent votes /// /// # Account references @@ -190,6 +197,23 @@ pub fn update_validator_identity( ) } +pub fn update_commission( + vote_pubkey: &Pubkey, + authorized_withdrawer_pubkey: &Pubkey, + commission: u8, +) -> Instruction { + let account_metas = vec![ + AccountMeta::new(*vote_pubkey, false), + AccountMeta::new_readonly(*authorized_withdrawer_pubkey, true), + ]; + + Instruction::new( + id(), + &VoteInstruction::UpdateCommission(commission), + account_metas, + ) +} + pub fn vote(vote_pubkey: &Pubkey, authorized_voter_pubkey: &Pubkey, vote: Vote) -> Instruction { let account_metas = vec![ AccountMeta::new(*vote_pubkey, false), @@ -271,6 +295,9 @@ pub fn process_instruction( next_keyed_account(keyed_accounts)?.unsigned_key(), &signers, ), + VoteInstruction::UpdateCommission(commission) => { + vote_state::update_commission(me, commission, &signers) + } VoteInstruction::Vote(vote) | VoteInstruction::VoteSwitch(vote, _) => { inc_new_counter_info!("vote-native", 1); vote_state::process_vote( @@ -380,6 +407,14 @@ mod tests { )), Err(InstructionError::InvalidAccountData), ); + assert_eq!( + process_instruction(&update_commission( + &Pubkey::default(), + &Pubkey::default(), + 0, + )), + Err(InstructionError::InvalidAccountData), + ); assert_eq!( process_instruction(&withdraw( diff --git a/programs/vote/src/vote_state/mod.rs b/programs/vote/src/vote_state/mod.rs index e9e960d2a7..26ed5e1e9d 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -597,6 +597,23 @@ pub fn update_validator_identity( vote_account.set_state(&VoteStateVersions::Current(Box::new(vote_state))) } +/// Update the vote account's commission +pub fn update_commission( + vote_account: &KeyedAccount, + commission: u8, + signers: &HashSet, +) -> Result<(), InstructionError> { + let mut vote_state: VoteState = + State::::state(vote_account)?.convert_to_current(); + + // current authorized withdrawer must say "yay" + verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?; + + vote_state.commission = commission; + + vote_account.set_state(&VoteStateVersions::Current(Box::new(vote_state))) +} + fn verify_authorized_signer( authorized: &Pubkey, signers: &HashSet, @@ -997,6 +1014,50 @@ mod tests { assert_eq!(vote_state.node_pubkey, node_pubkey); } + #[test] + fn test_vote_update_commission() { + let (vote_pubkey, _authorized_voter, authorized_withdrawer, vote_account) = + create_test_account_with_authorized(); + + let authorized_withdrawer_account = RefCell::new(Account::default()); + + let keyed_accounts = &[ + KeyedAccount::new(&vote_pubkey, true, &vote_account), + KeyedAccount::new( + &authorized_withdrawer, + false, + &authorized_withdrawer_account, + ), + ]; + let signers: HashSet = get_signers(keyed_accounts); + let res = update_commission(&keyed_accounts[0], 42, &signers); + assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); + + let keyed_accounts = &[ + KeyedAccount::new(&vote_pubkey, true, &vote_account), + KeyedAccount::new(&authorized_withdrawer, true, &authorized_withdrawer_account), + ]; + let signers: HashSet = get_signers(keyed_accounts); + let res = update_commission(&keyed_accounts[0], 42, &signers); + assert_eq!(res, Ok(())); + let vote_state: VoteState = StateMut::::state(&*vote_account.borrow()) + .unwrap() + .convert_to_current(); + assert_eq!(vote_state.commission, 42); + + let keyed_accounts = &[ + KeyedAccount::new(&vote_pubkey, true, &vote_account), + KeyedAccount::new(&authorized_withdrawer, true, &authorized_withdrawer_account), + ]; + let signers: HashSet = get_signers(keyed_accounts); + let res = update_commission(&keyed_accounts[0], u8::MAX, &signers); + assert_eq!(res, Ok(())); + let vote_state: VoteState = StateMut::::state(&*vote_account.borrow()) + .unwrap() + .convert_to_current(); + assert_eq!(vote_state.commission, u8::MAX); + } + #[test] fn test_vote_signature() { let (vote_pubkey, vote_account) = create_test_account();