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, B256, 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 = "batcherAddress", alias = "batcherAddr"))]
19    pub batcher_address: Address,
20    /// Fee overhead value
21    pub overhead: U256,
22    /// Fee scalar value
23    pub scalar: U256,
24    /// Gas limit value
25    pub gas_limit: u64,
26    /// Base fee scalar value
27    pub base_fee_scalar: Option<u64>,
28    /// Blob base fee scalar value
29    pub blob_base_fee_scalar: Option<u64>,
30    /// EIP-1559 denominator
31    pub eip1559_denominator: Option<u32>,
32    /// EIP-1559 elasticity
33    pub eip1559_elasticity: Option<u32>,
34    /// The operator fee scalar (isthmus hardfork)
35    pub operator_fee_scalar: Option<u32>,
36    /// The operator fee constant (isthmus hardfork)
37    pub operator_fee_constant: Option<u64>,
38}
39
40/// Custom EIP-1559 parameter decoding is needed here for holocene encoding.
41///
42/// This is used by the Optimism monorepo [here][here].
43///
44/// [here]: https://github.com/ethereum-optimism/optimism/blob/cf28bffc7d880292794f53bb76bfc4df7898307b/op-service/eth/types.go#L519
45#[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        // An alias struct that is identical to `SystemConfig`.
52        // We use the alias to decode the eip1559 params as their u32 values.
53        #[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    /// Filters all L1 receipts to find config updates and applies the config updates.
105    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                    // Safety: Error is bubbled up by the trailing `?`
123                    self.process_config_update_log(log, ecotone_active)?;
124                }
125                Ok::<(), SystemConfigUpdateError>(())
126            })?;
127        }
128        Ok(())
129    }
130
131    /// Returns the eip1559 parameters from a [SystemConfig] encoded as a [B64].
132    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        // For the first holocene block, a zero'd out B64 is returned to signal the
141        // execution layer to use the canyon base fee parameters. Else, the system
142        // config's eip1559 parameters are encoded as a B64.
143        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    /// Decodes an EVM log entry emitted by the system config contract and applies it as a
157    /// [SystemConfig] change.
158    ///
159    /// Parse log data for:
160    ///
161    /// ```text
162    /// event ConfigUpdate(
163    ///    uint256 indexed version,
164    ///    UpdateType indexed updateType,
165    ///    bytes data
166    /// );
167    /// ```
168    fn process_config_update_log(
169        &mut self,
170        log: &Log,
171        ecotone_active: bool,
172    ) -> Result<SystemConfigUpdateKind, SystemConfigUpdateError> {
173        // Construct the system config log from the log.
174        let log = SystemConfigLog::new(log.clone(), ecotone_active);
175
176        // Construct the update type from the log.
177        let update = log.build()?;
178
179        // Apply the update to the system config.
180        update.apply(self);
181
182        // Return the update type.
183        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        // Update the batcher address.
401        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        // Update the batcher address.
429        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        // Update the gas limit.
455        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        // Update the gas limit.
481        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        // Update the EIP-1559 parameters.
505        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        // Update the operator fee.
530        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}