* Revert solana-tokens to RpcClient * Fixup check_payer_balances tests * Use RpcClient::new_with_commitment in other tests * Sneak in helper fn from #13820 Co-authored-by: Tyera Eulberg <tyera@solana.com> Co-authored-by: Greg Fitzgerald <greg@solana.com>
This commit is contained in:
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -4948,14 +4948,11 @@ dependencies = [
|
|||||||
"pickledb",
|
"pickledb",
|
||||||
"serde",
|
"serde",
|
||||||
"solana-account-decoder",
|
"solana-account-decoder",
|
||||||
"solana-banks-client",
|
|
||||||
"solana-banks-server",
|
|
||||||
"solana-clap-utils",
|
"solana-clap-utils",
|
||||||
"solana-cli-config",
|
"solana-cli-config",
|
||||||
"solana-client",
|
"solana-client",
|
||||||
"solana-core",
|
"solana-core",
|
||||||
"solana-logger 1.4.14",
|
"solana-logger 1.4.14",
|
||||||
"solana-program-test",
|
|
||||||
"solana-remote-wallet",
|
"solana-remote-wallet",
|
||||||
"solana-runtime",
|
"solana-runtime",
|
||||||
"solana-sdk",
|
"solana-sdk",
|
||||||
@ -4966,8 +4963,6 @@ dependencies = [
|
|||||||
"spl-token",
|
"spl-token",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio 0.3.2",
|
|
||||||
"url 2.1.1",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -19,7 +19,6 @@ indicatif = "0.15.0"
|
|||||||
pickledb = "0.4.1"
|
pickledb = "0.4.1"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
solana-account-decoder = { path = "../account-decoder", version = "1.4.14" }
|
solana-account-decoder = { path = "../account-decoder", version = "1.4.14" }
|
||||||
solana-banks-client = { path = "../banks-client", version = "1.4.14" }
|
|
||||||
solana-clap-utils = { path = "../clap-utils", version = "1.4.14" }
|
solana-clap-utils = { path = "../clap-utils", version = "1.4.14" }
|
||||||
solana-cli-config = { path = "../cli-config", version = "1.4.14" }
|
solana-cli-config = { path = "../cli-config", version = "1.4.14" }
|
||||||
solana-client = { path = "../client", version = "1.4.14" }
|
solana-client = { path = "../client", version = "1.4.14" }
|
||||||
@ -33,13 +32,8 @@ spl-associated-token-account-v1-0 = { package = "spl-associated-token-account",
|
|||||||
spl-token-v2-0 = { package = "spl-token", version = "=3.0.0", features = ["no-entrypoint"] }
|
spl-token-v2-0 = { package = "spl-token", version = "=3.0.0", features = ["no-entrypoint"] }
|
||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tokio = { version = "0.3", features = ["full"] }
|
|
||||||
url = "2.1"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bincode = "1.3.1"
|
bincode = "1.3.1"
|
||||||
solana-banks-server = { path = "../banks-server", version = "1.4.14" }
|
|
||||||
solana-core = { path = "../core", version = "1.4.14" }
|
solana-core = { path = "../core", version = "1.4.14" }
|
||||||
solana-logger = { path = "../logger", version = "1.4.14" }
|
solana-logger = { path = "../logger", version = "1.4.14" }
|
||||||
solana-program-test = { path = "../program-test", version = "1.4.14" }
|
|
||||||
solana-runtime = { path = "../runtime", version = "1.4.14" }
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,8 @@
|
|||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use pickledb::{error::Error, PickleDb, PickleDbDumpPolicy};
|
use pickledb::{error::Error, PickleDb, PickleDbDumpPolicy};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use solana_banks_client::TransactionStatus;
|
|
||||||
use solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature, transaction::Transaction};
|
use solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature, transaction::Transaction};
|
||||||
|
use solana_transaction_status::TransactionStatus;
|
||||||
use std::{cmp::Ordering, fs, io, path::Path};
|
use std::{cmp::Ordering, fs, io, path::Path};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
@ -306,6 +306,7 @@ mod tests {
|
|||||||
slot: 0,
|
slot: 0,
|
||||||
confirmations: Some(1),
|
confirmations: Some(1),
|
||||||
err: None,
|
err: None,
|
||||||
|
status: Ok(()),
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0)
|
update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0)
|
||||||
@ -332,6 +333,7 @@ mod tests {
|
|||||||
slot: 0,
|
slot: 0,
|
||||||
confirmations: None,
|
confirmations: None,
|
||||||
err: Some(TransactionError::AccountNotFound),
|
err: Some(TransactionError::AccountNotFound),
|
||||||
|
status: Ok(()),
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0)
|
update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0)
|
||||||
@ -355,6 +357,7 @@ mod tests {
|
|||||||
slot: 0,
|
slot: 0,
|
||||||
confirmations: None,
|
confirmations: None,
|
||||||
err: None,
|
err: None,
|
||||||
|
status: Ok(()),
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0)
|
update_finalized_transaction(&mut db, &signature, Some(transaction_status), 0, 0)
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
use solana_banks_client::start_tcp_client;
|
|
||||||
use solana_cli_config::{Config, CONFIG_FILE};
|
use solana_cli_config::{Config, CONFIG_FILE};
|
||||||
|
use solana_client::rpc_client::RpcClient;
|
||||||
use solana_tokens::{arg_parser::parse_args, args::Command, commands, spl_token};
|
use solana_tokens::{arg_parser::parse_args, args::Command, commands, spl_token};
|
||||||
use std::{env, error::Error, path::Path, process};
|
use std::{env, error::Error, path::Path, process};
|
||||||
use tokio::runtime::Runtime;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let command_args = parse_args(env::args_os())?;
|
let command_args = parse_args(env::args_os())?;
|
||||||
@ -18,27 +16,16 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
Config::default()
|
Config::default()
|
||||||
};
|
};
|
||||||
let json_rpc_url = command_args.url.unwrap_or(config.json_rpc_url);
|
let json_rpc_url = command_args.url.unwrap_or(config.json_rpc_url);
|
||||||
let rpc_banks_url = Config::compute_rpc_banks_url(&json_rpc_url);
|
let client = RpcClient::new(json_rpc_url);
|
||||||
let url = Url::parse(&rpc_banks_url)?;
|
|
||||||
let host_port = (url.host_str().unwrap(), url.port().unwrap());
|
|
||||||
|
|
||||||
let runtime = Runtime::new().unwrap();
|
|
||||||
let mut banks_client = runtime.block_on(start_tcp_client(&host_port))?;
|
|
||||||
|
|
||||||
match command_args.command {
|
match command_args.command {
|
||||||
Command::DistributeTokens(mut args) => {
|
Command::DistributeTokens(mut args) => {
|
||||||
runtime.block_on(spl_token::update_token_args(
|
spl_token::update_token_args(&client, &mut args.spl_token_args)?;
|
||||||
&mut banks_client,
|
commands::process_allocations(&client, &args)?;
|
||||||
&mut args.spl_token_args,
|
|
||||||
))?;
|
|
||||||
runtime.block_on(commands::process_allocations(&mut banks_client, &args))?;
|
|
||||||
}
|
}
|
||||||
Command::Balances(mut args) => {
|
Command::Balances(mut args) => {
|
||||||
runtime.block_on(spl_token::update_decimals(
|
spl_token::update_decimals(&client, &mut args.spl_token_args)?;
|
||||||
&mut banks_client,
|
commands::process_balances(&client, &args)?;
|
||||||
&mut args.spl_token_args,
|
|
||||||
))?;
|
|
||||||
runtime.block_on(commands::process_balances(&mut banks_client, &args))?;
|
|
||||||
}
|
}
|
||||||
Command::TransactionLog(args) => {
|
Command::TransactionLog(args) => {
|
||||||
commands::process_transaction_log(&args)?;
|
commands::process_transaction_log(&args)?;
|
||||||
|
@ -6,7 +6,7 @@ use console::style;
|
|||||||
use solana_account_decoder::parse_token::{
|
use solana_account_decoder::parse_token::{
|
||||||
pubkey_from_spl_token_v2_0, spl_token_v2_0_pubkey, token_amount_to_ui_amount,
|
pubkey_from_spl_token_v2_0, spl_token_v2_0_pubkey, token_amount_to_ui_amount,
|
||||||
};
|
};
|
||||||
use solana_banks_client::{BanksClient, BanksClientExt};
|
use solana_client::rpc_client::RpcClient;
|
||||||
use solana_sdk::{instruction::Instruction, native_token::lamports_to_sol};
|
use solana_sdk::{instruction::Instruction, native_token::lamports_to_sol};
|
||||||
use solana_transaction_status::parse_token::spl_token_v2_0_instruction;
|
use solana_transaction_status::parse_token::spl_token_v2_0_instruction;
|
||||||
use spl_associated_token_account_v1_0::{
|
use spl_associated_token_account_v1_0::{
|
||||||
@ -17,32 +17,22 @@ use spl_token_v2_0::{
|
|||||||
state::{Account as SplTokenAccount, Mint},
|
state::{Account as SplTokenAccount, Mint},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn update_token_args(
|
pub fn update_token_args(client: &RpcClient, args: &mut Option<SplTokenArgs>) -> Result<(), Error> {
|
||||||
client: &mut BanksClient,
|
|
||||||
args: &mut Option<SplTokenArgs>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
if let Some(spl_token_args) = args {
|
if let Some(spl_token_args) = args {
|
||||||
let sender_account = client
|
let sender_account = client
|
||||||
.get_account(spl_token_args.token_account_address)
|
.get_account(&spl_token_args.token_account_address)
|
||||||
.await?
|
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let mint_address =
|
let mint_address =
|
||||||
pubkey_from_spl_token_v2_0(&SplTokenAccount::unpack(&sender_account.data)?.mint);
|
pubkey_from_spl_token_v2_0(&SplTokenAccount::unpack(&sender_account.data)?.mint);
|
||||||
spl_token_args.mint = mint_address;
|
spl_token_args.mint = mint_address;
|
||||||
update_decimals(client, args).await?;
|
update_decimals(client, args)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_decimals(
|
pub fn update_decimals(client: &RpcClient, args: &mut Option<SplTokenArgs>) -> Result<(), Error> {
|
||||||
client: &mut BanksClient,
|
|
||||||
args: &mut Option<SplTokenArgs>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
if let Some(spl_token_args) = args {
|
if let Some(spl_token_args) = args {
|
||||||
let mint_account = client
|
let mint_account = client.get_account(&spl_token_args.mint).unwrap_or_default();
|
||||||
.get_account(spl_token_args.mint)
|
|
||||||
.await?
|
|
||||||
.unwrap_or_default();
|
|
||||||
let mint = Mint::unpack(&mint_account.data)?;
|
let mint = Mint::unpack(&mint_account.data)?;
|
||||||
spl_token_args.decimals = mint.decimals;
|
spl_token_args.decimals = mint.decimals;
|
||||||
}
|
}
|
||||||
@ -93,10 +83,10 @@ pub fn build_spl_token_instructions(
|
|||||||
instructions
|
instructions
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn check_spl_token_balances(
|
pub fn check_spl_token_balances(
|
||||||
num_signatures: usize,
|
num_signatures: usize,
|
||||||
allocations: &[Allocation],
|
allocations: &[Allocation],
|
||||||
client: &mut BanksClient,
|
client: &RpcClient,
|
||||||
args: &DistributeTokensArgs,
|
args: &DistributeTokensArgs,
|
||||||
created_accounts: u64,
|
created_accounts: u64,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
@ -106,16 +96,16 @@ pub async fn check_spl_token_balances(
|
|||||||
.expect("spl_token_args must be some");
|
.expect("spl_token_args must be some");
|
||||||
let allocation_amount: u64 = allocations.iter().map(|x| x.amount).sum();
|
let allocation_amount: u64 = allocations.iter().map(|x| x.amount).sum();
|
||||||
|
|
||||||
let (fee_calculator, _blockhash, _last_valid_slot) = client.get_fees().await?;
|
let fee_calculator = client.get_recent_blockhash()?.1;
|
||||||
let fees = fee_calculator
|
let fees = fee_calculator
|
||||||
.lamports_per_signature
|
.lamports_per_signature
|
||||||
.checked_mul(num_signatures as u64)
|
.checked_mul(num_signatures as u64)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let rent = client.get_rent().await?;
|
let token_account_rent_exempt_balance =
|
||||||
let token_account_rent_exempt_balance = rent.minimum_balance(SplTokenAccount::LEN);
|
client.get_minimum_balance_for_rent_exemption(SplTokenAccount::LEN)?;
|
||||||
let account_creation_amount = created_accounts * token_account_rent_exempt_balance;
|
let account_creation_amount = created_accounts * token_account_rent_exempt_balance;
|
||||||
let fee_payer_balance = client.get_balance(args.fee_payer.pubkey()).await?;
|
let fee_payer_balance = client.get_balance(&args.fee_payer.pubkey())?;
|
||||||
if fee_payer_balance < fees + account_creation_amount {
|
if fee_payer_balance < fees + account_creation_amount {
|
||||||
return Err(Error::InsufficientFunds(
|
return Err(Error::InsufficientFunds(
|
||||||
vec![FundingSource::FeePayer].into(),
|
vec![FundingSource::FeePayer].into(),
|
||||||
@ -123,8 +113,7 @@ pub async fn check_spl_token_balances(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
let source_token_account = client
|
let source_token_account = client
|
||||||
.get_account(spl_token_args.token_account_address)
|
.get_account(&spl_token_args.token_account_address)
|
||||||
.await?
|
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let source_token = SplTokenAccount::unpack(&source_token_account.data)?;
|
let source_token = SplTokenAccount::unpack(&source_token_account.data)?;
|
||||||
if source_token.amount < allocation_amount {
|
if source_token.amount < allocation_amount {
|
||||||
@ -136,8 +125,8 @@ pub async fn check_spl_token_balances(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn print_token_balances(
|
pub fn print_token_balances(
|
||||||
client: &mut BanksClient,
|
client: &RpcClient,
|
||||||
allocation: &Allocation,
|
allocation: &Allocation,
|
||||||
spl_token_args: &SplTokenArgs,
|
spl_token_args: &SplTokenArgs,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
@ -148,8 +137,7 @@ pub async fn print_token_balances(
|
|||||||
&spl_token_v2_0_pubkey(&spl_token_args.mint),
|
&spl_token_v2_0_pubkey(&spl_token_args.mint),
|
||||||
);
|
);
|
||||||
let recipient_account = client
|
let recipient_account = client
|
||||||
.get_account(pubkey_from_spl_token_v2_0(&associated_token_address))
|
.get_account(&pubkey_from_spl_token_v2_0(&associated_token_address))
|
||||||
.await?
|
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let (actual, difference) = if let Ok(recipient_token) =
|
let (actual, difference) = if let Ok(recipient_token) =
|
||||||
SplTokenAccount::unpack(&recipient_account.data)
|
SplTokenAccount::unpack(&recipient_account.data)
|
||||||
@ -188,498 +176,15 @@ pub async fn print_token_balances(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
// The following unit tests were written for v1.4 using the ProgramTest framework, passing its
|
||||||
use crate::{
|
// BanksClient into the `solana-tokens` methods. With the revert to RpcClient in this module
|
||||||
commands::{process_allocations, tests::tmp_file_path, Allocation},
|
// (https://github.com/solana-labs/solana/pull/13623), that approach was no longer viable.
|
||||||
db::{self, check_output_file},
|
// These tests were removed rather than rewritten to avoid accruing technical debt. Once a new
|
||||||
};
|
// rpc/client framework is implemented, they should be restored.
|
||||||
use solana_account_decoder::parse_token::{spl_token_id_v2_0, spl_token_v2_0_pubkey};
|
//
|
||||||
use solana_program_test::*;
|
// async fn test_process_spl_token_allocations()
|
||||||
use solana_sdk::{
|
// async fn test_process_spl_token_transfer_amount_allocations()
|
||||||
hash::Hash,
|
// async fn test_check_spl_token_balances()
|
||||||
signature::{read_keypair_file, write_keypair_file, Keypair, Signer},
|
//
|
||||||
system_instruction,
|
// https://github.com/solana-labs/solana/blob/5511d52c6284013a24ced10966d11d8f4585799e/tokens/src/spl_token.rs#L490-L685
|
||||||
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<u64>,
|
|
||||||
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 {
|
|
||||||
100_000
|
|
||||||
};
|
|
||||||
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);
|
|
||||||
wtr.write_record(&["recipient", "amount"]).unwrap();
|
|
||||||
wtr.write_record(&[wallet_address_0.to_string(), allocation_amount.to_string()])
|
|
||||||
.unwrap();
|
|
||||||
wtr.write_record(&[wallet_address_1.to_string(), allocation_amount.to_string()])
|
|
||||||
.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)));
|
|
||||||
assert_eq!(transaction_infos[0].amount, allocation_amount);
|
|
||||||
assert_eq!(transaction_infos[1].amount, allocation_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,
|
|
||||||
allocation_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,
|
|
||||||
allocation_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)));
|
|
||||||
assert_eq!(transaction_infos[0].amount, allocation_amount);
|
|
||||||
assert_eq!(transaction_infos[1].amount, allocation_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,
|
|
||||||
allocation_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,
|
|
||||||
allocation_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(10550),
|
|
||||||
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 = 4200;
|
|
||||||
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 - token_amount_to_ui_amount(allocation_amount, decimals).ui_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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use solana_banks_client::start_tcp_client;
|
use solana_client::rpc_client::RpcClient;
|
||||||
use solana_core::test_validator::TestValidator;
|
use solana_core::test_validator::TestValidator;
|
||||||
use solana_tokens::commands::test_process_distribute_tokens_with_client;
|
use solana_tokens::commands::test_process_distribute_tokens_with_client;
|
||||||
use std::fs::remove_dir_all;
|
use std::fs::remove_dir_all;
|
||||||
use tokio::runtime::Runtime;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_process_distribute_with_rpc_client() {
|
fn test_process_distribute_with_rpc_client() {
|
||||||
@ -15,10 +14,8 @@ fn test_process_distribute_with_rpc_client() {
|
|||||||
..
|
..
|
||||||
} = TestValidator::run();
|
} = TestValidator::run();
|
||||||
|
|
||||||
Runtime::new().unwrap().block_on(async {
|
let client = RpcClient::new_socket(leader_data.rpc);
|
||||||
let mut banks_client = start_tcp_client(leader_data.rpc_banks).await.unwrap();
|
test_process_distribute_tokens_with_client(&client, alice, None);
|
||||||
test_process_distribute_tokens_with_client(&mut banks_client, alice, None).await
|
|
||||||
});
|
|
||||||
|
|
||||||
// Explicit cleanup, otherwise "pure virtual method called" crash in Docker
|
// Explicit cleanup, otherwise "pure virtual method called" crash in Docker
|
||||||
server.close().unwrap();
|
server.close().unwrap();
|
||||||
|
Reference in New Issue
Block a user