Remove RedeemVoteCredits (#7916)

* Move redeem_vote_credits into runtime

* Move redeem_vote_credits into runtime

* Remove RedeemVoteCredits

* chugga for less indentation

* resurrect NoCreditsToRedeem

* fixup
This commit is contained in:
Rob Walker 2020-01-22 16:53:42 -08:00 committed by GitHub
parent 964ff522be
commit 1e2b55c0d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 38 additions and 532 deletions

View File

@ -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)"];
}

View File

@ -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 <SECONDS> 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] <STAKE ACCOUNT> <VOTE ACCOUNT>
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 <KEYPAIR NAME> Recover a keypair using a seed phrase and optional passphrase [possible
values: keypair]
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKE ACCOUNT> Address of the stake account in which to redeem credits
<VOTE ACCOUNT> The vote account to which the stake is currently delegated.
```
#### solana-send-signature
```text
solana-send-signature
@ -1267,6 +1238,34 @@ ARGS:
<PROCESS ID> 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] <STAKE ACCOUNT>
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 <KEYPAIR NAME> Recover a keypair using a seed phrase and optional passphrase [possible
values: keypair]
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKE ACCOUNT> Address of the stake account to display
```
#### solana-slot
```text
solana-slot
@ -1292,34 +1291,6 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
```
#### solana-stake-account
```text
solana-stake-account
Show the contents of a stake account
USAGE:
solana stake-account [FLAGS] [OPTIONS] <STAKE ACCOUNT>
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 <KEYPAIR NAME> Recover a keypair using a seed phrase and optional passphrase [possible
values: keypair]
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKE ACCOUNT> Address of the stake account to display
```
#### solana-stake-authorize-staker
```text
solana-stake-authorize-staker

View File

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

View File

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

View File

@ -269,7 +269,6 @@ pub enum CliCommand {
nonce_account: Option<Pubkey>,
nonce_authority: Option<SigningAuthority>,
},
RedeemVoteCredits(Pubkey, Pubkey),
ShowStakeHistory {
use_lamports_unit: bool,
},
@ -489,9 +488,8 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, Box<dyn
("stake-authorize-withdrawer", Some(matches)) => {
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,

View File

@ -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<CliCommandInfo, CliError> {
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<CliCommandInfo, CliError> {
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::<StakeError>(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::<StakeError>(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);

View File

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

View File

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

View File

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

View File

@ -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<Pubkey>,
) -> 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::<StakeState>(),
&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();