Anvil as drop-in replacement for hardhat node
Component
Anvil
Have you ensured that all of these are up to date?
- [X] Foundry
- [X] Foundryup
What version of Foundry are you on?
forge 0.2.0 (e2fa2b5 2023-02-19T00:05:02.282096Z)
What command(s) is the bug in?
No response
Operating System
None
Describe the bug
When using anvil with hardhat/ethers.js it seems like there are a few incompatibility issues that I could report here while testing it. I'm not sure if it makes sense to group issues here because I just got started and found a few already. Let me know if it is preferred to have them as separate issues.
Contract function used for testing
struct Token {
uint tokenId;
}
function impossibleMethod(uint limit) public view returns(Token[] memory ret) {
ret = new Token[](limit);
for (uint256 index = 0; index < limit; index++) {
ret[index] = Token(index);
}
return ret;
}
1.) Default gasLimit seems to be different:
The above function when called with 20k iterations fails in anvil and succeeds in hardhat
2.) handling of gasLimit for eth_estimateGas and eth_call
const result = await contract.impossibleMethod(cycles, {
// The gas parameter of eth_call and eth_estimateGas is limited to 500_000_000 Gwei for EVM elastic nodes.
// gas limit on call should get ignored though
// gasLimit: 500_000_000
})
sending gasLimit on a read (especially larger than block limit) is ignored by hardhat but failing in anvil:
error: { code: -32603, message: 'EVM error CallerGasLimitMoreThenBlock' }
(Just noticed it seems there is also a typo: "CallerGasLimitMoreThanBlock")
I know that Infura supports up to 10x block gas on reads so maybe the default setting should not be capped like this?
3.) Transaction failing when not enough gas provided
a) First, the logs are not helpful when the above read transaction runs out of gas:

It would be great (and maybe is already possible) to also log the errors - now it is just displayed as "pending".
b) Hardhat/Ethers fails with generic ProviderError: HttpProviderError
There is no additional information displayed, making it very hard to understand what happened.
After digging a bit it seems that ethers v5 handles errors with string matching
if (errorGas.indexOf(method) >= 0 && message.match(/gas required exceeds allowance|always failing transaction|execution reverted|revert/)) {
logger.throwError("cannot estimate gas; transaction may fail or may require manual gas limit", Logger.errors.UNPREDICTABLE_GAS_LIMIT, {
error, method, transaction
});
}
https://github.com/ethers-io/ethers.js/blob/v5/packages/providers/src.ts/json-rpc-provider.ts#L124
I assume looking at other matched strings in the file will result in more conflicts.
Changing the error message in hardhat from
const jsonRpcResponse = await this._fetchJsonRpcResponse(jsonRpcRequest);
// console.log("===> response", args, jsonRpcResponse);
if (isErrorResponse(jsonRpcResponse)) {
// anvil: 'Out of gas: required gas exceeds allowance: 30000000'
error.message = jsonRpcResponse.error.message;
// what ethers string matches:
error.message = `gas required exceeds allowance: 30000000`;
error.code = jsonRpcResponse.error.code;
error.data = jsonRpcResponse.error.data;
// eslint-disable-next-line @nomiclabs/hardhat-internal-rules/only-hardhat-error
throw error;
}
results in the expected error being propagated.
Hopefully, there is a way to avoid the string matching while still producing visible user errors instead of generic HttpProvderErrors.
4.) Different gas estimations
Code:
const iterations = 5_000
const gasEstimate = await token.estimateGas.impossibleMethod(iterations)
Result:
hardhat node / ethereumjs evm:
5k: 10_328_330 // 5 seconds
anvil:
5k: 4_545_126 // 147 ms
I will continue some of these tests & benchmarks and can either comment here or use a different preferable approach.
Thanks for the amazing work on anvil based on first tests it is extremely fast and I would like to use it for more tests.
~~> Out of gas: required gas exceeds allowance: 30000000'~~
~~should we change this to just~~
~~> required gas exceeds allowance: 30000000'~~
~~would this match?~~
oh nvm the wording is actually different, fixing.
Another difference between hardhat and anvil that I found, is the fact that the hardhat node will reject creating a contract with empty bytecode whereas anvil does not.
I.e. if you send cast send --create "0x" to the anvil rpc it accepts the transaction whereas hardhat will error with (code: -32000, message: contract creation without any data provided, data: Some(Object {"message": String("contract creation without any data provided")}))
Not sure if this is to be considered an actual compatibility problem, but it might be a difference worth noting. ( This tripped me up in the past when using the hardhat-deploy package to deploy contracts on an anvil).
Re the gas issues, see https://github.com/foundry-rs/foundry/issues/5341
Also, re inline logging of console.log calls, which Anvil does not seem to currently do: https://github.com/foundry-rs/foundry/issues/5352
Marking this as not planned - full equivalence of Hardhat node is no longer planned or to be expected. If there are any obvious bugs please open a ticket, that makes it more actionable.