solana_fee_structure/
lib.rs

1//! Fee structures.
2#![cfg_attr(docsrs, feature(doc_auto_cfg))]
3#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
4
5#[cfg(not(target_os = "solana"))]
6use solana_message::SanitizedMessage;
7use std::num::NonZeroU32;
8
9/// A fee and its associated compute unit limit
10#[derive(Debug, Default, Clone, Eq, PartialEq)]
11pub struct FeeBin {
12    /// maximum compute units for which this fee will be charged
13    pub limit: u64,
14    /// fee in lamports
15    pub fee: u64,
16}
17
18pub struct FeeBudgetLimits {
19    pub loaded_accounts_data_size_limit: NonZeroU32,
20    pub heap_cost: u64,
21    pub compute_unit_limit: u64,
22    pub prioritization_fee: u64,
23}
24
25/// Information used to calculate fees
26#[derive(Debug, Clone, Eq, PartialEq)]
27pub struct FeeStructure {
28    /// lamports per signature
29    pub lamports_per_signature: u64,
30    /// lamports_per_write_lock
31    pub lamports_per_write_lock: u64,
32    /// Compute unit fee bins
33    pub compute_fee_bins: Vec<FeeBin>,
34}
35
36#[cfg_attr(
37    feature = "serde",
38    derive(serde_derive::Deserialize, serde_derive::Serialize)
39)]
40#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
41pub struct FeeDetails {
42    transaction_fee: u64,
43    prioritization_fee: u64,
44}
45
46impl FeeDetails {
47    pub fn new(transaction_fee: u64, prioritization_fee: u64) -> Self {
48        Self {
49            transaction_fee,
50            prioritization_fee,
51        }
52    }
53
54    pub fn total_fee(&self) -> u64 {
55        self.transaction_fee.saturating_add(self.prioritization_fee)
56    }
57
58    pub fn accumulate(&mut self, fee_details: &FeeDetails) {
59        self.transaction_fee = self
60            .transaction_fee
61            .saturating_add(fee_details.transaction_fee);
62        self.prioritization_fee = self
63            .prioritization_fee
64            .saturating_add(fee_details.prioritization_fee)
65    }
66
67    pub fn transaction_fee(&self) -> u64 {
68        self.transaction_fee
69    }
70
71    pub fn prioritization_fee(&self) -> u64 {
72        self.prioritization_fee
73    }
74}
75
76pub const ACCOUNT_DATA_COST_PAGE_SIZE: u64 = 32_u64.saturating_mul(1024);
77
78impl FeeStructure {
79    #[deprecated(
80        since = "2.3.0",
81        note = "Use FeeStructure::default() and modify fields as needed"
82    )]
83    #[allow(deprecated)]
84    pub fn new(
85        sol_per_signature: f64,
86        sol_per_write_lock: f64,
87        compute_fee_bins: Vec<(u64, f64)>,
88    ) -> Self {
89        let compute_fee_bins = compute_fee_bins
90            .iter()
91            .map(|(limit, sol)| FeeBin {
92                limit: *limit,
93                fee: solana_native_token::sol_to_lamports(*sol),
94            })
95            .collect::<Vec<_>>();
96        FeeStructure {
97            lamports_per_signature: solana_native_token::sol_to_lamports(sol_per_signature),
98            lamports_per_write_lock: solana_native_token::sol_to_lamports(sol_per_write_lock),
99            compute_fee_bins,
100        }
101    }
102
103    pub fn get_max_fee(&self, num_signatures: u64, num_write_locks: u64) -> u64 {
104        num_signatures
105            .saturating_mul(self.lamports_per_signature)
106            .saturating_add(num_write_locks.saturating_mul(self.lamports_per_write_lock))
107            .saturating_add(
108                self.compute_fee_bins
109                    .last()
110                    .map(|bin| bin.fee)
111                    .unwrap_or_default(),
112            )
113    }
114
115    pub fn calculate_memory_usage_cost(
116        loaded_accounts_data_size_limit: u32,
117        heap_cost: u64,
118    ) -> u64 {
119        (loaded_accounts_data_size_limit as u64)
120            .saturating_add(ACCOUNT_DATA_COST_PAGE_SIZE.saturating_sub(1))
121            .saturating_div(ACCOUNT_DATA_COST_PAGE_SIZE)
122            .saturating_mul(heap_cost)
123    }
124
125    /// Calculate fee for `SanitizedMessage`
126    #[cfg(not(target_os = "solana"))]
127    #[deprecated(
128        since = "2.1.0",
129        note = "Please use `solana_fee::calculate_fee` instead."
130    )]
131    pub fn calculate_fee(
132        &self,
133        message: &SanitizedMessage,
134        lamports_per_signature: u64,
135        budget_limits: &FeeBudgetLimits,
136        include_loaded_account_data_size_in_fee: bool,
137    ) -> u64 {
138        #[allow(deprecated)]
139        self.calculate_fee_details(
140            message,
141            lamports_per_signature,
142            budget_limits,
143            include_loaded_account_data_size_in_fee,
144        )
145        .total_fee()
146    }
147
148    /// Calculate fee details for `SanitizedMessage`
149    #[cfg(not(target_os = "solana"))]
150    #[deprecated(
151        since = "2.1.0",
152        note = "Please use `solana_fee::calculate_fee_details` instead."
153    )]
154    pub fn calculate_fee_details(
155        &self,
156        message: &SanitizedMessage,
157        lamports_per_signature: u64,
158        budget_limits: &FeeBudgetLimits,
159        include_loaded_account_data_size_in_fee: bool,
160    ) -> FeeDetails {
161        // Backward compatibility - lamports_per_signature == 0 means to clear
162        // transaction fee to zero
163        if lamports_per_signature == 0 {
164            return FeeDetails::default();
165        }
166
167        let signature_fee = message
168            .num_total_signatures()
169            .saturating_mul(self.lamports_per_signature);
170        let write_lock_fee = message
171            .num_write_locks()
172            .saturating_mul(self.lamports_per_write_lock);
173
174        // `compute_fee` covers costs for both requested_compute_units and
175        // requested_loaded_account_data_size
176        let loaded_accounts_data_size_cost = if include_loaded_account_data_size_in_fee {
177            FeeStructure::calculate_memory_usage_cost(
178                budget_limits.loaded_accounts_data_size_limit.get(),
179                budget_limits.heap_cost,
180            )
181        } else {
182            0_u64
183        };
184        let total_compute_units =
185            loaded_accounts_data_size_cost.saturating_add(budget_limits.compute_unit_limit);
186        let compute_fee = self
187            .compute_fee_bins
188            .iter()
189            .find(|bin| total_compute_units <= bin.limit)
190            .map(|bin| bin.fee)
191            .unwrap_or_else(|| {
192                self.compute_fee_bins
193                    .last()
194                    .map(|bin| bin.fee)
195                    .unwrap_or_default()
196            });
197
198        FeeDetails {
199            transaction_fee: signature_fee
200                .saturating_add(write_lock_fee)
201                .saturating_add(compute_fee),
202            prioritization_fee: budget_limits.prioritization_fee,
203        }
204    }
205}
206
207impl Default for FeeStructure {
208    fn default() -> Self {
209        Self {
210            lamports_per_signature: 5000,
211            lamports_per_write_lock: 0,
212            compute_fee_bins: vec![FeeBin {
213                limit: 1_400_000,
214                fee: 0,
215            }],
216        }
217    }
218}
219
220#[cfg(feature = "frozen-abi")]
221impl ::solana_frozen_abi::abi_example::AbiExample for FeeStructure {
222    fn example() -> Self {
223        FeeStructure::default()
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[test]
232    fn test_calculate_memory_usage_cost() {
233        let heap_cost = 99;
234        const K: u32 = 1024;
235
236        // accounts data size are priced in block of 32K, ...
237
238        // ... requesting less than 32K should still be charged as one block
239        assert_eq!(
240            heap_cost,
241            FeeStructure::calculate_memory_usage_cost(31 * K, heap_cost)
242        );
243
244        // ... requesting exact 32K should be charged as one block
245        assert_eq!(
246            heap_cost,
247            FeeStructure::calculate_memory_usage_cost(32 * K, heap_cost)
248        );
249
250        // ... requesting slightly above 32K should be charged as 2 block
251        assert_eq!(
252            heap_cost * 2,
253            FeeStructure::calculate_memory_usage_cost(33 * K, heap_cost)
254        );
255
256        // ... requesting exact 64K should be charged as 2 block
257        assert_eq!(
258            heap_cost * 2,
259            FeeStructure::calculate_memory_usage_cost(64 * K, heap_cost)
260        );
261    }
262}