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