fix split instruction doc (#7022)

This commit is contained in:
Rob Walker
2019-11-18 15:31:17 -08:00
committed by GitHub
parent a6196901de
commit cbf7c0080b
2 changed files with 136 additions and 79 deletions

View File

@ -94,17 +94,14 @@ pub enum StakeInstruction {
RedeemVoteCredits, RedeemVoteCredits,
/// Split u64 tokens and stake off a stake account into another stake /// Split u64 tokens and stake off a stake account into another stake
/// account. Requires Authorized::staker signature. /// account. Requires Authorized::staker signature and the
/// /// signature of the split-off stake address.
/// The split-off stake account must be Initialized and carry the
/// the same values for Lockup and Authorized as the source
/// or this instruction will fail.
/// ///
/// The source stake must be either Initialized or a Stake. /// The source stake must be either Initialized or a Stake.
/// ///
/// Expects 2 Accounts: /// Expects 2 Accounts:
/// 0 - StakeAccount to be split /// 0 - StakeAccount to be split
/// 1 - Initialized StakeAcount that will take the split-off amount /// 1 - Uninitialized StakeAcount that will take the split-off amount
/// ///
Split(u64), Split(u64),

View File

@ -5,18 +5,16 @@ use solana_runtime::{
genesis_utils::{create_genesis_config_with_leader, GenesisConfigInfo}, genesis_utils::{create_genesis_config_with_leader, GenesisConfigInfo},
}; };
use solana_sdk::{ use solana_sdk::{
account::Account,
account_utils::State, account_utils::State,
client::SyncClient, client::SyncClient,
message::Message, message::Message,
pubkey::Pubkey, pubkey::Pubkey,
signature::{Keypair, KeypairUtil}, signature::{Keypair, KeypairUtil},
sysvar::{self, rewards::Rewards, Sysvar}, sysvar::{self, rewards::Rewards, stake_history::StakeHistory, Sysvar},
}; };
use solana_stake_api::{ use solana_stake_api::{
id, stake_instruction::{self},
stake_instruction::{self, process_instruction}, stake_state::{self, StakeState},
stake_state::{self, Stake, StakeState},
}; };
use solana_vote_api::{ use solana_vote_api::{
vote_instruction, vote_instruction,
@ -24,6 +22,16 @@ use solana_vote_api::{
}; };
use std::sync::Arc; use std::sync::Arc;
fn next_epoch(bank: &Arc<Bank>) -> Arc<Bank> {
bank.squash();
Arc::new(Bank::new_from_parent(
&bank,
&Pubkey::default(),
bank.get_slots_in_epoch(bank.epoch()) + bank.slot(),
))
}
fn fill_epoch_with_votes( fn fill_epoch_with_votes(
bank: &Arc<Bank>, bank: &Arc<Bank>,
vote_keypair: &Keypair, vote_keypair: &Keypair,
@ -34,6 +42,7 @@ fn fill_epoch_with_votes(
let old_epoch = bank.epoch(); let old_epoch = bank.epoch();
let mut bank = bank.clone(); let mut bank = bank.clone();
while bank.epoch() != old_epoch + 1 { while bank.epoch() != old_epoch + 1 {
bank.squash();
bank = Arc::new(Bank::new_from_parent( bank = Arc::new(Bank::new_from_parent(
&bank, &bank,
&Pubkey::default(), &Pubkey::default(),
@ -58,10 +67,39 @@ fn fill_epoch_with_votes(
bank bank
} }
fn warmed_up(bank: &Bank, stake_pubkey: &Pubkey) -> bool {
let stake = StakeState::stake_from(&bank.get_account(stake_pubkey).unwrap()).unwrap();
stake.stake
== stake.stake(
bank.epoch(),
Some(
&StakeHistory::from_account(
&bank.get_account(&sysvar::stake_history::id()).unwrap(),
)
.unwrap(),
),
)
}
fn get_staked(bank: &Bank, stake_pubkey: &Pubkey) -> u64 {
StakeState::stake_from(&bank.get_account(stake_pubkey).unwrap())
.unwrap()
.stake(
bank.epoch(),
Some(
&StakeHistory::from_account(
&bank.get_account(&sysvar::stake_history::id()).unwrap(),
)
.unwrap(),
),
)
}
#[test] #[test]
fn test_stake_account_delegate() { fn test_stake_account_lifetime() {
let staker_keypair = Keypair::new(); let stake_keypair = Keypair::new();
let staker_pubkey = staker_keypair.pubkey(); let stake_pubkey = stake_keypair.pubkey();
let vote_keypair = Keypair::new(); let vote_keypair = Keypair::new();
let vote_pubkey = vote_keypair.pubkey(); let vote_pubkey = vote_keypair.pubkey();
let node_pubkey = Pubkey::new_rand(); let node_pubkey = Pubkey::new_rand();
@ -95,21 +133,21 @@ fn test_stake_account_delegate() {
.send_message(&[&mint_keypair, &vote_keypair], message) .send_message(&[&mint_keypair, &vote_keypair], message)
.expect("failed to create vote account"); .expect("failed to create vote account");
let authorized = stake_state::Authorized::auto(&staker_pubkey); let authorized = stake_state::Authorized::auto(&stake_pubkey);
// Create stake account and delegate to vote account // Create stake account and delegate to vote account
let message = Message::new(stake_instruction::create_stake_account_and_delegate_stake( let message = Message::new(stake_instruction::create_stake_account_and_delegate_stake(
&mint_pubkey, &mint_pubkey,
&staker_pubkey, &stake_pubkey,
&vote_pubkey, &vote_pubkey,
&authorized, &authorized,
1_000_000, 1_000_000,
)); ));
bank_client bank_client
.send_message(&[&mint_keypair, &staker_keypair], message) .send_message(&[&mint_keypair, &stake_keypair], message)
.expect("failed to create and delegate stake account"); .expect("failed to create and delegate stake account");
// Test that correct lamports are staked // Test that correct lamports are staked
let account = bank.get_account(&staker_pubkey).expect("account not found"); let account = bank.get_account(&stake_pubkey).expect("account not found");
let stake_state = account.state().expect("couldn't unpack account data"); let stake_state = account.state().expect("couldn't unpack account data");
if let StakeState::Stake(_meta, stake) = stake_state { if let StakeState::Stake(_meta, stake) = stake_state {
assert_eq!(stake.stake, 1_000_000); assert_eq!(stake.stake, 1_000_000);
@ -117,22 +155,22 @@ fn test_stake_account_delegate() {
assert!(false, "wrong account type found") assert!(false, "wrong account type found")
} }
// Test that we cannot withdraw staked lamports // Test that we cannot withdraw anything until deactivation
let message = Message::new_with_payer( let message = Message::new_with_payer(
vec![stake_instruction::withdraw( vec![stake_instruction::withdraw(
&staker_pubkey, &stake_pubkey,
&staker_pubkey, &stake_pubkey,
&Pubkey::new_rand(), &Pubkey::new_rand(),
1_000_000, 1,
)], )],
Some(&mint_pubkey), Some(&mint_pubkey),
); );
assert!(bank_client assert!(bank_client
.send_message(&[&mint_keypair, &staker_keypair], message) .send_message(&[&mint_keypair, &stake_keypair], message)
.is_err()); .is_err());
// Test that lamports are still staked // Test that lamports are still staked
let account = bank.get_account(&staker_pubkey).expect("account not found"); let account = bank.get_account(&stake_pubkey).expect("account not found");
let stake_state = account.state().expect("couldn't unpack account data"); let stake_state = account.state().expect("couldn't unpack account data");
if let StakeState::Stake(_meta, stake) = stake_state { if let StakeState::Stake(_meta, stake) = stake_state {
assert_eq!(stake.stake, 1_000_000); assert_eq!(stake.stake, 1_000_000);
@ -142,7 +180,6 @@ fn test_stake_account_delegate() {
// Reward redemption // Reward redemption
// Submit enough votes to generate rewards // Submit enough votes to generate rewards
let old_epoch = bank.epoch();
bank = fill_epoch_with_votes(&bank, &vote_keypair, &mint_keypair); bank = fill_epoch_with_votes(&bank, &vote_keypair, &mint_keypair);
// Test that votes and credits are there // Test that votes and credits are there
@ -152,127 +189,150 @@ fn test_stake_account_delegate() {
// 1 less vote, as the first vote should have cleared the lockout // 1 less vote, as the first vote should have cleared the lockout
assert_eq!(vote_state.votes.len(), 31); assert_eq!(vote_state.votes.len(), 31);
assert_eq!(vote_state.credits(), 1); assert_eq!(vote_state.credits(), 1);
assert_ne!(old_epoch, bank.epoch());
// Cycle thru banks until we reach next epoch
bank = fill_epoch_with_votes(&bank, &vote_keypair, &mint_keypair); bank = fill_epoch_with_votes(&bank, &vote_keypair, &mint_keypair);
loop {
if warmed_up(&bank, &stake_pubkey) {
break;
}
// Cycle thru banks until we're fully warmed up
bank = next_epoch(&bank);
}
// Test that rewards are there // Test that rewards are there
let rewards_account = bank let rewards_account = bank
.get_account(&sysvar::rewards::id()) .get_account(&sysvar::rewards::id())
.expect("account not found"); .expect("account not found");
assert_matches!(Rewards::from_account(&rewards_account), Some(_)); assert_matches!(Rewards::from_account(&rewards_account), Some(_));
let pre_staked = get_staked(&bank, &stake_pubkey);
// Redeem the credit // Redeem the credit
let bank_client = BankClient::new_shared(&bank); let bank_client = BankClient::new_shared(&bank);
let message = Message::new_with_payer( let message = Message::new_with_payer(
vec![stake_instruction::redeem_vote_credits( vec![stake_instruction::redeem_vote_credits(
&staker_pubkey, &stake_pubkey,
&vote_pubkey, &vote_pubkey,
)], )],
Some(&mint_pubkey), Some(&mint_pubkey),
); );
assert_matches!(bank_client.send_message(&[&mint_keypair], message), Ok(_)); assert_matches!(bank_client.send_message(&[&mint_keypair], message), Ok(_));
fn get_stake(bank: &Bank, staker_pubkey: &Pubkey) -> (Stake, Account) { // Test that balance increased, and that the balance got staked
let account = bank.get_account(staker_pubkey).unwrap(); let staked = get_staked(&bank, &stake_pubkey);
(StakeState::stake_from(&account).unwrap(), account) let lamports = bank.get_balance(&stake_pubkey);
} assert!(staked > pre_staked);
assert!(lamports > 1_000_000);
// Test that balance increased, and calculate the rewards // split the stake
let (stake, account) = get_stake(&bank, &staker_pubkey); let split_stake_keypair = Keypair::new();
assert!(account.lamports > 1_000_000); let split_stake_pubkey = split_stake_keypair.pubkey();
assert!(stake.stake > 1_000_000); // Test split
let rewards = account.lamports - 1_000_000; let message = Message::new_with_payer(
stake_instruction::split(
&stake_pubkey,
&stake_pubkey,
lamports / 2,
&split_stake_pubkey,
),
Some(&mint_pubkey),
);
assert!(bank_client
.send_message(
&[&mint_keypair, &stake_keypair, &split_stake_keypair],
message
)
.is_ok());
// Deactivate the stake // Deactivate the split
let message = Message::new_with_payer( let message = Message::new_with_payer(
vec![stake_instruction::deactivate_stake( vec![stake_instruction::deactivate_stake(
&staker_pubkey, &split_stake_pubkey,
&staker_pubkey, &stake_pubkey,
)], )],
Some(&mint_pubkey), Some(&mint_pubkey),
); );
assert!(bank_client assert!(bank_client
.send_message(&[&mint_keypair, &staker_keypair], message) .send_message(&[&mint_keypair, &stake_keypair], message)
.is_ok()); .is_ok());
// Test that we cannot withdraw staked lamports due to cooldown period let split_staked = get_staked(&bank, &split_stake_pubkey);
// Test that we cannot withdraw above what's staked
let message = Message::new_with_payer( let message = Message::new_with_payer(
vec![stake_instruction::withdraw( vec![stake_instruction::withdraw(
&staker_pubkey, &split_stake_pubkey,
&staker_pubkey, &stake_pubkey,
&Pubkey::new_rand(), &Pubkey::new_rand(),
1_000_000, lamports / 2 - split_staked + 1,
)], )],
Some(&mint_pubkey), Some(&mint_pubkey),
); );
assert!(bank_client assert!(bank_client
.send_message(&[&mint_keypair, &staker_keypair], message) .send_message(&[&mint_keypair, &stake_keypair], message)
.is_err()); .is_err());
let old_epoch = bank.epoch(); let mut bank = next_epoch(&bank);
let slots = bank.get_slots_in_epoch(old_epoch);
// Create a new bank at later epoch (within cooldown period)
let bank = Bank::new_from_parent(&bank, &Pubkey::default(), slots + bank.slot());
assert_ne!(old_epoch, bank.epoch());
let bank = Arc::new(bank);
let bank_client = BankClient::new_shared(&bank); let bank_client = BankClient::new_shared(&bank);
// assert we're still cooling down
let split_staked = get_staked(&bank, &split_stake_pubkey);
assert!(split_staked > 0);
// withdrawal in cooldown
let message = Message::new_with_payer( let message = Message::new_with_payer(
vec![stake_instruction::withdraw( vec![stake_instruction::withdraw(
&staker_pubkey, &split_stake_pubkey,
&staker_pubkey, &stake_pubkey,
&Pubkey::new_rand(), &Pubkey::new_rand(),
1_000_000, lamports / 2,
)], )],
Some(&mint_pubkey), Some(&mint_pubkey),
); );
assert!(bank_client assert!(bank_client
.send_message(&[&mint_keypair, &staker_keypair], message) .send_message(&[&mint_keypair, &stake_keypair], message)
.is_err()); .is_err());
// but we can withdraw unstaked
let message = Message::new_with_payer( let message = Message::new_with_payer(
vec![stake_instruction::withdraw( vec![stake_instruction::withdraw(
&staker_pubkey, &split_stake_pubkey,
&staker_pubkey, &stake_pubkey,
&Pubkey::new_rand(), &Pubkey::new_rand(),
250_000, lamports / 2 - split_staked,
)], )],
Some(&mint_pubkey), Some(&mint_pubkey),
); );
// assert we can withdraw some smaller amount, more than rewards // assert we can withdraw unstaked tokens
assert!(bank_client assert!(bank_client
.send_message(&[&mint_keypair, &staker_keypair], message) .send_message(&[&mint_keypair, &stake_keypair], message)
.is_ok()); .is_ok());
// Create a new bank at a much later epoch (to account for cooldown of stake) // finish cooldown
let mut bank = Bank::new_from_parent( loop {
&bank, if get_staked(&bank, &split_stake_pubkey) == 0 {
&Pubkey::default(), break;
genesis_config.epoch_schedule.slots_per_epoch * 10 + bank.slot(), }
); bank = next_epoch(&bank);
bank.add_instruction_processor(id(), process_instruction); }
let bank = Arc::new(bank); let bank_client = BankClient::new_shared(&bank);
let bank_client = BankClient::new_shared(&bank);
// Test that we can withdraw everything else out of the split
// Test that we can withdraw now let message = Message::new_with_payer(
let message = Message::new_with_payer( vec![stake_instruction::withdraw(
vec![stake_instruction::withdraw( &split_stake_pubkey,
&staker_pubkey, &stake_pubkey,
&staker_pubkey, &Pubkey::new_rand(),
&Pubkey::new_rand(), split_staked,
750_000, )],
)], Some(&mint_pubkey),
Some(&mint_pubkey), );
); assert!(bank_client
assert!(bank_client .send_message(&[&mint_keypair, &stake_keypair], message)
.send_message(&[&mint_keypair, &staker_keypair], message) .is_ok());
.is_ok());
// verify all the math sums to zero
// Test that balance and stake is updated correctly (we have withdrawn all lamports except rewards) assert_eq!(bank.get_balance(&split_stake_pubkey), 0);
let (_stake, account) = get_stake(&bank, &staker_pubkey); assert_eq!(bank.get_balance(&stake_pubkey), lamports - lamports / 2);
assert_eq!(account.lamports, rewards);
} }