alloy_hardforks/
forkcondition.rs

1use alloy_primitives::{BlockNumber, U256};
2
3/// The condition at which a fork is activated.
4#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub enum ForkCondition {
7    /// The fork is activated after a certain block.
8    Block(BlockNumber),
9    /// The fork is activated after a total difficulty has been reached.
10    TTD {
11        /// The activation block number for the merge.
12        ///
13        /// This should represent the first post-merge block for the given network. Sepolia and
14        /// mainnet are the only networks that have merged, and they have both finalized
15        /// post-merge, so total difficulty is effectively deprecated.
16        activation_block_number: BlockNumber,
17        /// The block number at which TTD is reached, if it is known.
18        ///
19        /// This should **NOT** be set unless you want this block advertised as [EIP-2124][eip2124]
20        /// `FORK_NEXT`. This is currently only the case for Sepolia and Holesky.
21        ///
22        /// [eip2124]: https://eips.ethereum.org/EIPS/eip-2124
23        fork_block: Option<BlockNumber>,
24        /// The total difficulty after which the fork is activated.
25        total_difficulty: U256,
26    },
27    /// The fork is activated after a specific timestamp.
28    Timestamp(u64),
29    /// The fork is never activated
30    #[default]
31    Never,
32}
33
34impl ForkCondition {
35    /// Block number 0, equivalent to activation at genesis.
36    pub const ZERO_BLOCK: Self = Self::Block(0);
37
38    /// Timestamp 0, equivalent to activation at genesis.
39    pub const ZERO_TIMESTAMP: Self = Self::Timestamp(0);
40
41    /// Returns true if the fork condition is timestamp based.
42    pub const fn is_timestamp(&self) -> bool {
43        matches!(self, Self::Timestamp(_))
44    }
45
46    /// Returns true if the fork condition is TTD based.
47    pub const fn is_ttd(&self) -> bool {
48        matches!(self, Self::TTD { .. })
49    }
50
51    /// Returns true if the fork condition is block based.
52    pub const fn is_block(&self) -> bool {
53        matches!(self, Self::Block(_))
54    }
55
56    /// Checks whether the fork condition is satisfied at the given block.
57    ///
58    /// This will return true if the block number is equal or greater than the activation block of:
59    /// - [`ForkCondition::Block`]
60    /// - [`ForkCondition::TTD`]
61    ///
62    /// For timestamp conditions, this will always return false.
63    pub const fn active_at_block(&self, current_block: BlockNumber) -> bool {
64        matches!(self, Self::Block(block)
65        | Self::TTD { activation_block_number: block, .. } if current_block >= *block)
66    }
67
68    /// Checks if the given block is the first block that satisfies the fork condition.
69    ///
70    /// This will return false for any condition that is not block based.
71    pub const fn transitions_at_block(&self, current_block: BlockNumber) -> bool {
72        matches!(self, Self::Block(block) if current_block == *block)
73    }
74
75    /// Checks whether the fork condition is satisfied at the given total difficulty and difficulty
76    /// of a current block.
77    ///
78    /// The fork is considered active if the _previous_ total difficulty is above the threshold.
79    /// To achieve that, we subtract the passed `difficulty` from the current block's total
80    /// difficulty, and check if it's above the Fork Condition's total difficulty (here:
81    /// `58_750_000_000_000_000_000_000`)
82    ///
83    /// This will return false for any condition that is not TTD-based.
84    pub fn active_at_ttd(&self, ttd: U256, difficulty: U256) -> bool {
85        matches!(self, Self::TTD { total_difficulty, .. }
86            if ttd.saturating_sub(difficulty) >= *total_difficulty)
87    }
88
89    /// Checks whether the fork condition is satisfied at the given timestamp.
90    ///
91    /// This will return false for any condition that is not timestamp-based.
92    pub const fn active_at_timestamp(&self, timestamp: u64) -> bool {
93        matches!(self, Self::Timestamp(time) if timestamp >= *time)
94    }
95
96    /// Checks if the given block is the first block that satisfies the fork condition.
97    ///
98    /// This will return false for any condition that is not timestamp based.
99    pub const fn transitions_at_timestamp(&self, timestamp: u64, parent_timestamp: u64) -> bool {
100        matches!(self, Self::Timestamp(time) if timestamp >= *time && parent_timestamp < *time)
101    }
102
103    /// Checks whether the fork condition is satisfied at the given timestamp or number.
104    pub const fn active_at_timestamp_or_number(&self, timestamp: u64, block_number: u64) -> bool {
105        self.active_at_timestamp(timestamp) || self.active_at_block(block_number)
106    }
107
108    /// Get the total terminal difficulty for this fork condition.
109    ///
110    /// Returns `None` for fork conditions that are not TTD based.
111    pub const fn ttd(&self) -> Option<U256> {
112        match self {
113            Self::TTD { total_difficulty, .. } => Some(*total_difficulty),
114            _ => None,
115        }
116    }
117
118    /// Returns the block of the fork condition, if it is block number based, or if it's difficulty
119    /// based and the fork block is known.
120    pub const fn block_number(&self) -> Option<u64> {
121        match self {
122            Self::Block(number) => Some(*number),
123            Self::TTD { activation_block_number, .. } => Some(*activation_block_number),
124            _ => None,
125        }
126    }
127
128    /// Returns the timestamp of the fork condition, if it is timestamp based.
129    pub const fn as_timestamp(&self) -> Option<u64> {
130        match self {
131            Self::Timestamp(timestamp) => Some(*timestamp),
132            _ => None,
133        }
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use alloy_primitives::U256;
141
142    #[test]
143    fn test_active_at_block() {
144        // Test if the condition is active at the current block number
145        let fork_condition = ForkCondition::Block(10);
146        assert!(fork_condition.active_at_block(10), "The condition should be active at block 10");
147
148        // Test if the condition is not active at a lower block number
149        assert!(
150            !fork_condition.active_at_block(9),
151            "The condition should not be active at block 9"
152        );
153
154        // Test if TTD-based condition with known block activates
155        let fork_condition = ForkCondition::TTD {
156            activation_block_number: 10,
157            fork_block: Some(10),
158            total_difficulty: U256::from(1000),
159        };
160        assert!(
161            fork_condition.active_at_block(10),
162            "The TTD condition should be active at block 10"
163        );
164    }
165
166    #[test]
167    fn test_transitions_at_block() {
168        // Test if the condition transitions at the correct block number
169        let fork_condition = ForkCondition::Block(10);
170        assert!(
171            fork_condition.transitions_at_block(10),
172            "The condition should transition at block 10"
173        );
174
175        // Test if the condition does not transition at a different block number
176        assert!(
177            !fork_condition.transitions_at_block(9),
178            "The condition should not transition at a different block number"
179        );
180        assert!(
181            !fork_condition.transitions_at_block(11),
182            "The condition should not transition at a different block number"
183        );
184    }
185
186    #[test]
187    fn test_active_at_ttd() {
188        // Test if the condition activates at the correct total difficulty
189        let fork_condition = ForkCondition::TTD {
190            activation_block_number: 10,
191            fork_block: Some(10),
192            total_difficulty: U256::from(1000),
193        };
194        assert!(
195            fork_condition.active_at_ttd(U256::from(1000000), U256::from(100)),
196            "The TTD condition should be active when the total difficulty matches"
197        );
198
199        // Test if the condition does not activate when the total difficulty is lower
200        assert!(
201            !fork_condition.active_at_ttd(U256::from(900), U256::from(100)),
202            "The TTD condition should not be active when the total difficulty is lower"
203        );
204
205        // Test with a saturated subtraction
206        assert!(
207            !fork_condition.active_at_ttd(U256::from(900), U256::from(1000)),
208            "The TTD condition should not be active when the subtraction saturates"
209        );
210    }
211
212    #[test]
213    fn test_active_at_timestamp() {
214        // Test if the condition activates at the correct timestamp
215        let fork_condition = ForkCondition::Timestamp(12345);
216        assert!(
217            fork_condition.active_at_timestamp(12345),
218            "The condition should be active at timestamp 12345"
219        );
220
221        // Test if the condition does not activate at an earlier timestamp
222        assert!(
223            !fork_condition.active_at_timestamp(12344),
224            "The condition should not be active at an earlier timestamp"
225        );
226    }
227
228    #[test]
229    fn test_transitions_at_timestamp() {
230        // Test if the condition transitions at the correct timestamp
231        let fork_condition = ForkCondition::Timestamp(12345);
232        assert!(
233            fork_condition.transitions_at_timestamp(12345, 12344),
234            "The condition should transition at timestamp 12345"
235        );
236
237        // Test if the condition does not transition if the parent timestamp is already the same
238        assert!(
239            !fork_condition.transitions_at_timestamp(12345, 12345),
240            "The condition should not transition if the parent timestamp is already 12345"
241        );
242        // Test with earlier timestamp
243        assert!(
244            !fork_condition.transitions_at_timestamp(123, 122),
245            "The condition should not transition if the parent timestamp is earlier"
246        );
247    }
248}