Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 60117fd

Browse files
authored
token-cli: Figure out program_id dynamically (#3354)
* Resolve program id with mint info, use it where possible * Avoid using `config.program_id` when we can figure it out * Rename function
1 parent 37280ce commit 60117fd

File tree

3 files changed

+281
-190
lines changed

3 files changed

+281
-190
lines changed

token/cli/src/bench.rs

Lines changed: 31 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -205,14 +205,9 @@ pub(crate) async fn bench_process_command(
205205
let (owner_signer, owner) =
206206
config.signer_or_default(arg_matches, "owner", wallet_manager);
207207
signers.push(owner_signer);
208-
let from = pubkey_of_signer(arg_matches, "from", wallet_manager)
209-
.unwrap()
210-
.unwrap_or_else(|| {
211-
get_associated_token_address_with_program_id(&owner, &token, &config.program_id)
212-
});
213-
208+
let from = pubkey_of_signer(arg_matches, "from", wallet_manager).unwrap();
214209
command_deposit_into_or_withdraw_from(
215-
config, signers, &token, n, &owner, ui_amount, &from, true,
210+
config, signers, &token, n, &owner, ui_amount, from, true,
216211
)
217212
.await?;
218213
}
@@ -225,14 +220,9 @@ pub(crate) async fn bench_process_command(
225220
let (owner_signer, owner) =
226221
config.signer_or_default(arg_matches, "owner", wallet_manager);
227222
signers.push(owner_signer);
228-
let to = pubkey_of_signer(arg_matches, "to", wallet_manager)
229-
.unwrap()
230-
.unwrap_or_else(|| {
231-
get_associated_token_address_with_program_id(&owner, &token, &config.program_id)
232-
});
233-
223+
let to = pubkey_of_signer(arg_matches, "to", wallet_manager).unwrap();
234224
command_deposit_into_or_withdraw_from(
235-
config, signers, &token, n, &owner, ui_amount, &to, false,
225+
config, signers, &token, n, &owner, ui_amount, to, false,
236226
)
237227
.await?;
238228
}
@@ -266,15 +256,18 @@ fn get_token_addresses_with_seed(
266256
.collect()
267257
}
268258

269-
async fn is_valid_token(rpc_client: &RpcClient, token: &Pubkey) -> Result<(), Error> {
259+
async fn get_valid_mint_program_id(
260+
rpc_client: &RpcClient,
261+
token: &Pubkey,
262+
) -> Result<Pubkey, Error> {
270263
let mint_account = rpc_client
271-
.get_account_data(token)
264+
.get_account(token)
272265
.await
273266
.map_err(|err| format!("Token mint {} does not exist: {}", token, err))?;
274267

275-
Mint::unpack(&mint_account)
276-
.map(|_| ())
277-
.map_err(|err| format!("Invalid token mint {}: {}", token, err).into())
268+
Mint::unpack(&mint_account.data)
269+
.map_err(|err| format!("Invalid token mint {}: {}", token, err))?;
270+
Ok(mint_account.owner)
278271
}
279272

280273
async fn command_create_accounts(
@@ -287,16 +280,15 @@ async fn command_create_accounts(
287280
let rpc_client = &config.rpc_client;
288281

289282
println!("Scanning accounts...");
290-
is_valid_token(rpc_client, token).await?;
283+
let program_id = get_valid_mint_program_id(rpc_client, token).await?;
291284

292285
let minimum_balance_for_rent_exemption = rpc_client
293286
.get_minimum_balance_for_rent_exemption(Account::get_packed_len())
294287
.await?;
295288

296289
let mut lamports_required = 0;
297290

298-
let token_addresses_with_seed =
299-
get_token_addresses_with_seed(&config.program_id, token, owner, n);
291+
let token_addresses_with_seed = get_token_addresses_with_seed(&program_id, token, owner, n);
300292
let mut messages = vec![];
301293
for address_chunk in token_addresses_with_seed.chunks(100) {
302294
let accounts_chunk = rpc_client
@@ -315,9 +307,9 @@ async fn command_create_accounts(
315307
seed,
316308
minimum_balance_for_rent_exemption,
317309
Account::get_packed_len() as u64,
318-
&config.program_id,
310+
&program_id,
319311
),
320-
instruction::initialize_account(&config.program_id, address, token, owner)?,
312+
instruction::initialize_account(&program_id, address, token, owner)?,
321313
],
322314
Some(&config.fee_payer),
323315
));
@@ -338,10 +330,9 @@ async fn command_close_accounts(
338330
let rpc_client = &config.rpc_client;
339331

340332
println!("Scanning accounts...");
341-
is_valid_token(rpc_client, token).await?;
333+
let program_id = get_valid_mint_program_id(rpc_client, token).await?;
342334

343-
let token_addresses_with_seed =
344-
get_token_addresses_with_seed(&config.program_id, token, owner, n);
335+
let token_addresses_with_seed = get_token_addresses_with_seed(&program_id, token, owner, n);
345336
let mut messages = vec![];
346337
for address_chunk in token_addresses_with_seed.chunks(100) {
347338
let accounts_chunk = rpc_client
@@ -360,7 +351,7 @@ async fn command_close_accounts(
360351
} else {
361352
messages.push(Message::new(
362353
&[instruction::close_account(
363-
&config.program_id,
354+
&program_id,
364355
address,
365356
owner,
366357
owner,
@@ -389,23 +380,21 @@ async fn command_deposit_into_or_withdraw_from(
389380
n: usize,
390381
owner: &Pubkey,
391382
ui_amount: f64,
392-
from_or_to: &Pubkey,
383+
from_or_to: Option<Pubkey>,
393384
deposit_into: bool,
394385
) -> Result<(), Error> {
395386
let rpc_client = &config.rpc_client;
396387

397388
println!("Scanning accounts...");
398-
is_valid_token(rpc_client, token).await?;
389+
let program_id = get_valid_mint_program_id(rpc_client, token).await?;
399390

400-
let (mint_pubkey, decimals) =
401-
crate::resolve_mint_info(config, from_or_to, Some(*token), None).await?;
402-
if mint_pubkey != *token {
403-
return Err(format!("Source account {} is not a {} token", from_or_to, token).into());
404-
}
405-
let amount = spl_token::ui_amount_to_amount(ui_amount, decimals);
391+
let mint_info = config.get_mint_info(token, None).await?;
392+
let from_or_to = from_or_to
393+
.unwrap_or_else(|| get_associated_token_address_with_program_id(owner, token, &program_id));
394+
config.check_account(&from_or_to, Some(*token)).await?;
395+
let amount = spl_token::ui_amount_to_amount(ui_amount, mint_info.decimals);
406396

407-
let token_addresses_with_seed =
408-
get_token_addresses_with_seed(&config.program_id, token, owner, n);
397+
let token_addresses_with_seed = get_token_addresses_with_seed(&program_id, token, owner, n);
409398
let mut messages = vec![];
410399
for address_chunk in token_addresses_with_seed.chunks(100) {
411400
let accounts_chunk = rpc_client
@@ -416,14 +405,14 @@ async fn command_deposit_into_or_withdraw_from(
416405
if account.is_some() {
417406
messages.push(Message::new(
418407
&[instruction::transfer_checked(
419-
&config.program_id,
420-
if deposit_into { from_or_to } else { address },
408+
&program_id,
409+
if deposit_into { &from_or_to } else { address },
421410
token,
422-
if deposit_into { address } else { from_or_to },
411+
if deposit_into { address } else { &from_or_to },
423412
owner,
424413
&[],
425414
amount,
426-
decimals,
415+
mint_info.decimals,
427416
)?],
428417
Some(&config.fee_payer),
429418
));

token/cli/src/config.rs

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::Error;
12
use clap::ArgMatches;
23
use solana_clap_utils::{
34
input_parsers::pubkey_of_signer,
@@ -6,8 +7,9 @@ use solana_clap_utils::{
67
use solana_cli_output::OutputFormat;
78
use solana_client::nonblocking::rpc_client::RpcClient;
89
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
9-
use solana_sdk::{pubkey::Pubkey, signature::Signer};
10+
use solana_sdk::{program_pack::Pack, pubkey::Pubkey, signature::Signer};
1011
use spl_associated_token_account::*;
12+
use spl_token::state::{Account, Mint};
1113
use std::{process::exit, sync::Arc};
1214

1315
#[cfg(test)]
@@ -21,6 +23,12 @@ pub(crate) enum KeypairOrPath {
2123
Path(String),
2224
}
2325

26+
pub(crate) struct MintInfo {
27+
pub program_id: Pubkey,
28+
pub address: Pubkey,
29+
pub decimals: u8,
30+
}
31+
2432
pub(crate) struct Config<'a> {
2533
pub(crate) rpc_client: Arc<RpcClient>,
2634
pub(crate) websocket_url: String,
@@ -69,13 +77,29 @@ impl<'a> Config<'a> {
6977
}
7078

7179
let token = token.unwrap();
80+
let program_id = self.get_mint_info(&token, None).await.unwrap().program_id;
81+
self.associated_token_address_for_token_and_program(
82+
arg_matches,
83+
wallet_manager,
84+
&token,
85+
&program_id,
86+
)
87+
}
88+
89+
pub(crate) fn associated_token_address_for_token_and_program(
90+
&self,
91+
arg_matches: &ArgMatches<'_>,
92+
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
93+
token: &Pubkey,
94+
program_id: &Pubkey,
95+
) -> Pubkey {
7296
let owner = self
7397
.default_address(arg_matches, wallet_manager)
7498
.unwrap_or_else(|e| {
7599
eprintln!("error: {}", e);
76100
exit(1);
77101
});
78-
get_associated_token_address_with_program_id(&owner, &token, &self.program_id)
102+
get_associated_token_address_with_program_id(&owner, token, program_id)
79103
}
80104

81105
// Checks if an explicit address was provided, otherwise return the default address.
@@ -184,4 +208,75 @@ impl<'a> Config<'a> {
184208
}
185209
}
186210
}
211+
212+
pub(crate) async fn get_mint_info(
213+
&self,
214+
mint: &Pubkey,
215+
mint_decimals: Option<u8>,
216+
) -> Result<MintInfo, Error> {
217+
if self.sign_only {
218+
Ok(MintInfo {
219+
program_id: self.program_id,
220+
address: *mint,
221+
decimals: mint_decimals.unwrap_or_default(),
222+
})
223+
} else {
224+
let account = self.rpc_client.get_account(mint).await?;
225+
self.check_owner(mint, &account.owner)?;
226+
let mint_account = Mint::unpack(&account.data)
227+
.map_err(|_| format!("Could not find mint account {}", mint))?;
228+
if let Some(decimals) = mint_decimals {
229+
if decimals != mint_account.decimals {
230+
return Err(format!(
231+
"Mint {:?} has decimals {}, not configured decimals {}",
232+
mint, mint_account.decimals, decimals
233+
)
234+
.into());
235+
}
236+
}
237+
Ok(MintInfo {
238+
program_id: account.owner,
239+
address: *mint,
240+
decimals: mint_account.decimals,
241+
})
242+
}
243+
}
244+
245+
pub(crate) fn check_owner(&self, account: &Pubkey, owner: &Pubkey) -> Result<(), Error> {
246+
if self.program_id != *owner {
247+
Err(format!(
248+
"Account {:?} is owned by {}, not configured program id {}",
249+
account, owner, self.program_id
250+
)
251+
.into())
252+
} else {
253+
Ok(())
254+
}
255+
}
256+
257+
pub(crate) async fn check_account(
258+
&self,
259+
token_account: &Pubkey,
260+
mint_address: Option<Pubkey>,
261+
) -> Result<Pubkey, Error> {
262+
if !self.sign_only {
263+
let account = self.rpc_client.get_account(token_account).await?;
264+
let source_account = Account::unpack(&account.data)
265+
.map_err(|_| format!("Could not find token account {}", token_account))?;
266+
let source_mint = source_account.mint;
267+
if let Some(mint) = mint_address {
268+
if source_mint != mint {
269+
return Err(format!(
270+
"Source {:?} does not contain {:?} tokens",
271+
token_account, mint
272+
)
273+
.into());
274+
}
275+
}
276+
self.check_owner(token_account, &account.owner)?;
277+
Ok(source_mint)
278+
} else {
279+
Ok(mint_address.unwrap_or_default())
280+
}
281+
}
187282
}

0 commit comments

Comments
 (0)