alloy_rpc_types_eth/
fee.rs

1use alloc::vec::Vec;
2
3/// Internal struct to calculate reward percentiles
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5#[doc(alias = "TransactionGasAndReward")]
6pub struct TxGasAndReward {
7    /// Gas used by the transaction
8    pub gas_used: u64,
9    /// The effective gas tip by the transaction
10    pub reward: u128,
11}
12
13impl PartialOrd for TxGasAndReward {
14    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
15        Some(self.cmp(other))
16    }
17}
18
19impl Ord for TxGasAndReward {
20    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
21        // compare only the reward
22        // see:
23        // <https://github.com/ethereum/go-ethereum/blob/ee8e83fa5f6cb261dad2ed0a7bbcde4930c41e6c/eth/gasprice/feehistory.go#L85>
24        self.reward.cmp(&other.reward)
25    }
26}
27
28/// Response type for `eth_feeHistory`
29#[derive(Clone, Debug, Default, PartialEq)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
32pub struct FeeHistory {
33    /// An array of block base fees per gas.
34    /// This includes the next block after the newest of the returned range,
35    /// because this value can be derived from the newest block. Zeroes are
36    /// returned for pre-EIP-1559 blocks.
37    ///
38    /// # Note
39    ///
40    /// Empty list is skipped only for compatibility with Erigon and Geth.
41    #[cfg_attr(
42        feature = "serde",
43        serde(default, skip_serializing_if = "Vec::is_empty", with = "alloy_serde::quantity::vec")
44    )]
45    pub base_fee_per_gas: Vec<u128>,
46    /// An array of block gas used ratios. These are calculated as the ratio
47    /// of `gasUsed` and `gasLimit`.
48    #[cfg_attr(feature = "serde", serde(deserialize_with = "alloy_serde::null_as_default"))]
49    pub gas_used_ratio: Vec<f64>,
50    /// An array of block base fees per blob gas. This includes the next block after the newest
51    /// of the returned range, because this value can be derived from the newest block. Zeroes
52    /// are returned for pre-EIP-4844 blocks.
53    #[cfg_attr(
54        feature = "serde",
55        serde(default, skip_serializing_if = "Vec::is_empty", with = "alloy_serde::quantity::vec")
56    )]
57    pub base_fee_per_blob_gas: Vec<u128>,
58    /// An array of block blob gas used ratios. These are calculated as the ratio of gasUsed and
59    /// gasLimit.
60    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Vec::is_empty"))]
61    pub blob_gas_used_ratio: Vec<f64>,
62    /// Lowest number block of the returned range.
63    #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity"))]
64    pub oldest_block: u64,
65    /// An (optional) array of effective priority fee per gas data points from a single
66    /// block. All zeroes are returned if the block is empty.
67    #[cfg_attr(
68        feature = "serde",
69        serde(
70            default,
71            skip_serializing_if = "Option::is_none",
72            with = "alloy_serde::quantity::u128_vec_vec_opt"
73        )
74    )]
75    pub reward: Option<Vec<Vec<u128>>>,
76}
77
78impl FeeHistory {
79    /// Returns the base fee of the latest block in the `eth_feeHistory` request.
80    pub fn latest_block_base_fee(&self) -> Option<u128> {
81        // The base fee of requested block is the second last element in the list.
82        self.base_fee_per_gas.iter().rev().nth(1).copied()
83    }
84
85    /// Returns the base fee of the next block.
86    pub fn next_block_base_fee(&self) -> Option<u128> {
87        self.base_fee_per_gas.last().copied()
88    }
89
90    /// Returns the blob base fee of the next block.
91    ///
92    /// If the next block is pre-EIP-4844, this will return `None`.
93    pub fn next_block_blob_base_fee(&self) -> Option<u128> {
94        self.base_fee_per_blob_gas
95            .last()
96            .copied()
97            // Skip zero values that are returned for pre-EIP-4844 blocks.
98            .filter(|fee| *fee != 0)
99    }
100
101    /// Returns the blob fee of the latest block in the `eth_feeHistory` request.
102    ///
103    /// If the next block is pre-EIP-4844, this will return `None`.
104    pub fn latest_block_blob_base_fee(&self) -> Option<u128> {
105        // The blob fee requested block is the second last element in the list.
106        self.base_fee_per_blob_gas
107            .iter()
108            .rev()
109            .nth(1)
110            .copied()
111            // Skip zero values that are returned for pre-EIP-4844 blocks.
112            .filter(|fee| *fee != 0)
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use crate::FeeHistory;
119    use similar_asserts::assert_eq;
120
121    #[test]
122    #[cfg(feature = "serde")]
123    fn test_fee_history_serde() {
124        let sample = r#"{"baseFeePerGas":["0x342770c0","0x2da282a8"],"gasUsedRatio":[0.0],"baseFeePerBlobGas":["0x0","0x0"],"blobGasUsedRatio":[0.0],"oldestBlock":"0x1"}"#;
125        let fee_history: FeeHistory = serde_json::from_str(sample).unwrap();
126        let expected = FeeHistory {
127            base_fee_per_blob_gas: vec![0, 0],
128            base_fee_per_gas: vec![875000000, 765625000],
129            blob_gas_used_ratio: vec![0.0],
130            gas_used_ratio: vec![0.0],
131            oldest_block: 1,
132            reward: None,
133        };
134
135        assert_eq!(fee_history, expected);
136        assert_eq!(serde_json::to_string(&fee_history).unwrap(), sample);
137    }
138
139    #[test]
140    #[cfg(feature = "serde")]
141    fn test_fee_history_serde_2() {
142        let json = r#"{"baseFeePerBlobGas":["0xc0","0xb2","0xab","0x98","0x9e","0x92","0xa4","0xb9","0xd0","0xea","0xfd"],"baseFeePerGas":["0x4cb8cf181","0x53075988e","0x4fb92ee18","0x45c209055","0x4e790dca2","0x58462e84e","0x5b7659f4e","0x5d66ea3aa","0x6283c6e45","0x5ecf0e1e5","0x5da59cf89"],"blobGasUsedRatio":[0.16666666666666666,0.3333333333333333,0,0.6666666666666666,0.16666666666666666,1,1,1,1,0.8333333333333334],"gasUsedRatio":[0.8288135,0.3407616666666667,0,0.9997232,0.999601,0.6444664333333333,0.5848306333333333,0.7189564,0.34952733333333336,0.4509799666666667],"oldestBlock":"0x59f94f","reward":[["0x59682f00"],["0x59682f00"],["0x0"],["0x59682f00"],["0x59682f00"],["0x3b9aca00"],["0x59682f00"],["0x59682f00"],["0x3b9aca00"],["0x59682f00"]]}"#;
143        let _actual = serde_json::from_str::<FeeHistory>(json).unwrap();
144    }
145
146    #[test]
147    #[cfg(feature = "serde")]
148    fn test_fee_history_serde_3() {
149        let json = r#"{"oldestBlock":"0xdee807","baseFeePerGas":["0x4ccf46253","0x4457de658","0x4531c5aee","0x3cfa33972","0x3d33403eb","0x399457884","0x40bdf9772","0x48d55e7c4","0x51e9ebf14","0x55f460bf9","0x4e31607e4"],"gasUsedRatio":[0.05909575012589385,0.5498182666666667,0.0249864,0.5146185,0.2633512,0.997582061117319,0.999914966153302,0.9986873805040722,0.6973219148223686,0.13879896448917434],"baseFeePerBlobGas":["0x0","0x0","0x0","0x0","0x0","0x0","0x0","0x0","0x0","0x0","0x0"],"blobGasUsedRatio":[0,0,0,0,0,0,0,0,0,0]}"#;
150        let _actual = serde_json::from_str::<FeeHistory>(json).unwrap();
151    }
152
153    #[test]
154    #[cfg(feature = "serde")]
155    fn test_fee_hist_null_gas_used_ratio() {
156        let json = r#"{"oldestBlock": "0x0", "gasUsedRatio": null}"#;
157        let _actual = serde_json::from_str::<FeeHistory>(json).unwrap();
158    }
159}