fix split instruction doc (#7022)
This commit is contained in:
@ -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),
|
||||||
|
|
||||||
|
@ -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 now
|
// Test that we can withdraw everything else out of the split
|
||||||
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(),
|
||||||
750_000,
|
split_staked,
|
||||||
)],
|
)],
|
||||||
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 balance and stake is updated correctly (we have withdrawn all lamports except rewards)
|
// verify all the math sums to zero
|
||||||
let (_stake, account) = get_stake(&bank, &staker_pubkey);
|
assert_eq!(bank.get_balance(&split_stake_pubkey), 0);
|
||||||
assert_eq!(account.lamports, rewards);
|
assert_eq!(bank.get_balance(&stake_pubkey), lamports - lamports / 2);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user