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