solana_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
74
75pub const DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE: u64 = 25_000;
76pub const DEFAULT_TARGET_SIGNATURES_PER_SLOT: u64 = 25 * DEFAULT_MS_PER_SLOT;
77
78// Percentage of tx fees to burn
79pub const DEFAULT_BURN_PERCENT: u8 = 50;
80
81impl Default for FeeRateGovernor {
82    fn default() -> Self {
83        Self {
84            lamports_per_signature: 0,
85            target_lamports_per_signature: DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE,
86            target_signatures_per_slot: DEFAULT_TARGET_SIGNATURES_PER_SLOT,
87            min_lamports_per_signature: 0,
88            max_lamports_per_signature: 0,
89            burn_percent: DEFAULT_BURN_PERCENT,
90        }
91    }
92}
93
94impl FeeRateGovernor {
95    pub fn new(target_lamports_per_signature: u64, target_signatures_per_slot: u64) -> Self {
96        let base_fee_rate_governor = Self {
97            target_lamports_per_signature,
98            lamports_per_signature: target_lamports_per_signature,
99            target_signatures_per_slot,
100            ..FeeRateGovernor::default()
101        };
102
103        Self::new_derived(&base_fee_rate_governor, 0)
104    }
105
106    pub fn new_derived(
107        base_fee_rate_governor: &FeeRateGovernor,
108        latest_signatures_per_slot: u64,
109    ) -> Self {
110        let mut me = base_fee_rate_governor.clone();
111
112        if me.target_signatures_per_slot > 0 {
113            // lamports_per_signature can range from 50% to 1000% of
114            // target_lamports_per_signature
115            me.min_lamports_per_signature = std::cmp::max(1, me.target_lamports_per_signature / 2);
116            me.max_lamports_per_signature = me.target_lamports_per_signature * 10;
117
118            // What the cluster should charge at `latest_signatures_per_slot`
119            let desired_lamports_per_signature =
120                me.max_lamports_per_signature
121                    .min(me.min_lamports_per_signature.max(
122                        me.target_lamports_per_signature
123                            * std::cmp::min(latest_signatures_per_slot, std::u32::MAX as u64)
124                                as u64
125                            / me.target_signatures_per_slot as u64,
126                    ));
127
128            trace!(
129                "desired_lamports_per_signature: {}",
130                desired_lamports_per_signature
131            );
132
133            let gap = desired_lamports_per_signature as i64
134                - base_fee_rate_governor.lamports_per_signature as i64;
135
136            if gap == 0 {
137                me.lamports_per_signature = desired_lamports_per_signature;
138            } else {
139                // Adjust fee by 5% of target_lamports_per_signature to produce a smooth
140                // increase/decrease in fees over time.
141                let gap_adjust =
142                    std::cmp::max(1, me.target_lamports_per_signature / 20) as i64 * gap.signum();
143
144                trace!(
145                    "lamports_per_signature gap is {}, adjusting by {}",
146                    gap,
147                    gap_adjust
148                );
149
150                me.lamports_per_signature =
151                    me.max_lamports_per_signature
152                        .min(me.min_lamports_per_signature.max(
153                            (base_fee_rate_governor.lamports_per_signature as i64 + gap_adjust)
154                                as u64,
155                        ));
156            }
157        } else {
158            me.lamports_per_signature = base_fee_rate_governor.target_lamports_per_signature;
159            me.min_lamports_per_signature = me.target_lamports_per_signature;
160            me.max_lamports_per_signature = me.target_lamports_per_signature;
161        }
162        debug!(
163            "new_derived(): lamports_per_signature: {}",
164            me.lamports_per_signature
165        );
166        me
167    }
168
169    pub fn clone_with_lamports_per_signature(&self, lamports_per_signature: u64) -> Self {
170        Self {
171            lamports_per_signature,
172            ..*self
173        }
174    }
175
176    /// calculate unburned fee from a fee total, returns (unburned, burned)
177    pub fn burn(&self, fees: u64) -> (u64, u64) {
178        let burned = fees * u64::from(self.burn_percent) / 100;
179        (fees - burned, burned)
180    }
181
182    /// create a FeeCalculator based on current cluster signature throughput
183    pub fn create_fee_calculator(&self) -> FeeCalculator {
184        FeeCalculator::new(self.lamports_per_signature)
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use {
191        super::*,
192        crate::{pubkey::Pubkey, system_instruction},
193    };
194
195    #[test]
196    fn test_fee_rate_governor_burn() {
197        let mut fee_rate_governor = FeeRateGovernor::default();
198        assert_eq!(fee_rate_governor.burn(2), (1, 1));
199
200        fee_rate_governor.burn_percent = 0;
201        assert_eq!(fee_rate_governor.burn(2), (2, 0));
202
203        fee_rate_governor.burn_percent = 100;
204        assert_eq!(fee_rate_governor.burn(2), (0, 2));
205    }
206
207    #[test]
208    #[allow(deprecated)]
209    fn test_fee_calculator_calculate_fee() {
210        // Default: no fee.
211        let message = Message::default();
212        assert_eq!(FeeCalculator::default().calculate_fee(&message), 0);
213
214        // No signature, no fee.
215        assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 0);
216
217        // One signature, a fee.
218        let pubkey0 = Pubkey::from([0; 32]);
219        let pubkey1 = Pubkey::from([1; 32]);
220        let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
221        let message = Message::new(&[ix0], Some(&pubkey0));
222        assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 2);
223
224        // Two signatures, double the fee.
225        let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
226        let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1);
227        let message = Message::new(&[ix0, ix1], Some(&pubkey0));
228        assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 4);
229    }
230
231    #[test]
232    #[allow(deprecated)]
233    fn test_fee_calculator_calculate_fee_secp256k1() {
234        use crate::instruction::Instruction;
235        let pubkey0 = Pubkey::from([0; 32]);
236        let pubkey1 = Pubkey::from([1; 32]);
237        let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
238        let mut secp_instruction = Instruction {
239            program_id: crate::secp256k1_program::id(),
240            accounts: vec![],
241            data: vec![],
242        };
243        let mut secp_instruction2 = Instruction {
244            program_id: crate::secp256k1_program::id(),
245            accounts: vec![],
246            data: vec![1],
247        };
248
249        let message = Message::new(
250            &[
251                ix0.clone(),
252                secp_instruction.clone(),
253                secp_instruction2.clone(),
254            ],
255            Some(&pubkey0),
256        );
257        assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 2);
258
259        secp_instruction.data = vec![0];
260        secp_instruction2.data = vec![10];
261        let message = Message::new(&[ix0, secp_instruction, secp_instruction2], Some(&pubkey0));
262        assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 11);
263    }
264
265    #[test]
266    fn test_fee_rate_governor_derived_default() {
267        solana_logger::setup();
268
269        let f0 = FeeRateGovernor::default();
270        assert_eq!(
271            f0.target_signatures_per_slot,
272            DEFAULT_TARGET_SIGNATURES_PER_SLOT
273        );
274        assert_eq!(
275            f0.target_lamports_per_signature,
276            DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE
277        );
278        assert_eq!(f0.lamports_per_signature, 0);
279
280        let f1 = FeeRateGovernor::new_derived(&f0, DEFAULT_TARGET_SIGNATURES_PER_SLOT);
281        assert_eq!(
282            f1.target_signatures_per_slot,
283            DEFAULT_TARGET_SIGNATURES_PER_SLOT
284        );
285        assert_eq!(
286            f1.target_lamports_per_signature,
287            DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE
288        );
289        assert_eq!(
290            f1.lamports_per_signature,
291            DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE / 2
292        ); // min
293    }
294
295    #[test]
296    fn test_fee_rate_governor_derived_adjust() {
297        solana_logger::setup();
298
299        let mut f = FeeRateGovernor {
300            target_lamports_per_signature: 100,
301            target_signatures_per_slot: 100,
302            ..FeeRateGovernor::default()
303        };
304        f = FeeRateGovernor::new_derived(&f, 0);
305
306        // Ramp fees up
307        let mut count = 0;
308        loop {
309            let last_lamports_per_signature = f.lamports_per_signature;
310
311            f = FeeRateGovernor::new_derived(&f, std::u64::MAX);
312            info!("[up] f.lamports_per_signature={}", f.lamports_per_signature);
313
314            // some maximum target reached
315            if f.lamports_per_signature == last_lamports_per_signature {
316                break;
317            }
318            // shouldn't take more than 1000 steps to get to minimum
319            assert!(count < 1000);
320            count += 1;
321        }
322
323        // Ramp fees down
324        let mut count = 0;
325        loop {
326            let last_lamports_per_signature = f.lamports_per_signature;
327            f = FeeRateGovernor::new_derived(&f, 0);
328
329            info!(
330                "[down] f.lamports_per_signature={}",
331                f.lamports_per_signature
332            );
333
334            // some minimum target reached
335            if f.lamports_per_signature == last_lamports_per_signature {
336                break;
337            }
338
339            // shouldn't take more than 1000 steps to get to minimum
340            assert!(count < 1000);
341            count += 1;
342        }
343
344        // Arrive at target rate
345        let mut count = 0;
346        while f.lamports_per_signature != f.target_lamports_per_signature {
347            f = FeeRateGovernor::new_derived(&f, f.target_signatures_per_slot);
348            info!(
349                "[target] f.lamports_per_signature={}",
350                f.lamports_per_signature
351            );
352            // shouldn't take more than 100 steps to get to target
353            assert!(count < 100);
354            count += 1;
355        }
356    }
357}