diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts index d05bc4d6a9..98c49665ca 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts @@ -2387,6 +2387,30 @@ Hardhat Network's forking functionality only works with blocks from at least spu // know anything about the txs in the current block } + // If this VM is running without EIP4895, but the block has withdrawals, + // we remove them and the withdrawal root from the block + if ( + !this.isEip4895Active(blockNumberOrPending) && + blockContext.withdrawals !== undefined + ) { + blockContext = Block.fromBlockData( + { + ...blockContext, + withdrawals: undefined, + header: { + ...blockContext.header, + withdrawalsRoot: undefined, + }, + }, + { + freeze: false, + common: this._vm._common, + + skipConsensusFormatValidation: true, + } + ); + } + // NOTE: This is a workaround of both an @nomicfoundation/ethereumjs-vm limitation, and // a bug in Hardhat Network. // @@ -2550,6 +2574,19 @@ Hardhat Network's forking functionality only works with blocks from at least spu return this._vm._common.gteHardfork("london"); } + public isEip4895Active(blockNumberOrPending?: bigint | "pending"): boolean { + if ( + blockNumberOrPending !== undefined && + blockNumberOrPending !== "pending" + ) { + return this._vm._common.hardforkGteHardfork( + this._selectHardfork(blockNumberOrPending), + "shanghai" + ); + } + return this._vm._common.gteHardfork("shanghai"); + } + public isPostMergeHardfork(): boolean { return hardforkGte(this.hardfork, HardforkName.MERGE); } diff --git a/packages/hardhat-core/test/internal/hardhat-network/helpers/providers.ts b/packages/hardhat-core/test/internal/hardhat-network/helpers/providers.ts index 45c3bbce3f..c2be576168 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/helpers/providers.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/helpers/providers.ts @@ -139,7 +139,7 @@ if (INFURA_URL !== undefined) { useProvider({ useJsonRpc: false, loggerEnabled: true, - forkConfig: { jsonRpcUrl: url }, + forkConfig: { jsonRpcUrl: url, blockNumber: options.forkBlockNumber }, ...options, }); }, @@ -153,7 +153,7 @@ if (INFURA_URL !== undefined) { useProvider({ useJsonRpc: false, loggerEnabled: true, - forkConfig: { jsonRpcUrl: url }, + forkConfig: { jsonRpcUrl: url, blockNumber: options.forkBlockNumber }, mining: { auto: false, interval: 10000, @@ -171,7 +171,7 @@ if (INFURA_URL !== undefined) { useProvider({ useJsonRpc: false, loggerEnabled: true, - forkConfig: { jsonRpcUrl: url }, + forkConfig: { jsonRpcUrl: url, blockNumber: options.forkBlockNumber }, ...options, }); }, @@ -188,7 +188,7 @@ if (ALCHEMY_URL !== undefined) { useProvider({ useJsonRpc: false, loggerEnabled: true, - forkConfig: { jsonRpcUrl: url }, + forkConfig: { jsonRpcUrl: url, blockNumber: options.forkBlockNumber }, ...options, }); }, diff --git a/packages/hardhat-core/test/internal/hardhat-network/helpers/useProvider.ts b/packages/hardhat-core/test/internal/hardhat-network/helpers/useProvider.ts index ffbb9c5ab5..eb1d36efaa 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/helpers/useProvider.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/helpers/useProvider.ts @@ -52,6 +52,7 @@ export interface UseProviderOptions { mempool?: HardhatNetworkMempoolConfig; coinbase?: string; chains?: HardhatNetworkChainsConfig; + forkBlockNumber?: number; } export function useProvider({ diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/forking-different-hardfork.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/forking-different-hardfork.ts new file mode 100644 index 0000000000..50c7b3506d --- /dev/null +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/forking-different-hardfork.ts @@ -0,0 +1,234 @@ +import { assert } from "chai"; + +import { + numberToRpcQuantity, + rpcDataToBigInt, +} from "../../../../src/internal/core/jsonrpc/types/base-types"; +import { workaroundWindowsCiFailures } from "../../../utils/workaround-windows-ci-failures"; +import { DAI_ADDRESS } from "../helpers/constants"; +import { setCWD } from "../helpers/cwd"; +import { + DEFAULT_ACCOUNTS_ADDRESSES, + FORKED_PROVIDERS, +} from "../helpers/providers"; + +const TOTAL_SUPPLY_SELECTOR = "0x18160ddd"; + +const SHANGHAI_HARDFORK_BLOCK_NUMBER = 17_034_870; +const MERGE_HARDFORK_BLOCK_NUMBER = 15_537_394; + +describe("Forking a block with a different hardfork", function () { + FORKED_PROVIDERS.forEach(({ rpcProvider, useProvider }) => { + workaroundWindowsCiFailures.call(this, { isFork: true }); + + describe(`Using ${rpcProvider}`, function () { + setCWD(); + + describe("shanghai hardfork", function () { + const hardfork = "shanghai"; + + describe("forking a 'shanghai' block", function () { + const forkBlockNumber = SHANGHAI_HARDFORK_BLOCK_NUMBER + 100; + + useProvider({ + hardfork, + forkBlockNumber, + }); + + it("should mine transactions", async function () { + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[1], + }, + ]); + }); + + it("should make calls in the forked block", async function () { + const daiSupply = await this.provider.send("eth_call", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DAI_ADDRESS.toString(), + data: TOTAL_SUPPLY_SELECTOR, + }, + numberToRpcQuantity(forkBlockNumber), + ]); + + assert.equal( + rpcDataToBigInt(daiSupply), + 5022305384218217259061852351n + ); + }); + + it("should make calls in blocks before the forked block", async function () { + const daiSupply = await this.provider.send("eth_call", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DAI_ADDRESS.toString(), + data: TOTAL_SUPPLY_SELECTOR, + }, + numberToRpcQuantity(forkBlockNumber - 1), + ]); + + assert.equal( + rpcDataToBigInt(daiSupply), + 5022305384218217259061852351n + ); + }); + }); + + describe("forking a 'merge' block", function () { + const forkBlockNumber = MERGE_HARDFORK_BLOCK_NUMBER + 100; + + useProvider({ + forkBlockNumber, + }); + + it("should mine transactions", async function () { + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[1], + }, + ]); + }); + + it("should make calls in the forked block", async function () { + const daiSupply = await this.provider.send("eth_call", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DAI_ADDRESS.toString(), + data: TOTAL_SUPPLY_SELECTOR, + }, + numberToRpcQuantity(forkBlockNumber), + ]); + + assert.equal( + rpcDataToBigInt(daiSupply), + 6378560137543824474512862351n + ); + }); + + it("should make calls in blocks before the forked block", async function () { + const daiSupply = await this.provider.send("eth_call", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DAI_ADDRESS.toString(), + data: TOTAL_SUPPLY_SELECTOR, + }, + numberToRpcQuantity(forkBlockNumber - 1), + ]); + + assert.equal( + rpcDataToBigInt(daiSupply), + 6378560137543824474512862351n + ); + }); + }); + }); + + describe("merge hardfork", function () { + const hardfork = "merge"; + + describe("forking a 'shanghai' block", function () { + const forkBlockNumber = SHANGHAI_HARDFORK_BLOCK_NUMBER + 100; + + useProvider({ + forkBlockNumber, + hardfork, + }); + + it("should mine transactions", async function () { + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[1], + }, + ]); + }); + + it("should make calls in the forked block", async function () { + const daiSupply = await this.provider.send("eth_call", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DAI_ADDRESS.toString(), + data: TOTAL_SUPPLY_SELECTOR, + }, + numberToRpcQuantity(forkBlockNumber), + ]); + + assert.equal( + rpcDataToBigInt(daiSupply), + 5022305384218217259061852351n + ); + }); + + it("should make calls in blocks before the forked block", async function () { + const daiSupply = await this.provider.send("eth_call", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DAI_ADDRESS.toString(), + data: TOTAL_SUPPLY_SELECTOR, + }, + numberToRpcQuantity(forkBlockNumber - 1), + ]); + + assert.equal( + rpcDataToBigInt(daiSupply), + 5022305384218217259061852351n + ); + }); + }); + + describe("forking a 'merge' block", function () { + const forkBlockNumber = MERGE_HARDFORK_BLOCK_NUMBER + 100; + + useProvider({ + forkBlockNumber, + }); + + it("should mine transactions", async function () { + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[1], + }, + ]); + }); + + it("should make calls in the forked block", async function () { + const daiSupply = await this.provider.send("eth_call", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DAI_ADDRESS.toString(), + data: TOTAL_SUPPLY_SELECTOR, + }, + numberToRpcQuantity(forkBlockNumber), + ]); + + assert.equal( + rpcDataToBigInt(daiSupply), + 6378560137543824474512862351n + ); + }); + + it("should make calls in blocks before the forked block", async function () { + const daiSupply = await this.provider.send("eth_call", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DAI_ADDRESS.toString(), + data: TOTAL_SUPPLY_SELECTOR, + }, + numberToRpcQuantity(forkBlockNumber - 1), + ]); + + assert.equal( + rpcDataToBigInt(daiSupply), + 6378560137543824474512862351n + ); + }); + }); + }); + }); + }); +});