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:
parent
7678af6300
commit
450f1d2867
|
@ -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)]
|
||||
|
|
|
@ -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))),
|
||||
_ => {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)?;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in New Issue