Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions evmd/tests/ibc/ics20_precompile_transfer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import (
"github.com/cosmos/evm/evmd"
"github.com/cosmos/evm/evmd/tests/integration"
"github.com/cosmos/evm/precompiles/ics20"
chainutil "github.com/cosmos/evm/testutil"
evmibctesting "github.com/cosmos/evm/testutil/ibc"
evmante "github.com/cosmos/evm/x/vm/ante"
evmtypes "github.com/cosmos/evm/x/vm/types"
transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"
clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"

Expand Down Expand Up @@ -299,8 +299,8 @@ func (suite *ICS20TransferTestSuite) TestHandleMsgTransfer() {
"INVALID-DENOM-HASH",
)
suite.Require().ErrorContains(err, vm.ErrExecutionReverted.Error())
revertErr := evmtypes.NewExecErrorWithReason(evmRes.Ret)
suite.Require().Contains(revertErr.ErrorData(), "invalid denom trace hash")
revertErr := chainutil.DecodeRevertReason(*evmRes)
suite.Require().Contains(revertErr.Error(), "invalid denom trace hash")

// denomHash query method
evmRes, err = evmAppB.EVMKeeper.CallEVM(
Expand Down Expand Up @@ -347,8 +347,8 @@ func (suite *ICS20TransferTestSuite) TestHandleMsgTransfer() {
"",
)
suite.Require().ErrorContains(err, vm.ErrExecutionReverted.Error())
revertErr = evmtypes.NewExecErrorWithReason(evmRes.Ret)
suite.Require().Contains(revertErr.ErrorData(), "invalid denomination for cross-chain transfer")
revertErr = chainutil.DecodeRevertReason(*evmRes)
suite.Require().Contains(revertErr.Error(), "invalid denomination for cross-chain transfer")
})
}
}
Expand Down
10 changes: 5 additions & 5 deletions evmd/tests/ibc/v2_ics20_precompile_transfer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import (
"github.com/cosmos/evm/evmd"
"github.com/cosmos/evm/evmd/tests/integration"
"github.com/cosmos/evm/precompiles/ics20"
chainutil "github.com/cosmos/evm/testutil"
evmibctesting "github.com/cosmos/evm/testutil/ibc"
evmante "github.com/cosmos/evm/x/vm/ante"
evmtypes "github.com/cosmos/evm/x/vm/types"
transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"
clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"

Expand Down Expand Up @@ -305,8 +305,8 @@ func (suite *ICS20TransferV2TestSuite) TestHandleMsgTransfer() {
"INVALID-DENOM-HASH",
)
suite.Require().ErrorContains(err, vm.ErrExecutionReverted.Error())
revertErr := evmtypes.NewExecErrorWithReason(evmRes.Ret)
suite.Require().Contains(revertErr.ErrorData(), "invalid denom trace hash")
revertErr := chainutil.DecodeRevertReason(*evmRes)
suite.Require().Contains(revertErr.Error(), "invalid denom trace hash")

// denomHash query method
evmRes, err = evmAppB.EVMKeeper.CallEVM(
Expand Down Expand Up @@ -353,8 +353,8 @@ func (suite *ICS20TransferV2TestSuite) TestHandleMsgTransfer() {
"",
)
suite.Require().ErrorContains(err, vm.ErrExecutionReverted.Error())
revertErr = evmtypes.NewExecErrorWithReason(evmRes.Ret)
suite.Require().Contains(revertErr.ErrorData(), "invalid denomination for cross-chain transfer")
revertErr = chainutil.DecodeRevertReason(*evmRes)
suite.Require().Contains(revertErr.Error(), "invalid denomination for cross-chain transfer")
})
}
}
Expand Down
4 changes: 2 additions & 2 deletions testutil/integration/evm/factory/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
abcitypes "github.com/cometbft/cometbft/abci/types"

"github.com/cosmos/evm/precompiles/testutil"
chainutil "github.com/cosmos/evm/testutil"
basefactory "github.com/cosmos/evm/testutil/integration/base/factory"
"github.com/cosmos/evm/testutil/integration/evm/grpc"
"github.com/cosmos/evm/testutil/integration/evm/network"
Expand Down Expand Up @@ -209,8 +210,7 @@ func (tf *IntegrationTxFactory) checkEthTxResponse(res *abcitypes.ExecTxResult)
}

