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