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};
9use revm::state::Bytecode;
10use tempo_chainspec::hardfork::TempoHardfork;
11use tempo_contracts::{
12    ARACHNID_CREATE2_FACTORY_ADDRESS, CREATEX_ADDRESS, CreateX, MULTICALL3_ADDRESS, Multicall3,
13    PERMIT2_ADDRESS, Permit2, SAFE_DEPLOYER_ADDRESS, SafeDeployer,
14    contracts::ARACHNID_CREATE2_FACTORY_BYTECODE, precompiles::VALIDATOR_CONFIG_ADDRESS,
15};
16use tempo_precompiles::{
17    error::TempoPrecompileError,
18    storage::{PrecompileStorageProvider, StorageCtx},
19    tip20::{ISSUER_ROLE, ITIP20, TIP20Token},
20    tip20_factory::TIP20Factory,
21    validator_config,
22};
23
24pub use foundry_common::tempo::{
25    ALPHA_USD_ADDRESS, BETA_USD_ADDRESS, PATH_USD_ADDRESS, THETA_USD_ADDRESS,
26};
27pub use foundry_evm_networks::{
28    TEMPO_PRECOMPILE_ADDRESSES, active_tempo_precompile_addresses, is_tempo_precompile_active_at,
29};
30pub use tempo_contracts::precompiles::{
31    ADDRESS_REGISTRY_ADDRESS, IAddressRegistry, IFeeManager, ISignatureVerifier, IStablecoinDEX,
32    ITIP20ChannelReserve, RECEIVE_POLICY_GUARD_ADDRESS, SIGNATURE_VERIFIER_ADDRESS,
33    STABLECOIN_DEX_ADDRESS, TIP_FEE_MANAGER_ADDRESS, TIP20_CHANNEL_RESERVE_ADDRESS,
34    TIP20_FACTORY_ADDRESS,
35};
36pub use tempo_precompiles::{
37    address_registry::{AddressRegistry, IMPLICIT_APPROVAL_LIST, is_implicitly_approved},
38    signature_verifier::SignatureVerifier,
39    stablecoin_dex::StablecoinDEX,
40    tip_fee_manager::TipFeeManager,
41    tip20::is_tip20_prefix,
42    tip20_channel_reserve::TIP20ChannelReserve,
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    initialize_tempo_genesis_at_hardfork(storage, admin, recipient, TempoHardfork::default())
69}
70
71/// Initialize Tempo precompiles and contracts for a specific active hardfork.
72pub fn initialize_tempo_genesis_at_hardfork(
73    storage: &mut impl PrecompileStorageProvider,
74    admin: Address,
75    recipient: Address,
76    hardfork: TempoHardfork,
77) -> Result<(), TempoPrecompileError> {
78    StorageCtx::enter(storage, || initialize_tempo_genesis_inner(admin, recipient, hardfork))
79}
80
81/// Inner genesis initialization logic. Must be called within a [`StorageCtx`] scope
82/// (either via [`StorageCtx::enter`] or [`StorageCtx::enter_evm`]).
83pub fn initialize_tempo_genesis_inner(
84    admin: Address,
85    recipient: Address,
86    hardfork: TempoHardfork,
87) -> Result<(), TempoPrecompileError> {
88    initialize_tempo_genesis_inner_with_precompiles(
89        admin,
90        recipient,
91        active_tempo_precompile_addresses(hardfork),
92    )
93}
94
95/// Inner genesis initialization for Forge's local test EVM.
96///
97/// Forge tests use sentinel bytecode to identify well-known Tempo precompile accounts, even when
98/// the current setup spec is earlier than the precompile's activation hardfork. This does not
99/// affect hardfork-aware execution, which remains handled by the precompile lookup.
100pub fn initialize_tempo_test_genesis_inner(
101    admin: Address,
102    recipient: Address,
103) -> Result<(), TempoPrecompileError> {
104    initialize_tempo_genesis_inner_with_precompiles(
105        admin,
106        recipient,
107        TEMPO_PRECOMPILE_ADDRESSES.iter().copied(),
108    )
109}
110
111fn initialize_tempo_genesis_inner_with_precompiles(
112    admin: Address,
113    recipient: Address,
114    precompiles: impl IntoIterator<Item = Address>,
115) -> Result<(), TempoPrecompileError> {
116    // Idempotent: PATH_USD is the first token created during genesis; if it already exists, skip.
117    if TIP20Factory::new().is_tip20(PATH_USD_ADDRESS)? {
118        return Ok(());
119    }
120
121    let mut ctx = StorageCtx;
122
123    // Set sentinel bytecode for precompile addresses
124    let sentinel = Bytecode::new_legacy(Bytes::from_static(&[0xef]));
125    for precompile in precompiles {
126        ctx.set_code(precompile, sentinel.clone())?;
127    }
128
129    // Create PathUSD token: 0x20C0000000000000000000000000000000000000
130    let path_usd_token_address = create_and_mint_token(
131        PATH_USD_ADDRESS,
132        "PathUSD",
133        "PathUSD",
134        "USD",
135        Address::ZERO,
136        admin,
137        recipient,
138        U256::from(u64::MAX),
139    )?;
140
141    // Create AlphaUSD token: 0x20C0000000000000000000000000000000000001
142    let _alpha_usd_token_address = create_and_mint_token(
143        ALPHA_USD_ADDRESS,
144        "AlphaUSD",
145        "AlphaUSD",
146        "USD",
147        path_usd_token_address,
148        admin,
149        recipient,
150        U256::from(u64::MAX),
151    )?;
152
153    // Create BetaUSD token: 0x20C0000000000000000000000000000000000002
154    let _beta_usd_token_address = create_and_mint_token(
155        BETA_USD_ADDRESS,
156        "BetaUSD",
157        "BetaUSD",
158        "USD",
159        path_usd_token_address,
160        admin,
161        recipient,
162        U256::from(u64::MAX),
163    )?;
164
165    // Create ThetaUSD token: 0x20C0000000000000000000000000000000000003
166    let _theta_usd_token_address = create_and_mint_token(
167        THETA_USD_ADDRESS,
168        "ThetaUSD",
169        "ThetaUSD",
170        "USD",
171        path_usd_token_address,
172        admin,
173        recipient,
174        U256::from(u64::MAX),
175    )?;
176
177    // Initialize ValidatorConfig with admin as owner
178    ctx.sstore(VALIDATOR_CONFIG_ADDRESS, validator_config::slots::OWNER, admin.into_word().into())?;
179
180    // Set bytecode for standard contracts
181    ctx.set_code(
182        MULTICALL3_ADDRESS,
183        Bytecode::new_legacy(Bytes::from_static(&Multicall3::DEPLOYED_BYTECODE)),
184    )?;
185    ctx.set_code(
186        CREATEX_ADDRESS,
187        Bytecode::new_legacy(Bytes::from_static(&CreateX::DEPLOYED_BYTECODE)),
188    )?;
189    ctx.set_code(
190        SAFE_DEPLOYER_ADDRESS,
191        Bytecode::new_legacy(Bytes::from_static(&SafeDeployer::DEPLOYED_BYTECODE)),
192    )?;
193    ctx.set_code(
194        PERMIT2_ADDRESS,
195        Bytecode::new_legacy(Bytes::from_static(&Permit2::DEPLOYED_BYTECODE)),
196    )?;
197    ctx.set_code(
198        ARACHNID_CREATE2_FACTORY_ADDRESS,
199        Bytecode::new_legacy(ARACHNID_CREATE2_FACTORY_BYTECODE),
200    )?;
201
202    Ok(())
203}
204
205/// Helper function to create and mint a TIP20 token.
206#[allow(clippy::too_many_arguments)]
207fn create_and_mint_token(
208    address: Address,
209    symbol: &str,
210    name: &str,
211    currency: &str,
212    quote_token: Address,
213    admin: Address,
214    recipient: Address,
215    mint_amount: U256,
216) -> Result<Address, TempoPrecompileError> {
217    let mut tip20_factory = TIP20Factory::new();
218
219    let token_address = tip20_factory.create_token_reserved_address(
220        address,
221        name,
222        symbol,
223        currency,
224        quote_token,
225        admin,
226    )?;
227
228    let mut token = TIP20Token::from_address(token_address)?;
229    token.grant_role_internal(admin, *ISSUER_ROLE)?;
230    token.mint(admin, ITIP20::mintCall { to: recipient, amount: mint_amount })?;
231    if admin != recipient {
232        token.mint(admin, ITIP20::mintCall { to: admin, amount: mint_amount })?;
233    }
234
235    Ok(token_address)
236}