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 for a specific active hardfork.
52pub fn initialize_tempo_genesis_at_hardfork(
53    storage: &mut impl PrecompileStorageProvider,
54    admin: Address,
55    recipient: Address,
56    hardfork: TempoHardfork,
57) -> Result<(), TempoPrecompileError> {
58    StorageCtx::enter(storage, || initialize_tempo_genesis_inner(admin, recipient, hardfork))
59}
60
61/// Inner genesis initialization logic. Must be called within a [`StorageCtx`] scope
62/// (either via [`StorageCtx::enter`] or [`StorageCtx::enter_evm`]).
63pub fn initialize_tempo_genesis_inner(
64    admin: Address,
65    recipient: Address,
66    hardfork: TempoHardfork,
67) -> Result<(), TempoPrecompileError> {
68    initialize_tempo_genesis_inner_with_precompiles(
69        admin,
70        recipient,
71        active_tempo_precompile_addresses(hardfork),
72    )
73}
74
75/// Inner genesis initialization for Forge's local test EVM.
76///
77/// Forge tests use sentinel bytecode to identify well-known Tempo precompile accounts, even when
78/// the current setup spec is earlier than the precompile's activation hardfork. This does not
79/// affect hardfork-aware execution, which remains handled by the precompile lookup.
80pub fn initialize_tempo_test_genesis_inner(
81    admin: Address,
82    recipient: Address,
83) -> Result<(), TempoPrecompileError> {
84    initialize_tempo_genesis_inner_with_precompiles(
85        admin,
86        recipient,
87        TEMPO_PRECOMPILE_ADDRESSES.iter().copied(),
88    )
89}
90
91fn initialize_tempo_genesis_inner_with_precompiles(
92    admin: Address,
93    recipient: Address,
94    precompiles: impl IntoIterator<Item = Address>,
95) -> Result<(), TempoPrecompileError> {
96    // Idempotent: PATH_USD is the first token created during genesis; if it already exists, skip.
97    if TIP20Factory::new().is_tip20(PATH_USD_ADDRESS)? {
98        return Ok(());
99    }
100
101    let mut ctx = StorageCtx;
102
103    // Set sentinel bytecode for precompile addresses
104    let sentinel = Bytecode::new_legacy(Bytes::from_static(SYSTEM_PRECOMPILE_STUB));
105    for precompile in precompiles {
106        ctx.set_code(precompile, sentinel.clone())?;
107    }
108
109    // Create PathUSD token: 0x20C0000000000000000000000000000000000000
110    let path_usd_token_address = create_and_mint_token(
111        PATH_USD_ADDRESS,
112        "PathUSD",
113        "PathUSD",
114        "USD",
115        Address::ZERO,
116        admin,
117        recipient,
118        U256::from(u64::MAX),
119    )?;
120
121    // Create AlphaUSD token: 0x20C0000000000000000000000000000000000001
122    let _alpha_usd_token_address = create_and_mint_token(
123        ALPHA_USD_ADDRESS,
124        "AlphaUSD",
125        "AlphaUSD",
126        "USD",
127        path_usd_token_address,
128        admin,
129        recipient,
130        U256::from(u64::MAX),
131    )?;
132
133    // Create BetaUSD token: 0x20C0000000000000000000000000000000000002
134    let _beta_usd_token_address = create_and_mint_token(
135        BETA_USD_ADDRESS,
136        "BetaUSD",
137        "BetaUSD",
138        "USD",
139        path_usd_token_address,
140        admin,
141        recipient,
142        U256::from(u64::MAX),
143    )?;
144
145    // Create ThetaUSD token: 0x20C0000000000000000000000000000000000003
146    let _theta_usd_token_address = create_and_mint_token(
147        THETA_USD_ADDRESS,
148        "ThetaUSD",
149        "ThetaUSD",
150        "USD",
151        path_usd_token_address,
152        admin,
153        recipient,
154        U256::from(u64::MAX),
155    )?;
156
157    // Initialize ValidatorConfig with admin as owner
158    ctx.sstore(VALIDATOR_CONFIG_ADDRESS, validator_config::slots::OWNER, admin.into_word().into())?;
159
160    // Set bytecode for standard contracts
161    ctx.set_code(
162        MULTICALL3_ADDRESS,
163        Bytecode::new_legacy(Bytes::from_static(&Multicall3::DEPLOYED_BYTECODE)),
164    )?;
165    ctx.set_code(
166        CREATEX_ADDRESS,
167        Bytecode::new_legacy(Bytes::from_static(&CreateX::DEPLOYED_BYTECODE)),
168    )?;
169    ctx.set_code(
170        SAFE_DEPLOYER_ADDRESS,
171        Bytecode::new_legacy(Bytes::from_static(&SafeDeployer::DEPLOYED_BYTECODE)),
172    )?;
173    ctx.set_code(
174        PERMIT2_ADDRESS,
175        Bytecode::new_legacy(Bytes::from_static(&Permit2::DEPLOYED_BYTECODE)),
176    )?;
177    ctx.set_code(
178        ARACHNID_CREATE2_FACTORY_ADDRESS,
179        Bytecode::new_legacy(ARACHNID_CREATE2_FACTORY_BYTECODE),
180    )?;
181
182    Ok(())
183}
184
185/// Helper function to create and mint a TIP20 token.
186#[allow(clippy::too_many_arguments)]
187fn create_and_mint_token(
188    address: Address,
189    symbol: &str,
190    name: &str,
191    currency: &str,
192    quote_token: Address,
193    admin: Address,
194    recipient: Address,
195    mint_amount: U256,
196) -> Result<Address, TempoPrecompileError> {
197    let mut tip20_factory = TIP20Factory::new();
198
199    let token_address = tip20_factory.create_token_reserved_address(
200        address,
201        name,
202        symbol,
203        currency,
204        quote_token,
205        admin,
206    )?;
207
208    let mut token = TIP20Token::from_address(token_address)?;
209    token.grant_role_internal(admin, TIP20Token::issuer_role())?;
210    token.mint(admin, ITIP20::mintCall { to: recipient, amount: mint_amount })?;
211    if admin != recipient {
212        token.mint(admin, ITIP20::mintCall { to: admin, amount: mint_amount })?;
213    }
214
215    Ok(token_address)
216}