Add set-lockup to solana-stake-accounts (#9827)

* Add a command to set lockups or authorize a new custodian on derived stake accounts

* Thanks clippy
This commit is contained in:
Greg Fitzgerald 2020-04-30 17:56:37 -06:00 committed by GitHub
parent 7678af6300
commit 450f1d2867
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 246 additions and 6 deletions

View File

@ -77,6 +77,18 @@ impl StakeState {
_ => None,
}
}
pub fn lockup_from(account: &Account) -> Option<Lockup> {
Self::from(account).and_then(|state: Self| state.lockup())
}
pub fn lockup(&self) -> Option<Lockup> {
match self {
StakeState::Stake(meta, _stake) => Some(meta.lockup),
StakeState::Initialized(meta) => Some(meta.lockup),
_ => None,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]

View File

@ -1,8 +1,12 @@
use crate::args::{
Args, AuthorizeArgs, Command, CountArgs, MoveArgs, NewArgs, QueryArgs, RebaseArgs,
SetLockupArgs,
};
use clap::{value_t, value_t_or_exit, App, Arg, ArgMatches, SubCommand};
use solana_clap_utils::{
input_parsers::unix_timestamp_from_rfc3339_datetime,
input_validators::{is_amount, is_rfc3339_datetime, is_valid_pubkey, is_valid_signer},
};
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
use solana_clap_utils::input_validators::{is_amount, is_valid_pubkey, is_valid_signer};
use solana_cli_config::CONFIG_FILE;
use solana_sdk::native_token::sol_to_lamports;
use std::ffi::OsString;
@ -36,6 +40,23 @@ fn base_pubkey_arg<'a, 'b>() -> Arg<'a, 'b> {
.help("Public key which stake account addresses are derived from")
}
fn custodian_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name("custodian")
.required(true)
.takes_value(true)
.value_name("KEYPAIR")
.validator(is_valid_signer)
.help("Authority to modify lockups")
}
fn new_custodian_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name("new_custodian")
.takes_value(true)
.value_name("PUBKEY")
.validator(is_valid_pubkey)
.help("New authority to modify lockups")
}
fn new_base_keypair_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name("new_base_keypair")
.required(true)
@ -85,6 +106,23 @@ fn new_withdraw_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
.help("New withdraw authority")
}
fn lockup_epoch_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name("lockup_epoch")
.long("lockup-epoch")
.takes_value(true)
.value_name("NUMBER")
.help("The epoch height at which each account will be available for withdrawl")
}
fn lockup_date_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name("lockup_date")
.long("lockup-date")
.value_name("RFC3339 DATETIME")
.validator(is_rfc3339_datetime)
.takes_value(true)
.help("The date and time at which each account will be available for withdrawl")
}
fn num_accounts_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name("num_accounts")
.long("num-accounts")
@ -197,6 +235,17 @@ where
.arg(new_withdraw_authority_arg())
.arg(num_accounts_arg()),
)
.subcommand(
SubCommand::with_name("set-lockup")
.about("Set new lockups in all derived stake accounts")
.arg(fee_payer_arg())
.arg(base_pubkey_arg().index(1))
.arg(custodian_arg())
.arg(lockup_epoch_arg())
.arg(lockup_date_arg())
.arg(new_custodian_arg())
.arg(num_accounts_arg()),
)
.subcommand(
SubCommand::with_name("rebase")
.about("Relocate derived stake accounts")
@ -258,6 +307,18 @@ fn parse_authorize_args(matches: &ArgMatches<'_>) -> AuthorizeArgs<String, Strin
}
}
fn parse_set_lockup_args(matches: &ArgMatches<'_>) -> SetLockupArgs<String, String> {
SetLockupArgs {
fee_payer: value_t_or_exit!(matches, "fee_payer", String),
base_pubkey: value_t_or_exit!(matches, "base_pubkey", String),
custodian: value_t_or_exit!(matches, "custodian", String),
lockup_epoch: value_t!(matches, "lockup_epoch", u64).ok(),
lockup_date: unix_timestamp_from_rfc3339_datetime(matches, "lockup_date"),
new_custodian: value_t!(matches, "new_custodian", String).ok(),
num_accounts: value_t_or_exit!(matches, "num_accounts", usize),
}
}
fn parse_rebase_args(matches: &ArgMatches<'_>) -> RebaseArgs<String, String> {
RebaseArgs {
fee_payer: value_t_or_exit!(matches, "fee_payer", String),
@ -290,6 +351,7 @@ where
("addresses", Some(matches)) => Command::Addresses(parse_query_args(matches)),
("balance", Some(matches)) => Command::Balance(parse_query_args(matches)),
("authorize", Some(matches)) => Command::Authorize(parse_authorize_args(matches)),
("set-lockup", Some(matches)) => Command::SetLockup(parse_set_lockup_args(matches)),
("rebase", Some(matches)) => Command::Rebase(parse_rebase_args(matches)),
("move", Some(matches)) => Command::Move(Box::new(parse_move_args(matches))),
_ => {

View File

@ -1,7 +1,11 @@
use clap::ArgMatches;
use solana_clap_utils::keypair::{pubkey_from_path, signer_from_path};
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{pubkey::Pubkey, signature::Signer};
use solana_sdk::{
clock::{Epoch, UnixTimestamp},
pubkey::Pubkey,
signature::Signer,
};
use std::error::Error;
use std::sync::Arc;
@ -34,6 +38,16 @@ pub(crate) struct AuthorizeArgs<P, K> {
pub num_accounts: usize,
}
pub(crate) struct SetLockupArgs<P, K> {
pub fee_payer: K,
pub base_pubkey: P,
pub custodian: K,
pub lockup_epoch: Option<Epoch>,
pub lockup_date: Option<UnixTimestamp>,
pub new_custodian: Option<P>,
pub num_accounts: usize,
}
pub(crate) struct RebaseArgs<P, K> {
pub fee_payer: K,
pub base_pubkey: P,
@ -53,6 +67,7 @@ pub(crate) enum Command<P, K> {
Addresses(QueryArgs<P>),
Balance(QueryArgs<P>),
Authorize(AuthorizeArgs<P, K>),
SetLockup(SetLockupArgs<P, K>),
Rebase(RebaseArgs<P, K>),
Move(Box<MoveArgs<P, K>>),
}
@ -103,6 +118,29 @@ fn resolve_fee_payer(
signer_from_path(&matches, key_url, "fee-payer", wallet_manager)
}
fn resolve_custodian(
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
key_url: &str,
) -> Result<Box<dyn Signer>, Box<dyn Error>> {
let matches = ArgMatches::default();
signer_from_path(&matches, key_url, "custodian", wallet_manager)
}
fn resolve_new_custodian(
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
key_url: &Option<String>,
) -> Result<Option<Pubkey>, Box<dyn Error>> {
let matches = ArgMatches::default();
let pubkey = match key_url {
None => None,
Some(key_url) => {
let pubkey = pubkey_from_path(&matches, key_url, "new custodian", wallet_manager)?;
Some(pubkey)
}
};
Ok(pubkey)
}
fn resolve_base_pubkey(
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
key_url: &str,
@ -141,6 +179,22 @@ fn resolve_authorize_args(
Ok(resolved_args)
}
fn resolve_set_lockup_args(
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
args: &SetLockupArgs<String, String>,
) -> Result<SetLockupArgs<Pubkey, Box<dyn Signer>>, Box<dyn Error>> {
let resolved_args = SetLockupArgs {
fee_payer: resolve_fee_payer(wallet_manager, &args.fee_payer)?,
base_pubkey: resolve_base_pubkey(wallet_manager, &args.base_pubkey)?,
custodian: resolve_custodian(wallet_manager, &args.custodian)?,
lockup_epoch: args.lockup_epoch,
lockup_date: args.lockup_date,
new_custodian: resolve_new_custodian(wallet_manager, &args.new_custodian)?,
num_accounts: args.num_accounts,
};
Ok(resolved_args)
}
fn resolve_rebase_args(
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
args: &RebaseArgs<String, String>,
@ -217,6 +271,10 @@ pub(crate) fn resolve_command(
let resolved_args = resolve_authorize_args(&mut wallet_manager, &args)?;
Ok(Command::Authorize(resolved_args))
}
Command::SetLockup(args) => {
let resolved_args = resolve_set_lockup_args(&mut wallet_manager, &args)?;
Ok(Command::SetLockup(resolved_args))
}
Command::Rebase(args) => {
let resolved_args = resolve_rebase_args(&mut wallet_manager, &args)?;
Ok(Command::Rebase(resolved_args))

View File

@ -3,7 +3,9 @@ mod args;
mod stake_accounts;
use crate::arg_parser::parse_args;
use crate::args::{resolve_command, AuthorizeArgs, Command, MoveArgs, NewArgs, RebaseArgs};
use crate::args::{
resolve_command, AuthorizeArgs, Command, MoveArgs, NewArgs, RebaseArgs, SetLockupArgs,
};
use solana_cli_config::Config;
use solana_client::client_error::ClientError;
use solana_client::rpc_client::RpcClient;
@ -15,6 +17,7 @@ use solana_sdk::{
signers::Signers,
transaction::Transaction,
};
use solana_stake_program::stake_instruction::LockupArgs;
use std::env;
use std::error::Error;
@ -53,6 +56,7 @@ fn process_new_stake_account(
args.lamports,
&args.stake_authority,
&args.withdraw_authority,
&Pubkey::default(),
args.index,
);
let signers = vec![
@ -89,6 +93,30 @@ fn process_authorize_stake_accounts(
Ok(())
}
fn process_lockup_stake_accounts(
client: &RpcClient,
args: &SetLockupArgs<Pubkey, Box<dyn Signer>>,
) -> Result<(), ClientError> {
let lockup = LockupArgs {
epoch: args.lockup_epoch,
unix_timestamp: args.lockup_date,
custodian: args.new_custodian,
};
let messages = stake_accounts::lockup_stake_accounts(
&args.fee_payer.pubkey(),
&args.base_pubkey,
&args.custodian.pubkey(),
&lockup,
args.num_accounts,
);
let signers = vec![&*args.fee_payer, &*args.custodian];
for message in messages {
let signature = send_message(client, message, &signers)?;
println!("{}", signature);
}
Ok(())
}
fn process_rebase_stake_accounts(
client: &RpcClient,
args: &RebaseArgs<Pubkey, Box<dyn Signer>>,
@ -201,6 +229,9 @@ fn main() -> Result<(), Box<dyn Error>> {
Command::Authorize(args) => {
process_authorize_stake_accounts(&client, &args)?;
}
Command::SetLockup(args) => {
process_lockup_stake_accounts(&client, &args)?;
}
Command::Rebase(args) => {
process_rebase_stake_accounts(&client, &args)?;
}

View File

@ -1,6 +1,6 @@
use solana_sdk::{instruction::Instruction, message::Message, pubkey::Pubkey};
use solana_stake_program::{
stake_instruction,
stake_instruction::{self, LockupArgs},
stake_state::{Authorized, Lockup, StakeAuthorize},
};
@ -25,6 +25,7 @@ pub(crate) fn new_stake_account(
lamports: u64,
stake_authority_pubkey: &Pubkey,
withdraw_authority_pubkey: &Pubkey,
custodian_pubkey: &Pubkey,
index: usize,
) -> Message {
let stake_account_address = derive_stake_account_address(base_pubkey, index);
@ -32,13 +33,17 @@ pub(crate) fn new_stake_account(
staker: *stake_authority_pubkey,
withdrawer: *withdraw_authority_pubkey,
};
let lockup = Lockup {
custodian: *custodian_pubkey,
..Lockup::default()
};
let instructions = stake_instruction::create_account_with_seed(
funding_pubkey,
&stake_account_address,
&base_pubkey,
&index.to_string(),
&authorized,
&Lockup::default(),
&lockup,
lamports,
);
Message::new_with_payer(&instructions, Some(fee_payer_pubkey))
@ -152,6 +157,23 @@ pub(crate) fn authorize_stake_accounts(
.collect::<Vec<_>>()
}
pub(crate) fn lockup_stake_accounts(
fee_payer_pubkey: &Pubkey,
base_pubkey: &Pubkey,
custodian_pubkey: &Pubkey,
lockup: &LockupArgs,
num_accounts: usize,
) -> Vec<Message> {
let stake_account_addresses = derive_stake_account_addresses(base_pubkey, num_accounts);
stake_account_addresses
.iter()
.map(|address| {
let instruction = stake_instruction::set_lockup(address, &lockup, custodian_pubkey);
Message::new_with_payer(&[instruction], Some(&fee_payer_pubkey))
})
.collect()
}
pub(crate) fn rebase_stake_accounts(
fee_payer_pubkey: &Pubkey,
new_base_pubkey: &Pubkey,
@ -273,6 +295,7 @@ mod tests {
lamports,
&stake_authority_pubkey,
&withdraw_authority_pubkey,
&Pubkey::default(),
0,
);
@ -310,6 +333,7 @@ mod tests {
lamports,
&stake_authority_pubkey,
&withdraw_authority_pubkey,
&Pubkey::default(),
0,
);
@ -343,6 +367,57 @@ mod tests {
assert_eq!(authorized.withdrawer, new_withdraw_authority_pubkey);
}
#[test]
fn test_lockup_stake_accounts() {
let (bank, funding_keypair, rent) = create_bank(10_000_000);
let funding_pubkey = funding_keypair.pubkey();
let bank_client = BankClient::new(bank);
let fee_payer_keypair = create_account(&bank_client, &funding_keypair, 1);
let fee_payer_pubkey = fee_payer_keypair.pubkey();
let base_keypair = Keypair::new();
let base_pubkey = base_keypair.pubkey();
let lamports = rent + 1;
let custodian_keypair = Keypair::new();
let custodian_pubkey = custodian_keypair.pubkey();
let message = new_stake_account(
&fee_payer_pubkey,
&funding_pubkey,
&base_pubkey,
lamports,
&Pubkey::default(),
&Pubkey::default(),
&custodian_pubkey,
0,
);
let signers = [&funding_keypair, &fee_payer_keypair, &base_keypair];
bank_client.send_message(&signers, message).unwrap();
let messages = lockup_stake_accounts(
&fee_payer_pubkey,
&base_pubkey,
&custodian_pubkey,
&LockupArgs {
unix_timestamp: Some(1),
..LockupArgs::default()
},
1,
);
let signers = [&fee_payer_keypair, &custodian_keypair];
for message in messages {
bank_client.send_message(&signers, message).unwrap();
}
let account = get_account_at(&bank_client, &base_pubkey, 0);
let lockup = StakeState::lockup_from(&account).unwrap();
assert_eq!(lockup.unix_timestamp, 1);
assert_eq!(lockup.epoch, 0);
}
#[test]
fn test_rebase_empty_account() {
let pubkey = Pubkey::default();
@ -384,6 +459,7 @@ mod tests {
lamports,
&stake_authority_pubkey,
&withdraw_authority_pubkey,
&Pubkey::default(),
0,
);
@ -442,6 +518,7 @@ mod tests {
lamports,
&stake_authority_pubkey,
&withdraw_authority_pubkey,
&Pubkey::default(),
0,
);