Answering the Core Question
Hardhat’s local node and a forked Hardhat network use different gas‑estimation engines. The local node runs an internal algorithm that approximates the required gas, while a forked node forwards the request to the upstream RPC’s eth_estimateGas endpoint. Because the RPC sees the live mempool and network‑specific heuristics, the two estimates can diverge.
Root Causes of Divergence
- Estimator implementation: Hardhat’s heuristic vs. the RPC’s native estimator.
- Pending transactions: The RPC considers mempool state; the local node does not.
- Fee market differences: EIP‑1559 fields (
maxFeePerGas, maxPriorityFeePerGas) may be handled differently.
- Compiler & optimization: Bytecode size changes alter gas usage.
- Block number mismatch: Forking at a different block changes base state and gas usage.
Steps to Achieve Consistent Estimates
- Use the same compiler version and optimization settings in
hardhat.config.js for both environments.
module.exports = {
solidity: {
version: "0.8.20",
settings: { optimizer: { enabled: true, runs: 200 } }
}
};
- Configure the fork with a specific block number and RPC URL.
module.exports = {
networks: {
hardhat: {
forking: {
url: process.env.ALCHEMY_MAINNET, // or Infura
blockNumber: 17800000 // example
}
}
}
};
- Deploy the same contract on both the local and forked network (or use the same deployment artifacts).
- Set explicit gas fields in the transaction object or in the Hardhat config to avoid defaults.
const tx = {
to: contract.address,
data: contract.interface.encodeFunctionData("foo", [42]),
gasPrice: ethers.utils.parseUnits("50", "gwei"),
maxFeePerGas: ethers.utils.parseUnits("100", "gwei"),
maxPriorityFeePerGas: ethers.utils.parseUnits("2", "gwei")
};
- Run
hardhat console --network localhost and hardhat console --network hardhat to compare provider.send("eth_estimateGas", [tx]) outputs. The values should differ by no more than a few hundred gas units.
- If discrepancies persist, override the local node’s block gas limit in the config:
hardhat: {
blockGasLimit: 12000000
}
- Optionally, use the same
network.provider.send("eth_estimateGas", …) call on both networks to isolate the estimation logic.
When to Ask for More Information
The recommendation above assumes you are forking at a specific block. If you are forking from the latest block or from a very old block, the base state can influence the estimate. Could you confirm which block number you used for forking? That detail may change the suggested block‑specific adjustments.
Key Takeaway
Consistency is achieved by aligning compiler settings, gas fields, block number, and optionally the block gas limit. After those alignments, the local and forked estimations should converge.