Distribute spl tokens (#13559)

* Add helpers to covert between sdk types

* Add distribute-spl-tokens to args and arg-parsing

* Build spl-token transfer-checked instructions

* Check spl-token balances properly

* Add display handling to support spl-token

* Small refactor to allow failures in allocation iter

* Use Associated Token Account for spl-token distributions

* Add spl token support to balances command

* Update readme

* Add spl-token tests

* Rename spl-tokens file

* Move a couple more things out of commands

* Stop requiring lockup_date heading for non-stake distributions

* Use epsilon for allocation retention
This commit is contained in:
Tyera Eulberg
2020-11-19 10:32:31 -07:00
committed by GitHub
parent 1ffab5de77
commit 2ef4369237
12 changed files with 793 additions and 75 deletions

View File

@@ -1,11 +1,11 @@
use crate::args::{
Args, BalancesArgs, Command, DistributeTokensArgs, StakeArgs, TransactionLogArgs,
Args, BalancesArgs, Command, DistributeTokensArgs, SplTokenArgs, StakeArgs, TransactionLogArgs,
};
use clap::{
crate_description, crate_name, value_t, value_t_or_exit, App, Arg, ArgMatches, SubCommand,
};
use solana_clap_utils::{
input_parsers::value_of,
input_parsers::{pubkey_of_signer, value_of},
input_validators::{is_amount, is_valid_pubkey, is_valid_signer},
keypair::{pubkey_from_path, signer_from_path},
};
@@ -42,7 +42,7 @@ where
)
.subcommand(
SubCommand::with_name("distribute-tokens")
.about("Distribute tokens")
.about("Distribute SOL")
.arg(
Arg::with_name("db_path")
.long("db-path")
@@ -201,6 +201,78 @@ where
.help("Fee payer"),
),
)
.subcommand(
SubCommand::with_name("distribute-spl-tokens")
.about("Distribute SPL tokens")
.arg(
Arg::with_name("db_path")
.long("db-path")
.required(true)
.takes_value(true)
.value_name("FILE")
.help(
"Location for storing distribution database. \
The database is used for tracking transactions as they are finalized \
and preventing double spends.",
),
)
.arg(
Arg::with_name("input_csv")
.long("input-csv")
.required(true)
.takes_value(true)
.value_name("FILE")
.help("Allocations CSV file"),
)
.arg(
Arg::with_name("dry_run")
.long("dry-run")
.help("Do not execute any transfers"),
)
.arg(
Arg::with_name("transfer_amount")
.long("transfer-amount")
.takes_value(true)
.value_name("AMOUNT")
.validator(is_amount)
.help("The amount of SPL tokens to send to each recipient"),
)
.arg(
Arg::with_name("output_path")
.long("output-path")
.short("o")
.value_name("FILE")
.takes_value(true)
.help("Write the transaction log to this file"),
)
.arg(
Arg::with_name("token_account_address")
.long("from")
.required(true)
.takes_value(true)
.value_name("TOKEN_ACCOUNT_ADDRESS")
.validator(is_valid_pubkey)
.help("SPL token account to send from"),
)
.arg(
Arg::with_name("token_owner")
.long("owner")
.required(true)
.takes_value(true)
.value_name("TOKEN_ACCOUNT_OWNER_KEYPAIR")
.validator(is_valid_signer)
.help("SPL token account owner"),
)
.arg(
Arg::with_name("fee_payer")
.long("fee-payer")
.required(true)
.takes_value(true)
.value_name("KEYPAIR")
.validator(is_valid_signer)
.help("Fee payer"),
),
)
.subcommand(
SubCommand::with_name("balances")
.about("Balance of each account")
@@ -213,6 +285,27 @@ where
.help("Allocations CSV file"),
),
)
.subcommand(
SubCommand::with_name("spl-token-balances")
.about("Balance of SPL token associated accounts")
.arg(
Arg::with_name("input_csv")
.long("input-csv")
.required(true)
.takes_value(true)
.value_name("FILE")
.help("Allocations CSV file"),
)
.arg(
Arg::with_name("mint_address")
.long("mint")
.required(true)
.takes_value(true)
.value_name("MINT_ADDRESS")
.validator(is_valid_pubkey)
.help("SPL token mint of distribution"),
),
)
.subcommand(
SubCommand::with_name("transaction-log")
.about("Print the database to a CSV file")
@@ -266,6 +359,7 @@ fn parse_distribute_tokens_args(
sender_keypair,
fee_payer,
stake_args: None,
spl_token_args: None,
transfer_amount: value_of(matches, "transfer_amount"),
})
}
@@ -342,14 +436,68 @@ fn parse_distribute_stake_args(
sender_keypair,
fee_payer,
stake_args: Some(stake_args),
spl_token_args: None,
transfer_amount: None,
})
}
fn parse_balances_args(matches: &ArgMatches<'_>) -> BalancesArgs {
BalancesArgs {
fn parse_distribute_spl_tokens_args(
matches: &ArgMatches<'_>,
) -> Result<DistributeTokensArgs, Box<dyn Error>> {
let mut wallet_manager = maybe_wallet_manager()?;
let signer_matches = ArgMatches::default(); // No default signer
let token_owner_str = value_t_or_exit!(matches, "token_owner", String);
let token_owner = signer_from_path(
&signer_matches,
&token_owner_str,
"owner",
&mut wallet_manager,
)?;
let fee_payer_str = value_t_or_exit!(matches, "fee_payer", String);
let fee_payer = signer_from_path(
&signer_matches,
&fee_payer_str,
"fee-payer",
&mut wallet_manager,
)?;
let token_account_address_str = value_t_or_exit!(matches, "token_account_address", String);
let token_account_address = pubkey_from_path(
&signer_matches,
&token_account_address_str,
"token account address",
&mut wallet_manager,
)?;
Ok(DistributeTokensArgs {
input_csv: value_t_or_exit!(matches, "input_csv", String),
}
transaction_db: value_t_or_exit!(matches, "db_path", String),
output_path: matches.value_of("output_path").map(|path| path.to_string()),
dry_run: matches.is_present("dry_run"),
sender_keypair: token_owner,
fee_payer,
stake_args: None,
spl_token_args: Some(SplTokenArgs {
token_account_address,
..SplTokenArgs::default()
}),
transfer_amount: value_of(matches, "transfer_amount"),
})
}
fn parse_balances_args(matches: &ArgMatches<'_>) -> Result<BalancesArgs, Box<dyn Error>> {
let mut wallet_manager = maybe_wallet_manager()?;
let spl_token_args =
pubkey_of_signer(matches, "mint_address", &mut wallet_manager)?.map(|mint| SplTokenArgs {
mint,
..SplTokenArgs::default()
});
Ok(BalancesArgs {
input_csv: value_t_or_exit!(matches, "input_csv", String),
spl_token_args,
})
}
fn parse_transaction_log_args(matches: &ArgMatches<'_>) -> TransactionLogArgs {
@@ -375,7 +523,11 @@ where
("distribute-stake", Some(matches)) => {
Command::DistributeTokens(parse_distribute_stake_args(matches)?)
}
("balances", Some(matches)) => Command::Balances(parse_balances_args(matches)),
("distribute-spl-tokens", Some(matches)) => {
Command::DistributeTokens(parse_distribute_spl_tokens_args(matches)?)
}
("balances", Some(matches)) => Command::Balances(parse_balances_args(matches)?),
("spl-token-balances", Some(matches)) => Command::Balances(parse_balances_args(matches)?),
("transaction-log", Some(matches)) => {
Command::TransactionLog(parse_transaction_log_args(matches))
}