add activate_stake to stake_api (#4600)

This commit is contained in:
Rob Walker 2019-06-10 12:17:29 -07:00 committed by GitHub
parent be3a0b6b10
commit a18c0e34f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 185 additions and 135 deletions

View File

@ -428,31 +428,16 @@ impl LocalCluster {
let stake_account_keypair = Keypair::new(); let stake_account_keypair = Keypair::new();
let stake_account_pubkey = stake_account_keypair.pubkey(); let stake_account_pubkey = stake_account_keypair.pubkey();
let mut transaction = Transaction::new_signed_instructions( let mut transaction = Transaction::new_signed_instructions(
&[from_account.as_ref()], &[from_account.as_ref(), &stake_account_keypair],
stake_instruction::create_delegate_account( stake_instruction::create_stake_account_and_delegate_stake(
&from_account.pubkey(), &from_account.pubkey(),
&stake_account_pubkey, &stake_account_pubkey,
&vote_account_pubkey,
amount, amount,
), ),
client.get_recent_blockhash().unwrap().0, 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 client
.send_and_confirm_transaction( .send_and_confirm_transaction(
&[from_account.as_ref(), &stake_account_keypair], &[from_account.as_ref(), &stake_account_keypair],
@ -461,6 +446,9 @@ impl LocalCluster {
0, 0,
) )
.expect("delegate stake"); .expect("delegate stake");
client
.wait_for_balance(&stake_account_pubkey, Some(amount))
.expect("get balance");
} }
info!("Checking for vote account registration"); info!("Checking for vote account registration");
let vote_account_user_data = client.get_account_data(&vote_account_pubkey); let vote_account_user_data = client.get_account_data(&vote_account_pubkey);

View File

@ -193,22 +193,14 @@ pub(crate) mod tests {
process_instructions( process_instructions(
bank, bank,
&[from_account], &[from_account, &stake_account_keypair],
stake_instruction::create_delegate_account( stake_instruction::create_stake_account_and_delegate_stake(
&from_account.pubkey(), &from_account.pubkey(),
&stake_account_pubkey, &stake_account_pubkey,
vote_pubkey,
amount, amount,
), ),
); );
process_instructions(
bank,
&[from_account, &stake_account_keypair],
vec![stake_instruction::delegate_stake(
&stake_account_pubkey,
vote_pubkey,
)],
);
} }
#[test] #[test]

View File

@ -245,7 +245,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
// passive bootstrap leader stake // passive bootstrap leader stake
( (
bootstrap_stake_keypair.pubkey(), bootstrap_stake_keypair.pubkey(),
stake_state::create_delegate_stake_account( stake_state::create_stake_account(
&bootstrap_vote_keypair.pubkey(), &bootstrap_vote_keypair.pubkey(),
&vote_state, &vote_state,
bootstrap_leader_stake_lamports, bootstrap_leader_stake_lamports,

View File

@ -112,7 +112,7 @@ setup_validator_accounts() {
# Delegate the stake. The transaction fee is paid by the node but the # Delegate the stake. The transaction fee is paid by the node but the
# transaction must be signed by the stake_keypair # transaction must be signed by the stake_keypair
$solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \ $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 # Setup validator storage account
$solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \ $solana_wallet --keypair "$node_keypair_path" --url "http://$entrypoint_ip:8899" \

View File

@ -10,39 +10,40 @@ use solana_sdk::system_instruction;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum StakeInstruction { pub enum StakeInstruction {
/// Initialize the stake account as a Delegate account. /// Initialize the stake account as a Stake account.
/// ///
/// Expects 2 Accounts: /// Expects 1 Accounts:
/// 0 - payer (TODO unused/remove) /// 0 - StakeAccount to be initialized
/// 1 - Delegate StakeAccount to be initialized InitializeStake,
InitializeDelegate,
// Initialize the stake account as a MiningPool account // Initialize the stake account as a MiningPool account
/// ///
/// Expects 2 Accounts: /// Expects 1 Accounts:
/// 0 - payer (TODO unused/remove) /// 0 - MiningPool StakeAccount to be initialized
/// 1 - MiningPool StakeAccount to be initialized
InitializeMiningPool, InitializeMiningPool,
/// `Delegate` or `Assign` a stake account to a particular node /// `Delegate` a stake to a particular node
/// ///
/// Expects 3 Accounts: /// Expects 2 Accounts:
/// 0 - payer (TODO unused/remove) /// 0 - Delegate StakeAccount to be updated <= must have this signature
/// 1 - Delegate StakeAccount to be updated /// 1 - VoteAccount to which this Stake will be delegated
/// 2 - VoteAccount to which this Stake will be delegated ///
DelegateStake, /// 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 /// Redeem credits in the stake account
/// ///
/// Expects 4 Accounts: /// Expects 3 Accounts:
/// 0 - payer (TODO unused/remove) /// 0 - MiningPool Stake Account to redeem credits from
/// 1 - MiningPool Stake Account to redeem credits from /// 1 - Delegate StakeAccount to be updated
/// 2 - Delegate StakeAccount to be updated /// 2 - VoteAccount to which the Stake is delegated,
/// 3 - VoteAccount to which the Stake is delegated
RedeemVoteCredits, RedeemVoteCredits,
} }
pub fn create_delegate_account( pub fn create_stake_account(
from_pubkey: &Pubkey, from_pubkey: &Pubkey,
staker_pubkey: &Pubkey, staker_pubkey: &Pubkey,
lamports: u64, lamports: u64,
@ -57,12 +58,23 @@ pub fn create_delegate_account(
), ),
Instruction::new( Instruction::new(
id(), id(),
&StakeInstruction::InitializeDelegate, &StakeInstruction::InitializeStake,
vec![AccountMeta::new(*staker_pubkey, false)], 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<Instruction> {
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( pub fn create_mining_pool_account(
from_pubkey: &Pubkey, from_pubkey: &Pubkey,
staker_pubkey: &Pubkey, staker_pubkey: &Pubkey,
@ -97,12 +109,12 @@ pub fn redeem_vote_credits(
Instruction::new(id(), &StakeInstruction::RedeemVoteCredits, account_metas) 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![ let account_metas = vec![
AccountMeta::new(*stake_pubkey, true), AccountMeta::new(*stake_pubkey, true),
AccountMeta::new(*vote_pubkey, false), AccountMeta::new(*vote_pubkey, false),
]; ];
Instruction::new(id(), &StakeInstruction::DelegateStake, account_metas) Instruction::new(id(), &StakeInstruction::DelegateStake(stake), account_metas)
} }
pub fn process_instruction( pub fn process_instruction(
@ -130,18 +142,18 @@ pub fn process_instruction(
} }
me.initialize_mining_pool() me.initialize_mining_pool()
} }
StakeInstruction::InitializeDelegate => { StakeInstruction::InitializeStake => {
if !rest.is_empty() { if !rest.is_empty() {
Err(InstructionError::InvalidInstructionData)?; Err(InstructionError::InvalidInstructionData)?;
} }
me.initialize_delegate() me.initialize_stake()
} }
StakeInstruction::DelegateStake => { StakeInstruction::DelegateStake(stake) => {
if rest.len() != 1 { if rest.len() != 1 {
Err(InstructionError::InvalidInstructionData)?; Err(InstructionError::InvalidInstructionData)?;
} }
let vote = &rest[0]; let vote = &rest[0];
me.delegate_stake(vote) me.delegate_stake(vote, stake)
} }
StakeInstruction::RedeemVoteCredits => { StakeInstruction::RedeemVoteCredits => {
if rest.len() != 2 { if rest.len() != 2 {
@ -189,7 +201,7 @@ mod tests {
Err(InstructionError::InvalidAccountData), Err(InstructionError::InvalidAccountData),
); );
assert_eq!( assert_eq!(
process_instruction(&delegate_stake(&Pubkey::default(), &Pubkey::default())), process_instruction(&delegate_stake(&Pubkey::default(), &Pubkey::default(), 0)),
Err(InstructionError::InvalidAccountData), Err(InstructionError::InvalidAccountData),
); );
} }
@ -207,7 +219,7 @@ mod tests {
false, false,
&mut Account::default(), &mut Account::default(),
)], )],
&serialize(&StakeInstruction::DelegateStake).unwrap(), &serialize(&StakeInstruction::DelegateStake(0)).unwrap(),
), ),
Err(InstructionError::InvalidInstructionData), Err(InstructionError::InvalidInstructionData),
); );
@ -221,7 +233,7 @@ mod tests {
false, false,
&mut Account::default() &mut Account::default()
),], ),],
&serialize(&StakeInstruction::DelegateStake).unwrap(), &serialize(&StakeInstruction::DelegateStake(0)).unwrap(),
), ),
Err(InstructionError::InvalidInstructionData), Err(InstructionError::InvalidInstructionData),
); );
@ -246,7 +258,7 @@ mod tests {
KeyedAccount::new(&Pubkey::default(), true, &mut Account::default()), KeyedAccount::new(&Pubkey::default(), true, &mut Account::default()),
KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()), KeyedAccount::new(&Pubkey::default(), false, &mut Account::default()),
], ],
&serialize(&StakeInstruction::DelegateStake).unwrap(), &serialize(&StakeInstruction::DelegateStake(0)).unwrap(),
), ),
Err(InstructionError::InvalidAccountData), Err(InstructionError::InvalidAccountData),
); );

