From 0c0559d075346249f4a9349d26445bff105250a6 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 10 May 2019 12:41:09 -0400 Subject: [PATCH] Decode revert errors via ABI unpacking The code was doing its own interpretive dance, but it now uses the proper subbits of ABI decoding to interpret a returned error in a good general way. The structure used is the one described at https://github.com/ethereum/EIPs/issues/838#issuecomment-458919375 . --- pkg/chain/ethereum/error_resolver.go | 43 +++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/pkg/chain/ethereum/error_resolver.go b/pkg/chain/ethereum/error_resolver.go index d81bfb595a..0b32b6ecbd 100644 --- a/pkg/chain/ethereum/error_resolver.go +++ b/pkg/chain/ethereum/error_resolver.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/big" + "strings" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" @@ -11,6 +12,22 @@ import ( "github.com/ethereum/go-ethereum/ethclient" ) +// ABI for errors bubbled out from revert calls. Not used directly as errors are +// neither encoded strictly as method callss nor strictly as return values, nor +// strictly as events, but some various bits of it are used for unpacking the +// errors. See ResolveError below. +const errorABIString = "[{\"constant\":true,\"outputs\":[{\"type\":\"string\"}],\"inputs\":[{\"name\":\"message\", \"type\":\"string\"}],\"name\":\"Error\"}]" + +var errorABI abi.ABI + +func init() { + var err error + errorABI, err = abi.JSON(strings.NewReader(errorABIString)) + if err != nil { + panic(fmt.Sprintf("Failed to parse error ABI string: [%v] (ABI was: [%v])", err, errorABIString)) + } +} + // errorResolver bundles up the bits needed to turn errors like "failed to // estimate gas needed" that are triggered by contract reverts but don't include // revert causes into proper revert error messages from a contract by calling @@ -25,8 +42,8 @@ type errorResolver struct { // Resolves the given transaction error to a standard error that, if available, // contains the error message the transaction produced when reverting. -func (er *errorResolver) ResolveError(originalErr error, value *big.Int, method string, parameters ...interface{}) error { - packed, err := er.abi.Pack(method, parameters...) +func (er *errorResolver) ResolveError(originalErr error, value *big.Int, methodName string, parameters ...interface{}) error { + packed, err := er.abi.Pack(methodName, parameters...) msg := ethereum.CallMsg{To: er.address, Data: packed, Value: value} response, err := er.client.CallContract(context.TODO(), msg, nil) @@ -34,15 +51,27 @@ func (er *errorResolver) ResolveError(originalErr error, value *big.Int, method return fmt.Errorf("got error [%v] while resolving original error [%v]", err, originalErr) } - responseString := string(response) - if len(responseString) < 68 { - return fmt.Errorf("couldn't interpret contract return while resolving original error [%v]", originalErr) + // An error is returned as a 4-byte error id (same encoding as a method id) + // followed by a set of ABI-encoded values as if the error were a method + // that returned those values. + // + // Current spec-ish @ https://github.com/ethereum/EIPs/issues/838#issuecomment-458919375 + // Bless Ethereum's heart. + errorID, encodedReturns := response[0:3], response[4:] + + errorMethod, err := errorABI.MethodById(errorID) + if err != nil { + return fmt.Errorf("got [%v] while resolving original error [%v] on return [%v]", err, originalErr, response) + } + + errorValues, err := errorMethod.Outputs.UnpackValues(encodedReturns) + if err != nil { + return fmt.Errorf("got [%v] while resolving original error [%v] on return [%v]", err, originalErr, response) } - contractErrorMessage := responseString[68:] return fmt.Errorf( "contract failed with: [%v] (original error [%v])", - contractErrorMessage, + errorValues, originalErr, ) }