Skip to main content

solomka_program/
fee_calculator.rs

1//! Calculation of transaction fees.
2
3#![allow(clippy::integer_arithmetic)]
4use {
5    crate::{clock::DEFAULT_MS_PER_SLOT, ed25519_program, message::Message, secp256k1_program},
6    log::*,
7};
8
9#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, AbiExample)]
10#[serde(rename_all = "camelCase")]
11pub struct FeeCalculator {
12    /// The current cost of a signature.
13    ///
14    /// This amount may increase/decrease over time based on cluster processing
15    /// load.
16    pub lamports_per_signature: u64,
17}
18
19impl FeeCalculator {
20    pub fn new(lamports_per_signature: u64) -> Self {
21        Self {
22            lamports_per_signature,
23        }
24    }
25
26    #[deprecated(
27        since = "1.9.0",
28        note = "Please do not use, will no longer be available in the future"
29    )]
30    pub fn calculate_fee(&self, message: &Message) -> u64 {
31        let mut num_signatures: u64 = 0;
32        for instruction in &message.instructions {
33            let program_index = instruction.program_id_index as usize;
34            // Message may not be sanitized here
35            if program_index < message.account_keys.len() {
36                let id = message.account_keys[program_index];
37                if (secp256k1_program::check_id(&id) || ed25519_program::check_id(&id))
38                    && !instruction.data.is_empty()
39                {
40                    num_signatures += instruction.data[0] as u64;
41                }
42            }
43        }
44
45        self.lamports_per_signature
46            * (u64::from(message.header.num_required_signatures) + num_signatures)
47    }
48}
49
50#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, AbiExample)]
51#[serde(rename_all = "camelCase")]
52pub struct FeeRateGovernor {
53    // The current cost of a signature  This amount may increase/decrease over time based on
54    // cluster processing load.
55    #[serde(skip)]
56    pub lamports_per_signature: u64,
57
58    // The target cost of a signature when the cluster is operating around target_signatures_per_slot
59    // signatures
60    pub target_lamports_per_signature: u64,
61
62    // Used to estimate the desired processing capacity of the cluster.  As the signatures for
63    // recent slots are fewer/greater than this value, lamports_per_signature will decrease/increase
64    // for the next slot.  A value of 0 disables lamports_per_signature fee adjustments
65    pub target_signatures_per_slot: u64,
66
67    pub min_lamports_per_signature: u64,
68    pub max_lamports_per_signature: u64,
69
70    // What portion of collected fees are to be destroyed, as a fraction of std::u8::MAX
71    pub burn_percent: u8,
72}
73
74pub const DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE: u64 = 10_000;
75pub const DEFAULT_TARGET_SIGNATURES_PER_SLOT: u64 = 50 * DEFAULT_MS_PER_SLOT;
76
77// Percentage of tx fees to burn
78pub const DEFAULT_BURN_PERCENT: u8 = 50;
79
80impl Default for FeeRateGovernor {
81    fn default() -> Self {
82        Self {
83            lamports_per_signature: 0,
84            target_lamports_per_signature: DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE,
85            target_signatures_per_slot: DEFAULT_TARGET_SIGNATURES_PER_SLOT,
86            min_lamports_per_signature: 0,
87            max_lamports_per_signature: 0,
88            burn_percent: DEFAULT_BURN_PERCENT,
89        }
90    }
91}
92
93impl FeeRateGovernor {
94    pub fn new(target_lamports_per_signature: u64, target_signatures_per_slot: u64) -> Self {
95        let base_fee_rate_governor = Self {
96            target_lamports_per_signature,
97            lamports_per_signature: target_lamports_per_signature,
98            target_signatures_per_slot,
99            ..FeeRateGovernor::default()
100        };
101
102        Self::new_derived(&base_fee_rate_governor, 0)
103    }
104
105    pub fn new_derived(
106        base_fee_rate_governor: &FeeRateGovernor,
107        latest_signatures_per_slot: u64,
108    ) -> Self {
109        let mut me = base_fee_rate_governor.clone();
110
111        if me.target_signatures_per_slot > 0 {
112            // lamports_per_signature can range from 50% to 1000% of
113            // target_lamports_per_signature
114            me.min_lamports_per_signature = std::cmp::max(1, me.target_lamports_per_signature / 2);
115            me.max_lamports_per_signature = me.target_lamports_per_signature * 10;
116
117            // What the cluster should charge at `latest_signatures_per_slot`
118            let desired_lamports_per_signature =
119                me.max_lamports_per_signature
120                    .min(me.min_lamports_per_signature.max(
121                        me.target_lamports_per_signature
122                            * std::cmp::min(latest_signatures_per_slot, std::u32::MAX as u64)
123                                as u64
124                            / me.target_signatures_per_slot as u64,
125                    ));
126
127            trace!(
128                "desired_lamports_per_signature: {}",
129                desired_lamports_per_signature
130            );
131
132            let gap = desired_lamports_per_signature as i64
133                - base_fee_rate_governor.lamports_per_signature as i64;
134
135            if gap == 0 {
136                me.lamports_per_signature = desired_lamports_per_signature;
137            } else {
138                // Adjust fee by 5% of target_lamports_per_signature to produce a smooth
139                // increase/decrease in fees over time.
140                let gap_adjust =
141                    std::cmp::max(1, me.target_lamports_per_signature / 20) as i64 * gap.signum();
142
143                trace!(
144                    "lamports_per_signature gap is {}, adjusting by {}",
145                    gap,
146                    gap_adjust
147                );
148
149                me.lamports_per_signature =
150                    me.max_lamports_per_signature
151                        .min(me.min_lamports_per_signature.max(
152                            (base_fee_rate_governor.lamports_per_signature as i64 + gap_adjust)
153                                as u64,
154                        ));
155            }
156        } else {
157            me.lamports_per_signature = base_fee_rate_governor.target_lamports_per_signature;
158            me.min_lamports_per_signature = me.target_lamports_per_signature;
159            me.max_lamports_per_signature = me.target_lamports_per_signature;
160        }
161        debug!(
162            "new_derived(): lamports_per_signature: {}",
163            me.lamports_per_signature
164        );
165        me
166    }
167
168    pub fn clone_with_lamports_per_signature(&self, lamports_per_signature: u64) -> Self {
169        Self {
170            lamports_per_signature,
171            ..*self
172        }
173    }
174
175    /// calculate unburned fee from a fee total, returns (unburned, burned)
176    pub fn burn(&self, fees: u64) -> (u64, u64) {
177        let burned = fees * u64::from(self.burn_percent) / 100;
178        (fees - burned, burned)
179    }
180
181    /// create a FeeCalculator based on current cluster signature throughput
182    pub fn create_fee_calculator(&self) -> FeeCalculator {
183        FeeCalculator::new(self.lamports_per_signature)
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use {
190        super::*,
191        crate::{pubkey::Pubkey, system_instruction},
192    };
193
194    #[test]
195    fn test_fee_rate_governor_burn() {
196        let mut fee_rate_governor = FeeRateGovernor::default();
197        assert_eq!(fee_rate_governor.burn(2), (1, 1));
198
199        fee_rate_governor.burn_percent = 0;
200        assert_eq!(fee_rate_governor.burn(2), (2, 0));
201
202        fee_rate_governor.burn_percent = 100;
203        assert_eq!(fee_rate_governor.burn(2), (0, 2));
204    }
205
206    #[test]
207    #[allow(deprecated)]
208    fn test_fee_calculator_calculate_fee() {
209        // Default: no fee.
210        let message = Message::default();
211        assert_eq!(FeeCalculator::default().calculate_fee(&message), 0);
212
213        // No signature, no fee.
214        assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 0);
215
216        // One signature, a fee.
217        let pubkey0 = Pubkey::from([0; 32]);
218        let pubkey1 = Pubkey::from([1; 32]);
219        let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
220        let message = Message::new(&[ix0], Some(&pubkey0));
221        assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 2);
222
223        // Two signatures, double the fee.
224        let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
225        let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1);
226        let message = Message::new(&[ix0, ix1], Some(&pubkey0));
227        assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 4);
228    }
229
230    #[test]
231    #[allow(deprecated)]
232    fn test_fee_calculator_calculate_fee_secp256k1() {
233        use crate::instruction::Instruction;
234        let pubkey0 = Pubkey::from([0; 32]);
235        let pubkey1 = Pubkey::from([1; 32]);
236        let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
237        let mut secp_instruction = Instruction {
238            program_id: crate::secp256k1_program::id(),
239            accounts: vec![],
240            data: vec![],
241        };
242        let mut secp_instruction2 = Instruction {
243            program_id: crate::secp256k1_program::id(),
244            accounts: vec![],
245            data: vec![1],
246        };
247
248        let message = Message::new(
249            &[
250                ix0.clone(),
251                secp_instruction.clone(),
252                secp_instruction2.clone(),
253            ],
254            Some(&pubkey0),
255        );
256        assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 2);
257
258        secp_instruction.data = vec![0];
259        secp_instruction2.data = vec![10];
260        let message = Message::new(&[ix0, secp_instruction, secp_instruction2], Some(&pubkey0));
261        assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 11);
262    }
263
264    #[test]
265    fn test_fee_rate_governor_derived_default() {
266        solana_logger::setup();
267
268        let f0 = FeeRateGovernor::default();
269        assert_eq!(
270            f0.target_signatures_per_slot,
271            DEFAULT_TARGET_SIGNATURES_PER_SLOT
272        );
273        assert_eq!(
274            f0.target_lamports_per_signature,
275            DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE
276        );
277        assert_eq!(f0.lamports_per_signature, 0);
278
279        let f1 = FeeRateGovernor::new_derived(&f0, DEFAULT_TARGET_SIGNATURES_PER_SLOT);
280        assert_eq!(
281            f1.target_signatures_per_slot,
282            DEFAULT_TARGET_SIGNATURES_PER_SLOT
283        );
284        assert_eq!(
285            f1.target_lamports_per_signature,
286            DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE
287        );
288        assert_eq!(
289            f1.lamports_per_signature,
290            DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE / 2
291        ); // min
292    }
293
294    #[test]
295    fn test_fee_rate_governor_derived_adjust() {
296        solana_logger::setup();
297
298        let mut f = FeeRateGovernor {
299            target_lamports_per_signature: 100,
300            target_signatures_per_slot: 100,
301            ..FeeRateGovernor::default()
302        };
303        f = FeeRateGovernor::new_derived(&f, 0);
304
305        // Ramp fees up
306        let mut count = 0;
307        loop {
308            let last_lamports_per_signature = f.lamports_per_signature;
309
310            f = FeeRateGovernor::new_derived(&f, std::u64::MAX);
311            info!("[up] f.lamports_per_signature={}", f.lamports_per_signature);
312
313            // some maximum target reached
314            if f.lamports_per_signature == last_lamports_per_signature {
315                break;
316            }
317            // shouldn't take more than 1000 steps to get to minimum
318            assert!(count < 1000);
319            count += 1;
320        }
321
322        // Ramp fees down
323        let mut count = 0;
324        loop {
325            let last_lamports_per_signature = f.lamports_per_signature;
326            f = FeeRateGovernor::new_derived(&f, 0);
327
328            info!(
329                "[down] f.lamports_per_signature={}",
330                f.lamports_per_signature
331            );
332
333            // some minimum target reached
334            if f.lamports_per_signature == last_lamports_per_signature {
335                break;
336            }
337
338            // shouldn't take more than 1000 steps to get to minimum
339            assert!(count < 1000);
340            count += 1;
341        }
342
343        // Arrive at target rate
344        let mut count = 0;
345        while f.lamports_per_signature != f.target_lamports_per_signature {
346            f = FeeRateGovernor::new_derived(&f, f.target_signatures_per_slot);
347            info!(
348                "[target] f.lamports_per_signature={}",
349                f.lamports_per_signature
350            );
351            // shouldn't take more than 100 steps to get to target
352            assert!(count < 100);
353            count += 1;
354        }
355    }
356}