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

Commit 27fe1e9

Browse files
committed
token-client: Specify compute unit limit more clearly
1 parent f75abb7 commit 27fe1e9

File tree

7 files changed

+97
-49
lines changed

7 files changed

+97
-49
lines changed

token/cli/src/clap_app.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use {
1515
},
1616
solana_sdk::{instruction::AccountMeta, pubkey::Pubkey},
1717
spl_token_2022::instruction::{AuthorityType, MAX_SIGNERS, MIN_SIGNERS},
18+
spl_token_client::token::ComputeUnitLimit,
1819
std::{fmt, str::FromStr},
1920
strum::IntoEnumIterator,
2021
strum_macros::{EnumIter, EnumString, IntoStaticStr},
@@ -73,7 +74,10 @@ pub const COMPUTE_UNIT_PRICE_ARG: ArgConstant<'static> = ArgConstant {
7374
pub const COMPUTE_UNIT_LIMIT_ARG: ArgConstant<'static> = ArgConstant {
7475
name: "compute_unit_limit",
7576
long: "--with-compute-unit-limit",
76-
help: "Set compute unit limit for transaction, in compute units.",
77+
help: "Set compute unit limit for transaction, in compute units; also accepts \
78+
keyword SIMULATED to use compute units from transaction simulation prior \
79+
to sending. Note that SIMULATED may fail if accounts are modified by another \
80+
transaction between simulation and execution.",
7781
};
7882

