From 40cf025d48e46d246e278680b55f2051e608a69a Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Mon, 11 Sep 2023 15:46:26 +0200 Subject: [PATCH] feat: Implement evm_increaseTime, evm_setNextBlockTimestamp, and evm_setTime (#93) --- SUPPORTED_APIS.md | 82 ++++++++- src/evm.rs | 424 ++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 6 + test_endpoints.http | 36 ++++ 4 files changed, 541 insertions(+), 7 deletions(-) create mode 100644 src/evm.rs diff --git a/SUPPORTED_APIS.md b/SUPPORTED_APIS.md index 1485b263..57189d68 100644 --- a/SUPPORTED_APIS.md +++ b/SUPPORTED_APIS.md @@ -74,7 +74,7 @@ The `status` options are: | `ETH` | `eth_uninstallFilter` | `NOT IMPLEMENTED`
[GitHub Issue #38](https://github.com/matter-labs/era-test-node/issues/38) | Uninstalls a filter with given id | | `ETH` | `eth_unsubscribe` | `NOT IMPLEMENTED` | Cancel a subscription to a particular event | | `EVM` | `evm_addAccount` | `NOT IMPLEMENTED` | Adds any arbitrary account | -| `EVM` | `evm_increaseTime` | `NOT IMPLEMENTED`
[GitHub Issue #66](https://github.com/matter-labs/era-test-node/issues/66) | Jump forward in time by the given amount of time, in seconds | +| [`EVM`](#evm-namespace) | [`evm_increaseTime`](#evm_increasetime) | `SUPPORTED` | Jump forward in time by the given amount of time, in seconds | | `EVM` | `evm_mine` | `NOT IMPLEMENTED`
[GitHub Issue #67](https://github.com/matter-labs/era-test-node/issues/67) | Force a single block to be mined | | `EVM` | `evm_removeAccount` | `NOT IMPLEMENTED` | Removes an account | | `EVM` | `evm_revert` | `NOT IMPLEMENTED`
[GitHub Issue #70](https://github.com/matter-labs/era-test-node/issues/70) | Revert the state of the blockchain to a previous snapshot | @@ -85,8 +85,8 @@ The `status` options are: | `EVM` | `evm_setAutomine` | `NOT IMPLEMENTED` | Enables or disables the automatic mining of new blocks with each new transaction submitted to the network | | `EVM` | `evm_setBlockGasLimit` | `NOT IMPLEMENTED` | Sets the Block Gas Limit of the network | | `EVM` | `evm_setIntervalMining` | `NOT IMPLEMENTED` | Enables (with a numeric argument greater than 0) or disables (with a numeric argument equal to 0), the automatic mining of blocks at a regular interval of milliseconds, each of which will include all pending transactions | -| `EVM` | `evm_setNextBlockTimestamp` | `NOT IMPLEMENTED`
[GitHub Issue #68](https://github.com/matter-labs/era-test-node/issues/68) | Works like `evm_increaseTime`, but takes the exact timestamp that you want in the next block, and increases the time accordingly | -| `EVM` | `evm_setTime` | `NOT IMPLEMENTED` | Sets the internal clock time to the given timestamp | +| [`EVM`](#evm-namespace) | [`evm_setNextBlockTimestamp`](#evm_setnextblocktimestamp) | `SUPPORTED` | Works like `evm_increaseTime`, but takes the exact timestamp that you want in the next block, and increases the time accordingly | +| [`EVM`](#evm-namespace) | [`evm_setTime`](#evm_settime) | `SUPPORTED` | Sets the internal clock time to the given timestamp | | `EVM` | `evm_snapshot` | `NOT IMPLEMENTED`
[GitHub Issue #69](https://github.com/matter-labs/era-test-node/issues/69) | Snapshot the state of the blockchain at the current block | | `HARDHAT` | `hardhat_addCompilationResult` | `NOT IMPLEMENTED` | Add information about compiled contracts | | `HARDHAT` | `hardhat_dropTransaction` | `NOT IMPLEMENTED` | Remove a transaction from the mempool | @@ -753,10 +753,6 @@ The new nonce must be greater than the existing nonce. + `address: Address` - The `Address` whose nonce is to be changed + `nonce: U256` - The new nonce -#### Status - -`SUPPORTED` - #### Example ```bash @@ -774,6 +770,78 @@ curl --request POST \ }' ``` +## `EVM NAMESPACE` + +### `evm_increaseTime` + +[source](src/evm.rs) + +Increase the current timestamp for the node + +#### Arguments + ++ `time_delta_seconds: U64` + +#### Status + +`SUPPORTED` + +#### Example + +```bash +curl --request POST \ + --url http://localhost:8011/ \ + --header 'content-type: application/json' \ + --data '{"jsonrpc": "2.0","id": "1","method": "evm_increaseTime","params": [10]}' +``` + +### `evm_setNextBlockTimestamp` + +[source](src/evm.rs) + +Sets the timestamp of the next block but doesn't mine one.. + +#### Arguments + ++ `timestamp: U64` + +#### Status + +`SUPPORTED` + +#### Example + +```bash +curl --request POST \ + --url http://localhost:8011/ \ + --header 'content-type: application/json' \ + --data '{"jsonrpc": "2.0","id": "1","method": "evm_setNextBlockTimestamp","params": [1672527600]}' +``` + +### `evm_setTime` + +[source](src/evm.rs) + +Set the current timestamp for the node. Warning: This will allow you to move _backwards_ in time, which +may cause new blocks to appear to be mined before old blocks. This will result in an invalid state. + +#### Arguments + ++ `time: U64` + +#### Status + +`SUPPORTED` + +#### Example + +```bash +curl --request POST \ + --url http://localhost:8011/ \ + --header 'content-type: application/json' \ + --data '{"jsonrpc": "2.0","id": "1","method": "evm_setTime","params": [1672527600]}' +``` + ## `ZKS NAMESPACE` ### `zks_estimateFee` diff --git a/src/evm.rs b/src/evm.rs new file mode 100644 index 00000000..8eede93b --- /dev/null +++ b/src/evm.rs @@ -0,0 +1,424 @@ +use std::sync::{Arc, RwLock}; + +use crate::{fork::ForkSource, node::InMemoryNodeInner}; +use jsonrpc_core::{BoxFuture, Result}; +use jsonrpc_derive::rpc; +use zksync_core::api_server::web3::backend_jsonrpc::error::into_jsrpc_error; +use zksync_web3_decl::error::Web3Error; + +/// Implementation of EvmNamespace +pub struct EvmNamespaceImpl { + node: Arc>>, +} + +impl EvmNamespaceImpl { + /// Creates a new `Evm` instance with the given `node`. + pub fn new(node: Arc>>) -> Self { + Self { node } + } +} + +#[rpc] +pub trait EvmNamespaceT { + /// Increase the current timestamp for the node + /// + /// # Parameters + /// - `time_delta`: The number of seconds to increase time by + /// + /// # Returns + /// The applied time delta to `current_timestamp` value for the InMemoryNodeInner. + #[rpc(name = "evm_increaseTime")] + fn increase_time(&self, time_delta_seconds: u64) -> BoxFuture>; + + /// Set the current timestamp for the node. The timestamp must be in future. + /// + /// # Parameters + /// - `timestamp`: The timestamp to set the time to + /// + /// # Returns + /// The new timestamp value for the InMemoryNodeInner. + #[rpc(name = "evm_setNextBlockTimestamp")] + fn set_next_block_timestamp(&self, timestamp: u64) -> BoxFuture>; + + /// Set the current timestamp for the node. + /// Warning: This will allow you to move backwards in time, which may cause new blocks to appear to be + /// mined before old blocks. This will result in an invalid state. + /// + /// # Parameters + /// - `time`: The timestamp to set the time to + /// + /// # Returns + /// The difference between the `current_timestamp` and the new timestamp for the InMemoryNodeInner. + #[rpc(name = "evm_setTime")] + fn set_time(&self, time: u64) -> BoxFuture>; +} + +impl EvmNamespaceT + for EvmNamespaceImpl +{ + fn increase_time(&self, time_delta_seconds: u64) -> BoxFuture> { + let inner = Arc::clone(&self.node); + + Box::pin(async move { + if time_delta_seconds == 0 { + return Ok(time_delta_seconds); + } + + let time_delta = time_delta_seconds.saturating_mul(1000); + match inner.write() { + Ok(mut inner_guard) => { + inner_guard.current_timestamp = + inner_guard.current_timestamp.saturating_add(time_delta); + Ok(time_delta_seconds) + } + Err(_) => Err(into_jsrpc_error(Web3Error::InternalError)), + } + }) + } + + fn set_next_block_timestamp(&self, timestamp: u64) -> BoxFuture> { + let inner = Arc::clone(&self.node); + + Box::pin(async move { + match inner.write() { + Ok(mut inner_guard) => { + if timestamp < inner_guard.current_timestamp { + Err(into_jsrpc_error(Web3Error::InternalError)) + } else { + inner_guard.current_timestamp = timestamp; + Ok(timestamp) + } + } + Err(_) => Err(into_jsrpc_error(Web3Error::InternalError)), + } + }) + } + + fn set_time(&self, time: u64) -> BoxFuture> { + let inner = Arc::clone(&self.node); + + Box::pin(async move { + match inner.write() { + Ok(mut inner_guard) => { + let time_diff = + (time as i128).saturating_sub(inner_guard.current_timestamp as i128); + inner_guard.current_timestamp = time; + Ok(time_diff) + } + Err(_) => Err(into_jsrpc_error(Web3Error::InternalError)), + } + }) + } +} + +#[cfg(test)] +mod tests { + use crate::{http_fork_source::HttpForkSource, node::InMemoryNode}; + + use super::*; + + #[tokio::test] + async fn test_increase_time_zero_value() { + let node = InMemoryNode::::default(); + let evm = EvmNamespaceImpl::new(node.get_inner()); + + let increase_value_seconds = 0u64; + let timestamp_before = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + let expected_response = increase_value_seconds; + + let actual_response = evm + .increase_time(increase_value_seconds) + .await + .expect("failed increasing timestamp"); + let timestamp_after = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + + assert_eq!(expected_response, actual_response, "erroneous response"); + assert_eq!( + increase_value_seconds.saturating_mul(1000u64), + timestamp_after.saturating_sub(timestamp_before), + "timestamp did not increase by the specified amount", + ); + } + + #[tokio::test] + async fn test_increase_time_max_value() { + let node = InMemoryNode::::default(); + let evm = EvmNamespaceImpl::new(node.get_inner()); + + let increase_value_seconds = u64::MAX; + let timestamp_before = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + assert_ne!(0, timestamp_before, "initial timestamp must be non zero",); + let expected_response = increase_value_seconds; + + let actual_response = evm + .increase_time(increase_value_seconds) + .await + .expect("failed increasing timestamp"); + let timestamp_after = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + + assert_eq!(expected_response, actual_response, "erroneous response"); + assert_eq!( + u64::MAX, + timestamp_after, + "timestamp did not saturate upon increase", + ); + } + + #[tokio::test] + async fn test_increase_time() { + let node = InMemoryNode::::default(); + let evm = EvmNamespaceImpl::new(node.get_inner()); + + let increase_value_seconds = 100u64; + let timestamp_before = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + let expected_response = increase_value_seconds; + + let actual_response = evm + .increase_time(increase_value_seconds) + .await + .expect("failed increasing timestamp"); + let timestamp_after = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + + assert_eq!(expected_response, actual_response, "erroneous response"); + assert_eq!( + increase_value_seconds.saturating_mul(1000u64), + timestamp_after.saturating_sub(timestamp_before), + "timestamp did not increase by the specified amount", + ); + } + + #[tokio::test] + async fn test_set_next_block_timestamp_future() { + let node = InMemoryNode::::default(); + let evm = EvmNamespaceImpl::new(node.get_inner()); + + let new_timestamp = 10_000u64; + let timestamp_before = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + assert_ne!( + timestamp_before, new_timestamp, + "timestamps must be different" + ); + let expected_response = new_timestamp; + + let actual_response = evm + .set_next_block_timestamp(new_timestamp) + .await + .expect("failed setting timestamp"); + let timestamp_after = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + + assert_eq!(expected_response, actual_response, "erroneous response"); + assert_eq!( + new_timestamp, timestamp_after, + "timestamp was not set correctly", + ); + } + + #[tokio::test] + async fn test_set_next_block_timestamp_past_fails() { + let node = InMemoryNode::::default(); + let evm = EvmNamespaceImpl::new(node.get_inner()); + + let timestamp_before = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + + let new_timestamp = timestamp_before + 500; + evm.set_next_block_timestamp(new_timestamp) + .await + .expect("failed setting timestamp"); + + let result = evm.set_next_block_timestamp(timestamp_before).await; + + assert!(result.is_err(), "expected an error for timestamp in past"); + } + + #[tokio::test] + async fn test_set_next_block_timestamp_same_value() { + let node = InMemoryNode::::default(); + let evm = EvmNamespaceImpl::new(node.get_inner()); + + let new_timestamp = 1000u64; + let timestamp_before = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + assert_eq!(timestamp_before, new_timestamp, "timestamps must be same"); + let expected_response = new_timestamp; + + let actual_response = evm + .set_next_block_timestamp(new_timestamp) + .await + .expect("failed setting timestamp"); + let timestamp_after = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + + assert_eq!(expected_response, actual_response, "erroneous response"); + assert_eq!( + timestamp_before, timestamp_after, + "timestamp must not change", + ); + } + + #[tokio::test] + async fn test_set_time_future() { + let node = InMemoryNode::::default(); + let evm = EvmNamespaceImpl::new(node.get_inner()); + + let new_time = 10_000u64; + let timestamp_before = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + assert_ne!(timestamp_before, new_time, "timestamps must be different"); + let expected_response = 9000; + + let actual_response = evm + .set_time(new_time) + .await + .expect("failed setting timestamp"); + let timestamp_after = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + + assert_eq!(expected_response, actual_response, "erroneous response"); + assert_eq!(new_time, timestamp_after, "timestamp was not set correctly",); + } + + #[tokio::test] + async fn test_set_time_past() { + let node = InMemoryNode::::default(); + let evm = EvmNamespaceImpl::new(node.get_inner()); + + let new_time = 10u64; + let timestamp_before = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + assert_ne!(timestamp_before, new_time, "timestamps must be different"); + let expected_response = -990; + + let actual_response = evm + .set_time(new_time) + .await + .expect("failed setting timestamp"); + let timestamp_after = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + + assert_eq!(expected_response, actual_response, "erroneous response"); + assert_eq!(new_time, timestamp_after, "timestamp was not set correctly",); + } + + #[tokio::test] + async fn test_set_time_same_value() { + let node = InMemoryNode::::default(); + let evm = EvmNamespaceImpl::new(node.get_inner()); + + let new_time = 1000u64; + let timestamp_before = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + assert_eq!(timestamp_before, new_time, "timestamps must be same"); + let expected_response = 0; + + let actual_response = evm + .set_time(new_time) + .await + .expect("failed setting timestamp"); + let timestamp_after = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .expect("failed reading timestamp"); + + assert_eq!(expected_response, actual_response, "erroneous response"); + assert_eq!( + timestamp_before, timestamp_after, + "timestamp must not change", + ); + } + + #[tokio::test] + async fn test_set_time_edges() { + let node = InMemoryNode::::default(); + let evm = EvmNamespaceImpl::new(node.get_inner()); + + for new_time in [0, u64::MAX] { + let timestamp_before = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .unwrap_or_else(|_| panic!("case {}: failed reading timestamp", new_time)); + assert_ne!( + timestamp_before, new_time, + "case {new_time}: timestamps must be different" + ); + let expected_response = (new_time as i128).saturating_sub(timestamp_before as i128); + + let actual_response = evm + .set_time(new_time) + .await + .expect("failed setting timestamp"); + let timestamp_after = node + .get_inner() + .read() + .map(|inner| inner.current_timestamp) + .unwrap_or_else(|_| panic!("case {}: failed reading timestamp", new_time)); + + assert_eq!( + expected_response, actual_response, + "case {new_time}: erroneous response" + ); + assert_eq!( + new_time, timestamp_after, + "case {new_time}: timestamp was not set correctly", + ); + } + } +} diff --git a/src/main.rs b/src/main.rs index 47636b11..5dbb6366 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,6 +45,7 @@ use crate::hardhat::{HardhatNamespaceImpl, HardhatNamespaceT}; use crate::node::{ShowGasDetails, ShowStorageLogs, ShowVMDetails}; use clap::{Parser, Subcommand}; use configuration_api::ConfigurationApiNamespaceT; +use evm::{EvmNamespaceImpl, EvmNamespaceT}; use fork::{ForkDetails, ForkSource}; use node::ShowCalls; use zks::ZkMockNamespaceImpl; @@ -53,6 +54,7 @@ mod bootloader_debug; mod configuration_api; mod console_log; mod deps; +mod evm; mod fork; mod formatter; mod hardhat; @@ -139,6 +141,7 @@ async fn build_json_http< node: InMemoryNode, net: NetNamespace, config_api: ConfigurationApiNamespace, + evm: EvmNamespaceImpl, zks: ZkMockNamespaceImpl, hardhat: HardhatNamespaceImpl, ) -> tokio::task::JoinHandle<()> { @@ -149,6 +152,7 @@ async fn build_json_http< io.extend_with(node.to_delegate()); io.extend_with(net.to_delegate()); io.extend_with(config_api.to_delegate()); + io.extend_with(evm.to_delegate()); io.extend_with(zks.to_delegate()); io.extend_with(hardhat.to_delegate()); io @@ -316,6 +320,7 @@ async fn main() -> anyhow::Result<()> { let net = NetNamespace::new(L2ChainId(TEST_NODE_NETWORK_ID)); let config_api = ConfigurationApiNamespace::new(node.get_inner()); + let evm = EvmNamespaceImpl::new(node.get_inner()); let zks = ZkMockNamespaceImpl::new(node.get_inner()); let hardhat = HardhatNamespaceImpl::new(node.get_inner()); @@ -324,6 +329,7 @@ async fn main() -> anyhow::Result<()> { node, net, config_api, + evm, zks, hardhat, ) diff --git a/test_endpoints.http b/test_endpoints.http index c5105738..c94d5f56 100644 --- a/test_endpoints.http +++ b/test_endpoints.http @@ -272,6 +272,42 @@ content-type: application/json "params": [] } +### + +POST http://localhost:8011 +content-type: application/json + +{ + "jsonrpc": "2.0", + "id": "2", + "method": "evm_increaseTime", + "params": [10] +} + +### + +POST http://localhost:8011 +content-type: application/json + +{ + "jsonrpc": "2.0", + "id": "2", + "method": "evm_setNextBlockTimestamp", + "params": [1672527600] +} + +### + +POST http://localhost:8011 +content-type: application/json + +{ + "jsonrpc": "2.0", + "id": "2", + "method": "evm_setTime", + "params": [1672527600] +} + ### POST http://localhost:8011 content-type: application/json