View File

@ -14,9 +14,10 @@ use solana_vote_api::vote_state::VoteState;
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub enum StakeState { pub enum StakeState {
Uninitialized, Uninitialized,
Delegate { Stake {
voter_pubkey: Pubkey, voter_pubkey: Pubkey,
credits_observed: u64, credits_observed: u64,
stake: u64,
}, },
MiningPool, MiningPool,
} }
@ -52,7 +53,7 @@ impl StakeState {
pub fn voter_pubkey(&self) -> Option<Pubkey> { pub fn voter_pubkey(&self) -> Option<Pubkey> {
match self { match self {
StakeState::Delegate { voter_pubkey, .. } => Some(*voter_pubkey), StakeState::Stake { voter_pubkey, .. } => Some(*voter_pubkey),
_ => None, _ => None,
} }
} }
@ -89,8 +90,12 @@ impl StakeState {
pub trait StakeAccount { pub trait StakeAccount {
fn initialize_mining_pool(&mut self) -> Result<(), InstructionError>; fn initialize_mining_pool(&mut self) -> Result<(), InstructionError>;
fn initialize_delegate(&mut self) -> Result<(), InstructionError>; fn initialize_stake(&mut self) -> Result<(), InstructionError>;
fn delegate_stake(&mut self, vote_account: &KeyedAccount) -> Result<(), InstructionError>; fn delegate_stake(
&mut self,
vote_account: &KeyedAccount,
stake: u64,
) -> Result<(), InstructionError>;
fn redeem_vote_credits( fn redeem_vote_credits(
&mut self, &mut self,
stake_account: &mut KeyedAccount, stake_account: &mut KeyedAccount,
@ -106,26 +111,35 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
} }
} }
fn initialize_delegate(&mut self) -> Result<(), InstructionError> { fn initialize_stake(&mut self) -> Result<(), InstructionError> {
if let StakeState::Uninitialized = self.state()? { if let StakeState::Uninitialized = self.state()? {
self.set_state(&StakeState::Delegate { self.set_state(&StakeState::Stake {
voter_pubkey: Pubkey::default(), voter_pubkey: Pubkey::default(),
credits_observed: 0, credits_observed: 0,
stake: 0,
}) })
} else { } else {
Err(InstructionError::InvalidAccountData) 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() { if self.signer_key().is_none() {
return Err(InstructionError::MissingRequiredSignature); 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()?; let vote_state: VoteState = vote_account.state()?;
self.set_state(&StakeState::Delegate { self.set_state(&StakeState::Stake {
voter_pubkey: *vote_account.unsigned_key(), voter_pubkey: *vote_account.unsigned_key(),
credits_observed: vote_state.credits(), credits_observed: vote_state.credits(),
stake,
}) })
} else { } else {
Err(InstructionError::InvalidAccountData) Err(InstructionError::InvalidAccountData)
@ -139,9 +153,10 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
if let ( if let (
StakeState::MiningPool, StakeState::MiningPool,
StakeState::Delegate { StakeState::Stake {
voter_pubkey, voter_pubkey,
credits_observed, credits_observed,
..
}, },
) = (self.state()?, stake_account.state()?) ) = (self.state()?, stake_account.state()?)
{ {
@ -167,9 +182,10 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
stake_account.account.lamports += stakers_reward; stake_account.account.lamports += stakers_reward;
vote_account.account.lamports += voters_reward; vote_account.account.lamports += voters_reward;
stake_account.set_state(&StakeState::Delegate { stake_account.set_state(&StakeState::Stake {
voter_pubkey, voter_pubkey,
credits_observed: vote_state.credits(), credits_observed: vote_state.credits(),
stake: 0,
}) })
} else { } else {
// not worth collecting // not worth collecting
@ -182,7 +198,7 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
} }
// utility function, used by Bank, tests, genesis // utility function, used by Bank, tests, genesis
pub fn create_delegate_stake_account( pub fn create_stake_account(
voter_pubkey: &Pubkey, voter_pubkey: &Pubkey,
vote_state: &VoteState, vote_state: &VoteState,
lamports: u64, lamports: u64,
@ -190,9 +206,10 @@ pub fn create_delegate_stake_account(
let mut stake_account = Account::new(lamports, std::mem::size_of::<StakeState>(), &id()); let mut stake_account = Account::new(lamports, std::mem::size_of::<StakeState>(), &id());
stake_account stake_account
.set_state(&StakeState::Delegate { .set_state(&StakeState::Stake {
voter_pubkey: *voter_pubkey, voter_pubkey: *voter_pubkey,
credits_observed: vote_state.credits(), credits_observed: vote_state.credits(),
stake: lamports,
}) })
.expect("set_state"); .expect("set_state");
@ -223,7 +240,9 @@ mod tests {
vote_keyed_account.set_state(&vote_state).unwrap(); vote_keyed_account.set_state(&vote_state).unwrap();
let stake_pubkey = Pubkey::default(); let stake_pubkey = Pubkey::default();
let mut stake_account = Account::new(0, std::mem::size_of::<StakeState>(), &id()); let stake_lamports = 42;
let mut stake_account =
Account::new(stake_lamports, std::mem::size_of::<StakeState>(), &id());
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, false, &mut stake_account); 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()); assert_eq!(stake_state, StakeState::default());
} }
stake_keyed_account.initialize_delegate().unwrap(); stake_keyed_account.initialize_stake().unwrap();
assert_eq!( assert_eq!(
stake_keyed_account.delegate_stake(&vote_keyed_account), stake_keyed_account.delegate_stake(&vote_keyed_account, 0),
Err(InstructionError::MissingRequiredSignature) Err(InstructionError::MissingRequiredSignature)
); );
let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account); let mut stake_keyed_account = KeyedAccount::new(&stake_pubkey, true, &mut stake_account);
assert!(stake_keyed_account assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account) .delegate_stake(&vote_keyed_account, stake_lamports)
.is_ok()); .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() // resulting account from delegate_stake()
assert_eq!( assert_eq!(
create_delegate_stake_account(&vote_pubkey, &vote_state, 0), create_stake_account(&vote_pubkey, &vote_state, stake_lamports),
*stake_keyed_account.account, *stake_keyed_account.account,
); );
let stake_state: StakeState = stake_keyed_account.state().unwrap(); let stake_state: StakeState = stake_keyed_account.state().unwrap();
assert_eq!( assert_eq!(
stake_state, stake_state,
StakeState::Delegate { StakeState::Stake {
voter_pubkey: vote_keypair.pubkey(), voter_pubkey: vote_keypair.pubkey(),
credits_observed: vote_state.credits() credits_observed: vote_state.credits(),
stake: stake_lamports,
} }
); );
let stake_state = StakeState::MiningPool; let stake_state = StakeState::MiningPool;
stake_keyed_account.set_state(&stake_state).unwrap(); stake_keyed_account.set_state(&stake_state).unwrap();
assert!(stake_keyed_account assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account) .delegate_stake(&vote_keyed_account, 0)
.is_err()); .is_err());
} }
#[test] #[test]
@ -334,11 +361,11 @@ mod tests {
&id(), &id(),
); );
let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account); 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 // delegate the stake
assert!(stake_keyed_account assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account) .delegate_stake(&vote_keyed_account, STAKE_GETS_PAID_EVERY_VOTE)
.is_ok()); .is_ok());
let mut mining_pool_account = Account::new(0, std::mem::size_of::<StakeState>(), &id()); let mut mining_pool_account = Account::new(0, std::mem::size_of::<StakeState>(), &id());
@ -402,13 +429,15 @@ mod tests {
vote_keyed_account.set_state(&vote_state).unwrap(); vote_keyed_account.set_state(&vote_state).unwrap();
let pubkey = Pubkey::default(); let pubkey = Pubkey::default();
let mut stake_account = Account::new(0, std::mem::size_of::<StakeState>(), &id()); let stake_lamports = 0;
let mut stake_account =
Account::new(stake_lamports, std::mem::size_of::<StakeState>(), &id());
let mut stake_keyed_account = KeyedAccount::new(&pubkey, true, &mut stake_account); 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 // delegate the stake
assert!(stake_keyed_account assert!(stake_keyed_account
.delegate_stake(&vote_keyed_account) .delegate_stake(&vote_keyed_account, stake_lamports)
.is_ok()); .is_ok());
let mut mining_pool_account = Account::new(0, std::mem::size_of::<StakeState>(), &id()); let mut mining_pool_account = Account::new(0, std::mem::size_of::<StakeState>(), &id());

View File

@ -53,7 +53,7 @@ pub fn create_genesis_block_with_leader(
// passive bootstrap leader stake, duplicates above temporarily // passive bootstrap leader stake, duplicates above temporarily
( (
staking_keypair.pubkey(), staking_keypair.pubkey(),
stake_state::create_delegate_stake_account( stake_state::create_stake_account(
&voting_keypair.pubkey(), &voting_keypair.pubkey(),
&vote_state, &vote_state,
bootstrap_leader_stake_lamports, bootstrap_leader_stake_lamports,

View File

@ -100,7 +100,7 @@ mod tests {
fn create_stake_account(stake: u64, vote_pubkey: &Pubkey) -> (Pubkey, Account) { fn create_stake_account(stake: u64, vote_pubkey: &Pubkey) -> (Pubkey, Account) {
( (
Pubkey::new_rand(), Pubkey::new_rand(),
stake_state::create_delegate_stake_account(&vote_pubkey, &VoteState::default(), stake), stake_state::create_stake_account(&vote_pubkey, &VoteState::default(), stake),
) )
} }

View File

@ -25,6 +25,9 @@ pub enum InstructionError {
/// An account's data was too small /// An account's data was too small
AccountDataTooSmall, AccountDataTooSmall,
/// An account's balance was too small to complete the instruction
InsufficientFunds,
/// The account did not have the expected program id /// The account did not have the expected program id
IncorrectProgramId, IncorrectProgramId,

View File

@ -52,7 +52,7 @@ pub enum WalletCommand {
ShowVoteAccount(Pubkey), ShowVoteAccount(Pubkey),
CreateStakeAccount(Pubkey, u64), CreateStakeAccount(Pubkey, u64),
CreateMiningPoolAccount(Pubkey, u64), CreateMiningPoolAccount(Pubkey, u64),
DelegateStake(Keypair, Pubkey), DelegateStake(Keypair, Pubkey, u64),
RedeemVoteCredits(Pubkey, Pubkey, Pubkey), RedeemVoteCredits(Pubkey, Pubkey, Pubkey),
ShowStakeAccount(Pubkey), ShowStakeAccount(Pubkey),
CreateStorageMiningPoolAccount(Pubkey, u64), CreateStorageMiningPoolAccount(Pubkey, u64),
@ -142,16 +142,26 @@ impl WalletConfig {
} }
} }
// Return the pubkey for an argument with `name` or None if not present. // Return parsed values from matches at `name`
fn pubkey_of(matches: &ArgMatches<'_>, name: &str) -> Option<Pubkey> { fn values_of<T>(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<T>>
matches.value_of(name).map(|x| x.parse::<Pubkey>().unwrap()) where
} T: std::str::FromStr,
<T as std::str::FromStr>::Err: std::fmt::Debug,
// Return the pubkeys for arguments with `name` or None if none present. {
fn pubkeys_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<Pubkey>> {
matches matches
.values_of(name) .values_of(name)
.map(|xs| xs.map(|x| x.parse::<Pubkey>().unwrap()).collect()) .map(|xs| xs.map(|x| x.parse::<T>().unwrap()).collect())
}
// Return a parsed value from matches at `name`
fn value_of<T>(matches: &ArgMatches<'_>, name: &str) -> Option<T>
where
T: std::str::FromStr,
<T as std::str::FromStr>::Err: std::fmt::Debug,
{
matches
.value_of(name)
.map(|value| value.parse::<T>().unwrap())
} }
// Return the keypair for an argument with filename `name` or None if not present. // 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)) Ok(WalletCommand::Airdrop(lamports))
} }
("balance", Some(balance_matches)) => { ("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)) Ok(WalletCommand::Balance(pubkey))
} }
("cancel", Some(cancel_matches)) => { ("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)) Ok(WalletCommand::Cancel(process_id))
} }
("confirm", Some(confirm_matches)) => { ("confirm", Some(confirm_matches)) => {
@ -188,8 +198,8 @@ pub fn parse_command(
} }
} }
("create-vote-account", Some(matches)) => { ("create-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();
let node_pubkey = pubkey_of(matches, "node_pubkey").unwrap(); let node_pubkey = value_of(matches, "node_pubkey").unwrap();
let commission = if let Some(commission) = matches.value_of("commission") { let commission = if let Some(commission) = matches.value_of("commission") {
commission.parse()? commission.parse()?
} else { } else {
@ -204,11 +214,11 @@ pub fn parse_command(
)) ))
} }
("authorize-voter", Some(matches)) => { ("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 = let authorized_voter_keypair =
keypair_of(matches, "authorized_voter_keypair_file").unwrap(); keypair_of(matches, "authorized_voter_keypair_file").unwrap();
let new_authorized_voter_pubkey = let new_authorized_voter_pubkey =
pubkey_of(matches, "new_authorized_voter_pubkey").unwrap(); value_of(matches, "new_authorized_voter_pubkey").unwrap();
Ok(WalletCommand::AuthorizeVoter( Ok(WalletCommand::AuthorizeVoter(
voting_account_pubkey, voting_account_pubkey,
@ -217,11 +227,11 @@ pub fn parse_command(
)) ))
} }
("show-vote-account", Some(matches)) => { ("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)) Ok(WalletCommand::ShowVoteAccount(voting_account_pubkey))
} }
("create-stake-account", Some(matches)) => { ("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()?; let lamports = matches.value_of("lamports").unwrap().parse()?;
Ok(WalletCommand::CreateStakeAccount( Ok(WalletCommand::CreateStakeAccount(
staking_account_pubkey, staking_account_pubkey,
@ -230,7 +240,7 @@ pub fn parse_command(
} }
("create-mining-pool-account", Some(matches)) => { ("create-mining-pool-account", Some(matches)) => {
let mining_pool_account_pubkey = 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()?; let lamports = matches.value_of("lamports").unwrap().parse()?;
Ok(WalletCommand::CreateMiningPoolAccount( Ok(WalletCommand::CreateMiningPoolAccount(
mining_pool_account_pubkey, mining_pool_account_pubkey,
@ -240,17 +250,19 @@ pub fn parse_command(
("delegate-stake", Some(matches)) => { ("delegate-stake", Some(matches)) => {
let staking_account_keypair = let staking_account_keypair =
keypair_of(matches, "staking_account_keypair_file").unwrap(); 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( Ok(WalletCommand::DelegateStake(
staking_account_keypair, staking_account_keypair,
voting_account_pubkey, voting_account_pubkey,
stake,
)) ))
} }
("redeem-vote-credits", Some(matches)) => { ("redeem-vote-credits", Some(matches)) => {
let mining_pool_account_pubkey = let mining_pool_account_pubkey =
pubkey_of(matches, "mining_pool_account_pubkey").unwrap(); value_of(matches, "mining_pool_account_pubkey").unwrap();
let staking_account_pubkey = pubkey_of(matches, "staking_account_pubkey").unwrap(); let staking_account_pubkey = value_of(matches, "staking_account_pubkey").unwrap();
let voting_account_pubkey = pubkey_of(matches, "voting_account_pubkey").unwrap(); let voting_account_pubkey = value_of(matches, "voting_account_pubkey").unwrap();
Ok(WalletCommand::RedeemVoteCredits( Ok(WalletCommand::RedeemVoteCredits(
mining_pool_account_pubkey, mining_pool_account_pubkey,
staking_account_pubkey, staking_account_pubkey,
@ -258,12 +270,12 @@ pub fn parse_command(
)) ))
} }
("show-stake-account", Some(matches)) => { ("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)) Ok(WalletCommand::ShowStakeAccount(staking_account_pubkey))
} }
("create-storage-mining-pool-account", Some(matches)) => { ("create-storage-mining-pool-account", Some(matches)) => {
let storage_mining_pool_account_pubkey = 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()?; let lamports = matches.value_of("lamports").unwrap().parse()?;
Ok(WalletCommand::CreateStorageMiningPoolAccount( Ok(WalletCommand::CreateStorageMiningPoolAccount(
storage_mining_pool_account_pubkey, storage_mining_pool_account_pubkey,
@ -271,16 +283,16 @@ pub fn parse_command(
)) ))
} }
("create-replicator-storage-account", Some(matches)) => { ("create-replicator-storage-account", Some(matches)) => {
let account_owner = pubkey_of(matches, "storage_account_owner").unwrap(); let account_owner = value_of(matches, "storage_account_owner").unwrap();
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap(); let storage_account_pubkey = value_of(matches, "storage_account_pubkey").unwrap();
Ok(WalletCommand::CreateReplicatorStorageAccount( Ok(WalletCommand::CreateReplicatorStorageAccount(
account_owner, account_owner,
storage_account_pubkey, storage_account_pubkey,
)) ))
} }
("create-validator-storage-account", Some(matches)) => { ("create-validator-storage-account", Some(matches)) => {
let account_owner = pubkey_of(matches, "storage_account_owner").unwrap(); let account_owner = value_of(matches, "storage_account_owner").unwrap();
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap(); let storage_account_pubkey = value_of(matches, "storage_account_pubkey").unwrap();
Ok(WalletCommand::CreateValidatorStorageAccount( Ok(WalletCommand::CreateValidatorStorageAccount(
account_owner, account_owner,
storage_account_pubkey, storage_account_pubkey,
@ -288,8 +300,8 @@ pub fn parse_command(
} }
("claim-storage-reward", Some(matches)) => { ("claim-storage-reward", Some(matches)) => {
let storage_mining_pool_account_pubkey = 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 storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap(); let storage_account_pubkey = value_of(matches, "storage_account_pubkey").unwrap();
let slot = matches.value_of("slot").unwrap().parse()?; let slot = matches.value_of("slot").unwrap().parse()?;
Ok(WalletCommand::ClaimStorageReward( Ok(WalletCommand::ClaimStorageReward(
storage_mining_pool_account_pubkey, storage_mining_pool_account_pubkey,
@ -298,7 +310,7 @@ pub fn parse_command(
)) ))
} }
("show-storage-account", Some(matches)) => { ("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)) Ok(WalletCommand::ShowStorageAccount(storage_account_pubkey))
} }
("deploy", Some(deploy_matches)) => Ok(WalletCommand::Deploy( ("deploy", Some(deploy_matches)) => Ok(WalletCommand::Deploy(
@ -310,7 +322,7 @@ pub fn parse_command(
("get-transaction-count", Some(_matches)) => Ok(WalletCommand::GetTransactionCount), ("get-transaction-count", Some(_matches)) => Ok(WalletCommand::GetTransactionCount),
("pay", Some(pay_matches)) => { ("pay", Some(pay_matches)) => {
let lamports = pay_matches.value_of("lamports").unwrap().parse()?; 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") { let timestamp = if pay_matches.is_present("timestamp") {
// Parse input for serde_json // Parse input for serde_json
let date_string = if !pay_matches.value_of("timestamp").unwrap().contains('Z') { let date_string = if !pay_matches.value_of("timestamp").unwrap().contains('Z') {
@ -322,8 +334,8 @@ pub fn parse_command(
} else { } else {
None None
}; };
let timestamp_pubkey = pubkey_of(&pay_matches, "timestamp_pubkey"); let timestamp_pubkey = value_of(&pay_matches, "timestamp_pubkey");
let witness_vec = pubkeys_of(&pay_matches, "witness"); let witness_vec = values_of(&pay_matches, "witness");
let cancelable = if pay_matches.is_present("cancelable") { let cancelable = if pay_matches.is_present("cancelable") {
Some(*pubkey) Some(*pubkey)
} else { } else {
@ -340,13 +352,13 @@ pub fn parse_command(
)) ))
} }
("send-signature", Some(sig_matches)) => { ("send-signature", Some(sig_matches)) => {
let to = pubkey_of(&sig_matches, "to").unwrap(); let to = value_of(&sig_matches, "to").unwrap();
let process_id = pubkey_of(&sig_matches, "process_id").unwrap(); let process_id = value_of(&sig_matches, "process_id").unwrap();
Ok(WalletCommand::Witness(to, process_id)) Ok(WalletCommand::Witness(to, process_id))
} }
("send-timestamp", Some(timestamp_matches)) => { ("send-timestamp", Some(timestamp_matches)) => {
let to = pubkey_of(&timestamp_matches, "to").unwrap(); let to = value_of(&timestamp_matches, "to").unwrap();
let process_id = pubkey_of(&timestamp_matches, "process_id").unwrap(); let process_id = value_of(&timestamp_matches, "process_id").unwrap();
let dt = if timestamp_matches.is_present("datetime") { let dt = if timestamp_matches.is_present("datetime") {
// Parse input for serde_json // Parse input for serde_json
let date_string = if !timestamp_matches let date_string = if !timestamp_matches
@ -553,7 +565,7 @@ fn process_create_stake_account(
lamports: u64, lamports: u64,
) -> ProcessResult { ) -> ProcessResult {
let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?; 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(), &config.keypair.pubkey(),
staking_account_pubkey, staking_account_pubkey,
lamports, lamports,
@ -595,11 +607,13 @@ fn process_delegate_stake(
config: &WalletConfig, config: &WalletConfig,
staking_account_keypair: &Keypair, staking_account_keypair: &Keypair,
voting_account_pubkey: &Pubkey, voting_account_pubkey: &Pubkey,
stake: u64,
) -> ProcessResult { ) -> ProcessResult {
let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?; let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = vec![stake_instruction::delegate_stake( let ixs = vec![stake_instruction::delegate_stake(
&staking_account_keypair.pubkey(), &staking_account_keypair.pubkey(),
voting_account_pubkey, voting_account_pubkey,
stake,
)]; )];
let mut tx = Transaction::new_signed_with_payer( let mut tx = Transaction::new_signed_with_payer(
@ -645,13 +659,15 @@ fn process_show_stake_account(
use solana_stake_api::stake_state::StakeState; use solana_stake_api::stake_state::StakeState;
let stake_account = rpc_client.get_account(staking_account_pubkey)?; let stake_account = rpc_client.get_account(staking_account_pubkey)?;
match stake_account.state() { match stake_account.state() {
Ok(StakeState::Delegate { Ok(StakeState::Stake {
voter_pubkey, voter_pubkey,
credits_observed, credits_observed,
stake,
}) => { }) => {
println!("account lamports: {}", stake_account.lamports); println!("account lamports: {}", stake_account.lamports);
println!("voter pubkey: {}", voter_pubkey); println!("voter pubkey: {}", voter_pubkey);
println!("credits observed: {}", credits_observed); println!("credits observed: {}", credits_observed);
println!("activated stake: {}", stake);
Ok("".to_string()) Ok("".to_string())
} }
Ok(StakeState::MiningPool) => { 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( process_delegate_stake(
&rpc_client, &rpc_client,
config, config,
&staking_account_keypair, &staking_account_keypair,
&voting_account_pubkey, &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) .takes_value(true)
.required(true) .required(true)
.validator(is_pubkey) .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( .subcommand(
@ -1891,10 +1916,11 @@ mod tests {
"delegate-stake", "delegate-stake",
&keypair_file, &keypair_file,
&pubkey_string, &pubkey_string,
"42",
]); ]);
assert_eq!( assert_eq!(
parse_command(&pubkey, &test_delegate_stake).unwrap(), parse_command(&pubkey, &test_delegate_stake).unwrap(),
WalletCommand::DelegateStake(keypair, pubkey) WalletCommand::DelegateStake(keypair, pubkey, 42)
); );
// Test Deploy Subcommand // Test Deploy Subcommand
@ -2062,7 +2088,7 @@ mod tests {
let bob_keypair = Keypair::new(); let bob_keypair = Keypair::new();
let node_pubkey = Pubkey::new_rand(); 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); let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string()); assert_eq!(signature.unwrap(), SIGNATURE.to_string());