1use 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_at_hardfork,
14};
15use revm::{
16 context::{BlockEnv, journaled_state::JournalCheckpoint},
17 state::{AccountInfo, Bytecode},
18};
19use std::collections::HashMap;
20use tempo_hardfork::TempoHardfork;
21use tempo_precompiles::{
22 TIP_FEE_MANAGER_ADDRESS,
23 account_keychain::{
24 AccountKeychain,
25 IAccountKeychain::{KeyRestrictions, SignatureType},
26 },
27 error::TempoPrecompileError,
28 storage::{PrecompileStorageProvider, StorageCtx},
29 tip_fee_manager::{IFeeManager, TipFeeManager},
30 tip20::{ITIP20, TIP20Token},
31};
32use tempo_primitives::TempoBlockEnv;
33
34use super::db::Db;
35
36const SENDER: Address = address!("0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38");
38const ADMIN: Address = address!("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f");
40
41pub struct AnvilStorageProvider<'a> {
43 db: &'a mut dyn Db,
44 chain_id: u64,
45 block_env: TempoBlockEnv,
46 gas_used: u64,
47 gas_refunded: i64,
48 reservoir: u64,
49 tip1060_storage_credits_enabled: bool,
50 transient: HashMap<(Address, U256), U256>,
51 hardfork: TempoHardfork,
52}
53
54impl<'a> AnvilStorageProvider<'a> {
55 pub fn new(
56 db: &'a mut dyn Db,
57 chain_id: u64,
58 timestamp: U256,
59 block_number: u64,
60 hardfork: TempoHardfork,
61 ) -> Self {
62 Self {
63 db,
64 chain_id,
65 block_env: TempoBlockEnv {
66 inner: BlockEnv {
67 timestamp,
68 number: U256::from(block_number),
69 ..Default::default()
70 },
71 ..Default::default()
72 },
73 gas_used: 0,
74 gas_refunded: 0,
75 reservoir: 0,
76 tip1060_storage_credits_enabled: hardfork.is_t7(),
77 transient: HashMap::new(),
78 hardfork,
79 }
80 }
81}
82
83impl PrecompileStorageProvider for AnvilStorageProvider<'_> {
84 fn spec(&self) -> TempoHardfork {
85 self.hardfork
86 }
87
88 fn chain_id(&self) -> u64 {
89 self.chain_id
90 }
91
92 fn block_env(&self) -> &TempoBlockEnv {
93 &self.block_env
94 }
95
96 fn set_code(&mut self, address: Address, code: Bytecode) -> Result<(), TempoPrecompileError> {
97 self.db.insert_account(
98 address,
99 AccountInfo {
100 code_hash: code.hash_slow(),
101 code: Some(code),
102 nonce: 1,
103 ..Default::default()
104 },
105 );
106 Ok(())
107 }
108
109 fn with_account_info(
110 &mut self,
111 address: Address,
112 f: &mut dyn FnMut(&AccountInfo),
113 ) -> Result<(), TempoPrecompileError> {
114 use revm::DatabaseRef;
115 if let Some(info) =
116 self.db.basic_ref(address).map_err(|e| TempoPrecompileError::Fatal(e.to_string()))?
117 {
118 f(&info);
119 Ok(())
120 } else {
121 Err(TempoPrecompileError::Fatal(format!("account '{address}' not found")))
122 }
123 }
124
125 fn sstore(
126 &mut self,
127 address: Address,
128 key: U256,
129 value: U256,
130 ) -> Result<(), TempoPrecompileError> {
131 use alloy_primitives::B256;
132 self.db
133 .set_storage_at(address, B256::from(key), B256::from(value))
134 .map_err(|e| TempoPrecompileError::Fatal(e.to_string()))
135 }
136
137 fn sload(&mut self, address: Address, key: U256) -> Result<U256, TempoPrecompileError> {
138 revm::Database::storage(self.db, address, key)
139 .map_err(|e| TempoPrecompileError::Fatal(e.to_string()))
140 }
141
142 fn tstore(
143 &mut self,
144 address: Address,
145 key: U256,
146 value: U256,
147 ) -> Result<(), TempoPrecompileError> {
148 self.transient.insert((address, key), value);
149 Ok(())
150 }
151
152 fn tload(&mut self, address: Address, key: U256) -> Result<U256, TempoPrecompileError> {
153 Ok(self.transient.get(&(address, key)).copied().unwrap_or(U256::ZERO))
154 }
155
156 fn emit_event(
157 &mut self,
158 _address: Address,
159 _event: alloy_primitives::LogData,
160 ) -> Result<(), TempoPrecompileError> {
161 Ok(())
162 }
163
164 fn deduct_gas(&mut self, gas: u64) -> Result<(), TempoPrecompileError> {
165 self.gas_used = self.gas_used.saturating_add(gas);
166 Ok(())
167 }
168
169 fn gas_used(&self) -> u64 {
170 self.gas_used
171 }
172
173 fn state_gas_used(&self) -> u64 {
174 0
175 }
176
177 fn gas_limit(&self) -> u64 {
178 u64::MAX
179 }
180
181 fn gas_refunded(&self) -> i64 {
182 self.gas_refunded
183 }
184
185 fn reservoir(&self) -> u64 {
186 self.reservoir
187 }
188
189 fn refund_gas(&mut self, gas: i64) {
190 self.gas_refunded = self.gas_refunded.saturating_add(gas);
191 }
192
193 fn is_static(&self) -> bool {
194 false
195 }
196
197 fn checkpoint(&mut self) -> JournalCheckpoint {
198 JournalCheckpoint { log_i: 0, journal_i: 0, selfdestructed_i: 0 }
199 }
200
201 fn checkpoint_commit(&mut self, _checkpoint: JournalCheckpoint) {}
202
203 fn checkpoint_revert(&mut self, _checkpoint: JournalCheckpoint) {}
204
205 fn amsterdam_eip8037_enabled(&self) -> bool {
206 false
207 }
208
209 fn set_tip1060_storage_credits(&mut self, enabled: bool) {
210 self.tip1060_storage_credits_enabled = enabled && self.hardfork.is_t7();
211 }
212}
213
214pub fn initialize_tempo_precompiles(
222 db: &mut dyn Db,
223 chain_id: u64,
224 timestamp: u64,
225 test_accounts: &[Address],
226 hardfork: TempoHardfork,
227) -> Result<(), TempoPrecompileError> {
228 let timestamp = U256::from(timestamp);
229
230 let mut storage = AnvilStorageProvider::new(db, chain_id, timestamp, 0, hardfork);
231
232 initialize_tempo_genesis_at_hardfork(&mut storage, ADMIN, SENDER, hardfork)?;
234
235 let mint_amount = U256::from(u64::MAX);
238 let tokens = [PATH_USD_ADDRESS, ALPHA_USD_ADDRESS, BETA_USD_ADDRESS, THETA_USD_ADDRESS];
239
240 StorageCtx::enter(&mut storage, || -> Result<(), TempoPrecompileError> {
241 for &token_address in &tokens {
243 let mut token = TIP20Token::from_address(token_address)?;
244 for &account in test_accounts {
245 token.mint(ADMIN, ITIP20::mintCall { to: account, amount: mint_amount })?;
246 }
247 }
248
249 let mut keychain = AccountKeychain::new();
253 for &account in test_accounts {
254 keychain.set_tx_origin(account)?;
257 keychain.authorize_key(
258 account, account, SignatureType::Secp256k1,
261 KeyRestrictions {
262 expiry: u64::MAX, enforceLimits: false, limits: vec![],
265 allowAnyCalls: true,
266 allowedCalls: vec![],
267 },
268 None,
269 )?;
270 }
271
272 let mut fee_manager = TipFeeManager::new();
275 fee_manager.initialize()?;
276
277 for (i, &account) in test_accounts.iter().enumerate() {
278 let fee_token = match i {
279 0 => ALPHA_USD_ADDRESS, 1 => BETA_USD_ADDRESS, 2 => THETA_USD_ADDRESS, _ => PATH_USD_ADDRESS, };
284 fee_manager
285 .set_user_token(account, IFeeManager::setUserTokenCall { token: fee_token })?;
286 }
287
288 for &token_address in &tokens {
290 let mut token = TIP20Token::from_address(token_address)?;
291 token.mint(
292 ADMIN,
293 ITIP20::mintCall { to: TIP_FEE_MANAGER_ADDRESS, amount: mint_amount },
294 )?;
295 }
296
297 let liquidity_amount = U256::from(10u64.pow(10));
301
302 for &user_token in &tokens {
305 for &validator_token in &tokens {
306 if user_token != validator_token {
307 fee_manager.mint(
308 ADMIN,
309 user_token,
310 validator_token,
311 liquidity_amount,
312 ADMIN,
313 )?;
314 }
315 }
316 }
317
318 Ok(())
319 })?;
320
321 Ok(())
322}