Skip to main content

foundry_evm_core/
tempo.rs

1//! Tempo precompile and contract initialization for Foundry.
2//!
3//! This module provides the core initialization logic for Tempo-specific precompiles,
4//! fee tokens (PathUSD, AlphaUSD, BetaUSD, ThetaUSD), and standard contracts.
5//!
6//! It includes the shared genesis initialization function used by both anvil and forge.
7
8use alloy_primitives::{Address, Bytes, U256, address};
9use revm::state::Bytecode;
10use tempo_contracts::{
11    ARACHNID_CREATE2_FACTORY_ADDRESS, CREATEX_ADDRESS, CreateX, MULTICALL3_ADDRESS, Multicall3,
12    PERMIT2_ADDRESS, Permit2, SAFE_DEPLOYER_ADDRESS, SafeDeployer,
13    contracts::ARACHNID_CREATE2_FACTORY_BYTECODE,
14    precompiles::{
15        ACCOUNT_KEYCHAIN_ADDRESS, ADDRESS_REGISTRY_ADDRESS, NONCE_PRECOMPILE_ADDRESS,
16        SIGNATURE_VERIFIER_ADDRESS, STABLECOIN_DEX_ADDRESS, TIP_FEE_MANAGER_ADDRESS,
17        TIP20_FACTORY_ADDRESS, TIP403_REGISTRY_ADDRESS, VALIDATOR_CONFIG_ADDRESS,
18        VALIDATOR_CONFIG_V2_ADDRESS,
19    },
20};
21use tempo_precompiles::{
22    error::TempoPrecompileError,
23    storage::{PrecompileStorageProvider, StorageCtx},
24    tip20::{ISSUER_ROLE, ITIP20, TIP20Token},
25    tip20_factory::TIP20Factory,
26    validator_config,
27};
28
29pub use tempo_contracts::precompiles::PATH_USD_ADDRESS;
30
31/// All well-known Tempo precompile addresses.
32pub const TEMPO_PRECOMPILE_ADDRESSES: &[Address] = &[
33    NONCE_PRECOMPILE_ADDRESS,
34    STABLECOIN_DEX_ADDRESS,
35    TIP20_FACTORY_ADDRESS,
36    TIP403_REGISTRY_ADDRESS,
37    TIP_FEE_MANAGER_ADDRESS,
38    VALIDATOR_CONFIG_ADDRESS,
39    VALIDATOR_CONFIG_V2_ADDRESS,
40    ACCOUNT_KEYCHAIN_ADDRESS,
41    SIGNATURE_VERIFIER_ADDRESS,
42    ADDRESS_REGISTRY_ADDRESS,
43];
44
45/// All well-known TIP20 fee token addresses on Tempo networks.
46pub const TEMPO_TIP20_TOKENS: &[Address] = &[PATH_USD_ADDRESS];
47
48/// Initialize Tempo precompiles and contracts using a storage provider.
49///
50/// This is the core initialization logic that sets up Tempo-specific precompiles,
51/// fee tokens (PathUSD, AlphaUSD, BetaUSD, ThetaUSD), and standard contracts.
52///
53/// This function should be called during genesis setup when running in Tempo mode.
54/// It uses the `StorageCtx` pattern to work with any storage backend that implements
55/// `PrecompileStorageProvider`.
56///
57/// # Arguments
58/// * `storage` - A mutable reference to a storage provider implementing `PrecompileStorageProvider`
59/// * `admin` - The admin address that will have control over tokens and config
60/// * `recipient` - The address that will receive minted tokens
61///
62/// Ref: <https://github.com/tempoxyz/tempo/blob/main/xtask/src/genesis_args.rs>
63pub fn initialize_tempo_genesis(
64    storage: &mut impl PrecompileStorageProvider,
65    admin: Address,
66    recipient: Address,
67) -> Result<(), TempoPrecompileError> {
68    StorageCtx::enter(storage, || initialize_tempo_genesis_inner(admin, recipient))
69}
70
71/// Inner genesis initialization logic. Must be called within a [`StorageCtx`] scope
72/// (either via [`StorageCtx::enter`] or [`StorageCtx::enter_evm`]).
73pub fn initialize_tempo_genesis_inner(
74    admin: Address,
75    recipient: Address,
76) -> Result<(), TempoPrecompileError> {
77    // Idempotent: PATH_USD is the first token created during genesis; if it already exists, skip.
78    if TIP20Factory::new().is_tip20(PATH_USD_ADDRESS)? {
79        return Ok(());
80    }
81
82    let mut ctx = StorageCtx;
83
84    // Set sentinel bytecode for precompile addresses
85    let sentinel = Bytecode::new_legacy(Bytes::from_static(&[0xef]));
86    for precompile in TEMPO_PRECOMPILE_ADDRESSES {
87        ctx.set_code(*precompile, sentinel.clone())?;
88    }
89
90    // Create PathUSD token: 0x20C0000000000000000000000000000000000000
91    let path_usd_token_address = create_and_mint_token(
92        PATH_USD_ADDRESS,
93        "PathUSD",
94        "PathUSD",
95        "USD",
96        Address::ZERO,
97        admin,
98        recipient,
99        U256::from(u64::MAX),
100    )?;
101
102    // Create AlphaUSD token: 0x20C0000000000000000000000000000000000001
103    let _alpha_usd_token_address = create_and_mint_token(
104        address!("20C0000000000000000000000000000000000001"),
105        "AlphaUSD",
106        "AlphaUSD",
107        "USD",
108        path_usd_token_address,
109        admin,
110        recipient,
111        U256::from(u64::MAX),
112    )?;
113
114    // Create BetaUSD token: 0x20C0000000000000000000000000000000000002
115    let _beta_usd_token_address = create_and_mint_token(
116        address!("20C0000000000000000000000000000000000002"),
117        "BetaUSD",
118        "BetaUSD",
119        "USD",
120        path_usd_token_address,
121        admin,
122        recipient,
123        U256::from(u64::MAX),
124    )?;
125
126    // Create ThetaUSD token: 0x20C0000000000000000000000000000000000003
127    let _theta_usd_token_address = create_and_mint_token(
128        address!("20C0000000000000000000000000000000000003"),
129        "ThetaUSD",
130        "ThetaUSD",
131        "USD",
132        path_usd_token_address,
133        admin,
134        recipient,
135        U256::from(u64::MAX),
136    )?;
137
138    // Initialize ValidatorConfig with admin as owner
139    ctx.sstore(VALIDATOR_CONFIG_ADDRESS, validator_config::slots::OWNER, admin.into_word().into())?;
140
141    // Set bytecode for standard contracts
142    ctx.set_code(
143        MULTICALL3_ADDRESS,
144        Bytecode::new_legacy(Bytes::from_static(&Multicall3::DEPLOYED_BYTECODE)),
145    )?;
146    ctx.set_code(
147        CREATEX_ADDRESS,
148        Bytecode::new_legacy(Bytes::from_static(&CreateX::DEPLOYED_BYTECODE)),
149    )?;
150    ctx.set_code(
151        SAFE_DEPLOYER_ADDRESS,
152        Bytecode::new_legacy(Bytes::from_static(&SafeDeployer::DEPLOYED_BYTECODE)),
153    )?;
154    ctx.set_code(
155        PERMIT2_ADDRESS,
156        Bytecode::new_legacy(Bytes::from_static(&Permit2::DEPLOYED_BYTECODE)),
157    )?;
158    ctx.set_code(
159        ARACHNID_CREATE2_FACTORY_ADDRESS,
160        Bytecode::new_legacy(ARACHNID_CREATE2_FACTORY_BYTECODE),
161    )?;
162
163    Ok(())
164}
165
166/// Helper function to create and mint a TIP20 token.
167#[allow(clippy::too_many_arguments)]
168fn create_and_mint_token(
169    address: Address,
170    symbol: &str,
171    name: &str,
172    currency: &str,
173    quote_token: Address,
174    admin: Address,
175    recipient: Address,
176    mint_amount: U256,
177) -> Result<Address, TempoPrecompileError> {
178    let mut tip20_factory = TIP20Factory::new();
179
180    let token_address = tip20_factory.create_token_reserved_address(
181        address,
182        name,
183        symbol,
184        currency,
185        quote_token,
186        admin,
187    )?;
188
189    let mut token = TIP20Token::from_address(token_address)?;
190    token.grant_role_internal(admin, *ISSUER_ROLE)?;
191    token.mint(admin, ITIP20::mintCall { to: recipient, amount: mint_amount })?;
192    if admin != recipient {
193        token.mint(admin, ITIP20::mintCall { to: admin, amount: mint_amount })?;
194    }
195
196    Ok(token_address)
197}