1use crate::{
4 CONFIG_UPDATE_TOPIC, RollupConfig, SystemConfigLog, SystemConfigUpdateError,
5 SystemConfigUpdateKind,
6};
7use alloy_consensus::{Eip658Value, Receipt};
8use alloy_primitives::{Address, B64, B256, Log, U256};
9
10#[derive(Debug, Copy, Clone, Default, Hash, Eq, PartialEq)]
12#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
13#[cfg_attr(feature = "serde", derive(serde::Serialize))]
14#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
15#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
16pub struct SystemConfig {
17 #[cfg_attr(feature = "serde", serde(rename = "batcherAddress", alias = "batcherAddr"))]
19 pub batcher_address: Address,
20 pub overhead: U256,
22 pub scalar: U256,
24 pub gas_limit: u64,
26 pub base_fee_scalar: Option<u64>,
28 pub blob_base_fee_scalar: Option<u64>,
30 pub eip1559_denominator: Option<u32>,
32 pub eip1559_elasticity: Option<u32>,
34 pub operator_fee_scalar: Option<u32>,
36 pub operator_fee_constant: Option<u64>,
38}
39
40#[cfg(feature = "serde")]
46impl<'a> serde::Deserialize<'a> for SystemConfig {
47 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
48 where
49 D: serde::Deserializer<'a>,
50 {
51 #[derive(serde::Deserialize)]
54 #[serde(rename_all = "camelCase")]
55 #[serde(deny_unknown_fields)]
56 struct SystemConfigAlias {
57 #[serde(rename = "batcherAddress", alias = "batcherAddr")]
58 batcher_address: Address,
59 overhead: U256,
60 scalar: U256,
61 gas_limit: u64,
62 base_fee_scalar: Option<u64>,
63 blob_base_fee_scalar: Option<u64>,
64 eip1559_params: Option<B64>,
65 eip1559_denominator: Option<u32>,
66 eip1559_elasticity: Option<u32>,
67 operator_fee_params: Option<B256>,
68 operator_fee_scalar: Option<u32>,
69 operator_fee_constant: Option<u64>,
70 }
71
72 let mut alias = SystemConfigAlias::deserialize(deserializer)?;
73 if let Some(params) = alias.eip1559_params {
74 alias.eip1559_denominator =
75 Some(u32::from_be_bytes(params.as_slice().get(0..4).unwrap().try_into().unwrap()));
76 alias.eip1559_elasticity =
77 Some(u32::from_be_bytes(params.as_slice().get(4..8).unwrap().try_into().unwrap()));
78 }
79 if let Some(params) = alias.operator_fee_params {
80 alias.operator_fee_scalar = Some(u32::from_be_bytes(
81 params.as_slice().get(20..24).unwrap().try_into().unwrap(),
82 ));
83 alias.operator_fee_constant = Some(u64::from_be_bytes(
84 params.as_slice().get(24..32).unwrap().try_into().unwrap(),
85 ));
86 }
87
88 Ok(Self {
89 batcher_address: alias.batcher_address,
90 overhead: alias.overhead,
91 scalar: alias.scalar,
92 gas_limit: alias.gas_limit,
93 base_fee_scalar: alias.base_fee_scalar,
94 blob_base_fee_scalar: alias.blob_base_fee_scalar,
95 eip1559_denominator: alias.eip1559_denominator,
96 eip1559_elasticity: alias.eip1559_elasticity,
97 operator_fee_scalar: alias.operator_fee_scalar,
98 operator_fee_constant: alias.operator_fee_constant,
99 })
100 }
101}
102
103impl SystemConfig {
104 pub fn update_with_receipts(
106 &mut self,
107 receipts: &[Receipt],
108 l1_system_config_address: Address,
109 ecotone_active: bool,
110 ) -> Result<(), SystemConfigUpdateError> {
111 for receipt in receipts {
112 if Eip658Value::Eip658(false) == receipt.status {
113 continue;
114 }
115
116 receipt.logs.iter().try_for_each(|log| {
117 let topics = log.topics();
118 if log.address == l1_system_config_address &&
119 !topics.is_empty() &&
120 topics[0] == CONFIG_UPDATE_TOPIC
121 {
122 self.process_config_update_log(log, ecotone_active)?;
124 }
125 Ok::<(), SystemConfigUpdateError>(())
126 })?;
127 }
128 Ok(())
129 }
130
131 pub fn eip_1559_params(
133 &self,
134 rollup_config: &RollupConfig,
135 parent_timestamp: u64,
136 next_timestamp: u64,
137 ) -> Option<B64> {
138 let is_holocene = rollup_config.is_holocene_active(next_timestamp);
139
140 if is_holocene && !rollup_config.is_holocene_active(parent_timestamp) {
144 Some(B64::ZERO)
145 } else {
146 is_holocene.then_some(B64::from_slice(
147 &[
148 self.eip1559_denominator.unwrap_or_default().to_be_bytes(),
149 self.eip1559_elasticity.unwrap_or_default().to_be_bytes(),
150 ]
151 .concat(),
152 ))
153 }
154 }
155
156 fn process_config_update_log(
169 &mut self,
170 log: &Log,
171 ecotone_active: bool,
172 ) -> Result<SystemConfigUpdateKind, SystemConfigUpdateError> {
173 let log = SystemConfigLog::new(log.clone(), ecotone_active);
175
176 let update = log.build()?;
178
179 update.apply(self);
181
182 Ok(update.kind())
184 }
185}
186
187#[cfg(test)]
188mod test {
189 use super::*;
190 use crate::{CONFIG_UPDATE_EVENT_VERSION_0, HardForkConfig};
191 use alloc::vec;
192 use alloy_primitives::{B256, LogData, address, b256, hex};
193
194 #[test]
195 #[cfg(feature = "serde")]
196 fn test_system_config_eip1559_params() {
197 let raw = r#"{
198 "batcherAddress": "0x6887246668a3b87F54DeB3b94Ba47a6f63F32985",
199 "overhead": "0x00000000000000000000000000000000000000000000000000000000000000bc",
200 "scalar": "0x00000000000000000000000000000000000000000000000000000000000a6fe0",
201 "gasLimit": 30000000,
202 "eip1559Params": "0x000000ab000000cd"
203 }"#;
204 let system_config: SystemConfig = serde_json::from_str(raw).unwrap();
205 assert_eq!(system_config.eip1559_denominator, Some(0xab_u32), "eip1559_denominator");
206 assert_eq!(system_config.eip1559_elasticity, Some(0xcd_u32), "eip1559_elasticity");
207 }
208
209 #[test]
210 #[cfg(feature = "serde")]
211 fn test_system_config_serde() {
212 let raw = r#"{
213 "batcherAddr": "0x6887246668a3b87F54DeB3b94Ba47a6f63F32985",
214 "overhead": "0x00000000000000000000000000000000000000000000000000000000000000bc",
215 "scalar": "0x00000000000000000000000000000000000000000000000000000000000a6fe0",
216 "gasLimit": 30000000
217 }"#;
218 let expected = SystemConfig {
219 batcher_address: address!("6887246668a3b87F54DeB3b94Ba47a6f63F32985"),
220 overhead: U256::from(0xbc),
221 scalar: U256::from(0xa6fe0),
222 gas_limit: 30000000,
223 ..Default::default()
224 };
225
226 let deserialized: SystemConfig = serde_json::from_str(raw).unwrap();
227 assert_eq!(deserialized, expected);
228 }
229
230 #[test]
231 #[cfg(feature = "serde")]
232 fn test_system_config_unknown_field() {
233 let raw = r#"{
234 "batcherAddr": "0x6887246668a3b87F54DeB3b94Ba47a6f63F32985",
235 "overhead": "0x00000000000000000000000000000000000000000000000000000000000000bc",
236 "scalar": "0x00000000000000000000000000000000000000000000000000000000000a6fe0",
237 "gasLimit": 30000000,
238 "unknown": 0
239 }"#;
240 let err = serde_json::from_str::<SystemConfig>(raw).unwrap_err();
241 assert_eq!(err.classify(), serde_json::error::Category::Data);
242 }
243
244 #[test]
245 #[cfg(feature = "arbitrary")]
246 fn test_arbitrary_system_config() {
247 use arbitrary::Arbitrary;
248 use rand::Rng;
249 let mut bytes = [0u8; 1024];
250 rand::rng().fill(bytes.as_mut_slice());
251 SystemConfig::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
252 }
253
254 #[test]
255 fn test_eip_1559_params_from_system_config_none() {
256 let rollup_config = RollupConfig::default();
257 let sys_config = SystemConfig::default();
258 assert_eq!(sys_config.eip_1559_params(&rollup_config, 0, 0), None);
259 }
260
261 #[test]
262 fn test_eip_1559_params_from_system_config_some() {
263 let rollup_config = RollupConfig {
264 hardforks: HardForkConfig { holocene_time: Some(0), ..Default::default() },
265 ..Default::default()
266 };
267 let sys_config = SystemConfig {
268 eip1559_denominator: Some(1),
269 eip1559_elasticity: None,
270 ..Default::default()
271 };
272 let expected = Some(B64::from_slice(&[1u32.to_be_bytes(), 0u32.to_be_bytes()].concat()));
273 assert_eq!(sys_config.eip_1559_params(&rollup_config, 0, 0), expected);
274 }
275
276 #[test]
277 fn test_eip_1559_params_from_system_config() {
278 let rollup_config = RollupConfig {
279 hardforks: HardForkConfig { holocene_time: Some(0), ..Default::default() },
280 ..Default::default()
281 };
282 let sys_config = SystemConfig {
283 eip1559_denominator: Some(1),
284 eip1559_elasticity: Some(2),
285 ..Default::default()
286 };
287 let expected = Some(B64::from_slice(&[1u32.to_be_bytes(), 2u32.to_be_bytes()].concat()));
288 assert_eq!(sys_config.eip_1559_params(&rollup_config, 0, 0), expected);
289 }
290
291 #[test]
292 fn test_default_eip_1559_params_from_system_config() {
293 let rollup_config = RollupConfig {
294 hardforks: HardForkConfig { holocene_time: Some(0), ..Default::default() },
295 ..Default::default()
296 };
297 let sys_config = SystemConfig {
298 eip1559_denominator: None,
299 eip1559_elasticity: None,
300 ..Default::default()
301 };
302 let expected = Some(B64::ZERO);
303 assert_eq!(sys_config.eip_1559_params(&rollup_config, 0, 0), expected);
304 }
305
306 #[test]
307 fn test_default_eip_1559_params_from_system_config_pre_holocene() {
308 let rollup_config = RollupConfig::default();
309 let sys_config = SystemConfig {
310 eip1559_denominator: Some(1),
311 eip1559_elasticity: Some(2),
312 ..Default::default()
313 };
314 assert_eq!(sys_config.eip_1559_params(&rollup_config, 0, 0), None);
315 }
316
317 #[test]
318 fn test_default_eip_1559_params_first_block_holocene() {
319 let rollup_config = RollupConfig {
320 hardforks: HardForkConfig { holocene_time: Some(2), ..Default::default() },
321 ..Default::default()
322 };
323 let sys_config = SystemConfig {
324 eip1559_denominator: Some(1),
325 eip1559_elasticity: Some(2),
326 ..Default::default()
327 };
328 assert_eq!(sys_config.eip_1559_params(&rollup_config, 0, 2), Some(B64::ZERO));
329 }
330
331 #[test]
332 fn test_system_config_update_with_receipts_unchanged() {
333 let mut system_config = SystemConfig::default();
334 let receipts = vec![];
335 let l1_system_config_address = Address::ZERO;
336 let ecotone_active = false;
337
338 system_config
339 .update_with_receipts(&receipts, l1_system_config_address, ecotone_active)
340 .unwrap();
341
342 assert_eq!(system_config, SystemConfig::default());
343 }
344
345 #[test]
346 fn test_system_config_update_with_receipts_batcher_address() {
347 const UPDATE_TYPE: B256 =
348 b256!("0000000000000000000000000000000000000000000000000000000000000000");
349 let mut system_config = SystemConfig::default();
350 let l1_system_config_address = Address::ZERO;
351 let ecotone_active = false;
352
353 let update_log = Log {
354 address: Address::ZERO,
355 data: LogData::new_unchecked(
356 vec![
357 CONFIG_UPDATE_TOPIC,
358 CONFIG_UPDATE_EVENT_VERSION_0,
359 UPDATE_TYPE,
360 ],
361 hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000beef").into()
362 )
363 };
364
365 let receipt = Receipt {
366 logs: vec![update_log],
367 status: Eip658Value::Eip658(true),
368 cumulative_gas_used: 0,
369 };
370
371 system_config
372 .update_with_receipts(&[receipt], l1_system_config_address, ecotone_active)
373 .unwrap();
374
375 assert_eq!(
376 system_config.batcher_address,
377 address!("000000000000000000000000000000000000bEEF"),
378 );
379 }
380
381 #[test]
382 fn test_system_config_update_batcher_log() {
383 const UPDATE_TYPE: B256 =
384 b256!("0000000000000000000000000000000000000000000000000000000000000000");
385
386 let mut system_config = SystemConfig::default();
387
388 let update_log = Log {
389 address: Address::ZERO,
390 data: LogData::new_unchecked(
391 vec![
392 CONFIG_UPDATE_TOPIC,
393 CONFIG_UPDATE_EVENT_VERSION_0,
394 UPDATE_TYPE,
395 ],
396 hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000beef").into()
397 )
398 };
399
400 system_config.process_config_update_log(&update_log, false).unwrap();
402
403 assert_eq!(
404 system_config.batcher_address,
405 address!("000000000000000000000000000000000000bEEF")
406 );
407 }
408
409 #[test]
410 fn test_system_config_update_gas_config_log() {
411 const UPDATE_TYPE: B256 =
412 b256!("0000000000000000000000000000000000000000000000000000000000000001");
413
414 let mut system_config = SystemConfig::default();
415
416 let update_log = Log {
417 address: Address::ZERO,
418 data: LogData::new_unchecked(
419 vec![
420 CONFIG_UPDATE_TOPIC,
421 CONFIG_UPDATE_EVENT_VERSION_0,
422 UPDATE_TYPE,
423 ],
424 hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000babe000000000000000000000000000000000000000000000000000000000000beef").into()
425 )
426 };
427
428 system_config.process_config_update_log(&update_log, false).unwrap();
430
431 assert_eq!(system_config.overhead, U256::from(0xbabe));
432 assert_eq!(system_config.scalar, U256::from(0xbeef));
433 }
434
435 #[test]
436 fn test_system_config_update_gas_config_log_ecotone() {
437 const UPDATE_TYPE: B256 =
438 b256!("0000000000000000000000000000000000000000000000000000000000000001");
439
440 let mut system_config = SystemConfig::default();
441
442 let update_log = Log {
443 address: Address::ZERO,
444 data: LogData::new_unchecked(
445 vec![
446 CONFIG_UPDATE_TOPIC,
447 CONFIG_UPDATE_EVENT_VERSION_0,
448 UPDATE_TYPE,
449 ],
450 hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000babe000000000000000000000000000000000000000000000000000000000000beef").into()
451 )
452 };
453
454 system_config.process_config_update_log(&update_log, true).unwrap();
456
457 assert_eq!(system_config.overhead, U256::from(0));
458 assert_eq!(system_config.scalar, U256::from(0xbeef));
459 }
460
461 #[test]
462 fn test_system_config_update_gas_limit_log() {
463 const UPDATE_TYPE: B256 =
464 b256!("0000000000000000000000000000000000000000000000000000000000000002");
465
466 let mut system_config = SystemConfig::default();
467
468 let update_log = Log {
469 address: Address::ZERO,
470 data: LogData::new_unchecked(
471 vec![
472 CONFIG_UPDATE_TOPIC,
473 CONFIG_UPDATE_EVENT_VERSION_0,
474 UPDATE_TYPE,
475 ],
476 hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000beef").into()
477 )
478 };
479
480 system_config.process_config_update_log(&update_log, false).unwrap();
482
483 assert_eq!(system_config.gas_limit, 0xbeef_u64);
484 }
485
486 #[test]
487 fn test_system_config_update_eip1559_params_log() {
488 const UPDATE_TYPE: B256 =
489 b256!("0000000000000000000000000000000000000000000000000000000000000004");
490
491 let mut system_config = SystemConfig::default();
492 let update_log = Log {
493 address: Address::ZERO,
494 data: LogData::new_unchecked(
495 vec![
496 CONFIG_UPDATE_TOPIC,
497 CONFIG_UPDATE_EVENT_VERSION_0,
498 UPDATE_TYPE,
499 ],
500 hex!("000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000babe0000beef").into()
501 )
502 };
503
504 system_config.process_config_update_log(&update_log, false).unwrap();
506
507 assert_eq!(system_config.eip1559_denominator, Some(0xbabe_u32));
508 assert_eq!(system_config.eip1559_elasticity, Some(0xbeef_u32));
509 }
510
511 #[test]
512 fn test_system_config_update_operator_fee_log() {
513 const UPDATE_TYPE: B256 =
514 b256!("0000000000000000000000000000000000000000000000000000000000000005");
515
516 let mut system_config = SystemConfig::default();
517 let update_log = Log {
518 address: Address::ZERO,
519 data: LogData::new_unchecked(
520 vec![
521 CONFIG_UPDATE_TOPIC,
522 CONFIG_UPDATE_EVENT_VERSION_0,
523 UPDATE_TYPE,
524 ],
525 hex!("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000babe000000000000beef").into()
526 )
527 };
528
529 system_config.process_config_update_log(&update_log, false).unwrap();
531
532 assert_eq!(system_config.operator_fee_scalar, Some(0xbabe_u32));
533 assert_eq!(system_config.operator_fee_constant, Some(0xbeef_u64));
534 }
535}