From f9473350cc37c2b26a2de1f8b8a2568694bf469b Mon Sep 17 00:00:00 2001 From: mrekucci Date: Thu, 5 Oct 2023 17:24:49 +0700 Subject: [PATCH] feat: include abi error params in the returned error --- pkg/transaction/backendmock/backend.go | 12 +++- pkg/transaction/transaction.go | 2 +- pkg/transaction/transaction_test.go | 89 ++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/pkg/transaction/backendmock/backend.go b/pkg/transaction/backendmock/backend.go index 8ce38614b6e..7f787fe3d60 100644 --- a/pkg/transaction/backendmock/backend.go +++ b/pkg/transaction/backendmock/backend.go @@ -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) @@ -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") } @@ -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 diff --git a/pkg/transaction/transaction.go b/pkg/transaction/transaction.go index d16494ba7b6..e969a785c3d 100644 --- a/pkg/transaction/transaction.go +++ b/pkg/transaction/transaction.go @@ -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) { diff --git a/pkg/transaction/transaction_test.go b/pkg/transaction/transaction_test.go index 8794fd33281..e00e061cd62 100644 --- a/pkg/transaction/transaction_test.go +++ b/pkg/transaction/transaction_test.go @@ -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" @@ -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" ) @@ -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") + } +}