Skip to content
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
2 changes: 0 additions & 2 deletions Cargo.lock

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

2 changes: 0 additions & 2 deletions crates/anvil/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ alloy-eips.workspace = true
alloy-eip5792.workspace = true
alloy-consensus = { workspace = true, features = ["k256", "kzg"] }
alloy-dyn-abi = { workspace = true, features = ["std", "eip712"] }
op-alloy-consensus = { workspace = true, features = ["serde"] }
alloy-network.workspace = true
serde.workspace = true
serde_json.workspace = true
bytes.workspace = true
Expand Down
228 changes: 3 additions & 225 deletions crates/anvil/core/src/eth/transaction/mod.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
//! Transaction related types
use alloy_consensus::{
Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, Transaction, TxEnvelope, TxReceipt,
Typed2718, transaction::Recovered,
};
use alloy_consensus::{Signed, Transaction, TxEnvelope, Typed2718, transaction::Recovered};

use alloy_eips::eip2718::Encodable2718;
use alloy_network::{AnyReceiptEnvelope, AnyTransactionReceipt};
use alloy_primitives::{Address, B256, Bloom, Bytes, TxHash, U64};
use alloy_primitives::{Address, B256, Bytes, TxHash};
use alloy_rlp::{Decodable, Encodable};
use alloy_rpc_types::{
Transaction as RpcTransaction, TransactionReceipt, trace::otterscan::OtsReceipt,
};
use alloy_serde::WithOtherFields;
use alloy_rpc_types::Transaction as RpcTransaction;
use bytes::BufMut;
use foundry_evm::traces::CallTraceNode;
use foundry_primitives::FoundryTxEnvelope;
use op_alloy_consensus::{OpDepositReceipt, OpDepositReceiptWithBloom};
use revm::interpreter::InstructionResult;
use serde::{Deserialize, Serialize};
use std::ops::Deref;
Expand Down Expand Up @@ -228,217 +220,3 @@ pub struct TransactionInfo {
pub nonce: u64,
pub gas_used: u64,
}

/// RPC-specific variant of TypedReceipt for boundary conversion
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum TypedReceiptRpc {
#[serde(rename = "0x0", alias = "0x00")]
Legacy(ReceiptWithBloom<Receipt<alloy_rpc_types::Log>>),
#[serde(rename = "0x1", alias = "0x01")]
Eip2930(ReceiptWithBloom<Receipt<alloy_rpc_types::Log>>),
#[serde(rename = "0x2", alias = "0x02")]
Eip1559(ReceiptWithBloom<Receipt<alloy_rpc_types::Log>>),
#[serde(rename = "0x3", alias = "0x03")]
Eip4844(ReceiptWithBloom<Receipt<alloy_rpc_types::Log>>),
#[serde(rename = "0x4", alias = "0x04")]
Eip7702(ReceiptWithBloom<Receipt<alloy_rpc_types::Log>>),
#[serde(rename = "0x7E", alias = "0x7e")]
Deposit(OpDepositReceiptWithBloom),
}

impl TypedReceiptRpc {
pub fn as_receipt_with_bloom(&self) -> &ReceiptWithBloom<Receipt<alloy_rpc_types::Log>> {
match self {
Self::Legacy(r)
| Self::Eip1559(r)
| Self::Eip2930(r)
| Self::Eip4844(r)
| Self::Eip7702(r) => r,
Self::Deposit(_) => unreachable!("use variant-specific helpers for deposit"),
}
}

pub fn logs_bloom(&self) -> &Bloom {
match self {
Self::Legacy(r)
| Self::Eip1559(r)
| Self::Eip2930(r)
| Self::Eip4844(r)
| Self::Eip7702(r) => &r.logs_bloom,
Self::Deposit(r) => &r.logs_bloom,
}
}

pub fn logs(&self) -> &[alloy_rpc_types::Log] {
match self {
Self::Legacy(r)
| Self::Eip1559(r)
| Self::Eip2930(r)
| Self::Eip4844(r)
| Self::Eip7702(r) => &r.receipt.logs,
Self::Deposit(_) => unreachable!("use variant-specific helpers for deposit"),
}
}

pub fn cumulative_gas_used(&self) -> u64 {
self.as_receipt_with_bloom().cumulative_gas_used()
}
}

