Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
HaoranYi committed Jun 2, 2023
1 parent 6c52039 commit 11f5792
Show file tree
Hide file tree
Showing 12 changed files with 547 additions and 98 deletions.
6 changes: 3 additions & 3 deletions account-decoder/src/parse_stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub fn parse_stake(data: &[u8]) -> Result<StakeAccountType, ParseAccountError> {
meta: meta.into(),
stake: None,
}),
StakeState::Stake(meta, stake) => StakeAccountType::Delegated(UiStakeAccount {
StakeState::Stake(meta, stake, _) => StakeAccountType::Delegated(UiStakeAccount {
meta: meta.into(),
stake: Some(stake.into()),
}),
Expand Down Expand Up @@ -136,7 +136,7 @@ impl From<Delegation> for UiDelegation {

#[cfg(test)]
mod test {
use {super::*, bincode::serialize};
use {super::*, bincode::serialize, solana_sdk::stake::state::DeactivationFlag};

#[test]
fn test_parse_stake() {
Expand Down Expand Up @@ -194,7 +194,7 @@ mod test {
credits_observed: 10,
};

let stake_state = StakeState::Stake(meta, stake);
let stake_state = StakeState::Stake(meta, stake, DeactivationFlag::Empty);
let stake_data = serialize(&stake_state).unwrap();
assert_eq!(
parse_stake(&stake_data).unwrap(),
Expand Down
2 changes: 1 addition & 1 deletion cli/src/cluster_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1819,7 +1819,7 @@ pub fn process_show_stakes(
});
}
}
StakeState::Stake(_, stake) => {
StakeState::Stake(_, stake, _) => {
if vote_account_pubkeys.is_none()
|| vote_account_pubkeys
.unwrap()
Expand Down
3 changes: 2 additions & 1 deletion cli/src/stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1628,7 +1628,7 @@ pub fn process_deactivate_stake_account(

let vote_account_address = match stake_account.state() {
Ok(stake_state) => match stake_state {
StakeState::Stake(_, stake) => stake.delegation.voter_pubkey,
StakeState::Stake(_, stake, _) => stake.delegation.voter_pubkey,
_ => {
return Err(CliError::BadParameter(format!(
"{stake_account_address} is not a delegated stake account",
Expand Down Expand Up @@ -2195,6 +2195,7 @@ pub fn build_stake_state(
lockup,
},
stake,
_,
) => {
let current_epoch = clock.epoch;
let StakeActivationStatus {
Expand Down
314 changes: 312 additions & 2 deletions cli/tests/stake.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![allow(clippy::integer_arithmetic)]
#![allow(clippy::redundant_closure)]

use {
solana_cli::{
check_balance,
Expand Down Expand Up @@ -165,7 +166,7 @@ fn test_stake_redelegation() {
let stake_state: StakeState = stake_account.state().unwrap();

let rent_exempt_reserve = match stake_state {
StakeState::Stake(meta, stake) => {
StakeState::Stake(meta, stake, _) => {
assert_eq!(stake.delegation.voter_pubkey, vote_keypair.pubkey());
meta.rent_exempt_reserve
}
Expand Down Expand Up @@ -270,7 +271,7 @@ fn test_stake_redelegation() {
let stake2_state: StakeState = stake2_account.state().unwrap();

match stake2_state {
StakeState::Stake(_meta, stake) => {
StakeState::Stake(_meta, stake, _) => {
assert_eq!(stake.delegation.voter_pubkey, vote2_keypair.pubkey());
}
_ => panic!("Unexpected stake2 state!"),
Expand Down Expand Up @@ -2319,3 +2320,312 @@ fn test_stake_minimum_delegation() {
let result = process_command(&config);
assert!(matches!(result, Ok(..)));
}

#[test]
fn test_stake_redelegation_withdraw_not_permitted() {
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let authorized_withdrawer = Keypair::new().pubkey();
let faucet_addr = run_local_faucet(mint_keypair, None);

// setup test validator
let slots_per_epoch = 32;
let test_validator = TestValidatorGenesis::default()
.fee_rate_governor(FeeRateGovernor::new(0, 0))
.rent(Rent {
lamports_per_byte_year: 1,
exemption_threshold: 1.0,
..Rent::default()
})
.epoch_schedule(EpochSchedule::custom(
slots_per_epoch,
slots_per_epoch,
/* enable_warmup_epochs = */ false,
))
.faucet_addr(Some(faucet_addr))
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");

let rpc_client =
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
let default_signer = Keypair::new();

let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
config.signers = vec![&default_signer];

request_and_confirm_airdrop(
&rpc_client,
&config,
&config.signers[0].pubkey(),
100_000_000_000,
)
.unwrap();

// Create vote account
let vote_keypair = Keypair::new();
config.signers = vec![&default_signer, &vote_keypair];
config.command = CliCommand::CreateVoteAccount {
vote_account: 1,
seed: None,
identity_account: 0,
authorized_voter: None,
authorized_withdrawer,
commission: 0,
sign_only: false,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
nonce_authority: 0,
memo: None,
fee_payer: 0,
compute_unit_price: None,
};
process_command(&config).unwrap();

// Create second vote account
let vote2_keypair = Keypair::new();
config.signers = vec![&default_signer, &vote2_keypair];
config.command = CliCommand::CreateVoteAccount {
vote_account: 1,
seed: None,
identity_account: 0,
authorized_voter: None,
authorized_withdrawer,
commission: 0,
sign_only: false,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
nonce_authority: 0,
memo: None,
fee_payer: 0,
compute_unit_price: None,
};
process_command(&config).unwrap();

// Create stake account
let stake_keypair = Keypair::new();
config.signers = vec![&default_signer, &stake_keypair];
config.command = CliCommand::CreateStakeAccount {
stake_account: 1,
seed: None,
staker: None,
withdrawer: None,
withdrawer_signer: None,
lockup: Lockup::default(),
amount: SpendAmount::Some(50_000_000_000),
sign_only: false,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
nonce_authority: 0,
memo: None,
fee_payer: 0,
from: 0,
compute_unit_price: None,
};
process_command(&config).unwrap();

// Delegate stake to `vote_keypair`
config.signers = vec![&default_signer];
config.command = CliCommand::DelegateStake {
stake_account_pubkey: stake_keypair.pubkey(),
vote_account_pubkey: vote_keypair.pubkey(),
stake_authority: 0,
force: true,
sign_only: false,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::default(),
nonce_account: None,
nonce_authority: 0,
memo: None,
fee_payer: 0,
redelegation_stake_account: None,
compute_unit_price: None,
};
process_command(&config).unwrap();

// wait for new epoch
wait_for_next_epoch_plus_n_slots(&rpc_client, 0);

// `stake_keypair` should now be delegated to `vote_keypair` and fully activated
let stake_account = rpc_client.get_account(&stake_keypair.pubkey()).unwrap();
let stake_state: StakeState = stake_account.state().unwrap();

let rent_exempt_reserve = match stake_state {
StakeState::Stake(meta, stake, _) => {
assert_eq!(stake.delegation.voter_pubkey, vote_keypair.pubkey());
meta.rent_exempt_reserve
}
_ => panic!("Unexpected stake state!"),
};

assert_eq!(
rpc_client
.get_stake_activation(stake_keypair.pubkey(), None)
.unwrap(),
RpcStakeActivation {
state: StakeActivationState::Active,
active: 50_000_000_000 - rent_exempt_reserve,
inactive: 0
}
);
check_balance!(50_000_000_000, &rpc_client, &stake_keypair.pubkey());

// Now try to withdraw from stake_account. It will fail.
let recipient = keypair_from_seed(&[5u8; 32]).unwrap();
let recipient_pubkey = recipient.pubkey();
config.command = CliCommand::WithdrawStake {
stake_account_pubkey: stake_keypair.pubkey(),
destination_account_pubkey: recipient_pubkey,
amount: SpendAmount::Some(50_000_000_000),
withdraw_authority: 0,
custodian: None,
sign_only: false,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
nonce_authority: 0,
memo: None,
seed: None,
fee_payer: 0,
compute_unit_price: None,
};

let withdraw_result = process_command(&config);
if let Err(e) = withdraw_result {
let s = format!("{}", e);
assert!(s.contains("insufficient funds for instruction"));
} else {
unreachable!();
}

// Setup `stake2_keypair. Add an extra `rent_exempt_reserve` amount into `stake2_keypair`
// before redelegation to account for the `rent_exempt_reserve` balance that'll be pealed off
// the stake during the redelegation process.
let stake2_keypair = Keypair::new();
request_and_confirm_airdrop(
&rpc_client,
&config,
&stake2_keypair.pubkey(),
rent_exempt_reserve,
)
.unwrap();

// Redelegate to `vote2_keypair` via `stake2_keypair
config.signers = vec![&default_signer, &stake2_keypair];
config.command = CliCommand::DelegateStake {
stake_account_pubkey: stake_keypair.pubkey(),
vote_account_pubkey: vote2_keypair.pubkey(),
stake_authority: 0,
force: true,
sign_only: false,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::default(),
nonce_account: None,
nonce_authority: 0,
memo: None,
fee_payer: 0,
redelegation_stake_account: Some(1),
compute_unit_price: None,
};
process_command(&config).unwrap();

// `stake_keypair` should now be deactivating
assert_eq!(
rpc_client
.get_stake_activation(stake_keypair.pubkey(), None)
.unwrap(),
RpcStakeActivation {
state: StakeActivationState::Deactivating,
active: 50_000_000_000 - rent_exempt_reserve,
inactive: 0,
}
);

// `stake_keypair2` should now be activating
assert_eq!(
rpc_client
.get_stake_activation(stake2_keypair.pubkey(), None)
.unwrap(),
RpcStakeActivation {
state: StakeActivationState::Activating,
active: 0,
inactive: 50_000_000_000 - rent_exempt_reserve,
}
);

// check that all the stake, save `rent_exempt_reserve`, have been moved from `stake_keypair`
// to `stake2_keypair`
check_balance!(rent_exempt_reserve, &rpc_client, &stake_keypair.pubkey());
check_balance!(50_000_000_000, &rpc_client, &stake2_keypair.pubkey());

// Deactivate stake2_account should fail because stake2_account is not fully activated.
config.signers = vec![&default_signer];

config.command = CliCommand::DeactivateStake {
stake_account_pubkey: stake2_keypair.pubkey(),
stake_authority: 0,
sign_only: false,
deactivate_delinquent: false,
dump_transaction_message: true,
blockhash_query: BlockhashQuery::default(),
nonce_account: None,
nonce_authority: 0,
memo: None,
seed: None,
fee_payer: 0,
compute_unit_price: None,
};
let deactivate_result = process_command(&config);
if let Err(e) = deactivate_result {
let s = format!("{}", e);
assert_eq!(
s,
"redelegated stake must be fully activated before deactivation"
);
} else {
unreachable!();
}

// `stake_keypair2` should still be Activating
assert_eq!(
rpc_client
.get_stake_activation(stake2_keypair.pubkey(), None)
.unwrap(),
RpcStakeActivation {
state: StakeActivationState::Activating,
active: 0,
inactive: 50_000_000_000 - rent_exempt_reserve,
}
);

// Withdraw from stake2 account should still fails
let recipient = keypair_from_seed(&[5u8; 32]).unwrap();
let recipient_pubkey = recipient.pubkey();
config.command = CliCommand::WithdrawStake {
stake_account_pubkey: stake2_keypair.pubkey(),
destination_account_pubkey: recipient_pubkey,
amount: SpendAmount::Some(50_000_000_000),
withdraw_authority: 0,
custodian: None,
sign_only: false,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
nonce_authority: 0,
memo: None,
seed: None,
fee_payer: 0,
compute_unit_price: None,
};

let withdraw_result = process_command(&config);
if let Err(e) = withdraw_result {
let s = format!("{}", e);
assert!(s.contains("insufficient funds for instruction"));
} else {
unreachable!();
}
}
2 changes: 1 addition & 1 deletion ledger-tool/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2861,7 +2861,7 @@ fn main() {
.unwrap()
.into_iter()
{
if let Ok(StakeState::Stake(meta, stake)) = account.state() {
if let Ok(StakeState::Stake(meta, stake, _)) = account.state() {
if vote_accounts_to_destake
.contains(&stake.delegation.voter_pubkey)
{
Expand Down
Loading

0 comments on commit 11f5792

Please sign in to comment.