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 5d8c8527df..77c087607d 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 @@ -11,8 +11,10 @@ import { Map as ImmutableMap, Record as ImmutableRecord } from "immutable"; import { callbackify } from "util"; import { JsonRpcClient } from "../../jsonrpc/client"; +import { GenesisAccount } from "../node-types"; import { PStateManager } from "../types/PStateManager"; import { StateManager } from "../types/StateManager"; +import { makeAccount } from "../utils/makeAccount"; import { AccountState, makeAccountState } from "./Account"; import { randomHash } from "./random"; @@ -36,7 +38,8 @@ const notSupportedError = (method: string) => export class ForkStateManager implements PStateManager { private _state: State = ImmutableMap(); - private _stateRoot: string = randomHash(); + private _initialStateRoot: string = randomHash(); + private _stateRoot: string = this._initialStateRoot; private _stateRootToState: Map = new Map(); private _originalStorageCache: Map = new Map(); private _stateCheckpoints: string[] = []; @@ -45,9 +48,17 @@ export class ForkStateManager implements PStateManager { constructor( private readonly _jsonRpcClient: JsonRpcClient, - private readonly _forkBlockNumber: BN + private readonly _forkBlockNumber: BN, + genesisAccounts: GenesisAccount[] = [] ) { this._state = ImmutableMap(); + + for (const ga of genesisAccounts) { + const { address, account } = makeAccount(ga); + this._putAccount(address, account); + } + + this._stateRootToState.set(this._initialStateRoot, this._state); } public copy(): ForkStateManager { @@ -104,20 +115,7 @@ export class ForkStateManager implements PStateManager { } public async putAccount(address: Buffer, account: Account): Promise { - // Because the vm only ever modifies the nonce, balance and codeHash using this - // method we ignore the stateRoot property - const hexAddress = bufferToHex(address); - let localAccount = this._state.get(hexAddress) ?? makeAccountState(); - localAccount = localAccount - .set("nonce", bufferToHex(account.nonce)) - .set("balance", bufferToHex(account.balance)); - - // Code is set to empty string here to prevent unnecessary - // JsonRpcClient.getCode calls in getAccount method - if (account.codeHash.equals(KECCAK256_NULL)) { - localAccount = localAccount.set("code", "0x"); - } - this._state = this._state.set(hexAddress, localAccount); + this._putAccount(address, account); } public touchAccount(address: Buffer): void { @@ -319,6 +317,10 @@ export class ForkStateManager implements PStateManager { if (this._stateCheckpoints.length !== 0) { throw checkpointedError("setBlockContext"); } + if (blockNumber.eq(this._forkBlockNumber)) { + this._setStateRoot(toBuffer(this._initialStateRoot)); + return; + } if (blockNumber.gt(this._forkBlockNumber)) { this._setStateRoot(stateRoot); return; @@ -343,6 +345,23 @@ export class ForkStateManager implements PStateManager { } } + private _putAccount(address: Buffer, account: Account): void { + // Because the vm only ever modifies the nonce, balance and codeHash using this + // method we ignore the stateRoot property + const hexAddress = bufferToHex(address); + let localAccount = this._state.get(hexAddress) ?? makeAccountState(); + localAccount = localAccount + .set("nonce", bufferToHex(account.nonce)) + .set("balance", bufferToHex(account.balance)); + + // Code is set to empty string here to prevent unnecessary + // JsonRpcClient.getCode calls in getAccount method + if (account.codeHash.equals(KECCAK256_NULL)) { + localAccount = localAccount.set("code", "0x"); + } + this._state = this._state.set(hexAddress, localAccount); + } + private _setStateRoot(stateRoot: Buffer) { const newRoot = bufferToHex(stateRoot); const state = this._stateRootToState.get(newRoot); 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 ac5324615e..6b4139abe1 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/node.ts @@ -15,7 +15,6 @@ import { ecsign, hashPersonalMessage, privateToAddress, - stripZeros, toBuffer, } from "ethereumjs-util"; import EventEmitter from "events"; @@ -71,7 +70,6 @@ import { makeCommon } from "./utils/makeCommon"; import { makeForkClient } from "./utils/makeForkClient"; import { makeForkCommon } from "./utils/makeForkCommon"; import { makeStateTrie } from "./utils/makeStateTrie"; -import { putGenesisAccounts } from "./utils/putGenesisAccounts"; import { putGenesisBlock } from "./utils/putGenesisBlock"; const log = debug("hardhat:core:hardhat-network:node"); @@ -109,8 +107,11 @@ export class HardhatNode extends EventEmitter { ); common = await makeForkCommon(forkClient, forkBlockNumber); - stateManager = new ForkStateManager(forkClient, forkBlockNumber); - await putGenesisAccounts(stateManager, genesisAccounts); + stateManager = new ForkStateManager( + forkClient, + forkBlockNumber, + genesisAccounts + ); blockchain = new ForkBlockchain(forkClient, forkBlockNumber, common); } else { diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/utils/putGenesisAccounts.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/utils/putGenesisAccounts.ts deleted file mode 100644 index a1e9222b9e..0000000000 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/utils/putGenesisAccounts.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ForkStateManager } from "../fork/ForkStateManager"; -import { GenesisAccount } from "../node-types"; - -import { makeAccount } from "./makeAccount"; - -export async function putGenesisAccounts( - stateManager: ForkStateManager, - genesisAccounts: GenesisAccount[] -) { - for (const ga of genesisAccounts) { - const { address, account } = makeAccount(ga); - await stateManager.putAccount(address, account); - } -} diff --git a/packages/hardhat-core/test/internal/hardhat-network/helpers/transactions.ts b/packages/hardhat-core/test/internal/hardhat-network/helpers/transactions.ts index ec6cf34b85..6e895e3e36 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/helpers/transactions.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/helpers/transactions.ts @@ -33,12 +33,13 @@ export async function deployContract( } export async function sendTxToZeroAddress( - provider: EthereumProvider + provider: EthereumProvider, + from?: string ): Promise { const accounts = await provider.send("eth_accounts"); const burnTxParams = { - from: accounts[0], + from: from ?? accounts[0], to: zeroAddress(), value: numberToRpcQuantity(1), gas: numberToRpcQuantity(21000), diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth.ts index d253637c71..33b2cd5eb4 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth.ts @@ -697,6 +697,25 @@ describe("Eth module", function () { `Received invalid block number ${futureBlock}. Latest block number is ${firstBlock}` ); }); + + it("Should return the initial balance for the genesis accounts in the previous block after a transaction", async function () { + const blockNumber = await this.provider.send("eth_blockNumber"); + const account = DEFAULT_ACCOUNTS_ADDRESSES[0]; + + const initialBalanceBeforeTx = await this.provider.send( + "eth_getBalance", + [account, blockNumber] + ); + assert.equal(initialBalanceBeforeTx, "0xde0b6b3a7640000"); + + await sendTxToZeroAddress(this.provider, account); + + const initialBalanceAfterTx = await this.provider.send( + "eth_getBalance", + [account, blockNumber] + ); + assert.equal(initialBalanceAfterTx, "0xde0b6b3a7640000"); + }); }); describe("eth_getBlockByHash", async function () {