From bbbe4aa8180161a922b3d0093ef3b4d4544fdf13 Mon Sep 17 00:00:00 2001 From: Nicholas Rodrigues Lordello Date: Tue, 26 Sep 2023 00:39:37 +0200 Subject: [PATCH] Add Support for Specifying Access Lists (#19) * Add Support for Specifying Access Lists * Document API Changes and Add E2E Test --- README.md | 6 +++++ src/evm.rs | 65 ++++++++++++++++++++++++++++++----------------- src/simulation.rs | 31 ++++++++++------------ tests/api.rs | 52 +++++++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 30cc767..f34e288 100644 --- a/README.md +++ b/README.md @@ -232,11 +232,17 @@ export type SimulationRequest = { data?: string; gasLimit: number; value: string; + accessList?: AccessListItem[]; blockNumber?: number; // if not specified, latest used, stateOverrides?: Record; formatTrace?: boolean; }; +export type AccessListItem = { + address: string; + storageKeys: string[]; +}; + export type StateOverride = { balance?: string; nonce?: number; diff --git a/src/evm.rs b/src/evm.rs index 3720977..ba49c15 100644 --- a/src/evm.rs +++ b/src/evm.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use ethers::abi::{Address, Hash, Uint}; use ethers::core::types::Log; +use ethers::types::transaction::eip2930::AccessList; use ethers::types::Bytes; use foundry_config::Chain; use foundry_evm::executor::{fork::CreateFork, Executor}; @@ -18,6 +19,16 @@ use revm::DatabaseCommit; use crate::errors::{EvmError, OverrideError}; use crate::simulation::CallTrace; +#[derive(Debug, Clone)] +pub struct CallRawRequest { + pub from: Address, + pub to: Address, + pub value: Option, + pub data: Option, + pub access_list: Option, + pub format_trace: bool, +} + #[derive(Debug, Clone)] pub struct CallRawResult { pub gas_used: u64, @@ -119,28 +130,22 @@ impl Evm { } } - pub async fn call_raw( - &mut self, - from: Address, - to: Address, - value: Option, - data: Option, - format_trace: bool, - ) -> Result { + pub async fn call_raw(&mut self, call: CallRawRequest) -> Result { + self.set_access_list(call.access_list); let res = self .executor .call_raw( - from, - to, - data.unwrap_or_default().0, - value.unwrap_or_default(), + call.from, + call.to, + call.data.unwrap_or_default().0, + call.value.unwrap_or_default(), ) .map_err(|err| { dbg!(&err); EvmError(err) })?; - let formatted_trace = if format_trace { + let formatted_trace = if call.format_trace { let mut output = String::new(); for trace in &mut res.traces.clone() { if let Some(identifier) = &mut self.etherscan_identifier { @@ -218,28 +223,25 @@ impl Evm { pub async fn call_raw_committing( &mut self, - from: Address, - to: Address, - value: Option, - data: Option, + call: CallRawRequest, gas_limit: u64, - format_trace: bool, ) -> Result { self.executor.set_gas_limit(gas_limit.into()); + self.set_access_list(call.access_list); let res = self .executor .call_raw_committing( - from, - to, - data.unwrap_or_default().0, - value.unwrap_or_default(), + call.from, + call.to, + call.data.unwrap_or_default().0, + call.value.unwrap_or_default(), ) .map_err(|err| { dbg!(&err); EvmError(err) })?; - let formatted_trace = if format_trace { + let formatted_trace = if call.format_trace { let mut output = String::new(); for trace in &mut res.traces.clone() { if let Some(identifier) = &mut self.etherscan_identifier { @@ -286,4 +288,21 @@ impl Evm { pub fn get_chain_id(&self) -> Uint { self.executor.env().cfg.chain_id.into() } + + fn set_access_list(&mut self, access_list: Option) { + self.executor.env_mut().tx.access_list = access_list + .unwrap_or_default() + .0 + .into_iter() + .map(|item| { + ( + h160_to_b160(item.address), + item.storage_keys + .into_iter() + .map(|key| u256_to_ru256(Uint::from_big_endian(key.as_bytes()))) + .collect(), + ) + }) + .collect(); + } } diff --git a/src/simulation.rs b/src/simulation.rs index 6ab293b..812d2ed 100644 --- a/src/simulation.rs +++ b/src/simulation.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use dashmap::mapref::one::RefMut; use ethers::abi::{Address, Hash, Uint}; use ethers::core::types::Log; +use ethers::types::transaction::eip2930::AccessList; use ethers::types::Bytes; use foundry_evm::CallKind; use revm::interpreter::InstructionResult; @@ -22,7 +23,7 @@ use crate::evm::StorageOverride; use crate::SharedSimulationState; use super::config::Config; -use super::evm::Evm; +use super::evm::{CallRawRequest, Evm}; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -33,6 +34,7 @@ pub struct SimulationRequest { pub data: Option, pub gas_limit: u64, pub value: Option, + pub access_list: Option, pub block_number: Option, pub state_overrides: Option>, pub format_trace: Option, @@ -189,25 +191,18 @@ async fn run( )?; } + let call = CallRawRequest { + from: transaction.from, + to: transaction.to, + value: transaction.value.map(Uint::from), + data: transaction.data, + access_list: transaction.access_list, + format_trace: transaction.format_trace.unwrap_or_default(), + }; let result = if commit { - evm.call_raw_committing( - transaction.from, - transaction.to, - transaction.value.map(Uint::from), - transaction.data, - transaction.gas_limit, - transaction.format_trace.unwrap_or_default(), - ) - .await? + evm.call_raw_committing(call, transaction.gas_limit).await? } else { - evm.call_raw( - transaction.from, - transaction.to, - transaction.value.map(Uint::from), - transaction.data, - transaction.format_trace.unwrap_or_default(), - ) - .await? + evm.call_raw(call).await? }; Ok(SimulationResponse { diff --git a/tests/api.rs b/tests/api.rs index 32b2bb7..8e9137b 100644 --- a/tests/api.rs +++ b/tests/api.rs @@ -154,6 +154,58 @@ async fn post_simulate_zerox_swap() { assert!(body.success); } +#[tokio::test(flavor = "multi_thread")] +async fn post_simulate_access_lists() { + let simulate_gas_used = |access_list: serde_json::Value| async move { + let filter = filter(); + + let json = serde_json::json!({ + "chainId": 1, + "from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", + "to": "0x0000000000000000000000000000000000000000", + "gasLimit": 50000, + "accessList": access_list, + }); + + let res = warp::test::request() + .method("POST") + .path("/simulate") + .json(&json) + .reply(&filter) + .await; + + assert_eq!(res.status(), 200); + + serde_json::from_slice::(res.body()) + .unwrap() + .gas_used + }; + + const TX_COST: u64 = 21_000; + const ADDRESS_COST: u64 = 2400; + const STORAGE_KEY_COST: u64 = 1900; + + assert_eq!(simulate_gas_used(serde_json::json!([])).await, TX_COST); + assert_eq!( + simulate_gas_used(serde_json::json!([ + { + "address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + "storageKeys": [], + }, + { + "address": "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB", + "storageKeys": [ + "0xfca351f4d96129454cfc8ef7930b638ac71fea35eb69ee3b8d959496beb04a33", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001", + ], + }, + ])) + .await, + TX_COST + ADDRESS_COST * 2 + STORAGE_KEY_COST * 3, + ); +} + #[tokio::test(flavor = "multi_thread")] async fn post_simulate_state_overrides() { let filter = filter();