foundry icon indicating copy to clipboard operation
foundry copied to clipboard

Anvil as drop-in replacement for hardhat node

Open PhilippLgh opened this issue 2 years ago • 4 comments

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: image

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.

PhilippLgh avatar Feb 19 '23 13:02 PhilippLgh

~~> 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.

mattsse avatar Feb 19 '23 13:02 mattsse

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).

ckoopmann avatar Feb 20 '23 03:02 ckoopmann

Re the gas issues, see https://github.com/foundry-rs/foundry/issues/5341

aathan avatar Jul 09 '23 23:07 aathan

Also, re inline logging of console.log calls, which Anvil does not seem to currently do: https://github.com/foundry-rs/foundry/issues/5352

aathan avatar Jul 10 '23 20:07 aathan

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.

zerosnacks avatar Mar 25 '25 15:03 zerosnacks