From e7e7cbe632068b070284bd04a23b2a275f06d085 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Thu, 19 Nov 2020 10:32:27 -0700 Subject: [PATCH] v1.4: Distribute spl tokens (#13688) * 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 * Adjust solana_rbpf log level up in coverage * Use epsilon for allocation retention --- Cargo.lock | 19 +- account-decoder/src/parse_token.rs | 10 + scripts/coverage.sh | 2 +- tokens/Cargo.toml | 5 + tokens/README.md | 118 ++- tokens/src/arg_parser.rs | 166 ++++- tokens/src/args.rs | 9 + tokens/src/commands.rs | 275 +++++-- tokens/src/lib.rs | 2 + tokens/src/main.rs | 14 +- tokens/src/spl_token.rs | 698 ++++++++++++++++++ tokens/src/token_display.rs | 62 ++ .../fixtures/spl_associated_token_account.so | Bin 0 -> 128920 bytes tokens/tests/fixtures/spl_token.so | Bin 0 -> 160584 bytes transaction-status/src/parse_token.rs | 25 +- 15 files changed, 1321 insertions(+), 84 deletions(-) create mode 100644 tokens/src/spl_token.rs create mode 100644 tokens/src/token_display.rs create mode 100644 tokens/tests/fixtures/spl_associated_token_account.so create mode 100644 tokens/tests/fixtures/spl_token.so diff --git a/Cargo.lock b/Cargo.lock index d60986d255..78081d332d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4945,6 +4945,7 @@ dependencies = [ "indicatif", "pickledb", "serde", + "solana-account-decoder", "solana-banks-client", "solana-banks-server", "solana-clap-utils", @@ -4952,11 +4953,15 @@ dependencies = [ "solana-client", "solana-core", "solana-logger 1.4.10", + "solana-program-test", "solana-remote-wallet", "solana-runtime", "solana-sdk", "solana-stake-program", + "solana-transaction-status", "solana-version", + "spl-associated-token-account", + "spl-token", "tempfile", "thiserror", "tokio 0.3.2", @@ -5109,9 +5114,9 @@ dependencies = [ [[package]] name = "solana_rbpf" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a95dbe2b00920ac4e1524b7442cf5319f01e8fa5742930ac60148882fd7738b" +checksum = "14a45ec96d6902676708f52d180229ea3933df93eadb3e96e356377d467831b6" dependencies = [ "byteorder", "combine", @@ -5131,6 +5136,16 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spl-associated-token-account" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a25d15fe67b755f95c575ce074e6e39c809fea86b2edb1bf2ae8b0473d5a1d" +dependencies = [ + "solana-program 1.4.4", + "spl-token", +] + [[package]] name = "spl-memo" version = "2.0.0" diff --git a/account-decoder/src/parse_token.rs b/account-decoder/src/parse_token.rs index 1592293001..2b416a519b 100644 --- a/account-decoder/src/parse_token.rs +++ b/account-decoder/src/parse_token.rs @@ -23,6 +23,16 @@ pub fn spl_token_v2_0_native_mint() -> Pubkey { Pubkey::from_str(&spl_token_v2_0::native_mint::id().to_string()).unwrap() } +// A helper function to convert a solana_sdk::pubkey::Pubkey to spl_sdk::pubkey::Pubkey +pub fn spl_token_v2_0_pubkey(pubkey: &Pubkey) -> SplTokenPubkey { + SplTokenPubkey::from_str(&pubkey.to_string()).unwrap() +} + +// A helper function to convert a spl_sdk::pubkey::Pubkey to solana_sdk::pubkey::Pubkey +pub fn pubkey_from_spl_token_v2_0(pubkey: &SplTokenPubkey) -> Pubkey { + Pubkey::from_str(&pubkey.to_string()).unwrap() +} + pub fn parse_token( data: &[u8], mint_decimals: Option, diff --git a/scripts/coverage.sh b/scripts/coverage.sh index 8bfa5831f1..449d3999c0 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -53,7 +53,7 @@ if [[ -n $CI || -z $1 ]]; then fi RUST_LOG=solana=trace _ "$cargo" nightly test --target-dir target/cov --no-run "${packages[@]}" -if RUST_LOG=solana=trace _ "$cargo" nightly test --target-dir target/cov "${packages[@]}" 2> target/cov/coverage-stderr.log; then +if RUST_LOG=solana=trace,solana_rbpf::vm=debug _ "$cargo" nightly test --target-dir target/cov "${packages[@]}" 2> target/cov/coverage-stderr.log; then test_status=0 else test_status=$? diff --git a/tokens/Cargo.toml b/tokens/Cargo.toml index ec2427ffe6..4b04639a70 100644 --- a/tokens/Cargo.toml +++ b/tokens/Cargo.toml @@ -18,6 +18,7 @@ indexmap = "1.5.1" indicatif = "0.15.0" pickledb = "0.4.1" serde = { version = "1.0", features = ["derive"] } +solana-account-decoder = { path = "../account-decoder", version = "1.4.10" } solana-banks-client = { path = "../banks-client", version = "1.4.10" } solana-clap-utils = { path = "../clap-utils", version = "1.4.10" } solana-cli-config = { path = "../cli-config", version = "1.4.10" } @@ -26,7 +27,10 @@ solana-remote-wallet = { path = "../remote-wallet", version = "1.4.10" } solana-runtime = { path = "../runtime", version = "1.4.10" } solana-sdk = { path = "../sdk", version = "1.4.10" } solana-stake-program = { path = "../programs/stake", version = "1.4.10" } +solana-transaction-status = { path = "../transaction-status", version = "1.4.10" } solana-version = { path = "../version", version = "1.4.10" } +spl-associated-token-account-v1-0 = { package = "spl-associated-token-account", version = "=1.0.1" } +spl-token-v2-0 = { package = "spl-token", version = "=3.0.0", features = ["no-entrypoint"] } tempfile = "3.1.0" thiserror = "1.0" tokio = { version = "0.3", features = ["full"] } @@ -37,4 +41,5 @@ bincode = "1.3.1" solana-banks-server = { path = "../banks-server", version = "1.4.10" } solana-core = { path = "../core", version = "1.4.10" } solana-logger = { path = "../logger", version = "1.4.10" } +solana-program-test = { path = "../program-test", version = "1.4.10" } solana-runtime = { path = "../runtime", version = "1.4.10" } diff --git a/tokens/README.md b/tokens/README.md index bb4853f1ed..65fc83edc1 100644 --- a/tokens/README.md +++ b/tokens/README.md @@ -38,7 +38,7 @@ solana-tokens distribute-tokens --from --input-csv -- Example output: ```text -Recipient Expected Balance (◎) +Recipient Expected Balance 3ihfUy1n9gaqihM5bJCiTAGLgWc5zo3DqVUS6T736NLM 42 UKUcTXgbeTYh65RaVV5gSf6xBHevqHvAXMo3e8Q6np8k 43 ``` @@ -77,7 +77,7 @@ recipient,amount,lockup_date Example output: ```text -Recipient Expected Balance (◎) +Recipient Expected Balance 6Vo87BaDhp4v4GHwVDhw5huhxVF8CyxSXYtkUwVHbbPv 10 7aHDubg5FBYj1SgmyBgU3ZJdtfuqYCQsJQK2pTR5JUqr 42 ``` @@ -102,7 +102,7 @@ solana-tokens distribute-tokens --transfer-amount 10 --from --input-cs Example output: ```text -Recipient Expected Balance (◎) +Recipient Expected Balance 6Vo87BaDhp4v4GHwVDhw5huhxVF8CyxSXYtkUwVHbbPv 10 7aHDubg5FBYj1SgmyBgU3ZJdtfuqYCQsJQK2pTR5JUqr 10 CYRJWqiSjLitBAcRxPvWpgX3s5TvmN2SuRY3eEYypFvT 10 @@ -125,3 +125,115 @@ recipient address. That SOL can be used to pay transaction fees on staking operations such as delegating stake. The rest of the allocation is put in a stake account. The new stake account address is output in the transaction log. + +## Distribute SPL tokens + +Distributing SPL Tokens works very similarly to distributing SOL, but requires +the `--owner` parameter to sign transactions. Each recipient account must be an +system account that will own an Associated Token Account for the SPL Token mint. +The Associated Token Account will be created, and funded by the fee_payer, if it +does not already exist. + +Send SPL tokens to the recipients in ``. + +Example recipients.csv: + +```text +recipient,amount +CYRJWqiSjLitBAcRxPvWpgX3s5TvmN2SuRY3eEYypFvT,75.4 +C56nwrDVFpPrqwGYsTgQxv1ZraTh81H14PV4RHvZe36s,10 +7aHDubg5FBYj1SgmyBgU3ZJdtfuqYCQsJQK2pTR5JUqr,42.1 +7qQPmVAQxEQ5djPDCtiEUrxaPf8wKtLG1m6SB1brejJ1,20 +``` + +You can check the status of the recipients before beginning a distribution. You +must include the SPL Token mint address: + +```bash +solana-tokens spl-token-balances --mint
--input-csv +``` + +Example output: + +```text +Token: JDte736XZ1jGUtfAS32DLpBUWBR7WGSHy1hSZ36VRQ5V +Recipient Expected Balance Actual Balance Difference +CYRJWqiSjLitBAcRxPvWpgX3s5TvmN2SuRY3eEYypFvT 75.40 0.00 -75.40 +C56nwrDVFpPrqwGYsTgQxv1ZraTh81H14PV4RHvZe36s 10.000 Associated token account not yet created +7aHDubg5FBYj1SgmyBgU3ZJdtfuqYCQsJQK2pTR5JUqr 42.10 0.00 -42.10 +7qQPmVAQxEQ5djPDCtiEUrxaPf8wKtLG1m6SB1brejJ1 20.000 Associated token account not yet created +``` + +To run the distribution: + +```bash +solana-tokens distribute-spl-tokens --from
--owner \ + --input-csv --fee-payer +``` + +Example output: + +```text +Total in input_csv: 147.5 tokens +Distributed: 0 tokens +Undistributed: 147.5 tokens +Total: 147.5 tokens +Recipient Expected Balance +CYRJWqiSjLitBAcRxPvWpgX3s5TvmN2SuRY3eEYypFvT 75.400 +C56nwrDVFpPrqwGYsTgQxv1ZraTh81H14PV4RHvZe36s 10.000 +7aHDubg5FBYj1SgmyBgU3ZJdtfuqYCQsJQK2pTR5JUqr 42.100 +7qQPmVAQxEQ5djPDCtiEUrxaPf8wKtLG1m6SB1brejJ1 20.000 +``` + +### Calculate what tokens should be sent + +As with SOL, you can List the differences between a list of expected +distributions and the record of what transactions have already been sent using +the `--dry-run` parameter, or `solana-tokens balances`. + +Example updated recipients.csv: + +```text +recipient,amount +CYRJWqiSjLitBAcRxPvWpgX3s5TvmN2SuRY3eEYypFvT,100 +C56nwrDVFpPrqwGYsTgQxv1ZraTh81H14PV4RHvZe36s,100 +7aHDubg5FBYj1SgmyBgU3ZJdtfuqYCQsJQK2pTR5JUqr,100 +7qQPmVAQxEQ5djPDCtiEUrxaPf8wKtLG1m6SB1brejJ1,100 +``` + +Using dry-run: + +```bash +solana-tokens distribute-tokens --dry-run --input-csv +``` + +Example output: + +```text +Total in input_csv: 400 tokens +Distributed: 147.5 tokens +Undistributed: 252.5 tokens +Total: 400 tokens +Recipient Expected Balance +CYRJWqiSjLitBAcRxPvWpgX3s5TvmN2SuRY3eEYypFvT 24.600 +C56nwrDVFpPrqwGYsTgQxv1ZraTh81H14PV4RHvZe36s 90.000 +7aHDubg5FBYj1SgmyBgU3ZJdtfuqYCQsJQK2pTR5JUqr 57.900 +7qQPmVAQxEQ5djPDCtiEUrxaPf8wKtLG1m6SB1brejJ1 80.000 +``` + +Or: + +```bash +solana-tokens balances --mint
--input-csv +``` + +Example output: + +```text +Token: JDte736XZ1jGUtfAS32DLpBUWBR7WGSHy1hSZ36VRQ5V +Recipient Expected Balance Actual Balance Difference +CYRJWqiSjLitBAcRxPvWpgX3s5TvmN2SuRY3eEYypFvT 100.000 75.400 -24.600 +C56nwrDVFpPrqwGYsTgQxv1ZraTh81H14PV4RHvZe36s 100.000 10.000 -90.000 +7aHDubg5FBYj1SgmyBgU3ZJdtfuqYCQsJQK2pTR5JUqr 100.000 42.100 -57.900 +7qQPmVAQxEQ5djPDCtiEUrxaPf8wKtLG1m6SB1brejJ1 100.000 20.000 -80.000 +``` diff --git a/tokens/src/arg_parser.rs b/tokens/src/arg_parser.rs index 35f6b54829..710d03b666 100644 --- a/tokens/src/arg_parser.rs +++ b/tokens/src/arg_parser.rs @@ -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> { + 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> { + 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)) } diff --git a/tokens/src/args.rs b/tokens/src/args.rs index 9d161f66fd..93b2f53f3a 100644 --- a/tokens/src/args.rs +++ b/tokens/src/args.rs @@ -8,6 +8,7 @@ pub struct DistributeTokensArgs { pub sender_keypair: Box, pub fee_payer: Box, pub stake_args: Option, + pub spl_token_args: Option, pub transfer_amount: Option, } @@ -19,8 +20,16 @@ pub struct StakeArgs { pub lockup_authority: Option>, } +#[derive(Default)] +pub struct SplTokenArgs { + pub token_account_address: Pubkey, + pub mint: Pubkey, + pub decimals: u8, +} + pub struct BalancesArgs { pub input_csv: String, + pub spl_token_args: Option, } pub struct TransactionLogArgs { diff --git a/tokens/src/commands.rs b/tokens/src/commands.rs index efb7d36df6..92964be373 100644 --- a/tokens/src/commands.rs +++ b/tokens/src/commands.rs @@ -1,5 +1,9 @@ -use crate::args::{BalancesArgs, DistributeTokensArgs, StakeArgs, TransactionLogArgs}; -use crate::db::{self, TransactionInfo}; +use crate::{ + args::{BalancesArgs, DistributeTokensArgs, StakeArgs, TransactionLogArgs}, + db::{self, TransactionInfo}, + spl_token::*, + token_display::Token, +}; use chrono::prelude::*; use console::style; use csv::{ReaderBuilder, Trim}; @@ -7,6 +11,7 @@ use indexmap::IndexMap; use indicatif::{ProgressBar, ProgressStyle}; use pickledb::PickleDb; use serde::{Deserialize, Serialize}; +use solana_account_decoder::parse_token::{pubkey_from_spl_token_v2_0, spl_token_v2_0_pubkey}; use solana_banks_client::{BanksClient, BanksClientExt}; use solana_sdk::{ commitment_config::CommitmentLevel, @@ -22,6 +27,8 @@ use solana_stake_program::{ stake_instruction::{self, LockupArgs}, stake_state::{Authorized, Lockup, StakeAuthorize}, }; +use spl_associated_token_account_v1_0::get_associated_token_address; +use spl_token_v2_0::solana_program::program_error::ProgramError; use std::{ cmp::{self}, io, @@ -30,15 +37,16 @@ use std::{ use tokio::time::sleep; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] -struct Allocation { - recipient: String, - amount: f64, - lockup_date: String, +pub struct Allocation { + pub recipient: String, + pub amount: f64, + pub lockup_date: String, } #[derive(Debug, PartialEq)] pub enum FundingSource { FeePayer, + SplTokenAccount, StakeAccount, SystemAccount, } @@ -83,6 +91,8 @@ pub enum Error { MissingLockupAuthority, #[error("insufficient funds in {0:?}, requires {1} SOL")] InsufficientFunds(FundingSources, f64), + #[error("Program error")] + ProgramError(#[from] ProgramError), } fn merge_allocations(allocations: &[Allocation]) -> Vec { @@ -125,7 +135,7 @@ fn apply_previous_transactions( } } } - allocations.retain(|x| x.amount > 0.5); + allocations.retain(|x| x.amount > f64::EPSILON); } async fn transfer( @@ -150,8 +160,9 @@ fn distribution_instructions( new_stake_account_address: &Pubkey, args: &DistributeTokensArgs, lockup_date: Option>, + do_create_associated_token_account: bool, ) -> Vec { - if args.stake_args.is_none() { + if args.stake_args.is_none() && args.spl_token_args.is_none() { let from = args.sender_keypair.pubkey(); let to = allocation.recipient.parse().unwrap(); let lamports = sol_to_lamports(allocation.amount); @@ -159,6 +170,10 @@ fn distribution_instructions( return vec![instruction]; } + if args.spl_token_args.is_some() { + return build_spl_token_instructions(allocation, args, do_create_associated_token_account); + } + let stake_args = args.stake_args.as_ref().unwrap(); let unlocked_sol = stake_args.unlocked_sol; let sender_pubkey = args.sender_keypair.pubkey(); @@ -225,34 +240,65 @@ async fn distribute_allocations( args: &DistributeTokensArgs, ) -> Result<(), Error> { type StakeExtras = Vec<(Keypair, Option>)>; - let (messages, stake_extras): (Vec, StakeExtras) = allocations - .iter() - .map(|allocation| { - let new_stake_account_keypair = Keypair::new(); - let lockup_date = if allocation.lockup_date == "" { - None - } else { - Some(allocation.lockup_date.parse::>().unwrap()) - }; + let mut messages: Vec = vec![]; + let mut stake_extras: StakeExtras = vec![]; + let mut created_accounts = 0; + for allocation in allocations.iter() { + let new_stake_account_keypair = Keypair::new(); + let lockup_date = if allocation.lockup_date == "" { + None + } else { + Some(allocation.lockup_date.parse::>().unwrap()) + }; - println!("{:<44} {:>24.9}", allocation.recipient, allocation.amount); - let instructions = distribution_instructions( - allocation, - &new_stake_account_keypair.pubkey(), - args, - lockup_date, - ); - let fee_payer_pubkey = args.fee_payer.pubkey(); - let message = Message::new(&instructions, Some(&fee_payer_pubkey)); - (message, (new_stake_account_keypair, lockup_date)) - }) - .unzip(); + let (decimals, do_create_associated_token_account) = + if let Some(spl_token_args) = &args.spl_token_args { + let wallet_address = allocation.recipient.parse().unwrap(); + let associated_token_address = get_associated_token_address( + &wallet_address, + &spl_token_v2_0_pubkey(&spl_token_args.mint), + ); + let do_create_associated_token_account = client + .get_account(pubkey_from_spl_token_v2_0(&associated_token_address)) + .await? + .is_none(); + if do_create_associated_token_account { + created_accounts += 1; + } + ( + spl_token_args.decimals as usize, + do_create_associated_token_account, + ) + } else { + (9, false) + }; + println!( + "{:<44} {:>24.2$}", + allocation.recipient, allocation.amount, decimals + ); + let instructions = distribution_instructions( + allocation, + &new_stake_account_keypair.pubkey(), + args, + lockup_date, + do_create_associated_token_account, + ); + let fee_payer_pubkey = args.fee_payer.pubkey(); + let message = Message::new(&instructions, Some(&fee_payer_pubkey)); + messages.push(message); + stake_extras.push((new_stake_account_keypair, lockup_date)); + } let num_signatures = messages .iter() .map(|message| message.header.num_required_signatures as usize) .sum(); - check_payer_balances(num_signatures, allocations, client, args).await?; + if args.spl_token_args.is_some() { + check_spl_token_balances(num_signatures, allocations, client, args, created_accounts) + .await?; + } else { + check_payer_balances(num_signatures, allocations, client, args).await?; + } for ((allocation, message), (new_stake_account_keypair, lockup_date)) in allocations.iter().zip(messages).zip(stake_extras) @@ -304,7 +350,11 @@ async fn distribute_allocations( Ok(()) } -fn read_allocations(input_csv: &str, transfer_amount: Option) -> io::Result> { +fn read_allocations( + input_csv: &str, + transfer_amount: Option, + require_lockup_heading: bool, +) -> io::Result> { let mut rdr = ReaderBuilder::new().trim(Trim::All).from_path(input_csv)?; let allocations = if let Some(amount) = transfer_amount { let recipients: Vec = rdr @@ -319,8 +369,21 @@ fn read_allocations(input_csv: &str, transfer_amount: Option) -> io::Result lockup_date: "".to_string(), }) .collect() - } else { + } else if require_lockup_heading { rdr.deserialize().map(|entry| entry.unwrap()).collect() + } else { + let recipients: Vec<(String, f64)> = rdr + .deserialize() + .map(|recipient| recipient.unwrap()) + .collect(); + recipients + .into_iter() + .map(|(recipient, amount)| Allocation { + recipient, + amount, + lockup_date: "".to_string(), + }) + .collect() }; Ok(allocations) } @@ -337,11 +400,17 @@ pub async fn process_allocations( client: &mut BanksClient, args: &DistributeTokensArgs, ) -> Result, Error> { - let mut allocations: Vec = read_allocations(&args.input_csv, args.transfer_amount)?; + let require_lockup_heading = args.stake_args.is_some(); + let mut allocations: Vec = read_allocations( + &args.input_csv, + args.transfer_amount, + require_lockup_heading, + )?; + let is_sol = args.spl_token_args.is_none(); - let starting_total_tokens: f64 = allocations.iter().map(|x| x.amount).sum(); + let starting_total_tokens = Token::from(allocations.iter().map(|x| x.amount).sum(), is_sol); println!( - "{} ◎{}", + "{} {}", style("Total in input_csv:").bold(), starting_total_tokens, ); @@ -359,27 +428,23 @@ pub async fn process_allocations( return Ok(confirmations); } - let distributed_tokens: f64 = transaction_infos.iter().map(|x| x.amount).sum(); - let undistributed_tokens: f64 = allocations.iter().map(|x| x.amount).sum(); - println!("{} ◎{}", style("Distributed:").bold(), distributed_tokens,); + let distributed_tokens = Token::from(transaction_infos.iter().map(|x| x.amount).sum(), is_sol); + let undistributed_tokens = Token::from(allocations.iter().map(|x| x.amount).sum(), is_sol); + println!("{} {}", style("Distributed:").bold(), distributed_tokens,); println!( - "{} ◎{}", + "{} {}", style("Undistributed:").bold(), undistributed_tokens, ); println!( - "{} ◎{}", + "{} {}", style("Total:").bold(), distributed_tokens + undistributed_tokens, ); println!( "{}", - style(format!( - "{:<44} {:>24}", - "Recipient", "Expected Balance (◎)" - )) - .bold() + style(format!("{:<44} {:>24}", "Recipient", "Expected Balance",)).bold() ); distribute_allocations(client, &mut db, &allocations, args).await?; @@ -563,33 +628,41 @@ async fn check_payer_balances( Ok(()) } -pub async fn process_balances( - client: &mut BanksClient, - args: &BalancesArgs, -) -> Result<(), csv::Error> { - let allocations: Vec = read_allocations(&args.input_csv, None)?; +pub async fn process_balances(client: &mut BanksClient, args: &BalancesArgs) -> Result<(), Error> { + let allocations: Vec = read_allocations(&args.input_csv, None, false)?; let allocations = merge_allocations(&allocations); + let token = if let Some(spl_token_args) = &args.spl_token_args { + spl_token_args.mint.to_string() + } else { + "◎".to_string() + }; + println!("{} {}", style("Token:").bold(), token); + println!( "{}", style(format!( "{:<44} {:>24} {:>24} {:>24}", - "Recipient", "Expected Balance (◎)", "Actual Balance (◎)", "Difference (◎)" + "Recipient", "Expected Balance", "Actual Balance", "Difference" )) .bold() ); for allocation in &allocations { - let address = allocation.recipient.parse().unwrap(); - let expected = lamports_to_sol(sol_to_lamports(allocation.amount)); - let actual = lamports_to_sol(client.get_balance(address).await.unwrap()); - println!( - "{:<44} {:>24.9} {:>24.9} {:>24.9}", - allocation.recipient, - expected, - actual, - actual - expected - ); + if let Some(spl_token_args) = &args.spl_token_args { + print_token_balances(client, allocation, spl_token_args).await?; + } else { + let address: Pubkey = allocation.recipient.parse().unwrap(); + let expected = lamports_to_sol(sol_to_lamports(allocation.amount)); + let actual = lamports_to_sol(client.get_balance(address).await.unwrap()); + println!( + "{:<44} {:>24.9} {:>24.9} {:>24.9}", + allocation.recipient, + expected, + actual, + actual - expected, + ); + } } Ok(()) @@ -665,6 +738,7 @@ pub async fn test_process_distribute_tokens_with_client( transaction_db: transaction_db.clone(), output_path: Some(output_path.clone()), stake_args: None, + spl_token_args: None, transfer_amount, }; let confirmations = process_allocations(client, &args).await.unwrap(); @@ -788,6 +862,7 @@ pub async fn test_process_distribute_stake_with_client( transaction_db: transaction_db.clone(), output_path: Some(output_path.clone()), stake_args: Some(stake_args), + spl_token_args: None, sender_keypair: Box::new(sender_keypair), transfer_amount: None, }; @@ -841,7 +916,7 @@ pub async fn test_process_distribute_stake_with_client( } #[cfg(test)] -mod tests { +pub(crate) mod tests { use super::*; use solana_banks_client::start_client; use solana_banks_server::banks_server::start_local_server; @@ -909,11 +984,78 @@ mod tests { wtr.flush().unwrap(); assert_eq!( - read_allocations(&input_csv, None).unwrap(), + read_allocations(&input_csv, None, false).unwrap(), + vec![allocation.clone()] + ); + assert_eq!( + read_allocations(&input_csv, None, true).unwrap(), vec![allocation] ); } + #[test] + fn test_read_allocations_no_lockup() { + let pubkey0 = solana_sdk::pubkey::new_rand(); + let pubkey1 = solana_sdk::pubkey::new_rand(); + let file = NamedTempFile::new().unwrap(); + let input_csv = file.path().to_str().unwrap().to_string(); + let mut wtr = csv::WriterBuilder::new().from_writer(file); + wtr.serialize(("recipient".to_string(), "amount".to_string())) + .unwrap(); + wtr.serialize((&pubkey0.to_string(), 42.0)).unwrap(); + wtr.serialize((&pubkey1.to_string(), 43.0)).unwrap(); + wtr.flush().unwrap(); + + let expected_allocations = vec![ + Allocation { + recipient: pubkey0.to_string(), + amount: 42.0, + lockup_date: "".to_string(), + }, + Allocation { + recipient: pubkey1.to_string(), + amount: 43.0, + lockup_date: "".to_string(), + }, + ]; + assert_eq!( + read_allocations(&input_csv, None, false).unwrap(), + expected_allocations + ); + } + + #[test] + #[should_panic] + fn test_read_allocations_malformed() { + let pubkey0 = solana_sdk::pubkey::new_rand(); + let pubkey1 = solana_sdk::pubkey::new_rand(); + let file = NamedTempFile::new().unwrap(); + let input_csv = file.path().to_str().unwrap().to_string(); + let mut wtr = csv::WriterBuilder::new().from_writer(file); + wtr.serialize(("recipient".to_string(), "amount".to_string())) + .unwrap(); + wtr.serialize((&pubkey0.to_string(), 42.0)).unwrap(); + wtr.serialize((&pubkey1.to_string(), 43.0)).unwrap(); + wtr.flush().unwrap(); + + let expected_allocations = vec![ + Allocation { + recipient: pubkey0.to_string(), + amount: 42.0, + lockup_date: "".to_string(), + }, + Allocation { + recipient: pubkey1.to_string(), + amount: 43.0, + lockup_date: "".to_string(), + }, + ]; + assert_eq!( + read_allocations(&input_csv, None, true).unwrap(), + expected_allocations + ); + } + #[test] fn test_read_allocations_transfer_amount() { let pubkey0 = solana_sdk::pubkey::new_rand(); @@ -948,7 +1090,7 @@ mod tests { }, ]; assert_eq!( - read_allocations(&input_csv, Some(amount)).unwrap(), + read_allocations(&input_csv, Some(amount), false).unwrap(), expected_allocations ); } @@ -1058,6 +1200,7 @@ mod tests { transaction_db: "".to_string(), output_path: None, stake_args: Some(stake_args), + spl_token_args: None, sender_keypair: Box::new(Keypair::new()), transfer_amount: None, }; @@ -1067,6 +1210,7 @@ mod tests { &new_stake_account_address, &args, Some(lockup_date), + false, ); let lockup_instruction = bincode::deserialize(&instructions[SET_LOCKUP_INDEX].data).unwrap(); @@ -1079,7 +1223,7 @@ mod tests { } } - fn tmp_file_path(name: &str, pubkey: &Pubkey) -> String { + pub fn tmp_file_path(name: &str, pubkey: &Pubkey) -> String { use std::env; let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string()); @@ -1106,6 +1250,7 @@ mod tests { transaction_db: "".to_string(), output_path: None, stake_args, + spl_token_args: None, transfer_amount: None, }; (allocations, args) diff --git a/tokens/src/lib.rs b/tokens/src/lib.rs index ad25864bbe..a864f200e7 100644 --- a/tokens/src/lib.rs +++ b/tokens/src/lib.rs @@ -2,3 +2,5 @@ pub mod arg_parser; pub mod args; pub mod commands; mod db; +pub mod spl_token; +pub mod token_display; diff --git a/tokens/src/main.rs b/tokens/src/main.rs index f677772bf8..a32e89ef24 100644 --- a/tokens/src/main.rs +++ b/tokens/src/main.rs @@ -1,6 +1,6 @@ use solana_banks_client::start_tcp_client; use solana_cli_config::{Config, CONFIG_FILE}; -use solana_tokens::{arg_parser::parse_args, args::Command, commands}; +use solana_tokens::{arg_parser::parse_args, args::Command, commands, spl_token}; use std::{env, error::Error, path::Path, process}; use tokio::runtime::Runtime; use url::Url; @@ -26,10 +26,18 @@ fn main() -> Result<(), Box> { let mut banks_client = runtime.block_on(start_tcp_client(&host_port))?; match command_args.command { - Command::DistributeTokens(args) => { + Command::DistributeTokens(mut args) => { + runtime.block_on(spl_token::update_token_args( + &mut banks_client, + &mut args.spl_token_args, + ))?; runtime.block_on(commands::process_allocations(&mut banks_client, &args))?; } - Command::Balances(args) => { + Command::Balances(mut args) => { + runtime.block_on(spl_token::update_decimals( + &mut banks_client, + &mut args.spl_token_args, + ))?; runtime.block_on(commands::process_balances(&mut banks_client, &args))?; } Command::TransactionLog(args) => { diff --git a/tokens/src/spl_token.rs b/tokens/src/spl_token.rs new file mode 100644 index 0000000000..b1e0f1dc31 --- /dev/null +++ b/tokens/src/spl_token.rs @@ -0,0 +1,698 @@ +use crate::{ + args::{DistributeTokensArgs, SplTokenArgs}, + commands::{Allocation, Error, FundingSource}, +}; +use console::style; +use solana_account_decoder::parse_token::{ + pubkey_from_spl_token_v2_0, spl_token_v2_0_pubkey, token_amount_to_ui_amount, +}; +use solana_banks_client::{BanksClient, BanksClientExt}; +use solana_sdk::{instruction::Instruction, native_token::lamports_to_sol}; +use solana_transaction_status::parse_token::spl_token_v2_0_instruction; +use spl_associated_token_account_v1_0::{ + create_associated_token_account, get_associated_token_address, +}; +use spl_token_v2_0::{ + solana_program::program_pack::Pack, + state::{Account as SplTokenAccount, Mint}, +}; + +pub async fn update_token_args( + client: &mut BanksClient, + args: &mut Option, +) -> Result<(), Error> { + if let Some(spl_token_args) = args { + let sender_account = client + .get_account(spl_token_args.token_account_address) + .await? + .unwrap_or_default(); + let mint_address = + pubkey_from_spl_token_v2_0(&SplTokenAccount::unpack(&sender_account.data)?.mint); + spl_token_args.mint = mint_address; + update_decimals(client, args).await?; + } + Ok(()) +} + +pub async fn update_decimals( + client: &mut BanksClient, + args: &mut Option, +) -> Result<(), Error> { + if let Some(spl_token_args) = args { + let mint_account = client + .get_account(spl_token_args.mint) + .await? + .unwrap_or_default(); + let mint = Mint::unpack(&mint_account.data)?; + spl_token_args.decimals = mint.decimals; + } + Ok(()) +} + +pub fn spl_token_amount(amount: f64, decimals: u8) -> u64 { + (amount * 10_usize.pow(decimals as u32) as f64) as u64 +} + +pub fn build_spl_token_instructions( + allocation: &Allocation, + args: &DistributeTokensArgs, + do_create_associated_token_account: bool, +) -> Vec { + let spl_token_args = args + .spl_token_args + .as_ref() + .expect("spl_token_args must be some"); + let wallet_address = allocation.recipient.parse().unwrap(); + let associated_token_address = get_associated_token_address( + &wallet_address, + &spl_token_v2_0_pubkey(&spl_token_args.mint), + ); + let mut instructions = vec![]; + if do_create_associated_token_account { + let create_associated_token_account_instruction = create_associated_token_account( + &spl_token_v2_0_pubkey(&args.fee_payer.pubkey()), + &wallet_address, + &spl_token_v2_0_pubkey(&spl_token_args.mint), + ); + instructions.push(spl_token_v2_0_instruction( + create_associated_token_account_instruction, + )); + } + let spl_instruction = spl_token_v2_0::instruction::transfer_checked( + &spl_token_v2_0::id(), + &spl_token_v2_0_pubkey(&spl_token_args.token_account_address), + &spl_token_v2_0_pubkey(&spl_token_args.mint), + &associated_token_address, + &spl_token_v2_0_pubkey(&args.sender_keypair.pubkey()), + &[], + spl_token_amount(allocation.amount, spl_token_args.decimals), + spl_token_args.decimals, + ) + .unwrap(); + instructions.push(spl_token_v2_0_instruction(spl_instruction)); + instructions +} + +pub async fn check_spl_token_balances( + num_signatures: usize, + allocations: &[Allocation], + client: &mut BanksClient, + args: &DistributeTokensArgs, + created_accounts: u64, +) -> Result<(), Error> { + let spl_token_args = args + .spl_token_args + .as_ref() + .expect("spl_token_args must be some"); + let undistributed_tokens: f64 = allocations.iter().map(|x| x.amount).sum(); + let allocation_amount = spl_token_amount(undistributed_tokens, spl_token_args.decimals); + + let (fee_calculator, _blockhash, _last_valid_slot) = client.get_fees().await?; + let fees = fee_calculator + .lamports_per_signature + .checked_mul(num_signatures as u64) + .unwrap(); + + let rent = client.get_rent().await?; + let token_account_rent_exempt_balance = rent.minimum_balance(SplTokenAccount::LEN); + let account_creation_amount = created_accounts * token_account_rent_exempt_balance; + let fee_payer_balance = client.get_balance(args.fee_payer.pubkey()).await?; + if fee_payer_balance < fees + account_creation_amount { + return Err(Error::InsufficientFunds( + vec![FundingSource::FeePayer].into(), + lamports_to_sol(fees + account_creation_amount), + )); + } + let source_token_account = client + .get_account(spl_token_args.token_account_address) + .await? + .unwrap_or_default(); + let source_token = SplTokenAccount::unpack(&source_token_account.data)?; + if source_token.amount < allocation_amount { + return Err(Error::InsufficientFunds( + vec![FundingSource::SplTokenAccount].into(), + token_amount_to_ui_amount(allocation_amount, spl_token_args.decimals).ui_amount, + )); + } + Ok(()) +} + +pub async fn print_token_balances( + client: &mut BanksClient, + allocation: &Allocation, + spl_token_args: &SplTokenArgs, +) -> Result<(), Error> { + let address = allocation.recipient.parse().unwrap(); + let expected = allocation.amount; + let associated_token_address = get_associated_token_address( + &spl_token_v2_0_pubkey(&address), + &spl_token_v2_0_pubkey(&spl_token_args.mint), + ); + let recipient_account = client + .get_account(pubkey_from_spl_token_v2_0(&associated_token_address)) + .await? + .unwrap_or_default(); + let (actual, difference) = + if let Ok(recipient_token) = SplTokenAccount::unpack(&recipient_account.data) { + let actual = token_amount_to_ui_amount(recipient_token.amount, spl_token_args.decimals) + .ui_amount; + ( + style(format!( + "{:>24.1$}", + actual, spl_token_args.decimals as usize + )), + format!( + "{:>24.1$}", + actual - expected, + spl_token_args.decimals as usize + ), + ) + } else { + ( + style("Associated token account not yet created".to_string()).yellow(), + "".to_string(), + ) + }; + println!( + "{:<44} {:>24.4$} {:>24} {:>24}", + allocation.recipient, expected, actual, difference, spl_token_args.decimals as usize + ); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + commands::{process_allocations, tests::tmp_file_path, Allocation}, + db::{self, check_output_file}, + spl_token::spl_token_amount, + }; + use solana_account_decoder::parse_token::{spl_token_id_v2_0, spl_token_v2_0_pubkey}; + use solana_program_test::*; + use solana_sdk::{ + hash::Hash, + signature::{read_keypair_file, write_keypair_file, Keypair, Signer}, + system_instruction, + transaction::Transaction, + }; + use solana_transaction_status::parse_token::spl_token_v2_0_instruction; + use spl_associated_token_account_v1_0::{ + create_associated_token_account, get_associated_token_address, + }; + use spl_token_v2_0::{ + instruction::{initialize_account, initialize_mint, mint_to}, + solana_program::pubkey::Pubkey, + }; + use tempfile::{tempdir, NamedTempFile}; + + fn program_test() -> ProgramTest { + // Add SPL Associated Token program + let mut pc = ProgramTest::new( + "spl_associated_token_account", + pubkey_from_spl_token_v2_0(&spl_associated_token_account_v1_0::id()), + None, + ); + // Add SPL Token program + pc.add_program("spl_token", spl_token_id_v2_0(), None); + pc + } + + async fn initialize_test_mint( + banks_client: &mut BanksClient, + fee_payer: &Keypair, + mint: &Keypair, + decimals: u8, + recent_blockhash: Hash, + ) { + let rent = banks_client.get_rent().await.unwrap(); + let expected_mint_balance = rent.minimum_balance(Mint::LEN); + let instructions = vec![ + system_instruction::create_account( + &fee_payer.pubkey(), + &mint.pubkey(), + expected_mint_balance, + Mint::LEN as u64, + &spl_token_id_v2_0(), + ), + spl_token_v2_0_instruction( + initialize_mint( + &spl_token_v2_0::id(), + &spl_token_v2_0_pubkey(&mint.pubkey()), + &spl_token_v2_0_pubkey(&mint.pubkey()), + None, + decimals, + ) + .unwrap(), + ), + ]; + let mut transaction = Transaction::new_with_payer(&instructions, Some(&fee_payer.pubkey())); + transaction.sign(&[fee_payer, mint], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + } + + async fn initialize_token_account( + banks_client: &mut BanksClient, + fee_payer: &Keypair, + sender_account: &Keypair, + mint: &Keypair, + owner: &Keypair, + recent_blockhash: Hash, + ) { + let rent = banks_client.get_rent().await.unwrap(); + let expected_token_account_balance = rent.minimum_balance(SplTokenAccount::LEN); + let instructions = vec![ + system_instruction::create_account( + &fee_payer.pubkey(), + &sender_account.pubkey(), + expected_token_account_balance, + SplTokenAccount::LEN as u64, + &spl_token_id_v2_0(), + ), + spl_token_v2_0_instruction( + initialize_account( + &spl_token_v2_0::id(), + &spl_token_v2_0_pubkey(&sender_account.pubkey()), + &spl_token_v2_0_pubkey(&mint.pubkey()), + &spl_token_v2_0_pubkey(&owner.pubkey()), + ) + .unwrap(), + ), + ]; + let mut transaction = Transaction::new_with_payer(&instructions, Some(&fee_payer.pubkey())); + transaction.sign(&[fee_payer, sender_account], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + } + + async fn mint_to_account( + banks_client: &mut BanksClient, + fee_payer: &Keypair, + sender_account: &Keypair, + mint: &Keypair, + recent_blockhash: Hash, + ) { + let instructions = vec![spl_token_v2_0_instruction( + mint_to( + &spl_token_v2_0::id(), + &spl_token_v2_0_pubkey(&mint.pubkey()), + &spl_token_v2_0_pubkey(&sender_account.pubkey()), + &spl_token_v2_0_pubkey(&mint.pubkey()), + &[], + 200_000, + ) + .unwrap(), + )]; + let mut transaction = Transaction::new_with_payer(&instructions, Some(&fee_payer.pubkey())); + transaction.sign(&[fee_payer, mint], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + } + + async fn test_process_distribute_spl_tokens_with_client( + banks_client: &mut BanksClient, + fee_payer: Keypair, + transfer_amount: Option, + recent_blockhash: Hash, + ) { + // Initialize Token Mint + let decimals = 2; + let mint = Keypair::new(); + initialize_test_mint(banks_client, &fee_payer, &mint, decimals, recent_blockhash).await; + + // Initialize Sender Token Account and Mint + let sender_account = Keypair::new(); + let owner = Keypair::new(); + initialize_token_account( + banks_client, + &fee_payer, + &sender_account, + &mint, + &owner, + recent_blockhash, + ) + .await; + + mint_to_account( + banks_client, + &fee_payer, + &sender_account, + &mint, + recent_blockhash, + ) + .await; + + // Initialize one recipient Associated Token Account + let wallet_address_0 = Pubkey::new_unique(); + let instructions = vec![spl_token_v2_0_instruction(create_associated_token_account( + &spl_token_v2_0_pubkey(&fee_payer.pubkey()), + &wallet_address_0, + &spl_token_v2_0_pubkey(&mint.pubkey()), + ))]; + let mut transaction = Transaction::new_with_payer(&instructions, Some(&fee_payer.pubkey())); + transaction.sign(&[&fee_payer], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + + let wallet_address_1 = Pubkey::new_unique(); + + // Create allocations csv + let allocation_amount = if let Some(amount) = transfer_amount { + amount + } else { + 1000.0 + }; + let allocations_file = NamedTempFile::new().unwrap(); + let input_csv = allocations_file.path().to_str().unwrap().to_string(); + let mut wtr = csv::WriterBuilder::new().from_writer(allocations_file); + let allocation = Allocation { + recipient: wallet_address_0.to_string(), + amount: allocation_amount, + lockup_date: "".to_string(), + }; + wtr.serialize(&allocation).unwrap(); + let allocation = Allocation { + recipient: wallet_address_1.to_string(), + amount: allocation_amount, + lockup_date: "".to_string(), + }; + wtr.serialize(&allocation).unwrap(); + wtr.flush().unwrap(); + + let dir = tempdir().unwrap(); + let transaction_db = dir + .path() + .join("transactions.db") + .to_str() + .unwrap() + .to_string(); + + let output_file = NamedTempFile::new().unwrap(); + let output_path = output_file.path().to_str().unwrap().to_string(); + + let args = DistributeTokensArgs { + sender_keypair: Box::new(owner), + fee_payer: Box::new(fee_payer), + dry_run: false, + input_csv, + transaction_db: transaction_db.clone(), + output_path: Some(output_path.clone()), + stake_args: None, + spl_token_args: Some(SplTokenArgs { + token_account_address: sender_account.pubkey(), + mint: mint.pubkey(), + decimals, + }), + transfer_amount, + }; + + // Distribute Allocations + let confirmations = process_allocations(banks_client, &args).await.unwrap(); + assert_eq!(confirmations, None); + + let associated_token_address_0 = + get_associated_token_address(&wallet_address_0, &spl_token_v2_0_pubkey(&mint.pubkey())); + let associated_token_address_1 = + get_associated_token_address(&wallet_address_1, &spl_token_v2_0_pubkey(&mint.pubkey())); + + let transaction_infos = + db::read_transaction_infos(&db::open_db(&transaction_db, true).unwrap()); + assert_eq!(transaction_infos.len(), 2); + assert!(transaction_infos + .iter() + .any(|info| info.recipient == pubkey_from_spl_token_v2_0(&wallet_address_0))); + assert!(transaction_infos + .iter() + .any(|info| info.recipient == pubkey_from_spl_token_v2_0(&wallet_address_1))); + let expected_amount = spl_token_amount(allocation.amount, decimals); + assert_eq!( + spl_token_amount(transaction_infos[0].amount, decimals), + expected_amount + ); + assert_eq!( + spl_token_amount(transaction_infos[1].amount, decimals), + expected_amount + ); + + let recipient_account_0 = banks_client + .get_account(pubkey_from_spl_token_v2_0(&associated_token_address_0)) + .await + .unwrap() + .unwrap_or_default(); + assert_eq!( + SplTokenAccount::unpack(&recipient_account_0.data) + .unwrap() + .amount, + expected_amount, + ); + let recipient_account_1 = banks_client + .get_account(pubkey_from_spl_token_v2_0(&associated_token_address_1)) + .await + .unwrap() + .unwrap_or_default(); + assert_eq!( + SplTokenAccount::unpack(&recipient_account_1.data) + .unwrap() + .amount, + expected_amount, + ); + + check_output_file(&output_path, &db::open_db(&transaction_db, true).unwrap()); + + // Now, run it again, and check there's no double-spend. + process_allocations(banks_client, &args).await.unwrap(); + let transaction_infos = + db::read_transaction_infos(&db::open_db(&transaction_db, true).unwrap()); + assert_eq!(transaction_infos.len(), 2); + assert!(transaction_infos + .iter() + .any(|info| info.recipient == pubkey_from_spl_token_v2_0(&wallet_address_0))); + assert!(transaction_infos + .iter() + .any(|info| info.recipient == pubkey_from_spl_token_v2_0(&wallet_address_1))); + let expected_amount = spl_token_amount(allocation.amount, decimals); + assert_eq!( + spl_token_amount(transaction_infos[0].amount, decimals), + expected_amount + ); + assert_eq!( + spl_token_amount(transaction_infos[1].amount, decimals), + expected_amount + ); + + let recipient_account_0 = banks_client + .get_account(pubkey_from_spl_token_v2_0(&associated_token_address_0)) + .await + .unwrap() + .unwrap_or_default(); + assert_eq!( + SplTokenAccount::unpack(&recipient_account_0.data) + .unwrap() + .amount, + expected_amount, + ); + let recipient_account_1 = banks_client + .get_account(pubkey_from_spl_token_v2_0(&associated_token_address_1)) + .await + .unwrap() + .unwrap_or_default(); + assert_eq!( + SplTokenAccount::unpack(&recipient_account_1.data) + .unwrap() + .amount, + expected_amount, + ); + + check_output_file(&output_path, &db::open_db(&transaction_db, true).unwrap()); + } + + #[tokio::test] + async fn test_process_spl_token_allocations() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + test_process_distribute_spl_tokens_with_client( + &mut banks_client, + payer, + None, + recent_blockhash, + ) + .await; + } + + #[tokio::test] + async fn test_process_spl_token_transfer_amount_allocations() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + test_process_distribute_spl_tokens_with_client( + &mut banks_client, + payer, + Some(105.5), + recent_blockhash, + ) + .await; + } + + #[tokio::test] + async fn test_check_check_spl_token_balances() { + let (mut banks_client, payer, recent_blockhash) = program_test().start().await; + + let (fee_calculator, _, _) = banks_client.get_fees().await.unwrap(); + let signatures = 2; + let fees = fee_calculator.lamports_per_signature * signatures; + let fees_in_sol = lamports_to_sol(fees); + + let rent = banks_client.get_rent().await.unwrap(); + let expected_token_account_balance = rent.minimum_balance(SplTokenAccount::LEN); + let expected_token_account_balance_sol = lamports_to_sol(expected_token_account_balance); + + // Initialize Token Mint + let decimals = 2; + let mint = Keypair::new(); + initialize_test_mint(&mut banks_client, &payer, &mint, decimals, recent_blockhash).await; + + // Initialize Sender Token Account and Mint + let sender_account = Keypair::new(); + let owner = Keypair::new(); + let owner_keypair_file = tmp_file_path("keypair_file", &owner.pubkey()); + write_keypair_file(&owner, &owner_keypair_file).unwrap(); + + initialize_token_account( + &mut banks_client, + &payer, + &sender_account, + &mint, + &owner, + recent_blockhash, + ) + .await; + + let unfunded_fee_payer = Keypair::new(); + + let allocation_amount = 42.0; + let allocations = vec![Allocation { + recipient: Pubkey::new_unique().to_string(), + amount: allocation_amount, + lockup_date: "".to_string(), + }]; + let mut args = DistributeTokensArgs { + sender_keypair: read_keypair_file(&owner_keypair_file).unwrap().into(), + fee_payer: Box::new(unfunded_fee_payer), + dry_run: false, + input_csv: "".to_string(), + transaction_db: "".to_string(), + output_path: None, + stake_args: None, + spl_token_args: Some(SplTokenArgs { + token_account_address: sender_account.pubkey(), + mint: mint.pubkey(), + decimals, + }), + transfer_amount: None, + }; + + // Unfunded fee_payer + let err_result = check_spl_token_balances( + signatures as usize, + &allocations, + &mut banks_client, + &args, + 1, + ) + .await + .unwrap_err(); + if let Error::InsufficientFunds(sources, amount) = err_result { + assert_eq!(sources, vec![FundingSource::FeePayer].into()); + assert!( + (amount - (fees_in_sol + expected_token_account_balance_sol)).abs() < f64::EPSILON + ); + } else { + panic!("check_spl_token_balances should have errored"); + } + + // Unfunded sender SPL Token account + let fee_payer = Keypair::new(); + + let instruction = system_instruction::transfer( + &payer.pubkey(), + &fee_payer.pubkey(), + fees + expected_token_account_balance, + ); + let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey())); + transaction.sign(&[&payer], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + + args.fee_payer = Box::new(fee_payer); + let err_result = check_spl_token_balances( + signatures as usize, + &allocations, + &mut banks_client, + &args, + 1, + ) + .await + .unwrap_err(); + if let Error::InsufficientFunds(sources, amount) = err_result { + assert_eq!(sources, vec![FundingSource::SplTokenAccount].into()); + assert!((amount - allocation_amount).abs() < f64::EPSILON); + } else { + panic!("check_spl_token_balances should have errored"); + } + + // Fully funded payers + mint_to_account( + &mut banks_client, + &payer, + &sender_account, + &mint, + recent_blockhash, + ) + .await; + + check_spl_token_balances( + signatures as usize, + &allocations, + &mut banks_client, + &args, + 1, + ) + .await + .unwrap(); + + // Partially-funded fee payer can afford fees, but not to create Associated Token Account + let partially_funded_fee_payer = Keypair::new(); + + let instruction = system_instruction::transfer( + &payer.pubkey(), + &partially_funded_fee_payer.pubkey(), + fees, + ); + let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey())); + transaction.sign(&[&payer], recent_blockhash); + banks_client.process_transaction(transaction).await.unwrap(); + + args.fee_payer = Box::new(partially_funded_fee_payer); + let err_result = check_spl_token_balances( + signatures as usize, + &allocations, + &mut banks_client, + &args, + 1, + ) + .await + .unwrap_err(); + if let Error::InsufficientFunds(sources, amount) = err_result { + assert_eq!(sources, vec![FundingSource::FeePayer].into()); + assert!( + (amount - (fees_in_sol + expected_token_account_balance_sol)).abs() < f64::EPSILON + ); + } else { + panic!("check_spl_token_balances should have errored"); + } + + // Succeeds if no account creation required + check_spl_token_balances( + signatures as usize, + &allocations, + &mut banks_client, + &args, + 0, + ) + .await + .unwrap(); + } +} diff --git a/tokens/src/token_display.rs b/tokens/src/token_display.rs new file mode 100644 index 0000000000..ed71e696f2 --- /dev/null +++ b/tokens/src/token_display.rs @@ -0,0 +1,62 @@ +use std::{ + fmt::{Debug, Display, Formatter, Result}, + ops::Add, +}; + +const SOL_SYMBOL: &str = "◎"; + +#[derive(PartialEq)] +pub enum TokenType { + Sol, + SplToken, +} + +pub struct Token { + amount: f64, + token_type: TokenType, +} + +impl Token { + fn write_with_symbol(&self, f: &mut Formatter) -> Result { + match &self.token_type { + TokenType::Sol => write!(f, "{}{}", SOL_SYMBOL, self.amount,), + TokenType::SplToken => write!(f, "{} tokens", self.amount,), + } + } + + pub fn from(amount: f64, is_sol: bool) -> Self { + let token_type = if is_sol { + TokenType::Sol + } else { + TokenType::SplToken + }; + Self { amount, token_type } + } +} + +impl Display for Token { + fn fmt(&self, f: &mut Formatter) -> Result { + self.write_with_symbol(f) + } +} + +impl Debug for Token { + fn fmt(&self, f: &mut Formatter) -> Result { + self.write_with_symbol(f) + } +} + +impl Add for Token { + type Output = Token; + + fn add(self, other: Self) -> Self { + if self.token_type == other.token_type { + Self { + amount: self.amount + other.amount, + token_type: self.token_type, + } + } else { + self + } + } +} diff --git a/tokens/tests/fixtures/spl_associated_token_account.so b/tokens/tests/fixtures/spl_associated_token_account.so new file mode 100644 index 0000000000000000000000000000000000000000..c50d9191acee8a09a94eb4cc442c21dbebfaf322 GIT binary patch literal 128920 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 literal 0 HcmV?d00001 diff --git a/transaction-status/src/parse_token.rs b/transaction-status/src/parse_token.rs index 4336935db9..42cae39451 100644 --- a/transaction-status/src/parse_token.rs +++ b/transaction-status/src/parse_token.rs @@ -2,11 +2,14 @@ use crate::parse_instruction::{ check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum, }; use serde_json::{json, Map, Value}; -use solana_account_decoder::parse_token::token_amount_to_ui_amount; -use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey}; +use solana_account_decoder::parse_token::{pubkey_from_spl_token_v2_0, token_amount_to_ui_amount}; +use solana_sdk::{ + instruction::{AccountMeta, CompiledInstruction, Instruction}, + pubkey::Pubkey, +}; use spl_token_v2_0::{ instruction::{AuthorityType, TokenInstruction}, - solana_program::program_option::COption, + solana_program::{instruction::Instruction as SplTokenInstruction, program_option::COption}, }; pub fn parse_token( @@ -410,6 +413,22 @@ fn check_num_token_accounts(accounts: &[u8], num: usize) -> Result<(), ParseInst check_num_accounts(accounts, num, ParsableProgram::SplToken) } +pub fn spl_token_v2_0_instruction(instruction: SplTokenInstruction) -> Instruction { + Instruction { + program_id: pubkey_from_spl_token_v2_0(&instruction.program_id), + accounts: instruction + .accounts + .iter() + .map(|meta| AccountMeta { + pubkey: pubkey_from_spl_token_v2_0(&meta.pubkey), + is_signer: meta.is_signer, + is_writable: meta.is_writable, + }) + .collect(), + data: instruction.data, + } +} + #[cfg(test)] mod test { use super::*;