Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: anvil API #313

Merged
merged 36 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
052530b
added evm_setAccountNonce alias
vbar Jul 10, 2024
a2d1f0d
added anvil_setNonce alias
vbar Jul 10, 2024
7ab50f0
added anvil_impersonateAccount & anvil_stopImpersonatingAccount aliases
vbar Jul 10, 2024
37e6bf5
added anvil_mine alias
vbar Jul 10, 2024
f083ab2
added anvil_reset alias
vbar Jul 10, 2024
08eaafa
added anvil_setBalance alias
vbar Jul 10, 2024
f76d5e6
added anvil_setCode alias
vbar Jul 10, 2024
6f4c64d
added anvil_setStorageAt alias
vbar Jul 10, 2024
b5dc622
added anvil_* aliases
vbar Jul 24, 2024
7b8392b
added anvil_mine test
vbar Jul 24, 2024
34c9dd0
modified hardhat_impersonateAccount & hardhat_stopImpersonatingAccount
vbar Jul 24, 2024
0373993
added anvil_impersonateAccount & anvil_stopImpersonatingAccount test
vbar Jul 24, 2024
97b943b
added anvil_setCode test
vbar Jul 24, 2024
ac62abf
added hardhat_setStorageAt and anvil_setStorageAt tests
vbar Jul 24, 2024
a14ca13
formatted
vbar Jul 24, 2024
90a1216
Merge branch 'main' into anvil-api
vbar Jul 25, 2024
d36852f
removed links to anvil namespace
vbar Jul 31, 2024
4a54fe8
split tables
vbar Jul 31, 2024
bcb3397
Revert "split tables"
vbar Jul 31, 2024
17f7559
Merge branch 'main' into anvil-api
vbar Aug 14, 2024
db1269b
renamed AnvilNamespaceT::hardhat_mine to anvil_mine
vbar Aug 14, 2024
781c5a0
Update SUPPORTED_APIS.md
vbar Aug 14, 2024
0ed672c
added section for evm_setAccountNonce
vbar Aug 14, 2024
5857b4b
changed var to let to satisfy linter
vbar Aug 14, 2024
879ac94
using random address in anvil_setCode test
vbar Aug 14, 2024
6befae3
added semicolon
vbar Aug 14, 2024
5ded038
named test account address
vbar Aug 14, 2024
ee3da1e
changed var to let to satisfy linter
vbar Aug 15, 2024
2440f93
Update src/namespaces/anvil.rs
vbar Aug 15, 2024
5ff2701
removed to.be.within checks
vbar Aug 15, 2024
1f8a1f4
supressing TypeScript errors
vbar Aug 15, 2024
2fd9f3f
supressing TypeScript errors
vbar Aug 15, 2024
bf3e63b
Merge branch 'main' into anvil-api
vbar Aug 20, 2024
18fb136
changed anvil_setCode code type
vbar Aug 20, 2024
bb26813
using find instead of an explicit loop
vbar Aug 27, 2024
0c62782
Merge branch 'main' into anvil-api
AnastasiiaVashchuk Aug 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 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 a given account |
vbar marked this conversation as resolved.
Show resolved Hide resolved
| `ANVIL` | `anvil_impersonateAccount` | `SUPPORTED` | Impersonate an account |
AnastasiiaVashchuk marked this conversation as resolved.
Show resolved Hide resolved
| `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 |
AnastasiiaVashchuk marked this conversation as resolved.
Show resolved Hide resolved
| `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
190 changes: 190 additions & 0 deletions e2e-tests/test/anvil-apis.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
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 () {
AnastasiiaVashchuk marked this conversation as resolved.
Show resolved Hide resolved
AnastasiiaVashchuk marked this conversation as resolved.
Show resolved Hide resolved
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 () {
AnastasiiaVashchuk marked this conversation as resolved.
Show resolved Hide resolved
// Arrange
const userWallet = Wallet.createRandom().connect(provider);
const beforeBalance = await provider.getBalance(RichAccounts[5].Account);
AnastasiiaVashchuk marked this conversation as resolved.
Show resolved Hide resolved

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

const signer = await ethers.getSigner(RichAccounts[5].Account);
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", [RichAccounts[5].Account]);

// Assert
expect((await userWallet.getBalance()).eq(ethers.utils.parseEther("0.42"))).to.true;
expect((await provider.getBalance(RichAccounts[5].Account)).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 address = "0x1000000000000000000000000000000000001111";
const artifact = await deployer.loadArtifact("Return5");
const contractCode = [...ethers.utils.arrayify(artifact.deployedBytecode)];

// Act
await provider.send("anvil_setCode", [address, contractCode]);
AnastasiiaVashchuk marked this conversation as resolved.
Show resolved Hide resolved

// 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 = [...ethers.utils.arrayify(artifact.deployedBytecode)];
const shortCode = contractCode.slice(0, contractCode.length - 1);

// 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 = [...ethers.utils.arrayify(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);
});
});
20 changes: 17 additions & 3 deletions e2e-tests/test/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +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);
var setGreetingLog = null;
for (var i = 0; setGreetingLog == null && i < receipt.logs.length; ++i) {
if (receipt.logs[i].address == greeter.address) {
setGreetingLog = receipt.logs[i];
}
}
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");
Expand All @@ -78,8 +83,17 @@ 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);
var setGreetingLog = null;
for (var i = 0; setGreetingLog == null && i < receipt.logs.length; ++i) {
if (receipt.logs[i].address == greeter.address) {
setGreetingLog = receipt.logs[i];
}
}
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: 4 additions & 3 deletions e2e-tests/test/zks-apis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ 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.be.within(3606743, 5868728, "Unexpected gas_limit");
AnastasiiaVashchuk marked this conversation as resolved.
Show resolved Hide resolved
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"),
expect(ethers.BigNumber.from(response.max_fee_per_gas).toNumber()).to.be.within(
25500297,
37500000,
AnastasiiaVashchuk marked this conversation as resolved.
Show resolved Hide resolved
"Unexpected max_fee_per_gas"
);
expect(ethers.BigNumber.from(response.max_priority_fee_per_gas)).to.eql(
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