hardhat 3.15.0 → 3.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/dist/src/internal/builtin-plugins/network-manager/accounts/derive-private-keys.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/accounts/derive-private-keys.js +0 -1
- package/dist/src/internal/builtin-plugins/network-manager/accounts/derive-private-keys.js.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/config-resolution.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/config-resolution.js +1 -0
- package/dist/src/internal/builtin-plugins/network-manager/config-resolution.js.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/edr/edr-constants.d.ts +1 -0
- package/dist/src/internal/builtin-plugins/network-manager/edr/edr-constants.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/edr/edr-constants.js +2 -0
- package/dist/src/internal/builtin-plugins/network-manager/edr/edr-constants.js.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/edr/edr-provider.d.ts +15 -0
- package/dist/src/internal/builtin-plugins/network-manager/edr/edr-provider.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/edr/edr-provider.js +43 -5
- package/dist/src/internal/builtin-plugins/network-manager/edr/edr-provider.js.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/edr/genesis-state.js +4 -4
- package/dist/src/internal/builtin-plugins/network-manager/edr/genesis-state.js.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.d.ts +3 -2
- package/dist/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.js +13 -5
- package/dist/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.js.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/http-provider.js +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/http-provider.js.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/provider-errors.d.ts +20 -0
- package/dist/src/internal/builtin-plugins/network-manager/provider-errors.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/provider-errors.js +24 -0
- package/dist/src/internal/builtin-plugins/network-manager/provider-errors.js.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/request-handlers/handlers/accounts/local-accounts.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/request-handlers/handlers/accounts/local-accounts.js +10 -13
- package/dist/src/internal/builtin-plugins/network-manager/request-handlers/handlers/accounts/local-accounts.js.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/automatic-gas-handler.d.ts +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/automatic-gas-handler.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/automatic-gas-handler.js +2 -2
- package/dist/src/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/automatic-gas-handler.js.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/multiplied-gas-estimation.d.ts +11 -3
- package/dist/src/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/multiplied-gas-estimation.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/multiplied-gas-estimation.js +53 -13
- package/dist/src/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/multiplied-gas-estimation.js.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/request-handlers/handlers-array.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/request-handlers/handlers-array.js +19 -1
- package/dist/src/internal/builtin-plugins/network-manager/request-handlers/handlers-array.js.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/type-extensions/config.d.ts +2 -0
- package/dist/src/internal/builtin-plugins/network-manager/type-extensions/config.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/type-validation.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/network-manager/type-validation.js +1 -0
- package/dist/src/internal/builtin-plugins/network-manager/type-validation.js.map +1 -1
- package/dist/src/internal/builtin-plugins/node/json-rpc/handler.js +12 -1
- package/dist/src/internal/builtin-plugins/node/json-rpc/handler.js.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity-test/eip712/canonicalize.d.ts +1 -1
- package/dist/src/internal/builtin-plugins/solidity-test/eip712/canonicalize.d.ts.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity-test/eip712/canonicalize.js +13 -11
- package/dist/src/internal/builtin-plugins/solidity-test/eip712/canonicalize.js.map +1 -1
- package/dist/src/internal/builtin-plugins/solidity-test/formatters.js +10 -2
- package/dist/src/internal/builtin-plugins/solidity-test/formatters.js.map +1 -1
- package/dist/src/internal/core/configuration-variables.d.ts +8 -0
- package/dist/src/internal/core/configuration-variables.d.ts.map +1 -1
- package/dist/src/internal/core/configuration-variables.js +9 -9
- package/dist/src/internal/core/configuration-variables.js.map +1 -1
- package/dist/src/internal/edr/exports.d.ts +2 -0
- package/dist/src/internal/edr/exports.d.ts.map +1 -0
- package/dist/src/internal/edr/exports.js +2 -0
- package/dist/src/internal/edr/exports.js.map +1 -0
- package/package.json +6 -5
- package/src/internal/builtin-plugins/network-manager/accounts/derive-private-keys.ts +0 -1
- package/src/internal/builtin-plugins/network-manager/config-resolution.ts +1 -0
- package/src/internal/builtin-plugins/network-manager/edr/edr-constants.ts +3 -0
- package/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts +52 -1
- package/src/internal/builtin-plugins/network-manager/edr/genesis-state.ts +5 -5
- package/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts +17 -3
- package/src/internal/builtin-plugins/network-manager/http-provider.ts +1 -1
- package/src/internal/builtin-plugins/network-manager/provider-errors.ts +30 -0
- package/src/internal/builtin-plugins/network-manager/request-handlers/handlers/accounts/local-accounts.ts +10 -26
- package/src/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/automatic-gas-handler.ts +3 -1
- package/src/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/multiplied-gas-estimation.ts +73 -18
- package/src/internal/builtin-plugins/network-manager/request-handlers/handlers-array.ts +24 -0
- package/src/internal/builtin-plugins/network-manager/type-extensions/config.ts +2 -0
- package/src/internal/builtin-plugins/network-manager/type-validation.ts +6 -0
- package/src/internal/builtin-plugins/node/json-rpc/handler.ts +15 -1
- package/src/internal/builtin-plugins/solidity-test/eip712/canonicalize.ts +13 -11
- package/src/internal/builtin-plugins/solidity-test/formatters.ts +14 -2
- package/src/internal/core/configuration-variables.ts +25 -10
- package/src/internal/edr/exports.ts +1 -0
- package/templates/01-node-test-runner-viem/package.json +1 -1
- package/templates/02-mocha-ethers/package.json +2 -2
- package/templates/03-minimal/package.json +1 -1
|
@@ -101,3 +101,33 @@ export class UnknownError extends ProviderError {
|
|
|
101
101
|
super(message, UnknownError.CODE, parent);
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Thrown by `eth_estimateGas` when the network's `gasEstimationMode` is
|
|
107
|
+
* `"noInternalOutOfGas"` and an internal call runs out of gas even when the
|
|
108
|
+
* transaction is executed with the maximum amount of gas available, meaning
|
|
109
|
+
* that no gas limit can prevent the internal out-of-gas error.
|
|
110
|
+
*/
|
|
111
|
+
export class InternalCallOutOfGasError extends ProviderError {
|
|
112
|
+
public static readonly CODE = -32000;
|
|
113
|
+
|
|
114
|
+
constructor(
|
|
115
|
+
message: string = "Gas estimation failed: an internal call runs out of gas regardless of the transaction's gas limit, so no reliable estimate exists. Set an explicit gas limit for the transaction, or set the network's gasEstimationMode to 'topLevelSuccess' to only require the top-level call to succeed.",
|
|
116
|
+
parent?: Error,
|
|
117
|
+
) {
|
|
118
|
+
super(message, InternalCallOutOfGasError.CODE, parent);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Detects the error data of EDR's estimation failure for the
|
|
124
|
+
* `NoInternalOutOfGas` gas estimation mode, reported when an internal call
|
|
125
|
+
* runs out of gas even when the transaction is executed with the maximum
|
|
126
|
+
* amount of gas available.
|
|
127
|
+
*
|
|
128
|
+
* The `reason` discriminator it looks for is also what `hardhat node` puts on
|
|
129
|
+
* the wire, so clients of an http connection can recognize the failure.
|
|
130
|
+
*/
|
|
131
|
+
export function isInternalCallOutOfGasErrorData(errorData: unknown): boolean {
|
|
132
|
+
return isObject(errorData) && errorData.reason === "InternalCallOutOfGas";
|
|
133
|
+
}
|
|
@@ -6,7 +6,6 @@ import type {
|
|
|
6
6
|
import type { RequestHandler } from "../../types.js";
|
|
7
7
|
import type { RpcTransactionRequest } from "@nomicfoundation/hardhat-zod-utils/rpc";
|
|
8
8
|
import type * as MicroEthSignerT from "micro-eth-signer";
|
|
9
|
-
import type * as MicroEthSignerTypedDataT from "micro-eth-signer/typed-data";
|
|
10
9
|
|
|
11
10
|
import {
|
|
12
11
|
assertHardhatInvariant,
|
|
@@ -32,7 +31,6 @@ import {
|
|
|
32
31
|
|
|
33
32
|
// micro-eth-signer is known to be slow to load, so we lazy load it
|
|
34
33
|
let microEthSigner: typeof MicroEthSignerT | undefined;
|
|
35
|
-
let microEthSignerTypedData: typeof MicroEthSignerTypedDataT | undefined;
|
|
36
34
|
|
|
37
35
|
import { getRequestParams } from "../../../json-rpc.js";
|
|
38
36
|
import { ChainId } from "../chain-id/chain-id.js";
|
|
@@ -129,19 +127,14 @@ export class LocalAccountsHandler extends ChainId implements RequestHandler {
|
|
|
129
127
|
);
|
|
130
128
|
}
|
|
131
129
|
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
await import("micro-eth-signer/typed-data");
|
|
130
|
+
if (microEthSigner === undefined) {
|
|
131
|
+
microEthSigner = await import("micro-eth-signer");
|
|
135
132
|
}
|
|
136
133
|
|
|
137
134
|
const privateKey = await this.#getPrivateKeyForAddress(address);
|
|
138
135
|
return this.#createJsonRpcResponse(
|
|
139
136
|
jsonRpcRequest.id,
|
|
140
|
-
|
|
141
|
-
data,
|
|
142
|
-
privateKey,
|
|
143
|
-
EXTRA_ENTROPY,
|
|
144
|
-
),
|
|
137
|
+
microEthSigner.eip191Signer.sign(data, privateKey, EXTRA_ENTROPY),
|
|
145
138
|
);
|
|
146
139
|
}
|
|
147
140
|
}
|
|
@@ -159,19 +152,14 @@ export class LocalAccountsHandler extends ChainId implements RequestHandler {
|
|
|
159
152
|
);
|
|
160
153
|
}
|
|
161
154
|
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
await import("micro-eth-signer/typed-data");
|
|
155
|
+
if (microEthSigner === undefined) {
|
|
156
|
+
microEthSigner = await import("micro-eth-signer");
|
|
165
157
|
}
|
|
166
158
|
|
|
167
159
|
const privateKey = await this.#getPrivateKeyForAddress(address);
|
|
168
160
|
return this.#createJsonRpcResponse(
|
|
169
161
|
jsonRpcRequest.id,
|
|
170
|
-
|
|
171
|
-
data,
|
|
172
|
-
privateKey,
|
|
173
|
-
EXTRA_ENTROPY,
|
|
174
|
-
),
|
|
162
|
+
microEthSigner.eip191Signer.sign(data, privateKey, EXTRA_ENTROPY),
|
|
175
163
|
);
|
|
176
164
|
}
|
|
177
165
|
}
|
|
@@ -201,17 +189,13 @@ export class LocalAccountsHandler extends ChainId implements RequestHandler {
|
|
|
201
189
|
// if we don't manage the address, the method is forwarded
|
|
202
190
|
const privateKey = await this.#getPrivateKeyForAddressOrNull(address);
|
|
203
191
|
if (privateKey !== null) {
|
|
204
|
-
if (
|
|
205
|
-
|
|
192
|
+
if (microEthSigner === undefined) {
|
|
193
|
+
microEthSigner = await import("micro-eth-signer");
|
|
206
194
|
}
|
|
207
195
|
|
|
208
196
|
return this.#createJsonRpcResponse(
|
|
209
197
|
jsonRpcRequest.id,
|
|
210
|
-
|
|
211
|
-
typedMessage,
|
|
212
|
-
privateKey,
|
|
213
|
-
EXTRA_ENTROPY,
|
|
214
|
-
),
|
|
198
|
+
microEthSigner.signTyped(typedMessage, privateKey, EXTRA_ENTROPY),
|
|
215
199
|
);
|
|
216
200
|
}
|
|
217
201
|
}
|
|
@@ -482,7 +466,7 @@ export class LocalAccountsHandler extends ChainId implements RequestHandler {
|
|
|
482
466
|
|
|
483
467
|
const signedTransaction = transaction.signBy(privateKey, EXTRA_ENTROPY);
|
|
484
468
|
|
|
485
|
-
return signedTransaction.
|
|
469
|
+
return signedTransaction.toBytes();
|
|
486
470
|
}
|
|
487
471
|
|
|
488
472
|
#createJsonRpcResponse(
|
|
@@ -30,8 +30,10 @@ export class AutomaticGasHandler
|
|
|
30
30
|
constructor(
|
|
31
31
|
provider: EthereumProvider,
|
|
32
32
|
gasMultiplier: number = DEFAULT_GAS_MULTIPLIER,
|
|
33
|
+
fallbackGas?: bigint,
|
|
34
|
+
isBlockGasLimitEnforced?: () => boolean,
|
|
33
35
|
) {
|
|
34
|
-
super(provider, gasMultiplier);
|
|
36
|
+
super(provider, gasMultiplier, fallbackGas, isBlockGasLimitEnforced);
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
public isSupportedMethod(jsonRpcRequest: JsonRpcRequest): boolean {
|
|
@@ -1,27 +1,48 @@
|
|
|
1
1
|
import type { EthereumProvider } from "../../../../../../types/providers.js";
|
|
2
2
|
|
|
3
3
|
import { assertHardhatInvariant } from "@nomicfoundation/hardhat-errors";
|
|
4
|
+
import { min } from "@nomicfoundation/hardhat-utils/bigint";
|
|
4
5
|
import { ensureError } from "@nomicfoundation/hardhat-utils/error";
|
|
5
6
|
import {
|
|
6
7
|
hexStringToNumber,
|
|
7
8
|
numberToHexString,
|
|
8
9
|
} from "@nomicfoundation/hardhat-utils/hex";
|
|
9
10
|
|
|
11
|
+
import { InternalCallOutOfGasError } from "../../../provider-errors.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The fraction of the observed block gas limit that gets cached and used as the
|
|
15
|
+
* gas limit ceiling. The cached value is deliberately lower than what the
|
|
16
|
+
* latest block reported, to leave room for the block gas limit varying
|
|
17
|
+
* slightly between blocks after we cached it.
|
|
18
|
+
*/
|
|
19
|
+
export const BLOCK_GAS_LIMIT_SAFETY_FACTOR = 0.95;
|
|
20
|
+
|
|
10
21
|
/**
|
|
11
22
|
* This class handles gas estimation for transactions by applying a multiplier to the estimated gas value.
|
|
12
23
|
* It requests a gas estimation from the provider and multiplies it by a predefined gas multiplier, ensuring the gas does not exceed the block's gas limit.
|
|
13
|
-
* If an
|
|
14
|
-
*
|
|
24
|
+
* If the estimation fails because an internal call runs out of gas regardless of the gas limit, the method returns the fallback gas limit, capped to the current block gas limit on networks that enforce one, or rethrows when no fallback gas limit is known.
|
|
25
|
+
* If any other error whose message contains "execution error" occurs, the method returns the capped block gas limit instead. Every other error is rethrown.
|
|
26
|
+
* The capped block gas limit is the latest block's gas limit scaled by `BLOCK_GAS_LIMIT_SAFETY_FACTOR`, cached after the first retrieval to optimize performance, except when capping the fallback gas limit, which reads the pending block's unscaled one.
|
|
15
27
|
*/
|
|
16
28
|
export abstract class MultipliedGasEstimation {
|
|
17
29
|
readonly #provider: EthereumProvider;
|
|
18
30
|
readonly #gasMultiplier: number;
|
|
31
|
+
readonly #fallbackGas: bigint | undefined;
|
|
32
|
+
readonly #isBlockGasLimitEnforced: () => boolean;
|
|
19
33
|
|
|
20
34
|
#blockGasLimit: number | undefined;
|
|
21
35
|
|
|
22
|
-
constructor(
|
|
36
|
+
constructor(
|
|
37
|
+
provider: EthereumProvider,
|
|
38
|
+
gasMultiplier: number,
|
|
39
|
+
fallbackGas?: bigint,
|
|
40
|
+
isBlockGasLimitEnforced: () => boolean = () => true,
|
|
41
|
+
) {
|
|
23
42
|
this.#provider = provider;
|
|
24
43
|
this.#gasMultiplier = gasMultiplier;
|
|
44
|
+
this.#fallbackGas = fallbackGas;
|
|
45
|
+
this.#isBlockGasLimitEnforced = isBlockGasLimitEnforced;
|
|
25
46
|
}
|
|
26
47
|
|
|
27
48
|
protected async getMultipliedGasEstimation(params: any[]): Promise<string> {
|
|
@@ -52,6 +73,34 @@ export abstract class MultipliedGasEstimation {
|
|
|
52
73
|
} catch (error) {
|
|
53
74
|
ensureError(error);
|
|
54
75
|
|
|
76
|
+
// We fall back instead of failing the transaction, because the user
|
|
77
|
+
// never asked for this estimation. The fallback is the gas limit the
|
|
78
|
+
// network itself uses for requests with no gas field. Connections that
|
|
79
|
+
// don't expose it (e.g. an http connection to a `hardhat node` server)
|
|
80
|
+
// get no fallback, and the estimation error reaches the user instead of
|
|
81
|
+
// a guessed limit that may not suit the remote network: the error names
|
|
82
|
+
// both remedies, an explicit gas limit or the topLevelSuccess mode.
|
|
83
|
+
//
|
|
84
|
+
// The cap reads the pending block's limit fresh rather than the cache:
|
|
85
|
+
// evm_setBlockGasLimit may have lowered it, and only the pending block
|
|
86
|
+
// reflects that before the next block is mined.
|
|
87
|
+
//
|
|
88
|
+
// Networks that don't enforce a block gas limit are exempt: their
|
|
89
|
+
// headers still report one, so capping would shrink the fallback to a
|
|
90
|
+
// limit nothing rejects it for.
|
|
91
|
+
if (
|
|
92
|
+
error instanceof InternalCallOutOfGasError &&
|
|
93
|
+
this.#fallbackGas !== undefined
|
|
94
|
+
) {
|
|
95
|
+
if (!this.#isBlockGasLimitEnforced()) {
|
|
96
|
+
return numberToHexString(this.#fallbackGas);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const blockGasLimit = BigInt(await this.#fetchBlockGasLimit("pending"));
|
|
100
|
+
|
|
101
|
+
return numberToHexString(min(this.#fallbackGas, blockGasLimit));
|
|
102
|
+
}
|
|
103
|
+
|
|
55
104
|
if (error.message.toLowerCase().includes("execution error")) {
|
|
56
105
|
const blockGasLimitTmp = await this.#getBlockGasLimit();
|
|
57
106
|
return numberToHexString(blockGasLimitTmp);
|
|
@@ -63,25 +112,31 @@ export abstract class MultipliedGasEstimation {
|
|
|
63
112
|
|
|
64
113
|
async #getBlockGasLimit(): Promise<number> {
|
|
65
114
|
if (this.#blockGasLimit === undefined) {
|
|
66
|
-
const
|
|
67
|
-
method: "eth_getBlockByNumber",
|
|
68
|
-
params: ["latest", false],
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
assertHardhatInvariant(
|
|
72
|
-
typeof latestBlock === "object" &&
|
|
73
|
-
latestBlock !== null &&
|
|
74
|
-
"gasLimit" in latestBlock &&
|
|
75
|
-
typeof latestBlock.gasLimit === "string",
|
|
76
|
-
"latestBlock should have a gasLimit",
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
const fetchedGasLimit = hexStringToNumber(latestBlock.gasLimit);
|
|
115
|
+
const fetchedGasLimit = await this.#fetchBlockGasLimit("latest");
|
|
80
116
|
|
|
81
117
|
// We store a lower value in case the gas limit varies slightly
|
|
82
|
-
this.#blockGasLimit = Math.floor(
|
|
118
|
+
this.#blockGasLimit = Math.floor(
|
|
119
|
+
fetchedGasLimit * BLOCK_GAS_LIMIT_SAFETY_FACTOR,
|
|
120
|
+
);
|
|
83
121
|
}
|
|
84
122
|
|
|
85
123
|
return this.#blockGasLimit;
|
|
86
124
|
}
|
|
125
|
+
|
|
126
|
+
async #fetchBlockGasLimit(blockTag: "latest" | "pending"): Promise<number> {
|
|
127
|
+
const block = await this.#provider.request({
|
|
128
|
+
method: "eth_getBlockByNumber",
|
|
129
|
+
params: [blockTag, false],
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
assertHardhatInvariant(
|
|
133
|
+
typeof block === "object" &&
|
|
134
|
+
block !== null &&
|
|
135
|
+
"gasLimit" in block &&
|
|
136
|
+
typeof block.gasLimit === "string",
|
|
137
|
+
`the ${blockTag} block should have a gasLimit`,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return hexStringToNumber(block.gasLimit);
|
|
141
|
+
}
|
|
87
142
|
}
|
|
@@ -63,10 +63,34 @@ export async function createHandlersArray<
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
if (networkConfig.gas === undefined || networkConfig.gas === "auto") {
|
|
66
|
+
// On EDR networks, if the gas estimation fails because an internal call
|
|
67
|
+
// runs out of gas regardless of the gas limit, we fall back to the same
|
|
68
|
+
// default transaction gas limit that the network uses for requests
|
|
69
|
+
// without a gas field. The EdrProvider exposes the exact value it was
|
|
70
|
+
// configured with, so the fallback always matches the provider's actual
|
|
71
|
+
// default. Providers that don't expose it (e.g. HTTP connections) get no
|
|
72
|
+
// fallback, and the handler surfaces the estimation error instead of
|
|
73
|
+
// guessing a limit that may not suit the remote network.
|
|
74
|
+
const provider = networkConnection.provider;
|
|
75
|
+
const fallbackGas =
|
|
76
|
+
"defaultTransactionGasLimit" in provider &&
|
|
77
|
+
typeof provider.defaultTransactionGasLimit === "bigint"
|
|
78
|
+
? provider.defaultTransactionGasLimit
|
|
79
|
+
: undefined;
|
|
80
|
+
|
|
81
|
+
// The fallback only gets capped on networks that enforce a block gas
|
|
82
|
+
// limit. Read lazily, as enforcement can turn on at runtime; providers
|
|
83
|
+
// that don't report it are conservatively assumed to enforce one.
|
|
84
|
+
const isBlockGasLimitEnforced = () =>
|
|
85
|
+
!("isBlockGasLimitEnforced" in provider) ||
|
|
86
|
+
provider.isBlockGasLimitEnforced !== false;
|
|
87
|
+
|
|
66
88
|
requestHandlers.push(
|
|
67
89
|
new AutomaticGasHandler(
|
|
68
90
|
networkConnection.provider,
|
|
69
91
|
networkConfig.gasMultiplier,
|
|
92
|
+
fallbackGas,
|
|
93
|
+
isBlockGasLimitEnforced,
|
|
70
94
|
),
|
|
71
95
|
);
|
|
72
96
|
} else {
|
|
@@ -110,6 +110,7 @@ declare module "../../../../types/config.js" {
|
|
|
110
110
|
blockGasLimit?: number | bigint | false;
|
|
111
111
|
coinbase?: string;
|
|
112
112
|
forking?: EdrNetworkForkingUserConfig;
|
|
113
|
+
gasEstimationMode?: "topLevelSuccess" | "noInternalOutOfGas";
|
|
113
114
|
hardfork?: string;
|
|
114
115
|
initialBaseFeePerGas?: number | bigint;
|
|
115
116
|
initialDate?: string | Date;
|
|
@@ -253,6 +254,7 @@ declare module "../../../../types/config.js" {
|
|
|
253
254
|
blockGasLimit?: bigint | false;
|
|
254
255
|
coinbase: Uint8Array;
|
|
255
256
|
forking?: EdrNetworkForkingConfig;
|
|
257
|
+
gasEstimationMode: "topLevelSuccess" | "noInternalOutOfGas";
|
|
256
258
|
hardfork: string;
|
|
257
259
|
initialBaseFeePerGas?: bigint;
|
|
258
260
|
initialDate: string | Date;
|
|
@@ -319,6 +319,12 @@ const edrNetworkUserConfigSchema = z.object({
|
|
|
319
319
|
blockGasLimit: z.optional(gasLimitWithDisableUserConfigSchema),
|
|
320
320
|
coinbase: z.optional(z.string()),
|
|
321
321
|
forking: z.optional(edrNetworkForkingUserConfigSchema),
|
|
322
|
+
gasEstimationMode: z.optional(
|
|
323
|
+
unionType(
|
|
324
|
+
[z.literal("topLevelSuccess"), z.literal("noInternalOutOfGas")],
|
|
325
|
+
"Expected 'topLevelSuccess' or 'noInternalOutOfGas'",
|
|
326
|
+
),
|
|
327
|
+
),
|
|
322
328
|
hardfork: z.optional(z.string()),
|
|
323
329
|
initialBaseFeePerGas: z.optional(gasUnitUserConfigSchema),
|
|
324
330
|
initialDate: z.optional(
|
|
@@ -256,6 +256,7 @@ const _handleError = (error: Error): JsonRpcResponse => {
|
|
|
256
256
|
// that would be lost when wrapped in InternalError below.
|
|
257
257
|
const txHash = extractTxHash(error);
|
|
258
258
|
const returnData = extractReturnData(error);
|
|
259
|
+
const reason = extractErrorReason(error);
|
|
259
260
|
|
|
260
261
|
// Check if this is a revert error (code 3) matching the geth/anvil convention.
|
|
261
262
|
const isRevertError =
|
|
@@ -271,9 +272,12 @@ const _handleError = (error: Error): JsonRpcResponse => {
|
|
|
271
272
|
|
|
272
273
|
// Revert errors (code 3): return raw hex to match the geth/anvil convention
|
|
273
274
|
// that viem/ethers/web3.js rely on. Other errors keep the wrapper for diagnostics.
|
|
275
|
+
// `reason` is the discriminator clients use to recognize specific failures
|
|
276
|
+
// (e.g. "InternalCallOutOfGas") once the error class itself is lost to
|
|
277
|
+
// serialization, so it must survive it.
|
|
274
278
|
const data: unknown = isRevertError
|
|
275
279
|
? returnData
|
|
276
|
-
: { message: error.message, txHash, data: returnData };
|
|
280
|
+
: { message: error.message, txHash, data: returnData, reason };
|
|
277
281
|
|
|
278
282
|
const response: FailedJsonRpcResponse = {
|
|
279
283
|
jsonrpc: "2.0",
|
|
@@ -318,3 +322,13 @@ function extractReturnData(error: Error): string | undefined {
|
|
|
318
322
|
return error.data.data;
|
|
319
323
|
}
|
|
320
324
|
}
|
|
325
|
+
|
|
326
|
+
function extractErrorReason(error: Error): string | undefined {
|
|
327
|
+
if (
|
|
328
|
+
"data" in error &&
|
|
329
|
+
isObject(error.data) &&
|
|
330
|
+
typeof error.data.reason === "string"
|
|
331
|
+
) {
|
|
332
|
+
return error.data.reason;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -6,7 +6,7 @@ export const SELECTED_CONFLICT_REMEDIATION =
|
|
|
6
6
|
"Rename one of the structs, or scope your `test.solidity.eip712Types.include` / `exclude` globs in `hardhat.config.ts` so that only one of them is selected.";
|
|
7
7
|
|
|
8
8
|
export const DEPENDENCY_CONFLICT_REMEDIATION =
|
|
9
|
-
"Rename one of the structs so the name has a single definition. The clashing name is pulled in as a dependency of a selected struct,
|
|
9
|
+
"Rename one of the structs so the name has a single definition. The clashing name is pulled in as a dependency of a selected struct, but none of its definitions is in a selected source, so you can also resolve the conflict with your `test.solidity.eip712Types.include` / `exclude` globs in `hardhat.config.ts`: include the source containing the canonical definition so it wins the clash, or deselect the struct that depends on it if you don't need it.";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Produces the flat list of canonical EIP-712 type strings expected by EDR.
|
|
@@ -220,12 +220,14 @@ function transitiveDeps(
|
|
|
220
220
|
* - Both sides selected: throw immediately — genuinely ambiguous which to
|
|
221
221
|
* emit.
|
|
222
222
|
* - Current side non-selected: defer; the stored definition keeps the name.
|
|
223
|
-
* Throw only if the name
|
|
224
|
-
* transitive deps), since then which copy got
|
|
225
|
-
* iteration order.
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
* run
|
|
223
|
+
* Throw only if the name has no selected definition AND is reachable from
|
|
224
|
+
* a selected struct (its transitive deps), since only then which copy got
|
|
225
|
+
* inlined would depend on iteration order. A name with a selected
|
|
226
|
+
* definition always resolves to it deterministically (selected structs
|
|
227
|
+
* come first), so a non-selected struct that shares a name with a
|
|
228
|
+
* selected one never aborts the run — even when that name is also a
|
|
229
|
+
* dependency of another selected struct. Names unreachable from the
|
|
230
|
+
* selected set are likewise silently deduped (first wins).
|
|
229
231
|
*/
|
|
230
232
|
function indexByName(
|
|
231
233
|
structs: CollectedStruct[],
|
|
@@ -276,9 +278,9 @@ function indexByName(
|
|
|
276
278
|
|
|
277
279
|
// Non-selected side, so the stored definition (selected if the name has
|
|
278
280
|
// one, since selected sources come first) keeps the name. Defer: throw
|
|
279
|
-
// later only if the name
|
|
280
|
-
// name with a selected
|
|
281
|
-
// must not abort.
|
|
281
|
+
// later only if the name has no selected definition and is reachable from
|
|
282
|
+
// a selected struct. Sharing a name with a selected definition or an
|
|
283
|
+
// unreferenced struct is harmless and must not abort.
|
|
282
284
|
if (!deferredConflicts.has(struct.name)) {
|
|
283
285
|
deferredConflicts.set(struct.name, {
|
|
284
286
|
firstSource: sourceByName.get(struct.name) ?? "<unknown>",
|
|
@@ -290,7 +292,7 @@ function indexByName(
|
|
|
290
292
|
if (deferredConflicts.size > 0) {
|
|
291
293
|
const reachable = reachableFromSelected(byName, selectedNames);
|
|
292
294
|
for (const [name, sources] of deferredConflicts) {
|
|
293
|
-
if (reachable.has(name)) {
|
|
295
|
+
if (reachable.has(name) && !selectedNames.has(name)) {
|
|
294
296
|
throw new HardhatError(
|
|
295
297
|
HardhatError.ERRORS.CORE.SOLIDITY_TESTS.EIP712_DUPLICATE_STRUCT_NAME,
|
|
296
298
|
{ name, ...sources, remediation: DEPENDENCY_CONFLICT_REMEDIATION },
|
|
@@ -32,11 +32,23 @@ export function formatInlineConfigErrors(
|
|
|
32
32
|
return `- ${sourceName}: ${formatInlineConfigSourceProblem(error.problem)}`;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
return `- ${sourceName}:${error.line}: ${error.contract
|
|
35
|
+
return `- ${sourceName}:${error.line}: ${formatDirectiveOrigin(error.contract, error.function)}: ${formatInlineConfigDirectiveProblem(error.problem)}`;
|
|
36
36
|
})
|
|
37
37
|
.join("\n");
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Names where a directive was found: `Contract.testFn` for a function-level
|
|
42
|
+
* directive, and just `Contract` for a contract-level one, which EDR reports
|
|
43
|
+
* without a function.
|
|
44
|
+
*/
|
|
45
|
+
function formatDirectiveOrigin(
|
|
46
|
+
contract: string,
|
|
47
|
+
functionName: string | undefined,
|
|
48
|
+
): string {
|
|
49
|
+
return functionName === undefined ? contract : `${contract}.${functionName}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
40
52
|
function formatInlineConfigDirectiveProblem(
|
|
41
53
|
problem: InlineConfigDirectiveProblem,
|
|
42
54
|
): string {
|
|
@@ -65,6 +77,6 @@ function formatInlineConfigSourceProblem(
|
|
|
65
77
|
case "InlineConfigSourceFileNotFound":
|
|
66
78
|
return `the source file could not be read at "${problem.path}": ${problem.reason}`;
|
|
67
79
|
case "InlineConfigDirectiveLocation":
|
|
68
|
-
return `a directive of ${problem.contract
|
|
80
|
+
return `a directive of ${formatDirectiveOrigin(problem.contract, problem.function)} could not be located: ${problem.reason}`;
|
|
69
81
|
}
|
|
70
82
|
}
|
|
@@ -32,6 +32,13 @@ abstract class BaseResolvedConfigurationVariable implements ResolvedConfiguratio
|
|
|
32
32
|
|
|
33
33
|
protected abstract _getRawValue(): Promise<string>;
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* A description of this variable, used to identify it in error messages.
|
|
37
|
+
*
|
|
38
|
+
* Resolved values may be secrets, so they must never be included in errors.
|
|
39
|
+
*/
|
|
40
|
+
protected abstract _getDescription(): string;
|
|
41
|
+
|
|
35
42
|
constructor(public readonly format: string) {
|
|
36
43
|
assertHardhatInvariant(
|
|
37
44
|
this.format.includes(CONFIGURATION_VARIABLE_MARKER),
|
|
@@ -57,9 +64,10 @@ abstract class BaseResolvedConfigurationVariable implements ResolvedConfiguratio
|
|
|
57
64
|
new URL(value);
|
|
58
65
|
return value;
|
|
59
66
|
} catch (_error) {
|
|
60
|
-
throw new HardhatError(
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
throw new HardhatError(
|
|
68
|
+
HardhatError.ERRORS.CORE.GENERAL.INVALID_CONFIG_VARIABLE_URL,
|
|
69
|
+
{ configVariable: this._getDescription() },
|
|
70
|
+
);
|
|
63
71
|
}
|
|
64
72
|
}
|
|
65
73
|
|
|
@@ -69,9 +77,10 @@ abstract class BaseResolvedConfigurationVariable implements ResolvedConfiguratio
|
|
|
69
77
|
try {
|
|
70
78
|
return BigInt(value);
|
|
71
79
|
} catch (_error) {
|
|
72
|
-
throw new HardhatError(
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
throw new HardhatError(
|
|
81
|
+
HardhatError.ERRORS.CORE.GENERAL.INVALID_CONFIG_VARIABLE_BIGINT,
|
|
82
|
+
{ configVariable: this._getDescription() },
|
|
83
|
+
);
|
|
75
84
|
}
|
|
76
85
|
}
|
|
77
86
|
|
|
@@ -81,10 +90,8 @@ abstract class BaseResolvedConfigurationVariable implements ResolvedConfiguratio
|
|
|
81
90
|
return normalizeHexString(value);
|
|
82
91
|
} catch {
|
|
83
92
|
throw new HardhatError(
|
|
84
|
-
HardhatError.ERRORS.CORE.GENERAL.
|
|
85
|
-
{
|
|
86
|
-
value,
|
|
87
|
-
},
|
|
93
|
+
HardhatError.ERRORS.CORE.GENERAL.INVALID_CONFIG_VARIABLE_HEX_STRING,
|
|
94
|
+
{ configVariable: this._getDescription() },
|
|
88
95
|
);
|
|
89
96
|
}
|
|
90
97
|
}
|
|
@@ -112,6 +119,10 @@ export class LazyResolvedConfigurationVariable extends BaseResolvedConfiguration
|
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
121
|
|
|
122
|
+
protected _getDescription(): string {
|
|
123
|
+
return `the configuration variable "${this.name}"`;
|
|
124
|
+
}
|
|
125
|
+
|
|
115
126
|
protected async _getRawValue(): Promise<string> {
|
|
116
127
|
// Env vars take precedence over every configurationVariables plugin hook
|
|
117
128
|
// (e.g. keystore). Skip the hook chain entirely when the env var is set so
|
|
@@ -157,6 +168,10 @@ export class FixedValueConfigurationVariable extends BaseResolvedConfigurationVa
|
|
|
157
168
|
this.#value = value;
|
|
158
169
|
}
|
|
159
170
|
|
|
171
|
+
protected _getDescription(): string {
|
|
172
|
+
return "an inline configuration value";
|
|
173
|
+
}
|
|
174
|
+
|
|
160
175
|
protected async _getRawValue(): Promise<string> {
|
|
161
176
|
return this.#value;
|
|
162
177
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { keccak256 } from "@nomicfoundation/edr";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"description": "A TypeScript Hardhat project using Node Test Runner and Viem",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"devDependencies": {
|
|
8
|
-
"hardhat": "workspace:^3.
|
|
8
|
+
"hardhat": "workspace:^3.17.0",
|
|
9
9
|
"@nomicfoundation/hardhat-toolbox-viem": "workspace:^5.0.7",
|
|
10
10
|
"@nomicfoundation/hardhat-ignition": "workspace:^3.1.8",
|
|
11
11
|
"@types/node": "^22.8.5",
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
"description": "A TypeScript Hardhat project using Mocha and Ethers.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"devDependencies": {
|
|
8
|
-
"hardhat": "workspace:^3.
|
|
8
|
+
"hardhat": "workspace:^3.17.0",
|
|
9
9
|
"@nomicfoundation/hardhat-toolbox-mocha-ethers": "workspace:^3.0.7",
|
|
10
|
-
"@nomicfoundation/hardhat-ethers": "workspace:^4.0
|
|
10
|
+
"@nomicfoundation/hardhat-ethers": "workspace:^4.1.0",
|
|
11
11
|
"@nomicfoundation/hardhat-ignition": "workspace:^3.1.8",
|
|
12
12
|
"@types/chai": "^5.2.3",
|
|
13
13
|
"@types/chai-as-promised": "^8.0.1",
|