1use alloy_genesis::ChainConfig;
4use reth_chainspec::{EthChainSpec, EthereumHardfork, EthereumHardforks, ForkCondition};
5
6pub(crate) fn chain_config<C>(spec: &C) -> ChainConfig
12where
13 C: EthChainSpec + EthereumHardforks + ?Sized,
14{
15 let mut config = ChainConfig {
16 chain_id: spec.chain().id(),
17 terminal_total_difficulty_passed: spec.final_paris_total_difficulty().is_some(),
18 terminal_total_difficulty: spec.ethereum_fork_activation(EthereumHardfork::Paris).ttd(),
19 deposit_contract_address: spec.deposit_contract().map(|dc| dc.address),
20 ..spec.genesis().config.clone()
21 };
22
23 let activation = |fork: EthereumHardfork| match spec.ethereum_fork_activation(fork) {
24 ForkCondition::Block(block) => Some(block),
25 ForkCondition::TTD { fork_block, .. } => fork_block,
26 ForkCondition::Timestamp(ts) => Some(ts),
27 ForkCondition::Never => None,
28 };
29
30 macro_rules! set_block_or_time {
31 ($( $field:ident => $fork:ident,)*) => {
32 $(
33 config.$field = activation(EthereumHardfork::$fork).or(config.$field);
36 )*
37 };
38 }
39
40 set_block_or_time!(
41 homestead_block => Homestead,
42 dao_fork_block => Dao,
43 eip150_block => Tangerine,
44 eip155_block => SpuriousDragon,
45 eip158_block => SpuriousDragon,
46 byzantium_block => Byzantium,
47 constantinople_block => Constantinople,
48 petersburg_block => Petersburg,
49 istanbul_block => Istanbul,
50 muir_glacier_block => MuirGlacier,
51 berlin_block => Berlin,
52 london_block => London,
53 arrow_glacier_block => ArrowGlacier,
54 gray_glacier_block => GrayGlacier,
55 shanghai_time => Shanghai,
56 cancun_time => Cancun,
57 prague_time => Prague,
58 osaka_time => Osaka,
59 amsterdam_time => Amsterdam,
60 bogota_time => Bogota,
61 bpo1_time => Bpo1,
62 bpo2_time => Bpo2,
63 bpo3_time => Bpo3,
64 bpo4_time => Bpo4,
65 bpo5_time => Bpo5,
66 );
67
68 if config.dao_fork_block.is_some() {
69 config.dao_fork_support = true;
70 }
71
72 let blob_forks = [EthereumHardfork::Cancun, EthereumHardfork::Prague, EthereumHardfork::Osaka]
73 .iter()
74 .chain(EthereumHardfork::bpo_variants());
75 for fork in blob_forks {
76 if let ForkCondition::Timestamp(ts) = spec.ethereum_fork_activation(*fork) &&
77 let Some(params) = spec.blob_params_at_timestamp(ts)
78 {
79 config.blob_schedule.entry(fork.name().to_lowercase()).or_insert(params);
81 }
82 }
83
84 config
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use alloy_eips::eip7840::BlobParams;
91 use reth_chainspec::{ChainSpec, HOODI, MAINNET, SEPOLIA};
92
93 #[test]
94 fn mainnet_config_is_derived_from_hardforks() {
95 let config = chain_config(&*MAINNET);
96
97 assert_eq!(config.chain_id, 1);
98 assert_eq!(config.homestead_block, Some(1_150_000));
99 assert_eq!(config.dao_fork_block, Some(1_920_000));
100 assert!(config.dao_fork_support);
101 assert_eq!(config.london_block, Some(12_965_000));
102 assert_eq!(config.shanghai_time, Some(1_681_338_455));
103 assert_eq!(config.cancun_time, Some(1_710_338_135));
104 assert_eq!(config.prague_time, Some(1_746_612_311));
105 assert_eq!(
106 config.osaka_time,
107 MAINNET.ethereum_fork_activation(EthereumHardfork::Osaka).as_timestamp()
108 );
109 assert_eq!(
110 config.bpo1_time,
111 MAINNET.ethereum_fork_activation(EthereumHardfork::Bpo1).as_timestamp()
112 );
113 assert!(config.terminal_total_difficulty_passed);
114 assert!(config.terminal_total_difficulty.is_some());
115 assert_eq!(
116 config.deposit_contract_address,
117 MAINNET.deposit_contract().map(|dc| dc.address)
118 );
119 assert_eq!(
120 config.blob_schedule.get("cancun").map(|p| p.max_blob_count),
121 MAINNET.blob_params_at_timestamp(config.cancun_time.unwrap()).map(|p| p.max_blob_count)
122 );
123 assert!(config.blob_schedule.contains_key("prague"));
124 assert!(config.blob_schedule.contains_key("osaka"));
125 assert_eq!(config.blob_schedule.contains_key("bpo1"), config.bpo1_time.is_some());
126 }
127
128 #[test]
129 fn sepolia_config_reports_its_chain_id() {
130 let config = chain_config(&*SEPOLIA);
131 assert_eq!(config.chain_id, SEPOLIA.chain().id());
132 assert_eq!(config.shanghai_time, Some(1_677_557_088));
133 assert!(config.blob_schedule.contains_key("cancun"));
134 }
135
136 #[test]
137 fn hoodi_config_includes_forks_missing_from_genesis() {
138 let config = chain_config(&*HOODI);
139 let osaka = HOODI.ethereum_fork_activation(EthereumHardfork::Osaka).as_timestamp();
140 assert!(osaka.is_some());
141 assert_eq!(config.osaka_time, osaka);
142 assert!(config.blob_schedule.contains_key("osaka"));
143 }
144
145 #[test]
146 fn genesis_config_fields_are_kept() {
147 let mut genesis = MAINNET.genesis().clone();
148 genesis.config.chain_id = 1337;
149 genesis.config.extra_fields.insert("custom".to_string(), serde_json::json!(true));
150 let spec = ChainSpec::from(genesis);
151
152 let config = chain_config(&spec);
153 assert_eq!(config.chain_id, 1337);
154 assert_eq!(config.extra_fields.get("custom"), Some(&serde_json::json!(true)));
155 }
156
157 #[test]
158 fn genesis_blob_schedule_is_kept_when_forks_share_a_timestamp() {
159 let mut genesis = MAINNET.genesis().clone();
160 genesis.config.cancun_time = Some(0);
161 genesis.config.prague_time = Some(0);
162 genesis.config.blob_schedule.insert("cancun".to_string(), BlobParams::cancun());
163 genesis.config.blob_schedule.insert("prague".to_string(), BlobParams::prague());
164 let expected = genesis.config.blob_schedule.clone();
165 let spec = ChainSpec::from(genesis);
166
167 assert_eq!(chain_config(&spec).blob_schedule, expected);
168 }
169}