superchain_primitives/
system_config.rs

1//! System Config Type
2
3use 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
9/// `keccak256("ConfigUpdate(uint256,uint8,bytes)")`
10pub const CONFIG_UPDATE_TOPIC: B256 =
11    b256!("1d2b0bda21d56b8bd12d4f94ebacffdfb35f5e226f84b461103bb8beab6353be");
12
13/// The initial version of the system config event log.
14pub const CONFIG_UPDATE_EVENT_VERSION_0: B256 = B256::ZERO;
15
16/// System configuration.
17#[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    /// Batcher address
23    #[cfg_attr(feature = "serde", serde(rename = "batcherAddr"))]
24    pub batcher_address: Address,
25    /// Fee overhead value
26    pub overhead: U256,
27    /// Fee scalar value
28    pub scalar: U256,
29    /// Gas limit value
30    pub gas_limit: u64,
31    /// Base fee scalar value
32    pub base_fee_scalar: Option<u64>,
33    /// Blob base fee scalar value
34    pub blob_base_fee_scalar: Option<u64>,
35}
36
37/// Represents type of update to the system config.
38#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
39#[repr(u64)]
40pub enum SystemConfigUpdateType {
41    /// Batcher update type
42    Batcher = 0,
43    /// Gas config update type
44    GasConfig = 1,
45    /// Gas limit update type
46    GasLimit = 2,
47    /// Unsafe block signer update type
48    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    /// Filters all L1 receipts to find config updates and applies the config updates.
67    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    /// Decodes an EVM log entry emitted by the system config contract and applies it as a
95    /// [SystemConfig] change.
96    ///
97    /// Parse log data for:
98    ///
99    /// ```text
100    /// event ConfigUpdate(
101    ///    uint256 indexed version,
102    ///    UpdateType indexed updateType,
103    ///    bytes data
104    /// );
105    /// ```
106    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        // Parse the config update log
120        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                        // ignore invalid scalars, retain the old system-config scalar
179                        return Ok(());
180                    }
181
182                    // retain the scalar data in encoded form
183                    self.scalar = scalar;
184                    // zero out the overhead, it will not affect the state-transition after Ecotone
185                    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                // Ignored in derivation
213            }
214        }
215
216        Ok(())
217    }
218}
219
220/// System accounts
221#[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    /// The address that can deposit attributes
226    pub attributes_depositor: Address,
227    /// The address of the attributes predeploy
228    pub attributes_predeploy: Address,
229    /// The address of the fee vault
230    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        // Update the batcher address.
311        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        // Update the batcher address.
342        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        // Update the batcher address.
371        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        // Update the batcher address.
400        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}