mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
accounts, internal: resolve revert reason if possible
This commit is contained in:
parent
bc1e1a1de0
commit
b0821c0fcc
5 changed files with 94 additions and 7 deletions
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The ABI holds information about a contract's context and available
|
// The ABI holds information about a contract's context and available
|
||||||
|
|
@ -234,3 +235,25 @@ func (abi *ABI) HasFallback() bool {
|
||||||
func (abi *ABI) HasReceive() bool {
|
func (abi *ABI) HasReceive() bool {
|
||||||
return abi.Receive.Type == Receive
|
return abi.Receive.Type == Receive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// revertSelector is a special function selector for revert reason unpacking.
|
||||||
|
var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]
|
||||||
|
|
||||||
|
// UnpackRevert resolves the abi-encoded revert reason. According to the solidity
|
||||||
|
// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert,
|
||||||
|
// the provided revert reason is abi-encoded as if it were a call to a function
|
||||||
|
// `Error(string)`. So it's a special tool for it.
|
||||||
|
func UnpackRevert(data []byte) (string, error) {
|
||||||
|
if len(data) < 4 {
|
||||||
|
return "", errors.New("invalid data for unpacking")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(data[:4], revertSelector) {
|
||||||
|
return "", errors.New("invalid data for unpacking")
|
||||||
|
}
|
||||||
|
var reason string
|
||||||
|
typ, _ := NewType("string", "", nil)
|
||||||
|
if err := (Arguments{{Type: typ}}).Unpack(&reason, data[4:]); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return reason, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package abi
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
@ -1070,3 +1071,34 @@ func TestUnnamedEventParam(t *testing.T) {
|
||||||
t.Fatalf("Could not find input")
|
t.Fatalf("Could not find input")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnpackRevert(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var cases = []struct {
|
||||||
|
input string
|
||||||
|
expect string
|
||||||
|
expectErr error
|
||||||
|
}{
|
||||||
|
{"", "", errors.New("invalid data for unpacking")},
|
||||||
|
{"08c379a1", "", errors.New("invalid data for unpacking")},
|
||||||
|
{"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil},
|
||||||
|
}
|
||||||
|
for index, c := range cases {
|
||||||
|
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {
|
||||||
|
got, err := UnpackRevert(common.Hex2Bytes(c.input))
|
||||||
|
if c.expectErr != nil {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected non-nil error")
|
||||||
|
}
|
||||||
|
if err.Error() != c.expectErr.Error() {
|
||||||
|
t.Fatalf("Expected error mismatch, want %v, got %v", c.expectErr, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.expect != got {
|
||||||
|
t.Fatalf("Output mismatch, want %v, got %v", c.expect, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum"
|
"github.com/ethereum/go-ethereum"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
|
|
@ -445,7 +446,12 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
|
||||||
if result != nil && result.Err != vm.ErrOutOfGas {
|
if result != nil && result.Err != vm.ErrOutOfGas {
|
||||||
errMsg := fmt.Sprintf("always failing transaction (%v)", result.Err)
|
errMsg := fmt.Sprintf("always failing transaction (%v)", result.Err)
|
||||||
if len(result.Revert()) > 0 {
|
if len(result.Revert()) > 0 {
|
||||||
|
ret, err := abi.UnpackRevert(result.Revert())
|
||||||
|
if err != nil {
|
||||||
errMsg += fmt.Sprintf(" (0x%x)", result.Revert())
|
errMsg += fmt.Sprintf(" (0x%x)", result.Revert())
|
||||||
|
} else {
|
||||||
|
errMsg += fmt.Sprintf(" (%s)", ret)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0, errors.New(errMsg)
|
return 0, errors.New(errMsg)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -411,7 +411,7 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) {
|
||||||
GasPrice: big.NewInt(0),
|
GasPrice: big.NewInt(0),
|
||||||
Value: nil,
|
Value: nil,
|
||||||
Data: common.Hex2Bytes("d8b98391"),
|
Data: common.Hex2Bytes("d8b98391"),
|
||||||
}, 0, errors.New("always failing transaction (execution reverted) (0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000)")},
|
}, 0, errors.New("always failing transaction (execution reverted) (revert reason)")},
|
||||||
|
|
||||||
{"PureRevert", ethereum.CallMsg{
|
{"PureRevert", ethereum.CallMsg{
|
||||||
From: addr,
|
From: addr,
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
"github.com/ethereum/go-ethereum/accounts/scwallet"
|
"github.com/ethereum/go-ethereum/accounts/scwallet"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -796,7 +797,6 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
|
||||||
if state == nil || err != nil {
|
if state == nil || err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override the fields of specified contracts before execution.
|
// Override the fields of specified contracts before execution.
|
||||||
for addr, account := range overrides {
|
for addr, account := range overrides {
|
||||||
// Override account nonce.
|
// Override account nonce.
|
||||||
|
|
@ -882,6 +882,23 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr
|
||||||
return result.Return(), nil
|
return result.Return(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type estimateGasError struct {
|
||||||
|
error string // Concrete error type if it's failed to estimate gas usage
|
||||||
|
vmerr error // Additional field, it's non-nil if the given transaction is invalid
|
||||||
|
revert string // Additional field, it's non-empty if the transaction is reverted and reason is provided
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e estimateGasError) Error() string {
|
||||||
|
errMsg := e.error
|
||||||
|
if e.vmerr != nil {
|
||||||
|
errMsg += fmt.Sprintf(" (%v)", e.vmerr)
|
||||||
|
}
|
||||||
|
if e.revert != "" {
|
||||||
|
errMsg += fmt.Sprintf(" (%s)", e.revert)
|
||||||
|
}
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
|
||||||
func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, error) {
|
func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, error) {
|
||||||
// Binary search the gas requirement, as it may be higher than the amount used
|
// Binary search the gas requirement, as it may be higher than the amount used
|
||||||
var (
|
var (
|
||||||
|
|
@ -947,14 +964,23 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
|
||||||
}
|
}
|
||||||
if failed {
|
if failed {
|
||||||
if result != nil && result.Err != vm.ErrOutOfGas {
|
if result != nil && result.Err != vm.ErrOutOfGas {
|
||||||
errMsg := fmt.Sprintf("always failing transaction (%v)", result.Err)
|
var revert string
|
||||||
if len(result.Revert()) > 0 {
|
if len(result.Revert()) > 0 {
|
||||||
errMsg += fmt.Sprintf(" (0x%x)", result.Revert())
|
ret, err := abi.UnpackRevert(result.Revert())
|
||||||
|
if err != nil {
|
||||||
|
revert = "0x" + common.Bytes2Hex(result.Revert())
|
||||||
|
} else {
|
||||||
|
revert = ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, estimateGasError{
|
||||||
|
error: "always failing transaction",
|
||||||
|
vmerr: result.Err,
|
||||||
|
revert: revert,
|
||||||
}
|
}
|
||||||
return 0, errors.New(errMsg)
|
|
||||||
}
|
}
|
||||||
// Otherwise, the specified gas cap is too low
|
// Otherwise, the specified gas cap is too low
|
||||||
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
|
return 0, estimateGasError{error: fmt.Sprintf("gas required exceeds allowance (%d)", cap)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hexutil.Uint64(hi), nil
|
return hexutil.Uint64(hi), nil
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue