Skip to content
Closed
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ea27270
Add a new extenders function for providers
nicosantangelo May 7, 2023
bc2ffd4
Delete the ExtendersManager class in favor of a simple array
nicosantangelo May 9, 2023
5cd65ea
Delete package-lock.json
nicosantangelo May 9, 2023
53cfffc
First approach to a lazy initialization provider
nicosantangelo May 12, 2023
b65ec8b
Clean up the code a bit and name functions
nicosantangelo May 14, 2023
4c8cb3f
CustomError
nicosantangelo May 14, 2023
d37f1ad
Rename extenders dropping the 'Manager'
nicosantangelo May 14, 2023
8f129a7
Rename internal extenders prop
nicosantangelo May 15, 2023
b6075a1
Be more specific on when the provider is initialized Lazily
nicosantangelo May 15, 2023
e18e61f
Enforce leading _ in private class properties
fvictorio May 16, 2023
b4f7749
Avoid floating promise in sendAsync
fvictorio May 16, 2023
1dd01be
Fix strict-boolean-expression warnings
fvictorio May 16, 2023
1e54d33
Allos Function type for EventEmitter functions
fvictorio May 16, 2023
72b3295
Add UNINITIALIZED_PROVIDER error code
fvictorio May 16, 2023
8c5f3f3
Create mean-crews-wait.md
fvictorio May 16, 2023
2e58fe7
Make ProviderFactory type internal
nicosantangelo May 16, 2023
32e7ae4
Allow for EventEmitter methods before initialization
nicosantangelo May 16, 2023
5fdf636
Reuse type
nicosantangelo May 16, 2023
e24d580
Typos and better English
nicosantangelo May 16, 2023
26becac
Forego of using sideeffects for creating providers
nicosantangelo May 16, 2023
13a98a7
Docs
nicosantangelo May 17, 2023
14252d1
LazyInitializationProvider tests
nicosantangelo May 18, 2023
91a1659
construction tests
nicosantangelo May 18, 2023
eb5a8c6
Export the ProviderWrapper helper class
nicosantangelo May 18, 2023
53fd5fa
Fix linting errors
nicosantangelo May 18, 2023
3e4b413
Provider wrapper tests
nicosantangelo May 18, 2023
8197e93
Supply the configuration to the extendProvider extenders
nicosantangelo May 19, 2023
fd17e6e
Supply the network name to the extendProvider extenders
nicosantangelo May 19, 2023
6362de0
Add a way to access the wrapped provider and to initialize it
nicosantangelo May 23, 2023
7d08583
Only initialize the provider once on init
nicosantangelo May 23, 2023
83b0c48
Handle concurrent calls to init()
fvictorio May 24, 2023
76948e4
Use done callback in sendAsync tests
fvictorio May 24, 2023
4ac2e52
Rename LazyInitializationProvider to LazyInitializationProviderAdapter
nicosantangelo May 24, 2023
6223bd6
Update comment
nicosantangelo May 24, 2023
2d42b3a
Make this a minor change instead of patch
fvictorio May 25, 2023
a60b1cd
More explicit support for non-async provider extenders
nicosantangelo May 26, 2023
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
5 changes: 5 additions & 0 deletions .changeset/mean-crews-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hardhat": patch
---

