Skip to content

Commit

Permalink
feat: hardhat_reset (#302)
Browse files Browse the repository at this point in the history
  • Loading branch information
vbar authored Jun 27, 2024
1 parent df89dca commit 1afa81a
Show file tree
Hide file tree
Showing 10 changed files with 402 additions and 123 deletions.
2 changes: 1 addition & 1 deletion SUPPORTED_APIS.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ The `status` options are:
| `HARDHAT` | `hardhat_getAutomine` | `NOT IMPLEMENTED` | Returns `true` if automatic mining is enabled, and `false` otherwise |
| `HARDHAT` | `hardhat_metadata` | `NOT IMPLEMENTED` | Returns the metadata of the current network |
| [`HARDHAT`](#hardhat-namespace) | [`hardhat_mine`](#hardhat_mine) | Mine any number of blocks at once, in constant time |
| `HARDHAT` | `hardhat_reset` | `NOT IMPLEMENTED` | Resets the state of the network |
| [`HARDHAT`](#hardhat-namespace) | [`hardhat_reset`] | `PARTIALLY` | Resets the state of the network; cannot revert to past block numbers, unless they're in a fork |
| [`HARDHAT`](#hardhat-namespace) | [`hardhat_setBalance`](#hardhat_setbalance) | `SUPPORTED` | Modifies the balance of an account |
| [`HARDHAT`](#hardhat-namespace) | [`hardhat_setCode`](#hardhat_setcode) | `SUPPORTED` | Sets the bytecode of a given account |
| `HARDHAT` | `hardhat_setCoinbase` | `NOT IMPLEMENTED` | Sets the coinbase address |
Expand Down
111 changes: 98 additions & 13 deletions src/fork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
use std::{
collections::HashMap,
convert::{TryFrom, TryInto},
fmt,
future::Future,
marker::PhantomData,
str::FromStr,
sync::{Arc, RwLock},
};
Expand Down Expand Up @@ -109,12 +111,14 @@ pub struct ForkStorageInner<S> {
pub factory_dep_cache: HashMap<H256, Option<Vec<u8>>>,
// If set - it hold the necessary information on where to fetch the data.
// If not set - it will simply read from underlying storage.
pub fork: Option<ForkDetails<S>>,
pub fork: Option<ForkDetails>,
// ForkSource type no longer needed but retained to keep the old interface.
pub dummy: PhantomData<S>,
}

impl<S: ForkSource> ForkStorage<S> {
pub fn new(
fork: Option<ForkDetails<S>>,
fork: Option<ForkDetails>,
system_contracts_options: &system_contracts::Options,
) -> Self {
let chain_id = fork
Expand All @@ -133,11 +137,40 @@ impl<S: ForkSource> ForkStorage<S> {
value_read_cache: Default::default(),
fork,
factory_dep_cache: Default::default(),
dummy: Default::default(),
})),
chain_id,
}
}

pub fn get_cache_config(&self) -> Result<CacheConfig, String> {
let reader = self
.inner
.read()
.map_err(|e| format!("Failed to acquire read lock: {}", e))?;
let cache_config = if let Some(ref fork_details) = reader.fork {
fork_details.cache_config.clone()
} else {
CacheConfig::default()
};
Ok(cache_config)
}

pub fn get_fork_url(&self) -> Result<String, String> {
let reader = self
.inner
.read()
.map_err(|e| format!("Failed to acquire read lock: {}", e))?;
if let Some(ref fork_details) = reader.fork {
fork_details
.fork_source
.get_fork_url()
.map_err(|e| e.to_string())
} else {
Err("not forked".to_string())
}
}

pub fn read_value_internal(
&self,
key: &StorageKey,
Expand Down Expand Up @@ -269,6 +302,9 @@ impl<S> ForkStorage<S> {
/// forking a remote chain.
/// The method signatures are similar to methods from ETHNamespace and ZKNamespace.
pub trait ForkSource {
/// Returns the forked URL.
fn get_fork_url(&self) -> eyre::Result<String>;

/// Returns the Storage value at a given index for given address.
fn get_storage_at(
&self,
Expand Down Expand Up @@ -347,11 +383,9 @@ pub trait ForkSource {
}

/// Holds the information about the original chain.
/// "S" is the implementation of the ForkSource.
#[derive(Debug, Clone)]
pub struct ForkDetails<S> {
// Source of the fork data (for example HTTPForkSource)
pub fork_source: S,
pub struct ForkDetails {
// Source of the fork data (for example HttpForkSource)
pub fork_source: Box<dyn ForkSource + Send + Sync>,
// Block number at which we forked (the next block to create is l1_block + 1)
pub l1_block: L1BatchNumber,
// The actual L2 block
Expand All @@ -367,6 +401,7 @@ pub struct ForkDetails<S> {
/// The factor by which to scale the gasLimit.
pub estimate_gas_scale_factor: f32,
pub fee_params: Option<FeeParams>,
pub cache_config: CacheConfig,
}

const SUPPORTED_VERSIONS: &[ProtocolVersionId] = &[
Expand Down Expand Up @@ -401,7 +436,22 @@ pub fn supported_versions_to_string() -> String {
versions.join(", ")
}

impl ForkDetails<HttpForkSource> {
impl fmt::Debug for ForkDetails {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ForkDetails")
.field("l1_block", &self.l1_block)
.field("l2_block", &self.l2_block)
.field("l2_miniblock", &self.l2_miniblock)
.field("l2_miniblock_hash", &self.l2_miniblock_hash)
.field("block_timestamp", &self.block_timestamp)
.field("overwrite_chain_id", &self.overwrite_chain_id)
.field("l1_gas_price", &self.l1_gas_price)
.field("l2_fair_gas_price", &self.l2_fair_gas_price)
.finish()
}
}

impl ForkDetails {
pub async fn from_network_and_miniblock_and_chain(
network: ForkNetwork,
client: Client<L2>,
Expand Down Expand Up @@ -454,7 +504,7 @@ impl ForkDetails<HttpForkSource> {
let fee_params = client.get_fee_params().await.ok();

ForkDetails {
fork_source: HttpForkSource::new(url.to_owned(), cache_config),
fork_source: Box::new(HttpForkSource::new(url.to_owned(), cache_config.clone())),
l1_block: l1_batch_number,
l2_block: block,
block_timestamp: block_details.base.timestamp,
Expand All @@ -466,6 +516,7 @@ impl ForkDetails<HttpForkSource> {
estimate_gas_price_scale_factor,
estimate_gas_scale_factor,
fee_params,
cache_config,
}
}
/// Create a fork from a given network at a given height.
Expand Down Expand Up @@ -509,9 +560,40 @@ impl ForkDetails<HttpForkSource> {
)
.await
}
}

impl<S: ForkSource> ForkDetails<S> {
/// Return URL and HTTP client for `hardhat_reset`.
pub fn from_url(
url: String,
fork_at: Option<u64>,
cache_config: CacheConfig,
) -> eyre::Result<Self> {
let parsed_url = SensitiveUrl::from_str(&url)?;
let builder = match Client::http(parsed_url) {
Ok(b) => b,
Err(error) => {
return Err(eyre::Report::msg(error));
}
};
let client = builder.build();

block_on(async move {
let l2_miniblock = if let Some(fork_at) = fork_at {
fork_at
} else {
client.get_block_number().await?.as_u64()
};

Ok(Self::from_network_and_miniblock_and_chain(
ForkNetwork::Other(url),
client,
l2_miniblock,
None,
cache_config,
)
.await)
})
}

/// Return [`ForkNetwork`] and HTTP client for a given fork name.
pub fn fork_network_and_client(fork: &str) -> (ForkNetwork, Client<L2>) {
let network = match fork {
Expand Down Expand Up @@ -571,6 +653,7 @@ mod tests {
use zksync_types::{api::TransactionVariant, StorageKey};

use crate::{
cache::CacheConfig,
deps::InMemoryStorage,
node::{
DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR, DEFAULT_ESTIMATE_GAS_SCALE_FACTOR,
Expand Down Expand Up @@ -598,7 +681,7 @@ mod tests {
let options = system_contracts::Options::default();

let fork_details = ForkDetails {
fork_source: &external_storage,
fork_source: Box::new(external_storage),
l1_block: L1BatchNumber(1),
l2_block: zksync_types::api::Block::<TransactionVariant>::default(),
l2_miniblock: 1,
Expand All @@ -610,9 +693,11 @@ mod tests {
estimate_gas_price_scale_factor: DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR,
estimate_gas_scale_factor: DEFAULT_ESTIMATE_GAS_SCALE_FACTOR,
fee_params: None,
cache_config: CacheConfig::None,
};

let mut fork_storage = ForkStorage::new(Some(fork_details), &options);
let mut fork_storage: ForkStorage<testing::ExternalStorage> =
ForkStorage::new(Some(fork_details), &options);

assert!(fork_storage.is_write_initial(&never_written_key));
assert!(!fork_storage.is_write_initial(&key_with_some_value));
Expand Down
4 changes: 4 additions & 0 deletions src/http_fork_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ impl HttpForkSource {
}

impl ForkSource for HttpForkSource {
fn get_fork_url(&self) -> eyre::Result<String> {
Ok(self.fork_url.clone())
}

fn get_storage_at(
&self,
address: zksync_basic_types::Address,
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::cache::CacheConfig;
use crate::http_fork_source::HttpForkSource;
use crate::node::{InMemoryNodeConfig, ShowGasDetails, ShowStorageLogs, ShowVMDetails};
use crate::observability::Observability;
use crate::utils::to_human_size;
Expand Down Expand Up @@ -395,7 +396,7 @@ async fn main() -> anyhow::Result<()> {
),
}

let node = InMemoryNode::new(
let node: InMemoryNode<HttpForkSource> = InMemoryNode::new(
fork_details,
Some(observability),
InMemoryNodeConfig {
Expand Down
31 changes: 31 additions & 0 deletions src/namespaces/hardhat.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
use jsonrpc_derive::rpc;
use serde::{Deserialize, Serialize};
use zksync_basic_types::{Address, U256, U64};

use super::RpcResult;

#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResetRequestForking {
pub json_rpc_url: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub block_number: Option<U64>,
}

#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
pub struct ResetRequest {
/// The block number to reset the state to.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub to: Option<U64>,
// Forking to a specified URL.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub forking: Option<ResetRequestForking>,
}

#[rpc]
pub trait HardhatNamespaceT {
/// Sets the balance of the given address to the given balance.
Expand Down Expand Up @@ -46,6 +65,18 @@ pub trait HardhatNamespaceT {
#[rpc(name = "hardhat_mine")]
fn hardhat_mine(&self, num_blocks: Option<U64>, interval: Option<U64>) -> RpcResult<bool>;

/// Reset the state of the network back to a fresh forked state, or disable forking.
///
/// # Arguments
///
/// * `reset_spec` - The requested state, defaults to resetting the current network.
///
/// # Returns
///
/// A `BoxFuture` containing a `Result` with a `bool` representing the success of the operation.
#[rpc(name = "hardhat_reset")]
fn reset_network(&self, reset_spec: Option<ResetRequest>) -> RpcResult<bool>;

/// Hardhat Network allows you to send transactions impersonating specific account and contract addresses.
/// To impersonate an account use this method, passing the address to impersonate as its parameter.
/// After calling this method, any transactions with this sender will be executed without verification.
Expand Down
2 changes: 1 addition & 1 deletion src/namespaces/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub use debug::DebugNamespaceT;
pub use eth::EthNamespaceT;
pub use eth_test::EthTestNodeNamespaceT;
pub use evm::EvmNamespaceT;
pub use hardhat::HardhatNamespaceT;
pub use hardhat::{HardhatNamespaceT, ResetRequest};
pub use net::NetNamespaceT;
pub use web3::Web3NamespaceT;
pub use zks::ZksNamespaceT;
Expand Down
11 changes: 10 additions & 1 deletion src/node/hardhat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use zksync_web3_decl::error::Web3Error;

use crate::{
fork::ForkSource,
namespaces::{HardhatNamespaceT, RpcResult},
namespaces::{HardhatNamespaceT, ResetRequest, RpcResult},
node::InMemoryNode,
utils::{into_jsrpc_error, into_jsrpc_error_message, IntoBoxedFuture},
};
Expand Down Expand Up @@ -38,6 +38,15 @@ impl<S: ForkSource + std::fmt::Debug + Clone + Send + Sync + 'static> HardhatNam
.into_boxed_future()
}

fn reset_network(&self, reset_spec: Option<ResetRequest>) -> RpcResult<bool> {
self.reset_network(reset_spec)
.map_err(|err| {
tracing::error!("failed reset: {:?}", err);
into_jsrpc_error(Web3Error::InternalError(err))
})
.into_boxed_future()
}

fn impersonate_account(&self, address: Address) -> RpcResult<bool> {
self.impersonate_account(address)
.map_err(|err| {
Expand Down
Loading

0 comments on commit 1afa81a

Please sign in to comment.