1use crate::rollup_config::RollupConfig;
4use alloy_consensus::{Eip658Value, Receipt};
5use alloy_primitives::{address, b256, Address, Log, B256, U256, U64};
6use alloy_sol_types::{sol, SolType};
7use anyhow::{anyhow, bail, Result};
8
9pub const CONFIG_UPDATE_TOPIC: B256 =
11 b256!("1d2b0bda21d56b8bd12d4f94ebacffdfb35f5e226f84b461103bb8beab6353be");
12
13pub const CONFIG_UPDATE_EVENT_VERSION_0: B256 = B256::ZERO;
15
16#[derive(Debug, Clone, Default, Hash, Eq, PartialEq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
20#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
21pub struct SystemConfig {
22 #[cfg_attr(feature = "serde", serde(rename = "batcherAddr"))]
24 pub batcher_address: Address,
25 pub overhead: U256,
27 pub scalar: U256,
29 pub gas_limit: u64,
31 pub base_fee_scalar: Option<u64>,
33 pub blob_base_fee_scalar: Option<u64>,
35}
36
37#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
39#[repr(u64)]
40pub enum SystemConfigUpdateType {
41 Batcher = 0,
43 GasConfig = 1,
45 GasLimit = 2,
47 UnsafeBlockSigner = 3,
49}
50
51impl TryFrom<u64> for SystemConfigUpdateType {
52 type Error = anyhow::Error;
53
54 fn try_from(value: u64) -> core::prelude::v1::Result<Self, Self::Error> {
55 match value {
56 0 => Ok(SystemConfigUpdateType::Batcher),
57 1 => Ok(SystemConfigUpdateType::GasConfig),
58 2 => Ok(SystemConfigUpdateType::GasLimit),
59 3 => Ok(SystemConfigUpdateType::UnsafeBlockSigner),
60 _ => bail!("Invalid SystemConfigUpdateType value: {}", value),
61 }
62 }
63}
64
65impl SystemConfig {
66 pub fn update_with_receipts(
68 &mut self,
69 receipts: &[Receipt],
70 rollup_config: &RollupConfig,
71 l1_time: u64,
72 ) -> Result<()> {
73 for receipt in receipts {
74 if Eip658Value::Eip658(false) == receipt.status {
75 continue;
76 }
77
78 receipt.logs.iter().try_for_each(|log| {
79 let topics = log.topics();
80 if log.address == rollup_config.l1_system_config_address
81 && !topics.is_empty()
82 && topics[0] == CONFIG_UPDATE_TOPIC
83 {
84 if let Err(e) = self.process_config_update_log(log, rollup_config, l1_time) {
85 anyhow::bail!("Failed to process config update log: {:?}", e);
86 }
87 }
88 Ok::<(), anyhow::Error>(())
89 })?;
90 }
91 Ok::<(), anyhow::Error>(())
92 }
93
94 fn process_config_update_log(
107 &mut self,
108 log: &Log,
109 rollup_config: &RollupConfig,
110 l1_time: u64,
111 ) -> Result<()> {
112 if log.topics().len() < 3 {
113 bail!("Invalid config update log: not enough topics");
114 }
115 if log.topics()[0] != CONFIG_UPDATE_TOPIC {
116 bail!("Invalid config update log: invalid topic");
117 }
118
119 let version = log.topics()[1];
121 if version != CONFIG_UPDATE_EVENT_VERSION_0 {
122 bail!("Invalid config update log: unsupported version");
123 }
124 let update_type = u64::from_be_bytes(
125 log.topics()[2].as_slice()[24..]
126 .try_into()
127 .map_err(|_| anyhow!("Failed to convert update type to u64"))?,
128 );
129 let log_data = log.data.data.as_ref();
130
131 match update_type.try_into()? {
132 SystemConfigUpdateType::Batcher => {
133 if log_data.len() != 96 {
134 bail!("Invalid config update log: invalid data length");
135 }
136
137 let pointer = <sol!(uint64)>::abi_decode(&log_data[0..32], true)
138 .map_err(|_| anyhow!("Failed to decode batcher update log"))?;
139 if pointer != 32 {
140 bail!("Invalid config update log: invalid data pointer");
141 }
142 let length = <sol!(uint64)>::abi_decode(&log_data[32..64], true)
143 .map_err(|_| anyhow!("Failed to decode batcher update log"))?;
144 if length != 32 {
145 bail!("Invalid config update log: invalid data length");
146 }
147
148 let batcher_address =
149 <sol!(address)>::abi_decode(&log.data.data.as_ref()[64..], true)
150 .map_err(|_| anyhow!("Failed to decode batcher update log"))?;
151 self.batcher_address = batcher_address;
152 }
153 SystemConfigUpdateType::GasConfig => {
154 if log_data.len() != 128 {
155 bail!("Invalid config update log: invalid data length");
156 }
157
158 let pointer = <sol!(uint64)>::abi_decode(&log_data[0..32], true)
159 .map_err(|_| anyhow!("Invalid config update log: invalid data pointer"))?;
160 if pointer != 32 {
161 bail!("Invalid config update log: invalid data pointer");
162 }
163 let length = <sol!(uint64)>::abi_decode(&log_data[32..64], true)
164 .map_err(|_| anyhow!("Invalid config update log: invalid data length"))?;
165 if length != 64 {
166 bail!("Invalid config update log: invalid data length");
167 }
168
169 let overhead = <sol!(uint256)>::abi_decode(&log_data[64..96], true)
170 .map_err(|_| anyhow!("Invalid config update log: invalid overhead"))?;
171 let scalar = <sol!(uint256)>::abi_decode(&log_data[96..], true)
172 .map_err(|_| anyhow!("Invalid config update log: invalid scalar"))?;
173
174 if rollup_config.is_ecotone_active(l1_time) {
175 if RollupConfig::check_ecotone_l1_system_config_scalar(scalar.to_be_bytes())
176 .is_err()
177 {
178 return Ok(());
180 }
181
182 self.scalar = scalar;
184 self.overhead = U256::ZERO;
186 } else {
187 self.scalar = scalar;
188 self.overhead = overhead;
189 }
190 }
191 SystemConfigUpdateType::GasLimit => {
192 if log_data.len() != 96 {
193 bail!("Invalid config update log: invalid data length");
194 }
195
196 let pointer = <sol!(uint64)>::abi_decode(&log_data[0..32], true)
197 .map_err(|_| anyhow!("Invalid config update log: invalid data pointer"))?;
198 if pointer != 32 {
199 bail!("Invalid config update log: invalid data pointer");
200 }
201 let length = <sol!(uint64)>::abi_decode(&log_data[32..64], true)
202 .map_err(|_| anyhow!("Invalid config update log: invalid data length"))?;
203 if length != 32 {
204 bail!("Invalid config update log: invalid data length");
205 }
206
207 let gas_limit = <sol!(uint256)>::abi_decode(&log_data[64..], true)
208 .map_err(|_| anyhow!("Invalid config update log: invalid gas limit"))?;
209 self.gas_limit = U64::from(gas_limit).saturating_to::<u64>();
210 }
211 SystemConfigUpdateType::UnsafeBlockSigner => {
212 }
214 }
215
216 Ok(())
217 }
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
223#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
224pub struct SystemAccounts {
225 pub attributes_depositor: Address,
227 pub attributes_predeploy: Address,
229 pub fee_vault: Address,
231}
232
233impl Default for SystemAccounts {
234 fn default() -> Self {
235 Self {
236 attributes_depositor: address!("deaddeaddeaddeaddeaddeaddeaddeaddead0001"),
237 attributes_predeploy: address!("4200000000000000000000000000000000000015"),
238 fee_vault: address!("4200000000000000000000000000000000000011"),
239 }
240 }
241}
242
243#[cfg(test)]
244mod test {
245 use super::*;
246 use crate::ChainGenesis;
247 use alloc::vec;
248 use alloy_primitives::{b256, hex, LogData, B256};
249
250 fn mock_rollup_config(system_config: SystemConfig) -> RollupConfig {
251 RollupConfig {
252 genesis: ChainGenesis {
253 system_config: Some(system_config),
254 ..Default::default()
255 },
256 block_time: 2,
257 l1_chain_id: 1,
258 l2_chain_id: 10,
259 regolith_time: Some(0),
260 canyon_time: Some(0),
261 delta_time: Some(0),
262 ecotone_time: Some(10),
263 fjord_time: Some(0),
264 granite_time: Some(0),
265 holocene_time: Some(0),
266 blobs_enabled_l1_timestamp: Some(0),
267 da_challenge_address: Some(Address::ZERO),
268 ..Default::default()
269 }
270 }
271
272 #[test]
273 fn test_system_config_serde() {
274 let sc_str = r#"{
275 "batcherAddr": "0x6887246668a3b87F54DeB3b94Ba47a6f63F32985",
276 "overhead": "0x00000000000000000000000000000000000000000000000000000000000000bc",
277 "scalar": "0x00000000000000000000000000000000000000000000000000000000000a6fe0",
278 "gasLimit": 30000000
279 }"#;
280 let system_config: SystemConfig = serde_json::from_str(sc_str).unwrap();
281 assert_eq!(
282 system_config.batcher_address,
283 address!("6887246668a3b87F54DeB3b94Ba47a6f63F32985")
284 );
285 assert_eq!(system_config.overhead, U256::from(0xbc));
286 assert_eq!(system_config.scalar, U256::from(0xa6fe0));
287 assert_eq!(system_config.gas_limit, 30000000);
288 }
289
290 #[test]
291 fn test_system_config_update_batcher_log() {
292 const UPDATE_TYPE: B256 =
293 b256!("0000000000000000000000000000000000000000000000000000000000000000");
294
295 let mut system_config = SystemConfig::default();
296 let rollup_config = mock_rollup_config(system_config.clone());
297
298 let update_log = Log {
299 address: Address::ZERO,
300 data: LogData::new_unchecked(
301 vec![
302 CONFIG_UPDATE_TOPIC,
303 CONFIG_UPDATE_EVENT_VERSION_0,
304 UPDATE_TYPE,
305 ],
306 hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000beef").into()
307 )
308 };
309
310 system_config
312 .process_config_update_log(&update_log, &rollup_config, 0)
313 .unwrap();
314
315 assert_eq!(
316 system_config.batcher_address,
317 address!("000000000000000000000000000000000000bEEF")
318 );
319 }
320
321 #[test]
322 fn test_system_config_update_gas_config_log() {
323 const UPDATE_TYPE: B256 =
324 b256!("0000000000000000000000000000000000000000000000000000000000000001");
325
326 let mut system_config = SystemConfig::default();
327 let rollup_config = mock_rollup_config(system_config.clone());
328
329 let update_log = Log {
330 address: Address::ZERO,
331 data: LogData::new_unchecked(
332 vec![
333 CONFIG_UPDATE_TOPIC,
334 CONFIG_UPDATE_EVENT_VERSION_0,
335 UPDATE_TYPE,
336 ],
337 hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000babe000000000000000000000000000000000000000000000000000000000000beef").into()
338 )
339 };
340
341 system_config
343 .process_config_update_log(&update_log, &rollup_config, 0)
344 .unwrap();
345
346 assert_eq!(system_config.overhead, U256::from(0xbabe));
347 assert_eq!(system_config.scalar, U256::from(0xbeef));
348 }
349
350 #[test]
351 fn test_system_config_update_gas_config_log_ecotone() {
352 const UPDATE_TYPE: B256 =
353 b256!("0000000000000000000000000000000000000000000000000000000000000001");
354
355 let mut system_config = SystemConfig::default();
356 let rollup_config = mock_rollup_config(system_config.clone());
357
358 let update_log = Log {
359 address: Address::ZERO,
360 data: LogData::new_unchecked(
361 vec![
362 CONFIG_UPDATE_TOPIC,
363 CONFIG_UPDATE_EVENT_VERSION_0,
364 UPDATE_TYPE,
365 ],
366 hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000babe000000000000000000000000000000000000000000000000000000000000beef").into()
367 )
368 };
369
370 system_config
372 .process_config_update_log(&update_log, &rollup_config, 10)
373 .unwrap();
374
375 assert_eq!(system_config.overhead, U256::from(0));
376 assert_eq!(system_config.scalar, U256::from(0xbeef));
377 }
378
379 #[test]
380 fn test_system_config_update_gas_limit_log() {
381 const UPDATE_TYPE: B256 =
382 b256!("0000000000000000000000000000000000000000000000000000000000000002");
383
384 let mut system_config = SystemConfig::default();
385 let rollup_config = mock_rollup_config(system_config.clone());
386
387 let update_log = Log {
388 address: Address::ZERO,
389 data: LogData::new_unchecked(
390 vec![
391 CONFIG_UPDATE_TOPIC,
392 CONFIG_UPDATE_EVENT_VERSION_0,
393 UPDATE_TYPE,
394 ],
395 hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000beef").into()
396 )
397 };
398
399 system_config
401 .process_config_update_log(&update_log, &rollup_config, 0)
402 .unwrap();
403
404 assert_eq!(system_config.gas_limit, 0xbeef_u64);
405 }
406}