solana_compute_budget/
compute_budget_limits.rs

1use {
2    solana_fee_structure::FeeBudgetLimits, solana_program_entrypoint::HEAP_LENGTH,
3    std::num::NonZeroU32,
4};
5
6/// Roughly 0.5us/page, where page is 32K; given roughly 15CU/us, the
7/// default heap page cost = 0.5 * 15 ~= 8CU/page
8pub const DEFAULT_HEAP_COST: u64 = 8;
9pub const DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT: u32 = 200_000;
10// SIMD-170 defines max CUs to be allocated for any builtin program instructions, that
11// have not been migrated to sBPF programs.
12pub const MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT: u32 = 3_000;
13pub const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000;
14pub const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024;
15pub const MIN_HEAP_FRAME_BYTES: u32 = HEAP_LENGTH as u32;
16
17type MicroLamports = u128;
18
19/// There are 10^6 micro-lamports in one lamport
20const MICRO_LAMPORTS_PER_LAMPORT: u64 = 1_000_000;
21
22/// The total accounts data a transaction can load is limited to 64MiB to not break
23/// anyone in Mainnet-beta today. It can be set by set_loaded_accounts_data_size_limit instruction
24pub const MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES: NonZeroU32 =
25    unsafe { NonZeroU32::new_unchecked(64 * 1024 * 1024) };
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub struct ComputeBudgetLimits {
29    pub updated_heap_bytes: u32,
30    pub compute_unit_limit: u32,
31    pub compute_unit_price: u64,
32    pub loaded_accounts_bytes: NonZeroU32,
33}
34
35impl Default for ComputeBudgetLimits {
36    fn default() -> Self {
37        ComputeBudgetLimits {
38            updated_heap_bytes: MIN_HEAP_FRAME_BYTES,
39            compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT,
40            compute_unit_price: 0,
41            loaded_accounts_bytes: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES,
42        }
43    }
44}
45
46fn get_prioritization_fee(compute_unit_price: u64, compute_unit_limit: u64) -> u64 {
47    let micro_lamport_fee: MicroLamports =
48        (compute_unit_price as u128).saturating_mul(compute_unit_limit as u128);
49    micro_lamport_fee
50        .saturating_add(MICRO_LAMPORTS_PER_LAMPORT.saturating_sub(1) as u128)
51        .checked_div(MICRO_LAMPORTS_PER_LAMPORT as u128)
52        .and_then(|fee| u64::try_from(fee).ok())
53        .unwrap_or(u64::MAX)
54}
55
56impl From<ComputeBudgetLimits> for FeeBudgetLimits {
57    fn from(val: ComputeBudgetLimits) -> Self {
58        let prioritization_fee =
59            get_prioritization_fee(val.compute_unit_price, u64::from(val.compute_unit_limit));
60
61        FeeBudgetLimits {
62            loaded_accounts_data_size_limit: val.loaded_accounts_bytes,
63            heap_cost: DEFAULT_HEAP_COST,
64            compute_unit_limit: u64::from(val.compute_unit_limit),
65            prioritization_fee,
66        }
67    }
68}
69
70#[cfg(test)]
71mod test {
72    use super::*;
73
74    #[test]
75    fn test_new_with_no_fee() {
76        for compute_units in [0, 1, MICRO_LAMPORTS_PER_LAMPORT, u64::MAX] {
77            assert_eq!(get_prioritization_fee(0, compute_units), 0);
78        }
79    }
80
81    #[test]
82    fn test_new_with_compute_unit_price() {
83        assert_eq!(
84            get_prioritization_fee(MICRO_LAMPORTS_PER_LAMPORT - 1, 1),
85            1,
86            "should round up (<1.0) lamport fee to 1 lamport"
87        );
88
89        assert_eq!(get_prioritization_fee(MICRO_LAMPORTS_PER_LAMPORT, 1), 1);
90
91        assert_eq!(
92            get_prioritization_fee(MICRO_LAMPORTS_PER_LAMPORT + 1, 1),
93            2,
94            "should round up (>1.0) lamport fee to 2 lamports"
95        );
96
97        assert_eq!(get_prioritization_fee(200, 100_000), 20);
98
99        assert_eq!(
100            get_prioritization_fee(MICRO_LAMPORTS_PER_LAMPORT, u64::MAX),
101            u64::MAX
102        );
103
104        assert_eq!(get_prioritization_fee(u64::MAX, u64::MAX), u64::MAX);
105    }
106}