2020-11-19 09:32:31 -08:00
|
|
|
use crate::{
|
|
|
|
args::{DistributeTokensArgs, SplTokenArgs},
|
|
|
|
commands::{Allocation, Error, FundingSource},
|
|
|
|
};
|
|
|
|
use console::style;
|
|
|
|
use solana_account_decoder::parse_token::{
|
2021-03-02 21:51:41 -08:00
|
|
|
pubkey_from_spl_token_v2_0, real_number_string, real_number_string_trimmed,
|
|
|
|
spl_token_v2_0_pubkey,
|
2020-11-19 09:32:31 -08:00
|
|
|
};
|
|
|
|
use solana_client::rpc_client::RpcClient;
|
|
|
|
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 fn update_token_args(client: &RpcClient, args: &mut Option<SplTokenArgs>) -> Result<(), Error> {
|
|
|
|
if let Some(spl_token_args) = args {
|
|
|
|
let sender_account = client
|
|
|
|
.get_account(&spl_token_args.token_account_address)
|
|
|
|
.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)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn update_decimals(client: &RpcClient, args: &mut Option<SplTokenArgs>) -> Result<(), Error> {
|
|
|
|
if let Some(spl_token_args) = args {
|
|
|
|
let mint_account = client.get_account(&spl_token_args.mint).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<Instruction> {
|
|
|
|
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()),
|
|
|
|
&[],
|
2020-11-25 16:00:49 -08:00
|
|
|
allocation.amount,
|
2020-11-19 09:32:31 -08:00
|
|
|
spl_token_args.decimals,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
instructions.push(spl_token_v2_0_instruction(spl_instruction));
|
|
|
|
instructions
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn check_spl_token_balances(
|
|
|
|
num_signatures: usize,
|
|
|
|
allocations: &[Allocation],
|
|
|
|
client: &RpcClient,
|
|
|
|
args: &DistributeTokensArgs,
|
|
|
|
created_accounts: u64,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let spl_token_args = args
|
|
|
|
.spl_token_args
|
|
|
|
.as_ref()
|
|
|
|
.expect("spl_token_args must be some");
|
2020-11-25 16:00:49 -08:00
|
|
|
let allocation_amount: u64 = allocations.iter().map(|x| x.amount).sum();
|
2020-11-19 09:32:31 -08:00
|
|
|
|
|
|
|
let fee_calculator = client.get_recent_blockhash()?.1;
|
|
|
|
let fees = fee_calculator
|
|
|
|
.lamports_per_signature
|
|
|
|
.checked_mul(num_signatures as u64)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let token_account_rent_exempt_balance =
|
|
|
|
client.get_minimum_balance_for_rent_exemption(SplTokenAccount::LEN)?;
|
|
|
|
let account_creation_amount = created_accounts * token_account_rent_exempt_balance;
|
|
|
|
let fee_payer_balance = client.get_balance(&args.fee_payer.pubkey())?;
|
|
|
|
if fee_payer_balance < fees + account_creation_amount {
|
|
|
|
return Err(Error::InsufficientFunds(
|
|
|
|
vec![FundingSource::FeePayer].into(),
|
2021-03-02 21:51:41 -08:00
|
|
|
lamports_to_sol(fees + account_creation_amount).to_string(),
|
2020-11-19 09:32:31 -08:00
|
|
|
));
|
|
|
|
}
|
|
|
|
let source_token_account = client
|
|
|
|
.get_account(&spl_token_args.token_account_address)
|
|
|
|
.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(),
|
2021-03-02 21:51:41 -08:00
|
|
|
real_number_string_trimmed(allocation_amount, spl_token_args.decimals),
|
2020-11-19 09:32:31 -08:00
|
|
|
));
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn print_token_balances(
|
|
|
|
client: &RpcClient,
|
|
|
|
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))
|
|
|
|
.unwrap_or_default();
|
2020-11-25 16:00:49 -08:00
|
|
|
let (actual, difference) = if let Ok(recipient_token) =
|
|
|
|
SplTokenAccount::unpack(&recipient_account.data)
|
|
|
|
{
|
2021-03-02 21:51:41 -08:00
|
|
|
let actual_ui_amount = real_number_string(recipient_token.amount, spl_token_args.decimals);
|
|
|
|
let delta_string =
|
|
|
|
real_number_string(recipient_token.amount - expected, spl_token_args.decimals);
|
2020-11-25 16:00:49 -08:00
|
|
|
(
|
2021-03-02 21:51:41 -08:00
|
|
|
style(format!("{:>24}", actual_ui_amount)),
|
|
|
|
format!("{:>24}", delta_string),
|
2020-11-25 16:00:49 -08:00
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
style("Associated token account not yet created".to_string()).yellow(),
|
|
|
|
"".to_string(),
|
|
|
|
)
|
|
|
|
};
|
2020-11-19 09:32:31 -08:00
|
|
|
println!(
|
2021-03-02 21:51:41 -08:00
|
|
|
"{:<44} {:>24} {:>24} {:>24}",
|
2020-11-25 16:00:49 -08:00
|
|
|
allocation.recipient,
|
2021-03-02 21:51:41 -08:00
|
|
|
real_number_string(expected, spl_token_args.decimals),
|
2020-11-25 16:00:49 -08:00
|
|
|
actual,
|
|
|
|
difference,
|
2020-11-19 09:32:31 -08:00
|
|
|
);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
// The following unit tests were written for v1.4 using the ProgramTest framework, passing its
|
|
|
|
// BanksClient into the `solana-tokens` methods. With the revert to RpcClient in this module
|
|
|
|
// (https://github.com/solana-labs/solana/pull/13623), that approach was no longer viable.
|
|
|
|
// These tests were removed rather than rewritten to avoid accruing technical debt. Once a new
|
|
|
|
// rpc/client framework is implemented, they should be restored.
|
|
|
|
//
|
|
|
|
// async fn test_process_spl_token_allocations()
|
|
|
|
// async fn test_process_spl_token_transfer_amount_allocations()
|
|
|
|
// async fn test_check_spl_token_balances()
|
|
|
|
//
|
2020-11-30 18:55:18 -08:00
|
|
|
// https://github.com/solana-labs/solana/blob/5511d52c6284013a24ced10966d11d8f4585799e/tokens/src/spl_token.rs#L490-L685
|
2020-11-19 09:32:31 -08:00
|
|
|
}
|