feat(wallet)_: handle paraswap price impact error (#5622)
This commit is contained in:
parent
01288227d3
commit
d7fcbd3444
1
Makefile
1
Makefile
|
@ -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/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_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_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-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}
|
docker run --privileged --rm -it -v "$(PWD):$(DOCKER_TEST_WORKDIR)" -w "$(DOCKER_TEST_WORKDIR)" $(DOCKER_TEST_IMAGE) go test ${ARGS}
|
||||||
|
|
|
@ -46,6 +46,7 @@ var (
|
||||||
ErrContextDeadlineExceeded = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-036"), Details: "context deadline exceeded"}
|
ErrContextDeadlineExceeded = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-036"), Details: "context deadline exceeded"}
|
||||||
ErrPriceTimeout = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-037"), Details: "price timeout"}
|
ErrPriceTimeout = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-037"), Details: "price timeout"}
|
||||||
ErrNotEnoughLiquidity = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-038"), Details: "not enough liquidity"}
|
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 {
|
func createErrorResponse(processorName string, err error) error {
|
||||||
|
|
|
@ -28,7 +28,7 @@ type SwapParaswapTxArgs struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type SwapParaswapProcessor struct {
|
type SwapParaswapProcessor struct {
|
||||||
paraswapClient *paraswap.ClientV5
|
paraswapClient paraswap.ClientInterface
|
||||||
transactor transactions.TransactorIface
|
transactor transactions.TransactorIface
|
||||||
priceRoute sync.Map // [fromChainName-toChainName-fromTokenSymbol-toTokenSymbol, paraswap.Route]
|
priceRoute sync.Map // [fromChainName-toChainName-fromTokenSymbol-toTokenSymbol, paraswap.Route]
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,8 @@ func createSwapParaswapErrorResponse(err error) error {
|
||||||
return ErrPriceTimeout
|
return ErrPriceTimeout
|
||||||
case "No routes found with enough liquidity":
|
case "No routes found with enough liquidity":
|
||||||
return ErrNotEnoughLiquidity
|
return ErrNotEnoughLiquidity
|
||||||
|
case "ESTIMATED_LOSS_GREATER_THAN_MAX_IMPACT":
|
||||||
|
return ErrPriceImpactTooHigh
|
||||||
}
|
}
|
||||||
return createErrorResponse(ProcessorSwapParaswapName, err)
|
return createErrorResponse(ProcessorSwapParaswapName, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
package pathprocessor
|
package pathprocessor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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/params"
|
||||||
"github.com/status-im/status-go/services/wallet/bigint"
|
"github.com/status-im/status-go/services/wallet/bigint"
|
||||||
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
||||||
"github.com/status-im/status-go/services/wallet/thirdparty/paraswap"
|
"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/status-im/status-go/services/wallet/token"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"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: ¶ms.Network{ChainID: chainID},
|
||||||
|
ToChain: ¶ms.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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
Loading…
Reference in New Issue