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

chore(anvil): mostly remove EthTransactionRequest usage, migrate AccessListTracer #6776

Merged
merged 3 commits into from
Jan 12, 2024
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 0 additions & 24 deletions crates/anvil/core/src/eth/transaction/ethers_compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use crate::eth::{
};
use alloy_primitives::{U128 as rU128, U256 as rU256, U64 as rU64};
use alloy_rpc_types::{
transaction::request::TransactionRequest as AlloyTransactionRequest,
AccessList as AlloyAccessList, CallRequest, Signature, Transaction as AlloyTransaction,
};
use ethers_core::types::{
Expand Down Expand Up @@ -47,29 +46,6 @@ pub fn to_alloy_storage_proof(proof: &StorageProof) -> alloy_rpc_types::EIP1186S
}
}

pub fn to_internal_tx_request(request: &AlloyTransactionRequest) -> EthTransactionRequest {
EthTransactionRequest {
from: request.from.map(|a| a.to_ethers()),
to: request.to.map(|a| a.to_ethers()),
gas_price: request.gas_price.map(|g| alloy_primitives::U256::from(g).to_ethers()),
max_fee_per_gas: request
.max_fee_per_gas
.map(|g| alloy_primitives::U256::from(g).to_ethers()),
max_priority_fee_per_gas: request
.max_priority_fee_per_gas
.map(|g| alloy_primitives::U256::from(g).to_ethers()),
gas: request.gas.map(|g| g.to_ethers()),
value: request.value.map(|v| v.to_ethers()),
data: request.data.clone().map(|b| b.clone().0.into()),
nonce: request.nonce.map(|n| n.to::<u64>().into()),
chain_id: None,
access_list: request.access_list.clone().map(|a| to_ethers_access_list(a.clone()).0),
transaction_type: request.transaction_type.map(|t| t.to::<u64>().into()),
// TODO: Should this be none?
optimism_fields: None,
}
}

