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, Timestamp,
10 Upgrade,
11};
12use serde::{Deserialize, Serialize};
13use vbs::version::Version;
14
15#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
17pub struct StakeTableConfig {
18 pub capacity: usize,
19}
20
21#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
23#[serde(untagged)]
24pub enum L1Finalized {
25 Block(L1BlockInfo),
31
32 Number { number: u64 },
38
39 Timestamp { timestamp: Timestamp },
45}
46
47#[derive(Clone, Debug, Deserialize, Serialize)]
49pub struct Genesis {
50 #[serde(with = "version_ser")]
51 pub base_version: Version,
52 #[serde(with = "version_ser")]
53 pub upgrade_version: Version,
54 #[serde(with = "version_ser")]
55 pub genesis_version: Version,
56 pub epoch_height: Option<u64>,
57 pub drb_difficulty: Option<u64>,
58 pub drb_upgrade_difficulty: Option<u64>,
59 pub epoch_start_block: Option<u64>,
60 pub stake_table_capacity: Option<usize>,
61 pub chain_config: ChainConfig,
62 pub stake_table: StakeTableConfig,
63 #[serde(default)]
64 pub accounts: HashMap<FeeAccount, FeeAmount>,
65 pub l1_finalized: L1Finalized,
66 pub header: GenesisHeader,
67 #[serde(rename = "upgrade", with = "upgrade_ser")]
68 #[serde(default)]
69 pub upgrades: BTreeMap<Version, Upgrade>,
70}
71
72impl Genesis {
73 pub fn max_base_fee(&self) -> FeeAmount {
74 let mut base_fee = self.chain_config.base_fee;
75
76 let upgrades: Vec<&Upgrade> = self.upgrades.values().collect();
77
78 for upgrade in upgrades {
79 let chain_config = upgrade.upgrade_type.chain_config();
80
81 if let Some(cf) = chain_config {
82 base_fee = std::cmp::max(cf.base_fee, base_fee);
83 }
84 }
85
86 base_fee
87 }
88}
89
90impl Genesis {
91 pub async fn validate_fee_contract(&self, l1: &L1Client) -> anyhow::Result<()> {
92 if let Some(fee_contract_address) = self.chain_config.fee_contract {
93 tracing::info!("validating fee contract at {fee_contract_address:x}");
94
95 if !l1
96 .retry_on_all_providers(|| l1.is_proxy_contract(fee_contract_address))
97 .await
98 .context("checking if fee contract is a proxy")?
99 {
100 anyhow::bail!("Fee contract address {fee_contract_address:x} is not a proxy");
101 }
102 }
103
104 for (version, upgrade) in &self.upgrades {
106 let chain_config = &upgrade.upgrade_type.chain_config();
107
108 if chain_config.is_none() {
109 continue;
110 }
111
112 let chain_config = chain_config.unwrap();
113
114 if let Some(fee_contract_address) = chain_config.fee_contract {
115 if fee_contract_address == Address::default() {
116 anyhow::bail!("Fee contract cannot use the zero address");
117 } else if !l1
118 .retry_on_all_providers(|| l1.is_proxy_contract(fee_contract_address))
119 .await
120 .context(format!(
121 "checking if fee contract is a proxy in upgrade {version}",
122 ))?
123 {
124 anyhow::bail!("Fee contract's address is not a proxy");
125 }
126 } else {
127 anyhow::bail!("Fee contract's address for the upgrade is missing");
129 }
130 }
131 Ok(())
133 }
134}
135
136mod version_ser {
137
138 use serde::{de, Deserialize, Deserializer, Serializer};
139 use vbs::version::Version;
140
141 pub fn serialize<S>(ver: &Version, serializer: S) -> Result<S::Ok, S::Error>
142 where
143 S: Serializer,
144 {
145 serializer.serialize_str(&ver.to_string())
146 }
147
148 pub fn deserialize<'de, D>(deserializer: D) -> Result<Version, D::Error>
149 where
150 D: Deserializer<'de>,
151 {
152 let version_str = String::deserialize(deserializer)?;
153
154 let version: Vec<_> = version_str.split('.').collect();
155
156 let version = Version {
157 major: version[0]
158 .parse()
159 .map_err(|_| de::Error::custom("invalid version format"))?,
160 minor: version[1]
161 .parse()
162 .map_err(|_| de::Error::custom("invalid version format"))?,
163 };
164
165 Ok(version)
166 }
167}
168
169mod upgrade_ser {
170
171 use std::{collections::BTreeMap, fmt};
172
173 use espresso_types::{
174 v0_1::{TimeBasedUpgrade, UpgradeMode, ViewBasedUpgrade},
175 Upgrade, UpgradeType,
176 };
177 use serde::{
178 de::{self, SeqAccess, Visitor},
179 ser::SerializeSeq,
180 Deserialize, Deserializer, Serialize, Serializer,
181 };
182 use vbs::version::Version;
183
184 pub fn serialize<S>(map: &BTreeMap<Version, Upgrade>, serializer: S) -> Result<S::Ok, S::Error>
185 where
186 S: Serializer,
187 {
188 #[derive(Debug, Clone, Serialize, Deserialize)]
189 pub struct Fields {
190 pub version: String,
191 #[serde(flatten)]
192 pub mode: UpgradeMode,
193 #[serde(flatten)]
194 pub upgrade_type: UpgradeType,
195 }
196
197 let mut seq = serializer.serialize_seq(Some(map.len()))?;
198 for (version, upgrade) in map {
199 seq.serialize_element(&Fields {
200 version: version.to_string(),
201 mode: upgrade.mode.clone(),
202 upgrade_type: upgrade.upgrade_type.clone(),
203 })?
204 }
205 seq.end()
206 }
207
208 pub fn deserialize<'de, D>(deserializer: D) -> Result<BTreeMap<Version, Upgrade>, D::Error>
209 where
210 D: Deserializer<'de>,
211 {
212 struct VecToHashMap;
213
214 #[derive(Debug, Clone, Serialize, Deserialize)]
215 pub struct Fields {
216 pub version: String,
217 #[serde(flatten)]
221 pub time_based: Option<TimeBasedUpgrade>,
222 #[serde(flatten)]
223 pub view_based: Option<ViewBasedUpgrade>,
224 #[serde(flatten)]
225 pub upgrade_type: UpgradeType,
226 }
227
228 impl<'de> Visitor<'de> for VecToHashMap {
229 type Value = BTreeMap<Version, Upgrade>;
230
231 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
232 formatter.write_str("a vector of tuples (key-value pairs)")
233 }
234
235 fn visit_seq<A>(self, mut seq: A) -> Result<BTreeMap<Version, Upgrade>, A::Error>
236 where
237 A: SeqAccess<'de>,
238 {
239 let mut map = BTreeMap::new();
240
241 while let Some(fields) = seq.next_element::<Fields>()? {
242 let version: Vec<_> = fields.version.split('.').collect();
244
245 let version = Version {
246 major: version[0]
247 .parse()
248 .map_err(|_| de::Error::custom("invalid version format"))?,
249 minor: version[1]
250 .parse()
251 .map_err(|_| de::Error::custom("invalid version format"))?,
252 };
253
254 match (fields.time_based, fields.view_based) {
255 (Some(_), Some(_)) => {
256 return Err(de::Error::custom(
257 "both view and time mode parameters are set",
258 ))
259 },
260 (None, None) => {
261 return Err(de::Error::custom(
262 "no view or time mode parameters provided",
263 ))
264 },
265 (None, Some(v)) => {
266 if v.start_proposing_view > v.stop_proposing_view {
267 return Err(de::Error::custom(
268 "stop_proposing_view is less than start_proposing_view",
269 ));
270 }
271
272 map.insert(
273 version,
274 Upgrade {
275 mode: UpgradeMode::View(v),
276 upgrade_type: fields.upgrade_type,
277 },
278 );
279 },
280 (Some(t), None) => {
281 if t.start_proposing_time.unix_timestamp()
282 > t.stop_proposing_time.unix_timestamp()
283 {
284 return Err(de::Error::custom(
285 "stop_proposing_time is less than start_proposing_time",
286 ));
287 }
288
289 map.insert(
290 version,
291 Upgrade {
292 mode: UpgradeMode::Time(t),
293 upgrade_type: fields.upgrade_type.clone(),
294 },
295 );
296 },
297 }
298 }
299
300 Ok(map)
301 }
302 }
303
304 deserializer.deserialize_seq(VecToHashMap)
305 }
306}
307
308impl Genesis {
309 pub fn to_file(&self, path: impl AsRef<Path>) -> anyhow::Result<()> {
310 let toml = toml::to_string_pretty(self)?;
311 std::fs::write(path, toml.as_bytes())?;
312 Ok(())
313 }
314
315 pub fn from_file(path: impl AsRef<Path>) -> anyhow::Result<Self> {
316 let path = path.as_ref();
317 let bytes = std::fs::read(path).context(format!("genesis file {}", path.display()))?;
318 let text = std::str::from_utf8(&bytes).context("genesis file must be UTF-8")?;
319
320 toml::from_str(text).context("malformed genesis file")
321 }
322}
323
324#[cfg(test)]
325mod test {
326 use std::sync::Arc;
327
328 use alloy::{
329 node_bindings::Anvil,
330 primitives::{B256, U256},
331 providers::{layers::AnvilProvider, ProviderBuilder},
332 };
333 use espresso_contract_deployer::{self as deployer, Contracts};
334 use espresso_types::{
335 L1BlockInfo, TimeBasedUpgrade, Timestamp, UpgradeMode, UpgradeType, ViewBasedUpgrade,
336 };
337 use sequencer_utils::ser::FromStringOrInteger;
338 use toml::toml;
339
340 use super::*;
341
342 #[test]
343 fn test_genesis_from_toml_with_optional_fields() {
344 let toml = toml! {
345 base_version = "0.1"
346 upgrade_version = "0.2"
347 genesis_version = "0.1"
348
349 [stake_table]
350 capacity = 10
351
352 [chain_config]
353 chain_id = 12345
354 max_block_size = 30000
355 base_fee = 1
356 fee_recipient = "0x0000000000000000000000000000000000000000"
357 fee_contract = "0x0000000000000000000000000000000000000000"
358
359 [header]
360 timestamp = 123456
361
362 [accounts]
363 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
364 "0x0000000000000000000000000000000000000000" = 42
365
366 [l1_finalized]
367 number = 64
368 timestamp = "0x123def"
369 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
370 }
371 .to_string();
372
373 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
374 assert_eq!(genesis.genesis_version, Version { major: 0, minor: 1 });
375 assert_eq!(genesis.stake_table, StakeTableConfig { capacity: 10 });
376 assert_eq!(
377 genesis.chain_config,
378 ChainConfig {
379 chain_id: 12345.into(),
380 max_block_size: 30000.into(),
381 base_fee: 1.into(),
382 fee_recipient: FeeAccount::default(),
383 fee_contract: Some(Address::default()),
384 stake_table_contract: None
385 }
386 );
387 assert_eq!(
388 genesis.header,
389 GenesisHeader {
390 timestamp: Timestamp::from_integer(123456).unwrap(),
391 }
392 );
393 assert_eq!(
394 genesis.accounts,
395 [
396 (
397 FeeAccount::from(Address::from([
398 0x23, 0x61, 0x8e, 0x81, 0xe3, 0xf5, 0xcd, 0xf7, 0xf5, 0x4c, 0x3d, 0x65,
399 0xf7, 0xfb, 0xc0, 0xab, 0xf5, 0xb2, 0x1e, 0x8f
400 ])),
401 100000.into()
402 ),
403 (FeeAccount::default(), 42.into())
404 ]
405 .into_iter()
406 .collect::<HashMap<_, _>>()
407 );
408 assert_eq!(
409 genesis.l1_finalized,
410 L1Finalized::Block(L1BlockInfo {
411 number: 64,
412 timestamp: U256::from(0x123def),
413 hash: B256::from([
415 0x80, 0xf5, 0xdd, 0x11, 0xf2, 0xbd, 0xda, 0x28, 0x14, 0xcb, 0x1a, 0xd9, 0x4e,
416 0xf3, 0x0a, 0x47, 0xde, 0x02, 0xcf, 0x28, 0xad, 0x68, 0xc8, 0x9e, 0x10, 0x4c,
417 0x00, 0xc4, 0xe5, 0x1b, 0xb7, 0xa5
418 ])
419 })
420 );
421 }
422
423 #[test]
424 fn test_genesis_from_toml_without_optional_fields() {
425 let toml = toml! {
426 base_version = "0.1"
427 upgrade_version = "0.2"
428 genesis_version = "0.1"
429
430 [stake_table]
431 capacity = 10
432
433 [chain_config]
434 chain_id = 12345
435 max_block_size = 30000
436 base_fee = 1
437 fee_recipient = "0x0000000000000000000000000000000000000000"
438
439 [header]
440 timestamp = 123456
441
442 [l1_finalized]
443 number = 0
444 }
445 .to_string();
446
447 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
448
449 assert_eq!(genesis.stake_table, StakeTableConfig { capacity: 10 });
450 assert_eq!(
451 genesis.chain_config,
452 ChainConfig {
453 chain_id: 12345.into(),
454 max_block_size: 30000.into(),
455 base_fee: 1.into(),
456 fee_recipient: FeeAccount::default(),
457 fee_contract: None,
458 stake_table_contract: None,
459 }
460 );
461 assert_eq!(
462 genesis.header,
463 GenesisHeader {
464 timestamp: Timestamp::from_integer(123456).unwrap(),
465 }
466 );
467 assert_eq!(genesis.accounts, HashMap::default());
468 assert_eq!(genesis.l1_finalized, L1Finalized::Number { number: 0 });
469 }
470
471 #[test]
472 fn test_genesis_l1_finalized_number_only() {
473 let toml = toml! {
474 base_version = "0.1"
475 upgrade_version = "0.2"
476 genesis_version = "0.1"
477
478 [stake_table]
479 capacity = 10
480
481 [chain_config]
482 chain_id = 12345
483 max_block_size = 30000
484 base_fee = 1
485 fee_recipient = "0x0000000000000000000000000000000000000000"
486
487 [header]
488 timestamp = 123456
489
490 [l1_finalized]
491 number = 42
492 }
493 .to_string();
494
495 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
496 assert_eq!(genesis.l1_finalized, L1Finalized::Number { number: 42 });
497 }
498
499 #[test]
500 fn test_genesis_l1_finalized_timestamp_only() {
501 let toml = toml! {
502 base_version = "0.1"
503 upgrade_version = "0.2"
504 genesis_version = "0.1"
505
506 [stake_table]
507 capacity = 10
508
509 [chain_config]
510 chain_id = 12345
511 max_block_size = 30000
512 base_fee = 1
513 fee_recipient = "0x0000000000000000000000000000000000000000"
514
515 [header]
516 timestamp = 123456
517
518 [l1_finalized]
519 timestamp = "2024-01-02T00:00:00Z"
520 }
521 .to_string();
522
523 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
524 assert_eq!(
525 genesis.l1_finalized,
526 L1Finalized::Timestamp {
527 timestamp: Timestamp::from_string("2024-01-02T00:00:00Z".to_string()).unwrap()
528 }
529 );
530 }
531
532 #[test_log::test(tokio::test(flavor = "multi_thread"))]
537 async fn test_genesis_fee_contract_is_a_proxy() -> anyhow::Result<()> {
538 let anvil = Arc::new(Anvil::new().spawn());
539 let wallet = anvil.wallet().unwrap();
540 let admin = wallet.default_signer().address();
541 let inner_provider = ProviderBuilder::new()
542 .wallet(wallet)
543 .on_http(anvil.endpoint_url());
544 let provider = AnvilProvider::new(inner_provider, Arc::clone(&anvil));
545 let mut contracts = Contracts::new();
546
547 let proxy_addr =
548 deployer::deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?;
549
550 let toml = format!(
551 r#"
552 base_version = "0.1"
553 upgrade_version = "0.2"
554 genesis_version = "0.1"
555
556 [stake_table]
557 capacity = 10
558
559 [chain_config]
560 chain_id = 12345
561 max_block_size = 30000
562 base_fee = 1
563 fee_recipient = "0x0000000000000000000000000000000000000000"
564 fee_contract = "{proxy_addr:?}"
565
566 [header]
567 timestamp = 123456
568
569 [l1_finalized]
570 number = 42
571 "#,
572 )
573 .to_string();
574
575 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
576
577 let result = genesis
579 .validate_fee_contract(&L1Client::anvil(&anvil).unwrap())
580 .await;
581
582 assert!(
583 result.is_ok(),
584 "Expected Fee Contract to be a proxy, but it was not"
585 );
586 Ok(())
587 }
588
589 #[test_log::test(tokio::test(flavor = "multi_thread"))]
590 async fn test_genesis_fee_contract_is_a_proxy_with_upgrades() -> anyhow::Result<()> {
591 let anvil = Arc::new(Anvil::new().spawn());
592 let wallet = anvil.wallet().unwrap();
593 let admin = wallet.default_signer().address();
594 let inner_provider = ProviderBuilder::new()
595 .wallet(wallet)
596 .on_http(anvil.endpoint_url());
597 let provider = AnvilProvider::new(inner_provider, Arc::clone(&anvil));
598 let mut contracts = Contracts::new();
599
600 let proxy_addr =
601 deployer::deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?;
602
603 let toml = format!(
604 r#"
605 base_version = "0.1"
606 upgrade_version = "0.2"
607 genesis_version = "0.1"
608
609 [stake_table]
610 capacity = 10
611
612 [chain_config]
613 chain_id = 12345
614 max_block_size = 30000
615 base_fee = 1
616 fee_recipient = "0x0000000000000000000000000000000000000000"
617
618 [header]
619 timestamp = 123456
620
621 [l1_finalized]
622 number = 42
623
624 [[upgrade]]
625 version = "0.2"
626 start_proposing_view = 5
627 stop_proposing_view = 15
628
629 [upgrade.fee]
630
631 [upgrade.fee.chain_config]
632 chain_id = 12345
633 max_block_size = 30000
634 base_fee = 1
635 fee_recipient = "0x0000000000000000000000000000000000000000"
636 fee_contract = "{proxy_addr:?}"
637
638
639 "#,
640 )
641 .to_string();
642
643 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
644
645 let result = genesis
647 .validate_fee_contract(&L1Client::anvil(&anvil).unwrap())
648 .await;
649
650 assert!(
651 result.is_ok(),
652 "Expected Fee Contract to be a proxy, but it was not"
653 );
654 Ok(())
655 }
656
657 #[test_log::test(tokio::test(flavor = "multi_thread"))]
658 async fn test_genesis_missing_fee_contract_with_upgrades() {
659 let toml = toml! {
660 base_version = "0.1"
661 upgrade_version = "0.2"
662 genesis_version = "0.1"
663
664 [stake_table]
665 capacity = 10
666
667 [chain_config]
668 chain_id = 12345
669 max_block_size = 30000
670 base_fee = 1
671 fee_recipient = "0x0000000000000000000000000000000000000000"
672
673 [header]
674 timestamp = 123456
675
676 [l1_finalized]
677 number = 42
678
679 [[upgrade]]
680 version = "0.2"
681 start_proposing_view = 5
682 stop_proposing_view = 15
683
684 [upgrade.fee]
685
686 [upgrade.fee.chain_config]
687 chain_id = 12345
688 max_block_size = 30000
689 base_fee = 1
690 fee_recipient = "0x0000000000000000000000000000000000000000"
691
692 [[upgrade]]
693 version = "0.3"
694 start_proposing_view = 5
695 stop_proposing_view = 15
696
697 [upgrade.epoch]
698 [upgrade.epoch.chain_config]
699 chain_id = 999999999
700 max_block_size = 3000
701 base_fee = 1
702 fee_recipient = "0x0000000000000000000000000000000000000000"
703 bid_recipient = "0x0000000000000000000000000000000000000000"
704 fee_contract = "0xa15bb66138824a1c7167f5e85b957d04dd34e468" }
706 .to_string();
707
708 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
709 let rpc_url = "https://ethereum-sepolia.publicnode.com";
710
711 let result = genesis
713 .validate_fee_contract(&L1Client::new(vec![rpc_url.parse().unwrap()]).unwrap())
714 .await;
715
716 if let Err(e) = result {
718 assert!(e
720 .to_string()
721 .contains("Fee contract's address for the upgrade is missing"));
722 } else {
723 panic!("Expected the fee contract to be missing, but the validation succeeded");
724 }
725 }
726
727 #[test_log::test(tokio::test(flavor = "multi_thread"))]
728 async fn test_genesis_upgrade_fee_contract_address_is_zero() {
729 let toml = toml! {
730 base_version = "0.1"
731 upgrade_version = "0.2"
732 genesis_version = "0.1"
733
734 [stake_table]
735 capacity = 10
736
737 [chain_config]
738 chain_id = 12345
739 max_block_size = 30000
740 base_fee = 1
741 fee_recipient = "0x0000000000000000000000000000000000000000"
742
743 [header]
744 timestamp = 123456
745
746 [l1_finalized]
747 number = 42
748
749 [[upgrade]]
750 version = "0.2"
751 start_proposing_view = 5
752 stop_proposing_view = 15
753
754 [upgrade.fee]
755 [upgrade.fee.chain_config]
756 chain_id = 12345
757 max_block_size = 30000
758 base_fee = 1
759 fee_recipient = "0x0000000000000000000000000000000000000000"
760 fee_contract = "0x0000000000000000000000000000000000000000"
761 }
762 .to_string();
763
764 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
765 let rpc_url = "https://ethereum-sepolia.publicnode.com";
766
767 let result = genesis
769 .validate_fee_contract(&L1Client::new(vec![rpc_url.parse().unwrap()]).unwrap())
770 .await;
771
772 if let Err(e) = result {
774 assert!(e
776 .to_string()
777 .contains("Fee contract cannot use the zero address"));
778 } else {
779 panic!(
780 "Expected the fee contract to complain about the zero address but the validation \
781 succeeded"
782 );
783 }
784 }
785
786 #[test_log::test(tokio::test(flavor = "multi_thread"))]
787 async fn test_genesis_fee_contract_l1_failover() -> anyhow::Result<()> {
788 let anvil = Arc::new(Anvil::new().spawn());
789 let wallet = anvil.wallet().unwrap();
790 let admin = wallet.default_signer().address();
791 let inner_provider = ProviderBuilder::new()
792 .wallet(wallet)
793 .on_http(anvil.endpoint_url());
794 let provider = AnvilProvider::new(inner_provider, Arc::clone(&anvil));
795 let mut contracts = Contracts::new();
796
797 let proxy_addr =
798 deployer::deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?;
799
800 let toml = format!(
801 r#"
802 base_version = "0.1"
803 upgrade_version = "0.2"
804 genesis_version = "0.1"
805
806 [stake_table]
807 capacity = 10
808
809 [chain_config]
810 chain_id = 12345
811 max_block_size = 30000
812 base_fee = 1
813 fee_recipient = "0x0000000000000000000000000000000000000000"
814 fee_contract = "{proxy_addr:?}"
815
816 [header]
817 timestamp = 123456
818
819 [l1_finalized]
820 number = 42
821 "#
822 )
823 .to_string();
824
825 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
826 genesis
827 .validate_fee_contract(
828 &L1Client::new(vec![
829 "http://notareall1provider".parse().unwrap(),
830 anvil.endpoint().parse().unwrap(),
831 ])
832 .unwrap(),
833 )
834 .await
835 .unwrap();
836
837 Ok(())
838 }
839
840 #[test]
841 fn test_genesis_from_toml_units() {
842 let toml = toml! {
843 base_version = "0.1"
844 upgrade_version = "0.2"
845 genesis_version = "0.1"
846
847 [stake_table]
848 capacity = 10
849
850 [chain_config]
851 chain_id = 12345
852 max_block_size = "30mb"
853 base_fee = "1 gwei"
854 fee_recipient = "0x0000000000000000000000000000000000000000"
855
856 [header]
857 timestamp = "2024-05-16T11:20:28-04:00"
858
859 [l1_finalized]
860 number = 0
861 }
862 .to_string();
863
864 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
865 assert_eq!(genesis.stake_table, StakeTableConfig { capacity: 10 });
866 assert_eq!(*genesis.chain_config.max_block_size, 30000000);
867 assert_eq!(genesis.chain_config.base_fee, 1_000_000_000.into());
868 assert_eq!(
869 genesis.header,
870 GenesisHeader {
871 timestamp: Timestamp::from_integer(1715872828).unwrap(),
872 }
873 )
874 }
875
876 #[test]
877 fn test_genesis_toml_fee_upgrade_view_mode() {
878 let toml = toml! {
881 base_version = "0.1"
882 upgrade_version = "0.2"
883 genesis_version = "0.1"
884
885 [stake_table]
886 capacity = 10
887
888 [chain_config]
889 chain_id = 12345
890 max_block_size = 30000
891 base_fee = 1
892 fee_recipient = "0x0000000000000000000000000000000000000000"
893 fee_contract = "0x0000000000000000000000000000000000000000"
894
895 [header]
896 timestamp = 123456
897
898 [accounts]
899 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
900 "0x0000000000000000000000000000000000000000" = 42
901
902 [l1_finalized]
903 number = 64
904 timestamp = "0x123def"
905 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
906
907 [[upgrade]]
908 version = "0.2"
909 start_proposing_view = 1
910 stop_proposing_view = 15
911
912 [upgrade.fee]
913
914 [upgrade.fee.chain_config]
915 chain_id = 12345
916 max_block_size = 30000
917 base_fee = 1
918 fee_recipient = "0x0000000000000000000000000000000000000000"
919 fee_contract = "0x0000000000000000000000000000000000000000"
920 }
921 .to_string();
922
923 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
924
925 let (version, genesis_upgrade) = genesis.upgrades.last_key_value().unwrap();
926 println!("{genesis_upgrade:?}");
927
928 assert_eq!(*version, Version { major: 0, minor: 2 });
929
930 let upgrade = Upgrade {
931 mode: UpgradeMode::View(ViewBasedUpgrade {
932 start_voting_view: None,
933 stop_voting_view: None,
934 start_proposing_view: 1,
935 stop_proposing_view: 15,
936 }),
937 upgrade_type: UpgradeType::Fee {
938 chain_config: genesis.chain_config,
939 },
940 };
941
942 assert_eq!(*genesis_upgrade, upgrade);
943 }
944
945 #[test]
946 fn test_genesis_toml_fee_upgrade_time_mode() {
947 let toml = toml! {
950 base_version = "0.1"
951 upgrade_version = "0.2"
952 genesis_version = "0.1"
953
954 [stake_table]
955 capacity = 10
956
957 [chain_config]
958 chain_id = 12345
959 max_block_size = 30000
960 base_fee = 1
961 fee_recipient = "0x0000000000000000000000000000000000000000"
962 fee_contract = "0x0000000000000000000000000000000000000000"
963
964 [header]
965 timestamp = 123456
966
967 [accounts]
968 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
969 "0x0000000000000000000000000000000000000000" = 42
970
971 [l1_finalized]
972 number = 64
973 timestamp = "0x123def"
974 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
975
976 [[upgrade]]
977 version = "0.2"
978 start_proposing_time = "2024-01-01T00:00:00Z"
979 stop_proposing_time = "2024-01-02T00:00:00Z"
980
981 [upgrade.fee]
982
983 [upgrade.fee.chain_config]
984 chain_id = 12345
985 max_block_size = 30000
986 base_fee = 1
987 fee_recipient = "0x0000000000000000000000000000000000000000"
988 fee_contract = "0x0000000000000000000000000000000000000000"
989 }
990 .to_string();
991
992 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
993
994 let (version, genesis_upgrade) = genesis.upgrades.last_key_value().unwrap();
995
996 assert_eq!(*version, Version { major: 0, minor: 2 });
997
998 let upgrade = Upgrade {
999 mode: UpgradeMode::Time(TimeBasedUpgrade {
1000 start_voting_time: None,
1001 stop_voting_time: None,
1002 start_proposing_time: Timestamp::from_string("2024-01-01T00:00:00Z".to_string())
1003 .unwrap(),
1004 stop_proposing_time: Timestamp::from_string("2024-01-02T00:00:00Z".to_string())
1005 .unwrap(),
1006 }),
1007 upgrade_type: UpgradeType::Fee {
1008 chain_config: genesis.chain_config,
1009 },
1010 };
1011
1012 assert_eq!(*genesis_upgrade, upgrade);
1013 }
1014
1015 #[test]
1016 fn test_genesis_toml_fee_upgrade_view_and_time_mode() {
1017 let toml = toml! {
1020 base_version = "0.1"
1021 upgrade_version = "0.2"
1022 genesis_version = "0.1"
1023
1024 [stake_table]
1025 capacity = 10
1026
1027 [chain_config]
1028 chain_id = 12345
1029 max_block_size = 30000
1030 base_fee = 1
1031 fee_recipient = "0x0000000000000000000000000000000000000000"
1032 fee_contract = "0x0000000000000000000000000000000000000000"
1033
1034 [header]
1035 timestamp = 123456
1036
1037 [accounts]
1038 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
1039 "0x0000000000000000000000000000000000000000" = 42
1040
1041 [l1_finalized]
1042 number = 64
1043 timestamp = "0x123def"
1044 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1045
1046 [[upgrade]]
1047 version = "0.2"
1048 start_proposing_view = 1
1049 stop_proposing_view = 10
1050 start_proposing_time = 1
1051 stop_proposing_time = 10
1052
1053 [upgrade.fee]
1054
1055 [upgrade.fee.chain_config]
1056 chain_id = 12345
1057 max_block_size = 30000
1058 base_fee = 1
1059 fee_recipient = "0x0000000000000000000000000000000000000000"
1060 fee_contract = "0x0000000000000000000000000000000000000000"
1061 }
1062 .to_string();
1063
1064 toml::from_str::<Genesis>(&toml).unwrap_err();
1065 }
1066
1067 #[test]
1068 fn test_fee_and_epoch_upgrade_toml() {
1069 let toml = toml! {
1070 base_version = "0.1"
1071 upgrade_version = "0.2"
1072 genesis_version = "0.1"
1073 epoch_height = 20
1074 drb_difficulty = 10
1075 drb_upgrade_difficulty = 20
1076 epoch_start_block = 1
1077 stake_table_capacity = 200
1078
1079 [stake_table]
1080 capacity = 10
1081
1082 [chain_config]
1083 chain_id = 12345
1084 max_block_size = 30000
1085 base_fee = 1
1086 fee_recipient = "0x0000000000000000000000000000000000000000"
1087 fee_contract = "0x0000000000000000000000000000000000000000"
1088
1089 [header]
1090 timestamp = 123456
1091
1092 [accounts]
1093 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
1094 "0x0000000000000000000000000000000000000000" = 42
1095
1096 [l1_finalized]
1097 number = 64
1098 timestamp = "0x123def"
1099 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1100
1101 [[upgrade]]
1102 version = "0.3"
1103 start_proposing_view = 1
1104 stop_proposing_view = 10
1105
1106 [upgrade.epoch]
1107 [upgrade.epoch.chain_config]
1108 chain_id = 12345
1109 max_block_size = 30000
1110 base_fee = 1
1111 fee_recipient = "0x0000000000000000000000000000000000000000"
1112 fee_contract = "0x0000000000000000000000000000000000000000"
1113 stake_table_contract = "0x0000000000000000000000000000000000000000"
1114
1115 [[upgrade]]
1116 version = "0.2"
1117 start_proposing_view = 1
1118 stop_proposing_view = 15
1119
1120 [upgrade.fee]
1121
1122 [upgrade.fee.chain_config]
1123 chain_id = 12345
1124 max_block_size = 30000
1125 base_fee = 1
1126 fee_recipient = "0x0000000000000000000000000000000000000000"
1127 fee_contract = "0x0000000000000000000000000000000000000000"
1128 }
1129 .to_string();
1130
1131 toml::from_str::<Genesis>(&toml).unwrap();
1132 }
1133}