// Intentionally only provide a concrete conversion used by RPC response/Otterscan path.
impl From<TypedReceiptRpc> for ReceiptWithBloom<Receipt<alloy_rpc_types::Log>> {
fn from(value: TypedReceiptRpc) -> Self {
match value {
TypedReceiptRpc::Legacy(r)
| TypedReceiptRpc::Eip1559(r)
| TypedReceiptRpc::Eip2930(r)
| TypedReceiptRpc::Eip4844(r)
| TypedReceiptRpc::Eip7702(r) => r,
TypedReceiptRpc::Deposit(r) => {
// Convert OP deposit receipt (primitives::Log) to RPC receipt (rpc_types::Log)
let receipt = Receipt::<alloy_rpc_types::Log> {
status: r.receipt.inner.status,
cumulative_gas_used: r.receipt.inner.cumulative_gas_used,
logs: r
.receipt
.inner
.logs
.into_iter()
.map(|l| alloy_rpc_types::Log {
inner: l,
block_hash: None,
block_number: None,
block_timestamp: None,
transaction_hash: None,
transaction_index: None,
log_index: None,
removed: false,
})
.collect(),
};
Self { receipt, logs_bloom: r.logs_bloom }
}
}
}
}

impl From<TypedReceiptRpc> for OtsReceipt {
fn from(value: TypedReceiptRpc) -> Self {
let r#type = match value {
TypedReceiptRpc::Legacy(_) => 0x00,
TypedReceiptRpc::Eip2930(_) => 0x01,
TypedReceiptRpc::Eip1559(_) => 0x02,
TypedReceiptRpc::Eip4844(_) => 0x03,
TypedReceiptRpc::Eip7702(_) => 0x04,
TypedReceiptRpc::Deposit(_) => 0x7E,
} as u8;
let receipt = ReceiptWithBloom::<Receipt<alloy_rpc_types::Log>>::from(value);
let status = receipt.status();
let cumulative_gas_used = receipt.cumulative_gas_used();
let logs = receipt.logs().to_vec();
let logs_bloom = receipt.logs_bloom;

Self { status, cumulative_gas_used, logs: Some(logs), logs_bloom: Some(logs_bloom), r#type }
}
}

impl From<ReceiptEnvelope<alloy_rpc_types::Log>> for TypedReceiptRpc {
fn from(value: ReceiptEnvelope<alloy_rpc_types::Log>) -> Self {
match value {
ReceiptEnvelope::Legacy(r) => Self::Legacy(r),
ReceiptEnvelope::Eip2930(r) => Self::Eip2930(r),
ReceiptEnvelope::Eip1559(r) => Self::Eip1559(r),
ReceiptEnvelope::Eip4844(r) => Self::Eip4844(r),
ReceiptEnvelope::Eip7702(r) => Self::Eip7702(r),
}
}
}

pub type ReceiptResponse = WithOtherFields<TransactionReceipt<TypedReceiptRpc>>;

pub fn convert_to_anvil_receipt(receipt: AnyTransactionReceipt) -> Option<ReceiptResponse> {
let WithOtherFields {
inner:
TransactionReceipt {
transaction_hash,
transaction_index,
block_hash,
block_number,
gas_used,
contract_address,
effective_gas_price,
from,
to,
blob_gas_price,
blob_gas_used,
inner: AnyReceiptEnvelope { inner: receipt_with_bloom, r#type },
},
other,
} = receipt;

Some(WithOtherFields {
inner: TransactionReceipt {
transaction_hash,
transaction_index,
block_hash,
block_number,
gas_used,
contract_address,
effective_gas_price,
from,
to,
blob_gas_price,
blob_gas_used,
inner: match r#type {
0x00 => TypedReceiptRpc::Legacy(receipt_with_bloom),
0x01 => TypedReceiptRpc::Eip2930(receipt_with_bloom),
0x02 => TypedReceiptRpc::Eip1559(receipt_with_bloom),
0x03 => TypedReceiptRpc::Eip4844(receipt_with_bloom),
0x04 => TypedReceiptRpc::Eip7702(receipt_with_bloom),
0x7E => TypedReceiptRpc::Deposit(OpDepositReceiptWithBloom {
receipt: OpDepositReceipt {
inner: Receipt {
status: alloy_consensus::Eip658Value::Eip658(
receipt_with_bloom.status(),
),
cumulative_gas_used: receipt_with_bloom.cumulative_gas_used(),
logs: receipt_with_bloom
.receipt
.logs
.into_iter()
.map(|l| l.inner)
.collect(),
},
deposit_nonce: other
.get_deserialized::<U64>("depositNonce")
.transpose()
.ok()?
.map(|v| v.to()),
deposit_receipt_version: other
.get_deserialized::<U64>("depositReceiptVersion")
.transpose()
.ok()?
.map(|v| v.to()),
},
logs_bloom: receipt_with_bloom.logs_bloom,
}),
_ => return None,
},
},
other,
})
}

