feat(wallet)_: handle paraswap price impact error (#5622) (#5680)

This commit is contained in:
dlipicar 2024-08-09 14:52:53 -03:00 committed by GitHub
parent b8ceedec50
commit d4c6734a44
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 209 additions and 1 deletions

View File

@ -357,6 +357,7 @@ mock: ##@other Regenerate mocks
mockgen -package=mock_collectibles -destination=services/wallet/collectibles/mock/collection_data_db.go -source=services/wallet/collectibles/collection_data_db.go
mockgen -package=mock_collectibles -destination=services/wallet/collectibles/mock/collectible_data_db.go -source=services/wallet/collectibles/collectible_data_db.go
mockgen -package=mock_thirdparty -destination=services/wallet/thirdparty/mock/collectible_types.go -source=services/wallet/thirdparty/collectible_types.go
mockgen -package=mock_paraswap -destination=services/wallet/thirdparty/paraswap/mock/types.go -source=services/wallet/thirdparty/paraswap/types.go
docker-test: ##@tests Run tests in a docker container with golang.
docker run --privileged --rm -it -v "$(PWD):$(DOCKER_TEST_WORKDIR)" -w "$(DOCKER_TEST_WORKDIR)" $(DOCKER_TEST_IMAGE) go test ${ARGS}

View File

@ -46,6 +46,7 @@ var (
ErrContextDeadlineExceeded = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-036"), Details: "context deadline exceeded"}
ErrPriceTimeout = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-037"), Details: "price timeout"}
ErrNotEnoughLiquidity = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-038"), Details: "not enough liquidity"}
ErrPriceImpactTooHigh = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-039"), Details: "price impact too high"}
)
func createErrorResponse(processorName string, err error) error {

View File

@ -28,7 +28,7 @@ type SwapParaswapTxArgs struct {
}
type SwapParaswapProcessor struct {
paraswapClient *paraswap.ClientV5
paraswapClient paraswap.ClientInterface
transactor transactions.TransactorIface
priceRoute sync.Map // [fromChainName-toChainName-fromTokenSymbol-toTokenSymbol, paraswap.Route]
}
@ -73,6 +73,8 @@ func createSwapParaswapErrorResponse(err error) error {
return ErrPriceTimeout
case "No routes found with enough liquidity":
return ErrNotEnoughLiquidity
case "ESTIMATED_LOSS_GREATER_THAN_MAX_IMPACT":
return ErrPriceImpactTooHigh
}
return createErrorResponse(ProcessorSwapParaswapName, err)
}

View File

@ -1,14 +1,19 @@
package pathprocessor
import (
"errors"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
gomock "github.com/golang/mock/gomock"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/services/wallet/bigint"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/thirdparty/paraswap"
mock_paraswap "github.com/status-im/status-go/services/wallet/thirdparty/paraswap/mock"
"github.com/status-im/status-go/services/wallet/token"
"github.com/stretchr/testify/require"
@ -69,3 +74,60 @@ func TestParaswapWithPartnerFee(t *testing.T) {
}
}
}
func expectClientFetchPriceRoute(clientMock *mock_paraswap.MockClientInterface, route paraswap.Route, err error) {
clientMock.EXPECT().FetchPriceRoute(
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(route, err)
}
func TestParaswapErrors(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
client := mock_paraswap.NewMockClientInterface(ctrl)
processor := NewSwapParaswapProcessor(nil, nil, nil)
processor.paraswapClient = client
fromToken := token.Token{
Symbol: EthSymbol,
}
toToken := token.Token{
Symbol: UsdcSymbol,
}
chainID := walletCommon.EthereumMainnet
testInputParams := ProcessorInputParams{
FromChain: &params.Network{ChainID: chainID},
ToChain: &params.Network{ChainID: chainID},
FromToken: &fromToken,
ToToken: &toToken,
}
// Test Errors
type testCase struct {
clientError string
processorError error
}
testCases := []testCase{
{"Price Timeout", ErrPriceTimeout},
{"No routes found with enough liquidity", ErrNotEnoughLiquidity},
{"ESTIMATED_LOSS_GREATER_THAN_MAX_IMPACT", ErrPriceImpactTooHigh},
}
for _, tc := range testCases {
expectClientFetchPriceRoute(client, paraswap.Route{}, errors.New(tc.clientError))
_, err := processor.EstimateGas(testInputParams)
require.Equal(t, tc.processorError.Error(), err.Error())
}
}

View File

@ -0,0 +1,120 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: services/wallet/thirdparty/paraswap/types.go
// Package mock_paraswap is a generated GoMock package.
package mock_paraswap
import (
context "context"
json "encoding/json"
big "math/big"
reflect "reflect"
common "github.com/ethereum/go-ethereum/common"
gomock "github.com/golang/mock/gomock"
paraswap "github.com/status-im/status-go/services/wallet/thirdparty/paraswap"
)
// MockClientInterface is a mock of ClientInterface interface.
type MockClientInterface struct {
ctrl *gomock.Controller
recorder *MockClientInterfaceMockRecorder
}
// MockClientInterfaceMockRecorder is the mock recorder for MockClientInterface.
type MockClientInterfaceMockRecorder struct {
mock *MockClientInterface
}
// NewMockClientInterface creates a new mock instance.
func NewMockClientInterface(ctrl *gomock.Controller) *MockClientInterface {
mock := &MockClientInterface{ctrl: ctrl}
mock.recorder = &MockClientInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockClientInterface) EXPECT() *MockClientInterfaceMockRecorder {
return m.recorder
}
// BuildTransaction mocks base method.
func (m *MockClientInterface) BuildTransaction(ctx context.Context, srcTokenAddress common.Address, srcTokenDecimals uint, srcAmountWei *big.Int, destTokenAddress common.Address, destTokenDecimals uint, destAmountWei *big.Int, slippageBasisPoints uint, addressFrom, addressTo common.Address, priceRoute json.RawMessage, side paraswap.SwapSide) (paraswap.Transaction, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BuildTransaction", ctx, srcTokenAddress, srcTokenDecimals, srcAmountWei, destTokenAddress, destTokenDecimals, destAmountWei, slippageBasisPoints, addressFrom, addressTo, priceRoute, side)
ret0, _ := ret[0].(paraswap.Transaction)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BuildTransaction indicates an expected call of BuildTransaction.
func (mr *MockClientInterfaceMockRecorder) BuildTransaction(ctx, srcTokenAddress, srcTokenDecimals, srcAmountWei, destTokenAddress, destTokenDecimals, destAmountWei, slippageBasisPoints, addressFrom, addressTo, priceRoute, side interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildTransaction", reflect.TypeOf((*MockClientInterface)(nil).BuildTransaction), ctx, srcTokenAddress, srcTokenDecimals, srcAmountWei, destTokenAddress, destTokenDecimals, destAmountWei, slippageBasisPoints, addressFrom, addressTo, priceRoute, side)
}
// FetchPriceRoute mocks base method.
func (m *MockClientInterface) FetchPriceRoute(ctx context.Context, srcTokenAddress common.Address, srcTokenDecimals uint, destTokenAddress common.Address, destTokenDecimals uint, amountWei *big.Int, addressFrom, addressTo common.Address, side paraswap.SwapSide) (paraswap.Route, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FetchPriceRoute", ctx, srcTokenAddress, srcTokenDecimals, destTokenAddress, destTokenDecimals, amountWei, addressFrom, addressTo, side)
ret0, _ := ret[0].(paraswap.Route)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FetchPriceRoute indicates an expected call of FetchPriceRoute.
func (mr *MockClientInterfaceMockRecorder) FetchPriceRoute(ctx, srcTokenAddress, srcTokenDecimals, destTokenAddress, destTokenDecimals, amountWei, addressFrom, addressTo, side interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchPriceRoute", reflect.TypeOf((*MockClientInterface)(nil).FetchPriceRoute), ctx, srcTokenAddress, srcTokenDecimals, destTokenAddress, destTokenDecimals, amountWei, addressFrom, addressTo, side)
}
// FetchTokensList mocks base method.
func (m *MockClientInterface) FetchTokensList(ctx context.Context) ([]paraswap.Token, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FetchTokensList", ctx)
ret0, _ := ret[0].([]paraswap.Token)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FetchTokensList indicates an expected call of FetchTokensList.
func (mr *MockClientInterfaceMockRecorder) FetchTokensList(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchTokensList", reflect.TypeOf((*MockClientInterface)(nil).FetchTokensList), ctx)
}
// SetChainID mocks base method.
func (m *MockClientInterface) SetChainID(chainID uint64) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetChainID", chainID)
}
// SetChainID indicates an expected call of SetChainID.
func (mr *MockClientInterfaceMockRecorder) SetChainID(chainID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetChainID", reflect.TypeOf((*MockClientInterface)(nil).SetChainID), chainID)
}
// SetPartnerAddress mocks base method.
func (m *MockClientInterface) SetPartnerAddress(partnerAddress common.Address) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetPartnerAddress", partnerAddress)
}
// SetPartnerAddress indicates an expected call of SetPartnerAddress.
func (mr *MockClientInterfaceMockRecorder) SetPartnerAddress(partnerAddress interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPartnerAddress", reflect.TypeOf((*MockClientInterface)(nil).SetPartnerAddress), partnerAddress)
}
// SetPartnerFeePcnt mocks base method.
func (m *MockClientInterface) SetPartnerFeePcnt(partnerFeePcnt float64) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetPartnerFeePcnt", partnerFeePcnt)
}
// SetPartnerFeePcnt indicates an expected call of SetPartnerFeePcnt.
func (mr *MockClientInterfaceMockRecorder) SetPartnerFeePcnt(partnerFeePcnt interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPartnerFeePcnt", reflect.TypeOf((*MockClientInterface)(nil).SetPartnerFeePcnt), partnerFeePcnt)
}

View File

@ -0,0 +1,22 @@
package paraswap
import (
"context"
"encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common"
)
type ClientInterface interface {
SetChainID(chainID uint64)
SetPartnerAddress(partnerAddress common.Address)
SetPartnerFeePcnt(partnerFeePcnt float64)
BuildTransaction(ctx context.Context, srcTokenAddress common.Address, srcTokenDecimals uint, srcAmountWei *big.Int,
destTokenAddress common.Address, destTokenDecimals uint, destAmountWei *big.Int, slippageBasisPoints uint,
addressFrom common.Address, addressTo common.Address, priceRoute json.RawMessage, side SwapSide) (Transaction, error)
FetchPriceRoute(ctx context.Context, srcTokenAddress common.Address, srcTokenDecimals uint,
destTokenAddress common.Address, destTokenDecimals uint, amountWei *big.Int, addressFrom common.Address,
addressTo common.Address, side SwapSide) (Route, error)
FetchTokensList(ctx context.Context) ([]Token, error)
}