This reverts commit 34344982a9
.
This commit is contained in:
26
programs/stake_api/Cargo.toml
Normal file
26
programs/stake_api/Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "solana-stake-api"
|
||||
version = "0.14.0"
|
||||
description = "Solana Stake program API"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.1.3"
|
||||
log = "0.4.2"
|
||||
serde = "1.0.90"
|
||||
serde_derive = "1.0.90"
|
||||
solana-logger = { path = "../../logger", version = "0.14.0" }
|
||||
solana-metrics = { path = "../../metrics", version = "0.14.0" }
|
||||
solana-sdk = { path = "../../sdk", version = "0.14.0" }
|
||||
solana-vote-api = { path = "../vote_api", version = "0.14.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-runtime = { path = "../../runtime", version = "0.14.0" }
|
||||
|
||||
[lib]
|
||||
name = "solana_stake_api"
|
||||
crate-type = ["lib"]
|
17
programs/stake_api/src/lib.rs
Normal file
17
programs/stake_api/src/lib.rs
Normal file
@ -0,0 +1,17 @@
|
||||
pub mod stake_instruction;
|
||||
pub mod stake_state;
|
||||
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
const STAKE_PROGRAM_ID: [u8; 32] = [
|
||||
135, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0,
|
||||
];
|
||||
|
||||
pub fn check_id(program_id: &Pubkey) -> bool {
|
||||
program_id.as_ref() == STAKE_PROGRAM_ID
|
||||
}
|
||||
|
||||
pub fn id() -> Pubkey {
|
||||
Pubkey::new(&STAKE_PROGRAM_ID)
|
||||
}
|
156
programs/stake_api/src/stake_instruction.rs
Normal file
156
programs/stake_api/src/stake_instruction.rs
Normal file
@ -0,0 +1,156 @@
|
||||
use crate::id;
|
||||
use crate::stake_state::{StakeAccount, StakeState};
|
||||
use bincode::deserialize;
|
||||
use log::*;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use solana_sdk::account::KeyedAccount;
|
||||
use solana_sdk::instruction::{AccountMeta, Instruction, InstructionError};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::system_instruction;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum StakeInstruction {
|
||||
/// `Delegate` or `Assign` a stake account to a particular node
|
||||
/// expects 2 KeyedAccounts:
|
||||
/// StakeAccount to be updated
|
||||
/// VoteAccount to which this Stake will be delegated
|
||||
DelegateStake,
|
||||
|
||||
/// Redeem credits in the stake account
|
||||
/// expects 3 KeyedAccounts: the StakeAccount to be updated
|
||||
/// and the VoteAccount to which this Stake will be delegated
|
||||
RedeemVoteCredits,
|
||||
}
|
||||
|
||||
pub fn create_account(from_id: &Pubkey, staker_id: &Pubkey, lamports: u64) -> Vec<Instruction> {
|
||||
vec![system_instruction::create_account(
|
||||
from_id,
|
||||
staker_id,
|
||||
lamports,
|
||||
std::mem::size_of::<StakeState>() as u64,
|
||||
&id(),
|
||||
)]
|
||||
}
|
||||
|
||||
pub fn redeem_vote_credits(
|
||||
from_id: &Pubkey,
|
||||
mining_pool_id: &Pubkey,
|
||||
stake_id: &Pubkey,
|
||||
vote_id: &Pubkey,
|
||||
) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*from_id, true),
|
||||
AccountMeta::new(*mining_pool_id, false),
|
||||
AccountMeta::new(*stake_id, false),
|
||||
AccountMeta::new(*vote_id, false),
|
||||
];
|
||||
Instruction::new(id(), &StakeInstruction::RedeemVoteCredits, account_metas)
|
||||
}
|
||||
|
||||
pub fn delegate_stake(from_id: &Pubkey, stake_id: &Pubkey, vote_id: &Pubkey) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*from_id, true),
|
||||
AccountMeta::new(*stake_id, true),
|
||||
AccountMeta::new(*vote_id, false),
|
||||
];
|
||||
Instruction::new(id(), &StakeInstruction::DelegateStake, account_metas)
|
||||
}
|
||||
|
||||
pub fn process_instruction(
|
||||
_program_id: &Pubkey,
|
||||
keyed_accounts: &mut [KeyedAccount],
|
||||
data: &[u8],
|
||||
_tick_height: u64,
|
||||
) -> Result<(), InstructionError> {
|
||||
solana_logger::setup();
|
||||
|
||||
trace!("process_instruction: {:?}", data);
|
||||
trace!("keyed_accounts: {:?}", keyed_accounts);
|
||||
|
||||
if keyed_accounts.len() < 3 {
|
||||
Err(InstructionError::InvalidInstructionData)?;
|
||||
}
|
||||
|
||||
// 0th index is the guy who paid for the transaction
|
||||
let (me, rest) = &mut keyed_accounts.split_at_mut(2);
|
||||
|
||||
let me = &mut me[1];
|
||||
|
||||
// TODO: data-driven unpack and dispatch of KeyedAccounts
|
||||
match deserialize(data).map_err(|_| InstructionError::InvalidInstructionData)? {
|
||||
StakeInstruction::DelegateStake => {
|
||||
if rest.len() != 1 {
|
||||
Err(InstructionError::InvalidInstructionData)?;
|
||||
}
|
||||
let vote = &rest[0];
|
||||
me.delegate_stake(vote)
|
||||
}
|
||||
StakeInstruction::RedeemVoteCredits => {
|
||||
if rest.len() != 2 {
|
||||
Err(InstructionError::InvalidInstructionData)?;
|
||||
}
|
||||
let (stake, vote) = rest.split_at_mut(1);
|
||||
let stake = &mut stake[0];
|
||||
let vote = &mut vote[0];
|
||||
|
||||
me.redeem_vote_credits(stake, vote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bincode::serialize;
|
||||
use solana_sdk::account::Account;
|
||||
|
||||
#[test]
|
||||
fn test_stake_process_instruction_decode_bail() {
|
||||
// these will not call stake_state, have bogus contents
|
||||
|
||||
// gets the first check
|
||||
assert_eq!(
|
||||
process_instruction(
|
||||
&Pubkey::default(),
|
||||
&mut [KeyedAccount::new(
|
||||
&Pubkey::default(),
|
||||
false,
|
||||
&mut Account::default(),
|
||||
)],
|
||||
&serialize(&StakeInstruction::DelegateStake).unwrap(),
|
||||
0,
|
||||
),
|
||||
Err(InstructionError::InvalidInstructionData),
|
||||
);
|
||||
|
||||
// gets the check in delegate_stake
|
||||
assert_eq!(
|
||||
process_instruction(
|
||||
&Pubkey::default(),
|
||||
&mut [
|
||||
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
|
||||
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
|
||||
],
|
||||
&serialize(&StakeInstruction::DelegateStake).unwrap(),
|
||||
0,
|
||||
),
|
||||
Err(InstructionError::InvalidInstructionData),
|
||||
);
|
||||
|
||||
// gets the check in redeem_vote_credits
|
||||
assert_eq!(
|
||||
process_instruction(
|
||||
&Pubkey::default(),
|
||||
&mut [
|
||||
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
|
||||
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
|
||||
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
|
||||
],
|
||||
&serialize(&StakeInstruction::RedeemVoteCredits).unwrap(),
|
||||
0,
|
||||
),
|
||||
Err(InstructionError::InvalidInstructionData),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
380
programs/stake_api/src/stake_state.rs
Normal file
380
programs/stake_api/src/stake_state.rs
Normal file
@ -0,0 +1,380 @@
|
||||
//! Stake state
|
||||
//! * delegate stakes to vote accounts
|
||||
//! * keep track of rewards
|
||||
//! * own mining pools
|
||||
|
||||
//use crate::{check_id, id};
|
||||
//use log::*;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use solana_sdk::account::KeyedAccount;
|
||||
use solana_sdk::instruction::InstructionError;
|
||||
use solana_sdk::instruction_processor_utils::State;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_vote_api::vote_state::VoteState;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||
pub enum StakeState {
|
||||
Delegate {
|
||||
voter_id: Pubkey,
|
||||
credits_observed: u64,
|
||||
},
|
||||
MiningPool,
|
||||
}
|
||||
|
||||
impl Default for StakeState {
|
||||
fn default() -> Self {
|
||||
StakeState::Delegate {
|
||||
voter_id: Pubkey::default(),
|
||||
credits_observed: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: trusted values of network parameters come from where?
|
||||
const TICKS_PER_SECOND: f64 = 10f64;
|
||||
const TICKS_PER_SLOT: f64 = 8f64;
|
||||
|
||||
// credits/yr or slots/yr is seconds/year * ticks/second * slots/tick
|
||||
const CREDITS_PER_YEAR: f64 = (365f64 * 24f64 * 3600f64) * TICKS_PER_SECOND / TICKS_PER_SLOT;
|
||||
|
||||
// TODO: 20% is a niiice rate... TODO: make this a member of MiningPool?
|
||||
const STAKE_REWARD_TARGET_RATE: f64 = 0.20;
|
||||
|
||||
#[cfg(test)]
|
||||
const STAKE_GETS_PAID_EVERY_VOTE: u64 = 200_000_000; // if numbers above move, fix this
|
||||
|
||||
impl StakeState {
|
||||
pub fn calculate_rewards(
|
||||
credits_observed: u64,
|
||||
stake: u64,
|
||||
vote_state: &VoteState,
|
||||
) -> Option<(u64, u64)> {
|
||||
if credits_observed >= vote_state.credits() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let total_rewards = stake as f64
|
||||
* STAKE_REWARD_TARGET_RATE
|
||||
* (vote_state.credits() - credits_observed) as f64
|
||||
/ CREDITS_PER_YEAR;
|
||||
|
||||
// don't bother trying to collect fractional lamports
|
||||
if total_rewards < 1f64 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (voter_rewards, staker_rewards, is_split) = vote_state.commission_split(total_rewards);
|
||||
|
||||
if (voter_rewards < 1f64 || staker_rewards < 1f64) && is_split {
|
||||
// don't bother trying to collect fractional lamports
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((voter_rewards as u64, staker_rewards as u64))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait StakeAccount {
|
||||
fn delegate_stake(&mut self, vote_account: &KeyedAccount) -> Result<(), InstructionError>;
|
||||
fn redeem_vote_credits(
|
||||
&mut self,
|
||||
stake_account: &mut KeyedAccount,
|
||||
vote_account: &mut KeyedAccount,
|
||||
) -> Result<(), InstructionError>;
|
||||
}
|
||||
|
||||
impl<'a> StakeAccount for KeyedAccount<'a> {
|
||||
fn delegate_stake(&mut self, vote_account: &KeyedAccount) -> Result<(), InstructionError> {
|
||||
if self.signer_key().is_none() {
|
||||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
|
||||
if let StakeState::Delegate { .. } = self.state()? {
|
||||
let vote_state: VoteState = vote_account.state()?;
|
||||
self.set_state(&StakeState::Delegate {
|
||||
voter_id: *vote_account.unsigned_key(),
|
||||
credits_observed: vote_state.credits(),
|
||||
})
|
||||
} else {
|
||||
Err(InstructionError::InvalidAccountData)
|
||||
}
|
||||
}
|
||||
|
||||
fn redeem_vote_credits(
|
||||
&mut self,
|
||||
stake_account: &mut KeyedAccount,
|
||||
vote_account: &mut KeyedAccount,
|
||||
) -> Result<(), InstructionError> {
|
||||
if let (
|
||||
StakeState::MiningPool,
|
||||
StakeState::Delegate {
|
||||
voter_id,
|
||||
credits_observed,
|
||||
},
|
||||
) = (self.state()?, stake_account.state()?)
|
||||
{
|
||||
let vote_state: VoteState = vote_account.state()?;
|
||||
|
||||
if voter_id != *vote_account.unsigned_key() {
|
||||
return Err(InstructionError::InvalidArgument);
|
||||
}
|
||||
|
||||
if credits_observed > vote_state.credits() {
|
||||
return Err(InstructionError::InvalidAccountData);
|
||||
}
|
||||
|
||||
if let Some((stakers_reward, voters_reward)) = StakeState::calculate_rewards(
|
||||
credits_observed,
|
||||
stake_account.account.lamports,
|
||||
&vote_state,
|
||||
) {
|
||||
if self.account.lamports < (stakers_reward + voters_reward) {
|
||||
return Err(InstructionError::UnbalancedInstruction);
|
||||
}
|
||||
self.account.lamports -= stakers_reward + voters_reward;
|
||||
stake_account.account.lamports += stakers_reward;
|
||||
vote_account.account.lamports += voters_reward;
|
||||
|
||||
stake_account.set_state(&StakeState::Delegate {
|
||||
voter_id,
|
||||
credits_observed: vote_state.credits(),
|
||||
})
|
||||
} else {
|
||||
// not worth collecting
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(InstructionError::InvalidAccountData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::id;
|
||||
use solana_sdk::account::Account;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_vote_api::vote_state::{self, Vote};
|
||||
|
||||
#[test]
|
||||
fn test_stake_delegate_stake() {
|
||||
let vote_keypair = Keypair::new();
|
||||
let mut vote_state = VoteState::default();
|
||||
for i in 0..1000 {
|
||||
vote_state.process_vote(&Vote::new(i));
|
||||
}
|
||||
|
||||
let vote_pubkey = vote_keypair.pubkey();
|
||||
let mut vote_account =
|
||||
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
|
||||
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
||||
vote_keyed_account.set_state(&vote_state).unwrap();
|
||||
|
||||
let stake_pubkey = Pubkey::default();
|
||||
let mut stake_account = Account::new(0, std::mem::size_of::<StakeState>(), &id());
|
||||
|
||||
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account);
|
||||
|
||||
assert_eq!(
|
||||
stake_keyed_account.delegate_stake(&vote_keyed_account),
|
||||
Err(InstructionError::MissingRequiredSignature)
|
||||
);
|
||||
|
||||
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
|
||||
assert!(stake_keyed_account
|
||||
.delegate_stake(&vote_keyed_account)
|
||||
.is_ok());
|
||||
|
||||
let stake_state: StakeState = stake_keyed_account.state().unwrap();
|
||||
assert_eq!(
|
||||
stake_state,
|
||||
StakeState::Delegate {
|
||||
voter_id: vote_keypair.pubkey(),
|
||||
credits_observed: vote_state.credits()
|
||||
}
|
||||
);
|
||||
let stake_state = StakeState::MiningPool;
|
||||
stake_keyed_account.set_state(&stake_state).unwrap();
|
||||
assert!(stake_keyed_account
|
||||
.delegate_stake(&vote_keyed_account)
|
||||
.is_err());
|
||||
}
|
||||
#[test]
|
||||
fn test_stake_state_calculate_rewards() {
|
||||
let mut vote_state = VoteState::default();
|
||||
let mut vote_i = 0;
|
||||
|
||||
// put a credit in the vote_state
|
||||
while vote_state.credits() == 0 {
|
||||
vote_state.process_vote(&Vote::new(vote_i));
|
||||
vote_i += 1;
|
||||
}
|
||||
// this guy can't collect now, not enough stake to get paid on 1 credit
|
||||
assert_eq!(None, StakeState::calculate_rewards(0, 100, &vote_state));
|
||||
// this guy can
|
||||
assert_eq!(
|
||||
Some((0, 1)),
|
||||
StakeState::calculate_rewards(0, STAKE_GETS_PAID_EVERY_VOTE, &vote_state)
|
||||
);
|
||||
// but, there's not enough to split
|
||||
vote_state.commission = std::u32::MAX / 2;
|
||||
assert_eq!(
|
||||
None,
|
||||
StakeState::calculate_rewards(0, STAKE_GETS_PAID_EVERY_VOTE, &vote_state)
|
||||
);
|
||||
|
||||
// put more credit in the vote_state
|
||||
while vote_state.credits() < 10 {
|
||||
vote_state.process_vote(&Vote::new(vote_i));
|
||||
vote_i += 1;
|
||||
}
|
||||
vote_state.commission = 0;
|
||||
assert_eq!(
|
||||
Some((0, 10)),
|
||||
StakeState::calculate_rewards(0, STAKE_GETS_PAID_EVERY_VOTE, &vote_state)
|
||||
);
|
||||
vote_state.commission = std::u32::MAX;
|
||||
assert_eq!(
|
||||
Some((10, 0)),
|
||||
StakeState::calculate_rewards(0, STAKE_GETS_PAID_EVERY_VOTE, &vote_state)
|
||||
);
|
||||
vote_state.commission = std::u32::MAX / 2;
|
||||
assert_eq!(
|
||||
Some((5, 5)),
|
||||
StakeState::calculate_rewards(0, STAKE_GETS_PAID_EVERY_VOTE, &vote_state)
|
||||
);
|
||||
// not even enough stake to get paid on 10 credits...
|
||||
assert_eq!(None, StakeState::calculate_rewards(0, 100, &vote_state));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stake_redeem_vote_credits() {
|
||||
let vote_keypair = Keypair::new();
|
||||
let mut vote_state = VoteState::default();
|
||||
for i in 0..1000 {
|
||||
vote_state.process_vote(&Vote::new(i));
|
||||
}
|
||||
|
||||
let vote_pubkey = vote_keypair.pubkey();
|
||||
let mut vote_account =
|
||||
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
|
||||
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
||||
vote_keyed_account.set_state(&vote_state).unwrap();
|
||||
|
||||
let pubkey = Pubkey::default();
|
||||
let mut stake_account = Account::new(
|
||||
STAKE_GETS_PAID_EVERY_VOTE,
|
||||
std::mem::size_of::<StakeState>(),
|
||||
&id(),
|
||||
);
|
||||
let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account);
|
||||
|
||||
// delegate the stake
|
||||
assert!(stake_keyed_account
|
||||
.delegate_stake(&vote_keyed_account)
|
||||
.is_ok());
|
||||
|
||||
let mut mining_pool_account = Account::new(0, std::mem::size_of::<StakeState>(), &id());
|
||||
let mut mining_pool_keyed_account =
|
||||
KeyedAccount::new(&pubkey, true, &mut mining_pool_account);
|
||||
|
||||
// not a mining pool yet...
|
||||
assert_eq!(
|
||||
mining_pool_keyed_account
|
||||
.redeem_vote_credits(&mut stake_keyed_account, &mut vote_keyed_account),
|
||||
Err(InstructionError::InvalidAccountData)
|
||||
);
|
||||
|
||||
mining_pool_keyed_account
|
||||
.set_state(&StakeState::MiningPool)
|
||||
.unwrap();
|
||||
|
||||
// no movement in vote account, so no redemption needed
|
||||
assert!(mining_pool_keyed_account
|
||||
.redeem_vote_credits(&mut stake_keyed_account, &mut vote_keyed_account)
|
||||
.is_ok());
|
||||
|
||||
// move the vote account forward
|
||||
vote_state.process_vote(&Vote::new(1000));
|
||||
vote_keyed_account.set_state(&vote_state).unwrap();
|
||||
|
||||
// now, no lamports in the pool!
|
||||
assert_eq!(
|
||||
mining_pool_keyed_account
|
||||
.redeem_vote_credits(&mut stake_keyed_account, &mut vote_keyed_account),
|
||||
Err(InstructionError::UnbalancedInstruction)
|
||||
);
|
||||
|
||||
// add a lamport to pool
|
||||
mining_pool_keyed_account.account.lamports = 2;
|
||||
assert!(mining_pool_keyed_account
|
||||
.redeem_vote_credits(&mut stake_keyed_account, &mut vote_keyed_account)
|
||||
.is_ok()); // yay
|
||||
|
||||
// lamports only shifted around, none made or lost
|
||||
assert_eq!(
|
||||
2 + 100 + STAKE_GETS_PAID_EVERY_VOTE,
|
||||
mining_pool_account.lamports + vote_account.lamports + stake_account.lamports
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stake_redeem_vote_credits_vote_errors() {
|
||||
let vote_keypair = Keypair::new();
|
||||
let mut vote_state = VoteState::default();
|
||||
for i in 0..1000 {
|
||||
vote_state.process_vote(&Vote::new(i));
|
||||
}
|
||||
|
||||
let vote_pubkey = vote_keypair.pubkey();
|
||||
let mut vote_account =
|
||||
vote_state::create_account(&vote_pubkey, &Pubkey::new_rand(), 0, 100);
|
||||
let mut vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &mut vote_account);
|
||||
vote_keyed_account.set_state(&vote_state).unwrap();
|
||||
|
||||
let pubkey = Pubkey::default();
|
||||
let mut stake_account = Account::new(0, std::mem::size_of::<StakeState>(), &id());
|
||||
let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account);
|
||||
|
||||
// delegate the stake
|
||||
assert!(stake_keyed_account
|
||||
.delegate_stake(&vote_keyed_account)
|
||||
.is_ok());
|
||||
|
||||
let mut mining_pool_account = Account::new(0, std::mem::size_of::<StakeState>(), &id());
|
||||
let mut mining_pool_keyed_account =
|
||||
KeyedAccount::new(&pubkey, true, &mut mining_pool_account);
|
||||
mining_pool_keyed_account
|
||||
.set_state(&StakeState::MiningPool)
|
||||
.unwrap();
|
||||
|
||||
let mut vote_state = VoteState::default();
|
||||
for i in 0..100 {
|
||||
// go back in time, previous state had 1000 votes
|
||||
vote_state.process_vote(&Vote::new(i));
|
||||
}
|
||||
vote_keyed_account.set_state(&vote_state).unwrap();
|
||||
// voter credits lower than stake_delegate credits... TODO: is this an error?
|
||||
assert_eq!(
|
||||
mining_pool_keyed_account
|
||||
.redeem_vote_credits(&mut stake_keyed_account, &mut vote_keyed_account),
|
||||
Err(InstructionError::InvalidAccountData)
|
||||
);
|
||||
|
||||
let vote1_keypair = Keypair::new();
|
||||
let vote1_pubkey = vote1_keypair.pubkey();
|
||||
let mut vote1_account =
|
||||
vote_state::create_account(&vote1_pubkey, &Pubkey::new_rand(), 0, 100);
|
||||
let mut vote1_keyed_account = KeyedAccount::new(&vote1_pubkey, false, &mut vote1_account);
|
||||
vote1_keyed_account.set_state(&vote_state).unwrap();
|
||||
|
||||
// wrong voter_id...
|
||||
assert_eq!(
|
||||
mining_pool_keyed_account
|
||||
.redeem_vote_credits(&mut stake_keyed_account, &mut vote1_keyed_account),
|
||||
Err(InstructionError::InvalidArgument)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user