diff --git a/ethers-core/src/types/block.rs b/ethers-core/src/types/block.rs index 61c266fc..a0ef2b5a 100644 --- a/ethers-core/src/types/block.rs +++ b/ethers-core/src/types/block.rs @@ -1,5 +1,7 @@ // Taken from https://github.com/tomusdrw/rust-web3/blob/master/src/types/block.rs use crate::types::{Address, Bloom, Bytes, H256, U256, U64}; +#[cfg(not(feature = "celo"))] +use core::cmp::Ordering; use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; use std::str::FromStr; @@ -89,6 +91,52 @@ pub struct Block { pub epoch_snark_data: Option, } +// ref https://eips.ethereum.org/EIPS/eip-1559 +#[cfg(not(feature = "celo"))] +pub const ELASTICITY_MULTIPLIER: U256 = U256([2u64, 0, 0, 0]); +// max base fee delta is 12.5% +#[cfg(not(feature = "celo"))] +pub const BASE_FEE_MAX_CHANGE_DENOMINATOR: U256 = U256([8u64, 0, 0, 0]); + +impl Block { + /// The target gas usage as per EIP-1559 + #[cfg(not(feature = "celo"))] + pub fn gas_target(&self) -> U256 { + self.gas_limit / ELASTICITY_MULTIPLIER + } + + /// The next block's base fee, it is a function of parent block's base fee and gas usage. + /// Reference: https://eips.ethereum.org/EIPS/eip-1559 + #[cfg(not(feature = "celo"))] + pub fn next_block_base_fee(&self) -> Option { + let target_usage = self.gas_target(); + let base_fee_per_gas = self.base_fee_per_gas?; + + match self.gas_used.cmp(&target_usage) { + Ordering::Greater => { + let gas_used_delta = self.gas_used - self.gas_target(); + let base_fee_per_gas_delta = U256::max( + base_fee_per_gas * gas_used_delta / + target_usage / + BASE_FEE_MAX_CHANGE_DENOMINATOR, + U256::from(1u32), + ); + let expected_base_fee_per_gas = base_fee_per_gas + base_fee_per_gas_delta; + Some(expected_base_fee_per_gas) + } + Ordering::Less => { + let gas_used_delta = self.gas_target() - self.gas_used; + let base_fee_per_gas_delta = base_fee_per_gas * gas_used_delta / + target_usage / + BASE_FEE_MAX_CHANGE_DENOMINATOR; + let expected_base_fee_per_gas = base_fee_per_gas - base_fee_per_gas_delta; + Some(expected_base_fee_per_gas) + } + Ordering::Equal => self.base_fee_per_gas, + } + } +} + #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[cfg(feature = "celo")] /// Commit-reveal data for generating randomness in the @@ -279,6 +327,33 @@ mod tests { let block: Block<()> = serde_json::from_value(json).unwrap(); assert_eq!(block.base_fee_per_gas, Some(U256::from(7))); } + + #[test] + fn test_next_block_base_fee() { + // https://etherscan.io/block/14402566 + let mut block_14402566 = Block::::default(); + block_14402566.number = Some(U64::from(14402566u64)); + block_14402566.base_fee_per_gas = Some(U256::from(36_803_013_756u128)); + block_14402566.gas_limit = U256::from(30_087_887u128); + block_14402566.gas_used = U256::from(2_023_848u128); + + assert_eq!(block_14402566.base_fee_per_gas, Some(U256::from(36_803_013_756u128))); + assert_eq!(block_14402566.gas_target(), U256::from(15_043_943u128)); + // next block decreasing base fee https://etherscan.io/block/14402567 + assert_eq!(block_14402566.next_block_base_fee(), Some(U256::from(32_821_521_542u128))); + + // https://etherscan.io/block/14402712 + let mut block_14402712 = Block::::default(); + block_14402712.number = Some(U64::from(14402712u64)); + block_14402712.base_fee_per_gas = Some(U256::from(24_870_031_149u128)); + block_14402712.gas_limit = U256::from(30_000_000u128); + block_14402712.gas_used = U256::from(29_999_374u128); + + assert_eq!(block_14402712.base_fee_per_gas, Some(U256::from(24_870_031_149u128))); + assert_eq!(block_14402712.gas_target(), U256::from(15_000_000u128)); + // next block increasing base fee https://etherscan.io/block/14402713 + assert_eq!(block_14402712.next_block_base_fee(), Some(U256::from(27_978_655_303u128))); + } } #[cfg(test)]