diff --git a/crates/edr_eth/src/remote/client.rs b/crates/edr_eth/src/remote/client.rs index 46d1dcad45..6ed88a75f5 100644 --- a/crates/edr_eth/src/remote/client.rs +++ b/crates/edr_eth/src/remote/client.rs @@ -823,8 +823,10 @@ impl RpcClient { } /// Calls `net_version`. - pub async fn network_id(&self) -> Result { - self.call(MethodInvocation::NetVersion()).await + pub async fn network_id(&self) -> Result { + self.call::(MethodInvocation::NetVersion()) + .await + .map(|network_id| network_id.as_limbs()[0]) } } @@ -1935,7 +1937,7 @@ mod tests { .await .expect("should have succeeded"); - assert_eq!(version, U256::from(1)); + assert_eq!(version, 1); } #[tokio::test] diff --git a/crates/edr_evm/src/blockchain.rs b/crates/edr_evm/src/blockchain.rs index a232742c81..d5a9df57ef 100644 --- a/crates/edr_evm/src/blockchain.rs +++ b/crates/edr_evm/src/blockchain.rs @@ -114,6 +114,9 @@ pub trait Blockchain { /// Retrieves the last block number in the blockchain. fn last_block_number(&self) -> u64; + /// Retrieves the network ID of the blockchain. + fn network_id(&self) -> u64; + /// Retrieves the receipt of the transaction with the provided hash, if it /// exists. fn receipt_by_transaction_hash( diff --git a/crates/edr_evm/src/blockchain/forked.rs b/crates/edr_evm/src/blockchain/forked.rs index b94037f7c8..a9c8b788cc 100644 --- a/crates/edr_evm/src/blockchain/forked.rs +++ b/crates/edr_evm/src/blockchain/forked.rs @@ -59,7 +59,7 @@ pub struct ForkedBlockchain { fork_state: ForkState, fork_block_number: u64, chain_id: u64, - _network_id: U256, + network_id: u64, spec_id: SpecId, hardfork_activations: Option, } @@ -157,7 +157,7 @@ impl ForkedBlockchain { fork_state, fork_block_number, chain_id, - _network_id: network_id, + network_id, spec_id, hardfork_activations, }) @@ -284,6 +284,10 @@ impl Blockchain for ForkedBlockchain { self.local_storage.last_block_number() } + fn network_id(&self) -> u64 { + self.network_id + } + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] fn receipt_by_transaction_hash( &self, diff --git a/crates/edr_evm/src/blockchain/local.rs b/crates/edr_evm/src/blockchain/local.rs index 527269d019..da2d8440fc 100644 --- a/crates/edr_evm/src/blockchain/local.rs +++ b/crates/edr_evm/src/blockchain/local.rs @@ -222,6 +222,10 @@ impl Blockchain for LocalBlockchain { self.storage.last_block_number() } + fn network_id(&self) -> u64 { + self.chain_id + } + #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))] fn receipt_by_transaction_hash( &self, diff --git a/crates/edr_provider/src/data.rs b/crates/edr_provider/src/data.rs index 4456f5a0e6..6de75deae1 100644 --- a/crates/edr_provider/src/data.rs +++ b/crates/edr_provider/src/data.rs @@ -28,6 +28,7 @@ use edr_evm::{ KECCAK_EMPTY, }; use indexmap::IndexMap; +use rpc_hardhat::ForkMetadata; use tokio::runtime; use self::account::{create_accounts, InitialAccounts}; @@ -35,6 +36,9 @@ use crate::{filter::Filter, logger::Logger, ProviderConfig, ProviderError}; #[derive(Debug, thiserror::Error)] pub enum CreationError { + /// A blockchain error + #[error(transparent)] + Blockchain(BlockchainError), /// An error that occurred while constructing a forked blockchain. #[error(transparent)] ForkedBlockchainCreation(#[from] ForkedCreationError), @@ -56,6 +60,9 @@ pub struct ProviderData { min_gas_price: U256, prevrandao_generator: RandomHashGenerator, block_time_offset_seconds: u64, + fork_metadata: Option, + instance_id: B256, + next_block_base_fee_per_gas: Option, next_block_timestamp: Option, allow_blocks_with_same_timestamp: bool, allow_unlimited_contract_size: bool, @@ -77,8 +84,11 @@ impl ProviderData { genesis_accounts, } = create_accounts(config); - let BlockchainAndState { state, blockchain } = - create_blockchain_and_state(runtime, config, genesis_accounts).await?; + let BlockchainAndState { + blockchain, + state, + fork_metadata, + } = create_blockchain_and_state(runtime, config, genesis_accounts).await?; let prevrandao_generator = RandomHashGenerator::with_seed("randomMixHashSeed"); @@ -93,6 +103,9 @@ impl ProviderData { min_gas_price: U256::from(1), prevrandao_generator, block_time_offset_seconds: block_time_offset_seconds(config)?, + fork_metadata, + instance_id: B256::random(), + next_block_base_fee_per_gas: None, next_block_timestamp: None, allow_blocks_with_same_timestamp: config.allow_blocks_with_same_timestamp, allow_unlimited_contract_size: config.allow_unlimited_contract_size, @@ -120,7 +133,20 @@ impl ProviderData { })? } - pub fn block_number(&self) -> u64 { + /// Returns the metadata of the forked blockchain, if it exists. + pub fn fork_metadata(&self) -> Option<&ForkMetadata> { + self.fork_metadata.as_ref() + } + + /// Returns the last block in the blockchain. + pub fn last_block( + &self, + ) -> Result>, BlockchainError> { + self.blockchain.last_block() + } + + /// Returns the number of the last block in the blockchain. + pub fn last_block_number(&self) -> u64 { self.blockchain.last_block_number() } @@ -260,6 +286,10 @@ impl ProviderData { self.block_time_offset_seconds } + pub fn instance_id(&self) -> &B256 { + &self.instance_id + } + pub fn logger(&self) -> &Logger { &self.logger } @@ -281,6 +311,9 @@ impl ProviderData { self.block_time_offset_seconds = new_offset; } + // Reset the next block base fee per gas upon successful execution + self.next_block_base_fee_per_gas.take(); + // Reset next block time stamp self.next_block_timestamp.take(); @@ -402,6 +435,16 @@ impl ProviderData { Ok(()) } + /// Sets the coinbase. + pub fn set_coinbase(&mut self, coinbase: Address) { + self.beneficiary = coinbase; + } + + /// Sets the next block's base fee per gas. + pub fn set_next_block_base_fee_per_gas(&mut self, base_fee_per_gas: U256) { + self.next_block_base_fee_per_gas = Some(base_fee_per_gas); + } + /// Set the next block timestamp. pub fn set_next_block_timestamp(&mut self, timestamp: u64) -> Result { use std::cmp::Ordering; @@ -424,6 +467,11 @@ impl ProviderData { } } + /// Sets the next block's prevrandao. + pub fn set_next_prev_randao(&mut self, prev_randao: B256) { + self.prevrandao_generator.set_next(prev_randao); + } + pub fn set_nonce(&mut self, address: Address, nonce: u64) -> Result<(), ProviderError> { self.state.modify_account( address, @@ -562,12 +610,6 @@ impl ProviderData { timestamp: u64, prevrandao: Option, ) -> Result, ProviderError> { - // TODO: when we support hardhat_setNextBlockBaseFeePerGas, incorporate - // the last-passed value here. (but don't .take() it yet, because we only - // want to clear it if the block mining is successful. - // https://github.com/NomicFoundation/edr/issues/145 - let base_fee = None; - // TODO: https://github.com/NomicFoundation/edr/issues/156 let reward = U256::ZERO; @@ -584,15 +626,11 @@ impl ProviderData { // TODO: make this configurable (https://github.com/NomicFoundation/edr/issues/111) MineOrdering::Fifo, reward, - base_fee, + self.next_block_base_fee_per_gas, prevrandao, None, )?; - // TODO: when we support hardhat_setNextBlockBaseFeePerGas, reset the user - // provided next block base fee per gas to `None` - // https://github.com/NomicFoundation/edr/issues/145 - Ok(result) } @@ -758,6 +796,7 @@ fn block_time_offset_seconds(config: &ProviderConfig) -> Result>, + fork_metadata: Option, state: Box>, } @@ -793,6 +832,15 @@ async fn create_blockchain_and_state( Ok(BlockchainAndState { state: Box::new(state), + fork_metadata: Some(ForkMetadata { + chain_id: blockchain.chain_id(), + fork_block_number, + fork_block_hash: *blockchain + .block_by_number(fork_block_number) + .map_err(CreationError::Blockchain)? + .expect("Fork block must exist") + .hash(), + }), blockchain: Box::new(blockchain), }) } else { @@ -818,6 +866,7 @@ async fn create_blockchain_and_state( Ok(BlockchainAndState { state, + fork_metadata: None, blockchain: Box::new(blockchain), }) } @@ -1013,7 +1062,7 @@ mod tests { // Mine a block to make sure we're not getting the genesis block fixture.provider_data.mine_and_commit_block(None)?; - let last_block_number = fixture.provider_data.block_number(); + let last_block_number = fixture.provider_data.last_block_number(); // Sanity check assert!(last_block_number > 0); diff --git a/crates/edr_provider/src/error.rs b/crates/edr_provider/src/error.rs index 0ff3dacef0..e29d314364 100644 --- a/crates/edr_provider/src/error.rs +++ b/crates/edr_provider/src/error.rs @@ -2,7 +2,7 @@ use std::time::SystemTimeError; use edr_eth::{ remote::{filter::SubscriptionType, jsonrpc, BlockSpec}, - Address, U256, + Address, SpecId, U256, }; use edr_evm::{ blockchain::BlockchainError, state::StateError, MineBlockError, MinerTransactionError, @@ -72,4 +72,7 @@ pub enum ProviderError { /// The address is not owned by this node. #[error("{address} is not owned by this node")] UnknownAddress { address: Address }, + /// Minimum required hardfork not met + #[error("Feature is only available in post-{minimum:?} hardforks, the current hardfork is {actual:?}")] + UnmetHardfork { actual: SpecId, minimum: SpecId }, } diff --git a/crates/edr_provider/src/lib.rs b/crates/edr_provider/src/lib.rs index 4649fda529..deb06856d2 100644 --- a/crates/edr_provider/src/lib.rs +++ b/crates/edr_provider/src/lib.rs @@ -237,7 +237,9 @@ fn handle_hardhat_request( rpc_hardhat::Request::IntervalMine() => { hardhat::handle_interval_mine_request(data).and_then(to_json) } - rpc_hardhat::Request::Metadata() => Err(ProviderError::Unimplemented("".to_string())), + rpc_hardhat::Request::Metadata() => { + hardhat::handle_metadata_request(data).and_then(to_json) + } rpc_hardhat::Request::Mine(_, _) => Err(ProviderError::Unimplemented("".to_string())), rpc_hardhat::Request::Reset(_) => Err(ProviderError::Unimplemented("".to_string())), rpc_hardhat::Request::SetBalance(address, balance) => { @@ -246,20 +248,25 @@ fn handle_hardhat_request( rpc_hardhat::Request::SetCode(address, code) => { hardhat::handle_set_code(data, address, code).and_then(to_json) } - rpc_hardhat::Request::SetCoinbase(_) => Err(ProviderError::Unimplemented("".to_string())), + rpc_hardhat::Request::SetCoinbase(coinbase) => { + hardhat::handle_set_coinbase_request(data, coinbase).and_then(to_json) + } rpc_hardhat::Request::SetLoggingEnabled(_) => { Err(ProviderError::Unimplemented("".to_string())) } rpc_hardhat::Request::SetMinGasPrice(_) => { Err(ProviderError::Unimplemented("".to_string())) } - rpc_hardhat::Request::SetNextBlockBaseFeePerGas(_) => { - Err(ProviderError::Unimplemented("".to_string())) + rpc_hardhat::Request::SetNextBlockBaseFeePerGas(base_fee_per_gas) => { + hardhat::handle_set_next_block_base_fee_per_gas_request(data, base_fee_per_gas) + .and_then(to_json) } rpc_hardhat::Request::SetNonce(address, nonce) => { hardhat::handle_set_nonce(data, address, nonce).and_then(to_json) } - rpc_hardhat::Request::SetPrevRandao(_) => Err(ProviderError::Unimplemented("".to_string())), + rpc_hardhat::Request::SetPrevRandao(prev_randao) => { + hardhat::handle_set_prev_randao_request(data, prev_randao).and_then(to_json) + } rpc_hardhat::Request::SetStorageAt(address, index, value) => { hardhat::handle_set_storage_at(data, address, index, value).and_then(to_json) } diff --git a/crates/edr_provider/src/requests/eth/blockchain.rs b/crates/edr_provider/src/requests/eth/blockchain.rs index f988858c81..c928c6dc93 100644 --- a/crates/edr_provider/src/requests/eth/blockchain.rs +++ b/crates/edr_provider/src/requests/eth/blockchain.rs @@ -3,7 +3,7 @@ use edr_eth::{remote::BlockSpec, Address, U256, U64}; use crate::{data::ProviderData, ProviderError}; pub fn handle_block_number_request(data: &ProviderData) -> Result { - Ok(U64::from(data.block_number())) + Ok(U64::from(data.last_block_number())) } pub fn handle_chain_id_request(data: &ProviderData) -> Result { diff --git a/crates/edr_provider/src/requests/eth/web3.rs b/crates/edr_provider/src/requests/eth/web3.rs index ce2474da9a..594bee8241 100644 --- a/crates/edr_provider/src/requests/eth/web3.rs +++ b/crates/edr_provider/src/requests/eth/web3.rs @@ -3,12 +3,16 @@ use sha3::{Digest, Keccak256}; use crate::ProviderError; -pub fn handle_web3_client_version_request() -> Result { - Ok(format!( +pub fn client_version() -> String { + format!( "edr/{}/revm/{}", env!("CARGO_PKG_VERSION"), env!("REVM_VERSION"), - )) + ) +} + +pub fn handle_web3_client_version_request() -> Result { + Ok(client_version()) } pub fn handle_web3_sha3_request(message: ZeroXPrefixedBytes) -> Result { diff --git a/crates/edr_provider/src/requests/hardhat.rs b/crates/edr_provider/src/requests/hardhat.rs index 1b67e64eb2..3e5f86272e 100644 --- a/crates/edr_provider/src/requests/hardhat.rs +++ b/crates/edr_provider/src/requests/hardhat.rs @@ -1,5 +1,6 @@ mod accounts; +mod config; mod miner; mod state; -pub use self::{accounts::*, miner::*, state::*}; +pub use self::{accounts::*, config::*, miner::*, state::*}; diff --git a/crates/edr_provider/src/requests/hardhat/config.rs b/crates/edr_provider/src/requests/hardhat/config.rs new file mode 100644 index 0000000000..f15db3cd8c --- /dev/null +++ b/crates/edr_provider/src/requests/hardhat/config.rs @@ -0,0 +1,58 @@ +use edr_eth::{Address, SpecId, B256, U256}; +use rpc_hardhat::Metadata; + +use crate::{data::ProviderData, requests::eth::client_version, ProviderError}; + +pub fn handle_metadata_request(data: &ProviderData) -> Result { + Ok(Metadata { + client_version: client_version(), + chain_id: data.chain_id(), + instance_id: *data.instance_id(), + latest_block_number: data.last_block_number(), + latest_block_hash: *data.last_block()?.hash(), + forked_network: data.fork_metadata().cloned(), + }) +} + +pub fn handle_set_coinbase_request( + data: &mut ProviderData, + coinbase: Address, +) -> Result { + data.set_coinbase(coinbase); + + Ok(true) +} + +pub fn handle_set_next_block_base_fee_per_gas_request( + data: &mut ProviderData, + base_fee_per_gas: U256, +) -> Result { + let spec_id = data.spec_id(); + if spec_id < SpecId::LONDON { + return Err(ProviderError::UnmetHardfork { + actual: spec_id, + minimum: SpecId::LONDON, + }); + } + + data.set_next_block_base_fee_per_gas(base_fee_per_gas); + + Ok(true) +} + +pub fn handle_set_prev_randao_request( + data: &mut ProviderData, + prev_randao: B256, +) -> Result { + let spec_id = data.spec_id(); + if spec_id < SpecId::MERGE { + return Err(ProviderError::UnmetHardfork { + actual: spec_id, + minimum: SpecId::MERGE, + }); + } + + data.set_next_prev_randao(prev_randao); + + Ok(true) +} diff --git a/crates/edr_rpc_hardhat/src/lib.rs b/crates/edr_rpc_hardhat/src/lib.rs index 13af022880..059ee49689 100644 --- a/crates/edr_rpc_hardhat/src/lib.rs +++ b/crates/edr_rpc_hardhat/src/lib.rs @@ -1,20 +1,21 @@ +/// Compiler input and output structures used as parameters to Hardhat RPC +/// methods +pub mod compiler; +/// Configuration types for Hardhat RPC methods +pub mod config; +mod metadata; + use edr_eth::{ serde::{sequence_to_single, single_to_sequence, ZeroXPrefixedBytes}, Address, B256, U256, }; +pub use self::metadata::{ForkMetadata, Metadata}; use self::{ compiler::{CompilerInput, CompilerOutput}, config::ResetProviderConfig, }; -/// Compiler input and output structures used as parameters to Hardhat RPC -/// methods -pub mod compiler; - -/// Configuration types for Hardhat RPC methods -pub mod config; - /// an invocation of a hardhat_* RPC method, with its parameters #[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(tag = "method", content = "params")] @@ -118,7 +119,7 @@ pub enum Request { serialize_with = "single_to_sequence", deserialize_with = "sequence_to_single" )] - SetPrevRandao(ZeroXPrefixedBytes), + SetPrevRandao(B256), /// hardhat_setStorageAt #[serde(rename = "hardhat_setStorageAt")] SetStorageAt(Address, U256, U256), diff --git a/crates/edr_rpc_hardhat/src/metadata.rs b/crates/edr_rpc_hardhat/src/metadata.rs new file mode 100644 index 0000000000..15e3d10e6f --- /dev/null +++ b/crates/edr_rpc_hardhat/src/metadata.rs @@ -0,0 +1,37 @@ +use edr_eth::B256; + +/// Metadata about the provider instance. +#[derive(serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Metadata { + /// A string identifying the version of Hardhat, for debugging purposes, + /// not meant to be displayed to users. + pub client_version: String, + /// The chain's id. Used to sign transactions. + pub chain_id: u64, + /// A 0x-prefixed hex-encoded 32 bytes id which uniquely identifies an + /// instance/run of Hardhat Network. Running Hardhat Network more than + /// once (even with the same version and parameters) will always result + /// in different `instanceId`s. Running `hardhat_reset` will change the + /// `instanceId` of an existing Hardhat Network. + pub instance_id: B256, + /// The latest block's number in Hardhat Network + pub latest_block_number: u64, + /// The latest block's hash in Hardhat Network + pub latest_block_hash: B256, + /// This field is only present when Hardhat Network is forking another + /// chain. + pub forked_network: Option, +} + +/// Metadata about the forked network. +#[derive(Clone, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ForkMetadata { + /// The chainId of the network that is being forked + pub chain_id: u64, + /// The number of the block that the network forked from. + pub fork_block_number: u64, + /// The hash of the block that the network forked from. + pub fork_block_hash: B256, +} diff --git a/crates/edr_rpc_hardhat/tests/hardhat.rs b/crates/edr_rpc_hardhat/tests/hardhat.rs index 75671ea943..c03cbdf3a6 100644 --- a/crates/edr_rpc_hardhat/tests/hardhat.rs +++ b/crates/edr_rpc_hardhat/tests/hardhat.rs @@ -149,9 +149,7 @@ fn serde_hardhat_set_nonce() { #[test] fn serde_hardhat_set_prev_randao() { - help_test_method_invocation_serde(edr_rpc_hardhat::Request::SetPrevRandao( - Bytes::from(&b"whatever"[..]).into(), - )); + help_test_method_invocation_serde(edr_rpc_hardhat::Request::SetPrevRandao(B256::random())); } #[test]