clone_solana_fee_structure/
lib.rs1#![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#[derive(Debug, Default, Clone, Eq, PartialEq)]
11pub struct FeeBin {
12 pub limit: u64,
14 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#[derive(Debug, Clone, Eq, PartialEq)]
27pub struct FeeStructure {
28 pub lamports_per_signature: u64,
30 pub lamports_per_write_lock: u64,
32 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 #[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 #[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 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 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 assert_eq!(
228 heap_cost,
229 FeeStructure::calculate_memory_usage_cost(31 * K, heap_cost)
230 );
231
232 assert_eq!(
234 heap_cost,
235 FeeStructure::calculate_memory_usage_cost(32 * K, heap_cost)
236 );
237
238 assert_eq!(
240 heap_cost * 2,
241 FeeStructure::calculate_memory_usage_cost(33 * K, heap_cost)
242 );
243
244 assert_eq!(
246 heap_cost * 2,
247 FeeStructure::calculate_memory_usage_cost(64 * K, heap_cost)
248 );
249 }
250}