Skip to main content

tempo_xtask/
genesis_args.rs

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/// Generate genesis allocation file for testing
65#[derive(Debug, clap::Args)]
66pub(crate) struct GenesisArgs {
67    /// Number of accounts to generate
68    #[arg(short, long, default_value = "50000")]
69    accounts: u32,
70
71    /// Mnemonic to use for account generation
72    #[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    /// Coinbase address
80    #[arg(long, default_value = "0x0000000000000000000000000000000000000000")]
81    coinbase: Address,
82
83    /// Chain ID
84    #[arg(long, short, default_value = "1337")]
85    chain_id: u64,
86
87    /// Genesis block gas limit
88    #[arg(long, default_value_t = 500_000_000)]
89    gas_limit: u64,
90
91    /// The hard-coded length of an epoch in blocks.
92    #[arg(long, default_value_t = 302_400)]
93    epoch_length: u64,
94
95    /// A comma-separated list of `<ip>:<port>`.
96    #[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    /// Will not write the initial DKG outcome into the extra_data field of
105    /// the genesis header.
106    #[arg(long)]
107    no_dkg_in_genesis: bool,
108
109    /// A fixed seed to generate all signing keys and group shares. This is
110    /// intended for use in development and testing. Use at your own peril.
111    #[arg(long)]
112    pub(crate) seed: Option<u64>,
113
114    /// Custom admin address for pathUSD token.
115    /// If not set, uses the first generated account.
116    #[arg(long)]
117    pathusd_admin: Option<Address>,
118
119    #[arg(long, default_value_t = u64::MAX)]
120    pathusd_amount: u64,
121
122    /// Custom admin address for validator config.
123    /// If not set, uses the first generated account.
124    #[arg(long)]
125    validator_admin: Option<Address>,
126
127    /// Custom onchain addresses for validators.
128    /// Must match the number of validators if provided.
129    #[arg(long, value_delimiter = ',')]
130    validator_addresses: Vec<Address>,
131
132    /// Disable creating Alpha/Beta/ThetaUSD tokens.
133    #[arg(long)]
134    no_extra_tokens: bool,
135
136    /// Enable creating deployment gas token.
137    #[arg(long)]
138    deployment_gas_token: bool,
139
140    /// Custom admin address for deployment gas token.
141    #[arg(long)]
142    deployment_gas_token_admin: Option<Address>,
143
144    /// Disable minting pairwise FeeAMM liquidity.
145    #[arg(long)]
146    no_pairwise_liquidity: bool,
147
148    /// Timestamp for T0 hardfork activation (0 = genesis).
149    #[arg(long, default_value = "0")]
150    t0_time: u64,
151
152    /// T1 hardfork activation time.
153    #[arg(long, default_value = "0")]
154    t1_time: u64,
155
156    /// T1.A hardfork activation time.
157    #[arg(long, default_value = "0")]
158    t1a_time: u64,
159
160    /// T1.B hardfork activation time.
161    #[arg(long, default_value = "0")]
162    t1b_time: u64,
163
164    /// T1.C hardfork activation time.
165    #[arg(long, default_value = "0")]
166    t1c_time: u64,
167
168    /// T2 hardfork activation time.
169    #[arg(long, default_value = "0")]
170    t2_time: u64,
171
172    /// Whether to skip initializing validator config v1
173    #[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    /// Generates a genesis json file.
222    ///
223    /// It creates a new genesis allocation for the configured accounts.
224    /// And creates accounts for system contracts.
225    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        // system contracts/precompiles must be initialized bottom up, if an init function (e.g. mint_pairwise_liquidity) uses another system contract/precompiles internally (tip403 registry), the registry must be initialized first.
239
240        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        // Initialize TIP20Factory once before creating any tokens
251        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            // TODO: also populate validators here, once the logic is back.
402            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        // Save EVM state to allocation
442        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        // Base fee determined by hardfork: T1 active at genesis (t1_time=0) uses T1 fee
555        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    // revm sets timestamp to 1 by default, override it to 0 for genesis initializations
578    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
585/// Deploys the Arachnid CREATE2 factory by directly inserting it into the EVM state.
586fn 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
599/// Deploys Permit2 contract via the Arachnid CREATE2 factory.
600fn deploy_permit2(evm: &mut TempoEvm<CacheDB<EmptyDB>>) -> eyre::Result<()> {
601    // Build calldata for Arachnid CREATE2 factory: salt (32 bytes) || creation bytecode
602    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
625/// Initializes the TIP20Factory contract (should be called once before creating any tokens)
626fn 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
638/// Creates pathUSD as the first TIP20 token at a reserved address.
639/// pathUSD is not created via factory since it's at a reserved address.
640fn 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            // Initialize pathUSD directly (not via factory) since it's at a reserved address.
663            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            // Mint to all recipients
668            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/// Creates a TIP20 token through the factory (factory must already be initialized)
691#[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    // Update the beneficiary since the validator can't set the validator fee token for themselves
791    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            // Set validator fee tokens to pathUSD
818            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                        // use random address to avoid `CannotChangeWithinBlock` error
827                        Address::random(),
828                    )
829                    .expect("Could not set validator fee token");
830            }
831        },
832    );
833}
834
835/// Initializes the [`TIP403Registry`] contract.
836fn 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
875/// Initializes the [`AccountKeychain`] contract.
876fn 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
889/// Initializes the initial validator config smart contract.
890///
891/// NOTE: Does not populate it at all because consensus does not read the
892/// validators at genesis.
893fn 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
964/// Initializes the [`ValidatorConfigV2`] contract at genesis (T2 active at genesis).
965///
966/// Populates validators directly into V2 with `needs_migration = false`.
967/// Each `add_validator` call requires an Ed25519 signature from the validator's signing key.
968fn 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
1053/// Generates the consensus configs of the validators.
1054fn 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}