diff --git a/.github/workflows/edr-ci.yml b/.github/workflows/edr-ci.yml index e919b5c63..2b253f4cc 100644 --- a/.github/workflows/edr-ci.yml +++ b/.github/workflows/edr-ci.yml @@ -112,6 +112,51 @@ jobs: # command: nextest # args: run --workspace --all-features --all-targets + test-edr-ts: + name: Test EDR TS bindings (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + needs: check-edr + strategy: + fail-fast: false + matrix: + node: [18.15] + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v3 + + - uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Install Node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + cache: pnpm + + - name: Install Rust (stable) + uses: actions-rs/toolchain@v1 + with: + profile: minimal + override: true + + - uses: Swatinem/rust-cache@v2 + + - name: Cache EDR RPC cache + uses: actions/cache@v2 + with: + path: | + **/edr-cache + key: edr-ts-rpc-cache-v1 + + - name: Install package + run: pnpm install --frozen-lockfile --prefer-offline + + - name: Run tests + env: + ALCHEMY_URL: ${{ secrets.ALCHEMY_URL }} + run: cd crates/edr_napi && pnpm test + edr-style: name: Check EDR Style runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index 8a7eb084b..1004ada2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1155,6 +1155,32 @@ dependencies = [ "tracing", ] +[[package]] +name = "edr_generic" +version = "0.3.5" +dependencies = [ + "alloy-rlp", + "anyhow", + "derive-where", + "edr_defaults", + "edr_eth", + "edr_evm", + "edr_provider", + "edr_rpc_client", + "edr_rpc_eth", + "edr_test_utils", + "log", + "parking_lot 0.12.1", + "paste", + "revm", + "revm-primitives", + "serde", + "serde_json", + "serial_test", + "thiserror", + "tokio", +] + [[package]] name = "edr_napi" version = "0.3.5" @@ -1163,6 +1189,7 @@ dependencies = [ "derive-where", "edr_eth", "edr_evm", + "edr_generic", "edr_napi_core", "edr_provider", "edr_rpc_client", diff --git a/crates/edr_eth/src/transaction/signed.rs b/crates/edr_eth/src/transaction/signed.rs index c47e25cec..e97708856 100644 --- a/crates/edr_eth/src/transaction/signed.rs +++ b/crates/edr_eth/src/transaction/signed.rs @@ -73,10 +73,6 @@ impl Signed { _ => None, } } - - pub fn is_invalid_transaction_type_error(message: &str) -> bool { - message == INVALID_TX_TYPE_ERROR_MESSAGE - } } impl alloy_rlp::Decodable for Signed { diff --git a/crates/edr_evm/src/hardfork.rs b/crates/edr_evm/src/hardfork.rs index 4930dd31c..3b709ea44 100644 --- a/crates/edr_evm/src/hardfork.rs +++ b/crates/edr_evm/src/hardfork.rs @@ -55,6 +55,17 @@ impl Activations { }) .map(|entry| entry.1) } + + /// Views the activations as for a different chain spec (that shares the + /// underlying hardforks). + pub fn as_chain_spec>( + &'static self, + ) -> &'static Activations { + // SAFETY: The layout is the same as we're using the same struct and the + // Hardfork associated type is the same and we are also converting from + // one static reference to another, so no lifetime hazards here as well. + unsafe { std::mem::transmute(self) } + } } impl From<&[(ForkCondition, ChainSpecT::Hardfork)]> diff --git a/crates/edr_evm/tests/optimism.rs b/crates/edr_evm/tests/optimism.rs deleted file mode 100644 index c1274d671..000000000 --- a/crates/edr_evm/tests/optimism.rs +++ /dev/null @@ -1,50 +0,0 @@ -#![cfg(feature = "test-remote")] - -// use std::sync::Arc; - -// use edr_defaults::CACHE_DIR; -// use edr_eth::{chain_spec::L1ChainSpec, HashMap, SpecId}; -// use edr_evm::{ -// blockchain::{Blockchain, ForkedBlockchain}, -// state::IrregularState, -// RandomHashGenerator, -// }; -// use edr_rpc_eth::client::EthRpcClient; -// use edr_test_utils::env::get_alchemy_url; -// use parking_lot::Mutex; -// use tokio::runtime; - -// TODO: Add fallback chain -// #[tokio::test(flavor = "multi_thread")] -// async fn unknown_transaction_types() -> anyhow::Result<()> { -// const BLOCK_NUMBER_WITH_TRANSACTIONS: u64 = 117_156_000; - -// let url = get_alchemy_url().replace("eth-", "opt-"); -// // TODO: https://github.com/NomicFoundation/edr/issues/512 -// // Change the spec to `OptimismChainSpec` once it's implemented -// let rpc_client = EthRpcClient::::new(&url, CACHE_DIR.into(), -// None)?; let mut irregular_state = IrregularState::default(); -// let state_root_generator = -// Arc::new(Mutex::new(RandomHashGenerator::with_seed("test"))); -// let hardfork_activation_overrides = HashMap::new(); - -// let blockchain = ForkedBlockchain::new( -// runtime::Handle::current(), -// None, -// SpecId::LATEST, -// Arc::new(rpc_client), -// None, -// &mut irregular_state, -// state_root_generator, -// &hardfork_activation_overrides, -// ) -// .await?; - -// let block_with_transactions = blockchain -// .block_by_number(BLOCK_NUMBER_WITH_TRANSACTIONS)? -// .expect("Block must exist"); - -// let _receipts = block_with_transactions.transaction_receipts()?; - -// Ok(()) -// } diff --git a/crates/edr_generic/Cargo.toml b/crates/edr_generic/Cargo.toml new file mode 100644 index 000000000..95a64819c --- /dev/null +++ b/crates/edr_generic/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "edr_generic" +version = "0.3.5" +edition = "2021" +description = "A generic chain specification for Ethereum Layer 1 chains." + +[dependencies] +alloy-rlp = { version = "0.3", default-features = false, features = ["derive"] } +derive-where = { version = "1.2.7", default-features = false } +edr_eth = { path = "../edr_eth" } +edr_evm = { path = "../edr_evm" } +edr_provider = { path = "../edr_provider" } +edr_rpc_eth = { path = "../edr_rpc_eth" } +log = { version = "0.4.17", default-features = false } +revm = { git = "https://github.com/Wodann/revm", rev = "a500675", version = "12.1", default-features = false, features = ["c-kzg", "dev", "serde"] } +revm-primitives = { git = "https://github.com/Wodann/revm", rev = "a500675", version = "7.1", default-features = false, features = ["c-kzg", "hashbrown"] } +serde = { version = "1.0.209", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.37", default-features = false } + +[dev-dependencies] +anyhow = "1.0.75" +edr_defaults = { path = "../edr_defaults" } +edr_evm = { path = "../edr_evm", features = ["test-utils"] } +edr_rpc_eth = { path = "../edr_rpc_eth", features = ["test-utils"] } +edr_rpc_client = { path = "../edr_rpc_client" } +edr_test_utils = { path = "../edr_test_utils" } +parking_lot = { version = "0.12.1", default-features = false } +paste = { version = "1.0.14", default-features = false } +serde_json = { version = "1.0.127" } +serial_test = "2.0.0" +tokio = { version = "1.21.2", default-features = false, features = ["macros", "rt-multi-thread", "sync"] } + +[features] +test-remote = [] + +[lints] +workspace = true diff --git a/crates/edr_generic/src/eip2718.rs b/crates/edr_generic/src/eip2718.rs new file mode 100644 index 000000000..613be7af0 --- /dev/null +++ b/crates/edr_generic/src/eip2718.rs @@ -0,0 +1,145 @@ +use alloy_rlp::Buf as _; +use edr_eth::{ + receipt::{MapReceiptLogs, Receipt, RootOrStatus}, + transaction::TransactionType, + Bloom, +}; + +use crate::transaction; + +/// An compile-time typed EIP-2718 envelope for L1 Ethereum. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum TypedEnvelope { + /// Legacy transaction. + Legacy(DataT), + /// EIP-2930 transaction. + Eip2930(DataT), + /// EIP-1559 transaction. + Eip1559(DataT), + /// EIP-4844 transaction. + Eip4844(DataT), + /// Unrecognized transaction type. + Unrecognized(DataT), +} + +impl TypedEnvelope { + /// Constructs a typed envelope around the given data. + pub fn new(data: DataT, transaction_type: transaction::Type) -> Self { + match transaction_type { + transaction::Type::Legacy => Self::Legacy(data), + transaction::Type::Eip2930 => Self::Eip2930(data), + transaction::Type::Eip1559 => Self::Eip1559(data), + transaction::Type::Eip4844 => Self::Eip4844(data), + transaction::Type::Unrecognized(_) => Self::Unrecognized(data), + } + } + + /// Returns a reference to the data inside the envelope. + pub fn data(&self) -> &DataT { + match self { + TypedEnvelope::Legacy(data) + | TypedEnvelope::Eip2930(data) + | TypedEnvelope::Eip1559(data) + | TypedEnvelope::Eip4844(data) + | TypedEnvelope::Unrecognized(data) => data, + } + } + + /// Maps the data inside the envelope to a new type. + pub fn map(self, f: F) -> TypedEnvelope + where + F: FnOnce(DataT) -> NewDataT, + { + match self { + TypedEnvelope::Legacy(data) => TypedEnvelope::Legacy(f(data)), + TypedEnvelope::Eip2930(data) => TypedEnvelope::Eip2930(f(data)), + TypedEnvelope::Eip1559(data) => TypedEnvelope::Eip1559(f(data)), + TypedEnvelope::Eip4844(data) => TypedEnvelope::Eip4844(f(data)), + TypedEnvelope::Unrecognized(data) => TypedEnvelope::Unrecognized(f(data)), + } + } +} + +impl TransactionType for TypedEnvelope { + type Type = transaction::Type; + + fn transaction_type(&self) -> Self::Type { + match self { + TypedEnvelope::Legacy(_) => transaction::Type::Legacy, + TypedEnvelope::Eip2930(_) => transaction::Type::Eip2930, + TypedEnvelope::Eip1559(_) => transaction::Type::Eip1559, + TypedEnvelope::Eip4844(_) => transaction::Type::Eip4844, + // TODO: Should we properly decode the transaction type? + TypedEnvelope::Unrecognized(_) => transaction::Type::Unrecognized(0xFF), + } + } +} + +impl alloy_rlp::Decodable for TypedEnvelope +where + DataT: alloy_rlp::Decodable, +{ + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + fn is_list(byte: u8) -> bool { + byte >= 0xc0 + } + + let first = *buf.first().ok_or(alloy_rlp::Error::InputTooShort)?; + let transaction_type = if is_list(first) { + transaction::Type::Legacy + } else { + // Consume the first byte + buf.advance(1); + + crate::transaction::Type::from(first) + }; + + let data = DataT::decode(buf)?; + Ok(TypedEnvelope::new(data, transaction_type)) + } +} + +impl alloy_rlp::Encodable for TypedEnvelope +where + DataT: alloy_rlp::Encodable, +{ + fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { + let transaction_type: u8 = self.transaction_type().into(); + if transaction_type > 0 { + out.put_u8(transaction_type); + } + + self.data().encode(out); + } + + fn length(&self) -> usize { + let type_length = usize::from(u8::from(self.transaction_type()) > 0u8); + type_length + self.data().length() + } +} + +impl, LogT> Receipt for TypedEnvelope { + fn cumulative_gas_used(&self) -> u64 { + self.data().cumulative_gas_used() + } + + fn logs_bloom(&self) -> &Bloom { + self.data().logs_bloom() + } + + fn transaction_logs(&self) -> &[LogT] { + self.data().transaction_logs() + } + + fn root_or_status(&self) -> RootOrStatus<'_> { + self.data().root_or_status() + } +} + +impl, OldLogT, NewLogT, NewDataT> + MapReceiptLogs> for TypedEnvelope +{ + fn map_logs(self, map_fn: impl FnMut(OldLogT) -> NewLogT) -> TypedEnvelope { + self.map(|data| data.map_logs(map_fn)) + } +} diff --git a/crates/edr_generic/src/lib.rs b/crates/edr_generic/src/lib.rs new file mode 100644 index 000000000..2f6a1207c --- /dev/null +++ b/crates/edr_generic/src/lib.rs @@ -0,0 +1,18 @@ +//! A slightly more flexible chain specification for Ethereum Layer 1 chain. + +mod eip2718; +mod receipt; +mod rpc; +mod spec; +mod transaction; + +/// The chain specification for Ethereum Layer 1 that is a bit more lenient +/// and allows for more flexibility in contrast to +/// [`L1ChainSpec`](edr_eth::chain_spec::L1ChainSpec). +/// +/// Specifically: +/// - it allows unknown transaction types (treats them as legacy +/// [`Eip155`](edr_eth::transaction::signed::Eip155) transactions) +/// - it allows remote blocks with missing `nonce` and `mix_hash` fields +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, alloy_rlp::RlpEncodable)] +pub struct GenericChainSpec; diff --git a/crates/edr_generic/src/receipt.rs b/crates/edr_generic/src/receipt.rs new file mode 100644 index 000000000..1428a01fc --- /dev/null +++ b/crates/edr_generic/src/receipt.rs @@ -0,0 +1,3 @@ +pub use edr_eth::receipt::BlockReceipt; + +pub mod execution; diff --git a/crates/edr_generic/src/receipt/execution.rs b/crates/edr_generic/src/receipt/execution.rs new file mode 100644 index 000000000..b77e6eacd --- /dev/null +++ b/crates/edr_generic/src/receipt/execution.rs @@ -0,0 +1,53 @@ +use edr_eth::{ + log::ExecutionLog, + receipt::{ + execution::{Eip658, Legacy}, + Execution, ExecutionReceiptBuilder, + }, + transaction::TransactionType, +}; +use revm_primitives::{EvmWiring, SpecId}; + +use crate::{eip2718::TypedEnvelope, GenericChainSpec}; + +pub struct Builder; + +impl ExecutionReceiptBuilder for Builder { + type Receipt = TypedEnvelope>; + + fn new_receipt_builder( + _pre_execution_state: StateT, + _transaction: &::Transaction, + ) -> Result { + Ok(Self) + } + + fn build_receipt( + self, + header: &edr_eth::block::PartialHeader, + transaction: &crate::transaction::SignedWithFallbackToPostEip155, + result: &revm_primitives::ExecutionResult, + hardfork: SpecId, + ) -> Self::Receipt { + let logs = result.logs().to_vec(); + let logs_bloom = edr_eth::log::logs_to_bloom(&logs); + + let receipt = if hardfork >= SpecId::BYZANTIUM { + Execution::Eip658(Eip658 { + status: result.is_success(), + cumulative_gas_used: header.gas_used, + logs_bloom, + logs, + }) + } else { + Execution::Legacy(Legacy { + root: header.state_root, + cumulative_gas_used: header.gas_used, + logs_bloom, + logs, + }) + }; + + TypedEnvelope::new(receipt, transaction.transaction_type()) + } +} diff --git a/crates/edr_generic/src/rpc.rs b/crates/edr_generic/src/rpc.rs new file mode 100644 index 000000000..05d5d85d3 --- /dev/null +++ b/crates/edr_generic/src/rpc.rs @@ -0,0 +1,17 @@ +use edr_rpc_eth::RpcSpec; +use serde::{de::DeserializeOwned, Serialize}; + +use crate::{eip2718::TypedEnvelope, GenericChainSpec}; + +pub mod block; +pub mod receipt; +pub mod transaction; + +impl RpcSpec for GenericChainSpec { + type ExecutionReceipt = TypedEnvelope>; + type RpcBlock = self::block::Block where Data: Default + DeserializeOwned + Serialize; + type RpcCallRequest = edr_rpc_eth::CallRequest; + type RpcReceipt = self::receipt::BlockReceipt; + type RpcTransaction = self::transaction::TransactionWithSignature; + type RpcTransactionRequest = edr_rpc_eth::TransactionRequest; +} diff --git a/crates/edr_generic/src/rpc/block.rs b/crates/edr_generic/src/rpc/block.rs new file mode 100644 index 000000000..dde95492d --- /dev/null +++ b/crates/edr_generic/src/rpc/block.rs @@ -0,0 +1,302 @@ +use derive_where::derive_where; +use edr_eth::{ + block::{BlobGas, Header}, + transaction::ExecutableTransaction as _, + withdrawal::Withdrawal, + Address, Bloom, Bytes, B256, B64, U256, +}; +use edr_evm::{chain_spec::ChainSpec, BlockAndTotalDifficulty, EthBlockData, EthRpcBlock}; +use edr_rpc_eth::spec::GetBlockNumber; +use serde::{Deserialize, Serialize}; + +use crate::GenericChainSpec; + +/// block object returned by `eth_getBlockBy*` +#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Block { + /// Hash of the block + pub hash: Option, + /// hash of the parent block. + pub parent_hash: B256, + /// SHA3 of the uncles data in the block + pub sha3_uncles: B256, + /// the root of the final state trie of the block + pub state_root: B256, + /// the root of the transaction trie of the block + pub transactions_root: B256, + /// the root of the receipts trie of the block + pub receipts_root: B256, + /// the block number. None when its pending block. + #[serde(with = "edr_eth::serde::optional_u64")] + pub number: Option, + /// the total used gas by all transactions in this block + #[serde(with = "edr_eth::serde::u64")] + pub gas_used: u64, + /// the maximum gas allowed in this block + #[serde(with = "edr_eth::serde::u64")] + pub gas_limit: u64, + /// the "extra data" field of this block + pub extra_data: Bytes, + /// the bloom filter for the logs of the block + pub logs_bloom: Bloom, + /// the unix timestamp for when the block was collated + #[serde(with = "edr_eth::serde::u64")] + pub timestamp: u64, + /// integer of the difficulty for this blocket + pub difficulty: U256, + /// integer of the total difficulty of the chain until this block + pub total_difficulty: Option, + /// Array of uncle hashes + #[serde(default)] + pub uncles: Vec, + /// Array of transaction objects, or 32 Bytes transaction hashes depending + /// on the last given parameter + #[serde(default)] + pub transactions: Vec, + /// the length of the RLP encoding of this block in bytes + #[serde(with = "edr_eth::serde::u64")] + pub size: u64, + /// Mix hash. None when it's a pending block. + pub mix_hash: Option, + /// hash of the generated proof-of-work. null when its pending block. + pub nonce: Option, + /// base fee per gas + #[serde(skip_serializing_if = "Option::is_none")] + pub base_fee_per_gas: Option, + /// the address of the beneficiary to whom the mining rewards were given + #[serde(skip_serializing_if = "Option::is_none")] + pub miner: Option
, + /// withdrawals + #[serde(skip_serializing_if = "Option::is_none")] + pub withdrawals: Option>, + /// withdrawals root + #[serde(skip_serializing_if = "Option::is_none")] + pub withdrawals_root: Option, + /// The total amount of gas used by the transactions. + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "edr_eth::serde::optional_u64" + )] + pub blob_gas_used: Option, + /// A running total of blob gas consumed in excess of the target, prior to + /// the block. + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "edr_eth::serde::optional_u64" + )] + pub excess_blob_gas: Option, + /// Root of the parent beacon block + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_beacon_block_root: Option, +} + +impl GetBlockNumber for Block { + fn number(&self) -> Option { + self.number + } +} + +impl EthRpcBlock for Block { + fn state_root(&self) -> &B256 { + &self.state_root + } + + fn timestamp(&self) -> u64 { + self.timestamp + } + + fn total_difficulty(&self) -> Option<&U256> { + self.total_difficulty.as_ref() + } +} + +/// Error that occurs when trying to convert the JSON-RPC `Block` type. +#[derive(thiserror::Error)] +#[derive_where(Debug; ChainSpecT::RpcTransactionConversionError)] +pub enum ConversionError { + /// Missing hash + #[error("Missing hash")] + MissingHash, + /// Missing miner + #[error("Missing miner")] + MissingMiner, + /// Missing number + #[error("Missing numbeer")] + MissingNumber, + /// Transaction conversion error + #[error(transparent)] + Transaction(ChainSpecT::RpcTransactionConversionError), +} + +impl TryFrom> for EthBlockData +where + TransactionT: TryInto< + crate::transaction::SignedWithFallbackToPostEip155, + Error = crate::rpc::transaction::ConversionError, + >, +{ + type Error = ConversionError; + + fn try_from(value: Block) -> Result { + let header = Header { + parent_hash: value.parent_hash, + ommers_hash: value.sha3_uncles, + beneficiary: value.miner.ok_or(ConversionError::MissingMiner)?, + state_root: value.state_root, + transactions_root: value.transactions_root, + receipts_root: value.receipts_root, + logs_bloom: value.logs_bloom, + difficulty: value.difficulty, + number: value.number.ok_or(ConversionError::MissingNumber)?, + gas_limit: value.gas_limit, + gas_used: value.gas_used, + timestamp: value.timestamp, + extra_data: value.extra_data, + // Do what Hardhat does and accept the remote blocks with missing + // nonce or mix hash. See https://github.com/NomicFoundation/edr/issues/635 + mix_hash: value.mix_hash.unwrap_or_default(), + nonce: value.nonce.unwrap_or_default(), + base_fee_per_gas: value.base_fee_per_gas, + withdrawals_root: value.withdrawals_root, + blob_gas: value.blob_gas_used.and_then(|gas_used| { + value.excess_blob_gas.map(|excess_gas| BlobGas { + gas_used, + excess_gas, + }) + }), + parent_beacon_block_root: value.parent_beacon_block_root, + }; + + let transactions = value + .transactions + .into_iter() + .map(TryInto::try_into) + .collect::, _>>() + .map_err(ConversionError::Transaction)?; + + let hash = value.hash.ok_or(ConversionError::MissingHash)?; + + Ok(Self { + header, + transactions, + ommer_hashes: value.uncles, + withdrawals: value.withdrawals, + hash, + rlp_size: value.size, + }) + } +} + +impl + From> for crate::rpc::block::Block +{ + fn from(value: BlockAndTotalDifficulty) -> Self { + let transactions = value + .block + .transactions() + .iter() + .map(|tx| *tx.transaction_hash()) + .collect(); + + let header = value.block.header(); + crate::rpc::block::Block { + hash: Some(*value.block.hash()), + parent_hash: header.parent_hash, + sha3_uncles: header.ommers_hash, + state_root: header.state_root, + transactions_root: header.transactions_root, + receipts_root: header.receipts_root, + number: Some(header.number), + gas_used: header.gas_used, + gas_limit: header.gas_limit, + extra_data: header.extra_data.clone(), + logs_bloom: header.logs_bloom, + timestamp: header.timestamp, + difficulty: header.difficulty, + total_difficulty: value.total_difficulty, + uncles: value.block.ommer_hashes().to_vec(), + transactions, + size: value.block.rlp_size(), + mix_hash: Some(header.mix_hash), + nonce: Some(header.nonce), + base_fee_per_gas: header.base_fee_per_gas, + miner: Some(header.beneficiary), + withdrawals: value + .block + .withdrawals() + .map(<[edr_eth::withdrawal::Withdrawal]>::to_vec), + withdrawals_root: header.withdrawals_root, + blob_gas_used: header.blob_gas.as_ref().map(|bg| bg.gas_used), + excess_blob_gas: header.blob_gas.as_ref().map(|bg| bg.excess_gas), + parent_beacon_block_root: header.parent_beacon_block_root, + } + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use edr_evm::RemoteBlock; + use edr_rpc_client::jsonrpc; + use edr_rpc_eth::client::EthRpcClient; + + use crate::{rpc::transaction::TransactionWithSignature, GenericChainSpec}; + + #[tokio::test(flavor = "current_thread")] + async fn test_allow_missing_nonce_or_mix_hash() { + // Taken from https://github.com/NomicFoundation/edr/issues/536 + const DATA: &str = r#"{ + "jsonrpc": "2.0", + "result": { + "author": "0xa3b079e4b54d2886ccd9cddc1335743bf1b2f0ad", + "difficulty": "0xfffffffffffffffffffffffffffffffe", + "extraData": "0x4e65746865726d696e64", + "gasLimit": "0x663be0", + "gasUsed": "0x0", + "hash": "0x12a6d4673bc9a55b5f77d897f32eb0ff15bca32d09d3dff8013e3ff684b0a84d", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0xa3b079e4b54d2886ccd9cddc1335743bf1b2f0ad", + "number": "0x1b000b1", + "parentHash": "0x26046ba285c7c87ff04783347a5b63cba77d884a11a50a632ea68ba2843b3064", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "signature": "0x012bb653a98ede38cd88f59fb09c67a8628012e3a4bdd6925a3dee547e0c4f470aa3118d03e4c81b715200aeb34b00f7c67a0a9cd687ff26c2dbc82964d4268a00", + "size": "0x238", + "stateRoot": "0x62cb23bc47439bc73eef6bf9fc0e54b1a40ebb3b0cbcfefd6b13cbc79a1095b6", + "step": 343268919, + "totalDifficulty": "0x1b000b0ffffffffffffffffffffffffe9dc2118", + "timestamp": "0x664d5713", + "transactions": [], + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "uncles": [] + }, + "id": 1 + }"#; + + type BlockResponsePayload = + ::RpcBlock; + + let response: jsonrpc::Response = serde_json::from_str(DATA).unwrap(); + let rpc_block = match response.data { + jsonrpc::ResponseData::Error { .. } => unreachable!("Payload above is a success"), + jsonrpc::ResponseData::Success { result } => result, + }; + + // Not using an actual client because we do not want to use a public + // node for less common, EVM-compatible chains that are not supported + // by reliable providers like Alchemy or Infura. + // Instead, we use a static response here. + let dummy_client = Arc::new( + EthRpcClient::::new("http://example.com", "".into(), None) + .unwrap(), + ); + let runtime = tokio::runtime::Handle::current(); + + RemoteBlock::new(rpc_block, dummy_client, runtime) + .expect("Conversion should accept a missing nonce or mix hash"); + } +} diff --git a/crates/edr_generic/src/rpc/receipt.rs b/crates/edr_generic/src/rpc/receipt.rs new file mode 100644 index 000000000..cb7d1d2fc --- /dev/null +++ b/crates/edr_generic/src/rpc/receipt.rs @@ -0,0 +1,130 @@ +use std::marker::PhantomData; + +use edr_eth::{log::FilterLog, SpecId}; +use edr_rpc_eth::RpcTypeFrom; +use serde::{Deserialize, Serialize}; + +use crate::eip2718::TypedEnvelope; + +#[derive(Debug, thiserror::Error)] +pub enum ConversionError { + #[error("Legacy transaction is missing state root or status")] + MissingStateRootOrStatus, + #[error("Missing status")] + MissingStatus, +} + +use edr_eth::{ + receipt::{ + execution::{Eip658, Legacy}, + Execution, Receipt, TransactionReceipt, + }, + transaction::TransactionType, +}; + +// We need to introduce a newtype for BlockReceipt again due to the orphan rule, +// even though we use our own TypedEnvelope. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct BlockReceipt(edr_rpc_eth::receipt::Block); + +impl TryFrom for crate::receipt::BlockReceipt>> { + type Error = ConversionError; + + fn try_from(value: BlockReceipt) -> Result { + let BlockReceipt(value) = value; + + // We explicitly treat unknown transaction types as post-EIP 155 legacy + // transactions + let transaction_type = value.transaction_type.map_or( + crate::transaction::Type::Legacy, + crate::transaction::Type::from, + ); + + let execution = if transaction_type == crate::transaction::Type::Legacy { + if let Some(status) = value.status { + Execution::Eip658(Eip658 { + status, + cumulative_gas_used: value.cumulative_gas_used, + logs_bloom: value.logs_bloom, + logs: value.logs, + }) + } else if let Some(state_root) = value.state_root { + Execution::Legacy(Legacy { + root: state_root, + cumulative_gas_used: value.cumulative_gas_used, + logs_bloom: value.logs_bloom, + logs: value.logs, + }) + } else { + return Err(ConversionError::MissingStateRootOrStatus); + } + } else { + Execution::Eip658(Eip658 { + status: value.status.ok_or(ConversionError::MissingStatus)?, + cumulative_gas_used: value.cumulative_gas_used, + logs_bloom: value.logs_bloom, + logs: value.logs, + }) + }; + + let enveloped = TypedEnvelope::new(execution, transaction_type); + + Ok(Self { + block_hash: value.block_hash, + block_number: value.block_number, + inner: TransactionReceipt { + inner: enveloped, + transaction_hash: value.transaction_hash, + transaction_index: value.transaction_index, + from: value.from, + to: value.to, + contract_address: value.contract_address, + gas_used: value.gas_used, + effective_gas_price: value.effective_gas_price, + phantom: PhantomData, + }, + }) + } +} + +impl RpcTypeFrom>>> + for BlockReceipt +{ + type Hardfork = SpecId; + + fn rpc_type_from( + value: &crate::receipt::BlockReceipt>>, + hardfork: Self::Hardfork, + ) -> Self { + let transaction_type = if hardfork >= SpecId::BERLIN { + Some(u8::from(value.inner.transaction_type())) + } else { + None + }; + + BlockReceipt(edr_rpc_eth::receipt::Block { + block_hash: value.block_hash, + block_number: value.block_number, + transaction_hash: value.inner.transaction_hash, + transaction_index: value.inner.transaction_index, + transaction_type, + from: value.inner.from, + to: value.inner.to, + cumulative_gas_used: value.inner.cumulative_gas_used(), + gas_used: value.inner.gas_used, + contract_address: value.inner.contract_address, + logs: value.inner.transaction_logs().to_vec(), + logs_bloom: *value.inner.logs_bloom(), + state_root: match value.inner.as_execution_receipt().data() { + Execution::Legacy(receipt) => Some(receipt.root), + Execution::Eip658(_) => None, + }, + status: match value.inner.as_execution_receipt().data() { + Execution::Legacy(_) => None, + Execution::Eip658(receipt) => Some(receipt.status), + }, + effective_gas_price: value.inner.effective_gas_price, + authorization_list: None, + }) + } +} diff --git a/crates/edr_generic/src/rpc/transaction.rs b/crates/edr_generic/src/rpc/transaction.rs new file mode 100644 index 000000000..e0548d1ae --- /dev/null +++ b/crates/edr_generic/src/rpc/transaction.rs @@ -0,0 +1,113 @@ +use edr_eth::{transaction::SignedTransaction as _, SpecId}; +use edr_evm::{ + block::transaction::{BlockDataForTransaction, TransactionAndBlock}, + transaction::remote::EthRpcTransaction, +}; +use edr_rpc_eth::RpcTypeFrom; +use serde::{Deserialize, Serialize}; + +use crate::GenericChainSpec; + +// We need to use a newtype here as `RpcTypeFrom` cannot be implemented here, +// in an external crate, even though `TransactionAndBlock` is generic over +// a type that we introduced. +// This originally works as the impl for `L1ChainSpec` lives already in the +// defining crate of `edr_evm::TransactionAndBlock`, which probably shouldn't +// as far as defining spec externally is concerned. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct TransactionWithSignature(edr_rpc_eth::TransactionWithSignature); + +impl EthRpcTransaction for TransactionWithSignature { + fn block_hash(&self) -> Option<&edr_eth::B256> { + self.0.block_hash() + } +} + +impl From for TransactionWithSignature { + fn from(value: edr_rpc_eth::TransactionWithSignature) -> Self { + Self(value) + } +} + +impl RpcTypeFrom> for TransactionWithSignature { + type Hardfork = SpecId; + + fn rpc_type_from( + value: &TransactionAndBlock, + hardfork: Self::Hardfork, + ) -> Self { + let (header, transaction_index) = value + .block_data + .as_ref() + .map( + |BlockDataForTransaction { + block, + transaction_index, + }| (block.header(), *transaction_index), + ) + .unzip(); + + let transaction = edr_rpc_eth::Transaction::new( + &value.transaction, + header, + transaction_index, + value.is_pending, + hardfork, + ); + let signature = value.transaction.0.signature(); + + edr_rpc_eth::TransactionWithSignature::new( + transaction, + signature.r(), + signature.s(), + signature.v(), + signature.y_parity(), + ) + .into() + } +} + +pub use edr_rpc_eth::TransactionConversionError as ConversionError; + +impl TryFrom for crate::transaction::SignedWithFallbackToPostEip155 { + type Error = ConversionError; + + fn try_from(value: TransactionWithSignature) -> Result { + use edr_eth::transaction::{self, Signed}; + + enum TxType { + Known(transaction::Type), + Unknown, + } + + let TransactionWithSignature(value) = value; + + let tx_type = match value.transaction_type.map(transaction::Type::try_from) { + None => TxType::Known(transaction::Type::Legacy), + Some(Ok(r#type)) => TxType::Known(r#type), + Some(Err(r#type)) => { + log::warn!("Unsupported transaction type: {type}. Reverting to post-EIP 155 legacy transaction"); + + TxType::Unknown + } + }; + + let transaction = match tx_type { + // We explicitly treat unknown transaction types as post-EIP 155 legacy transactions + TxType::Unknown => Signed::PostEip155Legacy(value.into()), + + TxType::Known(transaction::Type::Legacy) => { + if value.is_legacy() { + Signed::PreEip155Legacy(value.into()) + } else { + Signed::PostEip155Legacy(value.into()) + } + } + TxType::Known(transaction::Type::Eip2930) => Signed::Eip2930(value.try_into()?), + TxType::Known(transaction::Type::Eip1559) => Signed::Eip1559(value.try_into()?), + TxType::Known(transaction::Type::Eip4844) => Signed::Eip4844(value.try_into()?), + }; + + Ok(Self(transaction)) + } +} diff --git a/crates/edr_generic/src/spec.rs b/crates/edr_generic/src/spec.rs new file mode 100644 index 000000000..8bb739aed --- /dev/null +++ b/crates/edr_generic/src/spec.rs @@ -0,0 +1,103 @@ +use edr_eth::{ + block::{Header, PartialHeader}, + chain_spec::{EthHeaderConstants, L1ChainSpec}, + eips::eip1559::{BaseFeeParams, ConstantBaseFeeParams}, + result::HaltReason, + SpecId, +}; +use edr_evm::{ + chain_spec::{BlockEnvConstructor, ChainSpec}, + hardfork::Activations, + transaction::TransactionError, +}; +use edr_provider::{time::TimeSinceEpoch, ProviderSpec, TransactionFailureReason}; +use revm_primitives::{BlockEnv, EvmWiring, InvalidTransaction, TransactionValidation}; + +use crate::GenericChainSpec; + +impl EvmWiring for GenericChainSpec { + type Block = revm_primitives::BlockEnv; + + type Hardfork = revm_primitives::SpecId; + + type HaltReason = revm_primitives::HaltReason; + + type Transaction = crate::transaction::SignedWithFallbackToPostEip155; +} + +impl revm::EvmWiring for GenericChainSpec { + type Context = (); + + fn handler<'evm, EXT, DB>(hardfork: Self::Hardfork) -> revm::EvmHandler<'evm, Self, EXT, DB> + where + DB: revm::Database, + { + revm::EvmHandler::mainnet_with_spec(hardfork) + } +} + +impl EthHeaderConstants for GenericChainSpec { + const BASE_FEE_PARAMS: BaseFeeParams = + BaseFeeParams::Constant(ConstantBaseFeeParams::ethereum()); + + const MIN_ETHASH_DIFFICULTY: u64 = 131072; +} + +impl BlockEnvConstructor for BlockEnv { + fn new_block_env(header: &Header, hardfork: SpecId) -> Self { + BlockEnvConstructor::::new_block_env(header, hardfork) + } +} + +impl BlockEnvConstructor for BlockEnv { + fn new_block_env(header: &PartialHeader, hardfork: SpecId) -> Self { + BlockEnvConstructor::::new_block_env(header, hardfork) + } +} + +impl ChainSpec for GenericChainSpec { + type ReceiptBuilder = crate::receipt::execution::Builder; + type RpcBlockConversionError = crate::rpc::block::ConversionError; + type RpcReceiptConversionError = crate::rpc::receipt::ConversionError; + type RpcTransactionConversionError = crate::rpc::transaction::ConversionError; + + fn cast_transaction_error( + error: ::ValidationError, + ) -> TransactionError { + // Can't use L1ChainSpec impl here as the TransactionError is generic + // over the specific chain spec rather than just the validation error. + // Instead, we copy the impl here. + match error { + InvalidTransaction::LackOfFundForMaxFee { fee, balance } => { + TransactionError::LackOfFundForMaxFee { fee, balance } + } + remainder => TransactionError::InvalidTransaction(remainder), + } + } + + fn chain_hardfork_activations(chain_id: u64) -> Option<&'static Activations> { + L1ChainSpec::chain_hardfork_activations(chain_id).map(Activations::as_chain_spec) + } + + fn chain_name(chain_id: u64) -> Option<&'static str> { + L1ChainSpec::chain_name(chain_id) + } +} + +impl ProviderSpec for GenericChainSpec { + type PooledTransaction = edr_eth::transaction::pooled::PooledTransaction; + type TransactionRequest = crate::transaction::Request; + + fn cast_halt_reason(reason: Self::HaltReason) -> TransactionFailureReason { + match reason { + HaltReason::CreateContractSizeLimit => { + TransactionFailureReason::CreateContractSizeLimit + } + HaltReason::OpcodeNotFound | HaltReason::InvalidFEOpcode => { + TransactionFailureReason::OpcodeNotFound + } + HaltReason::OutOfGas(error) => TransactionFailureReason::OutOfGas(error), + remainder => TransactionFailureReason::Inner(remainder), + } + } +} diff --git a/crates/edr_generic/src/transaction.rs b/crates/edr_generic/src/transaction.rs new file mode 100644 index 000000000..1fbcb4914 --- /dev/null +++ b/crates/edr_generic/src/transaction.rs @@ -0,0 +1,4 @@ +mod request; +mod signed; +pub use request::Request; +pub use signed::{SignedWithFallbackToPostEip155, Type}; diff --git a/crates/edr_generic/src/transaction/request.rs b/crates/edr_generic/src/transaction/request.rs new file mode 100644 index 000000000..46f5c9593 --- /dev/null +++ b/crates/edr_generic/src/transaction/request.rs @@ -0,0 +1,272 @@ +use edr_eth::{ + signature::{SecretKey, SignatureError}, + transaction::signed::{FakeSign, Sign}, + Address, Bytes, SpecId, U256, +}; +use edr_evm::blockchain::BlockchainError; +use edr_provider::{ + requests::validation::{validate_call_request, validate_send_transaction_request}, + spec::{CallContext, FromRpcType, TransactionContext}, + time::TimeSinceEpoch, + ProviderData, ProviderError, +}; +use edr_rpc_eth::{CallRequest, TransactionRequest}; + +use crate::{transaction::SignedWithFallbackToPostEip155, GenericChainSpec}; + +/// Container type for various Ethereum transaction requests. +// NOTE: This is a newtype only because the default FromRpcType implementation +// provides an error of ProviderError specifically. Despite us +// wanting the same logic, we need to use our own type and copy the +// implementation. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Request(edr_eth::transaction::Request); + +impl From for Request { + fn from(value: edr_eth::transaction::Request) -> Self { + Self(value) + } +} + +impl FakeSign for Request { + type Signed = SignedWithFallbackToPostEip155; + + fn fake_sign(self, sender: edr_eth::Address) -> SignedWithFallbackToPostEip155 { + ::fake_sign(self.0, sender).into() + } +} + +impl Sign for Request { + type Signed = SignedWithFallbackToPostEip155; + + unsafe fn sign_for_sender_unchecked( + self, + secret_key: &SecretKey, + caller: Address, + ) -> Result { + ::sign_for_sender_unchecked( + self.0, secret_key, caller, + ) + .map(Into::into) + } +} + +impl FromRpcType for Request { + type Context<'context> = CallContext<'context, GenericChainSpec, TimerT>; + + type Error = ProviderError; + + fn from_rpc_type( + value: CallRequest, + context: Self::Context<'_>, + ) -> Result> { + let CallContext { + data, + block_spec, + state_overrides, + default_gas_price_fn, + max_fees_fn, + } = context; + + validate_call_request(data.evm_spec_id(), &value, block_spec)?; + + let CallRequest { + from, + to, + gas, + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + value, + data: input, + access_list, + .. + } = value; + + let chain_id = data.chain_id_at_block_spec(block_spec)?; + let sender = from.unwrap_or_else(|| data.default_caller()); + let gas_limit = gas.unwrap_or_else(|| data.block_gas_limit()); + let input = input.map_or(Bytes::new(), Bytes::from); + let nonce = data.nonce(&sender, Some(block_spec), state_overrides)?; + let value = value.unwrap_or(U256::ZERO); + + let evm_spec_id = data.evm_spec_id(); + let request = if evm_spec_id < SpecId::LONDON || gas_price.is_some() { + let gas_price = gas_price.map_or_else(|| default_gas_price_fn(data), Ok)?; + match access_list { + Some(access_list) if evm_spec_id >= SpecId::BERLIN => { + edr_eth::transaction::Request::Eip2930(edr_eth::transaction::request::Eip2930 { + nonce, + gas_price, + gas_limit, + value, + input, + kind: to.into(), + chain_id, + access_list, + }) + .into() + } + _ => edr_eth::transaction::Request::Eip155(edr_eth::transaction::request::Eip155 { + nonce, + gas_price, + gas_limit, + kind: to.into(), + value, + input, + chain_id, + }) + .into(), + } + } else { + let (max_fee_per_gas, max_priority_fee_per_gas) = + max_fees_fn(data, block_spec, max_fee_per_gas, max_priority_fee_per_gas)?; + + edr_eth::transaction::Request::Eip1559(edr_eth::transaction::request::Eip1559 { + chain_id, + nonce, + max_fee_per_gas, + max_priority_fee_per_gas, + gas_limit, + kind: to.into(), + value, + input, + access_list: access_list.unwrap_or_default(), + }) + .into() + }; + + Ok(request) + } +} + +impl FromRpcType for Request { + type Context<'context> = TransactionContext<'context, GenericChainSpec, TimerT>; + + type Error = ProviderError; + + fn from_rpc_type( + value: TransactionRequest, + context: Self::Context<'_>, + ) -> Result> { + const DEFAULT_MAX_PRIORITY_FEE_PER_GAS: u64 = 1_000_000_000; + + /// # Panics + /// + /// Panics if `data.evm_spec_id()` is less than `SpecId::LONDON`. + fn calculate_max_fee_per_gas( + data: &ProviderData, + max_priority_fee_per_gas: U256, + ) -> Result> { + let base_fee_per_gas = data + .next_block_base_fee_per_gas()? + .expect("We already validated that the block is post-London."); + Ok(U256::from(2) * base_fee_per_gas + max_priority_fee_per_gas) + } + + let TransactionContext { data } = context; + + validate_send_transaction_request(data, &value)?; + + let TransactionRequest { + from, + to, + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + gas, + value, + data: input, + nonce, + chain_id, + access_list, + // We ignore the transaction type + transaction_type: _transaction_type, + blobs: _blobs, + blob_hashes: _blob_hashes, + } = value; + + let chain_id = chain_id.unwrap_or_else(|| data.chain_id()); + let gas_limit = gas.unwrap_or_else(|| data.block_gas_limit()); + let input = input.map_or(Bytes::new(), Into::into); + let nonce = nonce.map_or_else(|| data.account_next_nonce(&from), Ok)?; + let value = value.unwrap_or(U256::ZERO); + + let request = match ( + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + access_list, + ) { + (gas_price, max_fee_per_gas, max_priority_fee_per_gas, access_list) + if data.evm_spec_id() >= SpecId::LONDON + && (gas_price.is_none() + || max_fee_per_gas.is_some() + || max_priority_fee_per_gas.is_some()) => + { + let (max_fee_per_gas, max_priority_fee_per_gas) = + match (max_fee_per_gas, max_priority_fee_per_gas) { + (Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => { + (max_fee_per_gas, max_priority_fee_per_gas) + } + (Some(max_fee_per_gas), None) => ( + max_fee_per_gas, + max_fee_per_gas.min(U256::from(DEFAULT_MAX_PRIORITY_FEE_PER_GAS)), + ), + (None, Some(max_priority_fee_per_gas)) => { + let max_fee_per_gas = + calculate_max_fee_per_gas(data, max_priority_fee_per_gas)?; + (max_fee_per_gas, max_priority_fee_per_gas) + } + (None, None) => { + let max_priority_fee_per_gas = + U256::from(DEFAULT_MAX_PRIORITY_FEE_PER_GAS); + let max_fee_per_gas = + calculate_max_fee_per_gas(data, max_priority_fee_per_gas)?; + (max_fee_per_gas, max_priority_fee_per_gas) + } + }; + + edr_eth::transaction::Request::Eip1559(edr_eth::transaction::request::Eip1559 { + nonce, + max_priority_fee_per_gas, + max_fee_per_gas, + gas_limit, + value, + input, + kind: to.into(), + chain_id, + access_list: access_list.unwrap_or_default(), + }) + .into() + } + (gas_price, _, _, Some(access_list)) => { + edr_eth::transaction::Request::Eip2930(edr_eth::transaction::request::Eip2930 { + nonce, + gas_price: gas_price.map_or_else(|| data.next_gas_price(), Ok)?, + gas_limit, + value, + input, + kind: to.into(), + chain_id, + access_list, + }) + .into() + } + (gas_price, _, _, _) => { + edr_eth::transaction::Request::Eip155(edr_eth::transaction::request::Eip155 { + nonce, + gas_price: gas_price.map_or_else(|| data.next_gas_price(), Ok)?, + gas_limit, + value, + input, + kind: to.into(), + chain_id, + }) + .into() + } + }; + + Ok(request) + } +} diff --git a/crates/edr_generic/src/transaction/signed.rs b/crates/edr_generic/src/transaction/signed.rs new file mode 100644 index 000000000..ac2fbff99 --- /dev/null +++ b/crates/edr_generic/src/transaction/signed.rs @@ -0,0 +1,208 @@ +use edr_eth::transaction::{self, ExecutableTransaction, TransactionMut, TransactionType}; +use revm_primitives::{ + AccessListItem, Address, AuthorizationList, Bytes, TransactionValidation, TxKind, B256, U256, +}; + +/// The type of transaction. +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)] +pub enum Type { + #[default] + /// Legacy transaction + Legacy = transaction::signed::Legacy::TYPE, + /// EIP-2930 transaction + Eip2930 = transaction::signed::Eip2930::TYPE, + /// EIP-1559 transaction + Eip1559 = transaction::signed::Eip1559::TYPE, + /// EIP-4844 transaction + Eip4844 = transaction::signed::Eip4844::TYPE, + /// Unrecognized transaction type. + Unrecognized(u8), +} + +impl From for u8 { + fn from(t: Type) -> u8 { + match t { + Type::Legacy => transaction::signed::Legacy::TYPE, + Type::Eip2930 => transaction::signed::Eip2930::TYPE, + Type::Eip1559 => transaction::signed::Eip1559::TYPE, + Type::Eip4844 => transaction::signed::Eip4844::TYPE, + Type::Unrecognized(t) => t, + } + } +} + +impl From for Type { + fn from(t: u8) -> Self { + match t { + transaction::signed::Legacy::TYPE => Self::Legacy, + transaction::signed::Eip2930::TYPE => Self::Eip2930, + transaction::signed::Eip1559::TYPE => Self::Eip1559, + transaction::signed::Eip4844::TYPE => Self::Eip4844, + t => Self::Unrecognized(t), + } + } +} + +impl From for Type { + fn from(t: edr_eth::transaction::Type) -> Self { + match t { + edr_eth::transaction::Type::Legacy => Self::Legacy, + edr_eth::transaction::Type::Eip2930 => Self::Eip2930, + edr_eth::transaction::Type::Eip1559 => Self::Eip1559, + edr_eth::transaction::Type::Eip4844 => Self::Eip4844, + } + } +} + +impl transaction::IsEip4844 for Type { + fn is_eip4844(&self) -> bool { + matches!(self, Type::Eip4844) + } +} + +impl transaction::IsLegacy for Type { + fn is_legacy(&self) -> bool { + matches!(self, Type::Legacy) + } +} + +/// A regular [`Signed`](edr_eth::transaction::Signed) transaction that falls +/// back to post-EIP 155 legacy transactions for unknown transaction types +/// when converting from an RPC request. +// NOTE: This is a newtype only because we need to use a different +// `TryFrom` impl that treats unknown transaction +// types different. +#[repr(transparent)] +#[derive(Clone, Debug, Default, PartialEq, Eq, alloy_rlp::RlpEncodable)] +pub struct SignedWithFallbackToPostEip155(pub transaction::Signed); + +impl From for SignedWithFallbackToPostEip155 { + fn from(value: transaction::Signed) -> Self { + Self(value) + } +} + +impl From for SignedWithFallbackToPostEip155 { + fn from(value: edr_eth::transaction::pooled::PooledTransaction) -> Self { + edr_eth::transaction::Signed::from(value).into() + } +} + +impl TransactionValidation for SignedWithFallbackToPostEip155 { + type ValidationError = ::ValidationError; +} + +impl revm_primitives::Transaction for SignedWithFallbackToPostEip155 { + fn caller(&self) -> &Address { + self.0.caller() + } + + fn gas_limit(&self) -> u64 { + self.0.gas_limit() + } + + fn gas_price(&self) -> &U256 { + self.0.gas_price() + } + + fn kind(&self) -> TxKind { + self.0.kind() + } + + fn value(&self) -> &U256 { + self.0.value() + } + + fn data(&self) -> &Bytes { + self.0.data() + } + + fn nonce(&self) -> u64 { + self.0.nonce() + } + + fn chain_id(&self) -> Option { + self.0.chain_id() + } + + fn access_list(&self) -> &[AccessListItem] { + self.0.access_list() + } + + fn max_priority_fee_per_gas(&self) -> Option<&U256> { + self.0.max_priority_fee_per_gas() + } + + fn blob_hashes(&self) -> &[B256] { + self.0.blob_hashes() + } + + fn max_fee_per_blob_gas(&self) -> Option<&U256> { + self.0.max_fee_per_blob_gas() + } + + fn authorization_list(&self) -> Option<&AuthorizationList> { + self.0.authorization_list() + } +} + +impl TransactionMut for SignedWithFallbackToPostEip155 { + fn set_gas_limit(&mut self, gas_limit: u64) { + self.0.set_gas_limit(gas_limit); + } +} + +impl ExecutableTransaction for SignedWithFallbackToPostEip155 { + fn effective_gas_price(&self, block_base_fee: U256) -> Option { + self.0.effective_gas_price(block_base_fee) + } + + fn max_fee_per_gas(&self) -> Option<&U256> { + self.0.max_fee_per_gas() + } + + fn rlp_encoding(&self) -> &Bytes { + self.0.rlp_encoding() + } + + fn total_blob_gas(&self) -> Option { + self.0.total_blob_gas() + } + + fn transaction_hash(&self) -> &B256 { + self.0.transaction_hash() + } +} + +impl TransactionType for SignedWithFallbackToPostEip155 { + type Type = crate::transaction::Type; + + fn transaction_type(&self) -> Self::Type { + self.0.transaction_type().into() + } +} + +impl transaction::HasAccessList for SignedWithFallbackToPostEip155 { + fn has_access_list(&self) -> bool { + self.0.has_access_list() + } +} + +impl transaction::IsEip155 for SignedWithFallbackToPostEip155 { + fn is_eip155(&self) -> bool { + self.0.is_eip155() + } +} + +impl transaction::IsEip4844 for SignedWithFallbackToPostEip155 { + fn is_eip4844(&self) -> bool { + self.0.is_eip4844() + } +} + +impl transaction::IsLegacy for SignedWithFallbackToPostEip155 { + fn is_legacy(&self) -> bool { + self.0.is_legacy() + } +} diff --git a/crates/edr_generic/tests/unknown_transaction_type.rs b/crates/edr_generic/tests/unknown_transaction_type.rs new file mode 100644 index 000000000..fbb385951 --- /dev/null +++ b/crates/edr_generic/tests/unknown_transaction_type.rs @@ -0,0 +1,50 @@ +#![cfg(feature = "test-remote")] + +use std::sync::Arc; + +use edr_defaults::CACHE_DIR; +use edr_eth::{HashMap, SpecId}; +use edr_evm::{ + blockchain::{Blockchain, ForkedBlockchain}, + state::IrregularState, + RandomHashGenerator, +}; +use edr_generic::GenericChainSpec; +use edr_rpc_eth::client::EthRpcClient; +use edr_test_utils::env::get_alchemy_url; +use parking_lot::Mutex; +use tokio::runtime; + +#[tokio::test(flavor = "multi_thread")] +async fn unknown_transaction_types() -> anyhow::Result<()> { + const BLOCK_NUMBER_WITH_TRANSACTIONS: u64 = 117_156_000; + + // Make sure that we do not error out when encountering unknown Ethereum + // transaction types (e.g. found in Optimism), as we want to fallback to + // legacy transactions for the for the generic (aka fallback) chain spec. + let url = get_alchemy_url().replace("eth-", "opt-"); + let rpc_client = EthRpcClient::::new(&url, CACHE_DIR.into(), None)?; + let mut irregular_state = IrregularState::default(); + let state_root_generator = Arc::new(Mutex::new(RandomHashGenerator::with_seed("test"))); + let hardfork_activation_overrides = HashMap::new(); + + let blockchain = ForkedBlockchain::new( + runtime::Handle::current(), + None, + SpecId::LATEST, + Arc::new(rpc_client), + None, + &mut irregular_state, + state_root_generator, + &hardfork_activation_overrides, + ) + .await?; + + let block_with_transactions = blockchain + .block_by_number(BLOCK_NUMBER_WITH_TRANSACTIONS)? + .expect("Block must exist"); + + let _receipts = block_with_transactions.transaction_receipts()?; + + Ok(()) +} diff --git a/crates/edr_napi/Cargo.toml b/crates/edr_napi/Cargo.toml index 05ed795c4..0b03fe552 100644 --- a/crates/edr_napi/Cargo.toml +++ b/crates/edr_napi/Cargo.toml @@ -11,6 +11,7 @@ ansi_term = { version = "0.12.1", default-features = false } derive-where = { version = "1.2.7", default-features = false } edr_eth = { path = "../edr_eth" } edr_evm = { path = "../edr_evm" } +edr_generic = { path = "../edr_generic" } edr_napi_core = { path = "../edr_napi_core" } edr_provider = { path = "../edr_provider" } edr_rpc_client = { path = "../edr_rpc_client" } diff --git a/crates/edr_napi/index.d.ts b/crates/edr_napi/index.d.ts index 05b374863..064a09567 100644 --- a/crates/edr_napi/index.d.ts +++ b/crates/edr_napi/index.d.ts @@ -195,6 +195,8 @@ export interface DebugTraceLogItem { /** Map of all stored values with keys and values encoded as hex strings. */ storage?: Record } +export const GENERIC_CHAIN_TYPE: string +export function genericChainProviderFactory(): ProviderFactory export const L1_CHAIN_TYPE: string export function l1ProviderFactory(): ProviderFactory /** Identifier for the Ethereum spec. */ diff --git a/crates/edr_napi/index.js b/crates/edr_napi/index.js index 99f33eb76..01f5c1d03 100644 --- a/crates/edr_napi/index.js +++ b/crates/edr_napi/index.js @@ -310,10 +310,12 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { MineOrdering, EdrContext, L1_CHAIN_TYPE, l1ProviderFactory, SpecId, FRONTIER, FRONTIER_THAWING, HOMESTEAD, DAO_FORK, TANGERINE, SPURIOUS_DRAGON, BYZANTIUM, CONSTANTINOPLE, PETERSBURG, ISTANBUL, MUIR_GLACIER, BERLIN, LONDON, ARROW_GLACIER, GRAY_GLACIER, MERGE, SHANGHAI, CANCUN, PRAGUE, PRAGUE_EOF, LATEST, ProviderFactory, Provider, SuccessReason, ExceptionalHalt, Response, RawTrace } = nativeBinding +const { MineOrdering, EdrContext, GENERIC_CHAIN_TYPE, genericChainProviderFactory, L1_CHAIN_TYPE, l1ProviderFactory, SpecId, FRONTIER, FRONTIER_THAWING, HOMESTEAD, DAO_FORK, TANGERINE, SPURIOUS_DRAGON, BYZANTIUM, CONSTANTINOPLE, PETERSBURG, ISTANBUL, MUIR_GLACIER, BERLIN, LONDON, ARROW_GLACIER, GRAY_GLACIER, MERGE, SHANGHAI, CANCUN, PRAGUE, PRAGUE_EOF, LATEST, ProviderFactory, Provider, SuccessReason, ExceptionalHalt, Response, RawTrace } = nativeBinding module.exports.MineOrdering = MineOrdering module.exports.EdrContext = EdrContext +module.exports.GENERIC_CHAIN_TYPE = GENERIC_CHAIN_TYPE +module.exports.genericChainProviderFactory = genericChainProviderFactory module.exports.L1_CHAIN_TYPE = L1_CHAIN_TYPE module.exports.l1ProviderFactory = l1ProviderFactory module.exports.SpecId = SpecId diff --git a/crates/edr_napi/src/generic_chain.rs b/crates/edr_napi/src/generic_chain.rs new file mode 100644 index 000000000..2eb0c1350 --- /dev/null +++ b/crates/edr_napi/src/generic_chain.rs @@ -0,0 +1,46 @@ +use std::sync::Arc; + +use edr_generic::GenericChainSpec; +use napi_derive::napi; + +use crate::{ + logger::{Logger, LoggerConfig}, + provider::{self, factory::SyncProviderFactory, ProviderBuilder, ProviderFactory}, + spec::SyncNapiSpec, + subscription::{SubscriptionCallback, SubscriptionConfig}, +}; + +pub struct GenericChainProviderFactory; + +impl SyncProviderFactory for GenericChainProviderFactory { + fn create_provider_builder( + &self, + env: &napi::Env, + provider_config: edr_napi_core::provider::Config, + logger_config: LoggerConfig, + subscription_config: SubscriptionConfig, + ) -> napi::Result> { + let logger = Logger::::new(env, logger_config)?; + + let provider_config = + edr_provider::ProviderConfig::::from(provider_config); + + let subscription_callback = + SubscriptionCallback::new(env, subscription_config.subscription_callback)?; + + Ok(Box::new(ProviderBuilder::new( + logger, + provider_config, + subscription_callback, + ))) + } +} + +#[napi] +pub const GENERIC_CHAIN_TYPE: &str = GenericChainSpec::CHAIN_TYPE; + +#[napi] +pub fn generic_chain_provider_factory() -> ProviderFactory { + let factory: Arc = Arc::new(GenericChainProviderFactory); + factory.into() +} diff --git a/crates/edr_napi/src/lib.rs b/crates/edr_napi/src/lib.rs index 97b15a873..d460cc274 100644 --- a/crates/edr_napi/src/lib.rs +++ b/crates/edr_napi/src/lib.rs @@ -16,6 +16,9 @@ pub mod config; /// Types related to an EDR N-API context. pub mod context; mod debug_trace; +/// Types for the generic L1 Ethereum implementation. +pub mod generic_chain; + /// Types for L1 Ethereum implementation. pub mod l1; /// Types for EVM execution logs. diff --git a/crates/edr_napi/src/result.rs b/crates/edr_napi/src/result.rs index 4a1f2948e..4d55fd764 100644 --- a/crates/edr_napi/src/result.rs +++ b/crates/edr_napi/src/result.rs @@ -1,5 +1,5 @@ -use edr_eth::chain_spec::L1ChainSpec; use edr_evm::trace::AfterMessage; +use edr_generic::GenericChainSpec; use napi::{ bindgen_prelude::{BigInt, Buffer, Either3}, Either, Env, JsBuffer, JsBufferValue, @@ -192,7 +192,7 @@ pub struct ExecutionResult { } impl ExecutionResult { - pub fn new(env: &Env, message: &AfterMessage) -> napi::Result { + pub fn new(env: &Env, message: &AfterMessage) -> napi::Result { let AfterMessage { execution_result, contract_address, diff --git a/crates/edr_napi/src/spec.rs b/crates/edr_napi/src/spec.rs index 9e9fba0d2..f537470e5 100644 --- a/crates/edr_napi/src/spec.rs +++ b/crates/edr_napi/src/spec.rs @@ -3,6 +3,7 @@ use edr_eth::{ result::InvalidTransaction, transaction::{IsEip155, IsEip4844, TransactionMut, TransactionType, TransactionValidation}, }; +use edr_generic::GenericChainSpec; use edr_provider::{time::CurrentTime, ProviderError, ResponseWithTraces, SyncProviderSpec}; use edr_rpc_client::jsonrpc; use napi::{Either, Status}; @@ -85,6 +86,39 @@ pub trait SyncNapiSpec: impl SyncNapiSpec for L1ChainSpec { const CHAIN_TYPE: &'static str = "L1"; + fn cast_response( + response: Result, ProviderError>, + ) -> napi::Result { + let response = jsonrpc::ResponseData::from(response.map(|response| response.result)); + + serde_json::to_string(&response) + .and_then(|json| { + // We experimentally determined that 500_000_000 was the maximum string length + // that can be returned without causing the error: + // + // > Failed to convert rust `String` into napi `string` + // + // To be safe, we're limiting string lengths to half of that. + const MAX_STRING_LENGTH: usize = 250_000_000; + + if json.len() <= MAX_STRING_LENGTH { + Ok(Either::A(json)) + } else { + serde_json::to_value(response).map(Either::B) + } + }) + .map_err(|error| napi::Error::new(Status::GenericFailure, error.to_string())) + .map(|data| Response { + solidity_trace: None, + data, + traces: Vec::new(), + }) + } +} + +impl SyncNapiSpec for GenericChainSpec { + const CHAIN_TYPE: &'static str = "generic"; + fn cast_response( mut response: Result, ProviderError>, ) -> napi::Result { diff --git a/crates/edr_napi/src/trace.rs b/crates/edr_napi/src/trace.rs index 2a21737bb..dfd4333de 100644 --- a/crates/edr_napi/src/trace.rs +++ b/crates/edr_napi/src/trace.rs @@ -1,7 +1,7 @@ use std::sync::Arc; -use edr_eth::chain_spec::L1ChainSpec; use edr_evm::{interpreter::OpCode, trace::BeforeMessage}; +use edr_generic::GenericChainSpec; use napi::{ bindgen_prelude::{BigInt, Buffer, Either3}, Env, JsBuffer, JsBufferValue, @@ -153,11 +153,11 @@ pub struct TracingMessageResult { #[napi] #[derive(Clone)] pub struct RawTrace { - inner: Arc>, + inner: Arc>, } -impl From> for RawTrace { - fn from(value: edr_evm::trace::Trace) -> Self { +impl From> for RawTrace { + fn from(value: edr_evm::trace::Trace) -> Self { Self { inner: Arc::new(value), } diff --git a/crates/edr_napi/test/issues.ts b/crates/edr_napi/test/issues.ts index 0e425113f..bfc30f5c8 100644 --- a/crates/edr_napi/test/issues.ts +++ b/crates/edr_napi/test/issues.ts @@ -1,18 +1,25 @@ -import chai, { assert } from "chai"; import { JsonStreamStringify } from "json-stream-stringify"; import { ContractAndFunctionName, EdrContext, + GENERIC_CHAIN_TYPE, + genericChainProviderFactory, MineOrdering, - Provider, - SpecId, SubscriptionEvent, } from ".."; import { ALCHEMY_URL, isCI } from "./helpers"; describe("Provider", () => { const context = new EdrContext(); + + before(async () => { + await context.registerProviderFactory( + GENERIC_CHAIN_TYPE, + genericChainProviderFactory(), + ); + }); + const providerConfig = { allowBlocksWithSameTimestamp: false, allowUnlimitedContractSize: true, @@ -74,7 +81,7 @@ describe("Provider", () => { this.timeout(240_000); const provider = await context.createProvider( - "L1", + GENERIC_CHAIN_TYPE, { fork: { jsonRpcUrl: ALCHEMY_URL, diff --git a/crates/edr_napi/test/provider.ts b/crates/edr_napi/test/provider.ts index 8271f818d..413234c87 100644 --- a/crates/edr_napi/test/provider.ts +++ b/crates/edr_napi/test/provider.ts @@ -4,8 +4,8 @@ import chaiAsPromised from "chai-as-promised"; import { ContractAndFunctionName, EdrContext, - L1_CHAIN_TYPE, - l1ProviderFactory, + GENERIC_CHAIN_TYPE, + genericChainProviderFactory, MineOrdering, SubscriptionEvent, } from ".."; @@ -17,7 +17,10 @@ describe("Provider", () => { const context = new EdrContext(); before(async () => { - await context.registerProviderFactory(L1_CHAIN_TYPE, l1ProviderFactory()); + await context.registerProviderFactory( + GENERIC_CHAIN_TYPE, + genericChainProviderFactory(), + ); }); const providerConfig = { @@ -74,7 +77,7 @@ describe("Provider", () => { it("initialize local", async function () { const provider = context.createProvider( - L1_CHAIN_TYPE, + GENERIC_CHAIN_TYPE, providerConfig, loggerConfig, { @@ -91,7 +94,7 @@ describe("Provider", () => { } const provider = context.createProvider( - L1_CHAIN_TYPE, + GENERIC_CHAIN_TYPE, { fork: { jsonRpcUrl: ALCHEMY_URL, @@ -110,7 +113,7 @@ describe("Provider", () => { describe("verbose mode", function () { it("should only include the top of the stack by default", async function () { const provider = await context.createProvider( - L1_CHAIN_TYPE, + GENERIC_CHAIN_TYPE, providerConfig, loggerConfig, @@ -153,7 +156,7 @@ describe("Provider", () => { it("should only include the whole stack if verbose mode is enabled", async function () { const provider = await context.createProvider( - L1_CHAIN_TYPE, + GENERIC_CHAIN_TYPE, providerConfig, loggerConfig, { @@ -181,8 +184,6 @@ describe("Provider", () => { }), ); - console.log(responseObject); - const rawTraces = responseObject.traces; assert.lengthOf(rawTraces, 1); @@ -202,7 +203,7 @@ describe("Provider", () => { it("should not include memory by default", async function () { const provider = await context.createProvider( - L1_CHAIN_TYPE, + GENERIC_CHAIN_TYPE, providerConfig, loggerConfig, { @@ -243,7 +244,7 @@ describe("Provider", () => { it("should include memory if verbose mode is enabled", async function () { const provider = await context.createProvider( - L1_CHAIN_TYPE, + GENERIC_CHAIN_TYPE, providerConfig, loggerConfig, { @@ -291,7 +292,7 @@ describe("Provider", () => { it("should include isStaticCall flag in tracing messages", async function () { const provider = await context.createProvider( - L1_CHAIN_TYPE, + GENERIC_CHAIN_TYPE, providerConfig, loggerConfig, @@ -333,7 +334,7 @@ describe("Provider", () => { it("should have tracing information when debug_traceTransaction is used", async function () { const provider = await context.createProvider( - L1_CHAIN_TYPE, + GENERIC_CHAIN_TYPE, providerConfig, loggerConfig, @@ -388,7 +389,7 @@ describe("Provider", () => { it("should have tracing information when debug_traceCall is used", async function () { const provider = await context.createProvider( - L1_CHAIN_TYPE, + GENERIC_CHAIN_TYPE, providerConfig, loggerConfig, diff --git a/crates/edr_provider/src/requests/eth/transactions.rs b/crates/edr_provider/src/requests/eth/transactions.rs index e5f71b8c7..c75f2fde6 100644 --- a/crates/edr_provider/src/requests/eth/transactions.rs +++ b/crates/edr_provider/src/requests/eth/transactions.rs @@ -6,7 +6,7 @@ use edr_eth::{ rlp::Decodable, transaction::{ request::TransactionRequestAndSender, IsEip155, IsEip4844, Transaction as _, - TransactionType, TransactionValidation, + TransactionType, TransactionValidation, INVALID_TX_TYPE_ERROR_MESSAGE, }, Bytes, PreEip1898BlockSpec, B256, U256, }; @@ -211,7 +211,7 @@ pub fn handle_send_raw_transaction_request< let mut raw_transaction: &[u8] = raw_transaction.as_ref(); let pooled_transaction = ChainSpecT::PooledTransaction::decode(&mut raw_transaction).map_err(|err| match err { - edr_eth::rlp::Error::Custom(message) if transaction::Signed::is_invalid_transaction_type_error(message) => { + edr_eth::rlp::Error::Custom(INVALID_TX_TYPE_ERROR_MESSAGE) => { let type_id = *raw_transaction.first().expect("We already validated that the transaction is not empty if it's an invalid transaction type error."); ProviderError::InvalidTransactionType(type_id) } diff --git a/crates/edr_rpc_eth/src/lib.rs b/crates/edr_rpc_eth/src/lib.rs index 2f0548a40..df11c09a1 100644 --- a/crates/edr_rpc_eth/src/lib.rs +++ b/crates/edr_rpc_eth/src/lib.rs @@ -24,6 +24,7 @@ pub use self::{ call_request::CallRequest, r#override::*, request_methods::RequestMethod, + spec::RpcSpec, transaction::{ ConversionError as TransactionConversionError, Transaction, TransactionRequest, TransactionWithSignature, diff --git a/crates/tools/src/remote_block.rs b/crates/tools/src/remote_block.rs index f99cd641c..7641e8b7b 100644 --- a/crates/tools/src/remote_block.rs +++ b/crates/tools/src/remote_block.rs @@ -23,7 +23,7 @@ pub async fn replay( ) -> anyhow::Result<()> { match chain_type { SupportedChainTypes::L1 => { - replay_chain_specific_block::("l1", url, block_number).await + replay_chain_specific_block::("L1", url, block_number).await } SupportedChainTypes::Optimism => { replay_chain_specific_block::( diff --git a/patches/hardhat@2.22.9.patch b/patches/hardhat@2.22.9.patch index fbb8aefc3..bca90ab4f 100644 --- a/patches/hardhat@2.22.9.patch +++ b/patches/hardhat@2.22.9.patch @@ -45,11 +45,11 @@ index 5538259ab7fcbf99b7fa82e26922510da6444613..f59d394fed89488f006122f9475eddd1 -function getGlobalEdrContext() { - const { EdrContext } = (0, napi_rs_1.requireNapiRsModule)("@nomicfoundation/edr"); +async function getGlobalEdrContext() { -+ const { EdrContext, L1_CHAIN_TYPE, l1ProviderFactory } = (0, napi_rs_1.requireNapiRsModule)("@nomicfoundation/edr"); ++ const { EdrContext, GENERIC_CHAIN_TYPE, genericChainProviderFactory } = (0, napi_rs_1.requireNapiRsModule)("@nomicfoundation/edr"); if (_globalEdrContext === undefined) { // Only one is allowed to exist _globalEdrContext = new EdrContext(); -+ await _globalEdrContext.registerProviderFactory(L1_CHAIN_TYPE, l1ProviderFactory()); ++ await _globalEdrContext.registerProviderFactory(GENERIC_CHAIN_TYPE, genericChainProviderFactory()); } return _globalEdrContext; } @@ -58,7 +58,7 @@ index 5538259ab7fcbf99b7fa82e26922510da6444613..f59d394fed89488f006122f9475eddd1 } static async create(config, loggerConfig, tracingConfig) { - const { Provider } = (0, napi_rs_1.requireNapiRsModule)("@nomicfoundation/edr"); -+ const { L1_CHAIN_TYPE } = (0, napi_rs_1.requireNapiRsModule)("@nomicfoundation/edr"); ++ const { GENERIC_CHAIN_TYPE } = (0, napi_rs_1.requireNapiRsModule)("@nomicfoundation/edr"); const coinbase = config.coinbase ?? exports.DEFAULT_COINBASE; let fork; if (config.forkConfig !== undefined) { @@ -68,7 +68,7 @@ index 5538259ab7fcbf99b7fa82e26922510da6444613..f59d394fed89488f006122f9475eddd1 const hardforkName = (0, hardforks_1.getHardforkName)(config.hardfork); - const provider = await Provider.withConfig(getGlobalEdrContext(), { + const context = await getGlobalEdrContext(); -+ const provider = await context.createProvider(L1_CHAIN_TYPE, { ++ const provider = await context.createProvider(GENERIC_CHAIN_TYPE, { allowBlocksWithSameTimestamp: config.allowBlocksWithSameTimestamp ?? false, allowUnlimitedContractSize: config.allowUnlimitedContractSize, bailOnCallFailure: config.throwOnCallFailures, @@ -253,7 +253,7 @@ index c48c51cfb758478b32295e82952b3cd4d9967aac..3dda0ab27f9a3dc7cf9032ddd39544ba -export function getGlobalEdrContext(): EdrContext { - const { EdrContext } = requireNapiRsModule( +export async function getGlobalEdrContext(): Promise { -+ const { EdrContext, L1_CHAIN_TYPE, l1ProviderFactory } = requireNapiRsModule( ++ const { EdrContext, GENERIC_CHAIN_TYPE, genericChainProviderFactory } = requireNapiRsModule( "@nomicfoundation/edr" ) as typeof import("@nomicfoundation/edr"); @@ -261,8 +261,8 @@ index c48c51cfb758478b32295e82952b3cd4d9967aac..3dda0ab27f9a3dc7cf9032ddd39544ba // Only one is allowed to exist _globalEdrContext = new EdrContext(); + await _globalEdrContext.registerProviderFactory( -+ L1_CHAIN_TYPE, -+ l1ProviderFactory() ++ GENERIC_CHAIN_TYPE, ++ genericChainProviderFactory() + ); } @@ -280,19 +280,19 @@ index c48c51cfb758478b32295e82952b3cd4d9967aac..3dda0ab27f9a3dc7cf9032ddd39544ba tracingConfig?: TracingConfig ): Promise { - const { Provider } = requireNapiRsModule( -+ const { L1_CHAIN_TYPE } = requireNapiRsModule( ++ const { GENERIC_CHAIN_TYPE } = requireNapiRsModule( "@nomicfoundation/edr" ) as typeof import("@nomicfoundation/edr"); - + @@ -228,8 +230,9 @@ export class EdrProviderWrapper - + const hardforkName = getHardforkName(config.hardfork); - + - const provider = await Provider.withConfig( - getGlobalEdrContext(), + const context = await getGlobalEdrContext(); + const provider = await context.createProvider( -+ L1_CHAIN_TYPE, ++ GENERIC_CHAIN_TYPE, { allowBlocksWithSameTimestamp: config.allowBlocksWithSameTimestamp ?? false, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e415341e..5a2c73c99 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,7 @@ patchedDependencies: hash: zemhiof4b3pqw5bzdzeiyb46dm path: patches/@defi-wonderland__smock@2.4.0.patch hardhat@2.22.9: - hash: 3rog4ti6i6ih255j5l5sfuozs4 + hash: vkqvlc66s2ufa4tdcctiaivtte path: patches/hardhat@2.22.9.patch importers: @@ -72,7 +72,7 @@ importers: version: 4.4.1 hardhat: specifier: 2.22.9 - version: 2.22.9(patch_hash=3rog4ti6i6ih255j5l5sfuozs4)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) + version: 2.22.9(patch_hash=vkqvlc66s2ufa4tdcctiaivtte)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) lodash: specifier: ^4.17.11 version: 4.17.21 @@ -192,7 +192,7 @@ importers: version: 7.0.1 hardhat: specifier: 2.22.9 - version: 2.22.9(patch_hash=3rog4ti6i6ih255j5l5sfuozs4)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) + version: 2.22.9(patch_hash=vkqvlc66s2ufa4tdcctiaivtte)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) mocha: specifier: ^10.0.0 version: 10.3.0 @@ -222,10 +222,10 @@ importers: devDependencies: '@defi-wonderland/smock': specifier: ^2.4.0 - version: 2.4.0(patch_hash=zemhiof4b3pqw5bzdzeiyb46dm)(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.9(patch_hash=3rog4ti6i6ih255j5l5sfuozs4)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.9(patch_hash=3rog4ti6i6ih255j5l5sfuozs4)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4)) + version: 2.4.0(patch_hash=zemhiof4b3pqw5bzdzeiyb46dm)(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.9(patch_hash=vkqvlc66s2ufa4tdcctiaivtte)(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.9(patch_hash=vkqvlc66s2ufa4tdcctiaivtte)(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4)) '@nomiclabs/hardhat-ethers': specifier: ^2.2.3 - version: 2.2.3(ethers@5.7.2)(hardhat@2.22.9(patch_hash=3rog4ti6i6ih255j5l5sfuozs4)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4)) + version: 2.2.3(ethers@5.7.2)(hardhat@2.22.9(patch_hash=vkqvlc66s2ufa4tdcctiaivtte)(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4)) chai: specifier: ^4.3.6 version: 4.4.1 @@ -234,7 +234,7 @@ importers: version: 5.7.2 hardhat: specifier: 2.22.9 - version: 2.22.9(patch_hash=3rog4ti6i6ih255j5l5sfuozs4)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) + version: 2.22.9(patch_hash=vkqvlc66s2ufa4tdcctiaivtte)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) mocha: specifier: ^10.0.0 version: 10.3.0 @@ -3098,16 +3098,16 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@defi-wonderland/smock@2.4.0(patch_hash=zemhiof4b3pqw5bzdzeiyb46dm)(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.9(patch_hash=3rog4ti6i6ih255j5l5sfuozs4)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.9(patch_hash=3rog4ti6i6ih255j5l5sfuozs4)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4))': + '@defi-wonderland/smock@2.4.0(patch_hash=zemhiof4b3pqw5bzdzeiyb46dm)(@ethersproject/abi@5.7.0)(@ethersproject/abstract-provider@5.7.0)(@ethersproject/abstract-signer@5.7.0)(@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.9(patch_hash=vkqvlc66s2ufa4tdcctiaivtte)(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4)))(ethers@5.7.2)(hardhat@2.22.9(patch_hash=vkqvlc66s2ufa4tdcctiaivtte)(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4))': dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/abstract-provider': 5.7.0 '@ethersproject/abstract-signer': 5.7.0 '@nomicfoundation/ethereumjs-util': 9.0.4 - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.22.9(patch_hash=3rog4ti6i6ih255j5l5sfuozs4)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4)) + '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.22.9(patch_hash=vkqvlc66s2ufa4tdcctiaivtte)(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4)) diff: 5.0.0 ethers: 5.7.2 - hardhat: 2.22.9(patch_hash=3rog4ti6i6ih255j5l5sfuozs4)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) + hardhat: 2.22.9(patch_hash=vkqvlc66s2ufa4tdcctiaivtte)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) lodash.isequal: 4.5.0 lodash.isequalwith: 4.4.0 rxjs: 7.8.1 @@ -3618,10 +3618,10 @@ snapshots: '@nomicfoundation/solidity-analyzer-win32-ia32-msvc': 0.1.1 '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.1 - '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.9(patch_hash=3rog4ti6i6ih255j5l5sfuozs4)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4))': + '@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.22.9(patch_hash=vkqvlc66s2ufa4tdcctiaivtte)(ts-node@10.9.2(typescript@5.0.4))(typescript@5.0.4))': dependencies: ethers: 5.7.2 - hardhat: 2.22.9(patch_hash=3rog4ti6i6ih255j5l5sfuozs4)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) + hardhat: 2.22.9(patch_hash=vkqvlc66s2ufa4tdcctiaivtte)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4) '@scure/base@1.1.5': {} @@ -4888,7 +4888,7 @@ snapshots: hard-rejection@2.1.0: {} - hardhat@2.22.9(patch_hash=3rog4ti6i6ih255j5l5sfuozs4)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4): + hardhat@2.22.9(patch_hash=vkqvlc66s2ufa4tdcctiaivtte)(ts-node@10.9.2(@types/node@18.15.13)(typescript@5.0.4))(typescript@5.0.4): dependencies: '@ethersproject/abi': 5.7.0 '@metamask/eth-sig-util': 4.0.1