Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add hardhat_getForkedChainId JSON-RPC method #1592

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/hardhat-network/guides/mainnet-forking.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ await network.provider.request({

This will reset Hardhat Network, starting a new instance in the state described [here](../hardhat-network/README.md#hardhat-network-initial-state).

## Getting the chainId of the original chain

You can query the original `chainId` of the network that is being forked:

```ts
await network.provider.request({
method: "hardhat_getForkedChainId",
params: [],
)};
```

Returns the `chanId` of the forked network, or null if this chain is not a fork.

## Troubleshooting

### "Project ID does not have access to archive state"
Expand Down
4 changes: 4 additions & 0 deletions docs/hardhat-network/reference/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,10 @@ Remove a transaction from the mempool
#### `hardhat_getStackTraceFailuresCount`
-->

#### `hardhat_getForkedChainId`

See the [Mainnet Forking guide](../guides/mainnet-forking.md)

#### `hardhat_impersonateAccount`

Hardhat Network allows you to send transactions impersonating specific account and contract addresses.
Expand Down
46 changes: 26 additions & 20 deletions packages/hardhat-core/src/internal/core/providers/chainId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,15 @@ export abstract class ProviderWrapperWithChainId extends ProviderWrapper {
protected async _getChainId(): Promise<number> {
if (this._chainId === undefined) {
try {
this._chainId = await this._getChainIdFromEthChainId();
this._chainId = await getChainIdFromEthChainId(this._wrappedProvider);
} catch (error) {
// If eth_chainId fails we default to net_version
this._chainId = await this._getChainIdFromEthNetVersion();
this._chainId = await getNetworkIdFromNetVersion(this._wrappedProvider);
}
}

return this._chainId;
}

private async _getChainIdFromEthChainId(): Promise<number> {
const id = (await this._wrappedProvider.request({
method: "eth_chainId",
})) as string;

return rpcQuantityToNumber(id);
}

private async _getChainIdFromEthNetVersion(): Promise<number> {
const id = (await this._wrappedProvider.request({
method: "net_version",
})) as string;

// There's a node returning this as decimal instead of QUANTITY.
// TODO: Document here which node does that
return id.startsWith("0x") ? rpcQuantityToNumber(id) : parseInt(id, 10);
}
}

export class ChainIdValidatorProvider extends ProviderWrapperWithChainId {
Expand Down Expand Up @@ -66,3 +48,27 @@ export class ChainIdValidatorProvider extends ProviderWrapperWithChainId {
return this._wrappedProvider.request(args);
}
}

export async function getChainIdFromEthChainId(
provider: EIP1193Provider
): Promise<number> {
const id = (await provider.request({
method: "eth_chainId",
})) as string;

return rpcQuantityToNumber(id);
}

export async function getNetworkIdFromNetVersion(
provider: EIP1193Provider
): Promise<number> {
const networkId = (await provider.request({
method: "net_version",
})) as string;

// There's a node returning this as decimal instead of QUANTITY.
// TODO: Document here which node does that
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested the eth_chainId and net_version methods in geth, nethermind and OE, and they all return a quantity and a decimal, respectively.

I'm ok with erring on the side of safety here, but maybe we can update this comment to say something like:

// Most nodes seem to return a decimal instead of a QUANTITY for this method, but we accept QUANTITYs too just to be extra safe

return networkId.startsWith("0x")
? rpcQuantityToNumber(networkId)
: parseInt(networkId, 10);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class JsonRpcClient {
constructor(
private _httpProvider: HttpProvider,
private _networkId: number,
private _chainId: number,
private _latestBlockNumberOnCreation: number,
private _maxReorg: number,
private _forkCachePath?: string
Expand All @@ -37,6 +38,10 @@ export class JsonRpcClient {
return this._networkId;
}

public getChainId(): number {
return this._chainId;
}

public async getDebugTraceTransaction(transactionHash: Buffer): Promise<any> {
return this._perform(
"debug_traceTransaction",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ export class HardhatModule {
case "hardhat_reset":
return this._resetAction(...this._resetParams(params));

case "hardhat_getForkedChainId":
return this._getForkedChainIdAction(
...this._getForkedChainIdParams(params)
);

case "hardhat_setLoggingEnabled":
return this._setLoggingEnabledAction(
...this._setLoggingEnabledParams(params)
Expand Down Expand Up @@ -207,6 +212,16 @@ export class HardhatModule {
return true;
}

// hardhat_getForkedChainId

private _getForkedChainIdParams(params: any[]): [] {
return validateParams(params);
}

private async _getForkedChainIdAction(): Promise<number | null> {
return (await this._node.getForkedChainId()) ?? null;
}

// hardhat_setLoggingEnabled

private _setLoggingEnabledParams(params: any[]): [boolean] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export class HardhatNode extends EventEmitter {
let initialBlockTimeOffset: BN | undefined;
let nextBlockBaseFeePerGas: BN | undefined;
let forkNetworkId: number | undefined;
let forkChainId: number | undefined;

const initialBaseFeePerGasConfig =
config.initialBaseFeePerGas !== undefined
Expand All @@ -157,6 +158,7 @@ export class HardhatNode extends EventEmitter {
common = await makeForkCommon(config);

forkNetworkId = forkClient.getNetworkId();
forkChainId = forkClient.getChainId();

this._validateHardforks(
config.forkConfig.blockNumber,
Expand Down Expand Up @@ -248,6 +250,7 @@ export class HardhatNode extends EventEmitter {
genesisAccounts,
tracingConfig,
forkNetworkId,
forkChainId,
nextBlockBaseFeePerGas
);

Expand Down Expand Up @@ -321,6 +324,7 @@ Hardhat Network's forking functionality only works with blocks from at least spu
genesisAccounts: GenesisAccount[],
tracingConfig?: TracingConfig,
private _forkNetworkId?: number,
private _forkChainId?: number,
nextBlockBaseFee?: BN
) {
super();
Expand Down Expand Up @@ -1271,6 +1275,10 @@ Hardhat Network's forking functionality only works with blocks from at least spu
);
}

public async getForkedChainId(): Promise<number | undefined> {
return this._forkChainId;
}

public async getFeeHistory(
blockCount: BN,
newestBlock: BN | "pending",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import {
numberToRpcQuantity,
rpcQuantityToNumber,
} from "../../../core/jsonrpc/types/base-types";
import {
getChainIdFromEthChainId,
getNetworkIdFromNetVersion,
} from "../../../core/providers/chainId";
import { HttpProvider } from "../../../core/providers/http";
import { JsonRpcClient } from "../../jsonrpc/client";
import { ForkConfig } from "../node-types";
Expand Down Expand Up @@ -37,7 +41,14 @@ export async function makeForkClient(
FORK_HTTP_TIMEOUT
);

const networkId = await getNetworkId(provider);
const networkId = await getNetworkIdFromNetVersion(provider);
let chainId;
try {
chainId = await getChainIdFromEthChainId(provider);
} catch (error) {
chainId = networkId;
}

const actualMaxReorg = getLargestPossibleReorg(networkId);
const maxReorg = actualMaxReorg ?? FALLBACK_MAX_REORG;

Expand Down Expand Up @@ -85,6 +96,7 @@ Please use block number ${lastSafeBlock} or wait for the block to get ${
const forkClient = new JsonRpcClient(
provider,
networkId,
chainId,
latestBlock,
maxReorg,
cacheToDiskEnabled ? forkCachePath : undefined
Expand All @@ -105,13 +117,6 @@ async function getBlockByNumber(
return rpcBlockOutput;
}

async function getNetworkId(provider: HttpProvider) {
const networkIdString = (await provider.request({
method: "net_version",
})) as string;
return parseInt(networkIdString, 10);
}

async function getLatestBlockNumber(provider: HttpProvider) {
const latestBlockString = (await provider.request({
method: "eth_blockNumber",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe("Local accounts provider", () => {
beforeEach(() => {
mock = new MockedProvider();
mock.setReturnValue(
"net_version",
"eth_chainId",
numberToRpcQuantity(MOCK_PROVIDER_CHAIN_ID)
);
mock.setReturnValue("eth_getTransactionCount", numberToRpcQuantity(0x8));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ describe("JsonRpcClient", () => {
clientWithFakeProvider = new JsonRpcClient(
fakeProvider as any,
1,
1,
fvictorio marked this conversation as resolved.
Show resolved Hide resolved
123,
3
);
Expand All @@ -87,6 +88,7 @@ describe("JsonRpcClient", () => {
clientWithFakeProvider = new JsonRpcClient(
fakeProvider as any,
1,
1,
123,
3
);
Expand All @@ -101,6 +103,7 @@ describe("JsonRpcClient", () => {
clientWithFakeProvider = new JsonRpcClient(
fakeProvider as any,
1,
1,
123,
3
);
Expand All @@ -121,6 +124,7 @@ describe("JsonRpcClient", () => {
clientWithFakeProvider = new JsonRpcClient(
fakeProvider as any,
1,
1,
123,
3,
this.tmpDir
Expand Down Expand Up @@ -148,6 +152,7 @@ describe("JsonRpcClient", () => {
clientWithFakeProvider = new JsonRpcClient(
fakeProvider as any,
1,
1,
123,
3,
this.tmpDir
Expand Down Expand Up @@ -182,6 +187,7 @@ describe("JsonRpcClient", () => {
const clientWithFakeProvider = new JsonRpcClient(
fakeProvider as any,
1,
1,
123,
3
);
Expand Down Expand Up @@ -212,6 +218,7 @@ describe("JsonRpcClient", () => {
const clientWithFakeProvider = new JsonRpcClient(
fakeProvider as any,
1,
1,
123,
3
);
Expand Down Expand Up @@ -241,6 +248,7 @@ describe("JsonRpcClient", () => {
const clientWithFakeProvider = new JsonRpcClient(
fakeProvider as any,
1,
1,
123,
3
);
Expand Down Expand Up @@ -269,6 +277,7 @@ describe("JsonRpcClient", () => {
const clientWithFakeProvider = new JsonRpcClient(
fakeProvider as any,
1,
1,
123,
3
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,19 @@ describe("Hardhat module", function () {
}
});

describe("hardhat_getForkedChainId", function () {
it("gets correct chainId", async function () {
const hardhatChainId = await this.provider.send(
"hardhat_getForkedChainId"
);
if (isFork) {
assert.equal(hardhatChainId, 1);
} else {
assert.isNull(hardhatChainId);
}
});
});

describe("hardhat_setBalance", function () {
it("should reject an invalid address", async function () {
await assertInvalidArgumentsError(
Expand Down