From b21474ce1df891edd7fb7b94f8bbc079c652b85a Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 12 Nov 2020 13:11:26 -0300 Subject: [PATCH 1/2] Fix how the state of the fork block is handled --- .../provider/fork/ForkStateManager.ts | 16 +++++++++++++++- .../provider/utils/putGenesisAccounts.ts | 2 ++ .../hardhat-network/helpers/transactions.ts | 5 +++-- .../hardhat-network/provider/modules/eth.ts | 19 +++++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) 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..cdccc6c13f 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 @@ -36,7 +36,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[] = []; @@ -50,6 +51,15 @@ export class ForkStateManager implements PStateManager { this._state = ImmutableMap(); } + /** + * The forked block needs special handling because the genesis accounts are added to it. + * This is meant to be called when the initial state is final, so that _initialStateRoot + * points to this starting fork state. + */ + public updateInitialStateRoot() { + this._stateRootToState.set(this._initialStateRoot, this._state); + } + public copy(): ForkStateManager { const fsm = new ForkStateManager( this._jsonRpcClient, @@ -319,6 +329,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; 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 index a1e9222b9e..955120e58c 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/utils/putGenesisAccounts.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/utils/putGenesisAccounts.ts @@ -11,4 +11,6 @@ export async function putGenesisAccounts( const { address, account } = makeAccount(ga); await stateManager.putAccount(address, account); } + + stateManager.updateInitialStateRoot(); } 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 () { From 959ed729325123f16f1bd236201be770a2ecc3ee Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Tue, 17 Nov 2020 14:56:33 -0300 Subject: [PATCH 2/2] Move genesis accounts logic to FSM constructor --- .../provider/fork/ForkStateManager.ts | 49 ++++++++++--------- .../internal/hardhat-network/provider/node.ts | 9 ++-- .../provider/utils/putGenesisAccounts.ts | 16 ------ 3 files changed, 32 insertions(+), 42 deletions(-) delete mode 100644 packages/hardhat-core/src/internal/hardhat-network/provider/utils/putGenesisAccounts.ts 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 cdccc6c13f..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"; @@ -46,17 +48,16 @@ export class ForkStateManager implements PStateManager { constructor( private readonly _jsonRpcClient: JsonRpcClient, - private readonly _forkBlockNumber: BN + private readonly _forkBlockNumber: BN, + genesisAccounts: GenesisAccount[] = [] ) { this._state = ImmutableMap(); - } - /** - * The forked block needs special handling because the genesis accounts are added to it. - * This is meant to be called when the initial state is final, so that _initialStateRoot - * points to this starting fork state. - */ - public updateInitialStateRoot() { + for (const ga of genesisAccounts) { + const { address, account } = makeAccount(ga); + this._putAccount(address, account); + } + this._stateRootToState.set(this._initialStateRoot, this._state); } @@ -114,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 { @@ -357,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 955120e58c..0000000000 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/utils/putGenesisAccounts.ts +++ /dev/null @@ -1,16 +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); - } - - stateManager.updateInitialStateRoot(); -}