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_cfg))]
6use log::*;
7#[cfg(feature = "frozen-abi")]
8extern crate std;
9#[cfg(feature = "wincode")]
10use wincode::{SchemaRead, SchemaWrite};
11
12#[repr(C)]
13#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
14#[cfg_attr(
15 feature = "serde",
16 derive(serde_derive::Serialize, serde_derive::Deserialize)
17)]
18#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
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(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
38#[cfg_attr(
39 feature = "serde",
40 derive(serde_derive::Serialize, serde_derive::Deserialize)
41)]
42#[derive(PartialEq, Eq, Clone, Debug)]
43#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
44pub struct FeeRateGovernor {
45 #[cfg_attr(feature = "serde", serde(skip))]
48 pub lamports_per_signature: u64,
49
50 pub target_lamports_per_signature: u64,
53
54 pub target_signatures_per_slot: u64,
58
59 pub min_lamports_per_signature: u64,
60 pub max_lamports_per_signature: u64,
61
62 pub burn_percent: u8,
64}
65
66pub const DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE: u64 = 10_000;
67const DEFAULT_MS_PER_SLOT: u64 = 400;
68#[cfg(test)]
69static_assertions::const_assert_eq!(DEFAULT_MS_PER_SLOT, solana_clock::DEFAULT_MS_PER_SLOT);
70pub const DEFAULT_TARGET_SIGNATURES_PER_SLOT: u64 = 50 * DEFAULT_MS_PER_SLOT;
71
72pub const DEFAULT_BURN_PERCENT: u8 = 50;
74
75impl Default for FeeRateGovernor {
76 fn default() -> Self {
77 Self {
78 lamports_per_signature: 0,
79 target_lamports_per_signature: DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE,
80 target_signatures_per_slot: DEFAULT_TARGET_SIGNATURES_PER_SLOT,
81 min_lamports_per_signature: 0,
82 max_lamports_per_signature: 0,
83 burn_percent: DEFAULT_BURN_PERCENT,
84 }
85 }
86}
87
88impl FeeRateGovernor {
89 pub fn new(target_lamports_per_signature: u64, target_signatures_per_slot: u64) -> Self {
90 let base_fee_rate_governor = Self {
91 target_lamports_per_signature,
92 lamports_per_signature: target_lamports_per_signature,
93 target_signatures_per_slot,
94 ..FeeRateGovernor::default()
95 };
96
97 Self::new_derived(&base_fee_rate_governor, 0)
98 }
99
100 pub fn new_derived(
101 base_fee_rate_governor: &FeeRateGovernor,
102 latest_signatures_per_slot: u64,
103 ) -> Self {
104 let mut me = base_fee_rate_governor.clone();
105
106 if me.target_signatures_per_slot > 0 {
107 me.min_lamports_per_signature = core::cmp::max(1, me.target_lamports_per_signature / 2);
110 me.max_lamports_per_signature = me.target_lamports_per_signature * 10;
111
112 let desired_lamports_per_signature =
114 me.max_lamports_per_signature
115 .min(me.min_lamports_per_signature.max(
116 me.target_lamports_per_signature
117 * core::cmp::min(latest_signatures_per_slot, u32::MAX as u64)
118 / me.target_signatures_per_slot,
119 ));
120
121 trace!(
122 "desired_lamports_per_signature: {}",
123 desired_lamports_per_signature
124 );
125
126 let gap = desired_lamports_per_signature as i64
127 - base_fee_rate_governor.lamports_per_signature as i64;
128
129 if gap == 0 {
130 me.lamports_per_signature = desired_lamports_per_signature;
131 } else {
132 let gap_adjust =
135 core::cmp::max(1, me.target_lamports_per_signature / 20) as i64 * gap.signum();
136
137 trace!(
138 "lamports_per_signature gap is {}, adjusting by {}",
139 gap,
140 gap_adjust
141 );
142
143 me.lamports_per_signature =
144 me.max_lamports_per_signature
145 .min(me.min_lamports_per_signature.max(
146 (base_fee_rate_governor.lamports_per_signature as i64 + gap_adjust)
147 as u64,
148 ));
149 }
150 } else {
151 me.lamports_per_signature = base_fee_rate_governor.target_lamports_per_signature;
152 me.min_lamports_per_signature = me.target_lamports_per_signature;
153 me.max_lamports_per_signature = me.target_lamports_per_signature;
154 }
155 debug!(
156 "new_derived(): lamports_per_signature: {}",
157 me.lamports_per_signature
158 );
159 me
160 }
161
162 pub fn clone_with_lamports_per_signature(&self, lamports_per_signature: u64) -> Self {
163 Self {
164 lamports_per_signature,
165 ..*self
166 }
167 }
168
169 pub fn burn(&self, fees: u64) -> (u64, u64) {
171 let burned = fees * u64::from(self.burn_percent) / 100;
172 (fees - burned, burned)
173 }
174
175 pub fn create_fee_calculator(&self) -> FeeCalculator {
177 FeeCalculator::new(self.lamports_per_signature)
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn test_fee_rate_governor_burn() {
187 let mut fee_rate_governor = FeeRateGovernor::default();
188 assert_eq!(fee_rate_governor.burn(2), (1, 1));
189
190 fee_rate_governor.burn_percent = 0;
191 assert_eq!(fee_rate_governor.burn(2), (2, 0));
192
193 fee_rate_governor.burn_percent = 100;
194 assert_eq!(fee_rate_governor.burn(2), (0, 2));
195 }
196
197 #[test]
198 fn test_fee_rate_governor_derived_default() {
199 let f0 = FeeRateGovernor::default();
200 assert_eq!(
201 f0.target_signatures_per_slot,
202 DEFAULT_TARGET_SIGNATURES_PER_SLOT
203 );
204 assert_eq!(
205 f0.target_lamports_per_signature,
206 DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE
207 );
208 assert_eq!(f0.lamports_per_signature, 0);
209
210 let f1 = FeeRateGovernor::new_derived(&f0, DEFAULT_TARGET_SIGNATURES_PER_SLOT);
211 assert_eq!(
212 f1.target_signatures_per_slot,
213 DEFAULT_TARGET_SIGNATURES_PER_SLOT
214 );
215 assert_eq!(
216 f1.target_lamports_per_signature,
217 DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE
218 );
219 assert_eq!(
220 f1.lamports_per_signature,
221 DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE / 2
222 ); }
224
225 #[test]
226 fn test_fee_rate_governor_derived_adjust() {
227 let mut f = FeeRateGovernor {
228 target_lamports_per_signature: 100,
229 target_signatures_per_slot: 100,
230 ..FeeRateGovernor::default()
231 };
232 f = FeeRateGovernor::new_derived(&f, 0);
233
234 let mut count = 0;
236 loop {
237 let last_lamports_per_signature = f.lamports_per_signature;
238 f = FeeRateGovernor::new_derived(&f, u64::MAX);
239
240 if f.lamports_per_signature == last_lamports_per_signature {
242 break;
243 }
244 assert!(count < 1000);
246 count += 1;
247 }
248
249 let mut count = 0;
251 loop {
252 let last_lamports_per_signature = f.lamports_per_signature;
253 f = FeeRateGovernor::new_derived(&f, 0);
254
255 if f.lamports_per_signature == last_lamports_per_signature {
257 break;
258 }
259
260 assert!(count < 1000);
262 count += 1;
263 }
264
265 let mut count = 0;
267 while f.lamports_per_signature != f.target_lamports_per_signature {
268 f = FeeRateGovernor::new_derived(&f, f.target_signatures_per_slot);
269
270 assert!(count < 100);
272 count += 1;
273 }
274 }
275}