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