use crate::Head; use alloy_primitives::{BlockNumber, U256}; /// The condition at which a fork is activated. #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum ForkCondition { /// The fork is activated after a certain block. Block(BlockNumber), /// The fork is activated after a total difficulty has been reached. TTD { /// The block number at which TTD is reached, if it is known. /// /// This should **NOT** be set unless you want this block advertised as [EIP-2124][eip2124] /// `FORK_NEXT`. This is currently only the case for Sepolia and Holesky. /// /// [eip2124]: https://eips.ethereum.org/EIPS/eip-2124 fork_block: Option, /// The total difficulty after which the fork is activated. total_difficulty: U256, }, /// The fork is activated after a specific timestamp. Timestamp(u64), /// The fork is never activated #[default] Never, } impl ForkCondition { /// Returns true if the fork condition is timestamp based. pub const fn is_timestamp(&self) -> bool { matches!(self, Self::Timestamp(_)) } /// Checks whether the fork condition is satisfied at the given block. /// /// For TTD conditions, this will only return true if the activation block is already known. /// /// For timestamp conditions, this will always return false. pub const fn active_at_block(&self, current_block: BlockNumber) -> bool { matches!(self, Self::Block(block) | Self::TTD { fork_block: Some(block), .. } if current_block >= *block) } /// Checks if the given block is the first block that satisfies the fork condition. /// /// This will return false for any condition that is not block based. pub const fn transitions_at_block(&self, current_block: BlockNumber) -> bool { matches!(self, Self::Block(block) if current_block == *block) } /// Checks whether the fork condition is satisfied at the given total difficulty and difficulty /// of a current block. /// /// The fork is considered active if the _previous_ total difficulty is above the threshold. /// To achieve that, we subtract the passed `difficulty` from the current block's total /// difficulty, and check if it's above the Fork Condition's total difficulty (here: /// `58_750_000_000_000_000_000_000`) /// /// This will return false for any condition that is not TTD-based. pub fn active_at_ttd(&self, ttd: U256, difficulty: U256) -> bool { matches!(self, Self::TTD { total_difficulty, .. } if ttd.saturating_sub(difficulty) >= *total_difficulty) } /// Checks whether the fork condition is satisfied at the given timestamp. /// /// This will return false for any condition that is not timestamp-based. pub const fn active_at_timestamp(&self, timestamp: u64) -> bool { matches!(self, Self::Timestamp(time) if timestamp >= *time) } /// Checks if the given block is the first block that satisfies the fork condition. /// /// This will return false for any condition that is not timestamp based. pub const fn transitions_at_timestamp(&self, timestamp: u64, parent_timestamp: u64) -> bool { matches!(self, Self::Timestamp(time) if timestamp >= *time && parent_timestamp < *time) } /// Checks whether the fork condition is satisfied at the given head block. /// /// This will return true if: /// /// - The condition is satisfied by the block number; /// - The condition is satisfied by the timestamp; /// - or the condition is satisfied by the total difficulty pub fn active_at_head(&self, head: &Head) -> bool { self.active_at_block(head.number) || self.active_at_timestamp(head.timestamp) || self.active_at_ttd(head.total_difficulty, head.difficulty) } /// Get the total terminal difficulty for this fork condition. /// /// Returns `None` for fork conditions that are not TTD based. pub const fn ttd(&self) -> Option { match self { Self::TTD { total_difficulty, .. } => Some(*total_difficulty), _ => None, } } /// Returns the timestamp of the fork condition, if it is timestamp based. pub const fn as_timestamp(&self) -> Option { match self { Self::Timestamp(timestamp) => Some(*timestamp), _ => None, } } }