From 1e2b55c0d7fa832826237ef3ab34b3bdd65aeb11 Mon Sep 17 00:00:00 2001 From: Rob Walker Date: Wed, 22 Jan 2020 16:53:42 -0800 Subject: [PATCH] Remove RedeemVoteCredits (#7916) * Move redeem_vote_credits into runtime * Move redeem_vote_credits into runtime * Remove RedeemVoteCredits * chugga for less indentation * resurrect NoCreditsToRedeem * fixup --- book/art/passive-staking-callflow.msc | 4 +- book/src/api-reference/cli.md | 87 +++---- .../cluster/stake-delegation-and-rewards.md | 21 -- book/src/running-validator/validator-stake.md | 9 +- cli/src/cli.rs | 14 +- cli/src/stake.rs | 61 +---- programs/stake/src/lib.rs | 5 - programs/stake/src/rewards_pools.rs | 56 ----- programs/stake/src/stake_instruction.rs | 97 +------- programs/stake/src/stake_state.rs | 216 +----------------- 10 files changed, 38 insertions(+), 532 deletions(-) delete mode 100644 programs/stake/src/rewards_pools.rs diff --git a/book/art/passive-staking-callflow.msc b/book/art/passive-staking-callflow.msc index 4eaaf8d7d..8383b0b6c 100644 --- a/book/art/passive-staking-callflow.msc +++ b/book/art/passive-staking-callflow.msc @@ -24,7 +24,7 @@ msc { ... ; Validator abox Validator [label="\nmax\nlockout\n"]; |||; - StakerX => Cluster [label="StakeState::RedeemCredits()"]; - StakerY => Cluster [label="StakeState::RedeemCredits()"] ; + Cluster box Cluster [label="credits redeemed (at epoch)"]; + } diff --git a/book/src/api-reference/cli.md b/book/src/api-reference/cli.md index 68d5a12f6..367ed12a0 100644 --- a/book/src/api-reference/cli.md +++ b/book/src/api-reference/cli.md @@ -231,11 +231,10 @@ SUBCOMMANDS: nonce-account Show the contents of a nonce account pay Send a payment ping Submit transactions sequentially - redeem-vote-credits Redeem credits in the stake account send-signature Send a signature to authorize a transfer send-timestamp Send a timestamp to unlock a transfer + show-stake-account Show the contents of a stake account slot Get current slot - stake-account Show the contents of a stake account stake-authorize-staker Authorize a new stake signing keypair for the given stake account stake-authorize-withdrawer Authorize a new withdraw signing keypair for the given stake account stake-history Show the stake history @@ -1182,34 +1181,6 @@ OPTIONS: -t, --timeout Wait up to timeout seconds for transaction confirmation [default: 15] ``` -#### solana-redeem-vote-credits -```text -solana-redeem-vote-credits -Redeem credits in the stake account - -USAGE: - solana redeem-vote-credits [FLAGS] [OPTIONS] - -FLAGS: - -h, --help Prints help information - --skip-seed-phrase-validation Skip validation of seed phrases. Use this if your phrase does not use the BIP39 - official English word list - -V, --version Prints version information - -v, --verbose Show extra information header - -OPTIONS: - --ask-seed-phrase Recover a keypair using a seed phrase and optional passphrase [possible - values: keypair] - -C, --config Configuration file to use [default: - ~/.config/solana/cli/config.yml] - -u, --url JSON RPC URL for the solana cluster - -k, --keypair /path/to/id.json - -ARGS: - Address of the stake account in which to redeem credits - The vote account to which the stake is currently delegated. -``` - #### solana-send-signature ```text solana-send-signature @@ -1267,6 +1238,34 @@ ARGS: The process id of the transfer to unlock ``` +#### solana-show-stake-account +```text +solana-show-stake-account +Show the contents of a stake account + +USAGE: + solana show-stake-account [FLAGS] [OPTIONS] + +FLAGS: + -h, --help Prints help information + --lamports Display balance in lamports instead of SOL + --skip-seed-phrase-validation Skip validation of seed phrases. Use this if your phrase does not use the BIP39 + official English word list + -V, --version Prints version information + -v, --verbose Show extra information header + +OPTIONS: + --ask-seed-phrase Recover a keypair using a seed phrase and optional passphrase [possible + values: keypair] + -C, --config Configuration file to use [default: + ~/.config/solana/cli/config.yml] + -u, --url JSON RPC URL for the solana cluster + -k, --keypair /path/to/id.json + +ARGS: + Address of the stake account to display +``` + #### solana-slot ```text solana-slot @@ -1292,34 +1291,6 @@ OPTIONS: -k, --keypair /path/to/id.json ``` -#### solana-stake-account -```text -solana-stake-account -Show the contents of a stake account - -USAGE: - solana stake-account [FLAGS] [OPTIONS] - -FLAGS: - -h, --help Prints help information - --lamports Display balance in lamports instead of SOL - --skip-seed-phrase-validation Skip validation of seed phrases. Use this if your phrase does not use the BIP39 - official English word list - -V, --version Prints version information - -v, --verbose Show extra information header - -OPTIONS: - --ask-seed-phrase Recover a keypair using a seed phrase and optional passphrase [possible - values: keypair] - -C, --config Configuration file to use [default: - ~/.config/solana/cli/config.yml] - -u, --url JSON RPC URL for the solana cluster - -k, --keypair /path/to/id.json - -ARGS: - Address of the stake account to display -``` - #### solana-stake-authorize-staker ```text solana-stake-authorize-staker diff --git a/book/src/cluster/stake-delegation-and-rewards.md b/book/src/cluster/stake-delegation-and-rewards.md index 0d88c7bcf..a7c1933b3 100644 --- a/book/src/cluster/stake-delegation-and-rewards.md +++ b/book/src/cluster/stake-delegation-and-rewards.md @@ -109,27 +109,6 @@ Updates the account with a new authorized staker or withdrawer, according to the `StakeState::authorized_staker` or `authorized_withdrawer` is set to to `Pubkey`. -### StakeInstruction::RedeemVoteCredits - -The staker or the owner of the Stake account sends a transaction with this instruction to claim rewards. - -The Vote account and the Stake account pair maintain a lifetime counter of total rewards generated and claimed. Rewards are paid according to a point value supplied by the Bank from inflation. A `point` is one credit \* one staked lamport, rewards paid are proportional to the number of lamports staked. - -* `account[0]` - RW - The StakeState::Stake instance that is redeeming rewards. -* `account[1]` - R - The VoteState instance, must be the same as `StakeState::voter_pubkey` -* `account[2]` - RW - The StakeState::RewardsPool instance that will fulfill the request \(picked at random\). -* `account[3]` - R - sysvar::rewards account from the Bank that carries point value. -* `account[4]` - R - sysvar::stake\_history account from the Bank that carries stake warmup/cooldown history - -Reward is paid out for the difference between `VoteState::credits` to `StakeState::Stake::credits_observed`, multiplied by `sysvar::rewards::Rewards::validator_point_value`. `StakeState::Stake::credits_observed` is updated to`VoteState::credits`. The commission is deposited into the Vote account token balance, and the reward is deposited to the Stake account token balance and the stake account's `stake` is increased by the same amount \(re-invested\). - -```text -let credits_to_claim = vote_state.credits - stake_state.credits_observed; -stake_state.credits_observed = vote_state.credits; -``` - -`credits_to_claim` is used to compute the reward and commission, and `StakeState::Stake::credits_observed` is updated to the latest `VoteState::credits` value. - ### StakeInstruction::Deactivate A staker may wish to withdraw from the network. To do so he must first deactivate his stake, and wait for cool down. diff --git a/book/src/running-validator/validator-stake.md b/book/src/running-validator/validator-stake.md index 67d37afc9..304122d3a 100644 --- a/book/src/running-validator/validator-stake.md +++ b/book/src/running-validator/validator-stake.md @@ -54,11 +54,7 @@ solana delegate-stake ~/validator-stake-keypair.json ~/some-other-validator-vote ``` Assuming the node is voting, now you're up and running and generating validator -rewards. You'll want to periodically redeem/claim your rewards: - -```bash -solana redeem-vote-credits ~/validator-stake-keypair.json ~/validator-vote-keypair.json -``` +rewards. Rewards are paid automatically on epoch boundaries. The rewards lamports earned are split between your stake account and the vote account according to the commission rate set in the vote account. Rewards can @@ -132,6 +128,3 @@ depending on active stake and the size of your stake. Note that a stake account may only be used once, so after deactivation, use the cli's `withdraw-stake` command to recover the previously staked lamports. - -Be sure and redeem your credits before withdrawing all your lamports. Once the -account is fully withdrawn, the account is destroyed. diff --git a/cli/src/cli.rs b/cli/src/cli.rs index e8ed6360b..d1c6a9cd2 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -269,7 +269,6 @@ pub enum CliCommand { nonce_account: Option, nonce_authority: Option, }, - RedeemVoteCredits(Pubkey, Pubkey), ShowStakeHistory { use_lamports_unit: bool, }, @@ -489,9 +488,8 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result { parse_stake_authorize(matches, StakeAuthorize::Withdrawer) } - ("redeem-vote-credits", Some(matches)) => parse_redeem_vote_credits(matches), - ("stake-account", Some(matches)) => parse_show_stake_account(matches), - ("stake-history", Some(matches)) => parse_show_stake_history(matches), + ("show-stake-account", Some(matches)) => parse_show_stake_account(matches), + ("show-stake-history", Some(matches)) => parse_show_stake_history(matches), // Storage Commands ("create-archiver-storage-account", Some(matches)) => { parse_storage_create_archiver_account(matches) @@ -1420,14 +1418,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { *nonce_account, nonce_authority.as_ref(), ), - CliCommand::RedeemVoteCredits(stake_account_pubkey, vote_account_pubkey) => { - process_redeem_vote_credits( - &rpc_client, - config, - &stake_account_pubkey, - &vote_account_pubkey, - ) - } CliCommand::ShowStakeAccount { pubkey: stake_account_pubkey, use_lamports_unit, diff --git a/cli/src/stake.rs b/cli/src/stake.rs index 35d267aae..33a8f52c7 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -373,29 +373,7 @@ impl StakeSubCommands for App<'_, '_> { .arg(withdraw_authority_arg()) ) .subcommand( - SubCommand::with_name("redeem-vote-credits") - .about("Redeem credits in the stake account") - .arg( - Arg::with_name("stake_account_pubkey") - .index(1) - .value_name("STAKE ACCOUNT") - .takes_value(true) - .required(true) - .validator(is_pubkey_or_keypair) - .help("Address of the stake account in which to redeem credits") - ) - .arg( - Arg::with_name("vote_account_pubkey") - .index(2) - .value_name("VOTE ACCOUNT") - .takes_value(true) - .required(true) - .validator(is_pubkey_or_keypair) - .help("The vote account to which the stake is currently delegated.") - ) - ) - .subcommand( - SubCommand::with_name("stake-account") + SubCommand::with_name("show-stake-account") .about("Show the contents of a stake account") .alias("show-stake-account") .arg( @@ -548,16 +526,6 @@ pub fn parse_stake_authorize( }) } -pub fn parse_redeem_vote_credits(matches: &ArgMatches<'_>) -> Result { - let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); - let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); - - Ok(CliCommandInfo { - command: CliCommand::RedeemVoteCredits(stake_account_pubkey, vote_account_pubkey), - require_keypair: true, - }) -} - pub fn parse_stake_deactivate_stake(matches: &ArgMatches<'_>) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); let sign_only = matches.is_present("sign_only"); @@ -901,33 +869,6 @@ pub fn process_withdraw_stake( log_instruction_custom_error::(result) } -pub fn process_redeem_vote_credits( - rpc_client: &RpcClient, - config: &CliConfig, - stake_account_pubkey: &Pubkey, - vote_account_pubkey: &Pubkey, -) -> ProcessResult { - let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; - let ixs = vec![stake_instruction::redeem_vote_credits( - stake_account_pubkey, - vote_account_pubkey, - )]; - let mut tx = Transaction::new_signed_with_payer( - ixs, - Some(&config.keypair.pubkey()), - &[&config.keypair], - recent_blockhash, - ); - check_account_for_fee( - rpc_client, - &config.keypair.pubkey(), - &fee_calculator, - &tx.message, - )?; - let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]); - log_instruction_custom_error::(result) -} - pub fn print_stake_state(stake_lamports: u64, stake_state: &StakeState, use_lamports_unit: bool) { fn show_authorized(authorized: &Authorized) { println!("authorized staker: {}", authorized.staker); diff --git a/programs/stake/src/lib.rs b/programs/stake/src/lib.rs index 529a8d820..7112680d9 100644 --- a/programs/stake/src/lib.rs +++ b/programs/stake/src/lib.rs @@ -1,7 +1,6 @@ use solana_sdk::genesis_config::GenesisConfig; pub mod config; -pub mod rewards_pools; pub mod stake_instruction; pub mod stake_state; @@ -12,9 +11,5 @@ solana_sdk::declare_program!( ); pub fn add_genesis_accounts(genesis_config: &mut GenesisConfig) -> u64 { - for (pubkey, account) in rewards_pools::create_genesis_accounts() { - genesis_config.add_rewards_pool(pubkey, account); - } - config::add_genesis_account(genesis_config) } diff --git a/programs/stake/src/rewards_pools.rs b/programs/stake/src/rewards_pools.rs deleted file mode 100644 index e00769704..000000000 --- a/programs/stake/src/rewards_pools.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! rewards_pools -//! * initialize genesis with rewards pools -//! * keep track of rewards -//! * own mining pools -use crate::stake_state::StakeState; -use rand::{thread_rng, Rng}; -use solana_sdk::{ - account::Account, - hash::{hash, Hash}, - pubkey::Pubkey, -}; - -// base rewards pool ID -solana_sdk::declare_id!("StakeRewards1111111111111111111111111111111"); - -// to cut down on collisions for redemptions, we make multiple accounts -pub const NUM_REWARDS_POOLS: usize = 256; - -pub fn random_id() -> Pubkey { - let mut id = Hash::new(id().as_ref()); - - for _i in 0..thread_rng().gen_range(0, NUM_REWARDS_POOLS) { - id = hash(id.as_ref()); - } - - Pubkey::new(id.as_ref()) -} - -pub fn create_genesis_accounts() -> Vec<(Pubkey, Account)> { - let mut accounts = Vec::with_capacity(NUM_REWARDS_POOLS); - let mut pubkey = id(); - - for _i in 0..NUM_REWARDS_POOLS { - accounts.push(( - pubkey, - Account::new_data(std::u64::MAX, &StakeState::RewardsPool, &crate::id()).unwrap(), - )); - pubkey = Pubkey::new(hash(pubkey.as_ref()).as_ref()); - } - accounts -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test() { - let accounts = create_genesis_accounts(); - - for _i in 0..NUM_REWARDS_POOLS { - let id = random_id(); - assert!(accounts.iter().position(|x| x.0 == id).is_some()); - } - } -} diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index 79eb783d7..35eedb298 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -11,9 +11,7 @@ use solana_sdk::{ instruction_processor_utils::{limited_deserialize, next_keyed_account, DecodeError}, pubkey::Pubkey, system_instruction, - sysvar::{ - self, clock::Clock, rent::Rent, rewards::Rewards, stake_history::StakeHistory, Sysvar, - }, + sysvar::{self, clock::Clock, rent::Rent, stake_history::StakeHistory, Sysvar}, }; use thiserror::Error; @@ -80,17 +78,6 @@ pub enum StakeInstruction { /// DelegateStake, - /// Redeem credits in the stake account - /// - /// Expects 5 Accounts: - /// 0 - StakeAccount to be updated with rewards - /// 1 - VoteAccount to which the Stake is delegated, - /// 2 - RewardsPool Stake Account from which to redeem credits - /// 3 - Rewards sysvar Account that carries points values - /// 4 - StakeHistory sysvar that carries stake warmup/cooldown history - /// - RedeemVoteCredits, - /// Split u64 tokens and stake off a stake account into another stake /// account. Requires Authorized::staker signature and the /// signature of the split-off stake address. @@ -302,17 +289,6 @@ pub fn authorize( ) } -pub fn redeem_vote_credits(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*stake_pubkey, false), - AccountMeta::new(*vote_pubkey, false), - AccountMeta::new(crate::rewards_pools::random_id(), false), - AccountMeta::new_readonly(sysvar::rewards::id(), false), - AccountMeta::new_readonly(sysvar::stake_history::id(), false), - ]; - Instruction::new(id(), &StakeInstruction::RedeemVoteCredits, account_metas) -} - pub fn delegate_stake( stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey, @@ -410,17 +386,6 @@ pub fn process_instruction( &signers, ) } - StakeInstruction::RedeemVoteCredits => { - let vote = &mut next_keyed_account(keyed_accounts)?; - let rewards_pool = &mut next_keyed_account(keyed_accounts)?; - - me.redeem_vote_credits( - vote, - rewards_pool, - &Rewards::from_keyed_account(next_keyed_account(keyed_accounts)?)?, - &StakeHistory::from_keyed_account(next_keyed_account(keyed_accounts)?)?, - ) - } StakeInstruction::Split(lamports) => { let split_stake = &mut next_keyed_account(keyed_accounts)?; me.split(lamports, split_stake, &signers) @@ -496,10 +461,6 @@ mod tests { )), Err(InstructionError::InvalidAccountData), ); - assert_eq!( - process_instruction(&redeem_vote_credits(&Pubkey::default(), &Pubkey::default())), - Err(InstructionError::InvalidAccountData), - ); assert_eq!( process_instruction(&authorize( &Pubkey::default(), @@ -658,35 +619,6 @@ mod tests { Err(InstructionError::NotEnoughAccountKeys), ); - // catches the number of args check - assert_eq!( - super::process_instruction( - &Pubkey::default(), - &mut [ - KeyedAccount::new(&Pubkey::default(), false, &mut create_default_account()), - KeyedAccount::new(&Pubkey::default(), false, &mut create_default_account()), - ], - &serialize(&StakeInstruction::RedeemVoteCredits).unwrap(), - ), - Err(InstructionError::NotEnoughAccountKeys), - ); - - // catches the type of args check - assert_eq!( - super::process_instruction( - &Pubkey::default(), - &mut [ - KeyedAccount::new(&Pubkey::default(), false, &mut create_default_account()), - KeyedAccount::new(&Pubkey::default(), false, &mut create_default_account()), - KeyedAccount::new(&Pubkey::default(), false, &mut create_default_account()), - KeyedAccount::new(&Pubkey::default(), false, &mut create_default_account()), - KeyedAccount::new(&Pubkey::default(), false, &mut create_default_account()), - ], - &serialize(&StakeInstruction::RedeemVoteCredits).unwrap(), - ), - Err(InstructionError::InvalidArgument), - ); - // gets the check non-deserialize-able account in delegate_stake assert_eq!( super::process_instruction( @@ -710,33 +642,6 @@ mod tests { Err(InstructionError::InvalidAccountData), ); - // gets the deserialization checks in redeem_vote_credits - assert_eq!( - super::process_instruction( - &Pubkey::default(), - &mut [ - KeyedAccount::new(&Pubkey::default(), false, &mut create_default_account()), - KeyedAccount::new(&Pubkey::default(), false, &mut create_default_account()), - KeyedAccount::new(&Pubkey::default(), false, &mut create_default_account()), - KeyedAccount::new( - &sysvar::rewards::id(), - false, - &mut RefCell::new(sysvar::rewards::create_account(1, 0.0, 0.0)) - ), - KeyedAccount::new( - &sysvar::stake_history::id(), - false, - &mut RefCell::new(sysvar::stake_history::create_account( - 1, - &StakeHistory::default() - )) - ), - ], - &serialize(&StakeInstruction::RedeemVoteCredits).unwrap(), - ), - Err(InstructionError::InvalidAccountData), - ); - // Tests 3rd keyed account is of correct type (Clock instead of rewards) in withdraw assert_eq!( super::process_instruction( diff --git a/programs/stake/src/stake_state.rs b/programs/stake/src/stake_state.rs index bfeb1d9c9..0d9f97d72 100644 --- a/programs/stake/src/stake_state.rs +++ b/programs/stake/src/stake_state.rs @@ -408,8 +408,8 @@ impl Stake { // the staker registered sometime during the epoch, partial credit credits - credits_observed } else { - // the staker has already observed/redeemed this epoch, or activated - // after this epoch + // the staker has already observed or been redeemed this epoch + // or was activated after this epoch 0 }; @@ -537,13 +537,6 @@ pub trait StakeAccount { clock: &sysvar::clock::Clock, signers: &HashSet, ) -> Result<(), InstructionError>; - fn redeem_vote_credits( - &mut self, - vote_account: &mut KeyedAccount, - rewards_account: &mut KeyedAccount, - rewards: &sysvar::rewards::Rewards, - stake_history: &sysvar::stake_history::StakeHistory, - ) -> Result<(), InstructionError>; fn split( &mut self, lamports: u64, @@ -647,51 +640,6 @@ impl<'a> StakeAccount for KeyedAccount<'a> { Err(InstructionError::InvalidAccountData) } } - fn redeem_vote_credits( - &mut self, - vote_account: &mut KeyedAccount, - rewards_account: &mut KeyedAccount, - rewards: &sysvar::rewards::Rewards, - stake_history: &sysvar::stake_history::StakeHistory, - ) -> Result<(), InstructionError> { - if let (StakeState::Stake(meta, mut stake), StakeState::RewardsPool) = - (self.state()?, rewards_account.state()?) - { - let vote_state: VoteState = vote_account.state()?; - - // the only valid use of current voter_pubkey, redelegation breaks - // rewards redemption for previous voter_pubkey - if stake.delegation.voter_pubkey != *vote_account.unsigned_key() { - return Err(InstructionError::InvalidArgument); - } - - if let Some((voters_reward, stakers_reward, credits_observed)) = stake - .calculate_rewards( - rewards.validator_point_value, - &vote_state, - Some(stake_history), - ) - { - if rewards_account.lamports()? < (stakers_reward + voters_reward) { - return Err(InstructionError::UnbalancedInstruction); - } - rewards_account.try_account_ref_mut()?.lamports -= stakers_reward + voters_reward; - - self.try_account_ref_mut()?.lamports += stakers_reward; - vote_account.try_account_ref_mut()?.lamports += voters_reward; - - stake.credits_observed = credits_observed; - stake.delegation.stake += stakers_reward; - - self.set_state(&StakeState::Stake(meta, stake)) - } else { - // not worth collecting - Err(StakeError::NoCreditsToRedeem.into()) - } - } else { - Err(InstructionError::InvalidAccountData) - } - } fn split( &mut self, @@ -2085,166 +2033,6 @@ mod tests { ); } - #[test] - fn test_stake_redeem_vote_credits() { - let clock = sysvar::clock::Clock::default(); - let mut rewards = sysvar::rewards::Rewards::default(); - rewards.validator_point_value = 100.0; - - let rewards_pool_pubkey = Pubkey::new_rand(); - let mut rewards_pool_account = Account::new_ref_data( - std::u64::MAX, - &StakeState::RewardsPool, - &crate::rewards_pools::id(), - ) - .unwrap(); - let mut rewards_pool_keyed_account = - KeyedAccount::new(&rewards_pool_pubkey, false, &mut rewards_pool_account); - - let stake_pubkey = Pubkey::default(); - let stake_lamports = 100; - let mut stake_account = Account::new_ref_data_with_space( - stake_lamports, - &StakeState::Initialized(Meta::auto(&stake_pubkey)), - std::mem::size_of::(), - &id(), - ) - .expect("stake_account"); - - let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); - - let vote_pubkey = Pubkey::new_rand(); - let mut vote_account = RefCell::new(vote_state::create_account( - &vote_pubkey, - &Pubkey::new_rand(), - 0, - 100, - )); - let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); - - // not delegated yet, deserialization fails - assert_eq!( - stake_keyed_account.redeem_vote_credits( - &mut vote_keyed_account, - &mut rewards_pool_keyed_account, - &rewards, - &StakeHistory::default(), - ), - Err(InstructionError::InvalidAccountData) - ); - let signers = vec![stake_pubkey].into_iter().collect(); - // delegate the stake - assert!(stake_keyed_account - .delegate_stake(&vote_keyed_account, &clock, &Config::default(), &signers) - .is_ok()); - - let stake_history = create_stake_history_from_delegations( - Some(100), - 0..10, - &[ - StakeState::stake_from(&stake_keyed_account.account.borrow()) - .unwrap() - .delegation, - ], - ); - - // no credits to claim - assert_eq!( - stake_keyed_account.redeem_vote_credits( - &mut vote_keyed_account, - &mut rewards_pool_keyed_account, - &rewards, - &stake_history, - ), - Err(StakeError::NoCreditsToRedeem.into()) - ); - - // in this call, we've swapped rewards and vote, deserialization of rewards_pool fails - assert_eq!( - stake_keyed_account.redeem_vote_credits( - &mut rewards_pool_keyed_account, - &mut vote_keyed_account, - &rewards, - &StakeHistory::default(), - ), - Err(InstructionError::InvalidAccountData) - ); - - let mut vote_account = RefCell::new(vote_state::create_account( - &vote_pubkey, - &Pubkey::new_rand(), - 0, - 100, - )); - - let mut vote_state = VoteState::from(&vote_account.borrow()).unwrap(); - // split credits 3:1 between staker and voter - vote_state.commission = 25; - // put in some credits in epoch 0 for which we should have a non-zero stake - for _i in 0..100 { - vote_state.increment_credits(1); - } - vote_state.increment_credits(2); - - vote_state.to(&mut vote_account.borrow_mut()).unwrap(); - let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account); - - // some credits to claim, but rewards pool empty (shouldn't ever happen) - rewards_pool_keyed_account.account.borrow_mut().lamports = 1; - assert_eq!( - stake_keyed_account.redeem_vote_credits( - &mut vote_keyed_account, - &mut rewards_pool_keyed_account, - &rewards, - &StakeHistory::default(), - ), - Err(InstructionError::UnbalancedInstruction) - ); - rewards_pool_keyed_account.account.borrow_mut().lamports = std::u64::MAX; - - // finally! some credits to claim - let stake_account_balance = stake_keyed_account.account.borrow().lamports; - let vote_account_balance = vote_keyed_account.account.borrow().lamports; - assert_eq!( - stake_keyed_account.redeem_vote_credits( - &mut vote_keyed_account, - &mut rewards_pool_keyed_account, - &rewards, - &stake_history, - ), - Ok(()) - ); - let staker_rewards = stake_keyed_account.account.borrow().lamports - stake_account_balance; - let voter_commission = vote_keyed_account.account.borrow().lamports - vote_account_balance; - assert!(voter_commission > 0); - assert!(staker_rewards > 0); - assert!( - staker_rewards / 3 >= voter_commission, - "rewards should be split ~3:1" - ); - // verify rewards are added to stake - let stake = StakeState::stake_from(&stake_keyed_account.account.borrow()).unwrap(); - assert_eq!( - stake.delegation.stake, - stake_keyed_account.account.borrow().lamports - ); - - let wrong_vote_pubkey = Pubkey::new_rand(); - let mut wrong_vote_keyed_account = - KeyedAccount::new(&wrong_vote_pubkey, false, &mut vote_account); - - // wrong voter_pubkey... - assert_eq!( - stake_keyed_account.redeem_vote_credits( - &mut wrong_vote_keyed_account, - &mut rewards_pool_keyed_account, - &rewards, - &stake_history, - ), - Err(InstructionError::InvalidArgument) - ); - } - #[test] fn test_authorize_uninit() { let stake_pubkey = Pubkey::new_rand();