Added support for extending the network provider
6 changes: 6 additions & 0 deletions config/eslint/eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ module.exports = {
format: ["camelCase", "UPPER_CASE"],
leadingUnderscore: "allow",
},
{
selector: ["classProperty"],
modifiers: ["private"],
format: ["camelCase", "UPPER_CASE"],
leadingUnderscore: "require",
},
{
selector: "enumMember",
format: ["UPPER_CASE"],
Expand Down
54 changes: 54 additions & 0 deletions docs/src/content/hardhat-runner/docs/advanced/building-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,60 @@ Hello, Hardhat!

This is literally all it takes to put together a plugin for Hardhat. Now `hi` is available to be used in the Hardhat console, your tasks, tests and other plugins.

## Extending the Hardhat provider

Next, we can take a look at how to add features on top of the default provider offered by Hardhat, found on `hre.network.provider`. Doing this, any extra functionality you add will be available everywhere, just as it would if you extend the [Hardhat Runtime Environment](#extending-the-hardhat-runtime-environment).

The Hardhat provider is configured through a queue of extension functions that you can add to by using the `extendProvider()` function. It receives one parameter which is a callback to be executed after first call to `hre.network.provider.request()` is made. This happens only once. If `extendProvider` is called multiple times, its callbacks will be executed in order, and Hardhat will wait on each one to finish before executing the next one. Returning an entirely new provider is possible but not advisable.

It's important to keep in mind that after all callbacks are executed, the provider will be wrapped by Hardhat one last time so it's backwards compatible with old non-standard implementations. This boils down to adding both `send` and `sendAsync` methods, which defer to the main `request`. Lastly, each callback added is `async`, so we should be careful when adding any functionality that might take a long time to resolve.

For example, adding the following to `hardhat.config.js`:

```js
import { ProviderWrapper } from 'hardhat/plugins'

class FixedGasProvider extends ProviderWrapper {
constructor(
public readonly gasPrice,
protected readonly _wrappedProvider
) {
super(_wrappedProvider);
}

public async request(args) {
if (args.method === "eth_estimateGas") {
return this.gasPrice;
} else if (args.method === "eth_sendTransaction") {
const params = this._getParams(args);
const tx = params[0];

// let's pretend that EIP-1559 never happened
tx.gasPrice = this.gasPrice;
}

return this._wrappedProvider.request(args);
}
}

extendProvider(async (provider, config, network) => {
// We fix the gas price to be set by the config or to a random high value
const gasPrice = config.fixedGasPrice || "0x1000000"
const newProvider = new FixedGasProvider(gasPrice, provider);
return newProvider;
});
```

Will make the `hre` provider use that gas price value everywhere it's used:

```js
task("request", async (args, hre) => {
await hre.network.request(/*{ method arguments }*/); // this will run FixedGasProvider's request method above
});

module.exports = {};
```

## Using the Hardhat TypeScript plugin boilerplate

For a complete example of a plugin you can take a look at the [Hardhat TypeScript plugin boilerplate project](https://github.com/NomicFoundation/hardhat-ts-plugin-boilerplate/).
Expand Down
7 changes: 2 additions & 5 deletions packages/hardhat-core/src/builtin-tasks/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,10 @@ subtask(TASK_NODE_GET_PROVIDER)
let provider = network.provider;

if (network.name !== HARDHAT_NETWORK_NAME) {
const networkConfig = config.networks[HARDHAT_NETWORK_NAME];

log(`Creating hardhat provider for JSON-RPC server`);
provider = createProvider(
provider = await createProvider(
config,
HARDHAT_NETWORK_NAME,
networkConfig,
config.paths,
artifacts
);
}
Expand Down
6 changes: 4 additions & 2 deletions packages/hardhat-core/src/internal/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@ async function main() {
Reporter.setEnabled(true);
}

const envExtenders = ctx.extendersManager.getExtenders();
const envExtenders = ctx.environmentExtenders;
const providerExtenders = ctx.providerExtenders;
const taskDefinitions = ctx.tasksDSL.getTaskDefinitions();

const [abortAnalytics, hitPromise] = await analytics.sendTaskHit(taskName);
Expand Down Expand Up @@ -266,7 +267,8 @@ async function main() {
taskDefinitions,
envExtenders,
ctx.experimentalHardhatNetworkMessageTraceHooks,
userConfig
userConfig,
providerExtenders
);

ctx.setHardhatRuntimeEnvironment(env);
Expand Down
7 changes: 5 additions & 2 deletions packages/hardhat-core/src/internal/context.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {
ConfigExtender,
EnvironmentExtender,
ExperimentalHardhatNetworkMessageTraceHook,
HardhatRuntimeEnvironment,
ProviderExtender,
} from "../types";

import { ExtenderManager } from "./core/config/extenders";
import { assertHardhatInvariant, HardhatError } from "./core/errors";
import { ERRORS } from "./core/errors-list";
import { TasksDSL } from "./core/tasks/dsl";
Expand Down Expand Up @@ -45,8 +46,10 @@ export class HardhatContext {
}

public readonly tasksDSL = new TasksDSL();
public readonly extendersManager = new ExtenderManager();
public readonly environmentExtenders: EnvironmentExtender[] = [];
public environment?: HardhatRuntimeEnvironment;
public readonly providerExtenders: ProviderExtender[] = [];

public readonly configExtenders: ConfigExtender[] = [];

// NOTE: This is experimental and will be removed. Please contact our team if
Expand Down
23 changes: 21 additions & 2 deletions packages/hardhat-core/src/internal/core/config/config-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ConfigurableTaskDefinition,
EnvironmentExtender,
ExperimentalHardhatNetworkMessageTraceHook,
ProviderExtender,
TaskArguments,
} from "../../../types";
import { HardhatContext } from "../../context";
Expand Down Expand Up @@ -126,15 +127,33 @@ export const types = argumentTypes;
*/
export function extendEnvironment(extender: EnvironmentExtender) {
const ctx = HardhatContext.getHardhatContext();
const extenderManager = ctx.extendersManager;
extenderManager.add(extender);
ctx.environmentExtenders.push(extender);
}

/**
* Register a config extender what will be run after the
* Hardhat Runtime Environment is initialized.
*
* @param extender A function that receives the resolved config
* to be modified and the config provided by the user
*/
export function extendConfig(extender: ConfigExtender) {
const ctx = HardhatContext.getHardhatContext();
ctx.configExtenders.push(extender);
}

/**
* Register a provider extender what will be run after the
* Hardhat Runtime Environment is initialized.
*
* @param extender A function that receives the current provider
* and returns a new one.
*/
export function extendProvider(extender: ProviderExtender) {
const ctx = HardhatContext.getHardhatContext();
ctx.providerExtenders.push(extender);
}

// NOTE: This is experimental and will be removed. Please contact our team
// if you are planning to use it.
export function experimentalAddHardhatNetworkMessageTraceHook(
Expand Down
13 changes: 0 additions & 13 deletions packages/hardhat-core/src/internal/core/config/extenders.ts

This file was deleted.

9 changes: 9 additions & 0 deletions packages/hardhat-core/src/internal/core/errors-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,15 @@ Rename the file to use the .cjs to fix this problem.`,
description: `Your project is an ESM project (you have "type": "module" set in your package.json) and you are trying to initialize a TypeScript project. This is not supported yet.`,
shouldBeReported: false,
},
UNINITIALIZED_PROVIDER: {
number: 21,
message:
"You tried to access an uninitialized provider. To initialize the provider, make sure you first call `.init()` or any method that hits a node like request, send or sendAsync.",
title: "Uninitialized provider",
description: `You tried to access an uninitialized provider. This is most likely caused by using the internal wrapped provider directly before using it to send a request or initializing it.
To initialize the provider, make sure you first call \`.init()\` or any method that hits a node like request, send or sendAsync.`,
shouldBeReported: true,
},
},
NETWORK: {
CONFIG_NOT_FOUND: {
Expand Down
21 changes: 14 additions & 7 deletions packages/hardhat-core/src/internal/core/providers/construction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import type {
BoundExperimentalHardhatNetworkMessageTraceHook,
EIP1193Provider,
EthereumProvider,
HardhatConfig,
HardhatNetworkConfig,
HDAccountsUserConfig,
HttpNetworkAccountsUserConfig,
HttpNetworkConfig,
NetworkConfig,
ProjectPathsConfig,
ProviderExtender,
} from "../../../types";

import type {
Expand Down Expand Up @@ -46,14 +47,16 @@ function importProvider<ModuleT, ProviderNameT extends keyof ModuleT>(
return mod[name];
}

export function createProvider(
export async function createProvider(
config: HardhatConfig,
networkName: string,
networkConfig: NetworkConfig,
paths?: ProjectPathsConfig,
artifacts?: Artifacts,
experimentalHardhatNetworkMessageTraceHooks: BoundExperimentalHardhatNetworkMessageTraceHook[] = []
): EthereumProvider {
experimentalHardhatNetworkMessageTraceHooks: BoundExperimentalHardhatNetworkMessageTraceHook[] = [],
extenders: ProviderExtender[] = []
): Promise<EthereumProvider> {
let eip1193Provider: EIP1193Provider;
const networkConfig = config.networks[networkName];
const paths = config.paths;

if (networkName === HARDHAT_NETWORK_NAME) {
const hardhatNetConfig = networkConfig as HardhatNetworkConfig;
Expand Down Expand Up @@ -132,7 +135,11 @@ export function createProvider(
);
}

const wrappedProvider = applyProviderWrappers(eip1193Provider, networkConfig);
let wrappedProvider = applyProviderWrappers(eip1193Provider, networkConfig);

for (const extender of extenders) {
wrappedProvider = await extender(wrappedProvider, config, networkName);
}

const BackwardsCompatibilityProviderAdapter = importProvider<
typeof import("./backwards-compatibility"),
Expand Down
Loading