Skip to content

Commit

Permalink
feat: anvil API (#313)
Browse files Browse the repository at this point in the history
* added evm_setAccountNonce alias

* added anvil_setNonce alias

* added anvil_impersonateAccount & anvil_stopImpersonatingAccount aliases

* added anvil_mine alias

* added anvil_reset alias

* added anvil_setBalance alias

* added anvil_setCode alias

* added anvil_setStorageAt alias

* added anvil_* aliases

* added anvil_mine test

* modified hardhat_impersonateAccount & hardhat_stopImpersonatingAccount
to actually call hardhat_stopImpersonatingAccount, interfere less
with other tests

* added anvil_impersonateAccount & anvil_stopImpersonatingAccount test

* added anvil_setCode test

* added hardhat_setStorageAt and anvil_setStorageAt tests

* formatted

* removed links to anvil namespace

* split tables

* Revert "split tables"

This reverts commit 4a54fe8.

* renamed AnvilNamespaceT::hardhat_mine to anvil_mine

* Update SUPPORTED_APIS.md

Co-authored-by: AnastasiiaVashchuk <[email protected]>

* added section for evm_setAccountNonce

* changed var to let to satisfy linter

* using random address in anvil_setCode test

* added semicolon

* named test account address

* changed var to let to satisfy linter

* Update src/namespaces/anvil.rs

Co-authored-by: AnastasiiaVashchuk <[email protected]>

* removed to.be.within checks

* supressing TypeScript errors

* supressing TypeScript errors

* changed anvil_setCode code type

* using find instead of an explicit loop

---------

Co-authored-by: AnastasiiaVashchuk <[email protected]>
  • Loading branch information
vbar and AnastasiiaVashchuk authored Aug 27, 2024
1 parent c797d5f commit 706d726
Show file tree
Hide file tree
Showing 13 changed files with 517 additions and 18 deletions.
39 changes: 38 additions & 1 deletion SUPPORTED_APIS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ The `status` options are:

| Namespace | API | <div style="width:130px">Status</div> | Description |
| --- | --- | --- | --- |
| `ANVIL` | `anvil_setNonce` | `SUPPORTED` | Sets the nonce of an address.|
| `ANVIL` | `anvil_impersonateAccount` | `SUPPORTED` | Impersonate an account |
| `ANVIL` | `anvil_stopImpersonatingAccount` | `SUPPORTED` | Stop impersonating an account after having previously used `anvil_impersonateAccount` |
| `ANVIL` | `anvil_reset` | `PARTIALLY` | Resets the state of the network; cannot revert to past block numbers, unless they're in a fork |
| `ANVIL` | `anvil_mine` | `SUPPORTED` | Mine any number of blocks at once, in constant time |
| `ANVIL` | `anvil_setBalance` | `SUPPORTED` | Modifies the balance of an account |
| `ANVIL` | `anvil_setCode` | `SUPPORTED` | Sets the bytecode of a given account |
| `ANVIL` | `anvil_setStorageAt` | `SUPPORTED` | Sets the storage value at a given key for a given account |
| [`CONFIG`](#config-namespace) | [`config_getShowCalls`](#config_getshowcalls) | `SUPPORTED` | Gets the current value of `show_calls` that's originally set with `--show-calls` option |
| [`CONFIG`](#config-namespace) | [`config_getShowOutputs`](#config_getshowoutputs) | `SUPPORTED` | Gets the current value of `show_outputs` that's originally set with `--show-outputs` option |
| [`CONFIG`](#config-namespace) | [`config_getCurrentTimestamp`](#config_getcurrenttimestamp) | `SUPPORTED` | Gets the value of `current_timestamp` for the node |
Expand Down Expand Up @@ -85,7 +93,7 @@ The `status` options are:
| [`EVM`](#evm-namespace) | [`evm_revert`](#evm_revert) | `SUPPORTED` | Revert the state of the blockchain to a previous snapshot |
| `EVM` | `evm_setAccountBalance` | `NOT IMPLEMENTED` | Sets the given account's balance to the specified WEI value |
| `EVM` | `evm_setAccountCode` | `NOT IMPLEMENTED` | Sets the given account's code to the specified data |
| `EVM` | `evm_setAccountNonce` | `NOT IMPLEMENTED` | Sets the given account's nonce to the specified value |
| [`EVM`](#evm-namespace) | [`evm_setAccountNonce`](#evm_setaccountnonce) | `SUPPORTED` | Sets the given account's nonce to the specified value |
| `EVM` | `evm_setAccountStorageAt` | `NOT IMPLEMENTED` | Sets the given account's storage slot to the specified data |
| `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 |
Expand Down Expand Up @@ -1669,6 +1677,35 @@ curl --request POST \
}'
```

### `evm_setAccountNonce`

[source](src/node/evm.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

#### Example

```bash
curl --request POST \
--url http://localhost:8011/ \
--header 'content-type: application/json' \
--data '{
"jsonrpc": "2.0",
"id": "1",
"method": "evm_setAccountNonce",
"params": [
"0x36615Cf349d7F6344891B1e7CA7C72883F5dc049",
"0x1337"
]
}'
```

### `evm_increaseTime`

[source](src/node/evm.rs)
Expand Down
191 changes: 191 additions & 0 deletions e2e-tests/test/anvil-apis.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { expect } from "chai";
import { Wallet } from "zksync-web3";
import { deployContract, expectThrowsAsync, getTestProvider } from "../helpers/utils";
import { RichAccounts } from "../helpers/constants";
import { ethers } from "hardhat";
import { Deployer } from "@matterlabs/hardhat-zksync-deploy";
import * as hre from "hardhat";
import { keccak256 } from "ethers/lib/utils";
import { BigNumber } from "ethers";

const provider = getTestProvider();

describe("anvil_setBalance", function () {
it("Should update the balance of an account", async function () {
// Arrange
const userWallet = Wallet.createRandom().connect(provider);
const newBalance = ethers.utils.parseEther("42");

// Act
await provider.send("anvil_setBalance", [userWallet.address, newBalance._hex]);

// Assert
const balance = await userWallet.getBalance();
expect(balance.eq(newBalance)).to.true;
});
});

describe("anvil_setNonce", function () {
it("Should update the nonce of an account", async function () {
// Arrange
const userWallet = Wallet.createRandom().connect(provider);
const newNonce = 42;

// Act
await provider.send("anvil_setNonce", [userWallet.address, ethers.utils.hexlify(newNonce)]);

// Assert
const nonce = await userWallet.getNonce();
expect(nonce).to.equal(newNonce);
});
});

describe("anvil_mine", function () {
it("Should mine multiple blocks with a given interval", async function () {
// Arrange
const numberOfBlocks = 100;
const intervalInSeconds = 60;
const startingBlock = await provider.getBlock("latest");
const startingTimestamp: number = await provider.send("config_getCurrentTimestamp", []);

// Act
await provider.send("anvil_mine", [ethers.utils.hexlify(numberOfBlocks), ethers.utils.hexlify(intervalInSeconds)]);

// Assert
const latestBlock = await provider.getBlock("latest");
expect(latestBlock.number).to.equal(startingBlock.number + numberOfBlocks, "Block number mismatch");
expect(latestBlock.timestamp).to.equal(
startingTimestamp + (numberOfBlocks - 1) * intervalInSeconds * 1000 + 1,
"Timestamp mismatch"
);
});
});

describe("anvil_impersonateAccount & anvil_stopImpersonatingAccount", function () {
it("Should allow transfers of funds without knowing the Private Key", async function () {
// Arrange
const userWallet = Wallet.createRandom().connect(provider);
const richAccount = RichAccounts[5].Account;
const beforeBalance = await provider.getBalance(richAccount);

// Act
await provider.send("anvil_impersonateAccount", [richAccount]);

const signer = await ethers.getSigner(richAccount);
const tx = {
to: userWallet.address,
value: ethers.utils.parseEther("0.42"),
};

const recieptTx = await signer.sendTransaction(tx);
await recieptTx.wait();

await provider.send("anvil_stopImpersonatingAccount", [richAccount]);

// Assert
expect((await userWallet.getBalance()).eq(ethers.utils.parseEther("0.42"))).to.true;
expect((await provider.getBalance(richAccount)).eq(beforeBalance.sub(ethers.utils.parseEther("0.42")))).to.true;
});
});

describe("anvil_setCode", function () {
it("Should set code at an address", async function () {
// Arrange
const wallet = new Wallet(RichAccounts[0].PrivateKey);
const deployer = new Deployer(hre, wallet);

const randomWallet = Wallet.createRandom();
const address = randomWallet.address;
const artifact = await deployer.loadArtifact("Return5");
const contractCode = artifact.deployedBytecode;

// Act
await provider.send("anvil_setCode", [address, contractCode]);

// Assert
const result = await provider.send("eth_call", [
{
to: address,
data: keccak256(ethers.utils.toUtf8Bytes("value()")).substring(0, 10),
from: wallet.address,
gas: "0x1000",
gasPrice: "0x0ee6b280",
value: "0x0",
nonce: "0x1",
},
"latest",
]);
expect(BigNumber.from(result).toNumber()).to.eq(5);
});

it("Should reject invalid code", async function () {
const action = async () => {
// Arrange
const wallet = new Wallet(RichAccounts[0].PrivateKey);
const deployer = new Deployer(hre, wallet);

const address = "0x1000000000000000000000000000000000001111";
const artifact = await deployer.loadArtifact("Return5");
const contractCode = artifact.deployedBytecode;
const shortCode = contractCode.slice(0, contractCode.length - 2);

// Act
await provider.send("anvil_setCode", [address, shortCode]);
};

await expectThrowsAsync(action, "bytes must be divisible by 32");
});

it("Should update code with a different smart contract", async function () {
// Arrange
const wallet = new Wallet(RichAccounts[0].PrivateKey);
const deployer = new Deployer(hre, wallet);

const greeter = await deployContract(deployer, "Greeter", ["Hi"]);
expect(await greeter.greet()).to.eq("Hi");
const artifact = await deployer.loadArtifact("Return5");
const newContractCode = artifact.deployedBytecode;

// Act
await provider.send("anvil_setCode", [greeter.address, newContractCode]);

// Assert
const result = await provider.send("eth_call", [
{
to: greeter.address,
data: keccak256(ethers.utils.toUtf8Bytes("value()")).substring(0, 10),
from: wallet.address,
gas: "0x1000",
gasPrice: "0x0ee6b280",
value: "0x0",
nonce: "0x1",
},
"latest",
]);
expect(BigNumber.from(result).toNumber()).to.eq(5);
});
});

describe("anvil_setStorageAt", function () {
it("Should set storage at an address", async function () {
const wallet = new Wallet(RichAccounts[0].PrivateKey, provider);
const userWallet = Wallet.createRandom().connect(provider);
await wallet.sendTransaction({
to: userWallet.address,
value: ethers.utils.parseEther("3"),
});

const deployer = new Deployer(hre, userWallet);
const artifact = await deployer.loadArtifact("MyERC20");
const token = await deployer.deploy(artifact, ["MyToken", "MyToken", 18]);

const before = await provider.send("eth_getStorageAt", [token.address, "0x0", "latest"]);
expect(BigNumber.from(before).toNumber()).to.eq(0);

const value = ethers.utils.hexlify(ethers.utils.zeroPad("0x10", 32));
await provider.send("anvil_setStorageAt", [token.address, "0x0", value]);

const after = await provider.send("eth_getStorageAt", [token.address, "0x0", "latest"]);
expect(BigNumber.from(after).toNumber()).to.eq(16);
});
});
15 changes: 15 additions & 0 deletions e2e-tests/test/evm-apis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@ import { Deployer } from "@matterlabs/hardhat-zksync-deploy";

const provider = getTestProvider();

describe("evm_setAccountNonce", function () {
it("Should update the nonce of an account", async function () {
// Arrange
const userWallet = Wallet.createRandom().connect(provider);
const newNonce = 42;

// Act
await provider.send("evm_setAccountNonce", [userWallet.address, ethers.utils.hexlify(newNonce)]);

// Assert
const nonce = await userWallet.getNonce();
expect(nonce).to.equal(newNonce);
});
});

describe("evm_mine", function () {
it("Should mine one block", async function () {
// Arrange
Expand Down
34 changes: 30 additions & 4 deletions e2e-tests/test/hardhat-apis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ describe("hardhat_impersonateAccount & hardhat_stopImpersonatingAccount", functi
it("Should allow transfers of funds without knowing the Private Key", async function () {
// Arrange
const userWallet = Wallet.createRandom().connect(provider);
const beforeBalance = await provider.getBalance(RichAccounts[0].Account);
const beforeBalance = await provider.getBalance(RichAccounts[5].Account);

// Act
await provider.send("hardhat_impersonateAccount", [RichAccounts[0].Account]);
await provider.send("hardhat_impersonateAccount", [RichAccounts[5].Account]);

const signer = await ethers.getSigner(RichAccounts[0].Account);
const signer = await ethers.getSigner(RichAccounts[5].Account);
const tx = {
to: userWallet.address,
value: ethers.utils.parseEther("0.42"),
Expand All @@ -82,9 +82,11 @@ describe("hardhat_impersonateAccount & hardhat_stopImpersonatingAccount", functi
const recieptTx = await signer.sendTransaction(tx);
await recieptTx.wait();

await provider.send("hardhat_stopImpersonatingAccount", [RichAccounts[5].Account]);

// Assert
expect((await userWallet.getBalance()).eq(ethers.utils.parseEther("0.42"))).to.true;
expect((await provider.getBalance(RichAccounts[0].Account)).eq(beforeBalance.sub(ethers.utils.parseEther("0.42"))))
expect((await provider.getBalance(RichAccounts[5].Account)).eq(beforeBalance.sub(ethers.utils.parseEther("0.42"))))
.to.true;
});
});
Expand Down Expand Up @@ -165,3 +167,27 @@ describe("hardhat_setCode", function () {
expect(BigNumber.from(result).toNumber()).to.eq(5);
});
});

describe("hardhat_setStorageAt", function () {
it("Should set storage at an address", async function () {
const wallet = new Wallet(RichAccounts[0].PrivateKey, provider);
const userWallet = Wallet.createRandom().connect(provider);
await wallet.sendTransaction({
to: userWallet.address,
value: ethers.utils.parseEther("3"),
});

const deployer = new Deployer(hre, userWallet);
const artifact = await deployer.loadArtifact("MyERC20");
const token = await deployer.deploy(artifact, ["MyToken", "MyToken", 18]);

const before = await provider.send("eth_getStorageAt", [token.address, "0x0", "latest"]);
expect(BigNumber.from(before).toNumber()).to.eq(0);

const value = ethers.utils.hexlify(ethers.utils.zeroPad("0x10", 32));
await provider.send("hardhat_setStorageAt", [token.address, "0x0", value]);

const after = await provider.send("eth_getStorageAt", [token.address, "0x0", "latest"]);
expect(BigNumber.from(after).toNumber()).to.eq(16);
});
});
14 changes: 10 additions & 4 deletions e2e-tests/test/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,13 @@ describe("Greeter Smart Contract", function () {

// Validate log is created
expect(receipt.logs.length).to.greaterThanOrEqual(1);
const setGreetingLog = receipt.logs[0];
expect(setGreetingLog.address).to.equal(greeter.address);
const setGreetingLog = receipt.logs.find((log) => log.address === greeter.address);
expect(setGreetingLog).not.to.equal(null);

const eventInterface = new ethers.utils.Interface(["event LogString(string value)"]);
expect(eventInterface.parseLog(setGreetingLog).args[0]).to.equal("Greeting is being updated to Luke Skywalker");
const parsedLog = eventInterface.parseLog(setGreetingLog!);
const parsedLogArg = parsedLog.args[0].toString();
expect(parsedLogArg).to.equal("Greeting is being updated to Luke Skywalker");
});

it("Should filter event logs", async function () {
Expand All @@ -78,8 +80,12 @@ describe("Greeter Smart Contract", function () {
const setGreetingTx = await greeter.setGreeting("Luke Skywalker");
let receipt: TransactionReceipt = await setGreetingTx.wait();

expect(receipt.logs.length).to.greaterThanOrEqual(1);
const setGreetingLog = receipt.logs.find((log) => log.address === greeter.address);
expect(setGreetingLog).not.to.equal(null);

// Create filter
const topic = receipt.logs[0].topics[0];
const topic = setGreetingLog!.topics[0];
const filterId = await provider.send("eth_newFilter", [
{
fromBlock: "earliest",
Expand Down
7 changes: 2 additions & 5 deletions e2e-tests/test/zks-apis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,12 @@ describe("zks_estimateFee", function () {
// Act
const response: Fee = await provider.send("zks_estimateFee", [transaction]);
// Assert
expect(ethers.BigNumber.from(response.gas_limit)).to.eql(ethers.BigNumber.from("3606743"), "Unexpected gas_limit");
expect(ethers.BigNumber.from(response.gas_limit).toNumber()).to.eql(4048728, "Unexpected gas_limit");
expect(ethers.BigNumber.from(response.gas_per_pubdata_limit)).to.eql(
ethers.BigNumber.from("50000"),
"Unexpected gas_per_pubdata_limit"
);
expect(ethers.BigNumber.from(response.max_fee_per_gas)).to.eql(
ethers.BigNumber.from("37500000"),
"Unexpected max_fee_per_gas"
);
expect(ethers.BigNumber.from(response.max_fee_per_gas).toNumber()).to.eql(37500000, "Unexpected max_fee_per_gas");
expect(ethers.BigNumber.from(response.max_priority_fee_per_gas)).to.eql(
ethers.BigNumber.from("0"),
"Unexpected max_priority_fee_per_gas"
Expand Down
6 changes: 4 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ use jsonrpc_core::MetaIoHandler;
use zksync_basic_types::H160;

use crate::namespaces::{
ConfigurationApiNamespaceT, DebugNamespaceT, EthNamespaceT, EthTestNodeNamespaceT,
EvmNamespaceT, HardhatNamespaceT, NetNamespaceT, Web3NamespaceT, ZksNamespaceT,
AnvilNamespaceT, ConfigurationApiNamespaceT, DebugNamespaceT, EthNamespaceT,
EthTestNodeNamespaceT, EvmNamespaceT, HardhatNamespaceT, NetNamespaceT, Web3NamespaceT,
ZksNamespaceT,
};

/// List of legacy wallets (address, private key) that we seed with tokens at start.
Expand Down Expand Up @@ -166,6 +167,7 @@ async fn build_json_http<
io.extend_with(DebugNamespaceT::to_delegate(node.clone()));
io.extend_with(EthNamespaceT::to_delegate(node.clone()));
io.extend_with(EthTestNodeNamespaceT::to_delegate(node.clone()));
io.extend_with(AnvilNamespaceT::to_delegate(node.clone()));
io.extend_with(EvmNamespaceT::to_delegate(node.clone()));
io.extend_with(HardhatNamespaceT::to_delegate(node.clone()));
io.extend_with(ZksNamespaceT::to_delegate(node));
Expand Down
Loading

0 comments on commit 706d726

Please sign in to comment.