sequencer/
genesis.rs

1use std::{
2    collections::{BTreeMap, HashMap},
3    path::Path,
4};
5
6use alloy::primitives::Address;
7use anyhow::{Context, Ok};
8use espresso_types::{
9    v0_3::ChainConfig, FeeAccount, FeeAmount, GenesisHeader, L1BlockInfo, L1Client, SeqTypes,
10    Timestamp, Upgrade,
11};
12use hotshot_types::{version_ser, VersionedDaCommittee};
13use serde::{Deserialize, Serialize};
14use vbs::version::Version;
15
16/// Initial configuration of an Espresso stake table.
17#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
18pub struct StakeTableConfig {
19    pub capacity: usize,
20}
21
22/// An L1 block from which an Espresso chain should start syncing.
23#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
24#[serde(untagged)]
25pub enum L1Finalized {
26    /// Complete block info.
27    ///
28    /// This allows a validator to specify the exact, existing L1 block to start syncing from. A
29    /// validator that specifies a specific L1 block will not be able to reach consensus with a
30    /// malicious validator that starts from a different L1 block.
31    Block(L1BlockInfo),
32
33    /// An L1 block number to sync from.
34    ///
35    /// This allows a validator to specify a future L1 block whose hash is not yet known, and start
36    /// syncing only when a finalized block with the given number becomes available. The configured
37    /// L1 client will be used to fetch the rest of the block info once available.
38    Number { number: u64 },
39
40    /// A time from which to start syncing L1 blocks.
41    ///
42    /// This allows a validator to specify a future time at which the network should start. The
43    /// network will start syncing from the first L1 block with timestamp greater than or equal to
44    /// this, once said block is finalized.
45    Timestamp { timestamp: Timestamp },
46}
47
48/// Genesis of an Espresso chain.
49#[derive(Clone, Debug, Deserialize, Serialize)]
50pub struct Genesis {
51    #[serde(with = "version_ser")]
52    pub base_version: Version,
53    #[serde(with = "version_ser")]
54    pub upgrade_version: Version,
55    #[serde(with = "version_ser")]
56    pub genesis_version: Version,
57    pub epoch_height: Option<u64>,
58    pub drb_difficulty: Option<u64>,
59    pub drb_upgrade_difficulty: Option<u64>,
60    pub epoch_start_block: Option<u64>,
61    pub stake_table_capacity: Option<usize>,
62    pub chain_config: ChainConfig,
63    pub stake_table: StakeTableConfig,
64    #[serde(default)]
65    pub accounts: HashMap<FeeAccount, FeeAmount>,
66    pub l1_finalized: L1Finalized,
67    pub header: GenesisHeader,
68    #[serde(rename = "upgrade", with = "upgrade_ser")]
69    #[serde(default)]
70    pub upgrades: BTreeMap<Version, Upgrade>,
71    #[serde(default)]
72    pub da_committees: Option<Vec<VersionedDaCommittee<SeqTypes>>>,
73}
74
75impl Genesis {
76    pub fn max_base_fee(&self) -> FeeAmount {
77        let mut base_fee = self.chain_config.base_fee;
78
79        let upgrades: Vec<&Upgrade> = self.upgrades.values().collect();
80
81        for upgrade in upgrades {
82            let chain_config = upgrade.upgrade_type.chain_config();
83
84            if let Some(cf) = chain_config {
85                base_fee = std::cmp::max(cf.base_fee, base_fee);
86            }
87        }
88
89        base_fee
90    }
91}
92
93impl Genesis {
94    pub async fn validate_fee_contract(&self, l1: &L1Client) -> anyhow::Result<()> {
95        if let Some(fee_contract_address) = self.chain_config.fee_contract {
96            tracing::info!("validating fee contract at {fee_contract_address:x}");
97
98            if !l1
99                .retry_on_all_providers(|| l1.is_proxy_contract(fee_contract_address))
100                .await
101                .context("checking if fee contract is a proxy")?
102            {
103                anyhow::bail!("Fee contract address {fee_contract_address:x} is not a proxy");
104            }
105        }
106
107        // now iterate over each upgrade type and validate the fee contract if it exists
108        for (version, upgrade) in &self.upgrades {
109            let chain_config = &upgrade.upgrade_type.chain_config();
110
111            if chain_config.is_none() {
112                continue;
113            }
114
115            let chain_config = chain_config.unwrap();
116
117            if let Some(fee_contract_address) = chain_config.fee_contract {
118                if fee_contract_address == Address::default() {
119                    anyhow::bail!("Fee contract cannot use the zero address");
120                } else if !l1
121                    .retry_on_all_providers(|| l1.is_proxy_contract(fee_contract_address))
122                    .await
123                    .context(format!(
124                        "checking if fee contract is a proxy in upgrade {version}",
125                    ))?
126                {
127                    anyhow::bail!("Fee contract's address is not a proxy");
128                }
129            } else {
130                // The Fee Contract address has to be provided for an upgrade so return an error
131                anyhow::bail!("Fee contract's address for the upgrade is missing");
132            }
133        }
134        // TODO: it's optional for the fee contract to be included in a proxy in v1 so no need to panic but revisit this after v1 https://github.com/EspressoSystems/espresso-sequencer/pull/2000#discussion_r1765174702
135        Ok(())
136    }
137}
138
139mod upgrade_ser {
140
141    use std::{collections::BTreeMap, fmt};
142
143    use espresso_types::{
144        v0_1::{TimeBasedUpgrade, UpgradeMode, ViewBasedUpgrade},
145        Upgrade, UpgradeType,
146    };
147    use serde::{
148        de::{self, SeqAccess, Visitor},
149        ser::SerializeSeq,
150        Deserialize, Deserializer, Serialize, Serializer,
151    };
152    use vbs::version::Version;
153
154    pub fn serialize<S>(map: &BTreeMap<Version, Upgrade>, serializer: S) -> Result<S::Ok, S::Error>
155    where
156        S: Serializer,
157    {
158        #[derive(Debug, Clone, Serialize, Deserialize)]
159        pub struct Fields {
160            pub version: String,
161            #[serde(flatten)]
162            pub mode: UpgradeMode,
163            #[serde(flatten)]
164            pub upgrade_type: UpgradeType,
165        }
166
167        let mut seq = serializer.serialize_seq(Some(map.len()))?;
168        for (version, upgrade) in map {
169            seq.serialize_element(&Fields {
170                version: version.to_string(),
171                mode: upgrade.mode.clone(),
172                upgrade_type: upgrade.upgrade_type.clone(),
173            })?
174        }
175        seq.end()
176    }
177
178    pub fn deserialize<'de, D>(deserializer: D) -> Result<BTreeMap<Version, Upgrade>, D::Error>
179    where
180        D: Deserializer<'de>,
181    {
182        struct VecToHashMap;
183
184        #[derive(Debug, Clone, Serialize, Deserialize)]
185        pub struct Fields {
186            pub version: String,
187            // If both `time_based` and `view_based` fields are provided
188            // and we use an enum for deserialization, then one of the variant fields will be ignored.
189            // We want to raise an error in such a case to avoid ambiguity
190            #[serde(flatten)]
191            pub time_based: Option<TimeBasedUpgrade>,
192            #[serde(flatten)]
193            pub view_based: Option<ViewBasedUpgrade>,
194            #[serde(flatten)]
195            pub upgrade_type: UpgradeType,
196        }
197
198        impl<'de> Visitor<'de> for VecToHashMap {
199            type Value = BTreeMap<Version, Upgrade>;
200
201            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
202                formatter.write_str("a vector of tuples (key-value pairs)")
203            }
204
205            fn visit_seq<A>(self, mut seq: A) -> Result<BTreeMap<Version, Upgrade>, A::Error>
206            where
207                A: SeqAccess<'de>,
208            {
209                let mut map = BTreeMap::new();
210
211                while let Some(fields) = seq.next_element::<Fields>()? {
212                    // add try_from in Version
213                    let version: Vec<_> = fields.version.split('.').collect();
214
215                    let version = Version {
216                        major: version[0]
217                            .parse()
218                            .map_err(|_| de::Error::custom("invalid version format"))?,
219                        minor: version[1]
220                            .parse()
221                            .map_err(|_| de::Error::custom("invalid version format"))?,
222                    };
223
224                    match (fields.time_based, fields.view_based) {
225                        (Some(_), Some(_)) => {
226                            return Err(de::Error::custom(
227                                "both view and time mode parameters are set",
228                            ))
229                        },
230                        (None, None) => {
231                            return Err(de::Error::custom(
232                                "no view or time mode parameters provided",
233                            ))
234                        },
235                        (None, Some(v)) => {
236                            if v.start_proposing_view > v.stop_proposing_view {
237                                return Err(de::Error::custom(
238                                    "stop_proposing_view is less than start_proposing_view",
239                                ));
240                            }
241
242                            map.insert(
243                                version,
244                                Upgrade {
245                                    mode: UpgradeMode::View(v),
246                                    upgrade_type: fields.upgrade_type,
247                                },
248                            );
249                        },
250                        (Some(t), None) => {
251                            if t.start_proposing_time.unix_timestamp()
252                                > t.stop_proposing_time.unix_timestamp()
253                            {
254                                return Err(de::Error::custom(
255                                    "stop_proposing_time is less than start_proposing_time",
256                                ));
257                            }
258
259                            map.insert(
260                                version,
261                                Upgrade {
262                                    mode: UpgradeMode::Time(t),
263                                    upgrade_type: fields.upgrade_type.clone(),
264                                },
265                            );
266                        },
267                    }
268                }
269
270                Ok(map)
271            }
272        }
273
274        deserializer.deserialize_seq(VecToHashMap)
275    }
276}
277
278impl Genesis {
279    pub fn to_file(&self, path: impl AsRef<Path>) -> anyhow::Result<()> {
280        let toml = toml::to_string_pretty(self)?;
281        std::fs::write(path, toml.as_bytes())?;
282        Ok(())
283    }
284
285    pub fn from_file(path: impl AsRef<Path>) -> anyhow::Result<Self> {
286        let path = path.as_ref();
287        let bytes = std::fs::read(path).context(format!("genesis file {}", path.display()))?;
288        let text = std::str::from_utf8(&bytes).context("genesis file must be UTF-8")?;
289
290        toml::from_str(text).context("malformed genesis file")
291    }
292}
293
294#[cfg(test)]
295mod test {
296    use std::sync::Arc;
297
298    use alloy::{
299        node_bindings::Anvil,
300        primitives::{B256, U256},
301        providers::{layers::AnvilProvider, ProviderBuilder},
302    };
303    use espresso_contract_deployer::{self as deployer, Contracts};
304    use espresso_types::{
305        L1BlockInfo, TimeBasedUpgrade, Timestamp, UpgradeMode, UpgradeType, ViewBasedUpgrade,
306    };
307    use sequencer_utils::ser::FromStringOrInteger;
308    use toml::toml;
309
310    use super::*;
311
312    #[test]
313    fn test_genesis_from_toml_with_optional_fields() {
314        let toml = toml! {
315            base_version = "0.1"
316            upgrade_version = "0.2"
317            genesis_version = "0.1"
318
319            [stake_table]
320            capacity = 10
321
322            [chain_config]
323            chain_id = 12345
324            max_block_size = 30000
325            base_fee = 1
326            fee_recipient = "0x0000000000000000000000000000000000000000"
327            fee_contract = "0x0000000000000000000000000000000000000000"
328
329            [header]
330            timestamp = 123456
331
332            [header.chain_config]
333            chain_id = 35353
334            max_block_size = 30720
335            base_fee = 0
336            fee_recipient = "0x0000000000000000000000000000000000000000"
337
338            [accounts]
339            "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
340            "0x0000000000000000000000000000000000000000" = 42
341
342            [l1_finalized]
343            number = 64
344            timestamp = "0x123def"
345            hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
346        }
347        .to_string();
348
349        let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
350        assert_eq!(genesis.genesis_version, Version { major: 0, minor: 1 });
351        assert_eq!(genesis.stake_table, StakeTableConfig { capacity: 10 });
352        assert_eq!(
353            genesis.chain_config,
354            ChainConfig {
355                chain_id: 12345.into(),
356                max_block_size: 30000.into(),
357                base_fee: 1.into(),
358                fee_recipient: FeeAccount::default(),
359                fee_contract: Some(Address::default()),
360                stake_table_contract: None
361            }
362        );
363        assert_eq!(
364            genesis.header,
365            GenesisHeader {
366                timestamp: Timestamp::from_integer(123456).unwrap(),
367                chain_config: ChainConfig::default(),
368            }
369        );
370        assert_eq!(
371            genesis.accounts,
372            [
373                (
374                    FeeAccount::from(Address::from([
375                        0x23, 0x61, 0x8e, 0x81, 0xe3, 0xf5, 0xcd, 0xf7, 0xf5, 0x4c, 0x3d, 0x65,
376                        0xf7, 0xfb, 0xc0, 0xab, 0xf5, 0xb2, 0x1e, 0x8f
377                    ])),
378                    100000.into()
379                ),
380                (FeeAccount::default(), 42.into())
381            ]
382            .into_iter()
383            .collect::<HashMap<_, _>>()
384        );
385        assert_eq!(
386            genesis.l1_finalized,
387            L1Finalized::Block(L1BlockInfo {
388                number: 64,
389                timestamp: U256::from(0x123def),
390                // Can't do B256 here directly because it's the wrong endianness
391                hash: B256::from([
392                    0x80, 0xf5, 0xdd, 0x11, 0xf2, 0xbd, 0xda, 0x28, 0x14, 0xcb, 0x1a, 0xd9, 0x4e,
393                    0xf3, 0x0a, 0x47, 0xde, 0x02, 0xcf, 0x28, 0xad, 0x68, 0xc8, 0x9e, 0x10, 0x4c,
394                    0x00, 0xc4, 0xe5, 0x1b, 0xb7, 0xa5
395                ])
396            })
397        );
398    }
399
400    #[test]
401    fn test_genesis_from_toml_without_optional_fields() {
402        let toml = toml! {
403            base_version = "0.1"
404            upgrade_version = "0.2"
405            genesis_version = "0.1"
406
407            [stake_table]
408            capacity = 10
409
410            [chain_config]
411            chain_id = 12345
412            max_block_size = 30000
413            base_fee = 1
414            fee_recipient = "0x0000000000000000000000000000000000000000"
415
416            [header]
417            timestamp = 123456
418           [header.chain_config]
419            chain_id = 35353
420            max_block_size = 30720
421            base_fee = 0
422            fee_recipient = "0x0000000000000000000000000000000000000000"
423
424            [l1_finalized]
425            number = 0
426        }
427        .to_string();
428
429        let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
430
431        assert_eq!(genesis.stake_table, StakeTableConfig { capacity: 10 });
432        assert_eq!(
433            genesis.chain_config,
434            ChainConfig {
435                chain_id: 12345.into(),
436                max_block_size: 30000.into(),
437                base_fee: 1.into(),
438                fee_recipient: FeeAccount::default(),
439                fee_contract: None,
440                stake_table_contract: None,
441            }
442        );
443        assert_eq!(
444            genesis.header,
445            GenesisHeader {
446                timestamp: Timestamp::from_integer(123456).unwrap(),
447                chain_config: ChainConfig::default(),
448            }
449        );
450        assert_eq!(genesis.accounts, HashMap::default());
451        assert_eq!(genesis.l1_finalized, L1Finalized::Number { number: 0 });
452    }
453
454    #[test]
455    fn test_genesis_l1_finalized_number_only() {
456        let toml = toml! {
457            base_version = "0.1"
458            upgrade_version = "0.2"
459            genesis_version = "0.1"
460
461            [stake_table]
462            capacity = 10
463
464            [chain_config]
465            chain_id = 12345
466            max_block_size = 30000
467            base_fee = 1
468            fee_recipient = "0x0000000000000000000000000000000000000000"
469
470            [header]
471            timestamp = 123456
472
473            [header.chain_config]
474            chain_id = 35353
475            max_block_size = 30720
476            base_fee = 0
477            fee_recipient = "0x0000000000000000000000000000000000000000"
478
479            [l1_finalized]
480            number = 42
481        }
482        .to_string();
483
484        let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
485        assert_eq!(genesis.l1_finalized, L1Finalized::Number { number: 42 });
486    }
487
488    #[test]
489    fn test_genesis_l1_finalized_timestamp_only() {
490        let toml = toml! {
491            base_version = "0.1"
492            upgrade_version = "0.2"
493            genesis_version = "0.1"
494
495            [stake_table]
496            capacity = 10
497
498            [chain_config]
499            chain_id = 12345
500            max_block_size = 30000
501            base_fee = 1
502            fee_recipient = "0x0000000000000000000000000000000000000000"
503
504            [header]
505            timestamp = 123456
506
507            [header.chain_config]
508            chain_id = 35353
509            max_block_size = 30720
510            base_fee = 0
511            fee_recipient = "0x0000000000000000000000000000000000000000"
512
513            [l1_finalized]
514            timestamp = "2024-01-02T00:00:00Z"
515        }
516        .to_string();
517
518        let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
519        assert_eq!(
520            genesis.l1_finalized,
521            L1Finalized::Timestamp {
522                timestamp: Timestamp::from_string("2024-01-02T00:00:00Z".to_string()).unwrap()
523            }
524        );
525    }
526
527    // tests for fee contract not being a proxy are removed, since we now only have one function in `deployer.rs` that ensures
528    // deploying of the fee contract behind proxy, and this function is being unit tested there.
529    // Here, we primarily focus on testing the config and validation logic, not deployment logic.
530
531    #[test_log::test(tokio::test(flavor = "multi_thread"))]
532    async fn test_genesis_fee_contract_is_a_proxy() -> anyhow::Result<()> {
533        let anvil = Arc::new(Anvil::new().spawn());
534        let wallet = anvil.wallet().unwrap();
535        let admin = wallet.default_signer().address();
536        let inner_provider = ProviderBuilder::new()
537            .wallet(wallet)
538            .connect_http(anvil.endpoint_url());
539        let provider = AnvilProvider::new(inner_provider, Arc::clone(&anvil));
540        let mut contracts = Contracts::new();
541
542        let proxy_addr =
543            deployer::deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?;
544
545        let toml = format!(
546            r#"
547            base_version = "0.1"
548            upgrade_version = "0.2"
549            genesis_version = "0.1"
550
551            [stake_table]
552            capacity = 10
553
554            [chain_config]
555            chain_id = 12345
556            max_block_size = 30000
557            base_fee = 1
558            fee_recipient = "0x0000000000000000000000000000000000000000"
559            fee_contract = "{proxy_addr:?}"
560
561            [header]
562            timestamp = 123456
563
564            [header.chain_config]
565            chain_id = 35353
566            max_block_size = 30720
567            base_fee = 0
568            fee_recipient = "0x0000000000000000000000000000000000000000"
569
570            [l1_finalized]
571            number = 42
572        "#,
573        )
574        .to_string();
575
576        let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
577
578        // Call the validation logic for the fee_contract address
579        let result = genesis
580            .validate_fee_contract(&L1Client::anvil(&anvil).unwrap())
581            .await;
582
583        assert!(
584            result.is_ok(),
585            "Expected Fee Contract to be a proxy, but it was not"
586        );
587        Ok(())
588    }
589
590    #[test_log::test(tokio::test(flavor = "multi_thread"))]
591    async fn test_genesis_fee_contract_is_a_proxy_with_upgrades() -> anyhow::Result<()> {
592        let anvil = Arc::new(Anvil::new().spawn());
593        let wallet = anvil.wallet().unwrap();
594        let admin = wallet.default_signer().address();
595        let inner_provider = ProviderBuilder::new()
596            .wallet(wallet)
597            .connect_http(anvil.endpoint_url());
598        let provider = AnvilProvider::new(inner_provider, Arc::clone(&anvil));
599        let mut contracts = Contracts::new();
600
601        let proxy_addr =
602            deployer::deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?;
603
604        let toml = format!(
605            r#"
606            base_version = "0.1"
607            upgrade_version = "0.2"
608            genesis_version = "0.1"
609
610            [stake_table]
611            capacity = 10
612
613            [chain_config]
614            chain_id = 12345
615            max_block_size = 30000
616            base_fee = 1
617            fee_recipient = "0x0000000000000000000000000000000000000000"
618
619            [header]
620            timestamp = 123456
621
622            [header.chain_config]
623            chain_id = 35353
624            max_block_size = 30720
625            base_fee = 0
626            fee_recipient = "0x0000000000000000000000000000000000000000"
627
628            [l1_finalized]
629            number = 42
630
631            [[upgrade]]
632            version = "0.2"
633            start_proposing_view = 5
634            stop_proposing_view = 15
635
636            [upgrade.fee]
637
638            [upgrade.fee.chain_config]
639            chain_id = 12345
640            max_block_size = 30000
641            base_fee = 1
642            fee_recipient = "0x0000000000000000000000000000000000000000"
643            fee_contract = "{proxy_addr:?}"
644
645           
646        "#,
647        )
648        .to_string();
649
650        let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
651
652        // Call the validation logic for the fee_contract address
653        let result = genesis
654            .validate_fee_contract(&L1Client::anvil(&anvil).unwrap())
655            .await;
656
657        assert!(
658            result.is_ok(),
659            "Expected Fee Contract to be a proxy, but it was not"
660        );
661        Ok(())
662    }
663
664    #[test_log::test(tokio::test(flavor = "multi_thread"))]
665    async fn test_genesis_missing_fee_contract_with_upgrades() {
666        let toml = toml! {
667            base_version = "0.1"
668            upgrade_version = "0.2"
669            genesis_version = "0.1"
670
671            [stake_table]
672            capacity = 10
673
674            [chain_config]
675            chain_id = 12345
676            max_block_size = 30000
677            base_fee = 1
678            fee_recipient = "0x0000000000000000000000000000000000000000"
679
680            [header]
681            timestamp = 123456
682
683            [header.chain_config]
684            chain_id = 35353
685            max_block_size = 30720
686            base_fee = 0
687            fee_recipient = "0x0000000000000000000000000000000000000000"
688
689            [l1_finalized]
690            number = 42
691
692            [[upgrade]]
693            version = "0.2"
694            start_proposing_view = 5
695            stop_proposing_view = 15
696
697            [upgrade.fee]
698
699            [upgrade.fee.chain_config]
700            chain_id = 12345
701            max_block_size = 30000
702            base_fee = 1
703            fee_recipient = "0x0000000000000000000000000000000000000000"
704
705            [[upgrade]]
706            version = "0.3"
707            start_proposing_view = 5
708            stop_proposing_view = 15
709
710            [upgrade.epoch]
711            [upgrade.epoch.chain_config]
712            chain_id = 999999999
713            max_block_size = 3000
714            base_fee = 1
715            fee_recipient = "0x0000000000000000000000000000000000000000"
716            bid_recipient = "0x0000000000000000000000000000000000000000"
717            fee_contract = "0xa15bb66138824a1c7167f5e85b957d04dd34e468" //not a proxy
718        }
719        .to_string();
720
721        let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
722        let rpc_url = "https://ethereum-sepolia.publicnode.com";
723
724        // validate the fee_contract address
725        let result = genesis
726            .validate_fee_contract(&L1Client::new(vec![rpc_url.parse().unwrap()]).unwrap())
727            .await;
728
729        // check if the result from the validation is an error
730        if let Err(e) = result {
731            // assert that the error message contains "Fee contract's address is not a proxy"
732            assert!(e
733                .to_string()
734                .contains("Fee contract's address for the upgrade is missing"));
735        } else {
736            panic!("Expected the fee contract to be missing, but the validation succeeded");
737        }
738    }
739
740    #[test_log::test(tokio::test(flavor = "multi_thread"))]
741    async fn test_genesis_upgrade_fee_contract_address_is_zero() {
742        let toml = toml! {
743            base_version = "0.1"
744            upgrade_version = "0.2"
745            genesis_version = "0.1"
746
747            [stake_table]
748            capacity = 10
749
750            [chain_config]
751            chain_id = 12345
752            max_block_size = 30000
753            base_fee = 1
754            fee_recipient = "0x0000000000000000000000000000000000000000"
755
756            [header]
757            timestamp = 123456
758
759            [header.chain_config]
760            chain_id = 35353
761            max_block_size = 30720
762            base_fee = 0
763            fee_recipient = "0x0000000000000000000000000000000000000000"
764
765            [l1_finalized]
766            number = 42
767
768            [[upgrade]]
769            version = "0.2"
770            start_proposing_view = 5
771            stop_proposing_view = 15
772
773            [upgrade.fee]
774            [upgrade.fee.chain_config]
775            chain_id = 12345
776            max_block_size = 30000
777            base_fee = 1
778            fee_recipient = "0x0000000000000000000000000000000000000000"
779            fee_contract = "0x0000000000000000000000000000000000000000"
780        }
781        .to_string();
782
783        let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
784        let rpc_url = "https://ethereum-sepolia.publicnode.com";
785
786        // validate the fee_contract address
787        let result = genesis
788            .validate_fee_contract(&L1Client::new(vec![rpc_url.parse().unwrap()]).unwrap())
789            .await;
790
791        // check if the result from the validation is an error
792        if let Err(e) = result {
793            // assert that the error message contains "Fee contract's address is not a proxy"
794            assert!(e
795                .to_string()
796                .contains("Fee contract cannot use the zero address"));
797        } else {
798            panic!(
799                "Expected the fee contract to complain about the zero address but the validation \
800                 succeeded"
801            );
802        }
803    }
804
805    #[test_log::test(tokio::test(flavor = "multi_thread"))]
806    async fn test_genesis_fee_contract_l1_failover() -> anyhow::Result<()> {
807        let anvil = Arc::new(Anvil::new().spawn());
808        let wallet = anvil.wallet().unwrap();
809        let admin = wallet.default_signer().address();
810        let inner_provider = ProviderBuilder::new()
811            .wallet(wallet)
812            .connect_http(anvil.endpoint_url());
813        let provider = AnvilProvider::new(inner_provider, Arc::clone(&anvil));
814        let mut contracts = Contracts::new();
815
816        let proxy_addr =
817            deployer::deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?;
818
819        let toml = format!(
820            r#"
821            base_version = "0.1"
822            upgrade_version = "0.2"
823            genesis_version = "0.1"
824
825            [stake_table]
826            capacity = 10
827
828            [chain_config]
829            chain_id = 12345
830            max_block_size = 30000
831            base_fee = 1
832            fee_recipient = "0x0000000000000000000000000000000000000000"
833            fee_contract = "{proxy_addr:?}"
834
835            [header]
836            timestamp = 123456
837
838            [header.chain_config]
839            chain_id = 35353
840            max_block_size = 30720
841            base_fee = 0
842            fee_recipient = "0x0000000000000000000000000000000000000000"
843
844            [l1_finalized]
845            number = 42
846        "#
847        )
848        .to_string();
849
850        let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
851        genesis
852            .validate_fee_contract(
853                &L1Client::new(vec![
854                    "http://notareall1provider".parse().unwrap(),
855                    anvil.endpoint().parse().unwrap(),
856                ])
857                .unwrap(),
858            )
859            .await
860            .unwrap();
861
862        Ok(())
863    }
864
865    #[test]
866    fn test_genesis_from_toml_units() {
867        let toml = toml! {
868            base_version = "0.1"
869            upgrade_version = "0.2"
870            genesis_version = "0.1"
871
872            [stake_table]
873            capacity = 10
874
875            [chain_config]
876            chain_id = 12345
877            max_block_size = "30mb"
878            base_fee = "1 gwei"
879            fee_recipient = "0x0000000000000000000000000000000000000000"
880
881            [header]
882            timestamp = "2024-05-16T11:20:28-04:00"
883
884
885
886           [header.chain_config]
887            chain_id = 35353
888            max_block_size = 30720
889            base_fee = 0
890            fee_recipient = "0x0000000000000000000000000000000000000000"
891
892            [l1_finalized]
893            number = 0
894        }
895        .to_string();
896
897        let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
898        assert_eq!(genesis.stake_table, StakeTableConfig { capacity: 10 });
899        assert_eq!(*genesis.chain_config.max_block_size, 30000000);
900        assert_eq!(genesis.chain_config.base_fee, 1_000_000_000.into());
901        assert_eq!(
902            genesis.header,
903            GenesisHeader {
904                timestamp: Timestamp::from_integer(1715872828).unwrap(),
905                chain_config: ChainConfig::default(),
906            }
907        )
908    }
909
910    #[test]
911    fn test_genesis_toml_fee_upgrade_view_mode() {
912        // without optional fields
913        // with view settings
914        let toml = toml! {
915            base_version = "0.1"
916            upgrade_version = "0.2"
917            genesis_version = "0.1"
918
919            [stake_table]
920            capacity = 10
921
922            [chain_config]
923            chain_id = 12345
924            max_block_size = 30000
925            base_fee = 1
926            fee_recipient = "0x0000000000000000000000000000000000000000"
927            fee_contract = "0x0000000000000000000000000000000000000000"
928
929            [header]
930            timestamp = 123456
931
932            [header.chain_config]
933            chain_id = 35353
934            max_block_size = 30720
935            base_fee = 0
936            fee_recipient = "0x0000000000000000000000000000000000000000"
937
938            [accounts]
939            "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
940            "0x0000000000000000000000000000000000000000" = 42
941
942            [l1_finalized]
943            number = 64
944            timestamp = "0x123def"
945            hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
946
947            [[upgrade]]
948            version = "0.2"
949            start_proposing_view = 1
950            stop_proposing_view = 15
951
952            [upgrade.fee]
953
954            [upgrade.fee.chain_config]
955            chain_id = 12345
956            max_block_size = 30000
957            base_fee = 1
958            fee_recipient = "0x0000000000000000000000000000000000000000"
959            fee_contract = "0x0000000000000000000000000000000000000000"
960        }
961        .to_string();
962
963        let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
964
965        let (version, genesis_upgrade) = genesis.upgrades.last_key_value().unwrap();
966        println!("{genesis_upgrade:?}");
967
968        assert_eq!(*version, Version { major: 0, minor: 2 });
969
970        let upgrade = Upgrade {
971            mode: UpgradeMode::View(ViewBasedUpgrade {
972                start_voting_view: None,
973                stop_voting_view: None,
974                start_proposing_view: 1,
975                stop_proposing_view: 15,
976            }),
977            upgrade_type: UpgradeType::Fee {
978                chain_config: genesis.chain_config,
979            },
980        };
981
982        assert_eq!(*genesis_upgrade, upgrade);
983    }
984
985    #[test]
986    fn test_genesis_toml_fee_upgrade_time_mode() {
987        // without optional fields
988        // with time settings
989        let toml = toml! {
990            base_version = "0.1"
991            upgrade_version = "0.2"
992            genesis_version = "0.1"
993
994            [stake_table]
995            capacity = 10
996
997            [chain_config]
998            chain_id = 12345
999            max_block_size = 30000
1000            base_fee = 1
1001            fee_recipient = "0x0000000000000000000000000000000000000000"
1002            fee_contract = "0x0000000000000000000000000000000000000000"
1003
1004            [header]
1005            timestamp = 123456
1006
1007            [header.chain_config]
1008            chain_id = 35353
1009            max_block_size = 30720
1010            base_fee = 0
1011            fee_recipient = "0x0000000000000000000000000000000000000000"
1012
1013            [accounts]
1014            "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
1015            "0x0000000000000000000000000000000000000000" = 42
1016
1017            [l1_finalized]
1018            number = 64
1019            timestamp = "0x123def"
1020            hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1021
1022            [[upgrade]]
1023            version = "0.2"
1024            start_proposing_time = "2024-01-01T00:00:00Z"
1025            stop_proposing_time = "2024-01-02T00:00:00Z"
1026
1027            [upgrade.fee]
1028
1029            [upgrade.fee.chain_config]
1030            chain_id = 12345
1031            max_block_size = 30000
1032            base_fee = 1
1033            fee_recipient = "0x0000000000000000000000000000000000000000"
1034            fee_contract = "0x0000000000000000000000000000000000000000"
1035        }
1036        .to_string();
1037
1038        let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
1039
1040        let (version, genesis_upgrade) = genesis.upgrades.last_key_value().unwrap();
1041
1042        assert_eq!(*version, Version { major: 0, minor: 2 });
1043
1044        let upgrade = Upgrade {
1045            mode: UpgradeMode::Time(TimeBasedUpgrade {
1046                start_voting_time: None,
1047                stop_voting_time: None,
1048                start_proposing_time: Timestamp::from_string("2024-01-01T00:00:00Z".to_string())
1049                    .unwrap(),
1050                stop_proposing_time: Timestamp::from_string("2024-01-02T00:00:00Z".to_string())
1051                    .unwrap(),
1052            }),
1053            upgrade_type: UpgradeType::Fee {
1054                chain_config: genesis.chain_config,
1055            },
1056        };
1057
1058        assert_eq!(*genesis_upgrade, upgrade);
1059    }
1060
1061    #[test]
1062    fn test_genesis_toml_fee_upgrade_view_and_time_mode() {
1063        // set both time and view parameters
1064        // this should err
1065        let toml = toml! {
1066            base_version = "0.1"
1067            upgrade_version = "0.2"
1068            genesis_version = "0.1"
1069
1070            [stake_table]
1071            capacity = 10
1072
1073            [chain_config]
1074            chain_id = 12345
1075            max_block_size = 30000
1076            base_fee = 1
1077            fee_recipient = "0x0000000000000000000000000000000000000000"
1078            fee_contract = "0x0000000000000000000000000000000000000000"
1079
1080            [header]
1081            timestamp = 123456
1082
1083            [header.chain_config]
1084            chain_id = 35353
1085            max_block_size = 30720
1086            base_fee = 0
1087            fee_recipient = "0x0000000000000000000000000000000000000000"
1088
1089            [accounts]
1090            "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
1091            "0x0000000000000000000000000000000000000000" = 42
1092
1093            [l1_finalized]
1094            number = 64
1095            timestamp = "0x123def"
1096            hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1097
1098            [[upgrade]]
1099            version = "0.2"
1100            start_proposing_view = 1
1101            stop_proposing_view = 10
1102            start_proposing_time = 1
1103            stop_proposing_time = 10
1104
1105            [upgrade.fee]
1106
1107            [upgrade.fee.chain_config]
1108            chain_id = 12345
1109            max_block_size = 30000
1110            base_fee = 1
1111            fee_recipient = "0x0000000000000000000000000000000000000000"
1112            fee_contract = "0x0000000000000000000000000000000000000000"
1113        }
1114        .to_string();
1115
1116        toml::from_str::<Genesis>(&toml).unwrap_err();
1117    }
1118
1119    #[test]
1120    fn test_fee_and_epoch_upgrade_toml() {
1121        let toml = toml! {
1122            base_version = "0.1"
1123            upgrade_version = "0.2"
1124            genesis_version = "0.1"
1125            epoch_height = 20
1126            drb_difficulty = 10
1127            drb_upgrade_difficulty = 20
1128            epoch_start_block = 1
1129            stake_table_capacity = 200
1130
1131            [stake_table]
1132            capacity = 10
1133
1134            [chain_config]
1135            chain_id = 12345
1136            max_block_size = 30000
1137            base_fee = 1
1138            fee_recipient = "0x0000000000000000000000000000000000000000"
1139            fee_contract = "0x0000000000000000000000000000000000000000"
1140
1141            [header]
1142            timestamp = 123456
1143
1144         [header.chain_config]
1145            chain_id = 35353
1146            max_block_size = 30720
1147            base_fee = 0
1148            fee_recipient = "0x0000000000000000000000000000000000000000"
1149
1150            [accounts]
1151            "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
1152            "0x0000000000000000000000000000000000000000" = 42
1153
1154            [l1_finalized]
1155            number = 64
1156            timestamp = "0x123def"
1157            hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1158
1159            [[upgrade]]
1160            version = "0.3"
1161            start_proposing_view = 1
1162            stop_proposing_view = 10
1163
1164            [upgrade.epoch]
1165            [upgrade.epoch.chain_config]
1166            chain_id = 12345
1167            max_block_size = 30000
1168            base_fee = 1
1169            fee_recipient = "0x0000000000000000000000000000000000000000"
1170            fee_contract = "0x0000000000000000000000000000000000000000"
1171            stake_table_contract = "0x0000000000000000000000000000000000000000"
1172
1173            [[upgrade]]
1174            version = "0.2"
1175            start_proposing_view = 1
1176            stop_proposing_view = 15
1177
1178            [upgrade.fee]
1179
1180            [upgrade.fee.chain_config]
1181            chain_id = 12345
1182            max_block_size = 30000
1183            base_fee = 1
1184            fee_recipient = "0x0000000000000000000000000000000000000000"
1185            fee_contract = "0x0000000000000000000000000000000000000000"
1186        }
1187        .to_string();
1188
1189        toml::from_str::<Genesis>(&toml).unwrap();
1190    }
1191
1192    #[test]
1193    fn test_genesis_chain_config() {
1194        let toml = toml! {
1195            base_version = "0.1"
1196            upgrade_version = "0.2"
1197            genesis_version = "0.2"
1198            epoch_height = 20
1199            drb_difficulty = 10
1200            drb_upgrade_difficulty = 20
1201            epoch_start_block = 1
1202            stake_table_capacity = 200
1203
1204            [stake_table]
1205            capacity = 10
1206
1207            [genesis_chain_config]
1208            chain_id = 33
1209            max_block_size = 5000
1210            base_fee = 1
1211            fee_recipient = "0x0000000000000000000000000000000000000000"
1212            fee_contract = "0x0000000000000000000000000000000000000000"
1213
1214            [chain_config]
1215            chain_id = 12345
1216            max_block_size = 30000
1217            base_fee = 1
1218            fee_recipient = "0x0000000000000000000000000000000000000000"
1219            fee_contract = "0x0000000000000000000000000000000000000000"
1220
1221            [header]
1222            timestamp = 123456
1223
1224            [header.chain_config]
1225            chain_id = 33
1226            max_block_size = 5000
1227            base_fee = 1
1228            fee_recipient = "0x0000000000000000000000000000000000000000"
1229            fee_contract = "0x0000000000000000000000000000000000000000"
1230
1231            [accounts]
1232            "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
1233            "0x0000000000000000000000000000000000000000" = 42
1234
1235            [l1_finalized]
1236            number = 64
1237            timestamp = "0x123def"
1238            hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1239
1240            [[upgrade]]
1241            version = "0.3"
1242            start_proposing_view = 1
1243            stop_proposing_view = 10
1244
1245            [upgrade.epoch]
1246            [upgrade.epoch.chain_config]
1247            chain_id = 12345
1248            max_block_size = 30000
1249            base_fee = 1
1250            fee_recipient = "0x0000000000000000000000000000000000000000"
1251            fee_contract = "0x0000000000000000000000000000000000000000"
1252            stake_table_contract = "0x0000000000000000000000000000000000000000"
1253
1254            [[upgrade]]
1255            version = "0.2"
1256            start_proposing_view = 1
1257            stop_proposing_view = 15
1258
1259            [upgrade.fee]
1260
1261            [upgrade.fee.chain_config]
1262            chain_id = 12345
1263            max_block_size = 30000
1264            base_fee = 1
1265            fee_recipient = "0x0000000000000000000000000000000000000000"
1266            fee_contract = "0x0000000000000000000000000000000000000000"
1267        }
1268        .to_string();
1269
1270        let genesis = toml::from_str::<Genesis>(&toml).unwrap();
1271
1272        assert_eq!(genesis.header.chain_config.chain_id, 33.into());
1273        assert_eq!(genesis.chain_config.chain_id, 12345.into());
1274
1275        assert_eq!(genesis.header.chain_config.max_block_size, 5000.into());
1276        assert_eq!(genesis.chain_config.max_block_size, 30000.into());
1277    }
1278
1279    #[test]
1280    fn test_genesis_da_committees() {
1281        let toml = toml! {
1282            base_version = "0.1"
1283            upgrade_version = "0.5"
1284            genesis_version = "0.1"
1285            epoch_height = 20
1286            drb_difficulty = 10
1287            drb_upgrade_difficulty = 20
1288            epoch_start_block = 1
1289            stake_table_capacity = 200
1290
1291            [stake_table]
1292            capacity = 10
1293
1294            [chain_config]
1295            chain_id = 12345
1296            max_block_size = 30000
1297            base_fee = 1
1298            fee_recipient = "0x0000000000000000000000000000000000000000"
1299            fee_contract = "0x0000000000000000000000000000000000000000"
1300
1301            [l1_finalized]
1302            number = 64
1303            timestamp = "0x123def"
1304            hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1305
1306            [header]
1307            timestamp = 123456
1308
1309            [header.chain_config]
1310            chain_id = 33
1311            max_block_size = 5000
1312            base_fee = 1
1313            fee_recipient = "0x0000000000000000000000000000000000000000"
1314            fee_contract = "0x0000000000000000000000000000000000000000"
1315
1316            [[upgrade]]
1317            version = "0.5"
1318            start_proposing_view = 1
1319            stop_proposing_view = 15
1320
1321            [upgrade.da]
1322            [upgrade.da.chain_config]
1323            chain_id = 12345
1324            max_block_size = 30000
1325            base_fee = 1
1326            fee_recipient = "0x0000000000000000000000000000000000000000"
1327            fee_contract = "0x0000000000000000000000000000000000000000"
1328            stake_table_contract = "0x0000000000000000000000000000000000000000"
1329
1330            [[da_committees]]
1331            start_version = "0.5"
1332            start_epoch = 10
1333            committee = [
1334                { stake_table_entry = { stake_key = "BLS_VER_KEY~bQszS-QKYvUij2g20VqS8asttGSb95NrTu2PUj0uMh1CBUxNy1FqyPDjZqB29M7ZbjWqj79QkEOWkpga84AmDYUeTuWmy-0P1AdKHD3ehc-dKvei78BDj5USwXPJiDUlCxvYs_9rWYhagaq-5_LXENr78xel17spftNd5MA1Mw5U", stake_amount = "0x1"}, state_ver_key = "SCHNORR_VER_KEY~lJqDaVZyM0hWP2Br52IX5FeE-dCAIC-dPX7bL5-qUx-vjbunwe-ENOeZxj6FuOyvDCFzoGeP7yZ0fM995qF-CRE"},
1335                { stake_table_entry = { stake_key = "BLS_VER_KEY~bQszS-QKYvUij2g20VqS8asttGSb95NrTu2PUj0uMh1CBUxNy1FqyPDjZqB29M7ZbjWqj79QkEOWkpga84AmDYUeTuWmy-0P1AdKHD3ehc-dKvei78BDj5USwXPJiDUlCxvYs_9rWYhagaq-5_LXENr78xel17spftNd5MA1Mw5U", stake_amount = "0x1"}, state_ver_key = "SCHNORR_VER_KEY~lJqDaVZyM0hWP2Br52IX5FeE-dCAIC-dPX7bL5-qUx-vjbunwe-ENOeZxj6FuOyvDCFzoGeP7yZ0fM995qF-CRE"}
1336            ]
1337        }
1338        .to_string();
1339
1340        let genesis = toml::from_str::<Genesis>(&toml).unwrap();
1341
1342        let da_committees = genesis
1343            .da_committees
1344            .expect("DA committees should be present");
1345        assert_eq!(da_committees.len(), 1);
1346
1347        let da_committee = &da_committees[0];
1348
1349        assert_eq!(da_committee.start_version, Version { major: 0, minor: 5 });
1350        assert_eq!(da_committee.start_epoch, 10);
1351        assert_eq!(da_committee.committee.len(), 2);
1352        assert_eq!(
1353            da_committee.committee[0].stake_table_entry.stake_amount,
1354            U256::from(1)
1355        );
1356        assert_eq!(
1357            da_committee.committee[1].stake_table_entry.stake_amount,
1358            U256::from(1)
1359        );
1360    }
1361}