clone_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 clone_solana_message::SanitizedMessage;
7use {clone_solana_native_token::sol_to_lamports, 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    pub fn new(
80        sol_per_signature: f64,
81        sol_per_write_lock: f64,
82        compute_fee_bins: Vec<(u64, f64)>,
83    ) -> Self {
84        let compute_fee_bins = compute_fee_bins
85            .iter()
86            .map(|(limit, sol)| FeeBin {
87                limit: *limit,
88                fee: sol_to_lamports(*sol),
89            })
90            .collect::<Vec<_>>();
91        FeeStructure {
92            lamports_per_signature: sol_to_lamports(sol_per_signature),
93            lamports_per_write_lock: sol_to_lamports(sol_per_write_lock),
94            compute_fee_bins,
95        }
96    }
97
98    pub fn get_max_fee(&self, num_signatures: u64, num_write_locks: u64) -> u64 {
99        num_signatures
100            .saturating_mul(self.lamports_per_signature)
101            .saturating_add(num_write_locks.saturating_mul(self.lamports_per_write_lock))
102            .saturating_add(
103                self.compute_fee_bins
104                    .last()
105                    .map(|bin| bin.fee)
106                    .unwrap_or_default(),
107            )
108    }
109
110    pub fn calculate_memory_usage_cost(
111        loaded_accounts_data_size_limit: u32,
112        heap_cost: u64,
113    ) -> u64 {
114        (loaded_accounts_data_size_limit as u64)
115            .saturating_add(ACCOUNT_DATA_COST_PAGE_SIZE.saturating_sub(1))
116            .saturating_div(ACCOUNT_DATA_COST_PAGE_SIZE)
117            .saturating_mul(heap_cost)
118    }
119
120    /// Calculate fee for `SanitizedMessage`
121    #[cfg(not(target_os = "solana"))]
122    #[deprecated(
123        since = "2.1.0",
124        note = "Please use `clone_solana_fee::calculate_fee` instead."
125    )]
126    pub fn calculate_fee(
127        &self,
128        message: &SanitizedMessage,
129        lamports_per_signature: u64,
130        budget_limits: &FeeBudgetLimits,
131        include_loaded_account_data_size_in_fee: bool,
132    ) -> u64 {
133        #[allow(deprecated)]
134        self.calculate_fee_details(
135            message,
136            lamports_per_signature,
137            budget_limits,
138            include_loaded_account_data_size_in_fee,
139        )
140        .total_fee()
141    }
142
143    /// Calculate fee details for `SanitizedMessage`
144    #[cfg(not(target_os = "solana"))]
145    #[deprecated(
146        since = "2.1.0",
147        note = "Please use `clone_solana_fee::calculate_fee_details` instead."
148    )]
149    pub fn calculate_fee_details(
150        &self,
151        message: &SanitizedMessage,
152        lamports_per_signature: u64,
153        budget_limits: &FeeBudgetLimits,
154        include_loaded_account_data_size_in_fee: bool,
155    ) -> FeeDetails {
156        // Backward compatibility - lamports_per_signature == 0 means to clear
157        // transaction fee to zero
158        if lamports_per_signature == 0 {
159            return FeeDetails::default();
160        }
161
162        let signature_fee = message
163            .num_total_signatures()
164            .saturating_mul(self.lamports_per_signature);
165        let write_lock_fee = message
166            .num_write_locks()
167            .saturating_mul(self.lamports_per_write_lock);
168
169        // `compute_fee` covers costs for both requested_compute_units and
170        // requested_loaded_account_data_size
171        let loaded_accounts_data_size_cost = if include_loaded_account_data_size_in_fee {
172            FeeStructure::calculate_memory_usage_cost(
173                budget_limits.loaded_accounts_data_size_limit.get(),
174                budget_limits.heap_cost,
175            )
176        } else {
177            0_u64
178        };
179        let total_compute_units =
180            loaded_accounts_data_size_cost.saturating_add(budget_limits.compute_unit_limit);
181        let compute_fee = self
182            .compute_fee_bins
183            .iter()
184            .find(|bin| total_compute_units <= bin.limit)
185            .map(|bin| bin.fee)
186            .unwrap_or_else(|| {
187                self.compute_fee_bins
188                    .last()
189                    .map(|bin| bin.fee)
190                    .unwrap_or_default()
191            });
192
193        FeeDetails {
194            transaction_fee: signature_fee
195                .saturating_add(write_lock_fee)
196                .saturating_add(compute_fee),
197            prioritization_fee: budget_limits.prioritization_fee,
198        }
199    }
200}
201
202impl Default for FeeStructure {
203    fn default() -> Self {
204        Self::new(0.000005, 0.0, vec![(1_400_000, 0.0)])
205    }
206}
207
208#[cfg(feature = "frozen-abi")]
209impl ::clone_solana_frozen_abi::abi_example::AbiExample for FeeStructure {
210    fn example() -> Self {
211        FeeStructure::default()
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218
219    #[test]
220    fn test_calculate_memory_usage_cost() {
221        let heap_cost = 99;
222        const K: u32 = 1024;
223
224        // accounts data size are priced in block of 32K, ...
225
226        // ... requesting less than 32K should still be charged as one block
227        assert_eq!(
228            heap_cost,
229            FeeStructure::calculate_memory_usage_cost(31 * K, heap_cost)
230        );
231
232        // ... requesting exact 32K should be charged as one block
233        assert_eq!(
234            heap_cost,
235            FeeStructure::calculate_memory_usage_cost(32 * K, heap_cost)
236        );
237
238        // ... requesting slightly above 32K should be charged as 2 block
239        assert_eq!(
240            heap_cost * 2,
241            FeeStructure::calculate_memory_usage_cost(33 * K, heap_cost)
242        );
243
244        // ... requesting exact 64K should be charged as 2 block
245        assert_eq!(
246            heap_cost * 2,
247            FeeStructure::calculate_memory_usage_cost(64 * K, heap_cost)
248        );
249    }
250}