From 36eda29fc9a9ac5bb54ec025dcb8317e2fdc66cf Mon Sep 17 00:00:00 2001 From: Pankaj Garg Date: Fri, 21 Jun 2019 23:45:03 -0700 Subject: [PATCH] Add instructions and processor for stake deactivation (#4781) automerge --- programs/stake_api/src/stake_instruction.rs | 62 +++++++++++++++++++++ programs/stake_api/src/stake_state.rs | 38 +++++++++++++ wallet/src/wallet.rs | 59 ++++++++++++++++++++ 3 files changed, 159 insertions(+) diff --git a/programs/stake_api/src/stake_instruction.rs b/programs/stake_api/src/stake_instruction.rs index ad40dd27b0..8c89c7ce58 100644 --- a/programs/stake_api/src/stake_instruction.rs +++ b/programs/stake_api/src/stake_instruction.rs @@ -42,6 +42,13 @@ pub enum StakeInstruction { /// The u64 is the portion of the Stake account balance to be withdrawn, /// must be <= StakeAccount.lamports - staked lamports Withdraw(u64), + + /// Deactivates the stake in the account + /// + /// Expects 2 Accounts: + /// 0 - Delegate StakeAccount + /// 1 - Syscall Account that carries epoch + Deactivate, } pub fn create_stake_account( @@ -97,6 +104,14 @@ pub fn withdraw(stake_pubkey: &Pubkey, to_pubkey: &Pubkey, lamports: u64) -> Ins Instruction::new(id(), &StakeInstruction::Withdraw(lamports), account_metas) } +pub fn deactivate_stake(stake_pubkey: &Pubkey) -> Instruction { + let account_metas = vec![ + AccountMeta::new(*stake_pubkey, true), + AccountMeta::new(syscall::current::id(), false), + ]; + Instruction::new(id(), &StakeInstruction::Deactivate, account_metas) +} + pub fn process_instruction( _program_id: &Pubkey, keyed_accounts: &mut [KeyedAccount], @@ -156,6 +171,14 @@ pub fn process_instruction( &syscall::current::from_keyed_account(&syscall[0])?, ) } + StakeInstruction::Deactivate => { + if rest.len() != 1 { + Err(InstructionError::InvalidInstructionData)?; + } + let syscall = &rest[0]; + + me.deactivate_stake(&syscall::current::from_keyed_account(&syscall)?) + } } } @@ -205,6 +228,10 @@ mod tests { process_instruction(&withdraw(&Pubkey::default(), &Pubkey::new_rand(), 100)), Err(InstructionError::InvalidAccountData), ); + assert_eq!( + process_instruction(&deactivate_stake(&Pubkey::default())), + Err(InstructionError::InvalidAccountData), + ); } #[test] @@ -322,6 +349,41 @@ mod tests { ), Err(InstructionError::InvalidInstructionData), ); + + // Tests 2nd keyed account is of correct type (Current instead of rewards) in deactivate + assert_eq!( + super::process_instruction( + &Pubkey::default(), + &mut [ + KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()), + KeyedAccount::new( + &syscall::rewards::id(), + false, + &mut syscall::rewards::create_account(1, 0.0, 0.0) + ), + ], + &serialize(&StakeInstruction::Deactivate).unwrap(), + ), + Err(InstructionError::InvalidArgument), + ); + + // Tests correct number of accounts are provided in deactivate + assert_eq!( + super::process_instruction( + &Pubkey::default(), + &mut [ + KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()), + KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()), + KeyedAccount::new( + &syscall::current::id(), + false, + &mut syscall::rewards::create_account(1, 0.0, 0.0) + ), + ], + &serialize(&StakeInstruction::Deactivate).unwrap(), + ), + Err(InstructionError::InvalidInstructionData), + ); } } diff --git a/programs/stake_api/src/stake_state.rs b/programs/stake_api/src/stake_state.rs index 67986d3665..b2c02c1271 100644 --- a/programs/stake_api/src/stake_state.rs +++ b/programs/stake_api/src/stake_state.rs @@ -464,6 +464,44 @@ mod tests { assert_eq!(stake.stake(STAKE_WARMUP_EPOCHS * 42), 0); } + #[test] + fn test_deactivate_stake() { + let stake_pubkey = Pubkey::new_rand(); + let stake_lamports = 42; + let mut stake_account = + Account::new(stake_lamports, std::mem::size_of::(), &id()); + + let current = syscall::current::Current::default(); + + // unsigned keyed account + let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account); + assert_eq!( + stake_keyed_account.deactivate_stake(¤t), + Err(InstructionError::MissingRequiredSignature) + ); + + // signed keyed account but not staked yet + let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); + assert_eq!( + stake_keyed_account.deactivate_stake(¤t), + Err(InstructionError::InvalidAccountData) + ); + + // Staking + let vote_pubkey = Pubkey::new_rand(); + 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(&VoteState::default()).unwrap(); + assert_eq!( + stake_keyed_account.delegate_stake(&vote_keyed_account, stake_lamports, ¤t), + Ok(()) + ); + + // Deactivate after staking + assert_eq!(stake_keyed_account.deactivate_stake(¤t), Ok(())); + } + #[test] fn test_withdraw_stake() { let stake_pubkey = Pubkey::new_rand(); diff --git a/wallet/src/wallet.rs b/wallet/src/wallet.rs index fff1c603dd..0207f81d05 100644 --- a/wallet/src/wallet.rs +++ b/wallet/src/wallet.rs @@ -53,6 +53,7 @@ pub enum WalletCommand { CreateStakeAccount(Pubkey, u64), DelegateStake(Keypair, Pubkey, u64), WithdrawStake(Keypair, Pubkey, u64), + DeactivateStake(Keypair), RedeemVoteCredits(Pubkey, Pubkey), ShowStakeAccount(Pubkey), CreateStorageMiningPoolAccount(Pubkey, u64), @@ -261,6 +262,11 @@ pub fn parse_command( lamports, )) } + ("deactivate-stake", Some(matches)) => { + let staking_account_keypair = + keypair_of(matches, "staking_account_keypair_file").unwrap(); + Ok(WalletCommand::DeactivateStake(staking_account_keypair)) + } ("redeem-vote-credits", Some(matches)) => { let staking_account_pubkey = value_of(matches, "staking_account_pubkey").unwrap(); let voting_account_pubkey = value_of(matches, "voting_account_pubkey").unwrap(); @@ -571,6 +577,24 @@ fn process_create_stake_account( Ok(signature_str.to_string()) } +fn process_deactivate_stake_account( + rpc_client: &RpcClient, + config: &WalletConfig, + staking_account_keypair: &Keypair, +) -> ProcessResult { + let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?; + let ixs = stake_instruction::deactivate_stake(&staking_account_keypair.pubkey()); + let mut tx = Transaction::new_signed_with_payer( + vec![ixs], + Some(&config.keypair.pubkey()), + &[&config.keypair, &staking_account_keypair], + recent_blockhash, + ); + let signature_str = rpc_client + .send_and_confirm_transaction(&mut tx, &[&config.keypair, &staking_account_keypair])?; + Ok(signature_str.to_string()) +} + fn process_delegate_stake( rpc_client: &RpcClient, config: &WalletConfig, @@ -1073,6 +1097,11 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult { *lamports, ), + // Deactivate stake account + WalletCommand::DeactivateStake(staking_account_keypair) => { + process_deactivate_stake_account(&rpc_client, config, &staking_account_keypair) + } + WalletCommand::RedeemVoteCredits(staking_account_pubkey, voting_account_pubkey) => { process_redeem_vote_credits( &rpc_client, @@ -1452,6 +1481,18 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .help("The number of lamports to stake, must be less than the stake account's balance."), ), ) + .subcommand( + SubCommand::with_name("deactivate-stake") + .about("Deactivate the delegated stake from the staking account") + .arg( + Arg::with_name("staking_account_keypair_file") + .index(1) + .value_name("KEYPAIR_FILE") + .takes_value(true) + .required(true) + .help("Keypair file for the staking account, for signing the delegate transaction."), + ) + ) .subcommand( SubCommand::with_name("withdraw-stake") .about("Withdraw the unstaked lamports from the stake account") @@ -1933,6 +1974,19 @@ mod tests { WalletCommand::WithdrawStake(keypair, pubkey, 42) ); + // Test Deactivate Stake Subcommand + let keypair_file = make_tmp_path("keypair_file"); + gen_keypair_file(&keypair_file).unwrap(); + let keypair = read_keypair(&keypair_file).unwrap(); + let test_deactivate_stake = + test_commands + .clone() + .get_matches_from(vec!["test", "deactivate-stake", &keypair_file]); + assert_eq!( + parse_command(&pubkey, &test_deactivate_stake).unwrap(), + WalletCommand::DeactivateStake(keypair) + ); + // Test Deploy Subcommand let test_deploy = test_commands @@ -2108,6 +2162,11 @@ mod tests { let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); + let bob_keypair = Keypair::new(); + config.command = WalletCommand::DeactivateStake(bob_keypair.into()); + let signature = process_command(&config); + assert_eq!(signature.unwrap(), SIGNATURE.to_string()); + config.command = WalletCommand::GetTransactionCount; assert_eq!(process_command(&config).unwrap(), "1234");