diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 772aaf5684..bcdf357a99 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1218,7 +1218,7 @@ type callResult struct { Logs []*types.Log `json:"logs"` GasUsed hexutil.Uint64 `json:"gasUsed"` Status hexutil.Uint64 `json:"status"` - Error string `json:"error,omitempty"` + Error *callError `json:"error,omitempty"` } func (r *callResult) MarshalJSON() ([]byte, error) { @@ -1326,7 +1326,8 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo } result, err := applyMessage(ctx, s.b, call, state, header, timeout, gp, &blockContext, vmConfig, precompiles, opts.Validation) if err != nil { - results[bi].Calls[i] = callResult{Error: err.Error(), Status: hexutil.Uint64(types.ReceiptStatusFailed)} + callErr := callErrorFromError(err) + results[bi].Calls[i] = callResult{Error: callErr, Status: hexutil.Uint64(types.ReceiptStatusFailed)} continue } // If the result contains a revert reason, try to unpack it. @@ -1337,7 +1338,11 @@ func (s *BlockChainAPI) MulticallV1(ctx context.Context, opts multicallOpts, blo callRes := callResult{ReturnValue: result.Return(), Logs: logs, GasUsed: hexutil.Uint64(result.UsedGas)} if result.Failed() { callRes.Status = hexutil.Uint64(types.ReceiptStatusFailed) - callRes.Error = result.Err.Error() + if errors.Is(result.Err, vm.ErrExecutionReverted) { + callRes.Error = &callError{Message: result.Err.Error(), Code: -32000} + } else { + callRes.Error = &callError{Message: result.Err.Error(), Code: -32015} + } } else { callRes.Status = hexutil.Uint64(types.ReceiptStatusSuccessful) } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 31636ced3e..9b61c84e4e 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -991,9 +991,13 @@ func TestMulticallV1(t *testing.T) { BlockHash common.Hash `json:"blockHash"` Index hexutil.Uint `json:"logIndex"` } + type callErr struct { + Message string + Code int + } type callRes struct { ReturnValue string `json:"returnData"` - Error string + Error callErr Logs []log GasUsed string Status string @@ -1043,7 +1047,7 @@ func TestMulticallV1(t *testing.T) { Number: "0xb", Hash: n11hash, GasLimit: "0x47e7c4", - GasUsed: "0xa410", + GasUsed: "0xf618", FeeRecipient: coinbase, Calls: []callRes{{ ReturnValue: "0x", @@ -1055,6 +1059,11 @@ func TestMulticallV1(t *testing.T) { GasUsed: "0x5208", Logs: []log{}, Status: "0x1", + }, { + ReturnValue: "0x", + GasUsed: "0x5208", + Logs: []log{}, + Status: "0x1", }}, }}, }, { @@ -1125,7 +1134,7 @@ func TestMulticallV1(t *testing.T) { GasUsed: "0x0", Logs: []log{}, Status: "0x0", - Error: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 4691388)", randomAccounts[3].addr.String()), + Error: callErr{Message: fmt.Sprintf("err: insufficient funds for gas * price + value: address %s have 0 want 1000 (supplied gas 4691388)", randomAccounts[3].addr.String()), Code: errCodeInsufficientFunds}, }}, }}, }, { @@ -1576,7 +1585,7 @@ func TestMulticallV1(t *testing.T) { GasUsed: "0x0", Logs: []log{}, Status: "0x0", - Error: fmt.Sprintf("err: nonce too high: address %s, tx: 2 state: 0 (supplied gas 4712388)", accounts[2].addr), + Error: callErr{Message: fmt.Sprintf("err: nonce too high: address %s, tx: 2 state: 0 (supplied gas 4712388)", accounts[2].addr), Code: errCodeNonceTooHigh}, }}, }}, }, diff --git a/internal/ethapi/errors.go b/internal/ethapi/errors.go new file mode 100644 index 0000000000..6ea92823a2 --- /dev/null +++ b/internal/ethapi/errors.go @@ -0,0 +1,70 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethapi + +import ( + "errors" + + "github.com/ethereum/go-ethereum/core" +) + +type callError struct { + Message string `json:"message"` + Code int `json:"code"` +} + +const ( + errCodeNonceTooHigh = -38011 + errCodeNonceTooLow = -38010 + errCodeInsufficientFunds = -38014 + errCodeIntrinsicGas = -38013 + errCodeInternalError = -32603 + errCodeInvalidParams = -32602 +) + +func callErrorFromError(err error) *callError { + if err == nil { + return nil + } + switch { + case errors.Is(err, core.ErrNonceTooHigh): + return &callError{Message: err.Error(), Code: errCodeNonceTooHigh} + case errors.Is(err, core.ErrNonceTooLow): + return &callError{Message: err.Error(), Code: errCodeNonceTooLow} + case errors.Is(err, core.ErrSenderNoEOA): + // TODO + case errors.Is(err, core.ErrFeeCapVeryHigh): + return &callError{Message: err.Error(), Code: errCodeInvalidParams} + case errors.Is(err, core.ErrTipVeryHigh): + return &callError{Message: err.Error(), Code: errCodeInvalidParams} + case errors.Is(err, core.ErrTipAboveFeeCap): + return &callError{Message: err.Error(), Code: errCodeInvalidParams} + case errors.Is(err, core.ErrFeeCapTooLow): + // TODO + return &callError{Message: err.Error(), Code: errCodeInvalidParams} + case errors.Is(err, core.ErrInsufficientFunds): + return &callError{Message: err.Error(), Code: errCodeInsufficientFunds} + case errors.Is(err, core.ErrIntrinsicGas): + return &callError{Message: err.Error(), Code: errCodeIntrinsicGas} + case errors.Is(err, core.ErrInsufficientFundsForTransfer): + return &callError{Message: err.Error(), Code: errCodeInsufficientFunds} + } + return &callError{ + Message: err.Error(), + Code: errCodeInternalError, + } +}