diff --git a/packages/hardhat-core/src/internal/core/jsonrpc/types/output/transaction.ts b/packages/hardhat-core/src/internal/core/jsonrpc/types/output/transaction.ts index 56abcd1f44..5eca96b6c3 100644 --- a/packages/hardhat-core/src/internal/core/jsonrpc/types/output/transaction.ts +++ b/packages/hardhat-core/src/internal/core/jsonrpc/types/output/transaction.ts @@ -3,6 +3,13 @@ import * as t from "io-ts"; import { nullable, optional } from "../../../../util/io-ts"; import { rpcAddress, rpcData, rpcHash, rpcQuantity } from "../base-types"; +const rpcAccessListItem = t.type({ + address: rpcData, + storageKeys: t.array(rpcData), +}); + +export const rpcAccessList = t.array(rpcAccessListItem); + export type RpcTransaction = t.TypeOf; export const rpcTransaction = t.type( { @@ -21,6 +28,11 @@ export const rpcTransaction = t.type( v: rpcQuantity, r: rpcQuantity, s: rpcQuantity, + + // EIP-2929/2930 properties + type: optional(rpcQuantity), + chainId: optional(rpcQuantity), + accessList: optional(rpcAccessList), }, "RpcTransaction" ); diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/fork/ForkStateManager.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/fork/ForkStateManager.ts index 7dd3b4856c..dd99ab4322 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/fork/ForkStateManager.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/fork/ForkStateManager.ts @@ -1,4 +1,5 @@ -import { StateManager } from "@ethereumjs/vm/dist/state"; +import { DefaultStateManager } from "@ethereumjs/vm/dist/state"; +import { EIP2929StateManager } from "@ethereumjs/vm/dist/state/interface"; import { Account, Address, @@ -37,7 +38,7 @@ const notCheckpointedError = (method: string) => const notSupportedError = (method: string) => new Error(`${method} is not supported when forking from remote network`); -export class ForkStateManager implements StateManager { +export class ForkStateManager implements EIP2929StateManager { private _state: State = ImmutableMap(); private _initialStateRoot: string = randomHash(); private _stateRoot: string = this._initialStateRoot; @@ -47,6 +48,12 @@ export class ForkStateManager implements StateManager { private _contextBlockNumber = this._forkBlockNumber.clone(); private _contextChanged = false; + // used by the DefaultStateManager calls + private _accessedStorage: Array>> = [new Map()]; + private _accessedStorageReverted: Array>> = [ + new Map(), + ]; + constructor( private readonly _jsonRpcClient: JsonRpcClient, private readonly _forkBlockNumber: BN @@ -371,6 +378,37 @@ export class ForkStateManager implements StateManager { return value; } + // the following methods are copied verbatim from + // DefaultStateManager + + public isWarmedAddress(address: Buffer): boolean { + return DefaultStateManager.prototype.isWarmedAddress.call(this, address); + } + + public addWarmedAddress(address: Buffer): void { + return DefaultStateManager.prototype.addWarmedAddress.call(this, address); + } + + public isWarmedStorage(address: Buffer, slot: Buffer): boolean { + return DefaultStateManager.prototype.isWarmedStorage.call( + this, + address, + slot + ); + } + + public addWarmedStorage(address: Buffer, slot: Buffer): void { + return DefaultStateManager.prototype.addWarmedStorage.call( + this, + address, + slot + ); + } + + public clearWarmedAccounts(): void { + return DefaultStateManager.prototype.clearWarmedAccounts.call(this); + } + private _putAccount(address: Address, account: Account): void { // Because the vm only ever modifies the nonce, balance and codeHash using this // method we ignore the stateRoot property diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/fork/rpcToTxData.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/fork/rpcToTxData.ts index 073a6dedfa..5424b81fbd 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/fork/rpcToTxData.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/fork/rpcToTxData.ts @@ -1,9 +1,11 @@ -import { TxData } from "@ethereumjs/tx"; +import { AccessListEIP2930TxData, TxData } from "@ethereumjs/tx"; import { RpcTransaction } from "../../../core/jsonrpc/types/output/transaction"; -export function rpcToTxData(rpcTransaction: RpcTransaction): TxData { - return { +export function rpcToTxData( + rpcTransaction: RpcTransaction +): TxData | AccessListEIP2930TxData { + const txData: AccessListEIP2930TxData = { gasLimit: rpcTransaction.gas, gasPrice: rpcTransaction.gasPrice, to: rpcTransaction.to ?? undefined, @@ -14,4 +16,19 @@ export function rpcToTxData(rpcTransaction: RpcTransaction): TxData { s: rpcTransaction.s, value: rpcTransaction.value, }; + + if (rpcTransaction.type !== undefined) { + txData.type = rpcTransaction.type; + } + if (rpcTransaction.chainId !== undefined) { + txData.chainId = rpcTransaction.chainId; + } + if (rpcTransaction.accessList !== undefined) { + txData.accessList = rpcTransaction.accessList.map((item) => [ + item.address, + item.storageKeys, + ]); + } + + return txData; } diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts index c00771e870..b2977314c7 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts @@ -37,17 +37,12 @@ import { // tslint:disable no-string-literal -interface ForkPoint { +interface ForkedBlock { networkName: string; url?: string; - /** - * Fork block number. - * This is the last observable block from the remote blockchain. - * Later blocks are all constructed by Hardhat Network. - */ - blockNumber: number; + blockToRun: number; chainId: number; - hardfork: "istanbul" | "muirGlacier"; + hardfork: "istanbul" | "muirGlacier" | "berlin"; } describe("HardhatNode", () => { @@ -552,44 +547,44 @@ describe("HardhatNode", () => { describe("full block", function () { this.timeout(120000); - // Note that here `blockNumber` is the number of the forked block, not the number of the "simulated" block. - // Tests are written to fork this block and execute all transactions of the block following the forked block. - // This means that if the forked block number is 9300076, what the test will do is: - // - setup a forked blockchain based on block 9300076 - // - fetch all transactions from 9300077 - // - create a new block with them - // - execute the whole block and save it with the rest of the blockchain - const forkPoints: ForkPoint[] = [ + const forkedBlocks: ForkedBlock[] = [ { networkName: "mainnet", url: ALCHEMY_URL, - blockNumber: 9300076, + blockToRun: 9300077, chainId: 1, hardfork: "muirGlacier", }, { networkName: "kovan", url: (ALCHEMY_URL ?? "").replace("mainnet", "kovan"), - blockNumber: 23115226, + blockToRun: 23115227, chainId: 42, hardfork: "istanbul", }, { networkName: "rinkeby", url: (ALCHEMY_URL ?? "").replace("mainnet", "rinkeby"), - blockNumber: 8004364, + blockToRun: 8004365, chainId: 4, hardfork: "istanbul", }, + { + networkName: "ropsten", + url: (ALCHEMY_URL ?? "").replace("mainnet", "ropsten"), + blockToRun: 9812365, // this block has a EIP-2930 tx + chainId: 3, + hardfork: "berlin", + }, ]; for (const { url, - blockNumber, + blockToRun, networkName, chainId, hardfork, - } of forkPoints) { + } of forkedBlocks) { it(`should run a ${networkName} block and produce the same results`, async function () { if (url === undefined || url === "") { this.skip(); @@ -597,13 +592,13 @@ describe("HardhatNode", () => { const forkConfig = { jsonRpcUrl: url, - blockNumber, + blockNumber: blockToRun - 1, }; const { forkClient } = await makeForkClient(forkConfig); const rpcBlock = await forkClient.getBlockByNumber( - new BN(blockNumber + 1), + new BN(blockToRun), true ); @@ -626,16 +621,13 @@ describe("HardhatNode", () => { const [common, forkedNode] = await HardhatNode.create(forkedNodeConfig); - let block = Block.fromBlockData(rpcToBlockData(rpcBlock), { common }); + const block = Block.fromBlockData(rpcToBlockData(rpcBlock), { + common, + freeze: false, + }); - block = Block.fromBlockData( - { - ...block, - // We wipe the receiptTrie just to be sure that it's not copied over - header: { ...block.header, receiptTrie: Buffer.alloc(32, 0) }, - }, - { common } - ); + // We wipe the receiptTrie just to be sure that it's not copied over + (block as any).header.receiptTrie = Buffer.alloc(32, 0); forkedNode["_vmTracer"].disableTracing(); @@ -650,15 +642,16 @@ describe("HardhatNode", () => { const modifiedBlock = afterBlockEvent.block; - await forkedNode["_vm"].blockchain.putBlock(modifiedBlock); - await forkedNode["_saveBlockAsSuccessfullyRun"]( - modifiedBlock, - afterBlockEvent - ); + // Restore the receipt trie + (block as any).header.receiptTrie = modifiedBlock.header.receiptTrie; - const newBlock = await forkedNode.getBlockByNumber( - new BN(blockNumber + 1) - ); + // TODO we should use modifiedBlock instead of block here, + // but we can't because of a bug in the vm + // TODO: Change this once https://github.com/ethereumjs/ethereumjs-monorepo/pull/1185 is released. + await forkedNode["_vm"].blockchain.putBlock(block); + await forkedNode["_saveBlockAsSuccessfullyRun"](block, afterBlockEvent); + + const newBlock = await forkedNode.getBlockByNumber(new BN(blockToRun)); if (newBlock === undefined) { assert.fail();