solana_fee_calculator/
lib.rs1#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
3#![allow(clippy::arithmetic_side_effects)]
4#![no_std]
5#![cfg_attr(docsrs, feature(doc_auto_cfg))]
6use log::*;
7#[cfg(feature = "frozen-abi")]
8extern crate std;
9
10#[repr(C)]
11#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
12#[cfg_attr(
13 feature = "serde",
14 derive(serde_derive::Serialize, serde_derive::Deserialize)
15)]
16#[derive(Default, PartialEq, Eq, Clone, Copy, Debug)]
17#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
18pub struct FeeCalculator {
19 pub lamports_per_signature: u64,
24}
25
26impl FeeCalculator {
27 pub fn new(lamports_per_signature: u64) -> Self {
28 Self {
29 lamports_per_signature,
30 }
31 }
32}
33
34#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
35#[cfg_attr(
36 feature = "serde",
37 derive(serde_derive::Serialize, serde_derive::Deserialize)
38)]
39#[derive(PartialEq, Eq, Clone, Debug)]
40#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
41pub struct FeeRateGovernor {
42 #[cfg_attr(feature = "serde", serde(skip))]
45 pub lamports_per_signature: u64,
46
47 pub target_lamports_per_signature: u64,
50
51 pub target_signatures_per_slot: u64,
55
56 pub min_lamports_per_signature: u64,
57 pub max_lamports_per_signature: u64,
58
59 pub burn_percent: u8,
61}
62
63pub const DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE: u64 = 10_000;
64const DEFAULT_MS_PER_SLOT: u64 = 400;
65#[cfg(test)]
66static_assertions::const_assert_eq!(DEFAULT_MS_PER_SLOT, solana_clock::DEFAULT_MS_PER_SLOT);
67pub const DEFAULT_TARGET_SIGNATURES_PER_SLOT: u64 = 50 * DEFAULT_MS_PER_SLOT;
68
69pub const DEFAULT_BURN_PERCENT: u8 = 50;
71
72impl Default for FeeRateGovernor {
73 fn default() -> Self {
74 Self {
75 lamports_per_signature: 0,
76 target_lamports_per_signature: DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE,
77 target_signatures_per_slot: DEFAULT_TARGET_SIGNATURES_PER_SLOT,
78 min_lamports_per_signature: 0,
79 max_lamports_per_signature: 0,
80 burn_percent: DEFAULT_BURN_PERCENT,
81 }
82 }
83}
84
85impl FeeRateGovernor {
86 pub fn new(target_lamports_per_signature: u64, target_signatures_per_slot: u64) -> Self {
87 let base_fee_rate_governor = Self {
88 target_lamports_per_signature,
89 lamports_per_signature: target_lamports_per_signature,
90 target_signatures_per_slot,
91 ..FeeRateGovernor::default()
92 };
93
94 Self::new_derived(&base_fee_rate_governor, 0)
95 }
96
97 pub fn new_derived(
98 base_fee_rate_governor: &FeeRateGovernor,
99 latest_signatures_per_slot: u64,
100 ) -> Self {
101 let mut me = base_fee_rate_governor.clone();
102
103 if me.target_signatures_per_slot > 0 {
104 me.min_lamports_per_signature = core::cmp::max(1, me.target_lamports_per_signature / 2);
107 me.max_lamports_per_signature = me.target_lamports_per_signature * 10;
108
109 let desired_lamports_per_signature =
111 me.max_lamports_per_signature
112 .min(me.min_lamports_per_signature.max(
113 me.target_lamports_per_signature
114 * core::cmp::min(latest_signatures_per_slot, u32::MAX as u64)
115 / me.target_signatures_per_slot,
116 ));
117
118 trace!(
119 "desired_lamports_per_signature: {}",
120 desired_lamports_per_signature
121 );
122
123 let gap = desired_lamports_per_signature as i64
124 - base_fee_rate_governor.lamports_per_signature as i64;
125
126 if gap == 0 {
127 me.lamports_per_signature = desired_lamports_per_signature;
128 } else {
129 let gap_adjust =
132 core::cmp::max(1, me.target_lamports_per_signature / 20) as i64 * gap.signum();
133
134 trace!(
135 "lamports_per_signature gap is {}, adjusting by {}",
136 gap,
137 gap_adjust
138 );
139
140 me.lamports_per_signature =
141 me.max_lamports_per_signature
142 .min(me.min_lamports_per_signature.max(
143 (base_fee_rate_governor.lamports_per_signature as i64 + gap_adjust)
144 as u64,
145 ));
146 }
147 } else {
148 me.lamports_per_signature = base_fee_rate_governor.target_lamports_per_signature;
149 me.min_lamports_per_signature = me.target_lamports_per_signature;
150 me.max_lamports_per_signature = me.target_lamports_per_signature;
151 }
152 debug!(
153 "new_derived(): lamports_per_signature: {}",
154 me.lamports_per_signature
155 );
156 me
157 }
158
159 pub fn clone_with_lamports_per_signature(&self, lamports_per_signature: u64) -> Self {
160 Self {
161 lamports_per_signature,
162 ..*self
163 }
164 }
165
166 pub fn burn(&self, fees: u64) -> (u64, u64) {
168 let burned = fees * u64::from(self.burn_percent) / 100;
169 (fees - burned, burned)
170 }
171
172 pub fn create_fee_calculator(&self) -> FeeCalculator {
174 FeeCalculator::new(self.lamports_per_signature)
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_fee_rate_governor_burn() {
184 let mut fee_rate_governor = FeeRateGovernor::default();
185 assert_eq!(fee_rate_governor.burn(2), (1, 1));
186
187 fee_rate_governor.burn_percent = 0;
188 assert_eq!(fee_rate_governor.burn(2), (2, 0));
189
190 fee_rate_governor.burn_percent = 100;
191 assert_eq!(fee_rate_governor.burn(2), (0, 2));
192 }
193
194 #[test]
195 fn test_fee_rate_governor_derived_default() {
196 solana_logger::setup();
197
198 let f0 = FeeRateGovernor::default();
199 assert_eq!(
200 f0.target_signatures_per_slot,
201 DEFAULT_TARGET_SIGNATURES_PER_SLOT
202 );
203 assert_eq!(
204 f0.target_lamports_per_signature,
205 DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE
206 );
207 assert_eq!(f0.lamports_per_signature, 0);
208
209 let f1 = FeeRateGovernor::new_derived(&f0, DEFAULT_TARGET_SIGNATURES_PER_SLOT);
210 assert_eq!(
211 f1.target_signatures_per_slot,
212 DEFAULT_TARGET_SIGNATURES_PER_SLOT
213 );
214 assert_eq!(
215 f1.target_lamports_per_signature,
216 DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE
217 );
218 assert_eq!(
219 f1.lamports_per_signature,
220 DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE / 2
221 ); }
223
224 #[test]
225 fn test_fee_rate_governor_derived_adjust() {
226 solana_logger::setup();
227
228 let mut f = FeeRateGovernor {
229 target_lamports_per_signature: 100,
230 target_signatures_per_slot: 100,
231 ..FeeRateGovernor::default()
232 };
233 f = FeeRateGovernor::new_derived(&f, 0);
234
235 let mut count = 0;
237 loop {
238 let last_lamports_per_signature = f.lamports_per_signature;
239
240 f = FeeRateGovernor::new_derived(&f, u64::MAX);
241 info!("[up] f.lamports_per_signature={}", f.lamports_per_signature);
242
243 if f.lamports_per_signature == last_lamports_per_signature {
245 break;
246 }
247 assert!(count < 1000);
249 count += 1;
250 }
251
252 let mut count = 0;
254 loop {
255 let last_lamports_per_signature = f.lamports_per_signature;
256 f = FeeRateGovernor::new_derived(&f, 0);
257
258 info!(
259 "[down] f.lamports_per_signature={}",
260 f.lamports_per_signature
261 );
262
263 if f.lamports_per_signature == last_lamports_per_signature {
265 break;
266 }
267
268 assert!(count < 1000);
270 count += 1;
271 }
272
273 let mut count = 0;
275 while f.lamports_per_signature != f.target_lamports_per_signature {
276 f = FeeRateGovernor::new_derived(&f, f.target_signatures_per_slot);
277 info!(
278 "[target] f.lamports_per_signature={}",
279 f.lamports_per_signature
280 );
281 assert!(count < 100);
283 count += 1;
284 }
285 }
286}