Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

fix(rpc): add revert reason for eth_estimateGas #1722

Closed
wants to merge 11 commits into from
Closed
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ Ref: https://keepachangelog.com/en/1.0.0/

## Unreleased

### Bug Fixes

* (rpc) [#1722](https://github.com/evmos/ethermint/pull/1722) Align revert response for `eth_estimateGas` and `eth_call` as Ethereum.


### State Machine Breaking

* (deps) [#1168](https://github.com/evmos/ethermint/pull/1168) Upgrade Cosmos SDK to [`v0.46.6`]
Expand Down
5 changes: 5 additions & 0 deletions proto/ethermint/evm/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@ message EthCallRequest {
message EstimateGasResponse {
// gas returns the estimated gas
uint64 gas = 1;
// ret is the returned data from evm function (result or data supplied with revert
// opcode)
bytes ret = 2;
// vm_error is the error returned by vm execution
string vm_error = 3;
}

// QueryTraceTxRequest defines TraceTx request
Expand Down
25 changes: 19 additions & 6 deletions rpc/backend/call_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,20 @@ func (b *Backend) SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.Transac
return args, nil
}

// handleRevertError returns revert related error.
func (b *Backend) handleRevertError(vmError string, ret []byte) error {
if len(vmError) > 0 {
if vmError != vm.ErrExecutionReverted.Error() {
return status.Error(codes.Internal, vmError)
}
if len(ret) == 0 {
return errors.New(vmError)
}
return evmtypes.NewExecErrorWithReason(ret)
}
return nil
}

// EstimateGas returns an estimate of gas usage for the given smart contract call.
func (b *Backend) EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *rpctypes.BlockNumber) (hexutil.Uint64, error) {
blockNr := rpctypes.EthPendingBlockNumber
Expand Down Expand Up @@ -336,6 +350,9 @@ func (b *Backend) EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *rp
if err != nil {
return 0, err
}
if err = b.handleRevertError(res.VmError, res.Ret); err != nil {
return 0, err
}
return hexutil.Uint64(res.Gas), nil
}

Expand Down Expand Up @@ -385,13 +402,9 @@ func (b *Backend) DoCall(
return nil, err
}

if res.Failed() {
if res.VmError != vm.ErrExecutionReverted.Error() {
return nil, status.Error(codes.Internal, res.VmError)
}
return nil, evmtypes.NewExecErrorWithReason(res.Ret)
if err = b.handleRevertError(res.VmError, res.Ret); err != nil {
return nil, err
}

return res, nil
}

Expand Down
14 changes: 14 additions & 0 deletions tests/integration_tests/hardhat/contracts/TestRevert.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

contract TestRevert {
// 0x9ffb86a5
function revertWithMsg() public pure {
revert("Function has been reverted");
}

// 0x3246485d
function revertWithoutMsg() public pure {
revert();
}
}
50 changes: 50 additions & 0 deletions tests/integration_tests/test_estimate_gas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import pytest

from .network import setup_ethermint
from .utils import ADDRS, CONTRACTS, deploy_contract


@pytest.fixture(scope="module")
def custom_ethermint(tmp_path_factory):
path = tmp_path_factory.mktemp("estimate-gas")
yield from setup_ethermint(path, 27010, long_timeout_commit=True)


@pytest.fixture(scope="module", params=["ethermint", "geth"])
def cluster(request, custom_ethermint, geth):
"""
run on both ethermint and geth
"""
provider = request.param
if provider == "ethermint":
yield custom_ethermint
elif provider == "geth":
yield geth
else:
raise NotImplementedError


def test_revert(cluster):
w3 = cluster.w3
call = w3.provider.make_request
validator = ADDRS["validator"]
erc20, _ = deploy_contract(
w3,
CONTRACTS["TestRevert"],
)
method = "eth_estimateGas"

def do_call(data):
params = {"from": validator, "to": erc20.address, "data": data}
return call(method, [params])["error"]

# revertWithMsg
error = do_call("0x9ffb86a5")
assert error["code"] == 3
assert error["message"] == "execution reverted: Function has been reverted"
assert (error["data"] == "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a46756e6374696f6e20686173206265656e207265766572746564000000000000") # noqa: E501

# revertWithoutMsg
error = do_call("0x3246485d")
assert error["code"] == -32000
assert error["message"] == "execution reverted"
1 change: 1 addition & 0 deletions tests/integration_tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"TestChainID": "ChainID.sol",
"Mars": "Mars.sol",
"StateContract": "StateContract.sol",
"TestRevert": "TestRevert.sol",
}


Expand Down
5 changes: 4 additions & 1 deletion x/evm/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,10 @@ func (k Keeper) EstimateGas(c context.Context, req *types.EthCallRequest) (*type
if failed {
if result != nil && result.VmError != vm.ErrOutOfGas.Error() {
if result.VmError == vm.ErrExecutionReverted.Error() {
return nil, types.NewExecErrorWithReason(result.Ret)
return &types.EstimateGasResponse{
Ret: result.Ret,
VmError: result.VmError,
}, nil
}
return nil, errors.New(result.VmError)
}
Expand Down
Loading