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");
+ }
+}