1#[cfg(not(target_os = "solana"))]
4use solana_program::message::SanitizedMessage;
5use {solana_native_token::sol_to_lamports, std::num::NonZeroU32};
6
7#[derive(Debug, Default, Clone, Eq, PartialEq)]
9pub struct FeeBin {
10 pub limit: u64,
12 pub fee: u64,
14}
15
16pub struct FeeBudgetLimits {
17 pub loaded_accounts_data_size_limit: NonZeroU32,
18 pub heap_cost: u64,
19 pub compute_unit_limit: u64,
20 pub prioritization_fee: u64,
21}
22
23#[derive(Debug, Clone, Eq, PartialEq)]
25pub struct FeeStructure {
26 pub lamports_per_signature: u64,
28 pub lamports_per_write_lock: u64,
30 pub compute_fee_bins: Vec<FeeBin>,
32}
33
34#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Deserialize, Serialize)]
35pub struct FeeDetails {
36 transaction_fee: u64,
37 prioritization_fee: u64,
38 remove_rounding_in_fee_calculation: bool,
39}
40
41impl FeeDetails {
42 pub fn new(
43 transaction_fee: u64,
44 prioritization_fee: u64,
45 remove_rounding_in_fee_calculation: bool,
46 ) -> Self {
47 Self {
48 transaction_fee,
49 prioritization_fee,
50 remove_rounding_in_fee_calculation,
51 }
52 }
53
54 pub fn total_fee(&self) -> u64 {
55 let total_fee = self.transaction_fee.saturating_add(self.prioritization_fee);
56 if self.remove_rounding_in_fee_calculation {
57 total_fee
58 } else {
59 (total_fee as f64).round() as u64
61 }
62 }
63
64 pub fn accumulate(&mut self, fee_details: &FeeDetails) {
65 self.transaction_fee = self
66 .transaction_fee
67 .saturating_add(fee_details.transaction_fee);
68 self.prioritization_fee = self
69 .prioritization_fee
70 .saturating_add(fee_details.prioritization_fee)
71 }
72
73 pub fn transaction_fee(&self) -> u64 {
74 self.transaction_fee
75 }
76
77 pub fn prioritization_fee(&self) -> u64 {
78 self.prioritization_fee
79 }
80}
81
82pub const ACCOUNT_DATA_COST_PAGE_SIZE: u64 = 32_u64.saturating_mul(1024);
83
84impl FeeStructure {
85 pub fn new(
86 sol_per_signature: f64,
87 sol_per_write_lock: f64,
88 compute_fee_bins: Vec<(u64, f64)>,
89 ) -> Self {
90 let compute_fee_bins = compute_fee_bins
91 .iter()
92 .map(|(limit, sol)| FeeBin {
93 limit: *limit,
94 fee: sol_to_lamports(*sol),
95 })
96 .collect::<Vec<_>>();
97 FeeStructure {
98 lamports_per_signature: sol_to_lamports(sol_per_signature),
99 lamports_per_write_lock: sol_to_lamports(sol_per_write_lock),
100 compute_fee_bins,
101 }
102 }
103
104 pub fn get_max_fee(&self, num_signatures: u64, num_write_locks: u64) -> u64 {
105 num_signatures
106 .saturating_mul(self.lamports_per_signature)
107 .saturating_add(num_write_locks.saturating_mul(self.lamports_per_write_lock))
108 .saturating_add(
109 self.compute_fee_bins
110 .last()
111 .map(|bin| bin.fee)
112 .unwrap_or_default(),
113 )
114 }
115
116 pub fn calculate_memory_usage_cost(
117 loaded_accounts_data_size_limit: u32,
118 heap_cost: u64,
119 ) -> u64 {
120 (loaded_accounts_data_size_limit as u64)
121 .saturating_add(ACCOUNT_DATA_COST_PAGE_SIZE.saturating_sub(1))
122 .saturating_div(ACCOUNT_DATA_COST_PAGE_SIZE)
123 .saturating_mul(heap_cost)
124 }
125
126 #[cfg(not(target_os = "solana"))]
128 #[deprecated(
129 since = "2.1.0",
130 note = "Please use `solana_fee::calculate_fee` instead."
131 )]
132 pub fn calculate_fee(
133 &self,
134 message: &SanitizedMessage,
135 lamports_per_signature: u64,
136 budget_limits: &FeeBudgetLimits,
137 include_loaded_account_data_size_in_fee: bool,
138 remove_rounding_in_fee_calculation: bool,
139 ) -> u64 {
140 #[allow(deprecated)]
141 self.calculate_fee_details(
142 message,
143 lamports_per_signature,
144 budget_limits,
145 include_loaded_account_data_size_in_fee,
146 remove_rounding_in_fee_calculation,
147 )
148 .total_fee()
149 }
150
151 #[cfg(not(target_os = "solana"))]
153 #[deprecated(
154 since = "2.1.0",
155 note = "Please use `solana_fee::calculate_fee_details` instead."
156 )]
157 pub fn calculate_fee_details(
158 &self,
159 message: &SanitizedMessage,
160 lamports_per_signature: u64,
161 budget_limits: &FeeBudgetLimits,
162 include_loaded_account_data_size_in_fee: bool,
163 remove_rounding_in_fee_calculation: bool,
164 ) -> FeeDetails {
165 if lamports_per_signature == 0 {
168 return FeeDetails::default();
169 }
170
171 let signature_fee = message
172 .num_total_signatures()
173 .saturating_mul(self.lamports_per_signature);
174 let write_lock_fee = message
175 .num_write_locks()
176 .saturating_mul(self.lamports_per_write_lock);
177
178 let loaded_accounts_data_size_cost = if include_loaded_account_data_size_in_fee {
181 FeeStructure::calculate_memory_usage_cost(
182 budget_limits.loaded_accounts_data_size_limit.get(),
183 budget_limits.heap_cost,
184 )
185 } else {
186 0_u64
187 };
188 let total_compute_units =
189 loaded_accounts_data_size_cost.saturating_add(budget_limits.compute_unit_limit);
190 let compute_fee = self
191 .compute_fee_bins
192 .iter()
193 .find(|bin| total_compute_units <= bin.limit)
194 .map(|bin| bin.fee)
195 .unwrap_or_else(|| {
196 self.compute_fee_bins
197 .last()
198 .map(|bin| bin.fee)
199 .unwrap_or_default()
200 });
201
202 FeeDetails {
203 transaction_fee: signature_fee
204 .saturating_add(write_lock_fee)
205 .saturating_add(compute_fee),
206 prioritization_fee: budget_limits.prioritization_fee,
207 remove_rounding_in_fee_calculation,
208 }
209 }
210}
211
212impl Default for FeeStructure {
213 fn default() -> Self {
214 Self::new(0.000005, 0.0, vec![(1_400_000, 0.0)])
215 }
216}
217
218#[cfg(feature = "frozen-abi")]
219impl ::solana_frozen_abi::abi_example::AbiExample for FeeStructure {
220 fn example() -> Self {
221 FeeStructure::default()
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_calculate_memory_usage_cost() {
231 let heap_cost = 99;
232 const K: u32 = 1024;
233
234 assert_eq!(
238 heap_cost,
239 FeeStructure::calculate_memory_usage_cost(31 * K, heap_cost)
240 );
241
242 assert_eq!(
244 heap_cost,
245 FeeStructure::calculate_memory_usage_cost(32 * K, heap_cost)
246 );
247
248 assert_eq!(
250 heap_cost * 2,
251 FeeStructure::calculate_memory_usage_cost(33 * K, heap_cost)
252 );
253
254 assert_eq!(
256 heap_cost * 2,
257 FeeStructure::calculate_memory_usage_cost(64 * K, heap_cost)
258 );
259 }
260
261 #[test]
262 fn test_total_fee_rounding() {
263 let transaction_fee = u64::MAX - 11;
267 let prioritization_fee = 1;
268 let expected_large_fee = u64::MAX - 10;
269
270 let details_with_rounding = FeeDetails {
271 transaction_fee,
272 prioritization_fee,
273 remove_rounding_in_fee_calculation: false,
274 };
275 let details_without_rounding = FeeDetails {
276 transaction_fee,
277 prioritization_fee,
278 remove_rounding_in_fee_calculation: true,
279 };
280
281 assert_eq!(details_without_rounding.total_fee(), expected_large_fee);
282 assert_ne!(details_with_rounding.total_fee(), expected_large_fee);
283 }
284}