if evmRes.Failed() {
revertErr := evmtypes.NewExecErrorWithReason(evmRes.Ret)
return fmt.Errorf("tx failed with VmError: %v: %s", evmRes.VmError, revertErr.ErrorData())
return chainutil.DecodeRevertReason(evmRes)
}
return nil
}
44 changes: 44 additions & 0 deletions testutil/util.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package testutil

import (
"bytes"
"context"
"fmt"
"time"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common/hexutil"

"github.com/cosmos/evm/crypto/ethsecp256k1"
evmtypes "github.com/cosmos/evm/x/vm/types"

errorsmod "cosmossdk.io/errors"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/tx"
Expand Down Expand Up @@ -108,3 +116,39 @@ func CreateTx(ctx context.Context, txCfg client.TxConfig, priv cryptotypes.PrivK

return txBuilder.GetTx(), nil
}

// DecodeRevertReason extracts and decodes the human-readable revert reason from an EVM transaction response.
// It processes the raw return data (Ret field) from a failed EVM transaction and attempts to decode
// any ABI-encoded revert messages into readable error strings.
//
// Returns:
// - error: A formatted error containing either:
// - "tx failed with VmError: <vmError>: <decoded_reason>" for successfully decoded reverts
// - "tx failed with VmError: <vmError>: <hex_data>" for non-decodable data
// - "failed to decode revert data: <decode_error>" if decoding fails
//
// Example usage:
//
// res, err := executeTransaction(...)
// if res.VmError != "" {
// decodedErr := DecodeRevertReason(res)
// // decodedErr might be: "tx failed with VmError: execution reverted: ERC20: insufficient allowance"
// }
func DecodeRevertReason(evmRes evmtypes.MsgEthereumTxResponse) error {
revertErr := evmtypes.NewExecErrorWithReason(evmRes.Ret)
hexData, ok := revertErr.ErrorData().(string)
if ok {
decodedBytes, err := hexutil.Decode(hexData)
if err == nil {
if len(decodedBytes) >= 4 && bytes.Equal(decodedBytes[:4], evmtypes.RevertSelector) {
var reason string
reason, err = abi.UnpackRevert(decodedBytes)
if err == nil {
return fmt.Errorf("tx failed with VmError: %v: %s", evmRes.VmError, reason)
}
}
}
return errorsmod.Wrap(err, "failed to decode revert data")
}
return fmt.Errorf("tx failed with VmError: %v: %s", evmRes.VmError, revertErr.ErrorData())
}
5 changes: 0 additions & 5 deletions x/vm/keeper/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,11 +444,6 @@ func (k *Keeper) ApplyMessageWithConfig(
// reset leftoverGas, to be used by the tracer
leftoverGas = msg.GasLimit - gasUsed

// if the execution reverted, we return the revert reason as the return data
if vmError == vm.ErrExecutionReverted.Error() {
ret = evm.Interpreter().ReturnData()
}

return &types.MsgEthereumTxResponse{
GasUsed: gasUsed,
VmError: vmError,
Expand Down
3 changes: 2 additions & 1 deletion x/vm/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"

errorsmod "cosmossdk.io/errors"
Expand Down Expand Up @@ -122,7 +123,7 @@ func NewExecErrorWithReason(revertReason []byte) *RevertError {
}
return &RevertError{
error: err,
reason: reason,
reason: hexutil.Encode(result),
}
}

Expand Down
51 changes: 51 additions & 0 deletions x/vm/types/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package types_test

import (
"testing"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"

"github.com/cosmos/evm/x/vm/types"
)

func TestNewExecErrorWithReason(t *testing.T) {
testCases := []struct {
name string
errorMessage string
revertReason []byte
data string
}{
{
"Empty reason",
"execution reverted",
nil,
"0x",
},
{
"With unpackable reason",
"execution reverted",
[]byte("a"),
"0x61",
},
{
"With packable reason but empty reason",
"execution reverted",
types.RevertSelector,
"0x08c379a0",
},
{
"With packable reason with reason",
"execution reverted: COUNTER_TOO_LOW",
hexutil.MustDecode("0x08C379A00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000F434F554E5445525F544F4F5F4C4F570000000000000000000000000000000000"),
"0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000f434f554e5445525f544f4f5f4c4f570000000000000000000000000000000000",
},
}

for _, tc := range testCases {
errWithReason := types.NewExecErrorWithReason(tc.revertReason)
require.Equal(t, tc.errorMessage, errWithReason.Error())
require.Equal(t, tc.data, errWithReason.ErrorData())
require.Equal(t, 3, errWithReason.ErrorCode())
}
}
Loading