diff --git a/SUPPORTED_APIS.md b/SUPPORTED_APIS.md
index c3bec4c3..1969a403 100644
--- a/SUPPORTED_APIS.md
+++ b/SUPPORTED_APIS.md
@@ -102,7 +102,7 @@ The `status` options are:
| `HARDHAT` | `hardhat_setMinGasPrice` | `NOT IMPLEMENTED` | Sets the minimum gas price |
| `HARDHAT` | `hardhat_setNextBlockBaseFeePerGas` | `NOT IMPLEMENTED` | Sets the base fee per gas for the next block |
| `HARDHAT` | `hardhat_setPrevRandao` | `NOT IMPLEMENTED` | Sets the PREVRANDAO value of the next block |
-| `HARDHAT` | `hardhat_setNonce` | `NOT IMPLEMENTED`
[GitHub Issue #77](https://github.com/matter-labs/era-test-node/issues/77) | Sets the nonce of a given account |
+| [`HARDHAT`](#hardhat-namespace) | [`hardhat_setNonce`](#hardhat_setnonce) | `SUPPORTED` | Sets the nonce of a given account |
| `HARDHAT` | `hardhat_setStorageAt` | `NOT IMPLEMENTED` | Sets the storage value at a given key for a given account |
| `HARDHAT` | `hardhat_stopImpersonatingAccount` | `NOT IMPLEMENTED`
[GitHub Issue #74](https://github.com/matter-labs/era-test-node/issues/74) | Stop impersonating an account after having previously used `hardhat_impersonateAccount` |
| [`NETWORK`](#network-namespace) | [`net_version`](#net_version) | `SUPPORTED` | Returns the current network id
_(default is `260`)_ |
@@ -717,6 +717,39 @@ curl --request POST \
}'
```
+### `hardhat_setNonce`
+
+[source](src/hardhat.rs)
+
+Modifies an account's nonce by overwriting it.
+The new nonce must be greater than the existing nonce.
+
+#### Arguments
+
++ `address: Address` - The `Address` whose nonce is to be changed
++ `nonce: U256` - The new nonce
+
+#### Status
+
+`SUPPORTED`
+
+#### Example
+
+```bash
+curl --request POST \
+ --url http://localhost:8011/ \
+ --header 'content-type: application/json' \
+ --data '{
+ "jsonrpc": "2.0",
+ "id": "1",
+ "method": "hardhat_setNonce",
+ "params": [
+ "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049",
+ "0x1337"
+ ]
+ }'
+```
+
## `ZKS NAMESPACE`
### `zks_estimateFee`
diff --git a/src/hardhat.rs b/src/hardhat.rs
index 0cb206f9..83b9aecf 100644
--- a/src/hardhat.rs
+++ b/src/hardhat.rs
@@ -5,8 +5,12 @@ use jsonrpc_core::{BoxFuture, Result};
use jsonrpc_derive::rpc;
use zksync_basic_types::{Address, U256};
use zksync_core::api_server::web3::backend_jsonrpc::error::into_jsrpc_error;
-use zksync_types::utils::storage_key_for_eth_balance;
-use zksync_utils::u256_to_h256;
+use zksync_state::ReadStorage;
+use zksync_types::{
+ get_nonce_key,
+ utils::{decompose_full_nonce, nonces_to_full_nonce, storage_key_for_eth_balance},
+};
+use zksync_utils::{h256_to_u256, u256_to_h256};
use zksync_web3_decl::error::Web3Error;
/// Implementation of HardhatNamespaceImpl
@@ -35,6 +39,19 @@ pub trait HardhatNamespaceT {
/// A `BoxFuture` containing a `Result` with a `bool` representing the success of the operation.
#[rpc(name = "hardhat_setBalance")]
fn set_balance(&self, address: Address, balance: U256) -> BoxFuture>;
+
+ /// Modifies an account's nonce by overwriting it.
+ ///
+ /// # Arguments
+ ///
+ /// * `address` - The `Address` whose nonce is to be changed
+ /// * `nonce` - The new nonce
+ ///
+ /// # Returns
+ ///
+ /// A `BoxFuture` containing a `Result` with a `bool` representing the success of the operation.
+ #[rpc(name = "hardhat_setNonce")]
+ fn set_nonce(&self, address: Address, balance: U256) -> BoxFuture>;
}
impl HardhatNamespaceT
@@ -67,6 +84,48 @@ impl HardhatNamespaceT
}
})
}
+
+ fn set_nonce(
+ &self,
+ address: Address,
+ nonce: U256,
+ ) -> jsonrpc_core::BoxFuture> {
+ let inner = Arc::clone(&self.node);
+ Box::pin(async move {
+ match inner.write() {
+ Ok(mut inner_guard) => {
+ let nonce_key = get_nonce_key(&address);
+ let full_nonce = inner_guard.fork_storage.read_value(&nonce_key);
+ let (mut account_nonce, mut deployment_nonce) =
+ decompose_full_nonce(h256_to_u256(full_nonce));
+ if account_nonce >= nonce {
+ return Err(jsonrpc_core::Error::invalid_params(format!(
+ "Account Nonce is already set to a higher value ({}, requested {})",
+ account_nonce, nonce
+ )));
+ }
+ account_nonce = nonce;
+ if deployment_nonce >= nonce {
+ return Err(jsonrpc_core::Error::invalid_params(format!(
+ "Deployment Nonce is already set to a higher value ({}, requested {})",
+ deployment_nonce, nonce
+ )));
+ }
+ deployment_nonce = nonce;
+ let enforced_full_nonce = nonces_to_full_nonce(account_nonce, deployment_nonce);
+ println!(
+ "👷 Nonces for address {:?} have been set to {}",
+ address, nonce
+ );
+ inner_guard
+ .fork_storage
+ .set_value(nonce_key, u256_to_h256(enforced_full_nonce));
+ Ok(true)
+ }
+ Err(_) => Err(into_jsrpc_error(Web3Error::InternalError)),
+ }
+ })
+ }
}
#[cfg(test)]
@@ -94,4 +153,24 @@ mod tests {
assert_eq!(balance_after, U256::from(1337));
assert_ne!(balance_before, balance_after);
}
+
+ #[tokio::test]
+ async fn test_set_nonce() {
+ let address = Address::from_str("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049").unwrap();
+ let node = InMemoryNode::::default();
+ let hardhat = HardhatNamespaceImpl::new(node.get_inner());
+
+ let nonce_before = node.get_transaction_count(address, None).await.unwrap();
+
+ let result = hardhat.set_nonce(address, U256::from(1337)).await.unwrap();
+ assert!(result);
+
+ let nonce_after = node.get_transaction_count(address, None).await.unwrap();
+ assert_eq!(nonce_after, U256::from(1337));
+ assert_ne!(nonce_before, nonce_after);
+
+ // setting nonce lower than the current one should fail
+ let result = hardhat.set_nonce(address, U256::from(1336)).await;
+ assert!(result.is_err());
+ }
}
diff --git a/test_endpoints.http b/test_endpoints.http
index 2d2572dc..584afa46 100644
--- a/test_endpoints.http
+++ b/test_endpoints.http
@@ -279,6 +279,20 @@ content-type: application/json
POST http://localhost:8011
content-type: application/json
+{
+ "jsonrpc": "2.0",
+ "id": "2",
+ "method": "hardhat_setNonce",
+ "params": [
+ "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049",
+ "0x56"
+ ]
+}
+
+###
+POST http://localhost:8011
+content-type: application/json
+
{
"jsonrpc": "2.0",
"id": "1",