From a0685a71f31c14f414c01cb5c7c91170fd0e84be Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Thu, 1 Feb 2024 14:25:55 -0700 Subject: [PATCH] Parse ABI errors in abi/bind --- accounts/abi/bind/base.go | 49 ++++++++++++++++++++++++++++++++++++--- accounts/abi/error.go | 6 ++--- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 1c87b69d52..8f620add38 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/rpc" ) const basefeeWiggleMultiplier = 2 @@ -150,6 +151,48 @@ func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend Co return c.address, tx, c, nil } +// parseError takes in an RPC error and attempts to parse any "execution reverted" +// data as an ABI error, returning an improved error if possible. +func (c *BoundContract) parseError(originalError error) error { + var dataErr rpc.DataError + if !errors.As(originalError, &dataErr) { + return originalError + } + dataString, ok := dataErr.ErrorData().(string) + if !ok { + return originalError + } + data := common.FromHex(dataString) + if len(data) < 4 { + return originalError + } + errAbi, _ := c.abi.ErrorByID([4]byte(data[:4])) + if errAbi == nil { + return originalError + } + vals, decodingErr := errAbi.Unpack(data) + if decodingErr != nil { + return fmt.Errorf("%w: failed to decode error as %v: %w", originalError, errAbi.Name, decodingErr) + } + for i, val := range vals { + bytes, ok := val.([32]byte) + if ok { + vals[i] = common.Hash(bytes) + } + } + fmtStr := "%w: %v(" + for i := range vals { + if i > 0 { + fmtStr += ", " + } + fmtStr += "%v" + } + fmtStr += ")" + fmtArgs := []any{originalError, errAbi.Name} + fmtArgs = append(fmtArgs, vals...) + return fmt.Errorf(fmtStr, fmtArgs...) +} + // Call invokes the (constant) contract method with params as input values and // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named @@ -180,7 +223,7 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri } output, err = pb.PendingCallContract(ctx, msg) if err != nil { - return err + return c.parseError(err) } if len(output) == 0 { // Make sure we have a contract to operate on, and bail out otherwise. @@ -193,7 +236,7 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri } else { output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber) if err != nil { - return err + return c.parseError(err) } if len(output) == 0 { // Make sure we have a contract to operate on, and bail out otherwise. @@ -357,7 +400,7 @@ func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Ad } gasLimit, err := c.transactor.EstimateGas(ensureContext(opts.Context), msg) if err != nil { - return 0, err + return 0, c.parseError(err) } // Arbitrum: adjust the estimate adjustedLimit := gasLimit * (10000 + opts.GasMargin) / 10000 diff --git a/accounts/abi/error.go b/accounts/abi/error.go index 218a22f1e4..6cdef70024 100644 --- a/accounts/abi/error.go +++ b/accounts/abi/error.go @@ -82,12 +82,12 @@ func (e Error) String() string { return e.str } -func (e *Error) Unpack(data []byte) (interface{}, error) { +func (e *Error) Unpack(data []byte) ([]interface{}, error) { if len(data) < 4 { - return "", errors.New("invalid data for unpacking") + return nil, errors.New("invalid data for unpacking") } if !bytes.Equal(data[:4], e.ID[:4]) { - return "", errors.New("invalid data for unpacking") + return nil, errors.New("invalid data for unpacking") } return e.Inputs.Unpack(data[4:]) }