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