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:
@ -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();
|
||||
|
Reference in New Issue
Block a user