#[cfg(test)]
mod tests {
use super::*;

// <https://github.com/foundry-rs/foundry/issues/10852>
#[test]
fn test_receipt_convert() {
let s = r#"{"type":"0x4","status":"0x1","cumulativeGasUsed":"0x903fd1","logs":[{"address":"0x0000d9fcd47bf761e7287d8ee09917d7e2100000","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000234ce51365b9c417171b6dad280f49143e1b0547"],"data":"0x00000000000000000000000000000000000000000000032139b42c3431700000","blockHash":"0xd26b59c1d8b5bfa9362d19eb0da3819dfe0b367987a71f6d30908dd45e0d7a60","blockNumber":"0x159663e","blockTimestamp":"0x68411f7b","transactionHash":"0x17a6af73d1317e69cfc3cac9221bd98261d40f24815850a44dbfbf96652ae52a","transactionIndex":"0x22","logIndex":"0x158","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000008100000000000000000000000000000000000000000000000020000200000000000000800000000800000000000000010000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x17a6af73d1317e69cfc3cac9221bd98261d40f24815850a44dbfbf96652ae52a","transactionIndex":"0x22","blockHash":"0xd26b59c1d8b5bfa9362d19eb0da3819dfe0b367987a71f6d30908dd45e0d7a60","blockNumber":"0x159663e","gasUsed":"0x28ee7","effectiveGasPrice":"0x4bf02090","from":"0x234ce51365b9c417171b6dad280f49143e1b0547","to":"0x234ce51365b9c417171b6dad280f49143e1b0547","contractAddress":null}"#;
let receipt: AnyTransactionReceipt = serde_json::from_str(s).unwrap();
let _converted = convert_to_anvil_receipt(receipt).unwrap();
}
}
33 changes: 13 additions & 20 deletions crates/anvil/src/eth/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ use alloy_eips::{
};
use alloy_evm::overrides::{OverrideBlockHashes, apply_state_overrides};
use alloy_network::{
AnyRpcBlock, AnyRpcTransaction, BlockResponse, TransactionBuilder, TransactionBuilder4844,
TransactionResponse, eip2718::Decodable2718,
AnyRpcBlock, AnyRpcTransaction, BlockResponse, ReceiptResponse, TransactionBuilder,
TransactionBuilder4844, TransactionResponse, eip2718::Decodable2718,
};
use alloy_primitives::{
Address, B64, B256, Bytes, Signature, TxHash, TxKind, U64, U256,
Expand Down Expand Up @@ -69,7 +69,7 @@ use anvil_core::{
eth::{
EthRequest,
block::BlockInfo,
transaction::{MaybeImpersonatedTransaction, PendingTransaction, ReceiptResponse},
transaction::{MaybeImpersonatedTransaction, PendingTransaction},
wallet::WalletCapabilities,
},
types::{ReorgOptions, TransactionData},
Expand All @@ -78,7 +78,7 @@ use anvil_rpc::{error::RpcError, response::ResponseResult};
use foundry_common::provider::ProviderBuilder;
use foundry_evm::decode::RevertDecoder;
use foundry_primitives::{
FoundryTransactionRequest, FoundryTxEnvelope, FoundryTxType, FoundryTypedTx,
FoundryTransactionRequest, FoundryTxEnvelope, FoundryTxReceipt, FoundryTxType, FoundryTypedTx,
};
use futures::{
StreamExt, TryFutureExt,
Expand Down Expand Up @@ -1099,7 +1099,7 @@ impl EthApi {
}

/// Waits for a transaction to be included in a block and returns its receipt (no timeout).
async fn await_transaction_inclusion(&self, hash: TxHash) -> Result<ReceiptResponse> {
async fn await_transaction_inclusion(&self, hash: TxHash) -> Result<FoundryTxReceipt> {
let mut stream = self.new_block_notifications();
// Check if the transaction is already included before listening for new blocks.
if let Some(receipt) = self.backend.transaction_receipt(hash).await? {
Expand All @@ -1118,7 +1118,7 @@ impl EthApi {
}

/// Waits for a transaction to be included in a block and returns its receipt, with timeout.
async fn check_transaction_inclusion(&self, hash: TxHash) -> Result<ReceiptResponse> {
async fn check_transaction_inclusion(&self, hash: TxHash) -> Result<FoundryTxReceipt> {
const TIMEOUT_DURATION: Duration = Duration::from_secs(30);
tokio::time::timeout(TIMEOUT_DURATION, self.await_transaction_inclusion(hash))
.await
Expand All @@ -1136,13 +1136,13 @@ impl EthApi {
pub async fn send_transaction_sync(
&self,
request: WithOtherFields<TransactionRequest>,
) -> Result<ReceiptResponse> {
) -> Result<FoundryTxReceipt> {
node_info!("eth_sendTransactionSync");
let hash = self.send_transaction(request).await?;

let receipt = self.check_transaction_inclusion(hash).await?;

Ok(ReceiptResponse::from(receipt))
Ok(receipt)
}

/// Sends signed transaction, returning its hash.
Expand Down Expand Up @@ -1186,13 +1186,13 @@ impl EthApi {
/// Sends signed transaction, returning its receipt.
///
/// Handler for ETH RPC call: `eth_sendRawTransactionSync`
pub async fn send_raw_transaction_sync(&self, tx: Bytes) -> Result<ReceiptResponse> {
pub async fn send_raw_transaction_sync(&self, tx: Bytes) -> Result<FoundryTxReceipt> {
node_info!("eth_sendRawTransactionSync");

let hash = self.send_raw_transaction(tx).await?;
let receipt = self.check_transaction_inclusion(hash).await?;

Ok(ReceiptResponse::from(receipt))
Ok(receipt)
}

/// Call contract, returning the output data.
Expand Down Expand Up @@ -1578,15 +1578,15 @@ impl EthApi {
/// Returns transaction receipt by transaction hash.
///
/// Handler for ETH RPC call: `eth_getTransactionReceipt`
pub async fn transaction_receipt(&self, hash: B256) -> Result<Option<ReceiptResponse>> {
pub async fn transaction_receipt(&self, hash: B256) -> Result<Option<FoundryTxReceipt>> {
node_info!("eth_getTransactionReceipt");
self.backend.transaction_receipt(hash).await
}

/// Returns block receipts by block number.
///
/// Handler for ETH RPC call: `eth_getBlockReceipts`
pub async fn block_receipts(&self, number: BlockId) -> Result<Option<Vec<ReceiptResponse>>> {
pub async fn block_receipts(&self, number: BlockId) -> Result<Option<Vec<FoundryTxReceipt>>> {
node_info!("eth_getBlockReceipts");
self.backend.block_receipts(number).await
}
Expand Down Expand Up @@ -2772,14 +2772,7 @@ impl EthApi {
&& let Some(output) = receipt.out
{
// insert revert reason if failure
if !receipt
.inner
.inner
.inner
.as_receipt_with_bloom()
.receipt
.status
.coerce_status()
if !receipt.inner.as_ref().status()
&& let Some(reason) = RevertDecoder::new().maybe_decode(&output, None)
{
tx.other.insert(
Expand Down
Loading
Loading