7983
pub static VALID_TOKEN_PROGRAM_IDS: [Pubkey; 2] = [spl_token_2022::ID, spl_token::ID];
@@ -350,6 +354,32 @@ where
350354
Err(e) => Err(e),
351355
}
352356
}
357+
358+
fn is_compute_unit_limit_or_simulated<T>(string: T) -> Result<(), String>
359+
where
360+
T: AsRef<str> + fmt::Display,
361+
{
362+
if string.as_ref().parse::<u32>().is_ok() || string.as_ref() == "SIMULATED" {
363+
Ok(())
364+
} else {
365+
Err(format!(
366+
"Unable to parse input compute unit limit as integer or SIMULATED, provided: {string}"
367+
))
368+
}
369+
}
370+
pub(crate) fn parse_compute_unit_limit<T>(string: T) -> Result<ComputeUnitLimit, String>
371+
where
372+
T: AsRef<str> + fmt::Display,
373+
{
374+
match string.as_ref().parse::<u32>() {
375+
Ok(compute_unit_limit) => Ok(ComputeUnitLimit::Static(compute_unit_limit)),
376+
Err(_) if string.as_ref() == "SIMULATED" => Ok(ComputeUnitLimit::Simulated),
377+
_ => Err(format!(
378+
"Unable to parse compute unit limit, provided: {string}"
379+
)),
380+
}
381+
}
382+
353383
struct SignOnlyNeedsFullMintSpec {}
354384
impl offline::ArgsConfig for SignOnlyNeedsFullMintSpec {
355385
fn sign_only_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
@@ -629,7 +659,7 @@ pub fn app<'a, 'b>(
629659
.takes_value(true)
630660
.global(true)
631661
.value_name("COMPUTE-UNIT-LIMIT")
632-
.validator(is_parsable::<u32>)
662+
.validator(is_compute_unit_limit_or_simulated)
633663
.help(COMPUTE_UNIT_LIMIT_ARG.help)
634664
)
635665
.arg(

token/cli/src/command.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ use {
6868
},
6969
spl_token_client::{
7070
client::{ProgramRpcClientSendTransaction, RpcClientResponse},
71-
token::{ExtensionInitializationParams, Token},
71+
token::{ComputeUnitLimit, ExtensionInitializationParams, Token},
7272
},
7373
spl_token_group_interface::state::TokenGroup,
7474
spl_token_metadata_interface::state::{Field, TokenMetadata},
@@ -152,11 +152,7 @@ fn config_token_client(
152152
token: Token<ProgramRpcClientSendTransaction>,
153153
config: &Config<'_>,
154154
) -> Result<Token<ProgramRpcClientSendTransaction>, Error> {
155-
let token = if let Some(compute_unit_limit) = config.compute_unit_limit {
156-
token.with_compute_unit_limit(compute_unit_limit)
157-
} else {
158-
token
159-
};
155+
let token = token.with_compute_unit_limit(config.compute_unit_limit.clone());
160156

161157
let token = if let Some(compute_unit_price) = config.compute_unit_price {
162158
token.with_compute_unit_price(compute_unit_price)
@@ -434,7 +430,8 @@ async fn command_set_interest_rate(
434430
) -> CommandResult {
435431
// Because set_interest_rate depends on the time, it can cost more between
436432
// simulation and execution. To help that, just set a static compute limit
437-
let token = base_token_client(config, &token_pubkey, None)?.with_compute_unit_limit(2_500);
433+
let token = base_token_client(config, &token_pubkey, None)?
434+
.with_compute_unit_limit(ComputeUnitLimit::Static(2_500));
438435
let token = config_token_client(token, config)?;
439436

440437
if !config.sign_only {

token/cli/src/config.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use {
2-
crate::clap_app::{Error, COMPUTE_UNIT_LIMIT_ARG, COMPUTE_UNIT_PRICE_ARG, MULTISIG_SIGNER_ARG},
2+
crate::clap_app::{
3+
parse_compute_unit_limit, Error, COMPUTE_UNIT_LIMIT_ARG, COMPUTE_UNIT_PRICE_ARG,
4+
MULTISIG_SIGNER_ARG,
5+
},
36
clap::ArgMatches,
47
solana_clap_utils::{
58
input_parsers::{pubkey_of_signer, value_of},
@@ -20,8 +23,11 @@ use {
2023
extension::StateWithExtensionsOwned,
2124
state::{Account, Mint},
2225
},
23-
spl_token_client::client::{
24-
ProgramClient, ProgramOfflineClient, ProgramRpcClient, ProgramRpcClientSendTransaction,
26+
spl_token_client::{
27+
client::{
28+
ProgramClient, ProgramOfflineClient, ProgramRpcClient, ProgramRpcClientSendTransaction,
29+
},
30+
token::ComputeUnitLimit,
2531
},
2632
std::{process::exit, rc::Rc, sync::Arc},
2733
};
@@ -68,7 +74,7 @@ pub struct Config<'a> {
6874
pub program_id: Pubkey,
6975
pub restrict_to_program_id: bool,
7076
pub compute_unit_price: Option<u64>,
71-
pub compute_unit_limit: Option<u32>,
77+
pub compute_unit_limit: ComputeUnitLimit,
7278
}
7379

7480
impl<'a> Config<'a> {
@@ -282,7 +288,10 @@ impl<'a> Config<'a> {
282288

283289
let nonce_blockhash = value_of(matches, BLOCKHASH_ARG.name);
284290
let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
285-
let compute_unit_limit = value_of(matches, COMPUTE_UNIT_LIMIT_ARG.name);
291+
let compute_unit_limit = matches
292+
.value_of(COMPUTE_UNIT_LIMIT_ARG.name)
293+
.map(|x| parse_compute_unit_limit(x).unwrap())
294+
.unwrap_or(ComputeUnitLimit::Default);
286295
Self {
287296
default_signer,
288297
rpc_client,

token/cli/tests/command.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ use {
4444
client::{
4545
ProgramClient, ProgramOfflineClient, ProgramRpcClient, ProgramRpcClientSendTransaction,
4646
},
47-
token::Token,
47+
token::{ComputeUnitLimit, Token},
4848
},
4949
spl_token_group_interface::state::{TokenGroup, TokenGroupMember},
5050
spl_token_metadata_interface::state::TokenMetadata,
@@ -201,7 +201,7 @@ fn test_config_with_default_signer<'a>(
201201
program_id: *program_id,
202202
restrict_to_program_id: true,
203203
compute_unit_price: None,
204-
compute_unit_limit: None,
204+
compute_unit_limit: ComputeUnitLimit::Simulated,
205205
}
206206
}
207207

@@ -230,7 +230,7 @@ fn test_config_without_default_signer<'a>(
230230
program_id: *program_id,
231231
restrict_to_program_id: true,
232232
compute_unit_price: None,
233-
compute_unit_limit: None,
233+
compute_unit_limit: ComputeUnitLimit::Simulated,
234234
}
235235
}
236236

@@ -2991,6 +2991,7 @@ async fn offline_multisig_transfer_with_nonce(test_validator: &TestValidator, pa
29912991
.unzip();
29922992
for program_id in VALID_TOKEN_PROGRAM_IDS.iter() {
29932993
let mut config = test_config_with_default_signer(test_validator, payer, program_id);
2994+
config.compute_unit_limit = ComputeUnitLimit::Default;
29942995
let token = create_token(&config, payer).await;
29952996
let nonce = create_nonce(&config, payer).await;
29962997

@@ -4081,7 +4082,7 @@ async fn compute_budget(test_validator: &TestValidator, payer: &Keypair) {
40814082
for program_id in VALID_TOKEN_PROGRAM_IDS.iter() {
40824083
let mut config = test_config_with_default_signer(test_validator, payer, program_id);
40834084
config.compute_unit_price = Some(42);
4084-
config.compute_unit_limit = Some(30_000);
4085+
config.compute_unit_limit = ComputeUnitLimit::Static(30_000);
40854086
run_transfer_test(&config, payer).await;
40864087
}
40874088
}

token/client/src/token.rs

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,13 @@ impl TokenMemo {
332332
}
333333
}
334334

335+
#[derive(Debug, Clone)]
336+
pub enum ComputeUnitLimit {
337+
Default,
338+
Simulated,
339+
Static(u32),
340+
}
341+
335342
pub struct Token<T> {
336343
client: Arc<dyn ProgramClient<T>>,
337344
pubkey: Pubkey, /* token mint */
@@ -344,7 +351,7 @@ pub struct Token<T> {
344351
memo: Arc<RwLock<Option<TokenMemo>>>,
345352
transfer_hook_accounts: Option<Vec<AccountMeta>>,
346353
compute_unit_price: Option<u64>,
347-
compute_unit_limit: Option<u32>,
354+
compute_unit_limit: ComputeUnitLimit,
348355
}
349356

350357
impl<T> fmt::Debug for Token<T> {
@@ -411,7 +418,7 @@ where
411418
memo: Arc::new(RwLock::new(None)),
412419
transfer_hook_accounts: None,
413420
compute_unit_price: None,
414-
compute_unit_limit: None,
421+
compute_unit_limit: ComputeUnitLimit::Default,
415422
}
416423
}
417424

@@ -466,8 +473,8 @@ where
466473
self
467474
}
468475

469-
pub fn with_compute_unit_limit(mut self, compute_unit_limit: u32) -> Self {
470-
self.compute_unit_limit = Some(compute_unit_limit);
476+
pub fn with_compute_unit_limit(mut self, compute_unit_limit: ComputeUnitLimit) -> Self {
477+
self.compute_unit_limit = compute_unit_limit;
471478
self
472479
}
473480

@@ -548,19 +555,16 @@ where
548555
.simulate_transaction(&transaction)
549556
.await
550557
.map_err(TokenError::Client)?;
551-
if let Ok(units_consumed) = simulation_result.get_compute_units_consumed() {
552-
// Overwrite the compute unit limit instruction with the actual units consumed
553-
let compute_unit_limit =
554-
u32::try_from(units_consumed).map_err(|x| TokenError::Client(x.into()))?;
555-
instructions
556-
.last_mut()
557-
.expect("Compute budget instruction was added earlier")
558-
.data = ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit).data;
559-
} else {
560-
// `get_compute_units_consumed()` fails for offline signing, so we
561-
// catch that error and remove the instruction that was added
562-
instructions.pop();
563-
}
558+
let units_consumed = simulation_result
559+
.get_compute_units_consumed()
560+
.map_err(TokenError::Client)?;
561+
// Overwrite the compute unit limit instruction with the actual units consumed
562+
let compute_unit_limit =
563+
u32::try_from(units_consumed).map_err(|x| TokenError::Client(x.into()))?;
564+
instructions
565+
.last_mut()
566+
.expect("Compute budget instruction was added earlier")
567+
.data = ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit).data;
564568
Ok(())
565569
}
566570

@@ -619,13 +623,17 @@ where
619623
// all instructions have been added to the transaction, so be sure to
620624
// keep this instruction as the last one before creating and sending the
621625
// transaction.
622-
if let Some(compute_unit_limit) = self.compute_unit_limit {
623-
instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
624-
compute_unit_limit,
625-
));
626-
} else {
627-
self.add_compute_unit_limit_from_simulation(&mut instructions, &blockhash)
628-
.await?;
626+
match self.compute_unit_limit {
627+
ComputeUnitLimit::Default => {}
628+
ComputeUnitLimit::Simulated => {
629+
self.add_compute_unit_limit_from_simulation(&mut instructions, &blockhash)
630+
.await?;
631+
}
632+
ComputeUnitLimit::Static(compute_unit_limit) => {
633+
instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
634+
compute_unit_limit,
635+
));
636+
}
629637
}
630638

631639
let message = Message::new_with_blockhash(&instructions, fee_payer, &blockhash);

token/program-2022-test/tests/confidential_transfer.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use {
3939
},
4040
spl_token_client::{
4141
proof_generation::transfer_with_fee_split_proof_data,
42-
token::{ExtensionInitializationParams, TokenError as TokenClientError},
42+
token::{ComputeUnitLimit, ExtensionInitializationParams, TokenError as TokenClientError},
4343
},
4444
std::{convert::TryInto, mem::size_of},
4545
};
@@ -2526,7 +2526,7 @@ async fn confidential_transfer_transfer_with_split_proof_contexts_in_parallel()
25262526
// With split proofs in parallel, one of the transactions does more work
25272527
// than the other, which isn't caught during the simulation to discover the
25282528
// compute unit limit.
2529-
let token = token.with_compute_unit_limit(500_000);
2529+
let token = token.with_compute_unit_limit(ComputeUnitLimit::Static(500_000));
25302530
token
25312531
.confidential_transfer_transfer_with_split_proofs_in_parallel(
25322532
&alice_meta.token_account,
@@ -2948,7 +2948,7 @@ async fn confidential_transfer_transfer_with_fee_and_split_proof_context_in_para
29482948
// With split proofs in parallel, one of the transactions does more work
29492949
// than the other, which isn't caught during the simulation to discover the
29502950
// compute unit limit.
2951-
let token = token.with_compute_unit_limit(500_000);
2951+
let token = token.with_compute_unit_limit(ComputeUnitLimit::Static(500_000));
29522952
token
29532953
.confidential_transfer_transfer_with_fee_and_split_proofs_in_parallel(
29542954
&alice_meta.token_account,

token/program-2022-test/tests/program_test.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use {
2020
ProgramBanksClient, ProgramBanksClientProcessTransaction, ProgramClient,
2121
SendTransaction, SimulateTransaction,
2222
},
23-
token::{ExtensionInitializationParams, Token, TokenResult},
23+
token::{ComputeUnitLimit, ExtensionInitializationParams, Token, TokenResult},
2424
},
2525
std::sync::Arc,
2626
};
@@ -113,15 +113,17 @@ impl TestContext {
113113
&mint_account.pubkey(),
114114
Some(decimals),
115115
Arc::new(keypair_clone(&payer)),
116-
);
116+
)
117+
.with_compute_unit_limit(ComputeUnitLimit::Simulated);
117118

118119
let token_unchecked = Token::new(
119120
Arc::clone(&client),
120121
&id(),
121122
&mint_account.pubkey(),
122123
None,
123124
Arc::new(payer),
124-
);
125+
)
126+
.with_compute_unit_limit(ComputeUnitLimit::Simulated);
125127

126128
token
127129
.create_mint(
@@ -157,7 +159,8 @@ impl TestContext {
157159
Token::create_native_mint(Arc::clone(&client), &id(), Arc::new(keypair_clone(&payer)))
158160
.await?;
159161
// unchecked native is never needed because decimals is known statically
160-
let token_unchecked = Token::new_native(Arc::clone(&client), &id(), Arc::new(payer));
162+
let token_unchecked = Token::new_native(Arc::clone(&client), &id(), Arc::new(payer))
163+
.with_compute_unit_limit(ComputeUnitLimit::Simulated);
161164
self.token_context = Some(TokenContext {
162165
decimals: native_mint::DECIMALS,
163166
mint_authority: Keypair::new(), /* bogus */

0 commit comments

Comments
 (0)