eip_utils/
lib.rs

1/// # Fee market change for ETH 1.0 chain
2/// A transaction pricing mechanism that includes fixed-per-block network fee that is burned and dynamically expands/contracts block sizes to deal with transient congestion.
3///
4/// [Fee market change for ETH 1.0 chain](https://eips.ethereum.org/EIPS/eip-1559)
5pub mod eip1559 {
6    use num_bigint::BigUint;
7    use num_traits::One;
8    use std::cmp::{self, Ordering};
9
10    const BASEFEE_MAX_CHANGE_DENOMINATOR: u64 = 8;
11
12    ///
13    /// Computes the base fee of a block given parent block header data.
14    /// # Arguments
15    /// * `parent_base_fee` - The value of the parent block base fee
16    /// * `parent_gas_used` - The value of the parent block gas used
17    /// * `parent_target_gas_used` - The value of the parent block target gas used
18    ///
19    /// # Examples
20    ///
21    /// ```
22    /// use num_bigint::BigUint;
23    /// use eip_utils::eip1559;
24    /// let parent_base_fee = BigUint::from(1000000000 as u64);
25    /// let parent_gas_used = BigUint::from(10000000 as u64);
26    /// let parent_target_gas_used = BigUint::from(5000000 as u64);
27    /// let base_fee = eip1559::compute_base_fee(&parent_base_fee, &parent_gas_used, &parent_target_gas_used);
28    /// assert_eq!(base_fee, BigUint::from(1125000000 as u64));
29    /// ```
30    pub fn compute_base_fee(
31        parent_base_fee: &BigUint,
32        parent_gas_used: &BigUint,
33        parent_target_gas_used: &BigUint,
34    ) -> BigUint {
35        match parent_gas_used.cmp(&parent_target_gas_used) {
36            Ordering::Equal => parent_base_fee.clone(),
37            Ordering::Greater => {
38                compute_base_fee_increase(parent_base_fee, parent_gas_used, parent_target_gas_used)
39            }
40            Ordering::Less => {
41                compute_base_fee_decrease(parent_base_fee, parent_gas_used, parent_target_gas_used)
42            }
43        }
44    }
45
46    fn compute_base_fee_increase(
47        parent_base_fee: &BigUint,
48        parent_gas_used: &BigUint,
49        parent_target_gas_used: &BigUint,
50    ) -> BigUint {
51        let gas_delta = parent_gas_used - parent_target_gas_used;
52        let fee_delta = cmp::max(
53            (parent_base_fee * gas_delta) / parent_target_gas_used / BASEFEE_MAX_CHANGE_DENOMINATOR,
54            One::one(),
55        );
56        parent_base_fee + fee_delta
57    }
58
59    fn compute_base_fee_decrease(
60        parent_base_fee: &BigUint,
61        parent_gas_used: &BigUint,
62        parent_target_gas_used: &BigUint,
63    ) -> BigUint {
64        let gas_delta = parent_target_gas_used - parent_gas_used;
65        let fee_delta =
66            (parent_base_fee * gas_delta) / parent_target_gas_used / BASEFEE_MAX_CHANGE_DENOMINATOR;
67        parent_base_fee - fee_delta
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use num_bigint::BigUint;
75    use serde::{Deserialize, Serialize};
76    use std::{cmp::Ordering, fs, path::PathBuf};
77
78    #[test]
79    fn assert_basefee_computation_works() {
80        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
81        d.push("resources/test/eip1559/basefee/reference-test.json");
82        let path = d.as_path().to_str().unwrap();
83        let data = fs::read_to_string(path);
84        let data = match data {
85            Ok(c) => c,
86            Err(error) => panic!("Problem opening the file: {:?}", error),
87        };
88
89        let test_cases = json_to_testcase(&data);
90        for case in &test_cases {
91            let parent_base_fee = BigUint::from(case.parent_base_fee);
92            let parent_gas_used = BigUint::from(case.parent_gas_used);
93            let parent_target_gas_used = BigUint::from(case.parent_target_gas_used);
94            let base_fee = eip1559::compute_base_fee(
95                &parent_base_fee,
96                &parent_gas_used,
97                &parent_target_gas_used,
98            );
99            assert_eq!(
100                true,
101                base_fee.cmp(&BigUint::from(case.expected_base_fee)) == Ordering::Equal
102            );
103        }
104    }
105
106    pub fn json_to_testcase(data: &str) -> Vec<TestCase> {
107        let t = serde_json::from_str(data);
108        match t {
109            Ok(c) => c,
110            Err(error) => panic!("Problem opening the file: {:?}", error),
111        }
112    }
113
114    #[derive(Debug, Serialize, Deserialize)]
115    pub struct TestCase {
116        #[serde(rename = "parentBaseFee")]
117        pub parent_base_fee: u64,
118        #[serde(rename = "parentGasUsed")]
119        pub parent_gas_used: u64,
120        #[serde(rename = "parentTargetGasUsed")]
121        pub parent_target_gas_used: u64,
122        #[serde(rename = "expectedBaseFee")]
123        pub expected_base_fee: u64,
124    }
125}