foundry-rs / foundry

Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.
https://getfoundry.sh
Apache License 2.0
8.28k stars 1.75k forks source link

Anvil as drop-in replacement for hardhat node #4388

Open PhilippLgh opened 1 year ago

PhilippLgh commented 1 year ago

Component

Anvil

Have you ensured that all of these are up to date?

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.

mattsse commented 1 year ago

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

ckoopmann commented 1 year ago

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

aathan commented 1 year ago

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

aathan commented 1 year ago

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