1use {
2 crate::prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType},
3 solana_sdk::{
4 borsh::try_from_slice_unchecked,
5 compute_budget::{self, ComputeBudgetInstruction},
6 entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES,
7 instruction::{CompiledInstruction, InstructionError},
8 pubkey::Pubkey,
9 transaction::TransactionError,
10 },
11};
12
13pub const DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT: u32 = 200_000;
14pub const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000;
15const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024;
16
17#[cfg(RUSTC_WITH_SPECIALIZATION)]
18impl ::solana_frozen_abi::abi_example::AbiExample for ComputeBudget {
19 fn example() -> Self {
20 ComputeBudget::default()
22 }
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub struct ComputeBudget {
27 pub compute_unit_limit: u64,
31 pub log_64_units: u64,
33 pub create_program_address_units: u64,
35 pub invoke_units: u64,
38 pub max_invoke_stack_height: usize,
43 pub max_instruction_trace_length: usize,
45 pub sha256_base_cost: u64,
47 pub sha256_byte_cost: u64,
49 pub sha256_max_slices: u64,
51 pub max_call_depth: usize,
53 pub stack_frame_size: usize,
55 pub log_pubkey_units: u64,
57 pub max_cpi_instruction_size: usize,
59 pub cpi_bytes_per_unit: u64,
61 pub sysvar_base_cost: u64,
63 pub secp256k1_recover_cost: u64,
65 pub syscall_base_cost: u64,
67 pub curve25519_edwards_validate_point_cost: u64,
69 pub curve25519_edwards_add_cost: u64,
71 pub curve25519_edwards_subtract_cost: u64,
73 pub curve25519_edwards_multiply_cost: u64,
75 pub curve25519_edwards_msm_base_cost: u64,
78 pub curve25519_edwards_msm_incremental_cost: u64,
81 pub curve25519_ristretto_validate_point_cost: u64,
83 pub curve25519_ristretto_add_cost: u64,
85 pub curve25519_ristretto_subtract_cost: u64,
87 pub curve25519_ristretto_multiply_cost: u64,
89 pub curve25519_ristretto_msm_base_cost: u64,
92 pub curve25519_ristretto_msm_incremental_cost: u64,
95 pub heap_size: Option<usize>,
97 pub heap_cost: u64,
100 pub mem_op_base_cost: u64,
102 pub alt_bn128_addition_cost: u64,
104 pub alt_bn128_multiplication_cost: u64,
106 pub alt_bn128_pairing_one_pair_cost_first: u64,
109 pub alt_bn128_pairing_one_pair_cost_other: u64,
110 pub big_modular_exponentiation_cost: u64,
112}
113
114impl Default for ComputeBudget {
115 fn default() -> Self {
116 Self::new(MAX_COMPUTE_UNIT_LIMIT as u64)
117 }
118}
119
120impl ComputeBudget {
121 pub fn new(compute_unit_limit: u64) -> Self {
122 ComputeBudget {
123 compute_unit_limit,
124 log_64_units: 100,
125 create_program_address_units: 1500,
126 invoke_units: 1000,
127 max_invoke_stack_height: 5,
128 max_instruction_trace_length: 64,
129 sha256_base_cost: 85,
130 sha256_byte_cost: 1,
131 sha256_max_slices: 20_000,
132 max_call_depth: 64,
133 stack_frame_size: 4_096,
134 log_pubkey_units: 100,
135 max_cpi_instruction_size: 1280, cpi_bytes_per_unit: 250, sysvar_base_cost: 100,
138 secp256k1_recover_cost: 25_000,
139 syscall_base_cost: 100,
140 curve25519_edwards_validate_point_cost: 159,
141 curve25519_edwards_add_cost: 473,
142 curve25519_edwards_subtract_cost: 475,
143 curve25519_edwards_multiply_cost: 2_177,
144 curve25519_edwards_msm_base_cost: 2_273,
145 curve25519_edwards_msm_incremental_cost: 758,
146 curve25519_ristretto_validate_point_cost: 169,
147 curve25519_ristretto_add_cost: 521,
148 curve25519_ristretto_subtract_cost: 519,
149 curve25519_ristretto_multiply_cost: 2_208,
150 curve25519_ristretto_msm_base_cost: 2303,
151 curve25519_ristretto_msm_incremental_cost: 788,
152 heap_size: None,
153 heap_cost: 8,
154 mem_op_base_cost: 10,
155 alt_bn128_addition_cost: 334,
156 alt_bn128_multiplication_cost: 3_840,
157 alt_bn128_pairing_one_pair_cost_first: 36_364,
158 alt_bn128_pairing_one_pair_cost_other: 12_121,
159 big_modular_exponentiation_cost: 33,
160 }
161 }
162
163 pub fn process_instructions<'a>(
164 &mut self,
165 instructions: impl Iterator<Item = (&'a Pubkey, &'a CompiledInstruction)>,
166 default_units_per_instruction: bool,
167 support_request_units_deprecated: bool,
168 enable_request_heap_frame_ix: bool,
169 ) -> Result<PrioritizationFeeDetails, TransactionError> {
170 let mut num_non_compute_budget_instructions: usize = 0;
171 let mut updated_compute_unit_limit = None;
172 let mut requested_heap_size = None;
173 let mut prioritization_fee = None;
174
175 for (i, (program_id, instruction)) in instructions.enumerate() {
176 if compute_budget::check_id(program_id) {
177 let invalid_instruction_data_error = TransactionError::InstructionError(
178 i as u8,
179 InstructionError::InvalidInstructionData,
180 );
181 let duplicate_instruction_error = TransactionError::DuplicateInstruction(i as u8);
182
183 match try_from_slice_unchecked(&instruction.data) {
184 Ok(ComputeBudgetInstruction::RequestUnitsDeprecated {
185 units: compute_unit_limit,
186 additional_fee,
187 }) if support_request_units_deprecated => {
188 if updated_compute_unit_limit.is_some() {
189 return Err(duplicate_instruction_error);
190 }
191 if prioritization_fee.is_some() {
192 return Err(duplicate_instruction_error);
193 }
194 updated_compute_unit_limit = Some(compute_unit_limit);
195 prioritization_fee =
196 Some(PrioritizationFeeType::Deprecated(additional_fee as u64));
197 }
198 Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => {
199 if requested_heap_size.is_some() {
200 return Err(duplicate_instruction_error);
201 }
202 requested_heap_size = Some((bytes, i as u8));
203 }
204 Ok(ComputeBudgetInstruction::SetComputeUnitLimit(compute_unit_limit)) => {
205 if updated_compute_unit_limit.is_some() {
206 return Err(duplicate_instruction_error);
207 }
208 updated_compute_unit_limit = Some(compute_unit_limit);
209 }
210 Ok(ComputeBudgetInstruction::SetComputeUnitPrice(micro_lamports)) => {
211 if prioritization_fee.is_some() {
212 return Err(duplicate_instruction_error);
213 }
214 prioritization_fee =
215 Some(PrioritizationFeeType::ComputeUnitPrice(micro_lamports));
216 }
217 _ => return Err(invalid_instruction_data_error),
218 }
219 } else {
220 num_non_compute_budget_instructions =
222 num_non_compute_budget_instructions.saturating_add(1);
223 }
224 }
225
226 if let Some((bytes, i)) = requested_heap_size {
227 if !enable_request_heap_frame_ix
228 || bytes > MAX_HEAP_FRAME_BYTES
229 || bytes < MIN_HEAP_FRAME_BYTES as u32
230 || bytes % 1024 != 0
231 {
232 return Err(TransactionError::InstructionError(
233 i,
234 InstructionError::InvalidInstructionData,
235 ));
236 }
237 self.heap_size = Some(bytes as usize);
238 }
239
240 self.compute_unit_limit = if default_units_per_instruction {
241 updated_compute_unit_limit.or_else(|| {
242 Some(
243 (num_non_compute_budget_instructions as u32)
244 .saturating_mul(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT),
245 )
246 })
247 } else {
248 updated_compute_unit_limit
249 }
250 .unwrap_or(MAX_COMPUTE_UNIT_LIMIT)
251 .min(MAX_COMPUTE_UNIT_LIMIT) as u64;
252
253 Ok(prioritization_fee
254 .map(|fee_type| PrioritizationFeeDetails::new(fee_type, self.compute_unit_limit))
255 .unwrap_or_default())
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use {
262 super::*,
263 solana_sdk::{
264 hash::Hash,
265 instruction::Instruction,
266 message::Message,
267 pubkey::Pubkey,
268 signature::Keypair,
269 signer::Signer,
270 transaction::{SanitizedTransaction, Transaction},
271 },
272 };
273
274 macro_rules! test {
275 ( $instructions: expr, $expected_result: expr, $expected_budget: expr, $enable_request_heap_frame_ix: expr ) => {
276 let payer_keypair = Keypair::new();
277 let tx = SanitizedTransaction::from_transaction_for_tests(Transaction::new(
278 &[&payer_keypair],
279 Message::new($instructions, Some(&payer_keypair.pubkey())),
280 Hash::default(),
281 ));
282 let mut compute_budget = ComputeBudget::default();
283 let result = compute_budget.process_instructions(
284 tx.message().program_instructions_iter(),
285 true,
286 false, $enable_request_heap_frame_ix,
288 );
289 assert_eq!($expected_result, result);
290 assert_eq!(compute_budget, $expected_budget);
291 };
292 ( $instructions: expr, $expected_result: expr, $expected_budget: expr) => {
293 test!($instructions, $expected_result, $expected_budget, true);
294 };
295 }
296
297 #[test]
298 fn test_process_instructions() {
299 test!(
301 &[],
302 Ok(PrioritizationFeeDetails::default()),
303 ComputeBudget {
304 compute_unit_limit: 0,
305 ..ComputeBudget::default()
306 }
307 );
308 test!(
309 &[
310 ComputeBudgetInstruction::set_compute_unit_limit(1),
311 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
312 ],
313 Ok(PrioritizationFeeDetails::default()),
314 ComputeBudget {
315 compute_unit_limit: 1,
316 ..ComputeBudget::default()
317 }
318 );
319 test!(
320 &[
321 ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT + 1),
322 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
323 ],
324 Ok(PrioritizationFeeDetails::default()),
325 ComputeBudget {
326 compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT as u64,
327 ..ComputeBudget::default()
328 }
329 );
330 test!(
331 &[
332 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
333 ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
334 ],
335 Ok(PrioritizationFeeDetails::default()),
336 ComputeBudget {
337 compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT as u64,
338 ..ComputeBudget::default()
339 }
340 );
341 test!(
342 &[
343 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
344 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
345 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
346 ComputeBudgetInstruction::set_compute_unit_limit(1),
347 ],
348 Ok(PrioritizationFeeDetails::default()),
349 ComputeBudget {
350 compute_unit_limit: 1,
351 ..ComputeBudget::default()
352 }
353 );
354
355 test!(
356 &[
357 ComputeBudgetInstruction::set_compute_unit_limit(1),
358 ComputeBudgetInstruction::set_compute_unit_price(42)
359 ],
360 Ok(PrioritizationFeeDetails::new(
361 PrioritizationFeeType::ComputeUnitPrice(42),
362 1
363 )),
364 ComputeBudget {
365 compute_unit_limit: 1,
366 ..ComputeBudget::default()
367 }
368 );
369
370 test!(
372 &[],
373 Ok(PrioritizationFeeDetails::default()),
374 ComputeBudget {
375 compute_unit_limit: 0,
376 ..ComputeBudget::default()
377 }
378 );
379 test!(
380 &[
381 ComputeBudgetInstruction::request_heap_frame(40 * 1024),
382 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
383 ],
384 Ok(PrioritizationFeeDetails::default()),
385 ComputeBudget {
386 compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
387 heap_size: Some(40 * 1024),
388 ..ComputeBudget::default()
389 }
390 );
391 test!(
392 &[
393 ComputeBudgetInstruction::request_heap_frame(40 * 1024 + 1),
394 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
395 ],
396 Err(TransactionError::InstructionError(
397 0,
398 InstructionError::InvalidInstructionData,
399 )),
400 ComputeBudget::default()
401 );
402 test!(
403 &[
404 ComputeBudgetInstruction::request_heap_frame(31 * 1024),
405 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
406 ],
407 Err(TransactionError::InstructionError(
408 0,
409 InstructionError::InvalidInstructionData,
410 )),
411 ComputeBudget::default()
412 );
413 test!(
414 &[
415 ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES + 1),
416 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
417 ],
418 Err(TransactionError::InstructionError(
419 0,
420 InstructionError::InvalidInstructionData,
421 )),
422 ComputeBudget::default()
423 );
424 test!(
425 &[
426 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
427 ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
428 ],
429 Ok(PrioritizationFeeDetails::default()),
430 ComputeBudget {
431 compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64,
432 heap_size: Some(MAX_HEAP_FRAME_BYTES as usize),
433 ..ComputeBudget::default()
434 }
435 );
436 test!(
437 &[
438 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
439 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
440 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
441 ComputeBudgetInstruction::request_heap_frame(1),
442 ],
443 Err(TransactionError::InstructionError(
444 3,
445 InstructionError::InvalidInstructionData,
446 )),
447 ComputeBudget::default()
448 );
449
450 test!(
451 &[
452 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
453 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
454 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
455 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
456 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
457 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
458 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
459 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
460 ],
461 Ok(PrioritizationFeeDetails::default()),
462 ComputeBudget {
463 compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64 * 7,
464 ..ComputeBudget::default()
465 }
466 );
467
468 test!(
470 &[
471 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
472 ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
473 ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
474 ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
475 ],
476 Ok(PrioritizationFeeDetails::new(
477 PrioritizationFeeType::ComputeUnitPrice(u64::MAX),
478 MAX_COMPUTE_UNIT_LIMIT as u64,
479 )),
480 ComputeBudget {
481 compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT as u64,
482 heap_size: Some(MAX_HEAP_FRAME_BYTES as usize),
483 ..ComputeBudget::default()
484 }
485 );
486
487 test!(
488 &[
489 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
490 ComputeBudgetInstruction::set_compute_unit_limit(1),
491 ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
492 ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
493 ],
494 Ok(PrioritizationFeeDetails::new(
495 PrioritizationFeeType::ComputeUnitPrice(u64::MAX),
496 1
497 )),
498 ComputeBudget {
499 compute_unit_limit: 1,
500 heap_size: Some(MAX_HEAP_FRAME_BYTES as usize),
501 ..ComputeBudget::default()
502 }
503 );
504
505 test!(
507 &[
508 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
509 ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
510 ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT - 1),
511 ],
512 Err(TransactionError::DuplicateInstruction(2)),
513 ComputeBudget::default()
514 );
515
516 test!(
517 &[
518 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
519 ComputeBudgetInstruction::request_heap_frame(MIN_HEAP_FRAME_BYTES as u32),
520 ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
521 ],
522 Err(TransactionError::DuplicateInstruction(2)),
523 ComputeBudget::default()
524 );
525
526 test!(
527 &[
528 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
529 ComputeBudgetInstruction::set_compute_unit_price(0),
530 ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
531 ],
532 Err(TransactionError::DuplicateInstruction(2)),
533 ComputeBudget::default()
534 );
535
536 test!(
538 &[Instruction::new_with_borsh(
539 compute_budget::id(),
540 &compute_budget::ComputeBudgetInstruction::RequestUnitsDeprecated {
541 units: 1_000,
542 additional_fee: 10
543 },
544 vec![]
545 )],
546 Err(TransactionError::InstructionError(
547 0,
548 InstructionError::InvalidInstructionData,
549 )),
550 ComputeBudget::default()
551 );
552 }
553
554 #[test]
555 fn test_process_instructions_disable_request_heap_frame() {
556 test!(
558 &[],
559 Ok(PrioritizationFeeDetails::default()),
560 ComputeBudget {
561 compute_unit_limit: 0,
562 ..ComputeBudget::default()
563 },
564 false
565 );
566
567 test!(
569 &[
570 ComputeBudgetInstruction::request_heap_frame(40 * 1024),
571 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
572 ],
573 Err(TransactionError::InstructionError(
574 0,
575 InstructionError::InvalidInstructionData
576 )),
577 ComputeBudget::default(),
578 false
579 );
580 test!(
581 &[
582 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
583 ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
584 ],
585 Err(TransactionError::InstructionError(
586 1,
587 InstructionError::InvalidInstructionData,
588 )),
589 ComputeBudget::default(),
590 false
591 );
592 test!(
593 &[
594 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
595 ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
596 ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
597 ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
598 ],
599 Err(TransactionError::InstructionError(
600 1,
601 InstructionError::InvalidInstructionData,
602 )),
603 ComputeBudget::default(),
604 false
605 );
606 test!(
607 &[
608 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
609 ComputeBudgetInstruction::set_compute_unit_limit(1),
610 ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
611 ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
612 ],
613 Err(TransactionError::InstructionError(
614 2,
615 InstructionError::InvalidInstructionData,
616 )),
617 ComputeBudget::default(),
618 false
619 );
620
621 test!(
623 &[
624 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
625 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
626 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
627 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
628 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
629 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
630 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
631 Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
632 ],
633 Ok(PrioritizationFeeDetails::default()),
634 ComputeBudget {
635 compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64 * 7,
636 ..ComputeBudget::default()
637 },
638 false
639 );
640 }
641}