Skip to content

Commit

Permalink
feat: include abi error params in the returned error
Browse files Browse the repository at this point in the history
  • Loading branch information
mrekucci committed Oct 5, 2023
1 parent 18caf2a commit f947335
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 2 deletions.
12 changes: 11 additions & 1 deletion pkg/transaction/backendmock/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

type backendMock struct {
codeAt func(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
callContract func(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
sendTransaction func(ctx context.Context, tx *types.Transaction) error
suggestGasPrice func(ctx context.Context) (*big.Int, error)
suggestGasTipCap func(ctx context.Context) (*big.Int, error)
Expand All @@ -38,7 +39,10 @@ func (m *backendMock) CodeAt(ctx context.Context, contract common.Address, block
return nil, errors.New("not implemented")
}

func (*backendMock) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
func (m *backendMock) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
if m.callContract != nil {
return m.callContract(ctx, call, blockNumber)
}
return nil, errors.New("not implemented")
}

Expand Down Expand Up @@ -161,6 +165,12 @@ type optionFunc func(*backendMock)

func (f optionFunc) apply(r *backendMock) { f(r) }

func WithCallContractFunc(f func(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)) Option {
return optionFunc(func(s *backendMock) {
s.callContract = f
})
}

func WithCodeAtFunc(f func(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)) Option {
return optionFunc(func(s *backendMock) {
s.codeAt = f
Expand Down
2 changes: 1 addition & 1 deletion pkg/transaction/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ func (t *transactionService) UnwrapABIError(ctx context.Context, req *TxRequest,
if cErr == nil {
return err
}
err = fmt.Errorf("%w: %s", err, cErr)
err = fmt.Errorf("%w: %s", err, cErr) //nolint:errorlint

var derr rpc.DataError
if !errors.As(cErr, &derr) {
Expand Down
89 changes: 89 additions & 0 deletions pkg/transaction/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import (
"errors"
"fmt"
"math/big"
"strings"
"testing"
"time"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethersphere/bee/pkg/crypto"
signermock "github.com/ethersphere/bee/pkg/crypto/mock"
"github.com/ethersphere/bee/pkg/log"
Expand All @@ -23,6 +26,7 @@ import (
"github.com/ethersphere/bee/pkg/transaction"
"github.com/ethersphere/bee/pkg/transaction/backendmock"
"github.com/ethersphere/bee/pkg/transaction/monitormock"
"github.com/ethersphere/bee/pkg/util/abiutil"
"github.com/ethersphere/bee/pkg/util/testutil"
)

Expand Down Expand Up @@ -898,3 +902,88 @@ func TestTransactionCancel(t *testing.T) {
}
})
}

// rpcAPIError is a copy of engine.EngineAPIError from go-ethereum pkg.
type rpcAPIError struct {
code int
msg string
err string
}

func (e *rpcAPIError) ErrorCode() int { return e.code }
func (e *rpcAPIError) Error() string { return e.msg }
func (e *rpcAPIError) ErrorData() interface{} { return e.err }

var _ rpc.DataError = (*rpcAPIError)(nil)

func TestTransactionService_UnwrapABIError(t *testing.T) {
t.Parallel()

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

var (
recipient = common.HexToAddress("0xbbbddd")
chainID = big.NewInt(5)
nonce = uint64(10)
gasTip = big.NewInt(100)
gasFee = big.NewInt(1100)
txData = common.Hex2Bytes("0xabcdee")
value = big.NewInt(1)

// This is the ABI of the following contract: https://goerli.etherscan.io/address/0xd29d9e385f19d888557cd609006bb1934cb5d1e2#code
contractABI = abiutil.MustParseABI(`[{"inputs":[{"internalType":"uint256","name":"available","type":"uint256"},{"internalType":"uint256","name":"required","type":"uint256"}],"name":"InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[],"stateMutability":"nonpayable","type":"function"}]`)
rpcAPIErr = &rpcAPIError{
code: 3,
msg: "execution reverted",
err: "0xcf4791810000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006f", // This is the ABI encoded error form the following failed transaction: https://goerli.etherscan.io/tx/0x74a2577db1c325c41e38977aa1eb32ab03dfa17cc1fa0649e84f3d8c0f0882ee
}
)

gasTipCap := new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(10)+100), gasTip), big.NewInt(100))
gasFeeCap := new(big.Int).Add(gasFee, gasTipCap)

signedTx := types.NewTx(&types.DynamicFeeTx{
ChainID: chainID,
Nonce: nonce,
To: &recipient,
Value: value,
Gas: 21000,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Data: txData,
})
request := &transaction.TxRequest{
To: &recipient,
Data: txData,
Value: value,
}

transactionService, err := transaction.NewService(log.Noop,
backendmock.New(
backendmock.WithCallContractFunc(func(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
return nil, rpcAPIErr
}),
),
signerMockForTransaction(t, signedTx, recipient, chainID),
storemock.NewStateStore(),
chainID,
monitormock.New(),
)
if err != nil {
t.Fatal(err)
}
testutil.CleanupCloser(t, transactionService)

originErr := errors.New("origin error")
wrappedErr := transactionService.UnwrapABIError(ctx, request, originErr, contractABI.Errors)
if !errors.Is(wrappedErr, originErr) {
t.Fatal("origin error not wrapped")
}
if !strings.Contains(wrappedErr.Error(), rpcAPIErr.Error()) {
t.Fatal("wrapped error without rpc api main error")
}
if !strings.Contains(wrappedErr.Error(), "InsufficientBalance(available=0,required=111)") {
t.Fatal("wrapped error without rpc api error data")
}
}

0 comments on commit f947335

Please sign in to comment.