diff --git a/Cargo.lock b/Cargo.lock index d3a8d414..74879d42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,9 +229,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f7ef09f21bd1e9cb8a686f168cb4a206646804567f0889eadb8dcc4c9288c8" +checksum = "e6ef28c9fdad22d4eec52d894f5f2673a0895f1e5ef196734568e68c0f6caca8" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -251,7 +251,6 @@ dependencies = [ "serde", "serde_with", "sha2", - "thiserror 2.0.18", ] [[package]] @@ -680,9 +679,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.7.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ce1e0dbf7720eee747700e300c99aac01b1a95bb93f493a01e78ee28bb1a37" +checksum = "11ece63b89294b8614ab3f483560c08d016930f842bf36da56bf0b764a15c11e" dependencies = [ "alloy-primitives", "arbitrary", @@ -2648,7 +2647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.114", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 70da7d9b..b6de030a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] alloy-consensus = "1.6.3" -alloy-eips = "1.6.3" +alloy-eips = "1.8.3" alloy-evm = "0.27.2" alloy-genesis = "1.6.3" alloy-network = "1.6.3" diff --git a/src/chainspec/mod.rs b/src/chainspec/mod.rs index 3bcff1b2..075ea9a1 100644 --- a/src/chainspec/mod.rs +++ b/src/chainspec/mod.rs @@ -65,6 +65,15 @@ impl BerachainChainSpec { self.pol_contract_address } + /// PoL distributor address when Prague1 is active at the given timestamp. + pub fn active_pol_distributor_at_timestamp(&self, timestamp: u64) -> Option
{ + if !self.is_prague1_active_at_timestamp(timestamp) { + return None; + } + let address = self.pol_contract(); + (!address.is_zero()).then_some(address) + } + /// Get blocked addresses for Prague3 if the hardfork is active pub fn prague3_blocked_addresses_at_timestamp(&self, timestamp: u64) -> Option<&[Address]> { if self.is_prague3_active_at_timestamp(timestamp) { @@ -817,6 +826,18 @@ mod tests { assert_eq!(bepolia.deposit_contract().map(|c| c.address), Some(expected)); } + #[test] + fn test_builtin_genesis_pol_distributor() { + let expected = address!("D2f19a79b026Fb636A7c300bF5947df113940761"); + let mainnet = BerachainChainSpecParser::parse(BERACHAIN_MAINNET).unwrap(); + let bepolia = BerachainChainSpecParser::parse(BERACHAIN_BEPOLIA).unwrap(); + + assert_eq!(mainnet.active_pol_distributor_at_timestamp(1_756_915_199), None); + assert_eq!(bepolia.active_pol_distributor_at_timestamp(1_754_495_999), None); + assert_eq!(mainnet.active_pol_distributor_at_timestamp(1_756_915_200), Some(expected)); + assert_eq!(bepolia.active_pol_distributor_at_timestamp(1_754_496_000), Some(expected)); + } + #[test] fn test_from_genesis() { let mut genesis = Genesis::default(); diff --git a/src/rpc/config.rs b/src/rpc/config.rs index 9a4ca4b6..70bdc2cb 100644 --- a/src/rpc/config.rs +++ b/src/rpc/config.rs @@ -19,6 +19,9 @@ use reth_rpc_eth_types::EthApiError; use std::collections::BTreeMap; +/// EIP-7910 `systemContracts` key for the PoL distributor (BRIP-0004 / Prague1). +const POL_DISTRIBUTOR_SYSTEM_CONTRACT: &str = "POL_DISTRIBUTOR_ADDRESS"; + /// Berachain `eth_config` RPC handler implementing EIP-7910. #[derive(Debug, Clone)] pub struct BerachainConfigHandler { @@ -40,31 +43,38 @@ where /// Builds fork config for timestamp, returns None if no blob params exist. fn build_fork_config_at( &self, - timestamp: u64, + fork_timestamp: u64, precompiles: BTreeMap, + system_contracts_eval_timestamp: u64, ) -> Option { let chain_spec = self.provider.chain_spec(); let mut system_contracts = BTreeMap::::default(); - if chain_spec.is_cancun_active_at_timestamp(timestamp) { + if chain_spec.is_cancun_active_at_timestamp(fork_timestamp) { system_contracts.extend(SystemContract::cancun()); } - if chain_spec.is_prague_active_at_timestamp(timestamp) { + if chain_spec.is_prague_active_at_timestamp(fork_timestamp) { system_contracts .extend(SystemContract::prague(chain_spec.deposit_contract().map(|c| c.address))); } + insert_berachain_system_contracts( + &chain_spec, + system_contracts_eval_timestamp, + &mut system_contracts, + ); + let fork_id = chain_spec - .fork_id(&Head { timestamp, number: u64::MAX, ..Default::default() }) + .fork_id(&Head { timestamp: fork_timestamp, number: u64::MAX, ..Default::default() }) .hash .0 .into(); Some(EthForkConfig { - activation_time: timestamp, - blob_schedule: chain_spec.blob_params_at_timestamp(timestamp)?, + activation_time: fork_timestamp, + blob_schedule: chain_spec.blob_params_at_timestamp(fork_timestamp)?, chain_id: chain_spec.chain().id(), fork_id, precompiles, @@ -98,7 +108,7 @@ where .ok_or_else(|| RethError::msg("no active timestamp fork found"))?; let current = self - .build_fork_config_at(current_fork_timestamp, current_precompiles) + .build_fork_config_at(current_fork_timestamp, current_precompiles, latest.timestamp) .ok_or_else(|| RethError::msg("no fork config for current fork"))?; let mut config = EthConfig { current, next: None, last: None }; @@ -115,7 +125,11 @@ where .map_err(RethError::other)?, ); - config.next = self.build_fork_config_at(next_fork_timestamp, next_precompiles); + config.next = self.build_fork_config_at( + next_fork_timestamp, + next_precompiles, + next_fork_timestamp, + ); } else { // If there is no fork scheduled, there is no "last" or "final" fork scheduled. return Ok(config); @@ -133,7 +147,8 @@ where .map_err(RethError::other)?, ); - config.last = self.build_fork_config_at(last_fork_timestamp, last_precompiles); + config.last = + self.build_fork_config_at(last_fork_timestamp, last_precompiles, last_fork_timestamp); Ok(config) } @@ -150,6 +165,18 @@ where } } +fn insert_berachain_system_contracts( + chain_spec: &BerachainChainSpec, + eval_timestamp: u64, + system_contracts: &mut BTreeMap, +) { + // BRIP-0004: PoL distributor is a Prague1 system contract, not part of Ethereum Prague. + if let Some(address) = chain_spec.active_pol_distributor_at_timestamp(eval_timestamp) { + system_contracts + .insert(SystemContract::Other(POL_DISTRIBUTOR_SYSTEM_CONTRACT.into()), address); + } +} + fn evm_to_precompiles_map( evm: impl Evm, ) -> BTreeMap { @@ -161,3 +188,59 @@ fn evm_to_precompiles_map( }) .collect() } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{chainspec::BerachainChainSpecParser, hardforks::BerachainHardforks}; + use alloy_primitives::address; + use reth_cli::chainspec::ChainSpecParser; + + const BERACHAIN_BEPOLIA: &str = "bepolia"; + + #[test] + fn test_insert_berachain_system_contracts_bepolia_boundary() { + let chain_spec = BerachainChainSpecParser::parse(BERACHAIN_BEPOLIA).unwrap(); + let expected = address!("D2f19a79b026Fb636A7c300bF5947df113940761"); + + let mut before_prague1 = BTreeMap::default(); + insert_berachain_system_contracts(&chain_spec, 1_754_495_999, &mut before_prague1); + assert!(before_prague1.is_empty()); + + let mut after_prague1 = BTreeMap::default(); + insert_berachain_system_contracts(&chain_spec, 1_754_496_000, &mut after_prague1); + assert_eq!( + after_prague1.get(&SystemContract::Other(POL_DISTRIBUTOR_SYSTEM_CONTRACT.into())), + Some(&expected), + ); + } + + #[test] + fn test_pol_distributor_uses_head_timestamp_not_prague_fork_timestamp() { + let chain_spec = BerachainChainSpecParser::parse(BERACHAIN_BEPOLIA).unwrap(); + let expected = address!("D2f19a79b026Fb636A7c300bF5947df113940761"); + let prague_fork_timestamp = 1_746_633_600; + let post_prague1_head_timestamp = 1_755_000_000; + + assert!(!chain_spec.is_prague1_active_at_timestamp(prague_fork_timestamp)); + assert!(chain_spec.is_prague1_active_at_timestamp(post_prague1_head_timestamp)); + + let mut at_prague_fork = BTreeMap::default(); + insert_berachain_system_contracts(&chain_spec, prague_fork_timestamp, &mut at_prague_fork); + assert!(at_prague_fork.is_empty()); + + let mut at_head = BTreeMap::default(); + insert_berachain_system_contracts(&chain_spec, post_prague1_head_timestamp, &mut at_head); + assert_eq!( + at_head.get(&SystemContract::Other(POL_DISTRIBUTOR_SYSTEM_CONTRACT.into())), + Some(&expected), + ); + } + + #[test] + fn test_pol_distributor_system_contract_serde() { + let contract = SystemContract::Other(POL_DISTRIBUTOR_SYSTEM_CONTRACT.into()); + let value = serde_json::to_value(contract).unwrap(); + assert_eq!(value, "POL_DISTRIBUTOR_ADDRESS"); + } +}