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