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