1use std::{
2 collections::{BTreeMap, HashMap},
3 path::Path,
4};
5
6use alloy::primitives::Address;
7use anyhow::{Context, Ok};
8use espresso_types::{
9 FeeAccount, FeeAmount, GenesisHeader, L1BlockInfo, L1Client, SeqTypes, Timestamp, Upgrade,
10 v0_3::ChainConfig,
11};
12use hotshot_types::{VersionedDaCommittee, version_ser};
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 use std::{collections::BTreeMap, fmt};
141
142 use espresso_types::{
143 Upgrade, UpgradeType,
144 v0_1::{TimeBasedUpgrade, UpgradeMode, ViewBasedUpgrade},
145 };
146 use serde::{
147 Deserialize, Deserializer, Serialize, Serializer,
148 de::{self, SeqAccess, Visitor},
149 ser::SerializeSeq,
150 };
151 use vbs::version::Version;
152
153 pub fn serialize<S>(map: &BTreeMap<Version, Upgrade>, serializer: S) -> Result<S::Ok, S::Error>
154 where
155 S: Serializer,
156 {
157 #[derive(Debug, Clone, Serialize, Deserialize)]
158 pub struct Fields {
159 pub version: String,
160 #[serde(flatten)]
161 pub mode: UpgradeMode,
162 #[serde(flatten)]
163 pub upgrade_type: UpgradeType,
164 }
165
166 let mut seq = serializer.serialize_seq(Some(map.len()))?;
167 for (version, upgrade) in map {
168 seq.serialize_element(&Fields {
169 version: version.to_string(),
170 mode: upgrade.mode.clone(),
171 upgrade_type: upgrade.upgrade_type.clone(),
172 })?
173 }
174 seq.end()
175 }
176
177 pub fn deserialize<'de, D>(deserializer: D) -> Result<BTreeMap<Version, Upgrade>, D::Error>
178 where
179 D: Deserializer<'de>,
180 {
181 struct VecToHashMap;
182
183 #[derive(Debug, Clone, Serialize, Deserialize)]
184 pub struct Fields {
185 pub version: String,
186 #[serde(flatten)]
190 pub time_based: Option<TimeBasedUpgrade>,
191 #[serde(flatten)]
192 pub view_based: Option<ViewBasedUpgrade>,
193 #[serde(flatten)]
194 pub upgrade_type: UpgradeType,
195 }
196
197 impl<'de> Visitor<'de> for VecToHashMap {
198 type Value = BTreeMap<Version, Upgrade>;
199
200 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
201 formatter.write_str("a vector of tuples (key-value pairs)")
202 }
203
204 fn visit_seq<A>(self, mut seq: A) -> Result<BTreeMap<Version, Upgrade>, A::Error>
205 where
206 A: SeqAccess<'de>,
207 {
208 let mut map = BTreeMap::new();
209
210 while let Some(fields) = seq.next_element::<Fields>()? {
211 let version: Vec<_> = fields.version.split('.').collect();
213
214 let version = Version {
215 major: version[0]
216 .parse()
217 .map_err(|_| de::Error::custom("invalid version format"))?,
218 minor: version[1]
219 .parse()
220 .map_err(|_| de::Error::custom("invalid version format"))?,
221 };
222
223 match (fields.time_based, fields.view_based) {
224 (Some(_), Some(_)) => {
225 return Err(de::Error::custom(
226 "both view and time mode parameters are set",
227 ));
228 },
229 (None, None) => {
230 return Err(de::Error::custom(
231 "no view or time mode parameters provided",
232 ));
233 },
234 (None, Some(v)) => {
235 if v.start_proposing_view > v.stop_proposing_view {
236 return Err(de::Error::custom(
237 "stop_proposing_view is less than start_proposing_view",
238 ));
239 }
240
241 map.insert(
242 version,
243 Upgrade {
244 mode: UpgradeMode::View(v),
245 upgrade_type: fields.upgrade_type,
246 },
247 );
248 },
249 (Some(t), None) => {
250 if t.start_proposing_time.unix_timestamp()
251 > t.stop_proposing_time.unix_timestamp()
252 {
253 return Err(de::Error::custom(
254 "stop_proposing_time is less than start_proposing_time",
255 ));
256 }
257
258 map.insert(
259 version,
260 Upgrade {
261 mode: UpgradeMode::Time(t),
262 upgrade_type: fields.upgrade_type.clone(),
263 },
264 );
265 },
266 }
267 }
268
269 Ok(map)
270 }
271 }
272
273 deserializer.deserialize_seq(VecToHashMap)
274 }
275}
276
277impl Genesis {
278 pub fn to_file(&self, path: impl AsRef<Path>) -> anyhow::Result<()> {
279 let toml = toml::to_string_pretty(self)?;
280 std::fs::write(path, toml.as_bytes())?;
281 Ok(())
282 }
283
284 pub fn from_file(path: impl AsRef<Path>) -> anyhow::Result<Self> {
285 let path = path.as_ref();
286 let bytes = std::fs::read(path).context(format!("genesis file {}", path.display()))?;
287 let text = std::str::from_utf8(&bytes).context("genesis file must be UTF-8")?;
288
289 toml::from_str(text).context("malformed genesis file")
290 }
291}
292
293#[cfg(test)]
294mod test {
295 use std::sync::Arc;
296
297 use alloy::{
298 node_bindings::Anvil,
299 primitives::{B256, U256},
300 providers::{ProviderBuilder, layers::AnvilProvider},
301 };
302 use espresso_contract_deployer::{self as deployer, Contracts};
303 use espresso_types::{
304 L1BlockInfo, TimeBasedUpgrade, Timestamp, UpgradeMode, UpgradeType, ViewBasedUpgrade,
305 };
306 use espresso_utils::ser::FromStringOrInteger;
307 use toml::toml;
308
309 use super::*;
310
311 #[test]
312 fn test_genesis_from_toml_with_optional_fields() {
313 let toml = toml! {
314 base_version = "0.1"
315 upgrade_version = "0.2"
316 genesis_version = "0.1"
317
318 [stake_table]
319 capacity = 10
320
321 [chain_config]
322 chain_id = 12345
323 max_block_size = 30000
324 base_fee = 1
325 fee_recipient = "0x0000000000000000000000000000000000000000"
326 fee_contract = "0x0000000000000000000000000000000000000000"
327
328 [header]
329 timestamp = 123456
330
331 [header.chain_config]
332 chain_id = 35353
333 max_block_size = 30720
334 base_fee = 0
335 fee_recipient = "0x0000000000000000000000000000000000000000"
336
337 [accounts]
338 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
339 "0x0000000000000000000000000000000000000000" = 42
340
341 [l1_finalized]
342 number = 64
343 timestamp = "0x123def"
344 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
345 }
346 .to_string();
347
348 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
349 assert_eq!(genesis.genesis_version, Version { major: 0, minor: 1 });
350 assert_eq!(genesis.stake_table, StakeTableConfig { capacity: 10 });
351 assert_eq!(
352 genesis.chain_config,
353 ChainConfig {
354 chain_id: 12345.into(),
355 max_block_size: 30000.into(),
356 base_fee: 1.into(),
357 fee_recipient: FeeAccount::default(),
358 fee_contract: Some(Address::default()),
359 stake_table_contract: None
360 }
361 );
362 assert_eq!(
363 genesis.header,
364 GenesisHeader {
365 timestamp: Timestamp::from_integer(123456).unwrap(),
366 chain_config: ChainConfig::default(),
367 }
368 );
369 assert_eq!(
370 genesis.accounts,
371 [
372 (
373 FeeAccount::from(Address::from([
374 0x23, 0x61, 0x8e, 0x81, 0xe3, 0xf5, 0xcd, 0xf7, 0xf5, 0x4c, 0x3d, 0x65,
375 0xf7, 0xfb, 0xc0, 0xab, 0xf5, 0xb2, 0x1e, 0x8f
376 ])),
377 100000.into()
378 ),
379 (FeeAccount::default(), 42.into())
380 ]
381 .into_iter()
382 .collect::<HashMap<_, _>>()
383 );
384 assert_eq!(
385 genesis.l1_finalized,
386 L1Finalized::Block(L1BlockInfo {
387 number: 64,
388 timestamp: U256::from(0x123def),
389 hash: B256::from([
391 0x80, 0xf5, 0xdd, 0x11, 0xf2, 0xbd, 0xda, 0x28, 0x14, 0xcb, 0x1a, 0xd9, 0x4e,
392 0xf3, 0x0a, 0x47, 0xde, 0x02, 0xcf, 0x28, 0xad, 0x68, 0xc8, 0x9e, 0x10, 0x4c,
393 0x00, 0xc4, 0xe5, 0x1b, 0xb7, 0xa5
394 ])
395 })
396 );
397 }
398
399 #[test]
400 fn test_genesis_from_toml_without_optional_fields() {
401 let toml = toml! {
402 base_version = "0.1"
403 upgrade_version = "0.2"
404 genesis_version = "0.1"
405
406 [stake_table]
407 capacity = 10
408
409 [chain_config]
410 chain_id = 12345
411 max_block_size = 30000
412 base_fee = 1
413 fee_recipient = "0x0000000000000000000000000000000000000000"
414
415 [header]
416 timestamp = 123456
417 [header.chain_config]
418 chain_id = 35353
419 max_block_size = 30720
420 base_fee = 0
421 fee_recipient = "0x0000000000000000000000000000000000000000"
422
423 [l1_finalized]
424 number = 0
425 }
426 .to_string();
427
428 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
429
430 assert_eq!(genesis.stake_table, StakeTableConfig { capacity: 10 });
431 assert_eq!(
432 genesis.chain_config,
433 ChainConfig {
434 chain_id: 12345.into(),
435 max_block_size: 30000.into(),
436 base_fee: 1.into(),
437 fee_recipient: FeeAccount::default(),
438 fee_contract: None,
439 stake_table_contract: None,
440 }
441 );
442 assert_eq!(
443 genesis.header,
444 GenesisHeader {
445 timestamp: Timestamp::from_integer(123456).unwrap(),
446 chain_config: ChainConfig::default(),
447 }
448 );
449 assert_eq!(genesis.accounts, HashMap::default());
450 assert_eq!(genesis.l1_finalized, L1Finalized::Number { number: 0 });
451 }
452
453 #[test]
454 fn test_genesis_l1_finalized_number_only() {
455 let toml = toml! {
456 base_version = "0.1"
457 upgrade_version = "0.2"
458 genesis_version = "0.1"
459
460 [stake_table]
461 capacity = 10
462
463 [chain_config]
464 chain_id = 12345
465 max_block_size = 30000
466 base_fee = 1
467 fee_recipient = "0x0000000000000000000000000000000000000000"
468
469 [header]
470 timestamp = 123456
471
472 [header.chain_config]
473 chain_id = 35353
474 max_block_size = 30720
475 base_fee = 0
476 fee_recipient = "0x0000000000000000000000000000000000000000"
477
478 [l1_finalized]
479 number = 42
480 }
481 .to_string();
482
483 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
484 assert_eq!(genesis.l1_finalized, L1Finalized::Number { number: 42 });
485 }
486
487 #[test]
488 fn test_genesis_l1_finalized_timestamp_only() {
489 let toml = toml! {
490 base_version = "0.1"
491 upgrade_version = "0.2"
492 genesis_version = "0.1"
493
494 [stake_table]
495 capacity = 10
496
497 [chain_config]
498 chain_id = 12345
499 max_block_size = 30000
500 base_fee = 1
501 fee_recipient = "0x0000000000000000000000000000000000000000"
502
503 [header]
504 timestamp = 123456
505
506 [header.chain_config]
507 chain_id = 35353
508 max_block_size = 30720
509 base_fee = 0
510 fee_recipient = "0x0000000000000000000000000000000000000000"
511
512 [l1_finalized]
513 timestamp = "2024-01-02T00:00:00Z"
514 }
515 .to_string();
516
517 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
518 assert_eq!(
519 genesis.l1_finalized,
520 L1Finalized::Timestamp {
521 timestamp: Timestamp::from_string("2024-01-02T00:00:00Z".to_string()).unwrap()
522 }
523 );
524 }
525
526 #[test_log::test(tokio::test(flavor = "multi_thread"))]
531 async fn test_genesis_fee_contract_is_a_proxy() -> anyhow::Result<()> {
532 let anvil = Arc::new(Anvil::new().spawn());
533 let wallet = anvil.wallet().unwrap();
534 let admin = wallet.default_signer().address();
535 let inner_provider = ProviderBuilder::new()
536 .wallet(wallet)
537 .connect_http(anvil.endpoint_url());
538 let provider = AnvilProvider::new(inner_provider, Arc::clone(&anvil));
539 let mut contracts = Contracts::new();
540
541 let proxy_addr =
542 deployer::deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?;
543
544 let toml = format!(
545 r#"
546 base_version = "0.1"
547 upgrade_version = "0.2"
548 genesis_version = "0.1"
549
550 [stake_table]
551 capacity = 10
552
553 [chain_config]
554 chain_id = 12345
555 max_block_size = 30000
556 base_fee = 1
557 fee_recipient = "0x0000000000000000000000000000000000000000"
558 fee_contract = "{proxy_addr:?}"
559
560 [header]
561 timestamp = 123456
562
563 [header.chain_config]
564 chain_id = 35353
565 max_block_size = 30720
566 base_fee = 0
567 fee_recipient = "0x0000000000000000000000000000000000000000"
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 .connect_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 [header.chain_config]
622 chain_id = 35353
623 max_block_size = 30720
624 base_fee = 0
625 fee_recipient = "0x0000000000000000000000000000000000000000"
626
627 [l1_finalized]
628 number = 42
629
630 [[upgrade]]
631 version = "0.2"
632 start_proposing_view = 5
633 stop_proposing_view = 15
634
635 [upgrade.fee]
636
637 [upgrade.fee.chain_config]
638 chain_id = 12345
639 max_block_size = 30000
640 base_fee = 1
641 fee_recipient = "0x0000000000000000000000000000000000000000"
642 fee_contract = "{proxy_addr:?}"
643
644
645 "#,
646 )
647 .to_string();
648
649 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
650
651 let result = genesis
653 .validate_fee_contract(&L1Client::anvil(&anvil).unwrap())
654 .await;
655
656 assert!(
657 result.is_ok(),
658 "Expected Fee Contract to be a proxy, but it was not"
659 );
660 Ok(())
661 }
662
663 #[test_log::test(tokio::test(flavor = "multi_thread"))]
664 async fn test_genesis_missing_fee_contract_with_upgrades() {
665 let toml = toml! {
666 base_version = "0.1"
667 upgrade_version = "0.2"
668 genesis_version = "0.1"
669
670 [stake_table]
671 capacity = 10
672
673 [chain_config]
674 chain_id = 12345
675 max_block_size = 30000
676 base_fee = 1
677 fee_recipient = "0x0000000000000000000000000000000000000000"
678
679 [header]
680 timestamp = 123456
681
682 [header.chain_config]
683 chain_id = 35353
684 max_block_size = 30720
685 base_fee = 0
686 fee_recipient = "0x0000000000000000000000000000000000000000"
687
688 [l1_finalized]
689 number = 42
690
691 [[upgrade]]
692 version = "0.2"
693 start_proposing_view = 5
694 stop_proposing_view = 15
695
696 [upgrade.fee]
697
698 [upgrade.fee.chain_config]
699 chain_id = 12345
700 max_block_size = 30000
701 base_fee = 1
702 fee_recipient = "0x0000000000000000000000000000000000000000"
703
704 [[upgrade]]
705 version = "0.3"
706 start_proposing_view = 5
707 stop_proposing_view = 15
708
709 [upgrade.epoch]
710 [upgrade.epoch.chain_config]
711 chain_id = 999999999
712 max_block_size = 3000
713 base_fee = 1
714 fee_recipient = "0x0000000000000000000000000000000000000000"
715 bid_recipient = "0x0000000000000000000000000000000000000000"
716 fee_contract = "0xa15bb66138824a1c7167f5e85b957d04dd34e468" }
718 .to_string();
719
720 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
721 let rpc_url = "https://ethereum-sepolia.publicnode.com";
722
723 let result = genesis
725 .validate_fee_contract(&L1Client::new(vec![rpc_url.parse().unwrap()]).unwrap())
726 .await;
727
728 if let Err(e) = result {
730 assert!(
732 e.to_string()
733 .contains("Fee contract's address for the upgrade is missing")
734 );
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!(
795 e.to_string()
796 .contains("Fee contract cannot use the zero address")
797 );
798 } else {
799 panic!(
800 "Expected the fee contract to complain about the zero address but the validation \
801 succeeded"
802 );
803 }
804 }
805
806 #[test_log::test(tokio::test(flavor = "multi_thread"))]
807 async fn test_genesis_fee_contract_l1_failover() -> anyhow::Result<()> {
808 let anvil = Arc::new(Anvil::new().spawn());
809 let wallet = anvil.wallet().unwrap();
810 let admin = wallet.default_signer().address();
811 let inner_provider = ProviderBuilder::new()
812 .wallet(wallet)
813 .connect_http(anvil.endpoint_url());
814 let provider = AnvilProvider::new(inner_provider, Arc::clone(&anvil));
815 let mut contracts = Contracts::new();
816
817 let proxy_addr =
818 deployer::deploy_fee_contract_proxy(&provider, &mut contracts, admin).await?;
819
820 let toml = format!(
821 r#"
822 base_version = "0.1"
823 upgrade_version = "0.2"
824 genesis_version = "0.1"
825
826 [stake_table]
827 capacity = 10
828
829 [chain_config]
830 chain_id = 12345
831 max_block_size = 30000
832 base_fee = 1
833 fee_recipient = "0x0000000000000000000000000000000000000000"
834 fee_contract = "{proxy_addr:?}"
835
836 [header]
837 timestamp = 123456
838
839 [header.chain_config]
840 chain_id = 35353
841 max_block_size = 30720
842 base_fee = 0
843 fee_recipient = "0x0000000000000000000000000000000000000000"
844
845 [l1_finalized]
846 number = 42
847 "#
848 )
849 .to_string();
850
851 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
852 genesis
853 .validate_fee_contract(
854 &L1Client::new(vec![
855 "http://notareall1provider".parse().unwrap(),
856 anvil.endpoint().parse().unwrap(),
857 ])
858 .unwrap(),
859 )
860 .await
861 .unwrap();
862
863 Ok(())
864 }
865
866 #[test]
867 fn test_genesis_from_toml_units() {
868 let toml = toml! {
869 base_version = "0.1"
870 upgrade_version = "0.2"
871 genesis_version = "0.1"
872
873 [stake_table]
874 capacity = 10
875
876 [chain_config]
877 chain_id = 12345
878 max_block_size = "30mb"
879 base_fee = "1 gwei"
880 fee_recipient = "0x0000000000000000000000000000000000000000"
881
882 [header]
883 timestamp = "2024-05-16T11:20:28-04:00"
884
885
886
887 [header.chain_config]
888 chain_id = 35353
889 max_block_size = 30720
890 base_fee = 0
891 fee_recipient = "0x0000000000000000000000000000000000000000"
892
893 [l1_finalized]
894 number = 0
895 }
896 .to_string();
897
898 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
899 assert_eq!(genesis.stake_table, StakeTableConfig { capacity: 10 });
900 assert_eq!(*genesis.chain_config.max_block_size, 30000000);
901 assert_eq!(genesis.chain_config.base_fee, 1_000_000_000.into());
902 assert_eq!(
903 genesis.header,
904 GenesisHeader {
905 timestamp: Timestamp::from_integer(1715872828).unwrap(),
906 chain_config: ChainConfig::default(),
907 }
908 )
909 }
910
911 #[test]
912 fn test_genesis_toml_fee_upgrade_view_mode() {
913 let toml = toml! {
916 base_version = "0.1"
917 upgrade_version = "0.2"
918 genesis_version = "0.1"
919
920 [stake_table]
921 capacity = 10
922
923 [chain_config]
924 chain_id = 12345
925 max_block_size = 30000
926 base_fee = 1
927 fee_recipient = "0x0000000000000000000000000000000000000000"
928 fee_contract = "0x0000000000000000000000000000000000000000"
929
930 [header]
931 timestamp = 123456
932
933 [header.chain_config]
934 chain_id = 35353
935 max_block_size = 30720
936 base_fee = 0
937 fee_recipient = "0x0000000000000000000000000000000000000000"
938
939 [accounts]
940 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
941 "0x0000000000000000000000000000000000000000" = 42
942
943 [l1_finalized]
944 number = 64
945 timestamp = "0x123def"
946 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
947
948 [[upgrade]]
949 version = "0.2"
950 start_proposing_view = 1
951 stop_proposing_view = 15
952
953 [upgrade.fee]
954
955 [upgrade.fee.chain_config]
956 chain_id = 12345
957 max_block_size = 30000
958 base_fee = 1
959 fee_recipient = "0x0000000000000000000000000000000000000000"
960 fee_contract = "0x0000000000000000000000000000000000000000"
961 }
962 .to_string();
963
964 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
965
966 let (version, genesis_upgrade) = genesis.upgrades.last_key_value().unwrap();
967 println!("{genesis_upgrade:?}");
968
969 assert_eq!(*version, Version { major: 0, minor: 2 });
970
971 let upgrade = Upgrade {
972 mode: UpgradeMode::View(ViewBasedUpgrade {
973 start_voting_view: None,
974 stop_voting_view: None,
975 start_proposing_view: 1,
976 stop_proposing_view: 15,
977 }),
978 upgrade_type: UpgradeType::Fee {
979 chain_config: genesis.chain_config,
980 },
981 };
982
983 assert_eq!(*genesis_upgrade, upgrade);
984 }
985
986 #[test]
987 fn test_genesis_toml_fee_upgrade_time_mode() {
988 let toml = toml! {
991 base_version = "0.1"
992 upgrade_version = "0.2"
993 genesis_version = "0.1"
994
995 [stake_table]
996 capacity = 10
997
998 [chain_config]
999 chain_id = 12345
1000 max_block_size = 30000
1001 base_fee = 1
1002 fee_recipient = "0x0000000000000000000000000000000000000000"
1003 fee_contract = "0x0000000000000000000000000000000000000000"
1004
1005 [header]
1006 timestamp = 123456
1007
1008 [header.chain_config]
1009 chain_id = 35353
1010 max_block_size = 30720
1011 base_fee = 0
1012 fee_recipient = "0x0000000000000000000000000000000000000000"
1013
1014 [accounts]
1015 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
1016 "0x0000000000000000000000000000000000000000" = 42
1017
1018 [l1_finalized]
1019 number = 64
1020 timestamp = "0x123def"
1021 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1022
1023 [[upgrade]]
1024 version = "0.2"
1025 start_proposing_time = "2024-01-01T00:00:00Z"
1026 stop_proposing_time = "2024-01-02T00:00:00Z"
1027
1028 [upgrade.fee]
1029
1030 [upgrade.fee.chain_config]
1031 chain_id = 12345
1032 max_block_size = 30000
1033 base_fee = 1
1034 fee_recipient = "0x0000000000000000000000000000000000000000"
1035 fee_contract = "0x0000000000000000000000000000000000000000"
1036 }
1037 .to_string();
1038
1039 let genesis: Genesis = toml::from_str(&toml).unwrap_or_else(|err| panic!("{err:#}"));
1040
1041 let (version, genesis_upgrade) = genesis.upgrades.last_key_value().unwrap();
1042
1043 assert_eq!(*version, Version { major: 0, minor: 2 });
1044
1045 let upgrade = Upgrade {
1046 mode: UpgradeMode::Time(TimeBasedUpgrade {
1047 start_voting_time: None,
1048 stop_voting_time: None,
1049 start_proposing_time: Timestamp::from_string("2024-01-01T00:00:00Z".to_string())
1050 .unwrap(),
1051 stop_proposing_time: Timestamp::from_string("2024-01-02T00:00:00Z".to_string())
1052 .unwrap(),
1053 }),
1054 upgrade_type: UpgradeType::Fee {
1055 chain_config: genesis.chain_config,
1056 },
1057 };
1058
1059 assert_eq!(*genesis_upgrade, upgrade);
1060 }
1061
1062 #[test]
1063 fn test_genesis_toml_fee_upgrade_view_and_time_mode() {
1064 let toml = toml! {
1067 base_version = "0.1"
1068 upgrade_version = "0.2"
1069 genesis_version = "0.1"
1070
1071 [stake_table]
1072 capacity = 10
1073
1074 [chain_config]
1075 chain_id = 12345
1076 max_block_size = 30000
1077 base_fee = 1
1078 fee_recipient = "0x0000000000000000000000000000000000000000"
1079 fee_contract = "0x0000000000000000000000000000000000000000"
1080
1081 [header]
1082 timestamp = 123456
1083
1084 [header.chain_config]
1085 chain_id = 35353
1086 max_block_size = 30720
1087 base_fee = 0
1088 fee_recipient = "0x0000000000000000000000000000000000000000"
1089
1090 [accounts]
1091 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
1092 "0x0000000000000000000000000000000000000000" = 42
1093
1094 [l1_finalized]
1095 number = 64
1096 timestamp = "0x123def"
1097 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1098
1099 [[upgrade]]
1100 version = "0.2"
1101 start_proposing_view = 1
1102 stop_proposing_view = 10
1103 start_proposing_time = 1
1104 stop_proposing_time = 10
1105
1106 [upgrade.fee]
1107
1108 [upgrade.fee.chain_config]
1109 chain_id = 12345
1110 max_block_size = 30000
1111 base_fee = 1
1112 fee_recipient = "0x0000000000000000000000000000000000000000"
1113 fee_contract = "0x0000000000000000000000000000000000000000"
1114 }
1115 .to_string();
1116
1117 toml::from_str::<Genesis>(&toml).unwrap_err();
1118 }
1119
1120 #[test]
1121 fn test_fee_and_epoch_upgrade_toml() {
1122 let toml = toml! {
1123 base_version = "0.1"
1124 upgrade_version = "0.2"
1125 genesis_version = "0.1"
1126 epoch_height = 20
1127 drb_difficulty = 10
1128 drb_upgrade_difficulty = 20
1129 epoch_start_block = 1
1130 stake_table_capacity = 200
1131
1132 [stake_table]
1133 capacity = 10
1134
1135 [chain_config]
1136 chain_id = 12345
1137 max_block_size = 30000
1138 base_fee = 1
1139 fee_recipient = "0x0000000000000000000000000000000000000000"
1140 fee_contract = "0x0000000000000000000000000000000000000000"
1141
1142 [header]
1143 timestamp = 123456
1144
1145 [header.chain_config]
1146 chain_id = 35353
1147 max_block_size = 30720
1148 base_fee = 0
1149 fee_recipient = "0x0000000000000000000000000000000000000000"
1150
1151 [accounts]
1152 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
1153 "0x0000000000000000000000000000000000000000" = 42
1154
1155 [l1_finalized]
1156 number = 64
1157 timestamp = "0x123def"
1158 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1159
1160 [[upgrade]]
1161 version = "0.3"
1162 start_proposing_view = 1
1163 stop_proposing_view = 10
1164
1165 [upgrade.epoch]
1166 [upgrade.epoch.chain_config]
1167 chain_id = 12345
1168 max_block_size = 30000
1169 base_fee = 1
1170 fee_recipient = "0x0000000000000000000000000000000000000000"
1171 fee_contract = "0x0000000000000000000000000000000000000000"
1172 stake_table_contract = "0x0000000000000000000000000000000000000000"
1173
1174 [[upgrade]]
1175 version = "0.2"
1176 start_proposing_view = 1
1177 stop_proposing_view = 15
1178
1179 [upgrade.fee]
1180
1181 [upgrade.fee.chain_config]
1182 chain_id = 12345
1183 max_block_size = 30000
1184 base_fee = 1
1185 fee_recipient = "0x0000000000000000000000000000000000000000"
1186 fee_contract = "0x0000000000000000000000000000000000000000"
1187 }
1188 .to_string();
1189
1190 toml::from_str::<Genesis>(&toml).unwrap();
1191 }
1192
1193 #[test]
1194 fn test_genesis_chain_config() {
1195 let toml = toml! {
1196 base_version = "0.1"
1197 upgrade_version = "0.2"
1198 genesis_version = "0.2"
1199 epoch_height = 20
1200 drb_difficulty = 10
1201 drb_upgrade_difficulty = 20
1202 epoch_start_block = 1
1203 stake_table_capacity = 200
1204
1205 [stake_table]
1206 capacity = 10
1207
1208 [genesis_chain_config]
1209 chain_id = 33
1210 max_block_size = 5000
1211 base_fee = 1
1212 fee_recipient = "0x0000000000000000000000000000000000000000"
1213 fee_contract = "0x0000000000000000000000000000000000000000"
1214
1215 [chain_config]
1216 chain_id = 12345
1217 max_block_size = 30000
1218 base_fee = 1
1219 fee_recipient = "0x0000000000000000000000000000000000000000"
1220 fee_contract = "0x0000000000000000000000000000000000000000"
1221
1222 [header]
1223 timestamp = 123456
1224
1225 [header.chain_config]
1226 chain_id = 33
1227 max_block_size = 5000
1228 base_fee = 1
1229 fee_recipient = "0x0000000000000000000000000000000000000000"
1230 fee_contract = "0x0000000000000000000000000000000000000000"
1231
1232 [accounts]
1233 "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" = 100000
1234 "0x0000000000000000000000000000000000000000" = 42
1235
1236 [l1_finalized]
1237 number = 64
1238 timestamp = "0x123def"
1239 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1240
1241 [[upgrade]]
1242 version = "0.3"
1243 start_proposing_view = 1
1244 stop_proposing_view = 10
1245
1246 [upgrade.epoch]
1247 [upgrade.epoch.chain_config]
1248 chain_id = 12345
1249 max_block_size = 30000
1250 base_fee = 1
1251 fee_recipient = "0x0000000000000000000000000000000000000000"
1252 fee_contract = "0x0000000000000000000000000000000000000000"
1253 stake_table_contract = "0x0000000000000000000000000000000000000000"
1254
1255 [[upgrade]]
1256 version = "0.2"
1257 start_proposing_view = 1
1258 stop_proposing_view = 15
1259
1260 [upgrade.fee]
1261
1262 [upgrade.fee.chain_config]
1263 chain_id = 12345
1264 max_block_size = 30000
1265 base_fee = 1
1266 fee_recipient = "0x0000000000000000000000000000000000000000"
1267 fee_contract = "0x0000000000000000000000000000000000000000"
1268 }
1269 .to_string();
1270
1271 let genesis = toml::from_str::<Genesis>(&toml).unwrap();
1272
1273 assert_eq!(genesis.header.chain_config.chain_id, 33.into());
1274 assert_eq!(genesis.chain_config.chain_id, 12345.into());
1275
1276 assert_eq!(genesis.header.chain_config.max_block_size, 5000.into());
1277 assert_eq!(genesis.chain_config.max_block_size, 30000.into());
1278 }
1279
1280 #[test]
1281 fn test_genesis_da_committees() {
1282 let toml = toml! {
1283 base_version = "0.1"
1284 upgrade_version = "0.5"
1285 genesis_version = "0.1"
1286 epoch_height = 20
1287 drb_difficulty = 10
1288 drb_upgrade_difficulty = 20
1289 epoch_start_block = 1
1290 stake_table_capacity = 200
1291
1292 [stake_table]
1293 capacity = 10
1294
1295 [chain_config]
1296 chain_id = 12345
1297 max_block_size = 30000
1298 base_fee = 1
1299 fee_recipient = "0x0000000000000000000000000000000000000000"
1300 fee_contract = "0x0000000000000000000000000000000000000000"
1301
1302 [l1_finalized]
1303 number = 64
1304 timestamp = "0x123def"
1305 hash = "0x80f5dd11f2bdda2814cb1ad94ef30a47de02cf28ad68c89e104c00c4e51bb7a5"
1306
1307 [header]
1308 timestamp = 123456
1309
1310 [header.chain_config]
1311 chain_id = 33
1312 max_block_size = 5000
1313 base_fee = 1
1314 fee_recipient = "0x0000000000000000000000000000000000000000"
1315 fee_contract = "0x0000000000000000000000000000000000000000"
1316
1317 [[upgrade]]
1318 version = "0.5"
1319 start_proposing_view = 1
1320 stop_proposing_view = 15
1321
1322 [upgrade.da]
1323 [upgrade.da.chain_config]
1324 chain_id = 12345
1325 max_block_size = 30000
1326 base_fee = 1
1327 fee_recipient = "0x0000000000000000000000000000000000000000"
1328 fee_contract = "0x0000000000000000000000000000000000000000"
1329 stake_table_contract = "0x0000000000000000000000000000000000000000"
1330
1331 [[da_committees]]
1332 start_version = "0.6"
1333 start_epoch = 10
1334 committee = [
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 { 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"}
1337 ]
1338 }
1339 .to_string();
1340
1341 let genesis = toml::from_str::<Genesis>(&toml).unwrap();
1342
1343 let da_committees = genesis
1344 .da_committees
1345 .expect("DA committees should be present");
1346 assert_eq!(da_committees.len(), 1);
1347
1348 let da_committee = &da_committees[0];
1349
1350 assert_eq!(da_committee.start_version, Version { major: 0, minor: 6 });
1351 assert_eq!(da_committee.start_epoch, 10);
1352 assert_eq!(da_committee.committee.len(), 2);
1353 assert_eq!(
1354 da_committee.committee[0].stake_table_entry.stake_amount,
1355 U256::from(1)
1356 );
1357 assert_eq!(
1358 da_committee.committee[1].stake_table_entry.stake_amount,
1359 U256::from(1)
1360 );
1361 }
1362}