1use alloy::{
2 genesis::{ChainConfig, Genesis, GenesisAccount},
3 primitives::{Address, U256, address},
4 signers::{local::MnemonicBuilder, utils::secret_key_to_address},
5};
6use alloy_primitives::{B256, Bytes};
7use commonware_codec::Encode as _;
8use commonware_consensus::types::Epoch;
9use commonware_cryptography::{
10 Signer as _,
11 bls12381::{
12 dkg::{self, Output},
13 primitives::{sharing::Mode, variant::MinSig},
14 },
15 ed25519::PublicKey,
16};
17use commonware_math::algebra::Random as _;
18use commonware_utils::{N3f1, TryFromIterator as _, ordered};
19use eyre::{WrapErr as _, eyre};
20use indicatif::{ParallelProgressIterator, ProgressIterator};
21use itertools::Itertools;
22use rand_08::SeedableRng as _;
23use rayon::prelude::*;
24use reth_evm::{
25 Evm as _, EvmEnv, EvmFactory,
26 revm::{
27 DatabaseCommit,
28 context_interface::JournalTr as _,
29 database::{CacheDB, EmptyDB},
30 inspector::JournalExt,
31 state::{AccountInfo, Bytecode},
32 },
33};
34use std::{
35 collections::BTreeMap,
36 iter::repeat_with,
37 net::SocketAddr,
38 path::{Path, PathBuf},
39};
40use tempo_chainspec::hardfork::TempoHardfork;
41use tempo_commonware_node_config::{SigningKey, SigningShare};
42use tempo_contracts::{
43 ARACHNID_CREATE2_FACTORY_ADDRESS, CREATEX_ADDRESS, MULTICALL3_ADDRESS, PERMIT2_ADDRESS,
44 PERMIT2_SALT, SAFE_DEPLOYER_ADDRESS,
45 contracts::{ARACHNID_CREATE2_FACTORY_BYTECODE, CreateX, Multicall3, SafeDeployer},
46 precompiles::{ITIP20Factory, IValidatorConfig, IValidatorConfigV2},
47};
48use tempo_dkg_onchain_artifacts::OnchainDkgOutcome;
49use tempo_evm::evm::{TempoEvm, TempoEvmFactory};
50use tempo_precompiles::{
51 PATH_USD_ADDRESS,
52 account_keychain::AccountKeychain,
53 nonce::NonceManager,
54 stablecoin_dex::StablecoinDEX,
55 storage::{ContractStorage, StorageCtx},
56 tip_fee_manager::{IFeeManager, TipFeeManager},
57 tip20::{ISSUER_ROLE, ITIP20, TIP20Token},
58 tip20_factory::TIP20Factory,
59 tip403_registry::TIP403Registry,
60 validator_config::ValidatorConfig,
61 validator_config_v2::ValidatorConfigV2,
62};
63
64#[derive(Debug, clap::Args)]
66pub(crate) struct GenesisArgs {
67 #[arg(short, long, default_value = "50000")]
69 accounts: u32,
70
71 #[arg(
73 short,
74 long,
75 default_value = "test test test test test test test test test test test junk"
76 )]
77 mnemonic: String,
78
79 #[arg(long, default_value = "0x0000000000000000000000000000000000000000")]
81 coinbase: Address,
82
83 #[arg(long, short, default_value = "1337")]
85 chain_id: u64,
86
87 #[arg(long, default_value_t = 500_000_000)]
89 gas_limit: u64,
90
91 #[arg(long, default_value_t = 302_400)]
93 epoch_length: u64,
94
95 #[arg(
97 long,
98 value_name = "<ip>:<port>",
99 value_delimiter = ',',
100 required_unless_present_all(["no_dkg_in_genesis"]),
101 )]
102 validators: Vec<SocketAddr>,
103
104 #[arg(long)]
107 no_dkg_in_genesis: bool,
108
109 #[arg(long)]
112 pub(crate) seed: Option<u64>,
113
114 #[arg(long)]
117 pathusd_admin: Option<Address>,
118
119 #[arg(long, default_value_t = u64::MAX)]
120 pathusd_amount: u64,
121
122 #[arg(long)]
125 validator_admin: Option<Address>,
126
127 #[arg(long, value_delimiter = ',')]
130 validator_addresses: Vec<Address>,
131
132 #[arg(long)]
134 no_extra_tokens: bool,
135
136 #[arg(long)]
138 deployment_gas_token: bool,
139
140 #[arg(long)]
142 deployment_gas_token_admin: Option<Address>,
143
144 #[arg(long)]
146 no_pairwise_liquidity: bool,
147
148 #[arg(long, default_value = "0")]
150 t0_time: u64,
151
152 #[arg(long, default_value = "0")]
154 t1_time: u64,
155
156 #[arg(long, default_value = "0")]
158 t1a_time: u64,
159
160 #[arg(long, default_value = "0")]
162 t1b_time: u64,
163
164 #[arg(long, default_value = "0")]
166 t1c_time: u64,
167
168 #[arg(long, default_value = "0")]
170 t2_time: u64,
171
172 #[arg(long)]
174 no_initialize_validator_config_v1: bool,
175}
176
177#[derive(Clone, Debug)]
178pub(crate) struct ConsensusConfig {
179 pub(crate) output: Output<MinSig, PublicKey>,
180 pub(crate) validators: Vec<Validator>,
181}
182impl ConsensusConfig {
183 pub(crate) fn to_genesis_dkg_outcome(&self) -> OnchainDkgOutcome {
184 OnchainDkgOutcome {
185 epoch: Epoch::zero(),
186 output: self.output.clone(),
187 next_players: ordered::Set::try_from_iter(
188 self.validators.iter().map(Validator::public_key),
189 )
190 .unwrap(),
191 is_next_full_dkg: false,
192 }
193 }
194}
195
196#[derive(Clone, Debug)]
197pub(crate) struct Validator {
198 pub(crate) addr: SocketAddr,
199 pub(crate) signing_key: SigningKey,
200 pub(crate) signing_share: SigningShare,
201}
202
203impl Validator {
204 pub(crate) fn public_key(&self) -> PublicKey {
205 self.signing_key.public_key()
206 }
207
208 pub(crate) fn dst_dir(&self, path: impl AsRef<Path>) -> PathBuf {
209 path.as_ref().join(self.addr.to_string())
210 }
211 pub(crate) fn dst_signing_key(&self, path: impl AsRef<Path>) -> PathBuf {
212 self.dst_dir(path).join("signing.key")
213 }
214
215 pub(crate) fn dst_signing_share(&self, path: impl AsRef<Path>) -> PathBuf {
216 self.dst_dir(path).join("signing.share")
217 }
218}
219
220impl GenesisArgs {
221 pub(crate) async fn generate_genesis(self) -> eyre::Result<(Genesis, Option<ConsensusConfig>)> {
226 println!("Generating {:?} accounts", self.accounts);
227
228 let addresses: Vec<Address> = (0..self.accounts)
229 .into_par_iter()
230 .progress()
231 .map(|worker_id| -> eyre::Result<Address> {
232 let signer = MnemonicBuilder::from_phrase_nth(&self.mnemonic, worker_id);
233 let address = secret_key_to_address(signer.credential());
234 Ok(address)
235 })
236 .collect::<eyre::Result<Vec<Address>>>()?;
237
238 let pathusd_admin = self.pathusd_admin.unwrap_or_else(|| addresses[0]);
241 let validator_admin = self.validator_admin.unwrap_or_else(|| addresses[0]);
242 let mut evm = setup_tempo_evm(self.chain_id);
243
244 deploy_arachnid_create2_factory(&mut evm);
245 deploy_permit2(&mut evm)?;
246
247 println!("Initializing registry");
248 initialize_registry(&mut evm)?;
249
250 println!("Initializing TIP20Factory");
252 initialize_tip20_factory(&mut evm)?;
253
254 println!("Creating pathUSD through factory");
255 create_path_usd_token(pathusd_admin, &addresses, self.pathusd_amount, &mut evm)?;
256
257 let (alpha_token_address, beta_token_address, theta_token_address) =
258 if !self.no_extra_tokens {
259 println!("Initializing TIP20 tokens");
260 let alpha = create_and_mint_token(
261 "AlphaUSD",
262 "AlphaUSD",
263 "USD",
264 PATH_USD_ADDRESS,
265 pathusd_admin,
266 &addresses,
267 U256::from(u64::MAX),
268 SaltOrAddress::Address(address!("20C0000000000000000000000000000000000001")),
269 &mut evm,
270 )?;
271
272 let beta = create_and_mint_token(
273 "BetaUSD",
274 "BetaUSD",
275 "USD",
276 PATH_USD_ADDRESS,
277 pathusd_admin,
278 &addresses,
279 U256::from(u64::MAX),
280 SaltOrAddress::Address(address!("20C0000000000000000000000000000000000002")),
281 &mut evm,
282 )?;
283
284 let theta = create_and_mint_token(
285 "ThetaUSD",
286 "ThetaUSD",
287 "USD",
288 PATH_USD_ADDRESS,
289 pathusd_admin,
290 &addresses,
291 U256::from(u64::MAX),
292 SaltOrAddress::Address(address!("20C0000000000000000000000000000000000003")),
293 &mut evm,
294 )?;
295
296 (Some(alpha), Some(beta), Some(theta))
297 } else {
298 println!("Skipping extra token creation (--no-extra-tokens)");
299 (None, None, None)
300 };
301
302 if self.deployment_gas_token && self.deployment_gas_token_admin.is_none() {
303 eyre::bail!(
304 "--deployment-gas-token-admin is required when --deployment-gas-token is set"
305 );
306 }
307
308 let deployment_gas_token = {
309 if self.deployment_gas_token {
310 let mut rng = rand_08::rngs::StdRng::seed_from_u64(
311 self.seed.unwrap_or_else(rand_08::random::<u64>),
312 );
313
314 let mut salt_bytes = [0u8; 32];
315 rand_08::Rng::fill(&mut rng, &mut salt_bytes);
316
317 let address = create_and_mint_token(
318 "DONOTUSE",
319 "DONOTUSE",
320 "USD",
321 PATH_USD_ADDRESS,
322 self.deployment_gas_token_admin.expect(
323 "Deployment gas token admin is required if you want to deploy the token",
324 ),
325 &addresses,
326 U256::from(u64::MAX),
327 SaltOrAddress::Salt(B256::from(salt_bytes)),
328 &mut evm,
329 )?;
330
331 println!("Deployment gas token address: {address}");
332 Some(address)
333 } else {
334 None
335 }
336 };
337
338 println!(
339 "generating consensus config for validators: {:?}",
340 self.validators
341 );
342 let consensus_config =
343 generate_consensus_config(&self.validators, self.seed, self.no_dkg_in_genesis);
344
345 let validator_onchain_addresses = if self.validator_addresses.is_empty() {
346 if addresses.len() < self.validators.len() + 1 {
347 return Err(eyre!("not enough accounts created for validators"));
348 }
349
350 &addresses[1..self.validators.len() + 1]
351 } else {
352 if self.validator_addresses.len() < self.validators.len() {
353 return Err(eyre!("not enough addresses provided for validators"));
354 }
355
356 &self.validator_addresses[0..self.validators.len()]
357 };
358
359 if self.t2_time == 0 {
360 println!("Initializing validator config v2 (T2 active at genesis)");
361 initialize_validator_config_v2(
362 validator_admin,
363 &mut evm,
364 &consensus_config,
365 validator_onchain_addresses,
366 self.no_dkg_in_genesis,
367 self.chain_id,
368 )?;
369 }
370
371 if !self.no_initialize_validator_config_v1 {
372 println!("Initializing validator config v1");
373 initialize_validator_config(
374 validator_admin,
375 &mut evm,
376 &consensus_config,
377 validator_onchain_addresses,
378 self.no_dkg_in_genesis,
379 )?;
380 } else {
381 println!("flag specified; skipping initialization of validator config v1");
382 }
383
384 println!("Initializing fee manager");
385 let default_user_fee_token = if let Some(address) = deployment_gas_token {
386 address
387 } else {
388 alpha_token_address.unwrap_or(PATH_USD_ADDRESS)
389 };
390
391 let default_validator_fee_token = if let Some(address) = deployment_gas_token {
392 address
393 } else {
394 PATH_USD_ADDRESS
395 };
396
397 initialize_fee_manager(
398 default_validator_fee_token,
399 default_user_fee_token,
400 addresses.clone(),
401 vec![self.coinbase],
403 &mut evm,
404 );
405
406 println!("Initializing stablecoin exchange");
407 initialize_stablecoin_dex(&mut evm)?;
408
409 println!("Initializing nonce manager");
410 initialize_nonce_manager(&mut evm)?;
411
412 println!("Initializing account keychain");
413 initialize_account_keychain(&mut evm)?;
414
415 if !self.no_pairwise_liquidity {
416 if let (Some(alpha), Some(beta), Some(theta)) =
417 (alpha_token_address, beta_token_address, theta_token_address)
418 {
419 println!("Minting pairwise FeeAMM liquidity");
420 mint_pairwise_liquidity(
421 alpha,
422 vec![PATH_USD_ADDRESS, beta, theta],
423 U256::from(10u64.pow(10)),
424 pathusd_admin,
425 &mut evm,
426 );
427 } else {
428 println!("Skipping pairwise liquidity (extra tokens not created)");
429 }
430 } else {
431 println!("Skipping pairwise liquidity (--no-pairwise-liquidity)");
432 }
433
434 evm.ctx_mut()
435 .journaled_state
436 .load_account(ARACHNID_CREATE2_FACTORY_ADDRESS)?;
437 evm.ctx_mut()
438 .journaled_state
439 .load_account(PERMIT2_ADDRESS)?;
440
441 println!("Saving EVM state to allocation");
443 let evm_state = evm.ctx_mut().journaled_state.evm_state();
444 let mut genesis_alloc: BTreeMap<Address, GenesisAccount> = evm_state
445 .iter()
446 .progress()
447 .map(|(address, account)| {
448 let storage = if !account.storage.is_empty() {
449 Some(
450 account
451 .storage
452 .iter()
453 .map(|(key, val)| ((*key).into(), val.present_value.into()))
454 .collect(),
455 )
456 } else {
457 None
458 };
459 let genesis_account = GenesisAccount {
460 nonce: Some(account.info.nonce),
461 code: account.info.code.as_ref().map(|c| c.original_bytes()),
462 storage,
463 ..Default::default()
464 };
465 (*address, genesis_account)
466 })
467 .collect();
468
469 genesis_alloc.insert(
470 MULTICALL3_ADDRESS,
471 GenesisAccount {
472 code: Some(Bytes::from_static(&Multicall3::DEPLOYED_BYTECODE)),
473 nonce: Some(1),
474 ..Default::default()
475 },
476 );
477
478 genesis_alloc.insert(
479 CREATEX_ADDRESS,
480 GenesisAccount {
481 code: Some(Bytes::from_static(&CreateX::DEPLOYED_BYTECODE)),
482 nonce: Some(1),
483 ..Default::default()
484 },
485 );
486
487 genesis_alloc.insert(
488 SAFE_DEPLOYER_ADDRESS,
489 GenesisAccount {
490 code: Some(Bytes::from_static(&SafeDeployer::DEPLOYED_BYTECODE)),
491 nonce: Some(1),
492 ..Default::default()
493 },
494 );
495
496 let mut chain_config = ChainConfig {
497 chain_id: self.chain_id,
498 homestead_block: Some(0),
499 eip150_block: Some(0),
500 eip155_block: Some(0),
501 eip158_block: Some(0),
502 byzantium_block: Some(0),
503 constantinople_block: Some(0),
504 petersburg_block: Some(0),
505 istanbul_block: Some(0),
506 berlin_block: Some(0),
507 london_block: Some(0),
508 merge_netsplit_block: Some(0),
509 shanghai_time: Some(0),
510 cancun_time: Some(0),
511 prague_time: Some(0),
512 osaka_time: Some(0),
513 terminal_total_difficulty: Some(U256::from(0)),
514 terminal_total_difficulty_passed: true,
515 deposit_contract_address: Some(Address::ZERO),
516 ..Default::default()
517 };
518
519 chain_config
520 .extra_fields
521 .insert_value("epochLength".to_string(), self.epoch_length)?;
522 chain_config
523 .extra_fields
524 .insert_value("t0Time".to_string(), self.t0_time)?;
525 chain_config
526 .extra_fields
527 .insert_value("t1Time".to_string(), self.t1_time)?;
528 chain_config
529 .extra_fields
530 .insert_value("t1aTime".to_string(), self.t1a_time)?;
531 chain_config
532 .extra_fields
533 .insert_value("t1bTime".to_string(), self.t1b_time)?;
534 chain_config
535 .extra_fields
536 .insert_value("t1cTime".to_string(), self.t1c_time)?;
537 chain_config
538 .extra_fields
539 .insert_value("t2Time".to_string(), self.t2_time)?;
540 let mut extra_data = Bytes::from_static(b"tempo-genesis");
541
542 if let Some(consensus_config) = &consensus_config {
543 if self.no_dkg_in_genesis {
544 println!("no-initial-dkg-in-genesis passed; not writing to header extra_data");
545 } else {
546 extra_data = consensus_config
547 .to_genesis_dkg_outcome()
548 .encode()
549 .to_vec()
550 .into();
551 }
552 }
553
554 let base_fee: u128 = if self.t1_time == 0 {
556 TempoHardfork::T1.base_fee().into()
557 } else {
558 TempoHardfork::T0.base_fee().into()
559 };
560
561 let mut genesis = Genesis::default()
562 .with_gas_limit(self.gas_limit)
563 .with_base_fee(Some(base_fee))
564 .with_nonce(0x42)
565 .with_extra_data(extra_data)
566 .with_coinbase(self.coinbase);
567
568 genesis.alloc = genesis_alloc;
569 genesis.config = chain_config;
570
571 Ok((genesis, consensus_config))
572 }
573}
574
575fn setup_tempo_evm(chain_id: u64) -> TempoEvm<CacheDB<EmptyDB>> {
576 let db = CacheDB::default();
577 let mut env = EvmEnv::default().with_timestamp(U256::ZERO);
579 env.cfg_env.chain_id = chain_id;
580
581 let factory = TempoEvmFactory::default();
582 factory.create_evm(db, env)
583}
584
585fn deploy_arachnid_create2_factory(evm: &mut TempoEvm<CacheDB<EmptyDB>>) {
587 println!("Deploying Arachnid CREATE2 factory at {ARACHNID_CREATE2_FACTORY_ADDRESS}");
588
589 evm.db_mut().insert_account_info(
590 ARACHNID_CREATE2_FACTORY_ADDRESS,
591 AccountInfo {
592 code: Some(Bytecode::new_raw(ARACHNID_CREATE2_FACTORY_BYTECODE)),
593 nonce: 0,
594 ..Default::default()
595 },
596 );
597}
598
599fn deploy_permit2(evm: &mut TempoEvm<CacheDB<EmptyDB>>) -> eyre::Result<()> {
601 let bytecode = &tempo_contracts::Permit2::BYTECODE;
603 let calldata: Bytes = PERMIT2_SALT
604 .as_slice()
605 .iter()
606 .chain(bytecode.iter())
607 .copied()
608 .collect();
609
610 println!("Deploying Permit2 via CREATE2 to {PERMIT2_ADDRESS}");
611
612 let result =
613 evm.transact_system_call(Address::ZERO, ARACHNID_CREATE2_FACTORY_ADDRESS, calldata)?;
614
615 if !result.result.is_success() {
616 return Err(eyre!("Permit2 deployment failed: {:?}", result));
617 }
618
619 evm.db_mut().commit(result.state);
620
621 println!("Permit2 deployed successfully at {PERMIT2_ADDRESS}");
622 Ok(())
623}
624
625fn initialize_tip20_factory(evm: &mut TempoEvm<CacheDB<EmptyDB>>) -> eyre::Result<()> {
627 let ctx = evm.ctx_mut();
628 StorageCtx::enter_evm(
629 &mut ctx.journaled_state,
630 &ctx.block,
631 &ctx.cfg,
632 &ctx.tx,
633 || TIP20Factory::new().initialize(),
634 )?;
635 Ok(())
636}
637
638fn create_path_usd_token(
641 admin: Address,
642 recipients: &[Address],
643 amount_per_recipient: u64,
644 evm: &mut TempoEvm<CacheDB<EmptyDB>>,
645) -> eyre::Result<()> {
646 let ctx = evm.ctx_mut();
647 StorageCtx::enter_evm(
648 &mut ctx.journaled_state,
649 &ctx.block,
650 &ctx.cfg,
651 &ctx.tx,
652 || {
653 TIP20Factory::new().create_token_reserved_address(
654 PATH_USD_ADDRESS,
655 "pathUSD",
656 "pathUSD",
657 "USD",
658 Address::ZERO,
659 admin,
660 )?;
661
662 let mut token = TIP20Token::from_address(PATH_USD_ADDRESS)
664 .expect("Could not create pathUSD token instance");
665 token.grant_role_internal(admin, *ISSUER_ROLE)?;
666
667 for recipient in recipients.iter().progress() {
669 token
670 .mint(
671 admin,
672 ITIP20::mintCall {
673 to: *recipient,
674 amount: U256::from(amount_per_recipient),
675 },
676 )
677 .expect("Could not mint pathUSD");
678 }
679
680 Ok(())
681 },
682 )
683}
684
685enum SaltOrAddress {
686 Salt(B256),
687 Address(Address),
688}
689
690#[expect(clippy::too_many_arguments)]
692fn create_and_mint_token(
693 symbol: &str,
694 name: &str,
695 currency: &str,
696 quote_token: Address,
697 admin: Address,
698 recipients: &[Address],
699 mint_amount: U256,
700 salt_or_address: SaltOrAddress,
701 evm: &mut TempoEvm<CacheDB<EmptyDB>>,
702) -> eyre::Result<Address> {
703 let ctx = evm.ctx_mut();
704 StorageCtx::enter_evm(
705 &mut ctx.journaled_state,
706 &ctx.block,
707 &ctx.cfg,
708 &ctx.tx,
709 || {
710 let mut factory = TIP20Factory::new();
711 assert!(
712 factory
713 .is_initialized()
714 .expect("Could not check factory initialization"),
715 "TIP20Factory must be initialized before creating tokens"
716 );
717
718 let token_address = match salt_or_address {
719 SaltOrAddress::Salt(salt) => factory
720 .create_token(
721 admin,
722 ITIP20Factory::createTokenCall {
723 name: name.into(),
724 symbol: symbol.into(),
725 currency: currency.into(),
726 quoteToken: quote_token,
727 salt,
728 admin,
729 },
730 )
731 .expect("Could not create token"),
732 SaltOrAddress::Address(address) => factory
733 .create_token_reserved_address(
734 address,
735 name,
736 symbol,
737 currency,
738 quote_token,
739 admin,
740 )
741 .expect("Could not create token"),
742 };
743
744 let mut token =
745 TIP20Token::from_address(token_address).expect("Could not create token instance");
746 token.grant_role_internal(admin, *ISSUER_ROLE)?;
747
748 let result = token.set_supply_cap(
749 admin,
750 ITIP20::setSupplyCapCall {
751 newSupplyCap: U256::from(u128::MAX),
752 },
753 );
754 assert!(result.is_ok());
755
756 token
757 .mint(
758 admin,
759 ITIP20::mintCall {
760 to: admin,
761 amount: mint_amount,
762 },
763 )
764 .expect("Token minting failed");
765
766 for address in recipients.iter().progress() {
767 token
768 .mint(
769 admin,
770 ITIP20::mintCall {
771 to: *address,
772 amount: U256::from(u64::MAX),
773 },
774 )
775 .expect("Could not mint fee token");
776 }
777
778 Ok(token.address())
779 },
780 )
781}
782
783fn initialize_fee_manager(
784 validator_fee_token_address: Address,
785 user_fee_token_address: Address,
786 initial_accounts: Vec<Address>,
787 validators: Vec<Address>,
788 evm: &mut TempoEvm<CacheDB<EmptyDB>>,
789) {
790 let ctx = evm.ctx_mut();
792 StorageCtx::enter_evm(
793 &mut ctx.journaled_state,
794 &ctx.block,
795 &ctx.cfg,
796 &ctx.tx,
797 || {
798 let mut fee_manager = TipFeeManager::new();
799 fee_manager
800 .initialize()
801 .expect("Could not init fee manager");
802 println!(
803 "Setting user fee token {user_fee_token_address} for {} accounts",
804 initial_accounts.len()
805 );
806 for address in initial_accounts.iter().progress() {
807 fee_manager
808 .set_user_token(
809 *address,
810 IFeeManager::setUserTokenCall {
811 token: user_fee_token_address,
812 },
813 )
814 .expect("Could not set fee token");
815 }
816
817 for validator in validators {
819 println!("Setting user token for {validator} {validator_fee_token_address}");
820 fee_manager
821 .set_validator_token(
822 validator,
823 IFeeManager::setValidatorTokenCall {
824 token: validator_fee_token_address,
825 },
826 Address::random(),
828 )
829 .expect("Could not set validator fee token");
830 }
831 },
832 );
833}
834
835fn initialize_registry(evm: &mut TempoEvm<CacheDB<EmptyDB>>) -> eyre::Result<()> {
837 let ctx = evm.ctx_mut();
838 StorageCtx::enter_evm(
839 &mut ctx.journaled_state,
840 &ctx.block,
841 &ctx.cfg,
842 &ctx.tx,
843 || TIP403Registry::new().initialize(),
844 )?;
845
846 Ok(())
847}
848
849fn initialize_stablecoin_dex(evm: &mut TempoEvm<CacheDB<EmptyDB>>) -> eyre::Result<()> {
850 let ctx = evm.ctx_mut();
851 StorageCtx::enter_evm(
852 &mut ctx.journaled_state,
853 &ctx.block,
854 &ctx.cfg,
855 &ctx.tx,
856 || StablecoinDEX::new().initialize(),
857 )?;
858
859 Ok(())
860}
861
862fn initialize_nonce_manager(evm: &mut TempoEvm<CacheDB<EmptyDB>>) -> eyre::Result<()> {
863 let ctx = evm.ctx_mut();
864 StorageCtx::enter_evm(
865 &mut ctx.journaled_state,
866 &ctx.block,
867 &ctx.cfg,
868 &ctx.tx,
869 || NonceManager::new().initialize(),
870 )?;
871
872 Ok(())
873}
874
875fn initialize_account_keychain(evm: &mut TempoEvm<CacheDB<EmptyDB>>) -> eyre::Result<()> {
877 let ctx = evm.ctx_mut();
878 StorageCtx::enter_evm(
879 &mut ctx.journaled_state,
880 &ctx.block,
881 &ctx.cfg,
882 &ctx.tx,
883 || AccountKeychain::new().initialize(),
884 )?;
885
886 Ok(())
887}
888
889fn initialize_validator_config(
894 admin: Address,
895 evm: &mut TempoEvm<CacheDB<EmptyDB>>,
896 consensus_config: &Option<ConsensusConfig>,
897 onchain_validator_addresses: &[Address],
898 no_dkg_in_genesis: bool,
899) -> eyre::Result<()> {
900 let ctx = evm.ctx_mut();
901 StorageCtx::enter_evm(
902 &mut ctx.journaled_state,
903 &ctx.block,
904 &ctx.cfg,
905 &ctx.tx,
906 || {
907 let mut validator_config = ValidatorConfig::new();
908 validator_config
909 .initialize(admin)
910 .wrap_err("failed to initialize validator config contract")?;
911
912 if no_dkg_in_genesis {
913 println!("no-dkg-in-genesis passed; not writing validators to genesis block");
914 return Ok(());
915 }
916
917 if let Some(consensus_config) = consensus_config.clone() {
918 let num_validators = consensus_config.validators.len();
919
920 if onchain_validator_addresses.len() < num_validators {
921 return Err(eyre!(
922 "need {} addresses for all validators, but only {} were provided",
923 num_validators,
924 onchain_validator_addresses.len()
925 ));
926 }
927
928 println!("writing {num_validators} validators into contract");
929 for (i, validator) in consensus_config.validators.iter().enumerate() {
930 #[expect(non_snake_case, reason = "field of a snakeCase smart contract call")]
931 let newValidatorAddress = onchain_validator_addresses[i];
932 let public_key = validator.public_key();
933 let addr = validator.addr;
934 validator_config
935 .add_validator(
936 admin,
937 IValidatorConfig::addValidatorCall {
938 newValidatorAddress,
939 publicKey: public_key.encode().as_ref().try_into().unwrap(),
940 active: true,
941 inboundAddress: addr.to_string(),
942 outboundAddress: addr.to_string(),
943 },
944 )
945 .wrap_err(
946 "failed to execute smart contract call to add validator to evm state",
947 )?;
948 println!(
949 "added validator\
950 \n\tpublic key: {public_key}\
951 \n\tonchain address: {newValidatorAddress}\
952 \n\tnet address: {addr}"
953 );
954 }
955 } else {
956 println!("no consensus config passed; no validators to write to contract");
957 }
958
959 Ok(())
960 },
961 )
962}
963
964fn initialize_validator_config_v2(
969 admin: Address,
970 evm: &mut TempoEvm<CacheDB<EmptyDB>>,
971 consensus_config: &Option<ConsensusConfig>,
972 onchain_validator_addresses: &[Address],
973 no_dkg_in_genesis: bool,
974 chain_id: u64,
975) -> eyre::Result<()> {
976 let ctx = evm.ctx_mut();
977 StorageCtx::enter_evm(
978 &mut ctx.journaled_state,
979 &ctx.block,
980 &ctx.cfg,
981 &ctx.tx,
982 || {
983 let mut v2 = ValidatorConfigV2::new();
984 v2.initialize(admin)
985 .wrap_err("failed to initialize validator config v2")?;
986
987 if no_dkg_in_genesis {
988 println!("no-dkg-in-genesis passed; not writing validators to genesis block");
989 return Ok(());
990 }
991
992 let Some(consensus_config) = consensus_config.clone() else {
993 println!("no consensus config passed; no validators to write to contract");
994 return Ok(());
995 };
996
997 let num_validators = consensus_config.validators.len();
998 if onchain_validator_addresses.len() < num_validators {
999 return Err(eyre!(
1000 "need {} addresses for all validators, but only {} were provided",
1001 num_validators,
1002 onchain_validator_addresses.len()
1003 ));
1004 }
1005
1006 println!("writing {num_validators} validators into v2 contract");
1007 for (i, validator) in consensus_config.validators.iter().enumerate() {
1008 let validator_address = onchain_validator_addresses[i];
1009 let public_key = validator.public_key();
1010 let pubkey: B256 = public_key.encode().as_ref().try_into().unwrap();
1011 let addr = validator.addr;
1012
1013 let config = tempo_validator_config::ValidatorConfig {
1014 chain_id,
1015 validator_address,
1016 public_key: pubkey,
1017 ingress: addr,
1018 egress: addr.ip(),
1019 };
1020
1021 let message = config.add_validator_message_hash(validator_address);
1022 let private_key = validator.signing_key.clone().into_inner();
1023 let signature = private_key.sign(
1024 tempo_precompiles::validator_config_v2::VALIDATOR_NS_ADD,
1025 message.as_slice(),
1026 );
1027
1028 v2.add_validator(
1029 admin,
1030 IValidatorConfigV2::addValidatorCall {
1031 validatorAddress: validator_address,
1032 publicKey: pubkey,
1033 ingress: config.ingress.to_string(),
1034 egress: config.egress.to_string(),
1035 feeRecipient: validator_address,
1036 signature: signature.encode().to_vec().into(),
1037 },
1038 )
1039 .wrap_err("failed to add validator to V2")?;
1040
1041 println!(
1042 "added validator (v2)\
1043 \n\tpublic key: {public_key}\
1044 \n\tonchain address: {validator_address}\
1045 \n\tnet address: {addr}"
1046 );
1047 }
1048 Ok(())
1049 },
1050 )
1051}
1052
1053fn generate_consensus_config(
1055 validators: &[SocketAddr],
1056 seed: Option<u64>,
1057 no_dkg_in_genesis: bool,
1058) -> Option<ConsensusConfig> {
1059 use commonware_cryptography::ed25519::PrivateKey;
1060
1061 match (validators.is_empty(), no_dkg_in_genesis) {
1062 (_, true) => {
1063 println!(
1064 "no-dkg-in-genesis passed; not generating any consensus config because I can't write it to the genesis block"
1065 );
1066 return None;
1067 }
1068 (true, false) => {
1069 panic!("no validators provided and no-dkg-in-genesis not set");
1070 }
1071 _ => {}
1072 }
1073
1074 let mut rng = rand_08::rngs::StdRng::seed_from_u64(seed.unwrap_or_else(rand_08::random::<u64>));
1075
1076 let mut signer_keys = repeat_with(|| PrivateKey::random(&mut rng))
1077 .take(validators.len())
1078 .collect::<Vec<_>>();
1079 signer_keys.sort_by_key(|key| key.public_key());
1080
1081 let (output, shares) = dkg::deal::<_, _, N3f1>(
1082 &mut rng,
1083 Mode::NonZeroCounter,
1084 ordered::Set::try_from_iter(signer_keys.iter().map(|key| key.public_key())).unwrap(),
1085 )
1086 .unwrap();
1087
1088 let validators = validators
1089 .iter()
1090 .copied()
1091 .zip_eq(signer_keys)
1092 .zip_eq(shares)
1093 .map(|((addr, signing_key), (verifying_key, signing_share))| {
1094 assert_eq!(signing_key.public_key(), verifying_key);
1095 Validator {
1096 addr,
1097 signing_key: SigningKey::from(signing_key),
1098 signing_share: SigningShare::from(signing_share),
1099 }
1100 })
1101 .collect();
1102
1103 Some(ConsensusConfig { output, validators })
1104}
1105
1106fn mint_pairwise_liquidity(
1107 a_token: Address,
1108 b_tokens: Vec<Address>,
1109 amount: U256,
1110 admin: Address,
1111 evm: &mut TempoEvm<CacheDB<EmptyDB>>,
1112) {
1113 let ctx = evm.ctx_mut();
1114 StorageCtx::enter_evm(
1115 &mut ctx.journaled_state,
1116 &ctx.block,
1117 &ctx.cfg,
1118 &ctx.tx,
1119 || {
1120 let mut fee_manager = TipFeeManager::new();
1121
1122 for b_token_address in b_tokens {
1123 fee_manager
1124 .mint(admin, a_token, b_token_address, amount, admin)
1125 .expect("Could not mint A -> B Liquidity pool");
1126 }
1127 },
1128 );
1129}