2023-09-01 00:26:13 -07:00
|
|
|
#![allow(clippy::arithmetic_side_effects)]
|
2020-03-31 20:47:43 -07:00
|
|
|
mod arg_parser;
|
2020-03-30 15:04:46 -07:00
|
|
|
mod args;
|
|
|
|
mod stake_accounts;
|
|
|
|
|
2021-12-03 09:00:31 -08:00
|
|
|
use {
|
|
|
|
crate::{
|
|
|
|
arg_parser::parse_args,
|
|
|
|
args::{
|
|
|
|
resolve_command, AuthorizeArgs, Command, MoveArgs, NewArgs, RebaseArgs, SetLockupArgs,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
solana_cli_config::Config,
|
2022-08-24 09:47:02 -07:00
|
|
|
solana_rpc_client::rpc_client::RpcClient,
|
|
|
|
solana_rpc_client_api::client_error::Error as ClientError,
|
2021-12-03 09:00:31 -08:00
|
|
|
solana_sdk::{
|
|
|
|
message::Message,
|
|
|
|
native_token::lamports_to_sol,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
signature::{unique_signers, Signature, Signer},
|
|
|
|
signers::Signers,
|
|
|
|
stake::{instruction::LockupArgs, state::Lockup},
|
|
|
|
transaction::Transaction,
|
|
|
|
},
|
|
|
|
solana_stake_program::stake_state,
|
|
|
|
std::{env, error::Error},
|
2020-04-30 16:56:37 -07:00
|
|
|
};
|
2020-03-31 19:07:00 -07:00
|
|
|
|
2020-03-30 15:04:46 -07:00
|
|
|
fn get_balance_at(client: &RpcClient, pubkey: &Pubkey, i: usize) -> Result<u64, ClientError> {
|
|
|
|
let address = stake_accounts::derive_stake_account_address(pubkey, i);
|
|
|
|
client.get_balance(&address)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the number of derived stake accounts with balances
|
|
|
|
fn count_stake_accounts(client: &RpcClient, base_pubkey: &Pubkey) -> Result<usize, ClientError> {
|
|
|
|
let mut i = 0;
|
|
|
|
while get_balance_at(client, base_pubkey, i)? > 0 {
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
Ok(i)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_balances(
|
|
|
|
client: &RpcClient,
|
|
|
|
addresses: Vec<Pubkey>,
|
|
|
|
) -> Result<Vec<(Pubkey, u64)>, ClientError> {
|
|
|
|
addresses
|
|
|
|
.into_iter()
|
|
|
|
.map(|pubkey| client.get_balance(&pubkey).map(|bal| (pubkey, bal)))
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2020-05-04 12:05:04 -07:00
|
|
|
fn get_lockup(client: &RpcClient, address: &Pubkey) -> Result<Lockup, ClientError> {
|
|
|
|
client
|
|
|
|
.get_account(address)
|
2021-06-15 09:04:00 -07:00
|
|
|
.map(|account| stake_state::lockup_from(&account).unwrap())
|
2020-05-04 12:05:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_lockups(
|
|
|
|
client: &RpcClient,
|
|
|
|
addresses: Vec<Pubkey>,
|
|
|
|
) -> Result<Vec<(Pubkey, Lockup)>, ClientError> {
|
|
|
|
addresses
|
|
|
|
.into_iter()
|
|
|
|
.map(|pubkey| get_lockup(client, &pubkey).map(|bal| (pubkey, bal)))
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2020-03-30 15:04:46 -07:00
|
|
|
fn process_new_stake_account(
|
|
|
|
client: &RpcClient,
|
2020-03-31 20:47:43 -07:00
|
|
|
args: &NewArgs<Pubkey, Box<dyn Signer>>,
|
|
|
|
) -> Result<Signature, ClientError> {
|
2020-03-30 15:04:46 -07:00
|
|
|
let message = stake_accounts::new_stake_account(
|
2020-03-31 20:47:43 -07:00
|
|
|
&args.fee_payer.pubkey(),
|
|
|
|
&args.funding_keypair.pubkey(),
|
|
|
|
&args.base_keypair.pubkey(),
|
|
|
|
args.lamports,
|
|
|
|
&args.stake_authority,
|
|
|
|
&args.withdraw_authority,
|
2020-04-30 16:56:37 -07:00
|
|
|
&Pubkey::default(),
|
2020-03-31 20:47:43 -07:00
|
|
|
args.index,
|
2020-03-30 15:04:46 -07:00
|
|
|
);
|
2020-05-04 12:05:04 -07:00
|
|
|
let signers = unique_signers(vec![
|
2020-03-31 20:47:43 -07:00
|
|
|
&*args.fee_payer,
|
|
|
|
&*args.funding_keypair,
|
|
|
|
&*args.base_keypair,
|
2020-05-04 12:05:04 -07:00
|
|
|
]);
|
2020-06-24 20:35:38 -07:00
|
|
|
let signature = send_and_confirm_message(client, message, &signers, false)?;
|
2020-03-30 15:04:46 -07:00
|
|
|
Ok(signature)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_authorize_stake_accounts(
|
|
|
|
client: &RpcClient,
|
2020-03-31 20:47:43 -07:00
|
|
|
args: &AuthorizeArgs<Pubkey, Box<dyn Signer>>,
|
|
|
|
) -> Result<(), ClientError> {
|
2020-03-30 15:04:46 -07:00
|
|
|
let messages = stake_accounts::authorize_stake_accounts(
|
2020-03-31 20:47:43 -07:00
|
|
|
&args.fee_payer.pubkey(),
|
|
|
|
&args.base_pubkey,
|
|
|
|
&args.stake_authority.pubkey(),
|
|
|
|
&args.withdraw_authority.pubkey(),
|
|
|
|
&args.new_stake_authority,
|
|
|
|
&args.new_withdraw_authority,
|
|
|
|
args.num_accounts,
|
2020-03-30 15:04:46 -07:00
|
|
|
);
|
2020-05-04 12:05:04 -07:00
|
|
|
let signers = unique_signers(vec![
|
2020-03-31 20:47:43 -07:00
|
|
|
&*args.fee_payer,
|
|
|
|
&*args.stake_authority,
|
|
|
|
&*args.withdraw_authority,
|
2020-05-04 12:05:04 -07:00
|
|
|
]);
|
2020-06-24 20:35:38 -07:00
|
|
|
send_and_confirm_messages(client, messages, &signers, false)?;
|
2020-03-30 15:04:46 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-04-30 16:56:37 -07:00
|
|
|
fn process_lockup_stake_accounts(
|
|
|
|
client: &RpcClient,
|
|
|
|
args: &SetLockupArgs<Pubkey, Box<dyn Signer>>,
|
|
|
|
) -> Result<(), ClientError> {
|
2020-05-04 12:05:04 -07:00
|
|
|
let addresses =
|
|
|
|
stake_accounts::derive_stake_account_addresses(&args.base_pubkey, args.num_accounts);
|
2021-06-18 06:34:46 -07:00
|
|
|
let existing_lockups = get_lockups(client, addresses)?;
|
2020-05-04 12:05:04 -07:00
|
|
|
|
2020-04-30 16:56:37 -07:00
|
|
|
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.custodian.pubkey(),
|
|
|
|
&lockup,
|
2020-05-04 12:05:04 -07:00
|
|
|
&existing_lockups,
|
|
|
|
args.unlock_years,
|
2020-04-30 16:56:37 -07:00
|
|
|
);
|
2020-05-04 12:05:04 -07:00
|
|
|
if messages.is_empty() {
|
|
|
|
eprintln!("No work to do");
|
|
|
|
return Ok(());
|
2020-04-30 16:56:37 -07:00
|
|
|
}
|
2020-05-04 12:05:04 -07:00
|
|
|
let signers = unique_signers(vec![&*args.fee_payer, &*args.custodian]);
|
2020-06-24 20:35:38 -07:00
|
|
|
send_and_confirm_messages(client, messages, &signers, args.no_wait)?;
|
2020-04-30 16:56:37 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-03-30 15:04:46 -07:00
|
|
|
fn process_rebase_stake_accounts(
|
|
|
|
client: &RpcClient,
|
2020-03-31 20:47:43 -07:00
|
|
|
args: &RebaseArgs<Pubkey, Box<dyn Signer>>,
|
|
|
|
) -> Result<(), ClientError> {
|
2020-03-30 15:04:46 -07:00
|
|
|
let addresses =
|
2020-03-31 20:47:43 -07:00
|
|
|
stake_accounts::derive_stake_account_addresses(&args.base_pubkey, args.num_accounts);
|
2021-06-18 06:34:46 -07:00
|
|
|
let balances = get_balances(client, addresses)?;
|
2020-03-30 15:04:46 -07:00
|
|
|
|
|
|
|
let messages = stake_accounts::rebase_stake_accounts(
|
2020-03-31 20:47:43 -07:00
|
|
|
&args.fee_payer.pubkey(),
|
|
|
|
&args.new_base_keypair.pubkey(),
|
|
|
|
&args.stake_authority.pubkey(),
|
2020-03-30 15:04:46 -07:00
|
|
|
&balances,
|
|
|
|
);
|
2020-04-22 09:45:44 -07:00
|
|
|
if messages.is_empty() {
|
|
|
|
eprintln!("No accounts found");
|
|
|
|
return Ok(());
|
|
|
|
}
|
2020-05-04 12:05:04 -07:00
|
|
|
let signers = unique_signers(vec![
|
2020-03-31 20:47:43 -07:00
|
|
|
&*args.fee_payer,
|
|
|
|
&*args.new_base_keypair,
|
|
|
|
&*args.stake_authority,
|
2020-05-04 12:05:04 -07:00
|
|
|
]);
|
2020-06-24 20:35:38 -07:00
|
|
|
send_and_confirm_messages(client, messages, &signers, false)?;
|
2020-03-30 15:04:46 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_move_stake_accounts(
|
|
|
|
client: &RpcClient,
|
2020-03-31 20:47:43 -07:00
|
|
|
move_args: &MoveArgs<Pubkey, Box<dyn Signer>>,
|
|
|
|
) -> Result<(), ClientError> {
|
|
|
|
let authorize_args = &move_args.authorize_args;
|
|
|
|
let args = &move_args.rebase_args;
|
2020-03-30 15:04:46 -07:00
|
|
|
let addresses =
|
2020-03-31 20:47:43 -07:00
|
|
|
stake_accounts::derive_stake_account_addresses(&args.base_pubkey, args.num_accounts);
|
2021-06-18 06:34:46 -07:00
|
|
|
let balances = get_balances(client, addresses)?;
|
2020-03-30 15:04:46 -07:00
|
|
|
|
|
|
|
let messages = stake_accounts::move_stake_accounts(
|
2020-03-31 20:47:43 -07:00
|
|
|
&args.fee_payer.pubkey(),
|
|
|
|
&args.new_base_keypair.pubkey(),
|
|
|
|
&args.stake_authority.pubkey(),
|
|
|
|
&authorize_args.withdraw_authority.pubkey(),
|
|
|
|
&authorize_args.new_stake_authority,
|
|
|
|
&authorize_args.new_withdraw_authority,
|
2020-03-30 15:04:46 -07:00
|
|
|
&balances,
|
|
|
|
);
|
2020-04-22 09:45:44 -07:00
|
|
|
if messages.is_empty() {
|
|
|
|
eprintln!("No accounts found");
|
|
|
|
return Ok(());
|
|
|
|
}
|
2020-05-04 12:05:04 -07:00
|
|
|
let signers = unique_signers(vec![
|
2020-03-31 20:47:43 -07:00
|
|
|
&*args.fee_payer,
|
|
|
|
&*args.new_base_keypair,
|
|
|
|
&*args.stake_authority,
|
|
|
|
&*authorize_args.withdraw_authority,
|
2020-05-04 12:05:04 -07:00
|
|
|
]);
|
2020-06-24 20:35:38 -07:00
|
|
|
send_and_confirm_messages(client, messages, &signers, false)?;
|
2020-03-30 15:04:46 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-06-24 20:35:38 -07:00
|
|
|
fn send_and_confirm_message<S: Signers>(
|
2020-03-30 15:04:46 -07:00
|
|
|
client: &RpcClient,
|
|
|
|
message: Message,
|
|
|
|
signers: &S,
|
2020-05-04 12:05:04 -07:00
|
|
|
no_wait: bool,
|
2020-03-30 15:04:46 -07:00
|
|
|
) -> Result<Signature, ClientError> {
|
|
|
|
let mut transaction = Transaction::new_unsigned(message);
|
2020-06-01 21:33:45 -07:00
|
|
|
|
2021-08-13 09:08:20 -07:00
|
|
|
let blockhash = client.get_new_latest_blockhash(&transaction.message().recent_blockhash)?;
|
2020-06-01 21:33:45 -07:00
|
|
|
transaction.try_sign(signers, blockhash)?;
|
|
|
|
|
2020-05-04 12:05:04 -07:00
|
|
|
if no_wait {
|
|
|
|
client.send_transaction(&transaction)
|
|
|
|
} else {
|
2020-05-09 09:06:32 -07:00
|
|
|
client.send_and_confirm_transaction_with_spinner(&transaction)
|
2020-05-04 12:05:04 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-24 20:35:38 -07:00
|
|
|
fn send_and_confirm_messages<S: Signers>(
|
2020-05-04 12:05:04 -07:00
|
|
|
client: &RpcClient,
|
|
|
|
messages: Vec<Message>,
|
|
|
|
signers: &S,
|
|
|
|
no_wait: bool,
|
|
|
|
) -> Result<Vec<Signature>, ClientError> {
|
|
|
|
let mut signatures = vec![];
|
|
|
|
for message in messages {
|
2020-06-24 20:35:38 -07:00
|
|
|
let signature = send_and_confirm_message(client, message, signers, no_wait)?;
|
2020-05-04 12:05:04 -07:00
|
|
|
signatures.push(signature);
|
2022-12-06 06:30:06 -08:00
|
|
|
println!("{signature}");
|
2020-05-04 12:05:04 -07:00
|
|
|
}
|
|
|
|
Ok(signatures)
|
2020-03-30 15:04:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn main() -> Result<(), Box<dyn Error>> {
|
2020-03-31 20:47:43 -07:00
|
|
|
let command_args = parse_args(env::args_os());
|
2022-05-11 14:07:59 -07:00
|
|
|
let config = Config::load(&command_args.config_file).unwrap_or_default();
|
2020-03-31 20:47:43 -07:00
|
|
|
let json_rpc_url = command_args.url.unwrap_or(config.json_rpc_url);
|
2020-03-30 15:04:46 -07:00
|
|
|
let client = RpcClient::new(json_rpc_url);
|
|
|
|
|
2020-03-31 20:47:43 -07:00
|
|
|
match resolve_command(&command_args.command)? {
|
|
|
|
Command::New(args) => {
|
|
|
|
process_new_stake_account(&client, &args)?;
|
2020-03-30 15:04:46 -07:00
|
|
|
}
|
2020-03-31 20:47:43 -07:00
|
|
|
Command::Count(args) => {
|
|
|
|
let num_accounts = count_stake_accounts(&client, &args.base_pubkey)?;
|
2022-12-06 06:30:06 -08:00
|
|
|
println!("{num_accounts}");
|
2020-03-30 15:04:46 -07:00
|
|
|
}
|
2020-03-31 20:47:43 -07:00
|
|
|
Command::Addresses(args) => {
|
2020-03-30 15:04:46 -07:00
|
|
|
let addresses = stake_accounts::derive_stake_account_addresses(
|
2020-03-31 20:47:43 -07:00
|
|
|
&args.base_pubkey,
|
|
|
|
args.num_accounts,
|
2020-03-30 15:04:46 -07:00
|
|
|
);
|
|
|
|
for address in addresses {
|
2022-12-06 06:30:06 -08:00
|
|
|
println!("{address:?}");
|
2020-03-30 15:04:46 -07:00
|
|
|
}
|
|
|
|
}
|
2020-03-31 20:47:43 -07:00
|
|
|
Command::Balance(args) => {
|
2020-03-30 15:04:46 -07:00
|
|
|
let addresses = stake_accounts::derive_stake_account_addresses(
|
2020-03-31 20:47:43 -07:00
|
|
|
&args.base_pubkey,
|
|
|
|
args.num_accounts,
|
2020-03-30 15:04:46 -07:00
|
|
|
);
|
|
|
|
let balances = get_balances(&client, addresses)?;
|
|
|
|
let lamports: u64 = balances.into_iter().map(|(_, bal)| bal).sum();
|
|
|
|
let sol = lamports_to_sol(lamports);
|
2022-12-06 06:30:06 -08:00
|
|
|
println!("{sol} SOL");
|
2020-03-30 15:04:46 -07:00
|
|
|
}
|
2020-03-31 20:47:43 -07:00
|
|
|
Command::Authorize(args) => {
|
|
|
|
process_authorize_stake_accounts(&client, &args)?;
|
2020-03-30 15:04:46 -07:00
|
|
|
}
|
2020-04-30 16:56:37 -07:00
|
|
|
Command::SetLockup(args) => {
|
|
|
|
process_lockup_stake_accounts(&client, &args)?;
|
|
|
|
}
|
2020-03-31 20:47:43 -07:00
|
|
|
Command::Rebase(args) => {
|
|
|
|
process_rebase_stake_accounts(&client, &args)?;
|
2020-03-30 15:04:46 -07:00
|
|
|
}
|
2020-03-31 20:47:43 -07:00
|
|
|
Command::Move(args) => {
|
|
|
|
process_move_stake_accounts(&client, &args)?;
|
2020-03-30 15:04:46 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|