Skip to content

Commit

Permalink
Add Support for Specifying Access Lists (#19)
Browse files Browse the repository at this point in the history
* Add Support for Specifying Access Lists

* Document API Changes and Add E2E Test
  • Loading branch information
Nicholas Rodrigues Lordello authored Sep 25, 2023
1 parent b4eea73 commit bbbe4aa
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 41 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,17 @@ export type SimulationRequest = {
data?: string;
gasLimit: number;
value: string;
accessList?: AccessListItem[];
blockNumber?: number; // if not specified, latest used,
stateOverrides?: Record<string, StateOverride>;
formatTrace?: boolean;
};

export type AccessListItem = {
address: string;
storageKeys: string[];
};

export type StateOverride = {
balance?: string;
nonce?: number;
Expand Down
65 changes: 42 additions & 23 deletions src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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<Uint>,
pub data: Option<Bytes>,
pub access_list: Option<AccessList>,
pub format_trace: bool,
}

#[derive(Debug, Clone)]
pub struct CallRawResult {
pub gas_used: u64,
Expand Down Expand Up @@ -119,28 +130,22 @@ impl Evm {
}
}

pub async fn call_raw(
&mut self,
from: Address,
to: Address,
value: Option<Uint>,
data: Option<Bytes>,
format_trace: bool,
) -> Result<CallRawResult, EvmError> {
pub async fn call_raw(&mut self, call: CallRawRequest) -> Result<CallRawResult, EvmError> {
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 {
Expand Down Expand Up @@ -218,28 +223,25 @@ impl Evm {

pub async fn call_raw_committing(
&mut self,
from: Address,
to: Address,
value: Option<Uint>,
data: Option<Bytes>,
call: CallRawRequest,
gas_limit: u64,
format_trace: bool,
) -> Result<CallRawResult, EvmError> {
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 {
Expand Down Expand Up @@ -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<AccessList>) {
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();
}
}
31 changes: 13 additions & 18 deletions src/simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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")]
Expand All @@ -33,6 +34,7 @@ pub struct SimulationRequest {
pub data: Option<Bytes>,
pub gas_limit: u64,
pub value: Option<PermissiveUint>,
pub access_list: Option<AccessList>,
pub block_number: Option<u64>,
pub state_overrides: Option<HashMap<Address, StateOverride>>,
pub format_trace: Option<bool>,
Expand Down Expand Up @@ -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 {
Expand Down
52 changes: 52 additions & 0 deletions tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<SimulationResponse>(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();
Expand Down

0 comments on commit bbbe4aa

Please sign in to comment.