pub fn call_to_internal_tx_request(request: &CallRequest) -> EthTransactionRequest {
EthTransactionRequest {
from: request.from.map(|a| a.to_ethers()),
Expand Down
1 change: 0 additions & 1 deletion crates/anvil/core/src/eth/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ mod ethers_compat;

pub use ethers_compat::{
call_to_internal_tx_request, from_ethers_access_list, to_alloy_proof, to_ethers_access_list,
to_internal_tx_request,
};

/// The signature used to bypass signing via the `eth_sendUnsignedTransaction` cheat RPC
Expand Down
7 changes: 7 additions & 0 deletions crates/anvil/core/src/eth/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use alloy_primitives::{Address, U256};
use alloy_rpc_types::AccessListItem as AlloyAccessListItem;
use ethers_core::{
types::transaction::eip2930::AccessListItem,
utils::{
Expand Down Expand Up @@ -26,3 +27,9 @@ pub fn to_revm_access_list(list: Vec<AccessListItem>) -> Vec<(Address, Vec<U256>
})
.collect()
}

pub fn alloy_to_revm_access_list(list: Vec<AlloyAccessListItem>) -> Vec<(Address, Vec<U256>)> {
list.into_iter()
.map(|item| (item.address, item.storage_keys.into_iter().map(|k| k.into()).collect()))
.collect()
}
Comment on lines +31 to +35
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should ideally make the Alloy Access List compatible with the Revm type directly or with a .into() impl

71 changes: 32 additions & 39 deletions crates/anvil/src/eth/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,8 @@ use anvil_core::{
eth::{
block::BlockInfo,
transaction::{
call_to_internal_tx_request, to_alloy_proof, to_ethers_access_list,
EthTransactionRequest, LegacyTransaction, PendingTransaction, TransactionKind,
TypedTransaction, TypedTransactionRequest,
call_to_internal_tx_request, to_alloy_proof, EthTransactionRequest, LegacyTransaction,
PendingTransaction, TransactionKind, TypedTransaction, TypedTransactionRequest,
},
EthRequest,
},
Expand Down Expand Up @@ -1007,7 +1006,6 @@ impl EthApi {
request.max_priority_fee_per_gas,
)?
.or_zero_fees();
let request = call_to_internal_tx_request(&request);
// this can be blocking for a bit, especially in forking mode
// <https://github.com/foundry-rs/foundry/issues/6036>
self.on_blocking_task(|this| async move {
Expand Down Expand Up @@ -1035,7 +1033,7 @@ impl EthApi {
/// Handler for ETH RPC call: `eth_createAccessList`
pub async fn create_access_list(
&self,
request: CallRequest,
mut request: CallRequest,
block_number: Option<BlockId>,
) -> Result<AccessListWithGasUsed> {
node_info!("eth_createAccessList");
Expand All @@ -1052,8 +1050,6 @@ impl EthApi {
}
}

let mut request = call_to_internal_tx_request(&request);

self.backend
.with_database_at(Some(block_request), |state, block_env| {
let (exit, out, _, access_list) = self.backend.build_access_list_with_state(
Expand All @@ -1065,7 +1061,7 @@ impl EthApi {
ensure_return_ok(exit, &out)?;

// execute again but with access list set
request.access_list = Some(to_ethers_access_list(access_list.clone()).0);
request.access_list = Some(access_list.clone());

let (exit, out, gas_used, _) = self.backend.call_with_state(
&state,
Expand Down Expand Up @@ -1474,8 +1470,6 @@ impl EthApi {
)?
.or_zero_fees();

let request = call_to_internal_tx_request(&request);

self.backend.call_with_tracing(request, fees, Some(block_request), opts).await
}

Expand Down Expand Up @@ -2187,20 +2181,19 @@ impl EthApi {
/// This will execute the [CallRequest] and find the best gas limit via binary search
fn do_estimate_gas_with_state<D>(
&self,
request: CallRequest,
mut request: CallRequest,
state: D,
block_env: BlockEnv,
) -> Result<U256>
where
D: DatabaseRef<Error = DatabaseError>,
{
let mut request = call_to_internal_tx_request(&request);
// if the request is a simple transfer we can optimize
let likely_transfer =
request.data.as_ref().map(|data| data.as_ref().is_empty()).unwrap_or(true);
// If the request is a simple native token transfer we can optimize
// We assume it's a transfer if we have no input data.
let likely_transfer = request.input.clone().into_input().is_none();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let likely_transfer = request.input.clone().into_input().is_none();
let likely_transfer = request.input.input().is_none();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 ^

if likely_transfer {
if let Some(to) = request.to {
if let Ok(target_code) = self.backend.get_code_with_state(&state, to.to_alloy()) {
if let Ok(target_code) = self.backend.get_code_with_state(&state, to) {
if target_code.as_ref().is_empty() {
return Ok(MIN_TRANSACTION_GAS);
}
Expand All @@ -2209,34 +2202,33 @@ impl EthApi {
}

let fees = FeeDetails::new(
request.gas_price.map(ToAlloy::to_alloy),
request.max_fee_per_gas.map(ToAlloy::to_alloy),
request.max_priority_fee_per_gas.map(ToAlloy::to_alloy),
request.gas_price,
request.max_fee_per_gas,
request.max_priority_fee_per_gas,
)?
.or_zero_fees();

// get the highest possible gas limit, either the request's set value or the currently
// configured gas limit
let mut highest_gas_limit = request.gas.unwrap_or(block_env.gas_limit.to_ethers());
let mut highest_gas_limit = request.gas.unwrap_or(block_env.gas_limit);

// check with the funds of the sender
if let Some(from) = request.from {
let gas_price = fees.gas_price.unwrap_or_default();
if gas_price > U256::ZERO {
let mut available_funds =
self.backend.get_balance_with_state(&state, from.to_alloy())?;
let mut available_funds = self.backend.get_balance_with_state(&state, from)?;
if let Some(value) = request.value {
if value > available_funds.to_ethers() {
if value > available_funds {
return Err(InvalidTransactionError::InsufficientFunds.into());
}
// safe: value < available_funds
available_funds -= value.to_alloy();
available_funds -= value;
}
// amount of gas the sender can afford with the `gas_price`
let allowance = available_funds.checked_div(gas_price).unwrap_or_default();
if highest_gas_limit > allowance.to_ethers() {
if highest_gas_limit > allowance {
trace!(target: "node", "eth_estimateGas capped by limited user funds");
highest_gas_limit = allowance.to_ethers();
highest_gas_limit = allowance;
}
}
}
Expand Down Expand Up @@ -2264,7 +2256,7 @@ impl EthApi {
self.backend.clone(),
block_env,
fees,
gas_limit.to_alloy(),
gas_limit,
));
}
}
Expand All @@ -2275,7 +2267,7 @@ impl EthApi {
// succeeded
}
InstructionResult::OutOfGas | InstructionResult::OutOfFund => {
return Err(InvalidTransactionError::BasicOutOfGas(gas_limit).into())
return Err(InvalidTransactionError::BasicOutOfGas(gas_limit.to_ethers()).into())
}
// need to check if the revert was due to lack of gas or unrelated reason
// we're also checking for InvalidFEOpcode here because this can be used to trigger an error <https://github.com/foundry-rs/foundry/issues/6138> common usage in openzeppelin <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/94697be8a3f0dfcd95dfb13ffbd39b5973f5c65d/contracts/metatx/ERC2771Forwarder.sol#L360-L367>
Expand All @@ -2289,7 +2281,7 @@ impl EthApi {
self.backend.clone(),
block_env,
fees,
gas_limit.to_alloy(),
gas_limit,
))
} else {
// the transaction did fail due to lack of gas from the user
Expand All @@ -2309,17 +2301,18 @@ impl EthApi {
// transaction requires to succeed
let gas: U256 = U256::from(gas);
// Get the starting lowest gas needed depending on the transaction kind.
let mut lowest_gas_limit = determine_base_gas_by_kind(request.clone());
let mut lowest_gas_limit =
determine_base_gas_by_kind(call_to_internal_tx_request(&request));

// pick a point that's close to the estimated gas
let mut mid_gas_limit = std::cmp::min(
gas * U256::from(3),
((highest_gas_limit + lowest_gas_limit.to_ethers()) / 2).to_alloy(),
(highest_gas_limit + lowest_gas_limit) / U256::from(2),
);

// Binary search for the ideal gas limit
while (highest_gas_limit - lowest_gas_limit.to_ethers()).to_alloy() > U256::from(1) {
request.gas = Some(mid_gas_limit.to_ethers());
while (highest_gas_limit - lowest_gas_limit) > U256::from(1) {
request.gas = Some(mid_gas_limit);
let ethres = self.backend.call_with_state(
&state,
request.clone(),
Expand All @@ -2337,7 +2330,7 @@ impl EthApi {
lowest_gas_limit = mid_gas_limit;

// new midpoint
mid_gas_limit = ((highest_gas_limit + lowest_gas_limit.to_ethers()) / 2).to_alloy();
mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / U256::from(2);
continue;
}

Expand All @@ -2347,7 +2340,7 @@ impl EthApi {
// at the current midpoint, as spending any more gas would
// make no sense (as the TX would still succeed).
return_ok!() => {
highest_gas_limit = mid_gas_limit.to_ethers();
highest_gas_limit = mid_gas_limit;
}
// If the transaction failed due to lack of gas, we can set a floor for the
// lowest gas limit at the current midpoint, as spending any
Expand All @@ -2374,12 +2367,12 @@ impl EthApi {
}
}
// new midpoint
mid_gas_limit = ((highest_gas_limit + lowest_gas_limit.to_ethers()) / 2).to_alloy();
mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / U256::from(2);
}

trace!(target : "node", "Estimated Gas for call {:?}", highest_gas_limit);

Ok(highest_gas_limit.to_alloy())
Ok(highest_gas_limit)
}

/// Updates the `TransactionOrder`
Expand Down Expand Up @@ -2647,7 +2640,7 @@ fn ensure_return_ok(exit: InstructionResult, out: &Option<Output>) -> Result<Byt
/// not
#[inline]
fn map_out_of_gas_err<D>(
mut request: EthTransactionRequest,
mut request: CallRequest,
state: D,
backend: Arc<backend::mem::Backend>,
block_env: BlockEnv,
Expand All @@ -2657,7 +2650,7 @@ fn map_out_of_gas_err<D>(
where
D: DatabaseRef<Error = DatabaseError>,
{
request.gas = Some(backend.gas_limit()).map(|g| g.to_ethers());
request.gas = Some(backend.gas_limit());
let (exit, out, _, _) = match backend.call_with_state(&state, request, fees, block_env) {
Ok(res) => res,
Err(err) => return err,
Expand Down
Loading
Loading