kona_genesis/system/
config.rs

1//! Contains the [`SystemConfig`] type.
2
3use 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/// System configuration.
11#[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    /// Batcher address
18    #[cfg_attr(feature = "serde", serde(rename = "batcherAddr"))]
19    pub batcher_address: Address,
20    /// Fee overhead value
21    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_u256_full"))]
22    pub overhead: U256,
23    /// Fee scalar value
24    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_u256_full"))]
25    pub scalar: U256,
26    /// Gas limit value
27    pub gas_limit: u64,
28    /// Base fee scalar value
29    pub base_fee_scalar: Option<u64>,
30    /// Blob base fee scalar value
31    pub blob_base_fee_scalar: Option<u64>,
32    /// EIP-1559 denominator
33    pub eip1559_denominator: Option<u32>,
34    /// EIP-1559 elasticity
35    pub eip1559_elasticity: Option<u32>,
36    /// The operator fee scalar (isthmus hardfork)
37    pub operator_fee_scalar: Option<u32>,
38    /// The operator fee constant (isthmus hardfork)
39    pub operator_fee_constant: Option<u64>,
40}
41
42/// Custom EIP-1559 parameter decoding is needed here for holocene encoding.
43///
44/// This is used by the Optimism monorepo [here][here].
45///
46/// [here]: https://github.com/ethereum-optimism/optimism/blob/cf28bffc7d880292794f53bb76bfc4df7898307b/op-service/eth/types.go#L519
47#[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        // An alias struct that is identical to `SystemConfig`.
55        // We use the alias to decode the eip1559 params as their u32 values.
56        #[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    /// Filters all L1 receipts to find config updates and applies the config updates.
108    ///
109    /// Returns `true` if any config updates were applied, `false` otherwise.
110    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                    // Safety: Error is bubbled up by the trailing `?`
129                    self.process_config_update_log(log, ecotone_active)?;
130                    updated = true;
131                }
132                Ok::<(), SystemConfigUpdateError>(())
133            })?;
134        }
135        Ok(updated)
136    }
137
138    /// Returns the eip1559 parameters from a [SystemConfig] encoded as a [B64].
139    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        // For the first holocene block, a zero'd out B64 is returned to signal the
148        // execution layer to use the canyon base fee parameters. Else, the system
149        // config's eip1559 parameters are encoded as a B64.
150        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    /// Decodes an EVM log entry emitted by the system config contract and applies it as a
164    /// [SystemConfig] change.
165    ///
166    /// Parse log data for:
167    ///
168    /// ```text
169    /// event ConfigUpdate(
170    ///    uint256 indexed version,
171    ///    UpdateType indexed updateType,
172    ///    bytes data
173    /// );
174    /// ```
175    fn process_config_update_log(
176        &mut self,
177        log: &Log,
178        ecotone_active: bool,
179    ) -> Result<SystemConfigUpdateKind, SystemConfigUpdateError> {
180        // Construct the system config log from the log.
181        let log = SystemConfigLog::new(log.clone(), ecotone_active);
182
183        // Construct the update type from the log.
184        let update = log.build()?;
185
186        // Apply the update to the system config.
187        update.apply(self);
188
189        // Return the update type.
190        Ok(update.kind())
191    }
192}
193
194/// Compatibility helper function to serialize a [`U256`] as a [`B256`].
195///
196/// [`B256`]: alloy_primitives::B256
197#[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        // Update the batcher address.
423        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        // Update the batcher address.
451        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        // Update the gas limit.
477        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        // Update the gas limit.
503        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        // Update the EIP-1559 parameters.
527        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        // Update the operator fee.
552        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}