Skip to content

Commit ac16d41

Browse files
authored
Merge pull request #8371 from gultekinmakif/fix/genesis-state-merge
fix(network-manager): genesis-state merge map should use hex string
2 parents e8ee01e + 802e3d4 commit ac16d41

3 files changed

Lines changed: 78 additions & 16 deletions

File tree

.changeset/fair-mares-rest.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"hardhat": patch
3+
---
4+
5+
Account overrides that share an address with the network's built-in genesis accounts are now correctly merged into a single genesis entry.

packages/hardhat/src/internal/builtin-plugins/network-manager/edr/genesis-state.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
opHardforkFromString,
1313
type AccountOverride,
1414
} from "@nomicfoundation/edr";
15+
import { bytesToHexString } from "@nomicfoundation/hardhat-utils/hex";
1516
import { AsyncMutex } from "@nomicfoundation/hardhat-utils/synchronization";
1617
import { hexToBytes } from "ethereum-cryptography/utils";
1718

@@ -50,7 +51,7 @@ const genesisStateAndAccountsCache: WeakMap<
5051
Map<
5152
string,
5253
{
53-
genesisState: Map<Uint8Array, AccountOverride>;
54+
genesisState: Map<string, AccountOverride>;
5455
ownedAccounts: Array<{ secretKey: string; balance: bigint }>;
5556
}
5657
>
@@ -66,7 +67,7 @@ export async function getGenesisStateAndOwnedAccounts(
6667
chainType: ChainType,
6768
specId: string,
6869
): Promise<{
69-
genesisState: Map<Uint8Array, AccountOverride>;
70+
genesisState: Map<string, AccountOverride>;
7071
ownedAccounts: Array<{ secretKey: string; balance: bigint }>;
7172
}> {
7273
const cached = genesisStateAndAccountsCache
@@ -131,7 +132,7 @@ async function createGenesisStateAndOwnedAccounts(
131132
chainType: ChainType,
132133
specId: string,
133134
): Promise<{
134-
genesisState: Map<Uint8Array, AccountOverride>;
135+
genesisState: Map<string, AccountOverride>;
135136
ownedAccounts: Array<{ secretKey: string; balance: bigint }>;
136137
}> {
137138
if (microEthSignerAddress === undefined) {
@@ -142,7 +143,7 @@ async function createGenesisStateAndOwnedAccounts(
142143

143144
const ownedAccounts = await hardhatAccountsToEdrOwnedAccounts(accountsConfig);
144145

145-
const genesisState: Map<Uint8Array, AccountOverride> = new Map(
146+
const genesisState: Map<string, AccountOverride> = new Map(
146147
ownedAccounts.map(({ secretKey, balance }) => {
147148
const address = hexToBytes(addr.fromPrivateKey(secretKey));
148149
const accountOverride: AccountOverride = {
@@ -151,7 +152,7 @@ async function createGenesisStateAndOwnedAccounts(
151152
code: new Uint8Array(), // Empty account code, removing potential delegation code when forking
152153
};
153154

154-
return [address, accountOverride];
155+
return [bytesToHexString(address), accountOverride];
155156
}),
156157
);
157158

@@ -162,18 +163,25 @@ async function createGenesisStateAndOwnedAccounts(
162163
? opGenesisState(opHardforkFromString(specId))
163164
: l1GenesisState(l1HardforkFromString(specId));
164165

166+
mergeGenesisState(genesisState, chainGenesisState);
167+
168+
return { genesisState, ownedAccounts };
169+
}
170+
171+
export function mergeGenesisState(
172+
genesisState: Map<string, AccountOverride>,
173+
chainGenesisState: readonly AccountOverride[],
174+
): void {
165175
for (const account of chainGenesisState) {
166-
const existingOverride = genesisState.get(account.address);
176+
const addressKey = bytesToHexString(account.address);
177+
const existingOverride = genesisState.get(addressKey);
167178
if (existingOverride !== undefined) {
168179
// Favor the genesis state specified by the user
169-
account.balance = account.balance ?? existingOverride.balance;
170-
account.nonce = account.nonce ?? existingOverride.nonce;
171-
account.code = account.code ?? existingOverride.code;
172-
account.storage = account.storage ?? existingOverride.storage;
173-
} else {
174-
genesisState.set(account.address, account);
180+
account.balance = existingOverride.balance ?? account.balance;
181+
account.nonce = existingOverride.nonce ?? account.nonce;
182+
account.code = existingOverride.code ?? account.code;
183+
account.storage = existingOverride.storage ?? account.storage;
175184
}
185+
genesisState.set(addressKey, account);
176186
}
177-
178-
return { genesisState, ownedAccounts };
179187
}

packages/hardhat/test/internal/builtin-plugins/network-manager/edr/genesis-state.ts

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import type { EdrNetworkAccountsConfig } from "../../../../../src/types/config.js";
2+
import type { AccountOverride } from "@nomicfoundation/edr";
23

34
import assert from "node:assert/strict";
4-
import { describe, it } from "node:test";
5+
import { beforeEach, describe, it } from "node:test";
56

67
import { CANCUN, LONDON } from "@nomicfoundation/edr";
8+
import { bytesToHexString } from "@nomicfoundation/hardhat-utils/hex";
9+
import { hexToBytes } from "ethereum-cryptography/utils";
710

8-
import { getGenesisStateAndOwnedAccounts } from "../../../../../src/internal/builtin-plugins/network-manager/edr/genesis-state.js";
11+
import {
12+
getGenesisStateAndOwnedAccounts,
13+
mergeGenesisState,
14+
} from "../../../../../src/internal/builtin-plugins/network-manager/edr/genesis-state.js";
915
import { L1_CHAIN_TYPE } from "../../../../../src/internal/constants.js";
1016
import { FixedValueConfigurationVariable } from "../../../../../src/internal/core/configuration-variables.js";
1117

@@ -65,4 +71,47 @@ describe("getGenesisStateAndOwnedAccounts", () => {
6571
assert.notEqual(result2, result3);
6672
assert.notEqual(result3, result1);
6773
});
74+
75+
describe("mergeGenesisState", () => {
76+
const address = hexToBytes("0x0000F90827F1C53a10cb7A02335B175320002935");
77+
const addressKey = bytesToHexString(address);
78+
79+
let genesisState: Map<string, AccountOverride>;
80+
81+
beforeEach(() => {
82+
genesisState = new Map();
83+
});
84+
85+
it("when the chain list is empty, leaves the map untouched", () => {
86+
genesisState.set(addressKey, { address, balance: 1n });
87+
mergeGenesisState(genesisState, []);
88+
assert.equal(genesisState.size, 1);
89+
assert.equal(genesisState.get(addressKey)?.balance, 1n);
90+
});
91+
92+
it("when the chain address is not in the map, adds it as a new entry", () => {
93+
mergeGenesisState(genesisState, [{ address, balance: 42n }]);
94+
assert.equal(genesisState.size, 1);
95+
assert.equal(genesisState.get(addressKey)?.balance, 42n);
96+
});
97+
98+
it("when a chain entry collides with an existing override, user wins overlapping fields and chain fills the gaps", () => {
99+
genesisState.set(addressKey, { address, balance: 1n, nonce: 1n });
100+
101+
mergeGenesisState(genesisState, [
102+
{ address, balance: 9n, code: new Uint8Array([0x99]) },
103+
]);
104+
105+
const merged = genesisState.get(addressKey);
106+
assert.ok(merged !== undefined, "merged entry must exist at the address");
107+
assert.equal(genesisState.size, 1, "must not duplicate the entry");
108+
assert.equal(merged.balance, 1n, "user wins on overlapping field");
109+
assert.equal(merged.nonce, 1n, "user-only field stays");
110+
assert.deepEqual(
111+
merged.code,
112+
new Uint8Array([0x99]),
113+
"chain-only field fills in",
114+
);
115+
});
116+
});
68117
});

0 commit comments

Comments
 (0)