-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
new handlers logic + chain-id handler
- Loading branch information
1 parent
3ccca1a
commit 05ce4ca
Showing
9 changed files
with
484 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
...c/internal/builtin-plugins/network-manager/request-handlers/handlers/chain-id/chain-id.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import type { EthereumProvider } from "../../../../../../types/providers.js"; | ||
|
||
import { assertHardhatInvariant } from "@ignored/hardhat-vnext-errors"; | ||
import { | ||
hexStringToNumber, | ||
isPrefixedHexString, | ||
} from "@ignored/hardhat-vnext-utils/hex"; | ||
|
||
/** | ||
* This class is responsible for retrieving the chain ID of the network. | ||
* It uses the provider to fetch the chain ID via two methods: 'eth_chainId' and, | ||
* as a fallback, 'net_version' if the first one fails. The chain ID is cached | ||
* after being retrieved to avoid redundant requests. | ||
*/ | ||
export class ChainId { | ||
readonly #provider: EthereumProvider; | ||
|
||
#chainId: number | undefined; | ||
|
||
constructor(provider: EthereumProvider) { | ||
this.#provider = provider; | ||
} | ||
|
||
public async getChainId(): Promise<number> { | ||
if (this.#chainId === undefined) { | ||
try { | ||
this.#chainId = await this.#getChainIdFromEthChainId(); | ||
} catch { | ||
// If eth_chainId fails we default to net_version | ||
this.#chainId = await this.#getChainIdFromEthNetVersion(); | ||
} | ||
} | ||
|
||
return this.#chainId; | ||
} | ||
|
||
async #getChainIdFromEthChainId(): Promise<number> { | ||
const id = await this.#provider.request({ | ||
method: "eth_chainId", | ||
}); | ||
|
||
assertHardhatInvariant(typeof id === "string", "id should be a string"); | ||
|
||
return hexStringToNumber(id); | ||
} | ||
|
||
async #getChainIdFromEthNetVersion(): Promise<number> { | ||
const id = await this.#provider.request({ | ||
method: "net_version", | ||
}); | ||
|
||
assertHardhatInvariant(typeof id === "string", "id should be a string"); | ||
|
||
// There's a node returning this as decimal instead of QUANTITY. | ||
// TODO: from V2 - Document here which node does that | ||
return isPrefixedHexString(id) ? hexStringToNumber(id) : parseInt(id, 10); | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
...rc/internal/builtin-plugins/network-manager/request-handlers/handlers/chain-id/handler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import type { | ||
EthereumProvider, | ||
JsonRpcRequest, | ||
JsonRpcResponse, | ||
} from "../../../../../../types/providers.js"; | ||
import type { RequestHandler } from "../../types.js"; | ||
|
||
import { HardhatError } from "@ignored/hardhat-vnext-errors"; | ||
|
||
import { ChainId } from "./chain-id.js"; | ||
|
||
/** | ||
* This class validates that the current provider's chain ID matches | ||
* an expected value. If the actual chain ID differs from the expected one, it throws a | ||
* HardhatError to signal a network configuration mismatch. Once validated, further checks | ||
* are skipped to avoid redundant validations. | ||
*/ | ||
export class ChainIdValidatorHandler implements RequestHandler { | ||
readonly #chainId: ChainId; | ||
readonly #expectedChainId: number; | ||
#alreadyValidated = false; | ||
|
||
constructor(provider: EthereumProvider, expectedChainId: number) { | ||
this.#chainId = new ChainId(provider); | ||
this.#expectedChainId = expectedChainId; | ||
} | ||
|
||
public async handle( | ||
jsonRpcRequest: JsonRpcRequest, | ||
): Promise<JsonRpcRequest | JsonRpcResponse> { | ||
if ( | ||
jsonRpcRequest.method === "eth_chainId" || | ||
jsonRpcRequest.method === "net_version" | ||
) { | ||
return jsonRpcRequest; | ||
} | ||
|
||
if (this.#alreadyValidated) { | ||
return jsonRpcRequest; | ||
} | ||
|
||
const actualChainId = await this.#chainId.getChainId(); | ||
|
||
if (actualChainId !== this.#expectedChainId) { | ||
throw new HardhatError( | ||
HardhatError.ERRORS.NETWORK.INVALID_GLOBAL_CHAIN_ID, | ||
{ | ||
configChainId: this.#expectedChainId, | ||
connectionChainId: actualChainId, | ||
}, | ||
); | ||
} | ||
|
||
this.#alreadyValidated = true; | ||
|
||
return jsonRpcRequest; | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
...t/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/hanlders-array.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import type { RequestHandler } from "./types.js"; | ||
import type { | ||
ChainType, | ||
NetworkConnection, | ||
} from "../../../../types/network.js"; | ||
|
||
import { ChainIdValidatorHandler } from "./handlers/chain-id/handler.js"; | ||
|
||
// TODO: finish docs | ||
/** | ||
* | ||
* This function returns an handlers array based on the values in the networkConfig and.... | ||
* The order of the handlers, if all are present, is: chain handler, gas handler and accounts handler. | ||
* The order is important to get a correct result when the handler are executed | ||
*/ | ||
export function createHandlersArray<ChainTypeT extends ChainType | string>( | ||
networkConnection: NetworkConnection<ChainTypeT>, | ||
): RequestHandler[] { | ||
const requestHandlers = []; | ||
|
||
if (networkConnection.networkConfig.type === "http") { | ||
if (networkConnection.networkConfig.chainId !== undefined) { | ||
requestHandlers.push( | ||
new ChainIdValidatorHandler( | ||
networkConnection.provider, | ||
networkConnection.networkConfig.chainId, | ||
), | ||
); | ||
} | ||
} | ||
|
||
return requestHandlers; | ||
} |
20 changes: 20 additions & 0 deletions
20
v-next/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import type { | ||
JsonRpcRequest, | ||
JsonRpcResponse, | ||
} from "../../../../types/providers.js"; | ||
|
||
/** | ||
* Common interface for request handlers, which can either return a new | ||
* modified request, or a response. | ||
* | ||
* If they return a request, it's passed to the next handler, or to the "next" | ||
* function if there are no more handlers. | ||
* | ||
* If they return a response, it's returned immediately. | ||
* | ||
*/ | ||
export interface RequestHandler { | ||
handle( | ||
jsonRpcRequest: JsonRpcRequest, | ||
): Promise<JsonRpcRequest | JsonRpcResponse>; | ||
} |
34 changes: 34 additions & 0 deletions
34
v-next/hardhat/test/internal/builtin-plugins/network-manager/request-handlers/e2e.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { describe, it } from "node:test"; | ||
|
||
import { createMockedNetworkHre } from "./hooks-mock.js"; | ||
|
||
// Test that the request and its additional sub-request (when present) | ||
// are correctly modified in the "onRequest" hook handler. | ||
// These tests simulate a real scenario where the user calls "await connection.provider.request(jsonRpcRequest)". | ||
describe("request-handlers - e2e", () => { | ||
it("should successfully executes all the handlers", async () => { | ||
const hre = await createMockedNetworkHre( | ||
{ | ||
networks: { | ||
localhost: { | ||
type: "http", | ||
url: "http://localhost:8545", | ||
chainId: 1, | ||
}, | ||
}, | ||
}, | ||
{ | ||
eth_chainId: "0x1", | ||
}, | ||
); | ||
|
||
// Use the localhost network for these tests because the modifier is only | ||
// applicable to HTTP networks. EDR networks do not require this modifier. | ||
const connection = await hre.network.connect("localhost"); | ||
|
||
await connection.provider.request({ | ||
method: "eth_sendTransaction", | ||
params: [], | ||
}); | ||
}); | ||
}); |
77 changes: 77 additions & 0 deletions
77
...est/internal/builtin-plugins/network-manager/request-handlers/ethereum-mocked-provider.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import type { | ||
EIP1193Provider, | ||
JsonRpcRequest, | ||
JsonRpcResponse, | ||
RequestArguments, | ||
} from "../../../../../src/types/providers.js"; | ||
|
||
import EventEmitter from "node:events"; | ||
|
||
// This mock is used in the unit tests to simulate the return value of the "request" method | ||
export class EthereumMockedProvider | ||
extends EventEmitter | ||
implements EIP1193Provider | ||
{ | ||
// Record<methodName, value> | ||
readonly #returnValues: Record<string, any> = {}; | ||
|
||
readonly #latestParams: Record<string, RequestArguments["params"]> = {}; | ||
|
||
readonly #numberOfCalls: { [method: string]: number } = {}; | ||
|
||
// If a lambda is passed as value, it's return value is used. | ||
public setReturnValue(method: string, value: any): void { | ||
this.#returnValues[method] = value; | ||
} | ||
|
||
public getNumberOfCalls(method: string): number { | ||
if (this.#numberOfCalls[method] === undefined) { | ||
return 0; | ||
} | ||
|
||
return this.#numberOfCalls[method]; | ||
} | ||
|
||
public getLatestParams(method: string): any { | ||
return this.#latestParams[method]; | ||
} | ||
|
||
public getTotalNumberOfCalls(): number { | ||
return Object.values(this.#numberOfCalls).reduce((p, c) => p + c, 0); | ||
} | ||
|
||
public async request({ | ||
method, | ||
params = [], | ||
}: RequestArguments): Promise<any> { | ||
// stringify the params to make sure they are serializable | ||
JSON.stringify(params); | ||
|
||
this.#latestParams[method] = params; | ||
|
||
if (this.#numberOfCalls[method] === undefined) { | ||
this.#numberOfCalls[method] = 1; | ||
} else { | ||
this.#numberOfCalls[method] += 1; | ||
} | ||
|
||
let ret = this.#returnValues[method]; | ||
|
||
if (ret instanceof Function) { | ||
ret = ret(); | ||
} | ||
|
||
return ret; | ||
} | ||
|
||
public send(_method: string, _params?: unknown[]): Promise<unknown> { | ||
return Promise.resolve(null); | ||
} | ||
|
||
public sendAsync( | ||
_jsonRpcRequest: JsonRpcRequest, | ||
_callback: (error: any, jsonRpcResponse: JsonRpcResponse) => void, | ||
): void {} | ||
|
||
public async close(): Promise<void> {} | ||
} |
Oops, something went wrong.