|
1 | 1 | use super::{fmt_err, Cheatcodes, Error, Result}; |
2 | 2 | use crate::{ |
3 | 3 | abi::HEVMCalls, |
4 | | - executor::{backend::DatabaseExt, fork::CreateFork}, |
| 4 | + executor::{ |
| 5 | + backend::DatabaseExt, fork::CreateFork, inspector::cheatcodes::ext::value_to_token, |
| 6 | + }, |
| 7 | + utils::{b160_to_h160, RuntimeOrHandle}, |
5 | 8 | }; |
6 | 9 | use ethers::{ |
7 | | - abi::AbiEncode, |
| 10 | + abi::{self, AbiEncode, Token, Tokenizable, Tokenize}, |
8 | 11 | prelude::U256, |
9 | | - types::{Bytes, H256}, |
| 12 | + providers::Middleware, |
| 13 | + types::{Bytes, Filter, H256}, |
10 | 14 | }; |
| 15 | +use foundry_abi::hevm::{EthGetLogsCall, RpcCall}; |
| 16 | +use foundry_common::ProviderBuilder; |
11 | 17 | use revm::EVMData; |
| 18 | +use serde_json::Value; |
12 | 19 |
|
13 | 20 | fn empty<T>(_: T) -> Bytes { |
14 | 21 | Bytes::new() |
@@ -140,6 +147,8 @@ pub fn apply<DB: DatabaseExt>( |
140 | 147 | ) |
141 | 148 | .map(empty) |
142 | 149 | .map_err(Into::into), |
| 150 | + HEVMCalls::EthGetLogs(inner) => eth_getlogs(data, inner), |
| 151 | + HEVMCalls::Rpc(inner) => rpc(data, inner), |
143 | 152 | _ => return None, |
144 | 153 | }; |
145 | 154 | Some(result) |
@@ -246,3 +255,107 @@ fn create_fork_request<DB: DatabaseExt>( |
246 | 255 | }; |
247 | 256 | Ok(fork) |
248 | 257 | } |
| 258 | + |
| 259 | +/// Retrieve the logs specified for the current fork. |
| 260 | +/// Equivalent to eth_getLogs but on a cheatcode. |
| 261 | +fn eth_getlogs<DB: DatabaseExt>(data: &EVMData<DB>, inner: &EthGetLogsCall) -> Result { |
| 262 | + let url = data.db.active_fork_url().ok_or(fmt_err!("No active fork url found"))?; |
| 263 | + if inner.0 > U256::from(u64::MAX) || inner.1 > U256::from(u64::MAX) { |
| 264 | + return Err(fmt_err!("Blocks in block range must be less than 2^64 - 1")) |
| 265 | + } |
| 266 | + // Cannot possibly have more than 4 topics in the topics array. |
| 267 | + if inner.3.len() > 4 { |
| 268 | + return Err(fmt_err!("Topics array must be less than 4 elements")) |
| 269 | + } |
| 270 | + |
| 271 | + let provider = ProviderBuilder::new(url).build()?; |
| 272 | + let mut filter = Filter::new() |
| 273 | + .address(b160_to_h160(inner.2.into())) |
| 274 | + .from_block(inner.0.as_u64()) |
| 275 | + .to_block(inner.1.as_u64()); |
| 276 | + for (i, item) in inner.3.iter().enumerate() { |
| 277 | + match i { |
| 278 | + 0 => filter = filter.topic0(U256::from(item)), |
| 279 | + 1 => filter = filter.topic1(U256::from(item)), |
| 280 | + 2 => filter = filter.topic2(U256::from(item)), |
| 281 | + 3 => filter = filter.topic3(U256::from(item)), |
| 282 | + _ => return Err(fmt_err!("Topics array should be less than 4 elements")), |
| 283 | + }; |
| 284 | + } |
| 285 | + |
| 286 | + let logs = RuntimeOrHandle::new() |
| 287 | + .block_on(provider.get_logs(&filter)) |
| 288 | + .map_err(|_| fmt_err!("Error in calling eth_getLogs"))?; |
| 289 | + |
| 290 | + if logs.is_empty() { |
| 291 | + let empty: Bytes = abi::encode(&[Token::Array(vec![])]).into(); |
| 292 | + return Ok(empty) |
| 293 | + } |
| 294 | + |
| 295 | + let result = abi::encode( |
| 296 | + &logs |
| 297 | + .iter() |
| 298 | + .map(|entry| { |
| 299 | + Token::Tuple(vec![ |
| 300 | + entry.address.into_token(), |
| 301 | + entry.topics.clone().into_token(), |
| 302 | + Token::Bytes(entry.data.to_vec()), |
| 303 | + entry |
| 304 | + .block_number |
| 305 | + .expect("eth_getLogs response should include block_number field") |
| 306 | + .as_u64() |
| 307 | + .into_token(), |
| 308 | + entry |
| 309 | + .transaction_hash |
| 310 | + .expect("eth_getLogs response should include transaction_hash field") |
| 311 | + .into_token(), |
| 312 | + entry |
| 313 | + .transaction_index |
| 314 | + .expect("eth_getLogs response should include transaction_index field") |
| 315 | + .as_u64() |
| 316 | + .into_token(), |
| 317 | + entry |
| 318 | + .block_hash |
| 319 | + .expect("eth_getLogs response should include block_hash field") |
| 320 | + .into_token(), |
| 321 | + entry |
| 322 | + .log_index |
| 323 | + .expect("eth_getLogs response should include log_index field") |
| 324 | + .into_token(), |
| 325 | + entry |
| 326 | + .removed |
| 327 | + .expect("eth_getLogs response should include removed field") |
| 328 | + .into_token(), |
| 329 | + ]) |
| 330 | + }) |
| 331 | + .collect::<Vec<Token>>() |
| 332 | + .into_tokens(), |
| 333 | + ) |
| 334 | + .into(); |
| 335 | + Ok(result) |
| 336 | +} |
| 337 | + |
| 338 | +fn rpc<DB: DatabaseExt>(data: &EVMData<DB>, inner: &RpcCall) -> Result { |
| 339 | + let url = data.db.active_fork_url().ok_or(fmt_err!("No active fork url found"))?; |
| 340 | + let provider = ProviderBuilder::new(url).build()?; |
| 341 | + |
| 342 | + let method = inner.0.as_str(); |
| 343 | + let params = inner.1.as_str(); |
| 344 | + let params_json: Value = serde_json::from_str(params)?; |
| 345 | + |
| 346 | + let result: Value = RuntimeOrHandle::new() |
| 347 | + .block_on(provider.request(method, params_json)) |
| 348 | + .map_err(|err| fmt_err!("Error in calling {:?}: {:?}", method, err))?; |
| 349 | + |
| 350 | + let result_as_tokens = |
| 351 | + value_to_token(&result).map_err(|err| fmt_err!("Failed to parse result: {err}"))?; |
| 352 | + |
| 353 | + let abi_encoded: Vec<u8> = match result_as_tokens { |
| 354 | + Token::Tuple(vec) | Token::Array(vec) | Token::FixedArray(vec) => abi::encode(&vec), |
| 355 | + _ => { |
| 356 | + let vec = vec![result_as_tokens]; |
| 357 | + abi::encode(&vec) |
| 358 | + } |
| 359 | + }; |
| 360 | + Ok(abi_encoded.into()) |
| 361 | +} |
0 commit comments