Support better gas estimation to avoid internal OOG errors - #8501
Support better gas estimation to avoid internal OOG errors#8501ChristopherDedominici wants to merge 18 commits into
Conversation
🦋 Changeset detectedLatest commit: 8fce209 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Pull request overview
Wires Hardhat’s EDR network configuration through to EDR’s new gas estimation mode that prevents silent internal out-of-gas (OOG) failures, and adds error mapping + an automatic-gas fallback path for the new “unavoidable internal OOG” estimation failure.
Changes:
- Added
gasEstimationModeto EDR network config (validation + resolved default tonoInternalOutOfGas) and mapped it to EDR’sGasEstimationModeenum. - Introduced
InternalCallOutOfGasErrorand mapped EDR’s new estimation failure reason to it duringeth_estimateGas. - When
gas: "auto"on EDR networks, if estimation fails withInternalCallOutOfGasError, fall back to the resolved default transaction gas limit (EIP-7825-aware, capped to block gas limit where applicable), without applying the gas multiplier.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/hardhat/src/internal/builtin-plugins/network-manager/type-validation.ts | Validates gasEstimationMode as a supported string union for EDR networks. |
| packages/hardhat/src/internal/builtin-plugins/network-manager/type-extensions/config.ts | Extends config types to expose gasEstimationMode (user + resolved). |
| packages/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts | Resolves default gasEstimationMode to noInternalOutOfGas. |
| packages/hardhat/src/internal/builtin-plugins/network-manager/provider-errors.ts | Adds InternalCallOutOfGasError surfaced to JSON-RPC clients. |
| packages/hardhat/src/internal/builtin-plugins/network-manager/edr/type-validation.ts | Adds a type guard for EDR’s internal-OOG estimation failure data. |
| packages/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts | Maps Hardhat mode to EDR enum; refines default gas cap logic and adds resolveEdrDefaultTransactionGasLimit. |
| packages/hardhat/src/internal/builtin-plugins/network-manager/edr/edr-provider.ts | Plumbs gasEstimationMode into EDR config and maps internal-OOG estimation failures to InternalCallOutOfGasError. |
| packages/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/multiplied-gas-estimation.ts | Adds fallback gas handling for internal-OOG estimation failures. |
| packages/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/automatic-gas-handler.ts | Passes fallback gas into the shared estimation logic. |
| packages/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/handlers-array.ts | Computes EDR-only fallback gas limit and wires it into AutomaticGasHandler. |
| packages/hardhat/test/internal/edr/context.ts | Updates EDR test config stub to include required gasEstimationMode. |
| packages/hardhat/test/internal/builtin-plugins/network-manager/utils/apply-coverage-network-overrides.ts | Updates coverage override tests to include gasEstimationMode. |
| packages/hardhat/test/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/automatic-gas-handler.ts | Adds unit tests for internal-OOG fallback vs rethrow behavior. |
| packages/hardhat/test/internal/builtin-plugins/network-manager/network-manager.ts | Adds validation tests for gasEstimationMode. |
| packages/hardhat/test/internal/builtin-plugins/network-manager/hook-handlers/config.ts | Asserts default resolved gasEstimationMode is noInternalOutOfGas. |
| packages/hardhat/test/internal/builtin-plugins/network-manager/config-resolution.ts | Adds resolution coverage for gasEstimationMode (explicit + default). |
| packages/hardhat/test/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts | Adds unit tests for new conversion helpers and mode-to-enum mapping. |
| packages/hardhat/test/internal/builtin-plugins/network-manager/edr/edr-provider.ts | Adds integration tests for internal-OOG behavior in both estimation modes and auto-gas fallback behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.
Suppressed comments (1)
packages/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/handlers-array.ts:9
- The new top-level import of
resolveEdrDefaultTransactionGasLimitpulls inedr/utils/convert-to-edr, which eagerly imports@nomicfoundation/edr(large/native dependency). SincecreateHandlersArrayis used for all network types, this makes EDR load even for pure HTTP networks. Consider lazily loading this helper only inside thenetworkConfig.type === "edr-simulated"branch (e.g.await import(...), ideally cached) or moving the gas-limit helper to a lightweight module that doesn’t import@nomicfoundation/edr.
import { resolveEdrDefaultTransactionGasLimit } from "../edr/utils/convert-to-edr.js";
…on/hardhat into better-gas-estimation-impl
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 25 changed files in this pull request and generated no new comments.
Suppressed comments (1)
.changeset/bold-geckos-agree.md:6
- The changeset description doesn’t mention that
noInternalOutOfGasis now the resolved defaultgasEstimationModefor EDR networks. Since this changes default behavior (and can surface a new estimation error), it should be called out explicitly in the release note so users know how to restore the previous behavior viatopLevelSuccess.
Add `gasEstimationMode` config option to EDR networks, featuring a `"noInternalOutOfGas"` mode to prevent misleading `eth_estimateGas` results when internal calls run out of gas.
…-gas-estimation-impl
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.
Suppressed comments (2)
packages/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/multiplied-gas-estimation.ts:94
- This unconditional header-based cap is incorrect when
blockGasLimit: false. In that configuration EDR has no mining block gas limit, buteth_getBlockByNumberstill reports a header gas limit; therefore a configuredtransactionGasCap/default above that value is silently reduced here. A transaction whose top-level execution needs the configured fallback can then OOG even though EDR would accept it. Only cap when a mining block gas limit is actually enforced (and preserve the exact resolved fallback otherwise).
this.#fallbackGas !== undefined
) {
const blockGasLimit = BigInt(await this.#fetchBlockGasLimit("pending"));
packages/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/multiplied-gas-estimation.ts:90
- Automatic gas still fails for clients connected to
hardhat nodeover HTTP.HttpProviderdeserializes this as a plainProviderErrorcarryingdata.reason, so thisinstanceofcheck cannot recognize it; moreover, those connections deliberately receive no fallback value. As a result,eth_sendTransactionwithoutgasrethrows the estimation error instead of using the node's default transaction gas limit, contrary to the automatic-gas requirement. Recognize the serialized discriminator and let the remote node apply its default (for example, by forwarding the request without injectinggas), or otherwise obtain the server-resolved fallback.
This issue also appears on line 92 of the same file.
// transaction rejected.
if (
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.
Suppressed comments (1)
packages/hardhat/src/internal/builtin-plugins/network-manager/request-handlers/handlers/gas/multiplied-gas-estimation.ts:96
#fetchBlockGasLimitconverts the pending limit withhexStringToNumberbefore thisBigIntconversion. SinceblockGasLimitexplicitly accepts bigints aboveNumber.MAX_SAFE_INTEGER, a valid large limit makes this fallback throwInvalidParameterErrorinstead of sending the transaction. Parse the pending quantity directly as a bigint on this path.
const blockGasLimit = BigInt(await this.#fetchBlockGasLimit("pending"));
return numberToHexString(min(this.#fallbackGas, blockGasLimit));
| `the ${blockTag} block should have a gasLimit`, | ||
| ); | ||
|
|
||
| return hexStringToNumber(block.gasLimit); |
There was a problem hiding this comment.
this will fail for anything above Number.MAX_SAFE_INTEGER. Consider using hexStringToBigInt
There was a problem hiding this comment.
True in principle, hexStringToNumber throws above 2^53-1, but it's pre-existing code, unreachable on any real chain's gas limit, and fixing it properly means converting the whole estimation path (including line 61 and the float multiplier math) to bigint, not just that one line. So considering that no chain is at risk, I"d skip
| "hardhat": minor | ||
| --- | ||
|
|
||
| Add `gasEstimationMode` config option to EDR networks, featuring a `"noInternalOutOfGas"` mode to prevent misleading `eth_estimateGas` results when internal calls run out of gas. |
There was a problem hiding this comment.
| Add `gasEstimationMode` config option to EDR networks, featuring a `"noInternalOutOfGas"` mode to prevent misleading `eth_estimateGas` results when internal calls run out of gas. | |
| Added the `gasEstimationMode` config option to EDR-simulated networks, defaulting to `"noInternalOutOfGas"`: `eth_estimateGas` now returns a gas limit that also keeps calls internal to the transaction from running out of gas, and fails with a descriptive error when no gas limit can prevent it. Set it to `"topLevelSuccess"` for the previous behavior, where only the top-level call had to succeed. |
| error instanceof InternalCallOutOfGasError && | ||
| this.#fallbackGas !== undefined | ||
| ) { | ||
| const blockGasLimit = BigInt(await this.#fetchBlockGasLimit("pending")); |
There was a problem hiding this comment.
If the network doesn't enforce a blockGasLimit, this will unnecessarily cap the estimation. Is this the expected behavior?
Fixes #8399
Link to website PR: NomicFoundation/hardhat-website#292