solana_cli/
compute_budget.rs1use {
2 solana_borsh::v1::try_from_slice_unchecked,
3 solana_clap_utils::compute_budget::ComputeUnitLimit,
4 solana_compute_budget_interface::{self as compute_budget, ComputeBudgetInstruction},
5 solana_instruction::Instruction,
6 solana_message::Message,
7 solana_program_runtime::execution_budget::MAX_COMPUTE_UNIT_LIMIT,
8 solana_rpc_client::rpc_client::RpcClient,
9 solana_rpc_client_api::config::RpcSimulateTransactionConfig,
10 solana_transaction::Transaction,
11};
12
13pub(crate) enum UpdateComputeUnitLimitResult {
16 UpdatedInstructionIndex(usize),
17 NoInstructionFound,
18 SimulationNotConfigured,
19}
20
21fn get_compute_unit_limit_instruction_index(message: &Message) -> Option<usize> {
22 message
23 .instructions
24 .iter()
25 .enumerate()
26 .find_map(|(ix_index, instruction)| {
27 let ix_program_id = message.program_id(ix_index)?;
28 if ix_program_id != &compute_budget::id() {
29 return None;
30 }
31
32 matches!(
33 try_from_slice_unchecked(&instruction.data),
34 Ok(ComputeBudgetInstruction::SetComputeUnitLimit(_))
35 )
36 .then_some(ix_index)
37 })
38}
39
40fn simulate_for_compute_unit_limit_unchecked(
43 rpc_client: &RpcClient,
44 message: &Message,
45) -> Result<u32, Box<dyn std::error::Error>> {
46 let transaction = Transaction::new_unsigned(message.clone());
47 let simulate_result = rpc_client
48 .simulate_transaction_with_config(
49 &transaction,
50 RpcSimulateTransactionConfig {
51 replace_recent_blockhash: true,
52 commitment: Some(rpc_client.commitment()),
53 ..RpcSimulateTransactionConfig::default()
54 },
55 )?
56 .value;
57
58 if let Some(err) = simulate_result.err {
60 return Err(err.into());
61 }
62
63 let units_consumed = simulate_result
64 .units_consumed
65 .expect("compute units unavailable");
66
67 u32::try_from(units_consumed).map_err(Into::into)
68}
69
70pub(crate) fn simulate_for_compute_unit_limit(
75 rpc_client: &RpcClient,
76 message: &Message,
77) -> Result<u32, Box<dyn std::error::Error>> {
78 if get_compute_unit_limit_instruction_index(message).is_none() {
79 return Err("No compute unit limit instruction found".into());
80 }
81 simulate_for_compute_unit_limit_unchecked(rpc_client, message)
82}
83
84pub(crate) fn simulate_and_update_compute_unit_limit(
91 compute_unit_limit: &ComputeUnitLimit,
92 rpc_client: &RpcClient,
93 message: &mut Message,
94) -> Result<UpdateComputeUnitLimitResult, Box<dyn std::error::Error>> {
95 let Some(compute_unit_limit_ix_index) = get_compute_unit_limit_instruction_index(message)
96 else {
97 return Ok(UpdateComputeUnitLimitResult::NoInstructionFound);
98 };
99
100 match compute_unit_limit {
101 ComputeUnitLimit::Simulated | ComputeUnitLimit::SimulatedWithExtraPercentage(_) => {
102 let base_compute_unit_limit =
103 simulate_for_compute_unit_limit_unchecked(rpc_client, message)?;
104
105 let compute_unit_limit =
106 if let ComputeUnitLimit::SimulatedWithExtraPercentage(n) = compute_unit_limit {
107 (base_compute_unit_limit as u64)
108 .saturating_mul(100_u64.saturating_add(*n as u64))
109 .saturating_div(100) as u32
110 } else {
111 base_compute_unit_limit
112 };
113
114 message.instructions[compute_unit_limit_ix_index].data =
116 ComputeBudgetInstruction::set_compute_unit_limit(compute_unit_limit).data;
117
118 Ok(UpdateComputeUnitLimitResult::UpdatedInstructionIndex(
119 compute_unit_limit_ix_index,
120 ))
121 }
122 ComputeUnitLimit::Static(_) | ComputeUnitLimit::Default => {
123 Ok(UpdateComputeUnitLimitResult::SimulationNotConfigured)
124 }
125 }
126}
127
128pub(crate) struct ComputeUnitConfig {
129 pub(crate) compute_unit_price: Option<u64>,
130 pub(crate) compute_unit_limit: ComputeUnitLimit,
131}
132
133pub(crate) trait WithComputeUnitConfig {
134 fn with_compute_unit_config(self, config: &ComputeUnitConfig) -> Self;
135}
136
137impl WithComputeUnitConfig for Vec<Instruction> {
138 fn with_compute_unit_config(mut self, config: &ComputeUnitConfig) -> Self {
139 if let Some(compute_unit_price) = config.compute_unit_price {
140 self.push(ComputeBudgetInstruction::set_compute_unit_price(
141 compute_unit_price,
142 ));
143 match config.compute_unit_limit {
144 ComputeUnitLimit::Default => {}
145 ComputeUnitLimit::Static(compute_unit_limit) => {
146 self.push(ComputeBudgetInstruction::set_compute_unit_limit(
147 compute_unit_limit,
148 ));
149 }
150 ComputeUnitLimit::Simulated | ComputeUnitLimit::SimulatedWithExtraPercentage(_) => {
151 self.push(ComputeBudgetInstruction::set_compute_unit_limit(
154 MAX_COMPUTE_UNIT_LIMIT,
155 ));
156 }
157 }
158 }
159 self
160 }
161}