From 1d0341db99044ccc65099933c37ec9e41ce8478e Mon Sep 17 00:00:00 2001 From: Wodann Date: Thu, 5 Oct 2023 11:55:52 +0200 Subject: [PATCH] feat: Cancun support in EDR --- crates/rethnet_eth/src/block.rs | 240 ++++++++---------- crates/rethnet_eth/src/receipt.rs | 13 +- crates/rethnet_eth/src/remote/eth.rs | 55 +++- crates/rethnet_eth/src/transaction/request.rs | 6 +- .../src/transaction/request/eip4844.rs | 182 +++++++++++++ crates/rethnet_eth/src/transaction/signed.rs | 66 ++++- .../src/transaction/signed/eip4844.rs | 237 +++++++++++++++++ crates/rethnet_evm/src/block/builder.rs | 20 +- crates/rethnet_evm/src/block/remote.rs | 55 ++-- crates/rethnet_evm/src/transaction.rs | 2 +- crates/rethnet_evm/src/transaction/pending.rs | 56 ++-- crates/rethnet_evm_napi/src/block.rs | 57 ++++- .../src/transaction/pending.rs | 30 ++- .../src/transaction/signed.rs | 24 +- .../src/transaction/signed/eip4844.rs | 127 +++++++++ .../hardhat-network/provider/context/dual.ts | 7 + .../provider/context/rethnet.ts | 27 +- .../provider/mem-pool/rethnet.ts | 8 +- .../hardhat-network/provider/miner.ts | 1 + .../hardhat-network/provider/miner/hardhat.ts | 10 + .../hardhat-network/provider/miner/rethnet.ts | 10 + .../internal/hardhat-network/provider/node.ts | 50 +--- .../provider/utils/convertToRethnet.ts | 4 + .../hardhat-network/provider/vm/creation.ts | 8 - .../hardhat-network/provider/vm/dual.ts | 10 +- .../hardhat-network/provider/vm/ethereumjs.ts | 19 +- .../hardhat-network/provider/vm/exit.ts | 2 + .../hardhat-network/provider/vm/rethnet.ts | 36 ++- .../hardhat-network/provider/vm/vm-adapter.ts | 4 +- .../internal/hardhat-network/provider/node.ts | 8 + yarn.lock | 80 +++--- 31 files changed, 1108 insertions(+), 346 deletions(-) create mode 100644 crates/rethnet_eth/src/transaction/request/eip4844.rs create mode 100644 crates/rethnet_eth/src/transaction/signed/eip4844.rs create mode 100644 crates/rethnet_evm_napi/src/transaction/signed/eip4844.rs diff --git a/crates/rethnet_eth/src/block.rs b/crates/rethnet_eth/src/block.rs index dd15bd0882..67fec6a732 100644 --- a/crates/rethnet_eth/src/block.rs +++ b/crates/rethnet_eth/src/block.rs @@ -9,7 +9,11 @@ mod reorg; use std::sync::OnceLock; -use revm_primitives::{keccak256, ruint::aliases::U160, SpecId}; +use revm_primitives::{ + calc_excess_blob_gas, keccak256, + ruint::aliases::{U160, U64}, + SpecId, +}; use rlp::Decodable; use crate::{ @@ -134,6 +138,9 @@ pub struct Header { pub base_fee_per_gas: Option, /// WithdrawalsHash was added by EIP-4895 and is ignored in legacy headers. pub withdrawals_root: Option, + /// Blob gas was added by EIP-4844 and is ignored in older headers. + #[cfg_attr(feature = "serde", serde(flatten))] + pub blob_gas: Option, } #[derive(serde::Serialize, serde::Deserialize)] @@ -148,8 +155,21 @@ impl From for B64 { } } +/// Information about the blob gas used in a block. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct BlobGas { + /// The total amount of blob gas consumed by the transactions within the block. + pub gas_used: u64, + /// The running total of blob gas consumed in excess of the target, prior to + /// the block. Blocks with above-target blob gas consumption increase this value, + /// blocks with below-target blob gas consumption decrease it (bounded at 0). + pub excess_gas: u64, +} + impl Header { - /// Constructs a [`Header`] from the provided [`PartialHeader`], ommers' root hash, transactions' root hash, and withdrawals' root hash. + /// Constructs a header from the provided [`PartialHeader`], ommers' root hash, transactions' root hash, and withdrawals' root hash. pub fn new( partial_header: PartialHeader, ommers_hash: B256, @@ -174,6 +194,7 @@ impl Header { nonce: partial_header.nonce, base_fee_per_gas: partial_header.base_fee, withdrawals_root, + blob_gas: partial_header.blob_gas, } } @@ -182,40 +203,6 @@ impl Header { let encoded = rlp::encode(self); keccak256(&encoded) } - - /// Returns the rlp length of the Header body, _not including_ trailing EIP155 fields or the - /// rlp list header - /// To get the length including the rlp list header, refer to the Encodable implementation. - #[cfg(feature = "fastrlp")] - pub(crate) fn header_payload_length(&self) -> usize { - use open_fastrlp::Encodable; - - let mut length = 0; - length += self.parent_hash.length(); - length += self.ommers_hash.length(); - length += self.beneficiary.length(); - length += self.state_root.length(); - length += self.transactions_root.length(); - length += self.receipts_root.length(); - length += self.logs_bloom.length(); - length += self.difficulty.length(); - length += self.number.length(); - length += self.gas_limit.length(); - length += self.gas_used.length(); - length += self.timestamp.length(); - length += self.extra_data.length(); - length += self.mix_hash.length(); - length += self.nonce.length(); - length += self - .base_fee_per_gas - .map(|fee| fee.length()) - .unwrap_or_default(); - length += self - .withdrawals_root - .map(|root| root.length()) - .unwrap_or_default(); - length - } } impl rlp::Encodable for Header { @@ -224,8 +211,10 @@ impl rlp::Encodable for Header { 15 } else if self.withdrawals_root.is_none() { 16 - } else { + } else if self.blob_gas.is_none() { 17 + } else { + 19 }); s.append(&self.parent_hash.as_bytes()); @@ -249,6 +238,14 @@ impl rlp::Encodable for Header { if let Some(ref root) = self.withdrawals_root { s.append(&root.as_bytes()); } + if let Some(BlobGas { + ref gas_used, + ref excess_gas, + }) = self.blob_gas + { + s.append(&U64::from_limbs([*gas_used])); + s.append(&U64::from_limbs([*excess_gas])); + } } } @@ -283,6 +280,18 @@ impl rlp::Decodable for Header { } else { None }, + + blob_gas: if let Ok((gas_used, excess_gas)) = rlp + .at(17) + .and_then(|gas_used| rlp.at(18).map(|excess_gas| (gas_used, excess_gas))) + { + Some(BlobGas { + gas_used: ::decode(&gas_used)?.as_limbs()[0], + excess_gas: ::decode(&excess_gas)?.as_limbs()[0], + }) + } else { + None + }, }; Ok(result) } @@ -390,6 +399,8 @@ pub struct PartialHeader { pub nonce: B64, /// BaseFee was added by EIP-1559 and is ignored in legacy headers. pub base_fee: Option, + /// Blob gas was added by EIP-4844 and is ignored in older headers. + pub blob_gas: Option, } impl PartialHeader { @@ -441,6 +452,23 @@ impl PartialHeader { None } }), + blob_gas: if spec_id >= SpecId::CANCUN { + let excess_gas = parent.and_then(|parent| parent.blob_gas.as_ref()).map_or( + // For the first (post-fork) block, both parent.blob_gas_used and parent.excess_blob_gas are evaluated as 0. + 0, + |BlobGas { + gas_used, + excess_gas, + }| calc_excess_blob_gas(*excess_gas, *gas_used), + ); + + Some(BlobGas { + gas_used: 0, + excess_gas, + }) + } else { + None + }, } } } @@ -463,7 +491,8 @@ impl Default for PartialHeader { extra_data: Bytes::default(), mix_hash: B256::default(), nonce: B64::default(), - base_fee: Option::default(), + base_fee: None, + blob_gas: None, } } } @@ -485,6 +514,7 @@ impl From
for PartialHeader { mix_hash: header.mix_hash, nonce: header.nonce, base_fee: header.base_fee_per_gas, + blob_gas: header.blob_gas, } } } @@ -517,6 +547,7 @@ mod tests { nonce: B64::from_limbs([99u64.to_be()]), base_fee_per_gas: None, withdrawals_root: None, + blob_gas: None, }; let encoded = rlp::encode(&header); @@ -531,71 +562,32 @@ mod tests { } #[test] - #[cfg(feature = "fastrlp")] - fn header_fastrlp_roundtrip() { - let mut header = Header { - parent_hash: Default::default(), - ommers_hash: Default::default(), - beneficiary: Default::default(), - state_root: Default::default(), - transactions_root: Default::default(), - receipts_root: Default::default(), - logs_bloom: Default::default(), - difficulty: Default::default(), - number: 124u64.into(), - gas_limit: Default::default(), - gas_used: 1337u64.into(), - timestamp: 0, - extra_data: Default::default(), - mix_hash: Default::default(), - nonce: H64::from_low_u64_be(99u64), - base_fee_per_gas: None, - }; - - let mut encoded = vec![]; -
::encode(&header, &mut encoded); - let decoded: Header = -
::decode(&mut encoded.as_slice()).unwrap(); - assert_eq!(header, decoded); - - header.base_fee_per_gas = Some(12345u64.into()); - - encoded.clear(); -
::encode(&header, &mut encoded); - let decoded: Header = -
::decode(&mut encoded.as_slice()).unwrap(); - assert_eq!(header, decoded); - } - - #[test] - #[cfg(feature = "fastrlp")] // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 fn test_encode_block_header() { - use open_fastrlp::Encodable; - let expected = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap(); - let mut data = vec![]; + let header = Header { - parent_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - ommers_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - beneficiary: H160::from_str("0000000000000000000000000000000000000000").unwrap(), - state_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - transactions_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - receipts_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - logs_bloom: <[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(), - difficulty: 0x8aeu64.into(), - number: 0xd05u64.into(), - gas_limit: 0x115cu64.into(), - gas_used: 0x15b3u64.into(), - timestamp: 0x1a0au64, + parent_hash: B256::zero(), + ommers_hash: B256::zero(), + beneficiary: Address::zero(), + state_root: B256::zero(), + transactions_root: B256::zero(), + receipts_root: B256::zero(), + logs_bloom: Bloom::zero(), + difficulty: U256::from(0x8aeu64), + number: U256::from(0xd05u64), + gas_limit: U256::from(0x115cu64), + gas_used: U256::from(0x15b3u64), + timestamp: U256::from(0x1a0au64), extra_data: hex::decode("7788").unwrap().into(), - mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - nonce: U64::from(0x0), + mix_hash: B256::zero(), + nonce: B64::ZERO, base_fee_per_gas: None, + withdrawals_root: None, + blob_gas: None, }; - header.encode(&mut data); - assert_eq!(hex::encode(&data), hex::encode(expected)); - assert_eq!(header.length(), data.len()); + let encoded = rlp::encode(&header); + assert_eq!(encoded, &expected); } #[test] @@ -642,53 +634,37 @@ mod tests { nonce: B64::from(U64::ZERO), base_fee_per_gas: Some(U256::from(0x036bu64)), withdrawals_root: None, + blob_gas: None, }; assert_eq!(header.hash(), expected_hash); } #[test] - #[cfg(feature = "fastrlp")] // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 fn test_decode_block_header() { let data = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap(); + let expected = Header { - parent_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - ommers_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - beneficiary: H160::from_str("0000000000000000000000000000000000000000").unwrap(), - state_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - transactions_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - receipts_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - logs_bloom: <[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(), - difficulty: 0x8aeu64.into(), - number: 0xd05u64.into(), - gas_limit: 0x115cu64.into(), - gas_used: 0x15b3u64.into(), - timestamp: 0x1a0au64, + parent_hash: B256::zero(), + ommers_hash: B256::zero(), + beneficiary: Address::zero(), + state_root: B256::zero(), + transactions_root: B256::zero(), + receipts_root: B256::zero(), + logs_bloom: Bloom::zero(), + difficulty: U256::from(0x8aeu64), + number: U256::from(0xd05u64), + gas_limit: U256::from(0x115cu64), + gas_used: U256::from(0x15b3u64), + timestamp: U256::from(0x1a0au64), extra_data: hex::decode("7788").unwrap().into(), - mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - nonce: U64::from(0x0), + mix_hash: B256::zero(), + nonce: B64::ZERO, base_fee_per_gas: None, + withdrawals_root: None, + blob_gas: None, }; - let header =
::decode(&mut data.as_slice()).unwrap(); - assert_eq!(header, expected); - } - - #[test] - #[cfg(feature = "fastrlp")] - // Test vector from network - fn block_network_fastrlp_roundtrip() { - use open_fastrlp::Encodable; - - let data = hex::decode("f9034df90348a0fbdbd8d2d0ac5f14bd5fa90e547fe6f1d15019c724f8e7b60972d381cd5d9cf8a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794c9577e7945db22e38fc060909f2278c7746b0f9ba05017cfa3b0247e35197215ae8d610265ffebc8edca8ea66d6567eb0adecda867a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018355bb7b871fffffffffffff808462bd0e1ab9014bf90148a00000000000000000000000000000000000000000000000000000000000000000f85494319fa8f1bc4e53410e92d10d918659b16540e60a945a573efb304d04c1224cd012313e827eca5dce5d94a9c831c5a268031176ebf5f3de5051e8cba0dbfe94c9577e7945db22e38fc060909f2278c7746b0f9b808400000000f8c9b841a6946f2d16f68338cbcbd8b117374ab421128ce422467088456bceba9d70c34106128e6d4564659cf6776c08a4186063c0a05f7cffd695c10cf26a6f301b67f800b8412b782100c18c35102dc0a37ece1a152544f04ad7dc1868d18a9570f744ace60870f822f53d35e89a2ea9709ccbf1f4a25ee5003944faa845d02dde0a41d5704601b841d53caebd6c8a82456e85c2806a9e08381f959a31fb94a77e58f00e38ad97b2e0355b8519ab2122662cbe022f2a4ef7ff16adc0b2d5dcd123181ec79705116db300a063746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365880000000000000000c0c0").unwrap(); - - let block = ::decode(&mut data.as_slice()).unwrap(); - - // encode and check that it matches the original data - let mut encoded = Vec::new(); - block.encode(&mut encoded); - assert_eq!(data, encoded); - - // check that length of encoding is the same as the output of `length` - assert_eq!(block.length(), encoded.len()); + let decoded: Header = rlp::decode(&data).unwrap(); + assert_eq!(decoded, expected); } } diff --git a/crates/rethnet_eth/src/receipt.rs b/crates/rethnet_eth/src/receipt.rs index a8279cc9f6..b00c012c62 100644 --- a/crates/rethnet_eth/src/receipt.rs +++ b/crates/rethnet_eth/src/receipt.rs @@ -54,6 +54,11 @@ pub enum TypedReceiptData { #[cfg_attr(feature = "serde", serde(with = "crate::serde::u8"))] status: u8, }, + #[cfg_attr(feature = "serde", serde(rename = "0x3"))] + Eip4844 { + #[cfg_attr(feature = "serde", serde(with = "crate::serde::u8"))] + status: u8, + }, } impl TypedReceipt { @@ -63,7 +68,8 @@ impl TypedReceipt { TypedReceiptData::PreEip658Legacy { .. } => None, TypedReceiptData::PostEip658Legacy { status } | TypedReceiptData::Eip2930 { status } - | TypedReceiptData::Eip1559 { status } => Some(*status), + | TypedReceiptData::Eip1559 { status } + | TypedReceiptData::Eip4844 { status } => Some(*status), } } @@ -82,6 +88,7 @@ impl TypedReceipt { | TypedReceiptData::PostEip658Legacy { .. } => 0u64, TypedReceiptData::Eip2930 { .. } => 1u64, TypedReceiptData::Eip1559 { .. } => 2u64, + TypedReceiptData::Eip4844 { .. } => 3u64, } } } @@ -306,6 +313,7 @@ where | TypedReceiptData::PostEip658Legacy { .. } => None, TypedReceiptData::Eip2930 { .. } => Some(1), TypedReceiptData::Eip1559 { .. } => Some(2), + TypedReceiptData::Eip4844 { .. } => Some(3), }; if let Some(id) = id { @@ -320,7 +328,8 @@ where } TypedReceiptData::PostEip658Legacy { status } | TypedReceiptData::Eip2930 { status } - | TypedReceiptData::Eip1559 { status } => { + | TypedReceiptData::Eip1559 { status } + | TypedReceiptData::Eip4844 { status } => { if *status == 0 { s.append_empty_data(); } else { diff --git a/crates/rethnet_eth/src/remote/eth.rs b/crates/rethnet_eth/src/remote/eth.rs index b0f144afaa..1278358331 100644 --- a/crates/rethnet_eth/src/remote/eth.rs +++ b/crates/rethnet_eth/src/remote/eth.rs @@ -16,7 +16,7 @@ use crate::{ signature::Signature, transaction::{ EIP1559SignedTransaction, EIP155SignedTransaction, EIP2930SignedTransaction, - LegacySignedTransaction, SignedTransaction, TransactionKind, + Eip4844SignedTransaction, LegacySignedTransaction, SignedTransaction, TransactionKind, }, withdrawal::Withdrawal, Address, Bloom, Bytes, B256, U256, @@ -82,6 +82,12 @@ pub struct Transaction { /// max priority fee per gas #[serde(default)] pub max_priority_fee_per_gas: Option, + /// The maximum total fee per gas the sender is willing to pay for blob gas in wei (EIP-4844) + #[serde(default)] + pub max_fee_per_blob_gas: Option, + /// List of versioned blob hashes associated with the transaction's EIP-4844 data blobs. + #[serde(default)] + pub blob_versioned_hashes: Option>, } impl Transaction { @@ -102,6 +108,9 @@ pub enum TransactionConversionError { /// Missing access list #[error("Missing access list")] MissingAccessList, + /// EIP-4844 transaction is missing blob (versioned) hashes + #[error("Missing blob hashes")] + MissingBlobHashes, /// Missing chain ID #[error("Missing chain ID")] MissingChainId, @@ -111,6 +120,12 @@ pub enum TransactionConversionError { /// Missing max priority fee per gas #[error("Missing max priority fee per gas")] MissingMaxPriorityFeePerGas, + /// EIP-4844 transaction is missing the max fee per blob gas + #[error("Missing max fee per blob gas")] + MissingMaxFeePerBlobGas, + /// EIP-4844 transaction is missing the receiver (to) address + #[error("Missing receiver (to) address")] + MissingReceiverAddress, /// The transaction type is not supported. #[error("Unsupported type {0}")] UnsupportedType(u64), @@ -203,6 +218,38 @@ impl TryFrom for (SignedTransaction, Address) { s: value.s, hash: OnceLock::from(value.hash), }), + 3 => SignedTransaction::Eip4844(Eip4844SignedTransaction { + odd_y_parity: value.odd_y_parity(), + chain_id: value + .chain_id + .ok_or(TransactionConversionError::MissingChainId)?, + nonce: value.nonce, + max_priority_fee_per_gas: value + .max_priority_fee_per_gas + .ok_or(TransactionConversionError::MissingMaxPriorityFeePerGas)?, + max_fee_per_gas: value + .max_fee_per_gas + .ok_or(TransactionConversionError::MissingMaxFeePerGas)?, + max_fee_per_blob_gas: value + .max_fee_per_blob_gas + .ok_or(TransactionConversionError::MissingMaxFeePerBlobGas)?, + gas_limit: value.gas.to(), + to: value + .to + .ok_or(TransactionConversionError::MissingReceiverAddress)?, + value: value.value, + input: value.input, + access_list: value + .access_list + .ok_or(TransactionConversionError::MissingAccessList)? + .into(), + blob_hashes: value + .blob_versioned_hashes + .ok_or(TransactionConversionError::MissingBlobHashes)?, + r: value.r, + s: value.s, + hash: OnceLock::from(value.hash), + }), r#type => { return Err(TransactionConversionError::UnsupportedType(r#type)); } @@ -275,4 +322,10 @@ pub struct Block { pub withdrawals: Option>, /// withdrawals root pub withdrawals_root: Option, + /// The total amount of gas used by the transactions. + #[serde(default, with = "crate::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, with = "crate::serde::optional_u64")] + pub excess_blob_gas: Option, } diff --git a/crates/rethnet_eth/src/transaction/request.rs b/crates/rethnet_eth/src/transaction/request.rs index 83e90ff8df..726926b18a 100644 --- a/crates/rethnet_eth/src/transaction/request.rs +++ b/crates/rethnet_eth/src/transaction/request.rs @@ -1,11 +1,13 @@ mod eip155; mod eip1559; mod eip2930; +mod eip4844; mod legacy; pub use self::{ eip155::EIP155TransactionRequest, eip1559::EIP1559TransactionRequest, - eip2930::EIP2930TransactionRequest, legacy::LegacyTransactionRequest, + eip2930::EIP2930TransactionRequest, eip4844::Eip4844TransactionRequest, + legacy::LegacyTransactionRequest, }; /// Container type for various Ethereum transaction requests @@ -24,4 +26,6 @@ pub enum TransactionRequest { EIP2930(EIP2930TransactionRequest), /// An EIP-1559 transaction request EIP1559(EIP1559TransactionRequest), + /// An EIP-4844 transaction request + Eip4844(Eip4844TransactionRequest), } diff --git a/crates/rethnet_eth/src/transaction/request/eip4844.rs b/crates/rethnet_eth/src/transaction/request/eip4844.rs new file mode 100644 index 0000000000..917dcd218b --- /dev/null +++ b/crates/rethnet_eth/src/transaction/request/eip4844.rs @@ -0,0 +1,182 @@ +use std::sync::OnceLock; + +use bytes::Bytes; +use revm_primitives::{keccak256, Address, B256, U256}; +use secp256k1::SecretKey; + +use crate::{ + access_list::AccessListItem, signature::Signature, transaction::Eip4844SignedTransaction, + utils::envelop_bytes, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr( + feature = "fastrlp", + derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable) +)] +pub struct Eip4844TransactionRequest { + pub chain_id: u64, + pub nonce: u64, + pub max_priority_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub max_fee_per_blob_gas: U256, + pub gas_limit: u64, + pub to: Address, + pub value: U256, + pub input: Bytes, + pub access_list: Vec, + pub blob_hashes: Vec, +} + +impl Eip4844TransactionRequest { + /// Computes the hash of the transaction. + pub fn hash(&self) -> B256 { + let encoded = rlp::encode(self); + + keccak256(&envelop_bytes(2, &encoded)) + } + + pub fn sign(self, private_key: &SecretKey) -> Eip4844SignedTransaction { + let hash = self.hash(); + + let signature = Signature::new(hash, private_key); + + Eip4844SignedTransaction { + chain_id: self.chain_id, + nonce: self.nonce, + max_priority_fee_per_gas: self.max_priority_fee_per_gas, + max_fee_per_gas: self.max_fee_per_gas, + max_fee_per_blob_gas: self.max_fee_per_blob_gas, + gas_limit: self.gas_limit, + to: self.to, + value: self.value, + input: self.input, + access_list: self.access_list.into(), + blob_hashes: self.blob_hashes, + odd_y_parity: signature.odd_y_parity(), + r: signature.r, + s: signature.s, + hash: OnceLock::new(), + } + } +} + +impl From<&Eip4844SignedTransaction> for Eip4844TransactionRequest { + fn from(t: &Eip4844SignedTransaction) -> Self { + Self { + chain_id: t.chain_id, + nonce: t.nonce, + max_priority_fee_per_gas: t.max_priority_fee_per_gas, + max_fee_per_gas: t.max_fee_per_gas, + max_fee_per_blob_gas: t.max_fee_per_blob_gas, + gas_limit: t.gas_limit, + to: t.to, + value: t.value, + input: t.input.clone(), + access_list: t.access_list.0.clone(), + blob_hashes: t.blob_hashes.clone(), + } + } +} + +impl rlp::Encodable for Eip4844TransactionRequest { + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(11); + s.append(&self.chain_id); + s.append(&self.nonce); + s.append(&self.max_priority_fee_per_gas); + s.append(&self.max_fee_per_gas); + s.append(&self.gas_limit); + s.append(&self.to.as_bytes()); + s.append(&self.value); + s.append(&self.input.as_ref()); + s.append_list(&self.access_list); + s.append(&self.max_fee_per_blob_gas); + + let blob_hashes = self + .blob_hashes + .iter() + .map(B256::as_bytes) + .collect::>(); + + s.append_list::<&[u8], &[u8]>(blob_hashes.as_slice()); + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use revm_primitives::Address; + + use super::*; + + fn _dummy_request() -> Eip4844TransactionRequest { + let to = Address::from_str("0xc014ba5ec014ba5ec014ba5ec014ba5ec014ba5e").unwrap(); + let input = hex::decode("1234").unwrap(); + Eip4844TransactionRequest { + chain_id: 1, + nonce: 1, + max_priority_fee_per_gas: U256::from(2), + max_fee_per_gas: U256::from(5), + max_fee_per_blob_gas: U256::from(7), + gas_limit: 3, + to, + value: U256::from(4), + input: Bytes::from(input), + access_list: vec![AccessListItem { + address: Address::zero(), + storage_keys: vec![B256::zero(), B256::from(U256::from(1))], + }], + blob_hashes: vec![B256::zero(), B256::from(U256::from(1))], + } + } + + // TODO: add tests + // #[test] + // fn test_eip1559_transaction_request_encoding() { + // // Generated by Hardhat + // // QUESTION: What is considered a valid RLP-encoding? With the prepending type? or without? + // let expected = + // hex::decode("f87b010102050394c014ba5ec014ba5ec014ba5ec014ba5ec014ba5e04821234f85bf859940000000000000000000000000000000000000000f842a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001") + // .unwrap(); + + // let request = dummy_request(); + + // let encoded = rlp::encode(&request); + // assert_eq!(expected, encoded.to_vec()); + // } + + // #[test] + // fn test_eip1559_transaction_request_encoding_empty() { + // // Generated by Hardhat + // let expected = hex::decode("c90180808080808080c0").unwrap(); + + // let empty = Eip4844TransactionRequest { + // chain_id: 1, + // nonce: 0, + // max_priority_fee_per_gas: U256::ZERO, + // max_fee_per_gas: U256::ZERO, + // gas_limit: 0, + // kind: TransactionKind::Create, + // value: U256::ZERO, + // input: Bytes::new(), + // access_list: vec![], + // }; + + // let encoded = rlp::encode(&empty); + // assert_eq!(expected, encoded.to_vec()); + // } + + // #[test] + // fn test_eip1559_transaction_request_hash() { + // // Generated by hardhat + // let expected = B256::from_slice( + // &hex::decode("1d21c520c93f0f8e07c2466361b22a8bb9906cdbf4670e53a701c075bbe69ecf") + // .unwrap(), + // ); + + // let request = dummy_request(); + // assert_eq!(expected, request.hash()); + // } +} diff --git a/crates/rethnet_eth/src/transaction/signed.rs b/crates/rethnet_eth/src/transaction/signed.rs index 5a5ea46189..5dee94d098 100644 --- a/crates/rethnet_eth/src/transaction/signed.rs +++ b/crates/rethnet_eth/src/transaction/signed.rs @@ -1,6 +1,7 @@ mod eip155; mod eip1559; mod eip2930; +mod eip4844; mod legacy; use bytes::Bytes; @@ -16,7 +17,8 @@ use super::kind::TransactionKind; pub use self::{ eip155::EIP155SignedTransaction, eip1559::EIP1559SignedTransaction, - eip2930::EIP2930SignedTransaction, legacy::LegacySignedTransaction, + eip2930::EIP2930SignedTransaction, eip4844::Eip4844SignedTransaction, + legacy::LegacySignedTransaction, }; #[derive(Clone, Debug, PartialEq, Eq)] @@ -30,6 +32,8 @@ pub enum SignedTransaction { Eip2930(EIP2930SignedTransaction), /// EIP-1559 transaction Eip1559(EIP1559SignedTransaction), + /// EIP-4844 transaction + Eip4844(Eip4844SignedTransaction), } impl SignedTransaction { @@ -40,6 +44,7 @@ impl SignedTransaction { SignedTransaction::PostEip155Legacy(tx) => tx.gas_price, SignedTransaction::Eip2930(tx) => tx.gas_price, SignedTransaction::Eip1559(tx) => tx.max_fee_per_gas, + SignedTransaction::Eip4844(tx) => tx.max_fee_per_gas, } } @@ -50,6 +55,7 @@ impl SignedTransaction { SignedTransaction::PostEip155Legacy(tx) => tx.gas_limit, SignedTransaction::Eip2930(tx) => tx.gas_limit, SignedTransaction::Eip1559(tx) => tx.gas_limit, + SignedTransaction::Eip4844(tx) => tx.gas_limit, } } @@ -60,6 +66,7 @@ impl SignedTransaction { SignedTransaction::PostEip155Legacy(tx) => tx.value, SignedTransaction::Eip2930(tx) => tx.value, SignedTransaction::Eip1559(tx) => tx.value, + SignedTransaction::Eip4844(tx) => tx.value, } } @@ -70,6 +77,7 @@ impl SignedTransaction { SignedTransaction::PostEip155Legacy(tx) => &tx.input, SignedTransaction::Eip2930(tx) => &tx.input, SignedTransaction::Eip1559(tx) => &tx.input, + SignedTransaction::Eip4844(tx) => &tx.input, } } @@ -79,6 +87,7 @@ impl SignedTransaction { SignedTransaction::PreEip155Legacy(_) | SignedTransaction::PostEip155Legacy(_) => None, SignedTransaction::Eip2930(tx) => Some(&tx.access_list), SignedTransaction::Eip1559(tx) => Some(&tx.access_list), + SignedTransaction::Eip4844(tx) => Some(&tx.access_list), } } @@ -94,6 +103,7 @@ impl SignedTransaction { | SignedTransaction::PostEip155Legacy(_) | SignedTransaction::Eip2930(_) => None, SignedTransaction::Eip1559(tx) => Some(tx.max_priority_fee_per_gas), + SignedTransaction::Eip4844(tx) => Some(tx.max_priority_fee_per_gas), } } @@ -109,6 +119,7 @@ impl SignedTransaction { SignedTransaction::PostEip155Legacy(t) => t.nonce, SignedTransaction::Eip2930(t) => t.nonce, SignedTransaction::Eip1559(t) => t.nonce, + SignedTransaction::Eip4844(t) => t.nonce, } } @@ -119,6 +130,7 @@ impl SignedTransaction { SignedTransaction::PostEip155Legacy(t) => Some(t.chain_id()), SignedTransaction::Eip2930(t) => Some(t.chain_id), SignedTransaction::Eip1559(t) => Some(t.chain_id), + SignedTransaction::Eip4844(t) => Some(t.chain_id), } } @@ -146,6 +158,7 @@ impl SignedTransaction { SignedTransaction::PostEip155Legacy(t) => t.hash(), SignedTransaction::Eip2930(t) => t.hash(), SignedTransaction::Eip1559(t) => t.hash(), + SignedTransaction::Eip4844(t) => t.hash(), } } @@ -156,22 +169,24 @@ impl SignedTransaction { SignedTransaction::PostEip155Legacy(tx) => tx.recover(), SignedTransaction::Eip2930(tx) => tx.recover(), SignedTransaction::Eip1559(tx) => tx.recover(), + SignedTransaction::Eip4844(tx) => tx.recover(), } } /// Returns what kind of transaction this is - pub fn kind(&self) -> &TransactionKind { + pub fn kind(&self) -> TransactionKind { match self { - SignedTransaction::PreEip155Legacy(tx) => &tx.kind, - SignedTransaction::PostEip155Legacy(tx) => &tx.kind, - SignedTransaction::Eip2930(tx) => &tx.kind, - SignedTransaction::Eip1559(tx) => &tx.kind, + SignedTransaction::PreEip155Legacy(tx) => tx.kind, + SignedTransaction::PostEip155Legacy(tx) => tx.kind, + SignedTransaction::Eip2930(tx) => tx.kind, + SignedTransaction::Eip1559(tx) => tx.kind, + SignedTransaction::Eip4844(tx) => TransactionKind::Call(tx.to), } } /// Returns the callee if this transaction is a call - pub fn to(&self) -> Option<&Address> { - self.kind().as_call() + pub fn to(&self) -> Option
{ + self.kind().as_call().copied() } /// Returns the [`Signature`] of the transaction @@ -189,6 +204,11 @@ impl SignedTransaction { s: tx.s, v: u64::from(tx.odd_y_parity), }, + SignedTransaction::Eip4844(tx) => Signature { + r: tx.r, + s: tx.s, + v: u64::from(tx.odd_y_parity), + }, } } } @@ -200,6 +220,7 @@ impl rlp::Encodable for SignedTransaction { SignedTransaction::PostEip155Legacy(tx) => tx.rlp_append(s), SignedTransaction::Eip2930(tx) => enveloped(1, tx, s), SignedTransaction::Eip1559(tx) => enveloped(2, tx, s), + SignedTransaction::Eip4844(tx) => enveloped(3, tx, s), } } } @@ -226,12 +247,14 @@ impl rlp::Decodable for SignedTransaction { } if first == 0x01 { - return rlp::decode(s).map(SignedTransaction::Eip2930); - } - if first == 0x02 { - return rlp::decode(s).map(SignedTransaction::Eip1559); + rlp::decode(s).map(SignedTransaction::Eip2930) + } else if first == 0x02 { + rlp::decode(s).map(SignedTransaction::Eip1559) + } else if first == 0x03 { + rlp::decode(s).map(SignedTransaction::Eip4844) + } else { + Err(rlp::DecoderError::Custom("invalid tx type")) } - Err(rlp::DecoderError::Custom("invalid tx type")) } } @@ -335,6 +358,23 @@ mod tests { s: U256::default(), hash: OnceLock::new(), }), + SignedTransaction::Eip4844(Eip4844SignedTransaction { + chain_id: 1u64, + nonce: 0, + max_priority_fee_per_gas: U256::from(1), + max_fee_per_gas: U256::from(2), + max_fee_per_blob_gas: U256::from(7), + gas_limit: 3, + to: Address::random(), + value: U256::from(4), + input: Bytes::from(vec![1, 2]), + access_list: vec![].into(), + blob_hashes: vec![B256::random(), B256::random()], + odd_y_parity: true, + r: U256::default(), + s: U256::default(), + hash: OnceLock::new(), + }), ]; for transaction in transactions { diff --git a/crates/rethnet_eth/src/transaction/signed/eip4844.rs b/crates/rethnet_eth/src/transaction/signed/eip4844.rs new file mode 100644 index 0000000000..3db4a208a1 --- /dev/null +++ b/crates/rethnet_eth/src/transaction/signed/eip4844.rs @@ -0,0 +1,237 @@ +use std::sync::OnceLock; + +use bytes::Bytes; +use revm_primitives::{ + keccak256, + ruint::aliases::{U160, U64}, + Address, B256, U256, +}; + +use crate::{ + access_list::AccessList, + signature::{Signature, SignatureError}, + transaction::Eip4844TransactionRequest, + utils::envelop_bytes, +}; + +#[derive(Clone, Debug, Eq)] +#[cfg_attr( + feature = "fastrlp", + derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable) +)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Eip4844SignedTransaction { + #[cfg_attr(feature = "serde", serde(with = "crate::serde::u64"))] + pub chain_id: u64, + #[cfg_attr(feature = "serde", serde(with = "crate::serde::u64"))] + pub nonce: u64, + pub max_priority_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub max_fee_per_blob_gas: U256, + #[cfg_attr(feature = "serde", serde(with = "crate::serde::u64"))] + pub gas_limit: u64, + pub to: Address, + pub value: U256, + #[cfg_attr(feature = "serde", serde(with = "crate::serde::bytes"))] + pub input: Bytes, + pub access_list: AccessList, + pub blob_hashes: Vec, + pub odd_y_parity: bool, + pub r: U256, + pub s: U256, + /// Cached transaction hash + #[cfg_attr(feature = "serde", serde(skip))] + pub hash: OnceLock, +} + +impl Eip4844SignedTransaction { + pub fn nonce(&self) -> &u64 { + &self.nonce + } + + pub fn hash(&self) -> &B256 { + self.hash.get_or_init(|| { + let encoded = rlp::encode(self); + let enveloped = envelop_bytes(2, &encoded); + + keccak256(&enveloped) + }) + } + + /// Recovers the Ethereum address which was used to sign the transaction. + pub fn recover(&self) -> Result { + let signature = Signature { + r: self.r, + s: self.s, + v: u64::from(self.odd_y_parity), + }; + + signature.recover(Eip4844TransactionRequest::from(self).hash()) + } +} + +impl PartialEq for Eip4844SignedTransaction { + fn eq(&self, other: &Self) -> bool { + self.chain_id == other.chain_id + && self.nonce == other.nonce + && self.max_priority_fee_per_gas == other.max_priority_fee_per_gas + && self.max_fee_per_gas == other.max_fee_per_gas + && self.max_fee_per_blob_gas == other.max_fee_per_blob_gas + && self.gas_limit == other.gas_limit + && self.to == other.to + && self.value == other.value + && self.input == other.input + && self.access_list == other.access_list + && self.blob_hashes == other.blob_hashes + && self.odd_y_parity == other.odd_y_parity + && self.r == other.r + && self.s == other.s + } +} + +impl rlp::Encodable for Eip4844SignedTransaction { + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(14); + s.append(&U64::from(self.chain_id)); + s.append(&U64::from(self.nonce)); + s.append(&self.max_priority_fee_per_gas); + s.append(&self.max_fee_per_gas); + s.append(&self.gas_limit); + s.append(&self.to.as_bytes()); + s.append(&self.value); + s.append(&self.input.as_ref()); + s.append(&self.access_list); + s.append(&self.max_fee_per_blob_gas); + + let blob_hashes = self + .blob_hashes + .iter() + .map(B256::as_bytes) + .collect::>(); + + s.append_list::<&[u8], &[u8]>(blob_hashes.as_slice()); + s.append(&self.odd_y_parity); + s.append(&self.r); + s.append(&self.s); + } +} + +impl rlp::Decodable for Eip4844SignedTransaction { + fn decode(rlp: &rlp::Rlp<'_>) -> Result { + if rlp.item_count()? != 12 { + return Err(rlp::DecoderError::RlpIncorrectListLen); + } + + Ok(Self { + chain_id: rlp.val_at(0)?, + nonce: rlp.val_at(1)?, + max_priority_fee_per_gas: rlp.val_at(2)?, + max_fee_per_gas: rlp.val_at(3)?, + gas_limit: rlp.val_at(4)?, + to: Address::from(rlp.val_at::(5)?.to_be_bytes()), + value: rlp.val_at(6)?, + input: rlp.val_at::>(7)?.into(), + access_list: rlp.val_at(8)?, + max_fee_per_blob_gas: rlp.val_at(9)?, + blob_hashes: { + let blob_hashes = rlp.list_at::(1)?; + blob_hashes + .into_iter() + .map(|hash| B256::from(hash.to_be_bytes())) + .collect() + }, + odd_y_parity: rlp.val_at(9)?, + r: rlp.val_at(10)?, + s: rlp.val_at(11)?, + hash: OnceLock::new(), + }) + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use revm_primitives::Address; + use secp256k1::{Secp256k1, SecretKey}; + + use crate::{access_list::AccessListItem, signature::private_key_to_address}; + + use super::*; + + const DUMMY_PRIVATE_KEY: &str = + "e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109"; + + fn dummy_request() -> Eip4844TransactionRequest { + let to = Address::from_str("0xc014ba5ec014ba5ec014ba5ec014ba5ec014ba5e").unwrap(); + let input = hex::decode("1234").unwrap(); + Eip4844TransactionRequest { + chain_id: 1, + nonce: 1, + max_priority_fee_per_gas: U256::from(2), + max_fee_per_gas: U256::from(5), + max_fee_per_blob_gas: U256::from(7), + gas_limit: 3, + to, + value: U256::from(4), + input: Bytes::from(input), + access_list: vec![AccessListItem { + address: Address::zero(), + storage_keys: vec![B256::zero(), B256::from(U256::from(1))], + }], + blob_hashes: vec![B256::zero(), B256::from(U256::from(1))], + } + } + + fn dummy_private_key() -> SecretKey { + SecretKey::from_str(DUMMY_PRIVATE_KEY).unwrap() + } + + #[test] + fn eip4884_signed_transaction_encoding() { + // Generated by Hardhat + let expected = + hex::decode("f8be010102050394c014ba5ec014ba5ec014ba5ec014ba5ec014ba5e04821234f85bf859940000000000000000000000000000000000000000f842a00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000101a07764e376b5b4090264f73abee68ebb5fdc9f76050eff800237e5a2bedadcd7eda044c0ae9b07c75cf4e0a14aebfe792ab2fdccd7d89550b166b1b4a4ece0054f02") + .unwrap(); + + let request = dummy_request(); + let signed = request.sign(&dummy_private_key()); + + let encoded = rlp::encode(&signed); + assert_eq!(expected, encoded.to_vec()); + } + + #[test] + fn eip4884_signed_transaction_hash() { + // Generated by hardhat + let expected = B256::from_slice( + &hex::decode("043d6f6de2e81af3f48d6c64d4cdfc7576f8754c73569bc6903e50f3c92988d8") + .unwrap(), + ); + + let request = dummy_request(); + let signed = request.sign(&dummy_private_key()); + + assert_eq!(expected, *signed.hash()); + } + + #[test] + fn eip4884_signed_transaction_recover() { + let request = dummy_request(); + + let signed = request.sign(&dummy_private_key()); + + let expected = private_key_to_address(&Secp256k1::signing_only(), DUMMY_PRIVATE_KEY) + .expect("Failed to retrieve address from private key"); + assert_eq!(expected, signed.recover().expect("should succeed")); + } + + #[test] + fn eip4884_signed_transaction_rlp() { + let request = dummy_request(); + let signed = request.sign(&dummy_private_key()); + + let encoded = rlp::encode(&signed); + assert_eq!(signed, rlp::decode(&encoded).unwrap()); + } +} diff --git a/crates/rethnet_evm/src/block/builder.rs b/crates/rethnet_evm/src/block/builder.rs index 9e022496e1..3291077876 100644 --- a/crates/rethnet_evm/src/block/builder.rs +++ b/crates/rethnet_evm/src/block/builder.rs @@ -4,7 +4,7 @@ use std::{ }; use rethnet_eth::{ - block::{BlockOptions, Header, PartialHeader}, + block::{BlobGas, BlockOptions, Header, PartialHeader}, log::Log, receipt::{TransactionReceipt, TypedReceipt, TypedReceiptData}, transaction::SignedTransaction, @@ -14,8 +14,9 @@ use rethnet_eth::{ use revm::{ db::DatabaseComponentError, primitives::{ - Account, AccountInfo, AccountStatus, BlockEnv, CfgEnv, EVMError, ExecutionResult, HashMap, - InvalidHeader, InvalidTransaction, Output, ResultAndState, SpecId, + Account, AccountInfo, AccountStatus, BlobExcessGasAndPrice, BlockEnv, CfgEnv, EVMError, + ExecutionResult, HashMap, InvalidHeader, InvalidTransaction, Output, ResultAndState, + SpecId, }, }; @@ -63,7 +64,7 @@ where EVMError::Transaction(e) => Self::InvalidTransaction(e), EVMError::Header( InvalidHeader::ExcessBlobGasNotSet | InvalidHeader::PrevrandaoNotSet, - ) => unreachable!(), + ) => unreachable!("error: {error:?}"), EVMError::Database(DatabaseComponentError::State(e)) => Self::State(e), EVMError::Database(DatabaseComponentError::BlockHash(e)) => Self::BlockHash(e), } @@ -189,9 +190,11 @@ impl BlockBuilder { } else { None }, - // TODO: Add support for EIP-4844 - // https://github.com/NomicFoundation/edr/issues/191 - blob_excess_gas_and_price: None, + blob_excess_gas_and_price: self + .header + .blob_gas + .as_ref() + .map(|BlobGas { excess_gas, .. }| BlobExcessGasAndPrice::new(*excess_gas)), }; let evm = build_evm( @@ -259,12 +262,13 @@ impl BlockBuilder { } SignedTransaction::Eip2930(_) => TypedReceiptData::Eip2930 { status }, SignedTransaction::Eip1559(_) => TypedReceiptData::Eip1559 { status }, + SignedTransaction::Eip4844(_) => TypedReceiptData::Eip4844 { status }, }, }, transaction_hash: *transaction.hash(), transaction_index: self.transactions.len() as u64, from: *transaction.caller(), - to: transaction.to().cloned(), + to: transaction.to(), contract_address, gas_used: U256::from(result.gas_used()), effective_gas_price, diff --git a/crates/rethnet_evm/src/block/remote.rs b/crates/rethnet_evm/src/block/remote.rs index 854e2f4917..45ef6e081b 100644 --- a/crates/rethnet_evm/src/block/remote.rs +++ b/crates/rethnet_evm/src/block/remote.rs @@ -2,7 +2,7 @@ use std::sync::{Arc, OnceLock}; use async_trait::async_trait; use rethnet_eth::{ - block::Header, + block::{BlobGas, Header}, receipt::BlockReceipt, remote::{ eth::{self, TransactionConversionError}, @@ -56,48 +56,51 @@ pub struct RemoteBlock { impl RemoteBlock { /// Constructs a new instance with the provided JSON-RPC block and client. pub fn new( - transaction: eth::Block, + block: eth::Block, rpc_client: Arc, ) -> Result { let header = Header { - parent_hash: transaction.parent_hash, - ommers_hash: transaction.sha3_uncles, - beneficiary: transaction.miner.ok_or(CreationError::MissingMiner)?, - state_root: transaction.state_root, - transactions_root: transaction.transactions_root, - receipts_root: transaction.receipts_root, - logs_bloom: transaction.logs_bloom, - difficulty: transaction.difficulty, - number: transaction.number.ok_or(CreationError::MissingNumber)?, - gas_limit: transaction.gas_limit, - gas_used: transaction.gas_used, - timestamp: transaction.timestamp, - extra_data: transaction.extra_data, - mix_hash: transaction.mix_hash, - nonce: B64::from_limbs([transaction - .nonce - .ok_or(CreationError::MissingNonce)? - .to_be()]), - base_fee_per_gas: transaction.base_fee_per_gas, - withdrawals_root: transaction.withdrawals_root, + parent_hash: block.parent_hash, + ommers_hash: block.sha3_uncles, + beneficiary: block.miner.ok_or(CreationError::MissingMiner)?, + state_root: block.state_root, + transactions_root: block.transactions_root, + receipts_root: block.receipts_root, + logs_bloom: block.logs_bloom, + difficulty: block.difficulty, + number: block.number.ok_or(CreationError::MissingNumber)?, + gas_limit: block.gas_limit, + gas_used: block.gas_used, + timestamp: block.timestamp, + extra_data: block.extra_data, + mix_hash: block.mix_hash, + nonce: B64::from_limbs([block.nonce.ok_or(CreationError::MissingNonce)?.to_be()]), + base_fee_per_gas: block.base_fee_per_gas, + withdrawals_root: block.withdrawals_root, + blob_gas: block.blob_gas_used.and_then(|gas_used| { + block.excess_blob_gas.map(|excess_gas| BlobGas { + gas_used, + excess_gas, + }) + }), }; let (transactions, callers): (Vec, Vec
) = itertools::process_results( - transaction.transactions.into_iter().map(TryInto::try_into), + block.transactions.into_iter().map(TryInto::try_into), #[allow(clippy::redundant_closure_for_method_calls)] |iter| iter.unzip(), )?; - let hash = transaction.hash.ok_or(CreationError::MissingHash)?; + let hash = block.hash.ok_or(CreationError::MissingHash)?; Ok(Self { header, transactions, callers, receipts: OnceLock::new(), - _ommers: transaction.uncles, - _withdrawals: transaction.withdrawals, + _ommers: block.uncles, + _withdrawals: block.withdrawals, hash, rpc_client, }) diff --git a/crates/rethnet_evm/src/transaction.rs b/crates/rethnet_evm/src/transaction.rs index f91617e975..aba84f939c 100644 --- a/crates/rethnet_evm/src/transaction.rs +++ b/crates/rethnet_evm/src/transaction.rs @@ -40,7 +40,7 @@ where EVMError::Transaction(e) => Self::InvalidTransaction(e), EVMError::Header( InvalidHeader::ExcessBlobGasNotSet | InvalidHeader::PrevrandaoNotSet, - ) => unreachable!(), + ) => unreachable!("error: {error:?}"), EVMError::Database(DatabaseComponentError::State(e)) => Self::State(e), EVMError::Database(DatabaseComponentError::BlockHash(e)) => Self::Blockchain(e), } diff --git a/crates/rethnet_evm/src/transaction/pending.rs b/crates/rethnet_evm/src/transaction/pending.rs index 0aa809cac7..2c5463ae35 100644 --- a/crates/rethnet_evm/src/transaction/pending.rs +++ b/crates/rethnet_evm/src/transaction/pending.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use rethnet_eth::{ transaction::{ EIP1559SignedTransaction, EIP155SignedTransaction, EIP2930SignedTransaction, - LegacySignedTransaction, SignedTransaction, TransactionKind, + Eip4844SignedTransaction, LegacySignedTransaction, SignedTransaction, TransactionKind, }, Address, U256, }; @@ -47,7 +47,7 @@ impl PendingTransaction { transaction: SignedTransaction, caller: Address, ) -> Result> { - if transaction.kind() == &TransactionKind::Create && transaction.data().is_empty() { + if transaction.kind() == TransactionKind::Create && transaction.data().is_empty() { return Err(TransactionCreationError::ContractMissingData); } @@ -101,69 +101,69 @@ impl PendingTransaction { match spec_id { SpecId::FRONTIER | SpecId::FRONTIER_THAWING => initial_tx_gas::( transaction.data(), - transaction.kind() == &TransactionKind::Create, + transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), SpecId::HOMESTEAD | SpecId::DAO_FORK => initial_tx_gas::( transaction.data(), - transaction.kind() == &TransactionKind::Create, + transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), SpecId::TANGERINE => initial_tx_gas::( transaction.data(), - transaction.kind() == &TransactionKind::Create, + transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), SpecId::SPURIOUS_DRAGON => initial_tx_gas::( transaction.data(), - transaction.kind() == &TransactionKind::Create, + transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), SpecId::BYZANTIUM => initial_tx_gas::( transaction.data(), - transaction.kind() == &TransactionKind::Create, + transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), SpecId::PETERSBURG | SpecId::CONSTANTINOPLE => initial_tx_gas::( transaction.data(), - transaction.kind() == &TransactionKind::Create, + transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), SpecId::ISTANBUL | SpecId::MUIR_GLACIER => initial_tx_gas::( transaction.data(), - transaction.kind() == &TransactionKind::Create, + transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), SpecId::BERLIN => initial_tx_gas::( transaction.data(), - transaction.kind() == &TransactionKind::Create, + transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), SpecId::LONDON | SpecId::ARROW_GLACIER | SpecId::GRAY_GLACIER => { initial_tx_gas::( transaction.data(), - transaction.kind() == &TransactionKind::Create, + transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ) } SpecId::MERGE => initial_tx_gas::( transaction.data(), - transaction.kind() == &TransactionKind::Create, + transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), SpecId::SHANGHAI => initial_tx_gas::( transaction.data(), - transaction.kind() == &TransactionKind::Create, + transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), SpecId::CANCUN => initial_tx_gas::( transaction.data(), - transaction.kind() == &TransactionKind::Create, + transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), SpecId::LATEST => initial_tx_gas::( transaction.data(), - transaction.kind() == &TransactionKind::Create, + transaction.kind() == TransactionKind::Create, access_list.as_ref().map_or(&[], |access_list| access_list), ), } @@ -267,6 +267,32 @@ impl From for TxEnv { blob_hashes: Vec::new(), max_fee_per_blob_gas: None, }, + SignedTransaction::Eip4844(Eip4844SignedTransaction { + nonce, + max_priority_fee_per_gas, + max_fee_per_gas, + max_fee_per_blob_gas, + gas_limit, + to, + value, + input, + access_list, + blob_hashes, + .. + }) => Self { + caller: transaction.caller, + gas_limit, + gas_price: max_fee_per_gas, + transact_to: TransactTo::Call(to), + value, + data: input, + nonce: Some(nonce), + chain_id, + access_list: access_list.into(), + gas_priority_fee: Some(max_priority_fee_per_gas), + blob_hashes, + max_fee_per_blob_gas: Some(max_fee_per_blob_gas), + }, } } } diff --git a/crates/rethnet_evm_napi/src/block.rs b/crates/rethnet_evm_napi/src/block.rs index 9c18aa7a7f..a526ee4653 100644 --- a/crates/rethnet_evm_napi/src/block.rs +++ b/crates/rethnet_evm_napi/src/block.rs @@ -3,18 +3,19 @@ mod builder; use std::{mem, ops::Deref, sync::Arc}; use napi::{ - bindgen_prelude::{BigInt, Buffer, Either3}, + bindgen_prelude::{BigInt, Buffer, Either4}, Env, JsBuffer, JsBufferValue, Status, }; use napi_derive::napi; use rethnet_eth::{Address, Bloom, Bytes, B256, B64}; -use rethnet_evm::{blockchain::BlockchainError, BlockEnv, SyncBlock}; +use rethnet_evm::{blockchain::BlockchainError, BlobExcessGasAndPrice, BlockEnv, SyncBlock}; use crate::{ cast::TryCast, receipt::Receipt, transaction::signed::{ - EIP1559SignedTransaction, EIP2930SignedTransaction, LegacySignedTransaction, + EIP1559SignedTransaction, EIP2930SignedTransaction, Eip4844SignedTransaction, + LegacySignedTransaction, }, }; @@ -30,6 +31,7 @@ pub struct BlockConfig { pub base_fee: Option, pub gas_limit: Option, pub parent_hash: Option, + pub blob_excess_gas: Option, } impl TryFrom for BlockEnv { @@ -56,6 +58,8 @@ impl TryFrom for BlockEnv { let gas_limit = value .gas_limit .map_or(Ok(default.gas_limit), TryCast::try_cast)?; + let blob_excess_gas: Option = + value.blob_excess_gas.map(TryCast::try_cast).transpose()?; Ok(Self { number, @@ -65,9 +69,7 @@ impl TryFrom for BlockEnv { prevrandao, basefee, gas_limit, - // TODO: Add support for EIP-4844 - // https://github.com/NomicFoundation/edr/issues/191 - blob_excess_gas_and_price: None, + blob_excess_gas_and_price: blob_excess_gas.map(BlobExcessGasAndPrice::new), }) } } @@ -159,6 +161,17 @@ impl TryFrom for rethnet_eth::block::BlockOptions { } } +/// Information about the blob gas used in a block. +#[napi(object)] +pub struct BlobGas { + /// The total amount of blob gas consumed by the transactions within the block. + pub gas_used: BigInt, + /// The running total of blob gas consumed in excess of the target, prior to + /// the block. Blocks with above-target blob gas consumption increase this value, + /// blocks with below-target blob gas consumption decrease it (bounded at 0). + pub excess_gas: BigInt, +} + #[napi(object)] pub struct BlockHeader { pub parent_hash: Buffer, @@ -178,6 +191,7 @@ pub struct BlockHeader { pub nonce: Buffer, pub base_fee_per_gas: Option, pub withdrawals_root: Option, + pub blob_gas: Option, } impl BlockHeader { @@ -233,6 +247,10 @@ impl BlockHeader { withdrawals_root: header .withdrawals_root .map(|root| Buffer::from(root.as_bytes())), + blob_gas: header.blob_gas.as_ref().map(|blob_gas| BlobGas { + gas_used: BigInt::from(blob_gas.gas_used), + excess_gas: BigInt::from(blob_gas.excess_gas), + }), }) } } @@ -270,6 +288,7 @@ impl TryFrom for rethnet_eth::block::Header { .withdrawals_root .map(TryCast::::try_cast) .transpose()?, + blob_gas: None, }) } } @@ -295,6 +314,12 @@ impl Deref for Block { #[napi] impl Block { + #[doc = "Retrieves the block's hash, potentially calculating it in the process."] + #[napi] + pub fn hash(&self) -> Buffer { + Buffer::from(self.inner.hash().as_bytes()) + } + #[doc = "Retrieves the block's header."] #[napi(getter)] pub fn header(&self, env: Env) -> napi::Result { @@ -309,23 +334,33 @@ impl Block { ) -> napi::Result< // HACK: napi does not convert Rust type aliases to its underlaying types when generating bindings // so manually do that here - Vec>, + Vec< + Either4< + LegacySignedTransaction, + EIP2930SignedTransaction, + EIP1559SignedTransaction, + Eip4844SignedTransaction, + >, + >, > { self.inner .transactions() .iter() .map(|transaction| match transaction { rethnet_eth::transaction::SignedTransaction::PreEip155Legacy(transaction) => { - LegacySignedTransaction::from_legacy(&env, transaction).map(Either3::A) + LegacySignedTransaction::from_legacy(&env, transaction).map(Either4::A) } rethnet_eth::transaction::SignedTransaction::PostEip155Legacy(transaction) => { - LegacySignedTransaction::from_eip155(&env, transaction).map(Either3::A) + LegacySignedTransaction::from_eip155(&env, transaction).map(Either4::A) } rethnet_eth::transaction::SignedTransaction::Eip2930(transaction) => { - EIP2930SignedTransaction::new(&env, transaction).map(Either3::B) + EIP2930SignedTransaction::new(&env, transaction).map(Either4::B) } rethnet_eth::transaction::SignedTransaction::Eip1559(transaction) => { - EIP1559SignedTransaction::new(&env, transaction).map(Either3::C) + EIP1559SignedTransaction::new(&env, transaction).map(Either4::C) + } + rethnet_eth::transaction::SignedTransaction::Eip4844(transaction) => { + Eip4844SignedTransaction::new(&env, transaction).map(Either4::D) } }) .collect() diff --git a/crates/rethnet_evm_napi/src/transaction/pending.rs b/crates/rethnet_evm_napi/src/transaction/pending.rs index 0d9e3b57f9..f3bdc3b53e 100644 --- a/crates/rethnet_evm_napi/src/transaction/pending.rs +++ b/crates/rethnet_evm_napi/src/transaction/pending.rs @@ -1,7 +1,7 @@ use std::ops::Deref; use napi::{ - bindgen_prelude::{Buffer, Either3}, + bindgen_prelude::{Buffer, Either4}, tokio::runtime, Env, JsObject, }; @@ -10,7 +10,10 @@ use rethnet_eth::Address; use crate::{cast::TryCast, config::SpecId, state::State}; -use super::signed::{EIP1559SignedTransaction, EIP2930SignedTransaction, LegacySignedTransaction}; +use super::signed::{ + EIP1559SignedTransaction, EIP2930SignedTransaction, Eip4844SignedTransaction, + LegacySignedTransaction, +}; #[napi] pub struct PendingTransaction { @@ -33,14 +36,15 @@ impl PendingTransaction { env: Env, state_manager: &State, spec_id: SpecId, - transaction: Either3< + transaction: Either4< LegacySignedTransaction, EIP2930SignedTransaction, EIP1559SignedTransaction, + Eip4844SignedTransaction, >, caller: Option, ) -> napi::Result { - let transaction = transaction.try_cast()?; + let transaction: rethnet_eth::transaction::SignedTransaction = transaction.try_cast()?; let spec_id: rethnet_evm::SpecId = spec_id.into(); let state = (*state_manager).clone(); @@ -79,20 +83,28 @@ impl PendingTransaction { ) -> napi::Result< // HACK: napi does not convert Rust type aliases to its underlaying types when generating bindings // so manually do that here - Either3, + Either4< + LegacySignedTransaction, + EIP2930SignedTransaction, + EIP1559SignedTransaction, + Eip4844SignedTransaction, + >, > { match &*self.inner { rethnet_eth::transaction::SignedTransaction::PreEip155Legacy(transaction) => { - LegacySignedTransaction::from_legacy(&env, transaction).map(Either3::A) + LegacySignedTransaction::from_legacy(&env, transaction).map(Either4::A) } rethnet_eth::transaction::SignedTransaction::PostEip155Legacy(transaction) => { - LegacySignedTransaction::from_eip155(&env, transaction).map(Either3::A) + LegacySignedTransaction::from_eip155(&env, transaction).map(Either4::A) } rethnet_eth::transaction::SignedTransaction::Eip2930(transaction) => { - EIP2930SignedTransaction::new(&env, transaction).map(Either3::B) + EIP2930SignedTransaction::new(&env, transaction).map(Either4::B) } rethnet_eth::transaction::SignedTransaction::Eip1559(transaction) => { - EIP1559SignedTransaction::new(&env, transaction).map(Either3::C) + EIP1559SignedTransaction::new(&env, transaction).map(Either4::C) + } + rethnet_eth::transaction::SignedTransaction::Eip4844(transaction) => { + Eip4844SignedTransaction::new(&env, transaction).map(Either4::D) } } } diff --git a/crates/rethnet_evm_napi/src/transaction/signed.rs b/crates/rethnet_evm_napi/src/transaction/signed.rs index 8f7fecee48..af6816c972 100644 --- a/crates/rethnet_evm_napi/src/transaction/signed.rs +++ b/crates/rethnet_evm_napi/src/transaction/signed.rs @@ -1,25 +1,30 @@ -use napi::bindgen_prelude::Either3; - mod eip1559; mod eip2930; +mod eip4844; mod legacy; +use napi::bindgen_prelude::Either4; + use crate::cast::TryCast; pub use self::{ eip1559::EIP1559SignedTransaction, eip2930::EIP2930SignedTransaction, - legacy::LegacySignedTransaction, + eip4844::Eip4844SignedTransaction, legacy::LegacySignedTransaction, }; -pub type SignedTransaction = - Either3; +pub type SignedTransaction = Either4< + LegacySignedTransaction, + EIP2930SignedTransaction, + EIP1559SignedTransaction, + Eip4844SignedTransaction, +>; impl TryCast for SignedTransaction { type Error = napi::Error; fn try_cast(self) -> Result { Ok(match self { - Either3::A(transaction) => { + Either4::A(transaction) => { let v: u64 = transaction.signature.v.clone().try_cast()?; if v >= 35 { @@ -32,12 +37,15 @@ impl TryCast for SignedTransaction ) } } - Either3::B(transaction) => { + Either4::B(transaction) => { rethnet_eth::transaction::SignedTransaction::Eip2930(transaction.try_into()?) } - Either3::C(transaction) => { + Either4::C(transaction) => { rethnet_eth::transaction::SignedTransaction::Eip1559(transaction.try_into()?) } + Either4::D(transaction) => { + rethnet_eth::transaction::SignedTransaction::Eip4844(transaction.try_into()?) + } }) } } diff --git a/crates/rethnet_evm_napi/src/transaction/signed/eip4844.rs b/crates/rethnet_evm_napi/src/transaction/signed/eip4844.rs new file mode 100644 index 0000000000..792a42c530 --- /dev/null +++ b/crates/rethnet_evm_napi/src/transaction/signed/eip4844.rs @@ -0,0 +1,127 @@ +use std::{mem, sync::OnceLock}; + +use napi::{ + bindgen_prelude::{BigInt, Buffer}, + Env, JsBuffer, JsBufferValue, +}; +use napi_derive::napi; +use rethnet_eth::{Address, Bytes}; + +use crate::{access_list::AccessListItem, cast::TryCast}; + +#[napi(object)] +pub struct Eip4844SignedTransaction { + pub chain_id: BigInt, + pub nonce: BigInt, + pub max_priority_fee_per_gas: BigInt, + pub max_fee_per_gas: BigInt, + pub max_fee_per_blob_gas: BigInt, + pub gas_limit: BigInt, + /// 160-bit address for receiver + pub to: Buffer, + pub value: BigInt, + pub input: JsBuffer, + pub access_list: Vec, + pub blob_hashes: Vec, + pub odd_y_parity: bool, + pub r: BigInt, + pub s: BigInt, +} + +impl Eip4844SignedTransaction { + /// Constructs a [`Eip4844SignedTransaction`] instance. + pub fn new( + env: &Env, + transaction: &rethnet_eth::transaction::Eip4844SignedTransaction, + ) -> napi::Result { + let input = transaction.input.clone(); + let input = unsafe { + env.create_buffer_with_borrowed_data( + input.as_ptr(), + input.len(), + input, + |input: Bytes, _env| { + mem::drop(input); + }, + ) + } + .map(JsBufferValue::into_raw)?; + + Ok(Self { + chain_id: BigInt::from(transaction.chain_id), + nonce: BigInt::from(transaction.nonce), + max_priority_fee_per_gas: BigInt { + sign_bit: false, + words: transaction.max_priority_fee_per_gas.as_limbs().to_vec(), + }, + max_fee_per_gas: BigInt { + sign_bit: false, + words: transaction.max_fee_per_gas.as_limbs().to_vec(), + }, + max_fee_per_blob_gas: BigInt { + sign_bit: false, + words: transaction.max_fee_per_blob_gas.as_limbs().to_vec(), + }, + gas_limit: BigInt::from(transaction.gas_limit), + to: Buffer::from(transaction.to.as_bytes()), + value: BigInt { + sign_bit: false, + words: transaction.value.as_limbs().to_vec(), + }, + input, + access_list: transaction + .access_list + .0 + .iter() + .map(AccessListItem::from) + .collect(), + blob_hashes: transaction + .blob_hashes + .iter() + .map(|hash| Buffer::from(hash.as_bytes())) + .collect(), + odd_y_parity: transaction.odd_y_parity, + r: BigInt { + sign_bit: false, + words: transaction.r.as_limbs().to_vec(), + }, + s: BigInt { + sign_bit: false, + words: transaction.s.as_limbs().to_vec(), + }, + }) + } +} + +impl TryFrom for rethnet_eth::transaction::Eip4844SignedTransaction { + type Error = napi::Error; + + fn try_from(value: Eip4844SignedTransaction) -> Result { + Ok(Self { + chain_id: value.chain_id.try_cast()?, + nonce: value.nonce.try_cast()?, + max_priority_fee_per_gas: value.max_priority_fee_per_gas.try_cast()?, + max_fee_per_gas: value.max_fee_per_gas.try_cast()?, + max_fee_per_blob_gas: value.max_fee_per_blob_gas.try_cast()?, + gas_limit: value.gas_limit.try_cast()?, + to: Address::from_slice(&value.to), + value: value.value.try_cast()?, + input: Bytes::copy_from_slice(value.input.into_value()?.as_ref()), + access_list: value + .access_list + .into_iter() + .map(TryFrom::try_from) + .collect::, _>>()? + .into(), + blob_hashes: value + .blob_hashes + .into_iter() + .map(Buffer::try_cast) + .collect::>()?, + odd_y_parity: value.odd_y_parity, + r: value.r.try_cast()?, + s: value.s.try_cast()?, + hash: OnceLock::new(), + }) + } +} diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/context/dual.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/context/dual.ts index ca6d29b488..c6d98b3c62 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/context/dual.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/context/dual.ts @@ -1,5 +1,6 @@ import cloneDeep from "lodash/cloneDeep"; import { timestampSecondsToDate } from "../../../util/date"; +import { HardforkName } from "../../../util/hardforks"; import { DualBlockMiner } from "../miner/dual"; import { DualMemPool } from "../mem-pool/dual"; import { makeCommon } from "../utils/makeCommon"; @@ -31,6 +32,12 @@ export class DualEthContext implements EthContextAdapter { // To avoid this from affecting the original config object, we clone it first. const tempConfig = cloneDeep(config); + // When transient storage is enabled, we want to use Cancun. However, as Shanghai is + // the latest supported hardfork by ethereumJS, we designate that. + if (tempConfig.enableTransientStorage) { + tempConfig.hardfork = HardforkName.SHANGHAI; + } + const common = makeCommon(tempConfig); const hardhat = await HardhatEthContext.create( diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/context/rethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/context/rethnet.ts index b7932567e7..fa9eb9b6cb 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/context/rethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/context/rethnet.ts @@ -38,7 +38,6 @@ export class RethnetEthContext implements EthContextAdapter { public static async create(config: NodeConfig): Promise { const common = makeCommon(config); - const hardforkName = getHardforkName(config.hardfork); const prevRandaoGenerator = RandomBufferGenerator.create("randomMixHashSeed"); @@ -46,6 +45,10 @@ export class RethnetEthContext implements EthContextAdapter { let blockchain: RethnetBlockchain; let state: RethnetStateManager; + const specId = config.enableTransientStorage + ? SpecId.Cancun + : ethereumsjsHardforkToRethnetSpecId(getHardforkName(config.hardfork)); + if (isForkedNodeConfig(config)) { const chainIdToHardforkActivations: Array< [bigint, Array<[bigint, SpecId]>] @@ -65,7 +68,7 @@ export class RethnetEthContext implements EthContextAdapter { blockchain = new RethnetBlockchain( await Blockchain.fork( globalRethnetContext, - ethereumsjsHardforkToRethnetSpecId(hardforkName), + specId, config.forkConfig.jsonRpcUrl, config.forkConfig.blockNumber !== undefined ? BigInt(config.forkConfig.blockNumber) @@ -99,17 +102,16 @@ export class RethnetEthContext implements EthContextAdapter { ? BigInt(config.initialBaseFeePerGas) : BigInt(HARDHAT_NETWORK_DEFAULT_INITIAL_BASE_FEE_PER_GAS); - const genesisBlockBaseFeePerGas = hardforkGte( - hardforkName, - HardforkName.LONDON - ) - ? initialBaseFeePerGas - : undefined; + const genesisBlockBaseFeePerGas = + specId >= SpecId.London ? initialBaseFeePerGas : undefined; const genesisBlockHeader = makeGenesisBlock( config, await state.getStateRoot(), - hardforkName, + // HardforkName.CANCUN is not supported yet, so use SHANGHAI instead + config.enableTransientStorage + ? HardforkName.SHANGHAI + : getHardforkName(config.hardfork), prevRandaoGenerator, genesisBlockBaseFeePerGas ); @@ -117,7 +119,7 @@ export class RethnetEthContext implements EthContextAdapter { blockchain = new RethnetBlockchain( Blockchain.withGenesisBlock( common.chainId(), - ethereumsjsHardforkToRethnetSpecId(hardforkName), + specId, ethereumjsHeaderDataToRethnetBlockOptions(genesisBlockHeader), config.genesisAccounts.map((account) => { return { @@ -137,13 +139,14 @@ export class RethnetEthContext implements EthContextAdapter { blockchain.asInner(), state, common, - limitContractCodeSize + limitContractCodeSize, + config.enableTransientStorage ); const memPool = new RethnetMemPool( BigInt(config.blockGasLimit), state, - hardforkName + specId ); const miner = new RethnetMiner( diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/mem-pool/rethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/mem-pool/rethnet.ts index 9bd68b90e3..c515bf6643 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/mem-pool/rethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/mem-pool/rethnet.ts @@ -1,6 +1,6 @@ import { Address } from "@nomicfoundation/ethereumjs-util"; import { TypedTransaction } from "@nomicfoundation/ethereumjs-tx"; -import { MemPool, PendingTransaction } from "rethnet-evm"; +import { MemPool, PendingTransaction, SpecId } from "rethnet-evm"; import { MemPoolAdapter } from "../mem-pool"; import { ethereumjsTransactionToRethnetSignedTransaction, @@ -21,7 +21,7 @@ export class RethnetMemPool implements MemPoolAdapter { constructor( blockGasLimit: bigint, private readonly _stateManager: RethnetStateManager, - private readonly _hardfork: HardforkName + private readonly _specId: SpecId ) { this._memPool = new MemPool(blockGasLimit); } @@ -52,12 +52,10 @@ export class RethnetMemPool implements MemPoolAdapter { const rethnetTx = ethereumjsTransactionToRethnetSignedTransaction(transaction); - const specId = ethereumsjsHardforkToRethnetSpecId(this._hardfork); - try { const pendingTransaction = await PendingTransaction.create( this._stateManager.asInner(), - specId, + this._specId, rethnetTx, caller ); diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/miner.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/miner.ts index 5fcde7c2e9..1603074f9d 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/miner.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/miner.ts @@ -5,6 +5,7 @@ import { PartialTrace, RunBlockResult } from "./vm/vm-adapter"; export interface PartialMineBlockResult { block: Block; blockResult: RunBlockResult; + totalDifficultyAfterBlock: bigint; traces: PartialTrace[]; } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/miner/hardhat.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/miner/hardhat.ts index d740f237ae..c21b933d1d 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/miner/hardhat.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/miner/hardhat.ts @@ -3,6 +3,7 @@ import { Common } from "@nomicfoundation/ethereumjs-common"; import { TypedTransaction } from "@nomicfoundation/ethereumjs-tx"; import { Address } from "@nomicfoundation/ethereumjs-util"; import { TxReceipt } from "@nomicfoundation/ethereumjs-vm"; +import { assertHardhatInvariant } from "../../../core/errors"; import { HardforkName, hardforkGte } from "../../../util/hardforks"; import { HardhatBlockchainInterface } from "../types/HardhatBlockchainInterface"; import { RandomBufferGenerator } from "../utils/random"; @@ -113,6 +114,14 @@ export class HardhatBlockMiner implements BlockMinerAdapter { await this._memPool.update(); + const totalDifficultyAfterBlock = + await this._blockchain.getTotalDifficultyByHash(block.hash()); + + assertHardhatInvariant( + totalDifficultyAfterBlock !== undefined, + "the total difficulty of the mined block should be defined" + ); + return { block, blockResult: { @@ -123,6 +132,7 @@ export class HardhatBlockMiner implements BlockMinerAdapter { receiptsRoot: block.header.receiptTrie, gasUsed: block.header.gasUsed, }, + totalDifficultyAfterBlock, traces, }; } catch (err) { diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/miner/rethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/miner/rethnet.ts index e60b96e34c..6367359ee4 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/miner/rethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/miner/rethnet.ts @@ -1,5 +1,6 @@ import { Common } from "@nomicfoundation/ethereumjs-common"; import { Address } from "@nomicfoundation/ethereumjs-util"; +import { assertHardhatInvariant } from "../../../core/errors"; import { ConfigOptions, MineOrdering, SpecId, mineBlock } from "rethnet-evm"; import { BlockMinerAdapter, PartialMineBlockResult } from "../miner"; import { @@ -90,6 +91,14 @@ export class RethnetMiner implements BlockMinerAdapter { const receipts = await mineResult.block.receipts(); + const totalDifficultyAfterBlock = + await this._blockchain.getTotalDifficultyByHash(mineResult.block.hash()); + + assertHardhatInvariant( + totalDifficultyAfterBlock !== undefined, + "the total difficulty of the mined block should be defined" + ); + return { block: rethnetBlockToEthereumJS(mineResult.block, common), blockResult: { @@ -107,6 +116,7 @@ export class RethnetMiner implements BlockMinerAdapter { receiptsRoot: mineResult.block.header.receiptsRoot, gasUsed: mineResult.block.header.gasUsed, }, + totalDifficultyAfterBlock, traces, }; } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts index 37d15bea98..579f6a27cc 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts @@ -399,7 +399,8 @@ export class HardhatNode extends EventEmitter { const result = await this._finalizeBlockResult(partialResult); await this._saveBlockAsSuccessfullyRun( result.block, - result.blockResult.results + result.blockResult.results, + partialResult.totalDifficultyAfterBlock ); if (needsTimestampIncrease) { @@ -1239,11 +1240,9 @@ export class HardhatNode extends EventEmitter { ) { const tx = await this._getTransactionForCall(call, blockNumberOrPending); - const blockContext = await this._getBlockContextForCall( - blockNumberOrPending - ); + const blockNumber = await this._getBlockNumberForCall(blockNumberOrPending); - return this._context.vm().traceCall(tx, blockContext, traceConfig); + return this._context.vm().traceCall(tx, blockNumber, traceConfig); } public async getFeeHistory( @@ -1830,7 +1829,8 @@ export class HardhatNode extends EventEmitter { */ private async _saveBlockAsSuccessfullyRun( block: Block, - transactionResults: RunTxResult[] + transactionResults: RunTxResult[], + totalDifficulty: bigint ) { const receipts = getRpcReceiptOutputsFromLocalBlockExecution( block, @@ -1838,13 +1838,6 @@ export class HardhatNode extends EventEmitter { shouldShowTransactionTypeForHardfork(this._common) ); - const td = await this.getBlockTotalDifficulty(block); - - assertHardhatInvariant( - td !== undefined, - "_saveBlockAsSuccessfullyRun should only be called after having inserted the block" - ); - const rpcLogs: RpcLogOutput[] = []; for (const receipt of receipts) { rpcLogs.push(...receipt.logs); @@ -1863,7 +1856,7 @@ export class HardhatNode extends EventEmitter { filter.id, getRpcBlock( block, - td, + totalDifficulty, shouldShowTransactionTypeForHardfork(this._common), false ) @@ -2083,13 +2076,11 @@ export class HardhatNode extends EventEmitter { forceBaseFeeZero = false, stateOverrideSet: StateOverrideSet = {} ): Promise { - const blockContext = await this._getBlockContextForCall( - blockNumberOrPending - ); + const blockNumber = await this._getBlockNumberForCall(blockNumberOrPending); const result = await this._context .vm() - .dryRun(tx, blockContext, forceBaseFeeZero, stateOverrideSet); + .dryRun(tx, blockNumber, forceBaseFeeZero, stateOverrideSet); return result; } @@ -2323,29 +2314,14 @@ export class HardhatNode extends EventEmitter { return this._getFakeTransaction(txParams); } - private async _getBlockContextForCall( + private async _getBlockNumberForCall( blockNumberOrPending: bigint | "pending" - ): Promise { - let blockContext: Block | undefined; - + ): Promise { if (blockNumberOrPending === "pending") { // the new block has already been mined by _runInBlockContext hence we take latest here - blockContext = await this.getLatestBlock(); + return this.getLatestBlockNumber(); } else { - // We know that this block number exists, because otherwise - // there would be an error in the RPC layer. - const block = await this.getBlockByNumber(blockNumberOrPending); - assertHardhatInvariant( - block !== undefined, - "Tried to run a tx in the context of a non-existent block" - ); - - blockContext = block; - - // we don't need to add the tx to the block because runTx doesn't - // know anything about the txs in the current block + return blockNumberOrPending; } - - return blockContext; } } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToRethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToRethnet.ts index ad30345619..29a9afe2cc 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToRethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToRethnet.ts @@ -187,6 +187,10 @@ export function rethnetSpecIdToEthereumHardfork(specId: SpecId): HardforkName { return HardforkName.MERGE; case SpecId.Shanghai: return HardforkName.SHANGHAI; + // HACK: EthereumJS doesn't support Cancun, so report Shanghai + case SpecId.Cancun: + return HardforkName.SHANGHAI; + default: throw new Error(`Unknown spec id '${specId}', this shouldn't happen`); } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/creation.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/creation.ts index 45483bcb3c..3a0b14c826 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/creation.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/creation.ts @@ -16,14 +16,6 @@ export async function createContext( const prevRandaoGenerator = RandomBufferGenerator.create("randomMixHashSeed"); - // throw if transient storage is enabled and the mode is not ethereumjs - if (config.enableTransientStorage && vmModeEnvVar !== "ethereumjs") { - // eslint-disable-next-line @nomicfoundation/hardhat-internal-rules/only-hardhat-error - throw new Error( - "Transient storage is only supported in ethereumjs mode. Please set HARDHAT_EXPERIMENTAL_VM_MODE=ethereumjs" - ); - } - if (vmModeEnvVar === "ethereumjs") { return HardhatEthContext.create(config, prevRandaoGenerator); } else if (vmModeEnvVar === "rethnet") { diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts index dd2398b8e0..d6e85e1fc7 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/dual.ts @@ -61,7 +61,7 @@ export class DualModeAdapter implements VMAdapter { public async dryRun( tx: TypedTransaction, - blockContext: Block, + blockNumber: bigint, forceBaseFeeZero?: boolean, stateOverrideSet: StateOverrideSet = {} ): Promise { @@ -69,13 +69,13 @@ export class DualModeAdapter implements VMAdapter { const [ethereumJSResult, rethnetResult] = await Promise.all([ this._ethereumJSAdapter.dryRun( tx, - blockContext, + blockNumber, forceBaseFeeZero, stateOverrideSet ), this._rethnetAdapter.dryRun( tx, - blockContext, + blockNumber, forceBaseFeeZero, stateOverrideSet ), @@ -219,10 +219,10 @@ export class DualModeAdapter implements VMAdapter { public async traceCall( tx: TypedTransaction, - block: Block, + blockNumber: bigint, config: RpcDebugTracingConfig ): Promise { - return this._ethereumJSAdapter.traceCall(tx, block, config); + return this._ethereumJSAdapter.traceCall(tx, blockNumber, config); } public async setBlockContext( diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts index 35bc1d630e..67b772283b 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts @@ -118,6 +118,7 @@ export class EthereumJSAdapter implements VMAdapter { constructor( private readonly _vm: VM, + private readonly _blockchain: HardhatBlockchainInterface, public readonly _stateManager: StateManagerWithAddresses, private readonly _common: Common, private readonly _configNetworkId: number, @@ -205,6 +206,7 @@ export class EthereumJSAdapter implements VMAdapter { return new EthereumJSAdapter( vm, + blockchain, stateManager, common, config.networkId, @@ -218,10 +220,21 @@ export class EthereumJSAdapter implements VMAdapter { public async dryRun( tx: TypedTransaction, - blockContext: Block, + blockNumber: bigint, forceBaseFeeZero = false, stateOverrideSet: StateOverrideSet = {} ): Promise { + // We know that this block number exists, because otherwise + // there would be an error in the RPC layer. + let blockContext = await this._blockchain.getBlockByNumber(blockNumber); + assertHardhatInvariant( + blockContext !== undefined, + "Tried to run a tx in the context of a non-existent block" + ); + + // we don't need to add the tx to the block because runTx doesn't + // know anything about the txs in the current block + const initialStateRoot = await this.getStateRoot(); await this._applyStateOverrideSet(stateOverrideSet); @@ -477,14 +490,14 @@ export class EthereumJSAdapter implements VMAdapter { } public async traceCall( tx: TypedTransaction, - block: Block, + blockNumber: bigint, traceConfig: RpcDebugTracingConfig ): Promise { const vmDebugTracer = new VMDebugTracer(this._vm); return vmDebugTracer.trace(async () => { const forceBaseFeeZero = true; - await this.dryRun(tx, block, forceBaseFeeZero); + await this.dryRun(tx, blockNumber, forceBaseFeeZero); }, traceConfig); } diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts index 77c02793e1..f64776575f 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/exit.ts @@ -31,6 +31,8 @@ export class Exit { case ExceptionalHalt.OpcodeNotFound: case ExceptionalHalt.InvalidFEOpcode: + // Returned when an opcode is not implemented for the hardfork + case ExceptionalHalt.NotActivated: return new Exit(ExitCode.INVALID_OPCODE); case ExceptionalHalt.StackUnderflow: diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts index 0e8e8243c8..0e38e6743b 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/rethnet.ts @@ -31,6 +31,7 @@ import { import { keccak256 } from "../../../util/keccak"; import { RpcDebugTraceOutput } from "../output"; import { RethnetStateManager } from "../RethnetState"; +import { assertHardhatInvariant } from "../../../core/errors"; import { StateOverrideSet } from "../../../core/jsonrpc/types/input/callRequest"; import { RpcDebugTracingConfig } from "../../../core/jsonrpc/types/input/debugTraceTransaction"; import { MessageTrace } from "../../stack-traces/message-trace"; @@ -52,7 +53,8 @@ export class RethnetAdapter implements VMAdapter { private _blockchain: Blockchain, private _state: RethnetStateManager, private readonly _common: Common, - private readonly _limitContractCodeSize: bigint | undefined + private readonly _limitContractCodeSize: bigint | undefined, + private readonly _enableTransientStorage: boolean ) { this._vmTracer = new VMTracer(_common, false); } @@ -79,7 +81,13 @@ export class RethnetAdapter implements VMAdapter { const limitContractCodeSize = config.allowUnlimitedContractSize === true ? 2n ** 64n - 1n : undefined; - return new RethnetAdapter(blockchain, state, common, limitContractCodeSize); + return new RethnetAdapter( + blockchain, + state, + common, + limitContractCodeSize, + config.enableTransientStorage + ); } /** @@ -87,7 +95,7 @@ export class RethnetAdapter implements VMAdapter { */ public async dryRun( tx: TypedTransaction, - blockContext: Block, + blockNumber: bigint, forceBaseFeeZero?: boolean, stateOverrideSet: StateOverrideSet = {} ): Promise { @@ -96,6 +104,17 @@ export class RethnetAdapter implements VMAdapter { throw new Error("state override not implemented for EDR"); } + // We know that this block number exists, because otherwise + // there would be an error in the RPC layer. + const blockContext = await this._blockchain.blockByNumber(blockNumber); + assertHardhatInvariant( + blockContext !== null, + "Tried to run a tx in the context of a non-existent block" + ); + + // we don't need to add the tx to the block because runTx doesn't + // know anything about the txs in the current block + const rethnetTx = ethereumjsTransactionToRethnetTransactionRequest(tx); const difficulty = this._getBlockEnvDifficulty( @@ -112,7 +131,8 @@ export class RethnetAdapter implements VMAdapter { ); const config: ConfigOptions = { chainId: this._common.chainId(), - specId, + // Enable Cancun if transient storage is enabled + specId: this._enableTransientStorage ? SpecId.Cancun : specId, limitContractCodeSize: this._limitContractCodeSize, disableBlockGasLimit: true, disableEip3607: true, @@ -125,13 +145,14 @@ export class RethnetAdapter implements VMAdapter { rethnetTx, { number: blockContext.header.number, - beneficiary: blockContext.header.coinbase.buf, + beneficiary: blockContext.header.beneficiary, timestamp: blockContext.header.timestamp, baseFee: forceBaseFeeZero === true ? 0n : blockContext.header.baseFeePerGas, gasLimit: blockContext.header.gasLimit, difficulty, mixHash: prevRandao, + blobExcessGas: blockContext.header.blobGas?.excessGas, }, true ); @@ -414,7 +435,8 @@ export class RethnetAdapter implements VMAdapter { ); const evmConfig: ConfigOptions = { chainId: this._common.chainId(), - specId, + // Enable Cancun if transient storage is enabled + specId: this._enableTransientStorage ? SpecId.Cancun : specId, limitContractCodeSize: this._limitContractCodeSize, disableBlockGasLimit: false, disableEip3607: true, @@ -453,7 +475,7 @@ export class RethnetAdapter implements VMAdapter { public async traceCall( tx: TypedTransaction, - block: Block, + blockNumber: bigint, config: RpcDebugTracingConfig ): Promise { throw new Error("traceCall not implemented for Rethnet"); diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts index bc5a6303cf..86a3675bde 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/vm/vm-adapter.ts @@ -40,7 +40,7 @@ export interface RunBlockResult { export interface VMAdapter { dryRun( tx: TypedTransaction, - blockContext: Block, + blockNumber: bigint, forceBaseFeeZero?: boolean, stateOverrideSet?: StateOverrideSet ): Promise; @@ -79,7 +79,7 @@ export interface VMAdapter { ): Promise; traceCall( tx: TypedTransaction, - block: Block, + blockNumber: bigint, traceConfig: RpcDebugTracingConfig ): Promise; diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts index 5bc55fa6f1..9ae10bccb0 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts @@ -1237,6 +1237,14 @@ describe("HardhatNode", () => { }); describe("When enabled", function () { + if ( + process.env.HARDHAT_EXPERIMENTAL_VM_MODE === undefined || + process.env.HARDHAT_EXPERIMENTAL_VM_MODE === "dual" + ) { + // disabled as Cancun is not supported in dual mode + return; + } + it("Should not revert if trying to run TLOAD in a tx", async function () { const [, hardhatNode] = await HardhatNode.create({ ...nodeConfig, diff --git a/yarn.lock b/yarn.lock index fc526ed8e4..acb6dc5bb0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1393,55 +1393,55 @@ mcl-wasm "^0.7.1" rustbn.js "~0.2.0" -"@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.0.tgz#83a7367342bd053a76d04bbcf4f373fef07cf760" - integrity sha512-vEF3yKuuzfMHsZecHQcnkUrqm8mnTWfJeEVFHpg+cO+le96xQA4lAJYdUan8pXZohQxv1fSReQsn4QGNuBNuCw== +"@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.1.tgz#4c858096b1c17fe58a474fe81b46815f93645c15" + integrity sha512-KcTodaQw8ivDZyF+D76FokN/HdpgGpfjc/gFCImdLUyqB6eSWVaZPazMbeAjmfhx3R0zm/NYVzxwAokFKgrc0w== -"@nomicfoundation/solidity-analyzer-darwin-x64@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.0.tgz#1225f7da647ae1ad25a87125664704ecc0af6ccc" - integrity sha512-dlHeIg0pTL4dB1l9JDwbi/JG6dHQaU1xpDK+ugYO8eJ1kxx9Dh2isEUtA4d02cQAl22cjOHTvifAk96A+ItEHA== +"@nomicfoundation/solidity-analyzer-darwin-x64@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.1.tgz#6e25ccdf6e2d22389c35553b64fe6f3fdaec432c" + integrity sha512-XhQG4BaJE6cIbjAVtzGOGbK3sn1BO9W29uhk9J8y8fZF1DYz0Doj8QDMfpMu+A6TjPDs61lbsmeYodIDnfveSA== -"@nomicfoundation/solidity-analyzer-freebsd-x64@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.0.tgz#dbc052dcdfd50ae50fd5ae1788b69b4e0fa40040" - integrity sha512-WFCZYMv86WowDA4GiJKnebMQRt3kCcFqHeIomW6NMyqiKqhK1kIZCxSLDYsxqlx396kKLPN1713Q1S8tu68GKg== +"@nomicfoundation/solidity-analyzer-freebsd-x64@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.1.tgz#0a224ea50317139caeebcdedd435c28a039d169c" + integrity sha512-GHF1VKRdHW3G8CndkwdaeLkVBi5A9u2jwtlS7SLhBc8b5U/GcoL39Q+1CSO3hYqePNP+eV5YI7Zgm0ea6kMHoA== -"@nomicfoundation/solidity-analyzer-linux-arm64-gnu@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.0.tgz#e6b2eea633995b557e74e881d2a43eab4760903d" - integrity sha512-DTw6MNQWWlCgc71Pq7CEhEqkb7fZnS7oly13pujs4cMH1sR0JzNk90Mp1zpSCsCs4oKan2ClhMlLKtNat/XRKQ== +"@nomicfoundation/solidity-analyzer-linux-arm64-gnu@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.1.tgz#dfa085d9ffab9efb2e7b383aed3f557f7687ac2b" + integrity sha512-g4Cv2fO37ZsUENQ2vwPnZc2zRenHyAxHcyBjKcjaSmmkKrFr64yvzeNO8S3GBFCo90rfochLs99wFVGT/0owpg== -"@nomicfoundation/solidity-analyzer-linux-arm64-musl@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.0.tgz#af81107f5afa794f19988a368647727806e18dc4" - integrity sha512-wUpUnR/3GV5Da88MhrxXh/lhb9kxh9V3Jya2NpBEhKDIRCDmtXMSqPMXHZmOR9DfCwCvG6vLFPr/+YrPCnUN0w== +"@nomicfoundation/solidity-analyzer-linux-arm64-musl@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.1.tgz#c9e06b5d513dd3ab02a7ac069c160051675889a4" + integrity sha512-WJ3CE5Oek25OGE3WwzK7oaopY8xMw9Lhb0mlYuJl/maZVo+WtP36XoQTb7bW/i8aAdHW5Z+BqrHMux23pvxG3w== -"@nomicfoundation/solidity-analyzer-linux-x64-gnu@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.0.tgz#6877e1da1a06a9f08446070ab6e0a5347109f868" - integrity sha512-lR0AxK1x/MeKQ/3Pt923kPvwigmGX3OxeU5qNtQ9pj9iucgk4PzhbS3ruUeSpYhUxG50jN4RkIGwUMoev5lguw== +"@nomicfoundation/solidity-analyzer-linux-x64-gnu@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.1.tgz#8d328d16839e52571f72f2998c81e46bf320f893" + integrity sha512-5WN7leSr5fkUBBjE4f3wKENUy9HQStu7HmWqbtknfXkkil+eNWiBV275IOlpXku7v3uLsXTOKpnnGHJYI2qsdA== -"@nomicfoundation/solidity-analyzer-linux-x64-musl@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.0.tgz#bb6cd83a0c259eccef4183796b6329a66cf7ebd9" - integrity sha512-A1he/8gy/JeBD3FKvmI6WUJrGrI5uWJNr5Xb9WdV+DK0F8msuOqpEByLlnTdLkXMwW7nSl3awvLezOs9xBHJEg== +"@nomicfoundation/solidity-analyzer-linux-x64-musl@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.1.tgz#9b49d0634b5976bb5ed1604a1e1b736f390959bb" + integrity sha512-KdYMkJOq0SYPQMmErv/63CwGwMm5XHenEna9X9aB8mQmhDBrYrlAOSsIPgFCUSL0hjxE3xHP65/EPXR/InD2+w== -"@nomicfoundation/solidity-analyzer-win32-arm64-msvc@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.0.tgz#9d4bca1cc9a1333fde985675083b0b7d165f6076" - integrity sha512-7x5SXZ9R9H4SluJZZP8XPN+ju7Mx+XeUMWZw7ZAqkdhP5mK19I4vz3x0zIWygmfE8RT7uQ5xMap0/9NPsO+ykw== +"@nomicfoundation/solidity-analyzer-win32-arm64-msvc@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.1.tgz#e2867af7264ebbcc3131ef837878955dd6a3676f" + integrity sha512-VFZASBfl4qiBYwW5xeY20exWhmv6ww9sWu/krWSesv3q5hA0o1JuzmPHR4LPN6SUZj5vcqci0O6JOL8BPw+APg== -"@nomicfoundation/solidity-analyzer-win32-ia32-msvc@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.0.tgz#0db5bfc6aa952bea4098d8d2c8947b4e5c4337ee" - integrity sha512-m7w3xf+hnE774YRXu+2mGV7RiF3QJtUoiYU61FascCkQhX3QMQavh7saH/vzb2jN5D24nT/jwvaHYX/MAM9zUw== +"@nomicfoundation/solidity-analyzer-win32-ia32-msvc@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.1.tgz#0685f78608dd516c8cdfb4896ed451317e559585" + integrity sha512-JnFkYuyCSA70j6Si6cS1A9Gh1aHTEb8kOTBApp/c7NRTFGNMH8eaInKlyuuiIbvYFhlXW4LicqyYuWNNq9hkpQ== -"@nomicfoundation/solidity-analyzer-win32-x64-msvc@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.0.tgz#2e0f39a2924dcd77db6b419828595e984fabcb33" - integrity sha512-xCuybjY0sLJQnJhupiFAXaek2EqF0AP0eBjgzaalPXSNvCEN6ZYHvUzdA50ENDVeSYFXcUsYf3+FsD3XKaeptA== +"@nomicfoundation/solidity-analyzer-win32-x64-msvc@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.1.tgz#c9a44f7108646f083b82e851486e0f6aeb785836" + integrity sha512-HrVJr6+WjIXGnw3Q9u6KQcbZCtk0caVWhCdFADySvRyUxJ8PnzlaP+MhwNE8oyT8OZ6ejHBRrrgjSqDCFXGirw== "@nomicfoundation/solidity-analyzer@^0.1.0": version "0.1.1"