From a18c0e34f41e3ccf6ceec43b3e3b8f6517e48928 Mon Sep 17 00:00:00 2001 From: Rob Walker Date: Mon, 10 Jun 2019 12:17:29 -0700 Subject: [PATCH] add activate_stake to stake_api (#4600) --- core/src/local_cluster.rs | 24 ++--- core/src/staking_utils.rs | 14 +-- genesis/src/main.rs | 2 +- multinode-demo/fullnode.sh | 2 +- programs/stake_api/src/stake_instruction.rs | 74 +++++++------ programs/stake_api/src/stake_state.rs | 83 +++++++++----- runtime/src/genesis_utils.rs | 2 +- runtime/src/stakes.rs | 2 +- sdk/src/instruction.rs | 3 + wallet/src/wallet.rs | 114 ++++++++++++-------- 10 files changed, 185 insertions(+), 135 deletions(-) diff --git a/core/src/local_cluster.rs b/core/src/local_cluster.rs index 1871e931f..bc223fb67 100644 --- a/core/src/local_cluster.rs +++ b/core/src/local_cluster.rs @@ -428,31 +428,16 @@ impl LocalCluster { let stake_account_keypair = Keypair::new(); let stake_account_pubkey = stake_account_keypair.pubkey(); let mut transaction = Transaction::new_signed_instructions( - &[from_account.as_ref()], - stake_instruction::create_delegate_account( + &[from_account.as_ref(), &stake_account_keypair], + stake_instruction::create_stake_account_and_delegate_stake( &from_account.pubkey(), &stake_account_pubkey, + &vote_account_pubkey, amount, ), client.get_recent_blockhash().unwrap().0, ); - client - .retry_transfer(&from_account, &mut transaction, 5) - .expect("fund stake"); - client - .wait_for_balance(&stake_account_pubkey, Some(amount)) - .expect("get balance"); - - let mut transaction = Transaction::new_signed_with_payer( - vec![stake_instruction::delegate_stake( - &stake_account_pubkey, - &vote_account_pubkey, - )], - Some(&from_account.pubkey()), - &[from_account.as_ref(), &stake_account_keypair], - client.get_recent_blockhash().unwrap().0, - ); client .send_and_confirm_transaction( &[from_account.as_ref(), &stake_account_keypair], @@ -461,6 +446,9 @@ impl LocalCluster { 0, ) .expect("delegate stake"); + client + .wait_for_balance(&stake_account_pubkey, Some(amount)) + .expect("get balance"); } info!("Checking for vote account registration"); let vote_account_user_data = client.get_account_data(&vote_account_pubkey); diff --git a/core/src/staking_utils.rs b/core/src/staking_utils.rs index ed0a6d72e..4275cd265 100644 --- a/core/src/staking_utils.rs +++ b/core/src/staking_utils.rs @@ -193,22 +193,14 @@ pub(crate) mod tests { process_instructions( bank, - &[from_account], - stake_instruction::create_delegate_account( + &[from_account, &stake_account_keypair], + stake_instruction::create_stake_account_and_delegate_stake( &from_account.pubkey(), &stake_account_pubkey, + vote_pubkey, amount, ), ); - - process_instructions( - bank, - &[from_account, &stake_account_keypair], - vec![stake_instruction::delegate_stake( - &stake_account_pubkey, - vote_pubkey, - )], - ); } #[test] diff --git a/genesis/src/main.rs b/genesis/src/main.rs index a9cbabefa..0b7cc1d3e 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -245,7 +245,7 @@ fn main() -> Result<(), Box> { // passive bootstrap leader stake ( bootstrap_stake_keypair.pubkey(), - stake_state::create_delegate_stake_account( + stake_state::create_stake_account( &bootstrap_vote_keypair.pubkey(), &vote_state, bootstrap_leader_stake_lamports, diff --git a/multinode-demo/fullnode.sh b/multinode-demo/fullnode.sh index bbfc07741..6f6b11c3d 100755 --- a/multinode-demo/fullnode.sh +++ b/multinode-demo/fullnode.sh @@ -112,7 +112,7 @@ setup_validator_accounts() { # Delegate the stake. The transaction fee is paid by the node but the # transaction must be signed by the stake_keypair $solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \ - delegate-stake "$stake_keypair_path" "$vote_pubkey" || return $? + delegate-stake "$stake_keypair_path" "$vote_pubkey" "$stake_lamports" || return $? # Setup validator storage account $solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \ diff --git a/programs/stake_api/src/stake_instruction.rs b/programs/stake_api/src/stake_instruction.rs index 6dd248b2c..16c83dd97 100644 --- a/programs/stake_api/src/stake_instruction.rs +++ b/programs/stake_api/src/stake_instruction.rs @@ -10,39 +10,40 @@ use solana_sdk::system_instruction; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub enum StakeInstruction { - /// Initialize the stake account as a Delegate account. + /// Initialize the stake account as a Stake account. /// - /// Expects 2 Accounts: - /// 0 - payer (TODO unused/remove) - /// 1 - Delegate StakeAccount to be initialized - InitializeDelegate, + /// Expects 1 Accounts: + /// 0 - StakeAccount to be initialized + InitializeStake, // Initialize the stake account as a MiningPool account /// - /// Expects 2 Accounts: - /// 0 - payer (TODO unused/remove) - /// 1 - MiningPool StakeAccount to be initialized + /// Expects 1 Accounts: + /// 0 - MiningPool StakeAccount to be initialized InitializeMiningPool, - /// `Delegate` or `Assign` a stake account to a particular node + /// `Delegate` a stake to a particular node /// - /// Expects 3 Accounts: - /// 0 - payer (TODO unused/remove) - /// 1 - Delegate StakeAccount to be updated - /// 2 - VoteAccount to which this Stake will be delegated - DelegateStake, + /// Expects 2 Accounts: + /// 0 - Delegate StakeAccount to be updated <= must have this signature + /// 1 - VoteAccount to which this Stake will be delegated + /// + /// The u64 is the portion of the Stake account balance to be activated, + /// must be less than StakeAccount.lamports + /// + /// This instruction resets rewards, so issue + DelegateStake(u64), /// Redeem credits in the stake account /// - /// Expects 4 Accounts: - /// 0 - payer (TODO unused/remove) - /// 1 - MiningPool Stake Account to redeem credits from - /// 2 - Delegate StakeAccount to be updated - /// 3 - VoteAccount to which the Stake is delegated + /// Expects 3 Accounts: + /// 0 - MiningPool Stake Account to redeem credits from + /// 1 - Delegate StakeAccount to be updated + /// 2 - VoteAccount to which the Stake is delegated, RedeemVoteCredits, } -pub fn create_delegate_account( +pub fn create_stake_account( from_pubkey: &Pubkey, staker_pubkey: &Pubkey, lamports: u64, @@ -57,12 +58,23 @@ pub fn create_delegate_account( ), Instruction::new( id(), - &StakeInstruction::InitializeDelegate, + &StakeInstruction::InitializeStake, vec![AccountMeta::new(*staker_pubkey, false)], ), ] } +pub fn create_stake_account_and_delegate_stake( + from_pubkey: &Pubkey, + staker_pubkey: &Pubkey, + vote_pubkey: &Pubkey, + lamports: u64, +) -> Vec { + let mut instructions = create_stake_account(from_pubkey, staker_pubkey, lamports); + instructions.push(delegate_stake(staker_pubkey, vote_pubkey, lamports)); + instructions +} + pub fn create_mining_pool_account( from_pubkey: &Pubkey, staker_pubkey: &Pubkey, @@ -97,12 +109,12 @@ pub fn redeem_vote_credits( Instruction::new(id(), &StakeInstruction::RedeemVoteCredits, account_metas) } -pub fn delegate_stake(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instruction { +pub fn delegate_stake(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey, stake: u64) -> Instruction { let account_metas = vec![ AccountMeta::new(*stake_pubkey, true), AccountMeta::new(*vote_pubkey, false), ]; - Instruction::new(id(), &StakeInstruction::DelegateStake, account_metas) + Instruction::new(id(), &StakeInstruction::DelegateStake(stake), account_metas) } pub fn process_instruction( @@ -130,18 +142,18 @@ pub fn process_instruction( } me.initialize_mining_pool() } - StakeInstruction::InitializeDelegate => { + StakeInstruction::InitializeStake => { if !rest.is_empty() { Err(InstructionError::InvalidInstructionData)?; } - me.initialize_delegate() + me.initialize_stake() } - StakeInstruction::DelegateStake => { + StakeInstruction::DelegateStake(stake) => { if rest.len() != 1 { Err(InstructionError::InvalidInstructionData)?; } let vote = &rest[0]; - me.delegate_stake(vote) + me.delegate_stake(vote, stake) } StakeInstruction::RedeemVoteCredits => { if rest.len() != 2 { @@ -189,7 +201,7 @@ mod tests { Err(InstructionError::InvalidAccountData), ); assert_eq!( - process_instruction(&delegate_stake(&Pubkey::default(), &Pubkey::default())), + process_instruction(&delegate_stake(&Pubkey::default(), &Pubkey::default(), 0)), Err(InstructionError::InvalidAccountData), ); } @@ -207,7 +219,7 @@ mod tests { false, &mut Account::default(), )], - &serialize(&StakeInstruction::DelegateStake).unwrap(), + &serialize(&StakeInstruction::DelegateStake(0)).unwrap(), ), Err(InstructionError::InvalidInstructionData), ); @@ -221,7 +233,7 @@ mod tests { false, &mut Account::default() ),], - &serialize(&StakeInstruction::DelegateStake).unwrap(), + &serialize(&StakeInstruction::DelegateStake(0)).unwrap(), ), Err(InstructionError::InvalidInstructionData), ); @@ -246,7 +258,7 @@ mod tests { KeyedAccount::new(&Pubkey::default(), true, &mut Account::default()), KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()), ], - &serialize(&StakeInstruction::DelegateStake).unwrap(), + &serialize(&StakeInstruction::DelegateStake(0)).unwrap(), ), Err(InstructionError::InvalidAccountData), ); diff --git a/programs/stake_api/src/stake_state.rs b/programs/stake_api/src/stake_state.rs index 7751542f4..4e718adf8 100644 --- a/programs/stake_api/src/stake_state.rs +++ b/programs/stake_api/src/stake_state.rs @@ -14,9 +14,10 @@ use solana_vote_api::vote_state::VoteState; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub enum StakeState { Uninitialized, - Delegate { + Stake { voter_pubkey: Pubkey, credits_observed: u64, + stake: u64, }, MiningPool, } @@ -52,7 +53,7 @@ impl StakeState { pub fn voter_pubkey(&self) -> Option { match self { - StakeState::Delegate { voter_pubkey, .. } => Some(*voter_pubkey), + StakeState::Stake { voter_pubkey, .. } => Some(*voter_pubkey), _ => None, } } @@ -89,8 +90,12 @@ impl StakeState { pub trait StakeAccount { fn initialize_mining_pool(&mut self) -> Result<(), InstructionError>; - fn initialize_delegate(&mut self) -> Result<(), InstructionError>; - fn delegate_stake(&mut self, vote_account: &KeyedAccount) -> Result<(), InstructionError>; + fn initialize_stake(&mut self) -> Result<(), InstructionError>; + fn delegate_stake( + &mut self, + vote_account: &KeyedAccount, + stake: u64, + ) -> Result<(), InstructionError>; fn redeem_vote_credits( &mut self, stake_account: &mut KeyedAccount, @@ -106,26 +111,35 @@ impl<'a> StakeAccount for KeyedAccount<'a> { Err(InstructionError::InvalidAccountData) } } - fn initialize_delegate(&mut self) -> Result<(), InstructionError> { + fn initialize_stake(&mut self) -> Result<(), InstructionError> { if let StakeState::Uninitialized = self.state()? { - self.set_state(&StakeState::Delegate { + self.set_state(&StakeState::Stake { voter_pubkey: Pubkey::default(), credits_observed: 0, + stake: 0, }) } else { Err(InstructionError::InvalidAccountData) } } - fn delegate_stake(&mut self, vote_account: &KeyedAccount) -> Result<(), InstructionError> { + fn delegate_stake( + &mut self, + vote_account: &KeyedAccount, + stake: u64, + ) -> Result<(), InstructionError> { if self.signer_key().is_none() { return Err(InstructionError::MissingRequiredSignature); } + if stake > self.account.lamports { + return Err(InstructionError::InsufficientFunds); + } - if let StakeState::Delegate { .. } = self.state()? { + if let StakeState::Stake { .. } = self.state()? { let vote_state: VoteState = vote_account.state()?; - self.set_state(&StakeState::Delegate { + self.set_state(&StakeState::Stake { voter_pubkey: *vote_account.unsigned_key(), credits_observed: vote_state.credits(), + stake, }) } else { Err(InstructionError::InvalidAccountData) @@ -139,9 +153,10 @@ impl<'a> StakeAccount for KeyedAccount<'a> { ) -> Result<(), InstructionError> { if let ( StakeState::MiningPool, - StakeState::Delegate { + StakeState::Stake { voter_pubkey, credits_observed, + .. }, ) = (self.state()?, stake_account.state()?) { @@ -167,9 +182,10 @@ impl<'a> StakeAccount for KeyedAccount<'a> { stake_account.account.lamports += stakers_reward; vote_account.account.lamports += voters_reward; - stake_account.set_state(&StakeState::Delegate { + stake_account.set_state(&StakeState::Stake { voter_pubkey, credits_observed: vote_state.credits(), + stake: 0, }) } else { // not worth collecting @@ -182,7 +198,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> { } // utility function, used by Bank, tests, genesis -pub fn create_delegate_stake_account( +pub fn create_stake_account( voter_pubkey: &Pubkey, vote_state: &VoteState, lamports: u64, @@ -190,9 +206,10 @@ pub fn create_delegate_stake_account( let mut stake_account = Account::new(lamports, std::mem::size_of::(), &id()); stake_account - .set_state(&StakeState::Delegate { + .set_state(&StakeState::Stake { voter_pubkey: *voter_pubkey, credits_observed: vote_state.credits(), + stake: lamports, }) .expect("set_state"); @@ -223,7 +240,9 @@ mod tests { vote_keyed_account.set_state(&vote_state).unwrap(); let stake_pubkey = Pubkey::default(); - let mut stake_account = Account::new(0, std::mem::size_of::(), &id()); + let stake_lamports = 42; + let mut stake_account = + Account::new(stake_lamports, std::mem::size_of::(), &id()); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account); @@ -232,37 +251,45 @@ mod tests { assert_eq!(stake_state, StakeState::default()); } - stake_keyed_account.initialize_delegate().unwrap(); + stake_keyed_account.initialize_stake().unwrap(); assert_eq!( - stake_keyed_account.delegate_stake(&vote_keyed_account), + stake_keyed_account.delegate_stake(&vote_keyed_account, 0), 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) + .delegate_stake(&vote_keyed_account, stake_lamports) .is_ok()); - // verify that create_delegate_stake_account() matches the + // verify can only stake up to account lamports + let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); + assert_eq!( + stake_keyed_account.delegate_stake(&vote_keyed_account, stake_lamports + 1), + Err(InstructionError::InsufficientFunds) + ); + + // verify that create_stake_account() matches the // resulting account from delegate_stake() assert_eq!( - create_delegate_stake_account(&vote_pubkey, &vote_state, 0), + create_stake_account(&vote_pubkey, &vote_state, stake_lamports), *stake_keyed_account.account, ); let stake_state: StakeState = stake_keyed_account.state().unwrap(); assert_eq!( stake_state, - StakeState::Delegate { + StakeState::Stake { voter_pubkey: vote_keypair.pubkey(), - credits_observed: vote_state.credits() + credits_observed: vote_state.credits(), + stake: stake_lamports, } ); let stake_state = StakeState::MiningPool; stake_keyed_account.set_state(&stake_state).unwrap(); assert!(stake_keyed_account - .delegate_stake(&vote_keyed_account) + .delegate_stake(&vote_keyed_account, 0) .is_err()); } #[test] @@ -334,11 +361,11 @@ mod tests { &id(), ); let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account); - stake_keyed_account.initialize_delegate().unwrap(); + stake_keyed_account.initialize_stake().unwrap(); // delegate the stake assert!(stake_keyed_account - .delegate_stake(&vote_keyed_account) + .delegate_stake(&vote_keyed_account, STAKE_GETS_PAID_EVERY_VOTE) .is_ok()); let mut mining_pool_account = Account::new(0, std::mem::size_of::(), &id()); @@ -402,13 +429,15 @@ mod tests { vote_keyed_account.set_state(&vote_state).unwrap(); let pubkey = Pubkey::default(); - let mut stake_account = Account::new(0, std::mem::size_of::(), &id()); + let stake_lamports = 0; + let mut stake_account = + Account::new(stake_lamports, std::mem::size_of::(), &id()); let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account); - stake_keyed_account.initialize_delegate().unwrap(); + stake_keyed_account.initialize_stake().unwrap(); // delegate the stake assert!(stake_keyed_account - .delegate_stake(&vote_keyed_account) + .delegate_stake(&vote_keyed_account, stake_lamports) .is_ok()); let mut mining_pool_account = Account::new(0, std::mem::size_of::(), &id()); diff --git a/runtime/src/genesis_utils.rs b/runtime/src/genesis_utils.rs index 61ac26300..403c99081 100644 --- a/runtime/src/genesis_utils.rs +++ b/runtime/src/genesis_utils.rs @@ -53,7 +53,7 @@ pub fn create_genesis_block_with_leader( // passive bootstrap leader stake, duplicates above temporarily ( staking_keypair.pubkey(), - stake_state::create_delegate_stake_account( + stake_state::create_stake_account( &voting_keypair.pubkey(), &vote_state, bootstrap_leader_stake_lamports, diff --git a/runtime/src/stakes.rs b/runtime/src/stakes.rs index 541d87921..e7772733f 100644 --- a/runtime/src/stakes.rs +++ b/runtime/src/stakes.rs @@ -100,7 +100,7 @@ mod tests { fn create_stake_account(stake: u64, vote_pubkey: &Pubkey) -> (Pubkey, Account) { ( Pubkey::new_rand(), - stake_state::create_delegate_stake_account(&vote_pubkey, &VoteState::default(), stake), + stake_state::create_stake_account(&vote_pubkey, &VoteState::default(), stake), ) } diff --git a/sdk/src/instruction.rs b/sdk/src/instruction.rs index 5bfa50e6d..5dcd23225 100644 --- a/sdk/src/instruction.rs +++ b/sdk/src/instruction.rs @@ -25,6 +25,9 @@ pub enum InstructionError { /// An account's data was too small AccountDataTooSmall, + /// An account's balance was too small to complete the instruction + InsufficientFunds, + /// The account did not have the expected program id IncorrectProgramId, diff --git a/wallet/src/wallet.rs b/wallet/src/wallet.rs index cf5509d03..873484f7c 100644 --- a/wallet/src/wallet.rs +++ b/wallet/src/wallet.rs @@ -52,7 +52,7 @@ pub enum WalletCommand { ShowVoteAccount(Pubkey), CreateStakeAccount(Pubkey, u64), CreateMiningPoolAccount(Pubkey, u64), - DelegateStake(Keypair, Pubkey), + DelegateStake(Keypair, Pubkey, u64), RedeemVoteCredits(Pubkey, Pubkey, Pubkey), ShowStakeAccount(Pubkey), CreateStorageMiningPoolAccount(Pubkey, u64), @@ -142,16 +142,26 @@ impl WalletConfig { } } -// Return the pubkey for an argument with `name` or None if not present. -fn pubkey_of(matches: &ArgMatches<'_>, name: &str) -> Option { - matches.value_of(name).map(|x| x.parse::().unwrap()) -} - -// Return the pubkeys for arguments with `name` or None if none present. -fn pubkeys_of(matches: &ArgMatches<'_>, name: &str) -> Option> { +// Return parsed values from matches at `name` +fn values_of(matches: &ArgMatches<'_>, name: &str) -> Option> +where + T: std::str::FromStr, + ::Err: std::fmt::Debug, +{ matches .values_of(name) - .map(|xs| xs.map(|x| x.parse::().unwrap()).collect()) + .map(|xs| xs.map(|x| x.parse::().unwrap()).collect()) +} + +// Return a parsed value from matches at `name` +fn value_of(matches: &ArgMatches<'_>, name: &str) -> Option +where + T: std::str::FromStr, + ::Err: std::fmt::Debug, +{ + matches + .value_of(name) + .map(|value| value.parse::().unwrap()) } // Return the keypair for an argument with filename `name` or None if not present. @@ -171,11 +181,11 @@ pub fn parse_command( Ok(WalletCommand::Airdrop(lamports)) } ("balance", Some(balance_matches)) => { - let pubkey = pubkey_of(&balance_matches, "pubkey").unwrap_or(*pubkey); + let pubkey = value_of(&balance_matches, "pubkey").unwrap_or(*pubkey); Ok(WalletCommand::Balance(pubkey)) } ("cancel", Some(cancel_matches)) => { - let process_id = pubkey_of(cancel_matches, "process_id").unwrap(); + let process_id = value_of(cancel_matches, "process_id").unwrap(); Ok(WalletCommand::Cancel(process_id)) } ("confirm", Some(confirm_matches)) => { @@ -188,8 +198,8 @@ pub fn parse_command( } } ("create-vote-account", Some(matches)) => { - let voting_account_pubkey = pubkey_of(matches, "voting_account_pubkey").unwrap(); - let node_pubkey = pubkey_of(matches, "node_pubkey").unwrap(); + let voting_account_pubkey = value_of(matches, "voting_account_pubkey").unwrap(); + let node_pubkey = value_of(matches, "node_pubkey").unwrap(); let commission = if let Some(commission) = matches.value_of("commission") { commission.parse()? } else { @@ -204,11 +214,11 @@ pub fn parse_command( )) } ("authorize-voter", Some(matches)) => { - let voting_account_pubkey = pubkey_of(matches, "voting_account_pubkey").unwrap(); + let voting_account_pubkey = value_of(matches, "voting_account_pubkey").unwrap(); let authorized_voter_keypair = keypair_of(matches, "authorized_voter_keypair_file").unwrap(); let new_authorized_voter_pubkey = - pubkey_of(matches, "new_authorized_voter_pubkey").unwrap(); + value_of(matches, "new_authorized_voter_pubkey").unwrap(); Ok(WalletCommand::AuthorizeVoter( voting_account_pubkey, @@ -217,11 +227,11 @@ pub fn parse_command( )) } ("show-vote-account", Some(matches)) => { - let voting_account_pubkey = pubkey_of(matches, "voting_account_pubkey").unwrap(); + let voting_account_pubkey = value_of(matches, "voting_account_pubkey").unwrap(); Ok(WalletCommand::ShowVoteAccount(voting_account_pubkey)) } ("create-stake-account", Some(matches)) => { - let staking_account_pubkey = pubkey_of(matches, "staking_account_pubkey").unwrap(); + let staking_account_pubkey = value_of(matches, "staking_account_pubkey").unwrap(); let lamports = matches.value_of("lamports").unwrap().parse()?; Ok(WalletCommand::CreateStakeAccount( staking_account_pubkey, @@ -230,7 +240,7 @@ pub fn parse_command( } ("create-mining-pool-account", Some(matches)) => { let mining_pool_account_pubkey = - pubkey_of(matches, "mining_pool_account_pubkey").unwrap(); + value_of(matches, "mining_pool_account_pubkey").unwrap(); let lamports = matches.value_of("lamports").unwrap().parse()?; Ok(WalletCommand::CreateMiningPoolAccount( mining_pool_account_pubkey, @@ -240,17 +250,19 @@ pub fn parse_command( ("delegate-stake", Some(matches)) => { let staking_account_keypair = keypair_of(matches, "staking_account_keypair_file").unwrap(); - let voting_account_pubkey = pubkey_of(matches, "voting_account_pubkey").unwrap(); + let voting_account_pubkey = value_of(matches, "voting_account_pubkey").unwrap(); + let stake = matches.value_of("stake").unwrap().parse()?; Ok(WalletCommand::DelegateStake( staking_account_keypair, voting_account_pubkey, + stake, )) } ("redeem-vote-credits", Some(matches)) => { let mining_pool_account_pubkey = - pubkey_of(matches, "mining_pool_account_pubkey").unwrap(); - let staking_account_pubkey = pubkey_of(matches, "staking_account_pubkey").unwrap(); - let voting_account_pubkey = pubkey_of(matches, "voting_account_pubkey").unwrap(); + value_of(matches, "mining_pool_account_pubkey").unwrap(); + let staking_account_pubkey = value_of(matches, "staking_account_pubkey").unwrap(); + let voting_account_pubkey = value_of(matches, "voting_account_pubkey").unwrap(); Ok(WalletCommand::RedeemVoteCredits( mining_pool_account_pubkey, staking_account_pubkey, @@ -258,12 +270,12 @@ pub fn parse_command( )) } ("show-stake-account", Some(matches)) => { - let staking_account_pubkey = pubkey_of(matches, "staking_account_pubkey").unwrap(); + let staking_account_pubkey = value_of(matches, "staking_account_pubkey").unwrap(); Ok(WalletCommand::ShowStakeAccount(staking_account_pubkey)) } ("create-storage-mining-pool-account", Some(matches)) => { let storage_mining_pool_account_pubkey = - pubkey_of(matches, "storage_mining_pool_account_pubkey").unwrap(); + value_of(matches, "storage_mining_pool_account_pubkey").unwrap(); let lamports = matches.value_of("lamports").unwrap().parse()?; Ok(WalletCommand::CreateStorageMiningPoolAccount( storage_mining_pool_account_pubkey, @@ -271,16 +283,16 @@ pub fn parse_command( )) } ("create-replicator-storage-account", Some(matches)) => { - let account_owner = pubkey_of(matches, "storage_account_owner").unwrap(); - let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap(); + let account_owner = value_of(matches, "storage_account_owner").unwrap(); + let storage_account_pubkey = value_of(matches, "storage_account_pubkey").unwrap(); Ok(WalletCommand::CreateReplicatorStorageAccount( account_owner, storage_account_pubkey, )) } ("create-validator-storage-account", Some(matches)) => { - let account_owner = pubkey_of(matches, "storage_account_owner").unwrap(); - let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap(); + let account_owner = value_of(matches, "storage_account_owner").unwrap(); + let storage_account_pubkey = value_of(matches, "storage_account_pubkey").unwrap(); Ok(WalletCommand::CreateValidatorStorageAccount( account_owner, storage_account_pubkey, @@ -288,8 +300,8 @@ pub fn parse_command( } ("claim-storage-reward", Some(matches)) => { let storage_mining_pool_account_pubkey = - pubkey_of(matches, "storage_mining_pool_account_pubkey").unwrap(); - let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap(); + value_of(matches, "storage_mining_pool_account_pubkey").unwrap(); + let storage_account_pubkey = value_of(matches, "storage_account_pubkey").unwrap(); let slot = matches.value_of("slot").unwrap().parse()?; Ok(WalletCommand::ClaimStorageReward( storage_mining_pool_account_pubkey, @@ -298,7 +310,7 @@ pub fn parse_command( )) } ("show-storage-account", Some(matches)) => { - let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap(); + let storage_account_pubkey = value_of(matches, "storage_account_pubkey").unwrap(); Ok(WalletCommand::ShowStorageAccount(storage_account_pubkey)) } ("deploy", Some(deploy_matches)) => Ok(WalletCommand::Deploy( @@ -310,7 +322,7 @@ pub fn parse_command( ("get-transaction-count", Some(_matches)) => Ok(WalletCommand::GetTransactionCount), ("pay", Some(pay_matches)) => { let lamports = pay_matches.value_of("lamports").unwrap().parse()?; - let to = pubkey_of(&pay_matches, "to").unwrap_or(*pubkey); + let to = value_of(&pay_matches, "to").unwrap_or(*pubkey); let timestamp = if pay_matches.is_present("timestamp") { // Parse input for serde_json let date_string = if !pay_matches.value_of("timestamp").unwrap().contains('Z') { @@ -322,8 +334,8 @@ pub fn parse_command( } else { None }; - let timestamp_pubkey = pubkey_of(&pay_matches, "timestamp_pubkey"); - let witness_vec = pubkeys_of(&pay_matches, "witness"); + let timestamp_pubkey = value_of(&pay_matches, "timestamp_pubkey"); + let witness_vec = values_of(&pay_matches, "witness"); let cancelable = if pay_matches.is_present("cancelable") { Some(*pubkey) } else { @@ -340,13 +352,13 @@ pub fn parse_command( )) } ("send-signature", Some(sig_matches)) => { - let to = pubkey_of(&sig_matches, "to").unwrap(); - let process_id = pubkey_of(&sig_matches, "process_id").unwrap(); + let to = value_of(&sig_matches, "to").unwrap(); + let process_id = value_of(&sig_matches, "process_id").unwrap(); Ok(WalletCommand::Witness(to, process_id)) } ("send-timestamp", Some(timestamp_matches)) => { - let to = pubkey_of(×tamp_matches, "to").unwrap(); - let process_id = pubkey_of(×tamp_matches, "process_id").unwrap(); + let to = value_of(×tamp_matches, "to").unwrap(); + let process_id = value_of(×tamp_matches, "process_id").unwrap(); let dt = if timestamp_matches.is_present("datetime") { // Parse input for serde_json let date_string = if !timestamp_matches @@ -553,7 +565,7 @@ fn process_create_stake_account( lamports: u64, ) -> ProcessResult { let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?; - let ixs = stake_instruction::create_delegate_account( + let ixs = stake_instruction::create_stake_account( &config.keypair.pubkey(), staking_account_pubkey, lamports, @@ -595,11 +607,13 @@ fn process_delegate_stake( config: &WalletConfig, staking_account_keypair: &Keypair, voting_account_pubkey: &Pubkey, + stake: u64, ) -> ProcessResult { let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?; let ixs = vec![stake_instruction::delegate_stake( &staking_account_keypair.pubkey(), voting_account_pubkey, + stake, )]; let mut tx = Transaction::new_signed_with_payer( @@ -645,13 +659,15 @@ fn process_show_stake_account( use solana_stake_api::stake_state::StakeState; let stake_account = rpc_client.get_account(staking_account_pubkey)?; match stake_account.state() { - Ok(StakeState::Delegate { + Ok(StakeState::Stake { voter_pubkey, credits_observed, + stake, }) => { println!("account lamports: {}", stake_account.lamports); println!("voter pubkey: {}", voter_pubkey); println!("credits observed: {}", credits_observed); + println!("activated stake: {}", stake); Ok("".to_string()) } Ok(StakeState::MiningPool) => { @@ -1059,12 +1075,13 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult { ) } - WalletCommand::DelegateStake(staking_account_keypair, voting_account_pubkey) => { + WalletCommand::DelegateStake(staking_account_keypair, voting_account_pubkey, lamports) => { process_delegate_stake( &rpc_client, config, &staking_account_keypair, &voting_account_pubkey, + *lamports, ) } @@ -1460,7 +1477,15 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .takes_value(true) .required(true) .validator(is_pubkey) - .help("The voting account to which to delegate the stake."), + .help("The voting account to which the stake will be delegated"), + ) + .arg( + Arg::with_name("stake") + .index(3) + .value_name("NUM") + .takes_value(true) + .required(true) + .help("The number of lamports to stake, must be less than the stake account's balance."), ), ) .subcommand( @@ -1891,10 +1916,11 @@ mod tests { "delegate-stake", &keypair_file, &pubkey_string, + "42", ]); assert_eq!( parse_command(&pubkey, &test_delegate_stake).unwrap(), - WalletCommand::DelegateStake(keypair, pubkey) + WalletCommand::DelegateStake(keypair, pubkey, 42) ); // Test Deploy Subcommand @@ -2062,7 +2088,7 @@ mod tests { let bob_keypair = Keypair::new(); let node_pubkey = Pubkey::new_rand(); - config.command = WalletCommand::DelegateStake(bob_keypair.into(), node_pubkey); + config.command = WalletCommand::DelegateStake(bob_keypair.into(), node_pubkey, 100); let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string());