Skip to content

Commit

Permalink
dencun upgrade, tests pending
Browse files Browse the repository at this point in the history
devanoneth committed Mar 30, 2024

Verified

This commit was signed with the committer’s verified signature.
florianduros Florian Duros
1 parent ee12a28 commit 1ba1f62
Showing 5 changed files with 3,243 additions and 1,113 deletions.
4,044 changes: 3,101 additions & 943 deletions Cargo.lock

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -21,17 +21,19 @@ serde_json = "1"
bytes = "1.2.1"

# ethereum, evm
revm = { version = "3", default-features = false, features = [
revm = { version = "7.1", default-features = false, features = [
"std",
"serde",
"memory_limit",
"optional_eip3607",
"optional_block_gas_limit",
"optional_no_base_fee"
"optional_no_base_fee",
"arbitrary",
] }
ethers = { git = "https://github.com/gakonst/ethers-rs" }
foundry-config = { git = "https://github.com/foundry-rs/foundry", rev = "12ea9f6" }
foundry-evm = { git = "https://github.com/foundry-rs/foundry", rev = "12ea9f6" }
foundry-config = { git = "https://github.com/foundry-rs/foundry", rev = "a527c1c" }
foundry-evm = { git = "https://github.com/foundry-rs/foundry", rev = "a527c1c" }
revm-inspectors = { git = "https://github.com/paradigmxyz/evm-inspectors", rev = "ba0b6ab" }

# env, logs, errors, uuid
dotenvy = "0.15"
@@ -43,4 +45,3 @@ uuid = { version = "1.3.4", features = ["v4", "fast-rng", "serde"] }

[dev-dependencies]
temp-env = { version = "0.3.4", features = ["async_closure"] }

187 changes: 93 additions & 94 deletions src/evm.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
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};
use foundry_evm::executor::{opts::EvmOpts, Backend, ExecutorBuilder};
use foundry_evm::trace::identifier::{EtherscanIdentifier, SignaturesIdentifier};
use foundry_evm::trace::node::CallTraceNode;
use foundry_evm::trace::{CallTraceArena, CallTraceDecoder, CallTraceDecoderBuilder};
use foundry_evm::utils::{h160_to_b160, u256_to_ru256};
use foundry_evm::backend::Backend;
use foundry_evm::executors::Executor;
use foundry_evm::executors::ExecutorBuilder;
use foundry_evm::fork::CreateFork;
use foundry_evm::opts::EvmOpts;

use foundry_evm::traces::identifier::{EtherscanIdentifier, SignaturesIdentifier};
use foundry_evm::traces::{
CallTraceArena, CallTraceDecoder, CallTraceDecoderBuilder, CallTraceNode,
};

use revm::db::DatabaseRef;
use revm::interpreter::InstructionResult;
use revm::primitives::{Account, Bytecode, Env, StorageSlot};
use revm::primitives::Log;
use revm::primitives::{Account, Address, Bytecode, Bytes, Env, StorageSlot, U256};
use revm::DatabaseCommit;
use revm_inspectors::tracing::TraceWriter;

use crate::errors::{EvmError, OverrideError};
use crate::simulation::CallTrace;

pub type AccessList = Vec<(Address, Vec<U256>)>;

#[derive(Debug, Clone)]
pub struct CallRawRequest {
pub from: Address,
pub to: Address,
pub value: Option<Uint>,
pub value: Option<U256>,
pub data: Option<Bytes>,
pub access_list: Option<AccessList>,
pub format_trace: bool,
@@ -54,7 +59,7 @@

#[derive(Debug, Clone, PartialEq)]
pub struct StorageOverride {
pub slots: HashMap<Hash, Uint>,
pub slots: HashMap<U256, U256>,
pub diff: bool,
}

@@ -65,18 +70,17 @@
}

impl Evm {
pub fn new(
pub async fn new(
env: Option<Env>,
fork_url: String,
fork_block_number: Option<u64>,
gas_limit: u64,
tracing: bool,
gas_limit: U256,
etherscan_key: Option<String>,
) -> Self {
let evm_opts = EvmOpts {
fork_url: Some(fork_url.clone()),
fork_block_number,
env: foundry_evm::executor::opts::Env {
env: foundry_evm::opts::Env {
chain_id: None,
code_size_limit: None,
gas_price: Some(0),
@@ -90,54 +94,50 @@
let fork_opts = CreateFork {
url: fork_url,
enable_caching: true,
env: evm_opts.evm_env_blocking().unwrap(),
env: evm_opts.evm_env().await.unwrap(),
evm_opts,
};

let db = Backend::spawn(Some(fork_opts.clone()));

let mut builder = ExecutorBuilder::default()
.with_gas_limit(gas_limit.into())
.set_tracing(tracing);

if let Some(env) = env {
builder = builder.with_config(env);
} else {
builder = builder.with_config(fork_opts.env.clone());
}
let builder = ExecutorBuilder::default().gas_limit(gas_limit);
// .set_tracing(tracing);

let executor = builder.build(db);
let executor = builder.build(env.unwrap_or(fork_opts.env.clone()), db);

let foundry_config = foundry_config::Config {
etherscan_api_key: etherscan_key,
..Default::default()
};

let chain: Chain = fork_opts.env.cfg.chain_id.to::<u64>().into();
let etherscan_identifier = EtherscanIdentifier::new(&foundry_config, Some(chain)).ok();
let mut decoder = CallTraceDecoderBuilder::new().with_verbosity(5).build();
let chain: Chain = fork_opts.env.cfg.chain_id.into();
let etherscan_identifier =
EtherscanIdentifier::new(&foundry_config, Some(chain)).unwrap_or_default();
let decoder = CallTraceDecoderBuilder::new().with_verbosity(5);

if let Ok(identifier) =
let decoder = if let Ok(identifier) =
SignaturesIdentifier::new(foundry_config::Config::foundry_cache_dir(), false)
{
decoder.add_signature_identifier(identifier);
}
decoder.with_signature_identifier(identifier)
} else {
decoder
};

Evm {
executor,
decoder,
decoder: decoder.build(),
etherscan_identifier,
}
}

pub async fn call_raw(&mut self, call: CallRawRequest) -> Result<CallRawResult, EvmError> {
self.set_access_list(call.access_list);
let res = self
self.set_access_list(call.access_list)?;
let mut res = self
.executor
.call_raw(
call.from,
call.to,
call.data.unwrap_or_default().0,
call.data.unwrap_or_default(),
call.value.unwrap_or_default(),
)
.map_err(|err| {
@@ -146,15 +146,19 @@
})?;

let formatted_trace = if call.format_trace {
let mut output = String::new();
for trace in &mut res.traces.clone() {
let mut trace_writer = TraceWriter::new(Vec::<u8>::new());
for trace in &mut res.traces {
if let Some(identifier) = &mut self.etherscan_identifier {
self.decoder.identify(trace, identifier);
}
self.decoder.decode(trace).await;
output.push_str(format!("{trace}").as_str());
trace_writer
.write_arena(trace)
.expect("trace writer failure");
}
Some(output)
Some(
String::from_utf8(trace_writer.into_writer())
.expect("trace writer wrote invalid UTF-8"),
)
} else {
None
};
@@ -166,32 +170,32 @@
trace: res.traces,
logs: res.logs,
exit_reason: res.exit_reason,
return_data: Bytes(res.result),
return_data: res.result,
formatted_trace,
})
}

pub fn override_account(
&mut self,
address: Address,
balance: Option<Uint>,
balance: Option<U256>,
nonce: Option<u64>,
code: Option<Bytes>,
storage: Option<StorageOverride>,
) -> Result<(), OverrideError> {
let address = h160_to_b160(address);
// let address = h160_to_b160(address);
let mut account = Account {
info: self
.executor
.backend()
.basic(address)
.backend
.basic_ref(address)
.map_err(|_| OverrideError)?
.unwrap_or_default(),
..Account::new_not_existing()
};

if let Some(balance) = balance {
account.info.balance = u256_to_ru256(balance);
account.info.balance = balance;
}
if let Some(nonce) = nonce {
account.info.nonce = nonce;
@@ -203,19 +207,19 @@
// If we do a "full storage override", make sure to set this flag so
// that existing storage slots are cleared, and unknown ones aren't
// fetched from the forked node.
account.storage_cleared = !storage.diff;
account
.storage
.extend(storage.slots.into_iter().map(|(key, value)| {
(
u256_to_ru256(Uint::from_big_endian(key.as_bytes())),
StorageSlot::new(u256_to_ru256(value)),
)
}));
if storage.diff {
account.storage.clear();
}
account.storage.extend(
storage
.slots
.into_iter()
.map(|(key, value)| (key, StorageSlot::new(value))),
);
}

self.executor
.backend_mut()
.backend
.commit([(address, account)].into_iter().collect());

Ok(())
@@ -224,16 +228,16 @@
pub async fn call_raw_committing(
&mut self,
call: CallRawRequest,
gas_limit: u64,
gas_limit: U256,
) -> Result<CallRawResult, EvmError> {
self.executor.set_gas_limit(gas_limit.into());

Check failure on line 233 in src/evm.rs

GitHub Actions / clippy

useless conversion to the same type: `revm::revm_primitives::alloy_primitives::Uint<256, 4>`

error: useless conversion to the same type: `revm::revm_primitives::alloy_primitives::Uint<256, 4>` --> src/evm.rs:233:37 | 233 | self.executor.set_gas_limit(gas_limit.into()); | ^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `gas_limit` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion = note: `-D clippy::useless-conversion` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::useless_conversion)]`
self.set_access_list(call.access_list);
let res = self
self.set_access_list(call.access_list)?;
let mut res = self
.executor
.call_raw_committing(
call.from,
call.to,
call.data.unwrap_or_default().0,
call.data.unwrap_or_default(),
call.value.unwrap_or_default(),
)
.map_err(|err| {
@@ -242,15 +246,19 @@
})?;

let formatted_trace = if call.format_trace {
let mut output = String::new();
for trace in &mut res.traces.clone() {
let mut trace_writer = TraceWriter::new(Vec::<u8>::new());
for trace in &mut res.traces {
if let Some(identifier) = &mut self.etherscan_identifier {
self.decoder.identify(trace, identifier);
}
self.decoder.decode(trace).await;
output.push_str(format!("{trace}").as_str());
trace_writer
.write_arena(trace)
.expect("trace writer failure");
}
Some(output)
Some(
String::from_utf8(trace_writer.into_writer())
.expect("trace writer wrote invalid UTF-8"),
)
} else {
None
};
@@ -262,47 +270,38 @@
trace: res.traces,
logs: res.logs,
exit_reason: res.exit_reason,
return_data: Bytes(res.result),
return_data: res.result,
formatted_trace,
})
}

pub async fn set_block(&mut self, number: u64) -> Result<(), EvmError> {
self.executor.env_mut().block.number = Uint::from(number).into();
pub async fn set_block(&mut self, number: U256) -> Result<(), EvmError> {
self.executor.env.block.number = number;
Ok(())
}

pub fn get_block(&self) -> Uint {
self.executor.env().block.number.into()
pub fn get_block(&self) -> U256 {
self.executor.env.block.number
}

pub async fn set_block_timestamp(&mut self, timestamp: u64) -> Result<(), EvmError> {
self.executor.env_mut().block.timestamp = Uint::from(timestamp).into();
pub async fn set_block_timestamp(&mut self, timestamp: U256) -> Result<(), EvmError> {
self.executor.env.block.timestamp = timestamp;
Ok(())
}

pub fn get_block_timestamp(&self) -> Uint {
self.executor.env().block.timestamp.into()
pub fn get_block_timestamp(&self) -> U256 {
self.executor.env.block.timestamp
}

pub fn get_chain_id(&self) -> Uint {
self.executor.env().cfg.chain_id.into()
pub fn get_chain_id(&self) -> u64 {
self.executor.env.cfg.chain_id
}

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();
fn set_access_list(&mut self, access_list: Option<AccessList>) -> Result<(), EvmError> {
if let Some(access_list) = access_list {
self.executor.env.tx.access_list = access_list;
}

Ok(())
}
}
112 changes: 42 additions & 70 deletions src/simulation.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
use std::collections::HashMap;
use std::str::FromStr;
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 foundry_evm::traces::CallKind;
use revm::interpreter::InstructionResult;
use revm::primitives::{Address, Bytes, Log, U256};
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use uuid::Uuid;
@@ -19,7 +15,7 @@
IncorrectChainIdError, InvalidBlockNumbersError, MultipleChainIdsError, NoURLForChainIdError,
StateNotFound,
};
use crate::evm::StorageOverride;
use crate::evm::{AccessList, StorageOverride};
use crate::SharedSimulationState;

use super::config::Config;
@@ -32,11 +28,11 @@
pub from: Address,
pub to: Address,
pub data: Option<Bytes>,
pub gas_limit: u64,
pub value: Option<PermissiveUint>,
pub gas_limit: U256,
pub value: Option<U256>,
pub access_list: Option<AccessList>,
pub block_number: Option<u64>,
pub block_timestamp: Option<u64>,
pub block_timestamp: Option<U256>,
pub state_overrides: Option<HashMap<Address, StateOverride>>,
pub format_trace: Option<bool>,
}
@@ -59,9 +55,9 @@
#[serde(rename_all = "camelCase")]
pub struct StatefulSimulationRequest {
pub chain_id: u64,
pub gas_limit: u64,
pub gas_limit: U256,
pub block_number: Option<u64>,
pub block_timestamp: Option<u64>,
pub block_timestamp: Option<U256>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
@@ -77,7 +73,7 @@

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct StateOverride {
pub balance: Option<PermissiveUint>,
pub balance: Option<U256>,
pub nonce: Option<u64>,
pub code: Option<Bytes>,
#[serde(flatten)]
@@ -88,11 +84,11 @@
#[serde(untagged)]
pub enum State {
Full {
state: HashMap<Hash, PermissiveUint>,
state: HashMap<U256, U256>,
},
#[serde(rename_all = "camelCase")]
Diff {
state_diff: HashMap<Hash, PermissiveUint>,
state_diff: HashMap<U256, U256>,
},
}

@@ -106,7 +102,7 @@
StorageOverride {
slots: slots
.into_iter()
.map(|(key, value)| (key, value.into()))

Check failure on line 105 in src/simulation.rs

GitHub Actions / clippy

useless conversion to the same type: `revm::revm_primitives::alloy_primitives::Uint<256, 4>`

error: useless conversion to the same type: `revm::revm_primitives::alloy_primitives::Uint<256, 4>` --> src/simulation.rs:105:43 | 105 | .map(|(key, value)| (key, value.into())) | ^^^^^^^^^^^^ help: consider removing `.into()`: `value` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
.collect(),
diff,
}
@@ -119,39 +115,13 @@
pub call_type: CallKind,
pub from: Address,
pub to: Address,
pub value: Uint,
}

#[derive(Debug, Default, Clone, Copy, Serialize, PartialEq)]
#[serde(transparent)]
pub struct PermissiveUint(pub Uint);

impl From<PermissiveUint> for Uint {
fn from(value: PermissiveUint) -> Self {
value.0
}
}

impl<'de> Deserialize<'de> for PermissiveUint {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
// Accept value in hex or decimal formats
let value = String::deserialize(deserializer)?;
let parsed = if value.starts_with("0x") {
Uint::from_str(&value).map_err(serde::de::Error::custom)?
} else {
Uint::from_dec_str(&value).map_err(serde::de::Error::custom)?
};
Ok(Self(parsed))
}
pub value: U256,
}

fn chain_id_to_fork_url(chain_id: u64) -> Result<String, Rejection> {
match chain_id {
// ethereum
1 => Ok("https://eth.llamarpc.com".to_string()),
1 => Ok("https://eth.drpc.org".to_string()),
5 => Ok("https://eth-goerli.g.alchemy.com/v2/demo".to_string()),
11155111 => Ok("https://eth-sepolia.g.alchemy.com/v2/demo".to_string()),
// polygon
@@ -186,7 +156,7 @@
for (address, state_override) in transaction.state_overrides.into_iter().flatten() {
evm.override_account(
address,
state_override.balance.map(Uint::from),
state_override.balance.map(U256::from),
state_override.nonce,
state_override.code,
state_override.state.map(StorageOverride::from),
@@ -196,7 +166,7 @@
let call = CallRawRequest {
from: transaction.from,
to: transaction.to,
value: transaction.value.map(Uint::from),
value: transaction.value.map(U256::from),
data: transaction.data,
access_list: transaction.access_list,
format_trace: transaction.format_trace.unwrap_or_default(),
@@ -215,7 +185,7 @@
trace: result
.trace
.unwrap_or_default()
.arena
.into_nodes()
.into_iter()
.map(CallTrace::from)
.collect(),
@@ -235,16 +205,16 @@
fork_url,
transaction.block_number,
transaction.gas_limit,
true,
config.etherscan_key,
);
)
.await;

if evm.get_chain_id() != Uint::from(transaction.chain_id) {
if evm.get_chain_id() != transaction.chain_id {
return Err(warp::reject::custom(IncorrectChainIdError()));
}

if let Some(timestamp) = transaction.block_timestamp {
evm.set_block_timestamp(timestamp)
evm.set_block_timestamp(U256::from(timestamp))
.await
.expect("failed to set block timestamp");
}
@@ -270,11 +240,11 @@
fork_url,
first_block_number,
transactions[0].gas_limit,
true,
config.etherscan_key,
);
)
.await;

if evm.get_chain_id() != Uint::from(first_chain_id) {
if evm.get_chain_id() != first_chain_id {
return Err(warp::reject::custom(IncorrectChainIdError()));
}

@@ -290,17 +260,18 @@
return Err(warp::reject::custom(MultipleChainIdsError()));
}
if transaction.block_number != first_block_number {
let tx_block = transaction
.block_number
.expect("Transaction has no block number");
if transaction.block_number < first_block_number || tx_block < evm.get_block().as_u64()
{
let tx_block = U256::from(
transaction
.block_number
.expect("Transaction has no block number"),
);
if transaction.block_number < first_block_number || tx_block < evm.get_block() {
return Err(warp::reject::custom(InvalidBlockNumbersError()));
}
evm.set_block(tx_block)
.await
.expect("Failed to set block number");
evm.set_block_timestamp(evm.get_block_timestamp().as_u64() + 12)
evm.set_block_timestamp(evm.get_block_timestamp() + U256::from(12)) // TOOD: make block time configurable
.await
.expect("Failed to set block timestamp");
}
@@ -323,9 +294,9 @@
fork_url,
stateful_simulation_request.block_number,
stateful_simulation_request.gas_limit,
true,
config.etherscan_key,
);
)
.await;

if let Some(timestamp) = stateful_simulation_request.block_timestamp {
evm.set_block_timestamp(timestamp).await?;
@@ -374,7 +345,7 @@
let evm = evm_ref_mut.value();
let mut evm = evm.lock().await;

if evm.get_chain_id() != Uint::from(first_chain_id) {
if evm.get_chain_id() != first_chain_id {
return Err(warp::reject::custom(IncorrectChainIdError()));
}

@@ -383,20 +354,21 @@
return Err(warp::reject::custom(MultipleChainIdsError()));
}
if transaction.block_number != first_block_number
|| transaction.block_number.unwrap() != evm.get_block().as_u64()
|| U256::from(transaction.block_number.unwrap_or_default()) != evm.get_block()
{
let tx_block = transaction
.block_number
.expect("Transaction has no block number");
if transaction.block_number < first_block_number || tx_block < evm.get_block().as_u64()
{
let tx_block = U256::from(
transaction
.block_number
.expect("Transaction has no block number"),
);
if transaction.block_number < first_block_number || tx_block < evm.get_block() {
return Err(warp::reject::custom(InvalidBlockNumbersError()));
}
evm.set_block(tx_block)
.await
.expect("Failed to set block number");
let block_timestamp = evm.get_block_timestamp().as_u64();
evm.set_block_timestamp(block_timestamp + 12)
let block_timestamp = evm.get_block_timestamp();
evm.set_block_timestamp(block_timestamp + U256::from(12))
.await
.expect("Failed to set block timestamp");
}
2 changes: 1 addition & 1 deletion tests/api.rs
Original file line number Diff line number Diff line change
@@ -510,7 +510,7 @@ async fn post_simulate_invalid_data() {

assert_eq!(
body.message,
"BAD REQUEST: Odd number of digits at line 1 column 709".to_string()
"BAD REQUEST: invalid value: string \"0xffa2ca3b44eea7c8e659973cbdf476546e9e6adfd1c580700537e52ba7124933a97904ea000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001d0e30db00300ffffffffffffc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000186a\", expected a valid hex string at line 1 column 709".to_string()
);
}

0 comments on commit 1ba1f62

Please sign in to comment.