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