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

fix: estimate gas if missing for eth_sendTransaction #6934

Merged
merged 2 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
38 changes: 37 additions & 1 deletion crates/anvil/core/src/eth/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use alloy_network::{Signed, Transaction, TxKind};
use alloy_primitives::{Address, Bloom, Bytes, Log, Signature, TxHash, B256, U128, U256, U64};
use alloy_rlp::{Decodable, Encodable};
use alloy_rpc_types::{
request::TransactionRequest, AccessList, AccessListItem, CallRequest,
request::TransactionRequest, AccessList, AccessListItem, CallInput, CallRequest,
Signature as RpcSignature, Transaction as RpcTransaction,
};
use foundry_evm::traces::CallTraceNode;
Expand Down Expand Up @@ -72,6 +72,42 @@ pub struct EthTransactionRequest {
pub optimism_fields: Option<OptimismDepositRequestFields>,
}

impl EthTransactionRequest {
pub fn into_call_request(self) -> CallRequest {
let Self {
from,
to,
gas_price,
max_fee_per_gas,
max_priority_fee_per_gas,
gas,
value,
data,
nonce,
chain_id,
access_list,
transaction_type,
optimism_fields: _,
} = self;
CallRequest {
from,
to,
gas_price,
max_fee_per_gas,
max_priority_fee_per_gas,
gas,
value,
input: CallInput::maybe_input(data),
nonce: nonce.map(|n| n.to()),
chain_id,
access_list: access_list.map(AccessList),
max_fee_per_blob_gas: None,
blob_versioned_hashes: None,
transaction_type: transaction_type.map(|ty| ty.to()),
}
}
}

#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
Expand Down
21 changes: 19 additions & 2 deletions crates/anvil/src/eth/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@ impl EthApi {
/// Signs a transaction
///
/// Handler for ETH RPC call: `eth_signTransaction`
pub async fn sign_transaction(&self, request: EthTransactionRequest) -> Result<String> {
pub async fn sign_transaction(&self, mut request: EthTransactionRequest) -> Result<String> {
node_info!("eth_signTransaction");

let from = request.from.map(Ok).unwrap_or_else(|| {
Expand All @@ -858,6 +858,13 @@ impl EthApi {

let (nonce, _) = self.request_nonce(&request, from).await?;

if request.gas.is_none() {
// estimate if not provided
if let Ok(gas) = self.estimate_gas(request.clone().into_call_request(), None).await {
request.gas = Some(gas);
}
}

let request = self.build_typed_tx_request(request, nonce)?;

let signer = self.get_signer(from).ok_or(BlockchainError::NoSignerAvailable)?;
Expand All @@ -869,13 +876,21 @@ impl EthApi {
/// Sends a transaction
///
/// Handler for ETH RPC call: `eth_sendTransaction`
pub async fn send_transaction(&self, request: EthTransactionRequest) -> Result<TxHash> {
pub async fn send_transaction(&self, mut request: EthTransactionRequest) -> Result<TxHash> {
node_info!("eth_sendTransaction");

let from = request.from.map(Ok).unwrap_or_else(|| {
self.accounts()?.first().cloned().ok_or(BlockchainError::NoSignerAvailable)
})?;
let (nonce, on_chain_nonce) = self.request_nonce(&request, from).await?;

if request.gas.is_none() {
// estimate if not provided
if let Ok(gas) = self.estimate_gas(request.clone().into_call_request(), None).await {
request.gas = Some(gas);
}
}

let request = self.build_typed_tx_request(request, nonce)?;
// if the sender is currently impersonated we need to "bypass" signing
let pending_transaction = if self.is_impersonated(from) {
Expand All @@ -885,7 +900,9 @@ impl EthApi {
trace!(target : "node", ?from, "eth_sendTransaction: impersonating");
PendingTransaction::with_impersonated(transaction, from)
} else {
dbg!(&request);
let transaction = self.sign_request(&from, request)?;
dbg!(&transaction);
mattsse marked this conversation as resolved.
Show resolved Hide resolved
self.ensure_typed_transaction_supported(&transaction)?;
PendingTransaction::new(transaction)?
};
Expand Down
8 changes: 7 additions & 1 deletion crates/anvil/src/eth/backend/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use foundry_evm::{
use std::sync::Arc;

/// Represents an executed transaction (transacted on the DB)
#[derive(Debug)]
pub struct ExecutedTransaction {
transaction: Arc<PoolTransaction>,
exit_reason: InstructionResult,
Expand Down Expand Up @@ -142,8 +143,12 @@ impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<'
included.push(tx.transaction.clone());
tx
}
TransactionExecutionOutcome::Exhausted(_) => continue,
TransactionExecutionOutcome::Exhausted(tx) => {
trace!(target: "backend", tx_gas_limit = %tx.pending_transaction.transaction.gas_limit(), ?tx, "block gas limit exhausting, skipping transaction");
continue
}
TransactionExecutionOutcome::Invalid(tx, _) => {
trace!(target: "backend", ?tx, "skipping invalid transaction");
invalid.push(tx);
continue
}
Expand Down Expand Up @@ -221,6 +226,7 @@ impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<'
}

/// Represents the result of a single transaction execution attempt
#[derive(Debug)]
pub enum TransactionExecutionOutcome {
/// Transaction successfully executed
Executed(ExecutedTransaction),
Expand Down
27 changes: 27 additions & 0 deletions crates/anvil/tests/it/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ use crate::{
utils::{ethers_http_provider, ethers_ws_provider},
};
use alloy_primitives::U256 as rU256;
use alloy_rpc_types::BlockNumberOrTag;
use alloy_signer::Signer as AlloySigner;
use anvil::{spawn, Hardfork, NodeConfig};
use anvil_core::eth::transaction::EthTransactionRequest;
use ethers::{
abi::ethereum_types::BigEndianHash,
prelude::{
Expand Down Expand Up @@ -1032,3 +1034,28 @@ async fn test_reject_eip1559_pre_london() {
let greeting = greeter_contract.greet().call().await.unwrap();
assert_eq!("Hello World!", greeting);
}

// https://github.com/foundry-rs/foundry/issues/6931
#[tokio::test(flavor = "multi_thread")]
async fn can_mine_multiple_in_block() {
let (api, _handle) = spawn(NodeConfig::test()).await;

// disable auto mine
api.anvil_set_auto_mine(false).await.unwrap();

let tx = EthTransactionRequest {
from: Some("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap()),
..Default::default()
};

// broadcast it via the eth_sendTransaction API
let first = api.send_transaction(tx.clone()).await.unwrap();
let second = api.send_transaction(tx.clone()).await.unwrap();

api.anvil_mine(Some(rU256::from(1)), Some(rU256::ZERO)).await.unwrap();

let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap();

let txs = block.transactions.hashes().copied().collect::<Vec<_>>();
assert_eq!(txs, vec![first, second]);
}
Loading