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:
parent
964ff522be
commit
1e2b55c0d7
|
@ -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)"];
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue