add passive staking and rewards (#3579)
* add stake stuff * more generic * test decode bail cases * favor early returns
This commit is contained in:
parent
3152090a66
commit
25a2f08f8d
|
@ -2465,6 +2465,31 @@ dependencies = [
|
||||||
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "solana-stake-api"
|
||||||
|
version = "0.13.0"
|
||||||
|
dependencies = [
|
||||||
|
"bincode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"solana-logger 0.13.0",
|
||||||
|
"solana-metrics 0.13.0",
|
||||||
|
"solana-runtime 0.13.0",
|
||||||
|
"solana-sdk 0.13.0",
|
||||||
|
"solana-vote-api 0.13.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "solana-stake-program"
|
||||||
|
version = "0.13.0"
|
||||||
|
dependencies = [
|
||||||
|
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"solana-logger 0.13.0",
|
||||||
|
"solana-sdk 0.13.0",
|
||||||
|
"solana-stake-api 0.13.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "solana-storage-api"
|
name = "solana-storage-api"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
|
|
|
@ -24,6 +24,8 @@ members = [
|
||||||
"programs/token_program",
|
"programs/token_program",
|
||||||
"programs/failure_program",
|
"programs/failure_program",
|
||||||
"programs/noop_program",
|
"programs/noop_program",
|
||||||
|
"programs/stake_api",
|
||||||
|
"programs/stake_program",
|
||||||
"programs/storage_api",
|
"programs/storage_api",
|
||||||
"programs/storage_program",
|
"programs/storage_program",
|
||||||
"programs/vote_api",
|
"programs/vote_api",
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
[package]
|
||||||
|
name = "solana-stake-api"
|
||||||
|
version = "0.13.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.2"
|
||||||
|
log = "0.4.2"
|
||||||
|
serde = "1.0.89"
|
||||||
|
serde_derive = "1.0.89"
|
||||||
|
solana-logger = { path = "../../logger", version = "0.13.0" }
|
||||||
|
solana-metrics = { path = "../../metrics", version = "0.13.0" }
|
||||||
|
solana-sdk = { path = "../../sdk", version = "0.13.0" }
|
||||||
|
solana-vote-api = { path = "../vote_api", version = "0.13.0" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
solana-runtime = { path = "../../runtime", version = "0.13.0" }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "solana_stake_api"
|
||||||
|
crate-type = ["lib"]
|
|
@ -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)
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
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::SystemInstruction;
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StakeInstruction {
|
||||||
|
pub fn new_account(from_id: &Pubkey, staker_id: &Pubkey, lamports: u64) -> Vec<Instruction> {
|
||||||
|
vec![SystemInstruction::new_program_account(
|
||||||
|
from_id,
|
||||||
|
staker_id,
|
||||||
|
lamports,
|
||||||
|
std::mem::size_of::<StakeState>() as u64,
|
||||||
|
&id(),
|
||||||
|
)]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_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 new_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 = &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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,293 @@
|
||||||
|
//! Stake state
|
||||||
|
//! * delegate stakes to vote accounts
|
||||||
|
//! * keep track of rewards
|
||||||
|
//! * own mining pools
|
||||||
|
|
||||||
|
//use crate::{check_id, id};
|
||||||
|
//use log::*;
|
||||||
|
use bincode::{deserialize, serialize_into, ErrorKind};
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use solana_sdk::account::KeyedAccount;
|
||||||
|
use solana_sdk::instruction::InstructionError;
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: &KeyedAccount,
|
||||||
|
) -> Result<(), InstructionError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait State<T> {
|
||||||
|
fn state(&self) -> Result<T, InstructionError>;
|
||||||
|
fn set_state(&mut self, state: &T) -> Result<(), InstructionError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> State<T> for KeyedAccount<'a>
|
||||||
|
where
|
||||||
|
T: serde::Serialize + serde::de::DeserializeOwned,
|
||||||
|
{
|
||||||
|
fn state(&self) -> Result<T, InstructionError> {
|
||||||
|
deserialize(&self.account.data).map_err(|_| InstructionError::InvalidAccountData)
|
||||||
|
}
|
||||||
|
fn set_state(&mut self, state: &T) -> Result<(), InstructionError> {
|
||||||
|
serialize_into(&mut self.account.data[..], state).map_err(|err| match *err {
|
||||||
|
ErrorKind::SizeLimit => InstructionError::AccountDataTooSmall,
|
||||||
|
_ => InstructionError::GenericError,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: &KeyedAccount,
|
||||||
|
) -> Result<(), InstructionError> {
|
||||||
|
if let (
|
||||||
|
StakeState::MiningPool,
|
||||||
|
StakeState::Delegate {
|
||||||
|
voter_id,
|
||||||
|
mut 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
let credits = vote_state.credits() - credits_observed;
|
||||||
|
credits_observed = vote_state.credits();
|
||||||
|
|
||||||
|
if self.account.lamports < credits {
|
||||||
|
return Err(InstructionError::UnbalancedInstruction);
|
||||||
|
}
|
||||||
|
// TODO: commission and network inflation parameter
|
||||||
|
// mining pool lamports reduced by credits * network_inflation_param
|
||||||
|
// stake_account and vote_account lamports up by the net
|
||||||
|
// split by a commission in vote_state
|
||||||
|
self.account.lamports -= credits;
|
||||||
|
stake_account.account.lamports += credits;
|
||||||
|
|
||||||
|
stake_account.set_state(&StakeState::Delegate {
|
||||||
|
voter_id,
|
||||||
|
credits_observed,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(InstructionError::InvalidAccountData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::id;
|
||||||
|
use solana_sdk::account::Account;
|
||||||
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
|
use solana_vote_api::vote_instruction::Vote;
|
||||||
|
use solana_vote_api::vote_state::create_vote_account;
|
||||||
|
|
||||||
|
#[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 = create_vote_account(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_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 = create_vote_account(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);
|
||||||
|
|
||||||
|
// no mining pool yet...
|
||||||
|
assert_eq!(
|
||||||
|
mining_pool_keyed_account
|
||||||
|
.redeem_vote_credits(&mut stake_keyed_account, &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, &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();
|
||||||
|
|
||||||
|
// no lamports in the pool
|
||||||
|
assert_eq!(
|
||||||
|
mining_pool_keyed_account
|
||||||
|
.redeem_vote_credits(&mut stake_keyed_account, &vote_keyed_account),
|
||||||
|
Err(InstructionError::UnbalancedInstruction)
|
||||||
|
);
|
||||||
|
|
||||||
|
// add a lamport
|
||||||
|
mining_pool_keyed_account.account.lamports = 2;
|
||||||
|
assert!(mining_pool_keyed_account
|
||||||
|
.redeem_vote_credits(&mut stake_keyed_account, &vote_keyed_account)
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 = create_vote_account(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, &vote_keyed_account),
|
||||||
|
Err(InstructionError::InvalidAccountData)
|
||||||
|
);
|
||||||
|
|
||||||
|
let vote1_keypair = Keypair::new();
|
||||||
|
let vote1_pubkey = vote1_keypair.pubkey();
|
||||||
|
let mut vote1_account = create_vote_account(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, &vote1_keyed_account),
|
||||||
|
Err(InstructionError::InvalidArgument)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "solana-stake-program"
|
||||||
|
version = "0.13.0"
|
||||||
|
description = "Solana stake program"
|
||||||
|
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||||
|
repository = "https://github.com/solana-labs/solana"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
homepage = "https://solana.com/"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4.2"
|
||||||
|
solana-logger = { path = "../../logger", version = "0.13.0" }
|
||||||
|
solana-sdk = { path = "../../sdk", version = "0.13.0" }
|
||||||
|
solana-stake-api = { path = "../stake_api", version = "0.13.0" }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "solana_stake_program"
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
use solana_stake_api::stake_instruction::process_instruction;
|
||||||
|
|
||||||
|
solana_sdk::solana_entrypoint!(process_instruction);
|
Loading…
Reference in New Issue