Skip to main content

anvil/eth/backend/
tempo.rs

1//! Tempo precompile and fee token initialization for Anvil.
2//!
3//! When running in Tempo mode, Anvil needs to set up Tempo-specific precompiles
4//! and fee tokens (PathUSD, AlphaUSD, BetaUSD, ThetaUSD) to enable proper
5//! transaction validation.
6//!
7//! This module provides a storage provider adapter for Anvil's `Db` trait and
8//! uses the shared initialization logic from `foundry-evm-core`.
9
10use alloy_primitives::{Address, U256, address};
11use foundry_evm::core::tempo::{
12    ALPHA_USD_ADDRESS, BETA_USD_ADDRESS, PATH_USD_ADDRESS, THETA_USD_ADDRESS,
13    initialize_tempo_genesis,
14};
15use revm::{
16    context::journaled_state::JournalCheckpoint,
17    state::{AccountInfo, Bytecode},
18};
19use std::collections::HashMap;
20use tempo_chainspec::hardfork::TempoHardfork;
21use tempo_precompiles::{
22    TIP_FEE_MANAGER_ADDRESS,
23    account_keychain::{
24        AccountKeychain,
25        IAccountKeychain::{SignatureType, authorizeKeyCall},
26    },
27    error::TempoPrecompileError,
28    storage::{PrecompileStorageProvider, StorageCtx},
29    tip_fee_manager::{IFeeManager, TipFeeManager},
30    tip20::{ITIP20, TIP20Token},
31};
32
33use super::db::Db;
34
35/// Sender address used for genesis initialization.
36const SENDER: Address = address!("0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38");
37/// Admin address used for genesis initialization.
38const ADMIN: Address = address!("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f");
39
40const PATH_USD: Address = PATH_USD_ADDRESS;
41const ALPHA_USD: Address = ALPHA_USD_ADDRESS;
42const BETA_USD: Address = BETA_USD_ADDRESS;
43const THETA_USD: Address = THETA_USD_ADDRESS;
44
45/// Storage provider adapter for Anvil's Db to work with Tempo precompiles.
46pub struct AnvilStorageProvider<'a> {
47    db: &'a mut dyn Db,
48    chain_id: u64,
49    timestamp: U256,
50    block_number: u64,
51    gas_used: u64,
52    gas_refunded: i64,
53    transient: HashMap<(Address, U256), U256>,
54    hardfork: TempoHardfork,
55}
56
57impl<'a> AnvilStorageProvider<'a> {
58    pub fn new(
59        db: &'a mut dyn Db,
60        chain_id: u64,
61        timestamp: U256,
62        block_number: u64,
63        hardfork: TempoHardfork,
64    ) -> Self {
65        Self {
66            db,
67            chain_id,
68            timestamp,
69            block_number,
70            gas_used: 0,
71            gas_refunded: 0,
72            transient: HashMap::new(),
73            hardfork,
74        }
75    }
76}
77
78impl PrecompileStorageProvider for AnvilStorageProvider<'_> {
79    fn spec(&self) -> TempoHardfork {
80        self.hardfork
81    }
82
83    fn chain_id(&self) -> u64 {
84        self.chain_id
85    }
86
87    fn timestamp(&self) -> U256 {
88        self.timestamp
89    }
90
91    fn block_number(&self) -> u64 {
92        self.block_number
93    }
94
95    fn set_code(&mut self, address: Address, code: Bytecode) -> Result<(), TempoPrecompileError> {
96        self.db.insert_account(
97            address,
98            AccountInfo {
99                code_hash: code.hash_slow(),
100                code: Some(code),
101                nonce: 1,
102                ..Default::default()
103            },
104        );
105        Ok(())
106    }
107
108    fn with_account_info(
109        &mut self,
110        address: Address,
111        f: &mut dyn FnMut(&AccountInfo),
112    ) -> Result<(), TempoPrecompileError> {
113        use revm::DatabaseRef;
114        if let Some(info) =
115            self.db.basic_ref(address).map_err(|e| TempoPrecompileError::Fatal(e.to_string()))?
116        {
117            f(&info);
118            Ok(())
119        } else {
120            Err(TempoPrecompileError::Fatal(format!("account '{address}' not found")))
121        }
122    }
123
124    fn sstore(
125        &mut self,
126        address: Address,
127        key: U256,
128        value: U256,
129    ) -> Result<(), TempoPrecompileError> {
130        use alloy_primitives::B256;
131        self.db
132            .set_storage_at(address, B256::from(key), B256::from(value))
133            .map_err(|e| TempoPrecompileError::Fatal(e.to_string()))
134    }
135
136    fn sload(&mut self, address: Address, key: U256) -> Result<U256, TempoPrecompileError> {
137        revm::Database::storage(self.db, address, key)
138            .map_err(|e| TempoPrecompileError::Fatal(e.to_string()))
139    }
140
141    fn tstore(
142        &mut self,
143        address: Address,
144        key: U256,
145        value: U256,
146    ) -> Result<(), TempoPrecompileError> {
147        self.transient.insert((address, key), value);
148        Ok(())
149    }
150
151    fn tload(&mut self, address: Address, key: U256) -> Result<U256, TempoPrecompileError> {
152        Ok(self.transient.get(&(address, key)).copied().unwrap_or(U256::ZERO))
153    }
154
155    fn emit_event(
156        &mut self,
157        _address: Address,
158        _event: alloy_primitives::LogData,
159    ) -> Result<(), TempoPrecompileError> {
160        Ok(())
161    }
162
163    fn deduct_gas(&mut self, gas: u64) -> Result<(), TempoPrecompileError> {
164        self.gas_used = self.gas_used.saturating_add(gas);
165        Ok(())
166    }
167
168    fn gas_used(&self) -> u64 {
169        self.gas_used
170    }
171
172    fn gas_refunded(&self) -> i64 {
173        self.gas_refunded
174    }
175
176    fn refund_gas(&mut self, gas: i64) {
177        self.gas_refunded = self.gas_refunded.saturating_add(gas);
178    }
179
180    fn beneficiary(&self) -> Address {
181        Address::ZERO
182    }
183
184    fn is_static(&self) -> bool {
185        false
186    }
187
188    fn checkpoint(&mut self) -> JournalCheckpoint {
189        JournalCheckpoint { log_i: 0, journal_i: 0, selfdestructed_i: 0 }
190    }
191
192    fn checkpoint_commit(&mut self, _checkpoint: JournalCheckpoint) {}
193
194    fn checkpoint_revert(&mut self, _checkpoint: JournalCheckpoint) {}
195}
196
197/// Initialize Tempo precompiles and fee tokens for Anvil.
198///
199/// This sets up the same precompiles and tokens as Tempo's genesis, enabling
200/// proper fee token validation for transactions.
201///
202/// Additionally, mints fee tokens to the provided test accounts so they can
203/// send transactions in Tempo mode.
204pub fn initialize_tempo_precompiles(
205    db: &mut dyn Db,
206    chain_id: u64,
207    timestamp: u64,
208    test_accounts: &[Address],
209    hardfork: TempoHardfork,
210) -> Result<(), TempoPrecompileError> {
211    let timestamp = U256::from(timestamp);
212
213    let mut storage = AnvilStorageProvider::new(db, chain_id, timestamp, 0, hardfork);
214
215    // Initialize base Tempo genesis (precompiles and tokens)
216    initialize_tempo_genesis(&mut storage, ADMIN, SENDER)?;
217
218    // Mint fee tokens to test accounts
219    // u64::MAX per account - safe since u128::MAX can hold ~18 quintillion u64::MAX values
220    let mint_amount = U256::from(u64::MAX);
221    let tokens = [PATH_USD, ALPHA_USD, BETA_USD, THETA_USD];
222
223    StorageCtx::enter(&mut storage, || -> Result<(), TempoPrecompileError> {
224        // Mint fee tokens to test accounts
225        for &token_address in &tokens {
226            let mut token = TIP20Token::from_address(token_address)?;
227            for &account in test_accounts {
228                token.mint(ADMIN, ITIP20::mintCall { to: account, amount: mint_amount })?;
229            }
230        }
231
232        // Register secp256k1 keys for test accounts in the AccountKeychain
233        // This allows them to sign Tempo transactions using their private keys.
234        // The key ID is the account address itself (standard for secp256k1 keys).
235        let mut keychain = AccountKeychain::new();
236        for &account in test_accounts {
237            // Seed tx_origin so ensure_admin_caller passes on T2+ (requires
238            // tx_origin != zero && tx_origin == msg_sender).
239            keychain.set_tx_origin(account)?;
240            keychain.authorize_key(
241                account, // msg_sender (root account authorizes its own key)
242                authorizeKeyCall {
243                    keyId: account, // key ID = account address for secp256k1
244                    signatureType: SignatureType::Secp256k1,
245                    expiry: u64::MAX,     // never expires
246                    enforceLimits: false, // no spending limits
247                    limits: vec![],
248                },
249            )?;
250        }
251
252        // Initialize TipFeeManager and set default fee tokens for test accounts
253        // Alice (0) -> AlphaUSD, Bob (1) -> BetaUSD, Charlie (2) -> ThetaUSD, others -> PathUSD
254        let mut fee_manager = TipFeeManager::new();
255        fee_manager.initialize()?;
256
257        for (i, &account) in test_accounts.iter().enumerate() {
258            let fee_token = match i {
259                0 => ALPHA_USD, // Alice
260                1 => BETA_USD,  // Bob
261                2 => THETA_USD, // Charlie
262                _ => PATH_USD,  // Everyone else
263            };
264            fee_manager
265                .set_user_token(account, IFeeManager::setUserTokenCall { token: fee_token })?;
266        }
267
268        // Mint fee tokens to the FeeManager contract for liquidity operations
269        for &token_address in &tokens {
270            let mut token = TIP20Token::from_address(token_address)?;
271            token.mint(
272                ADMIN,
273                ITIP20::mintCall { to: TIP_FEE_MANAGER_ADDRESS, amount: mint_amount },
274            )?;
275        }
276
277        // Mint pairwise FeeAMM liquidity for all fee token pairs (both directions)
278        // This enables EIP-1559/legacy transactions by allowing fee swaps between tokens
279        // Liquidity amount: 10^10 tokens (matching Tempo genesis)
280        let liquidity_amount = U256::from(10u64.pow(10));
281
282        // Create bidirectional liquidity pools between all fee tokens
283        // Pools are directional: user_token -> validator_token
284        for &user_token in &tokens {
285            for &validator_token in &tokens {
286                if user_token != validator_token {
287                    fee_manager.mint(
288                        ADMIN,
289                        user_token,
290                        validator_token,
291                        liquidity_amount,
292                        ADMIN,
293                    )?;
294                }
295            }
296        }
297
298        Ok(())
299    })?;
300
301    Ok(())
302}