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#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
18pub struct StakeTableConfig {
19 pub capacity: usize,
20}
21
22#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
24#[serde(untagged)]
25pub enum L1Finalized {
26 Block(L1BlockInfo),
32
33 Number { number: u64 },
39
40 Timestamp { timestamp: Timestamp },
46}
47
48#[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 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 anyhow::bail!("Fee contract's address for the upgrade is missing");
132 }
133 }
134 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 #[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 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 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 #[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 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 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" }
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 let result = genesis
726 .validate_fee_contract(&L1Client::new(vec![rpc_url.parse().unwrap()]).unwrap())
727 .await;
728
729 if let Err(e) = result {
731 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 let result = genesis
788 .validate_fee_contract(&L1Client::new(vec![rpc_url.parse().unwrap()]).unwrap())
789 .await;
790
791 if let Err(e) = result {
793 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 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 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 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}