Skip to content

Commit a175880

Browse files
Merge pull request #7914 from NomicFoundation/support-eth-get-proof
Support eth_getProof
2 parents 69dbeba + 4ac9511 commit a175880

File tree

4 files changed

+236
-34
lines changed

4 files changed

+236
-34
lines changed

.changeset/silver-toes-wonder.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+
Added support for `eth_getProof` ([3345](https://github.com/NomicFoundation/hardhat/issues/3345)).

pnpm-lock.yaml

Lines changed: 33 additions & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v-next/hardhat/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
"typescript": "~5.8.0"
8888
},
8989
"dependencies": {
90-
"@nomicfoundation/edr": "0.12.0-next.22",
90+
"@nomicfoundation/edr": "0.12.0-next.23",
9191
"@nomicfoundation/hardhat-errors": "workspace:^3.0.6",
9292
"@nomicfoundation/hardhat-utils": "workspace:^3.0.6",
9393
"@nomicfoundation/hardhat-vendored": "workspace:^3.0.1",

v-next/hardhat/test/internal/builtin-plugins/network-manager/edr/edr-provider.ts

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { before, describe, it } from "node:test";
1313
import { HardhatError } from "@nomicfoundation/hardhat-errors";
1414
import { assertRejectsWithHardhatError } from "@nomicfoundation/hardhat-test-utils";
1515
import { mkdtemp } from "@nomicfoundation/hardhat-utils/fs";
16+
import { numberToHexString } from "@nomicfoundation/hardhat-utils/hex";
1617

1718
import { createHardhatRuntimeEnvironment } from "../../../../../src/hre.js";
1819
import {
@@ -205,6 +206,202 @@ describe("edr-provider", () => {
205206
}
206207
assert.fail("Function did not throw any error");
207208
});
209+
210+
describe("eth_getProof", () => {
211+
let tmpCacheDir: string;
212+
213+
before(async () => {
214+
tmpCacheDir = await mkdtemp("edr-provider-eth-getProof");
215+
});
216+
217+
it("should return account proof on local network", async () => {
218+
const { provider } = await hre.network.connect();
219+
220+
const accounts = await provider.request({
221+
method: "eth_accounts",
222+
});
223+
224+
assert.ok(
225+
Array.isArray(accounts) && accounts.length > 0,
226+
"Accounts should be a non empty array",
227+
);
228+
229+
const account = accounts[0];
230+
231+
const proof = await provider.request({
232+
method: "eth_getProof",
233+
params: [
234+
account,
235+
[], // storage keys (empty array)
236+
"latest",
237+
],
238+
});
239+
240+
assert.equal(
241+
proof.address,
242+
account,
243+
"Address should match the requested account",
244+
);
245+
246+
// Default hardhat accounts have 10_000 ETH
247+
assert.equal(
248+
proof.balance,
249+
numberToHexString(10_000n * 10n ** 18n),
250+
"Balance should be 10_000 ETH",
251+
);
252+
253+
// Check cryptographic proof structure
254+
assert.ok(
255+
Array.isArray(proof.accountProof) && proof.accountProof.length > 0,
256+
"accountProof should be a non empty array",
257+
);
258+
assert.ok(
259+
Array.isArray(proof.storageProof) && proof.storageProof.length === 0,
260+
// we passed `[]` as storage keys in the request
261+
"StorageProof should be an empty array for 0 storage keys",
262+
);
263+
});
264+
265+
it("should return storage proof for contract on local network", async () => {
266+
const { provider } = await hre.network.connect();
267+
268+
// Define arbitrary address and storage key
269+
const contractAddress = "0x1234567890123456789012345678901234567890";
270+
const slotZero =
271+
"0x0000000000000000000000000000000000000000000000000000000000000000";
272+
const valueOne =
273+
"0x0000000000000000000000000000000000000000000000000000000000000001";
274+
275+
// Set storage directly (bypassing deployment & mining)
276+
await provider.request({
277+
method: "hardhat_setStorageAt",
278+
params: [contractAddress, slotZero, valueOne],
279+
});
280+
281+
const proof = await provider.request({
282+
method: "eth_getProof",
283+
params: [contractAddress, [slotZero], "latest"],
284+
});
285+
286+
assert.equal(proof.address, contractAddress, "Address should match");
287+
assert.equal(
288+
proof.storageProof.length,
289+
1,
290+
"Should return 1 storage proof",
291+
);
292+
assert.equal(
293+
proof.storageProof[0].key,
294+
slotZero,
295+
"Storage key should match",
296+
);
297+
assert.equal(
298+
proof.storageProof[0].value,
299+
"0x1",
300+
"Storage value should be 0x1",
301+
);
302+
assert.ok(
303+
proof.storageProof[0].proof.length > 0,
304+
"Storage proof should not be empty",
305+
);
306+
});
307+
308+
it("should return proof on fork without local changes", async () => {
309+
// NOTE: Accounts are disabled in the network configuration to prevent local state changes, which would
310+
// cause eth_getProof to fail with a "proof not supported in fork mode" error.
311+
312+
const forkedHre = await createHardhatRuntimeEnvironment({
313+
paths: { cache: tmpCacheDir },
314+
networks: {
315+
edrOptimism: {
316+
type: "edr-simulated",
317+
chainId: 10,
318+
chainType: "op",
319+
// Disable default accounts to prevent local state modification
320+
accounts: [],
321+
forking: {
322+
url: "https://mainnet.optimism.io",
323+
enabled: true,
324+
},
325+
},
326+
},
327+
});
328+
329+
const { provider } = await forkedHre.network.connect("edrOptimism");
330+
331+
try {
332+
// WETH Optimism address
333+
const targetAddress = "0x4200000000000000000000000000000000000006";
334+
335+
const proof = await provider.request({
336+
method: "eth_getProof",
337+
params: [targetAddress, [], "latest"],
338+
});
339+
340+
assert.equal(
341+
proof.address,
342+
targetAddress,
343+
"Should return proof for the requested address",
344+
);
345+
assert.ok(
346+
proof.accountProof.length > 0,
347+
"Should have account proof from remote",
348+
);
349+
} finally {
350+
await provider.close();
351+
}
352+
});
353+
354+
it("should throw error on fork with local changes", async () => {
355+
// NOTE: Accounts are NOT disabled in the network configuration, so Hardhat modifies the state by
356+
// injecting default accounts, causing eth_getProof to fail with a
357+
// "proof not supported in fork mode" error.
358+
359+
const forkedHre = await createHardhatRuntimeEnvironment({
360+
paths: { cache: tmpCacheDir },
361+
networks: {
362+
edrOptimism: {
363+
type: "edr-simulated",
364+
chainId: 10,
365+
chainType: "op",
366+
forking: {
367+
url: "https://mainnet.optimism.io",
368+
enabled: true,
369+
},
370+
},
371+
},
372+
});
373+
374+
const { provider } = await forkedHre.network.connect("edrOptimism");
375+
376+
try {
377+
const accounts = await provider.request({ method: "eth_accounts" });
378+
const sender = accounts[0];
379+
380+
// We expect this to fail
381+
await provider.request({
382+
method: "eth_getProof",
383+
params: [sender, [], "latest"],
384+
});
385+
} catch (error) {
386+
assert.ok(
387+
ProviderError.isProviderError(error),
388+
"Error is not a ProviderError",
389+
);
390+
391+
assert.match(
392+
error.message,
393+
/proof is not supported in fork mode when local changes have been made/,
394+
"Error message should explain lack of support for local blocks on fork",
395+
);
396+
397+
return;
398+
} finally {
399+
await provider.close();
400+
}
401+
402+
assert.fail("eth_getProof should have thrown an error");
403+
});
404+
});
208405
});
209406

210407
describe("EdrProvider#onSubscriptionEvent", () => {

0 commit comments

Comments
 (0)