Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for Specifying Access Lists #19

Merged
merged 2 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading