From b5ad0e7385de6c122e1ba71e986a71d5bbd83576 Mon Sep 17 00:00:00 2001 From: Iulian Pascalau Date: Wed, 11 Sep 2024 16:23:57 +0300 Subject: [PATCH 1/2] - updated aggregator package - added EVM gas price fetcher --- aggregator/fetchers/baseFetcher.go | 2 +- aggregator/fetchers/constants.go | 8 +- aggregator/fetchers/errors.go | 15 +- aggregator/fetchers/evmGasPriceFetcher.go | 76 ++++++++++ .../fetchers/evmGasPriceFetcher_test.go | 135 ++++++++++++++++++ aggregator/fetchers/factory.go | 16 ++- aggregator/fetchers/factory_test.go | 28 ++-- aggregator/fetchers/fetchers_test.go | 109 ++++++++++---- aggregator/fetchers/{okex.go => okx.go} | 24 ++-- examples/examplesPriceAggregator/main.go | 6 +- 10 files changed, 343 insertions(+), 76 deletions(-) create mode 100644 aggregator/fetchers/evmGasPriceFetcher.go create mode 100644 aggregator/fetchers/evmGasPriceFetcher_test.go rename aggregator/fetchers/{okex.go => okx.go} (56%) diff --git a/aggregator/fetchers/baseFetcher.go b/aggregator/fetchers/baseFetcher.go index 27b4d40a..1915358b 100644 --- a/aggregator/fetchers/baseFetcher.go +++ b/aggregator/fetchers/baseFetcher.go @@ -21,7 +21,7 @@ func newBaseFetcher() baseFetcher { func (b *baseFetcher) normalizeQuoteName(quote string, fetcherName string) string { if strings.Contains(quote, quoteUSDFiat) { switch fetcherName { - case BinanceName, CryptocomName, HitbtcName, HuobiName, OkexName: + case BinanceName, CryptocomName, HitbtcName, HuobiName, OkxName: return quoteUSDT default: return quoteUSDFiat diff --git a/aggregator/fetchers/constants.go b/aggregator/fetchers/constants.go index b64c4f5c..9e668d08 100644 --- a/aggregator/fetchers/constants.go +++ b/aggregator/fetchers/constants.go @@ -18,10 +18,12 @@ const ( HuobiName = "Huobi" // KrakenName defines the Kraken exchange name KrakenName = "Kraken" - // OkexName defines the Okex exchange name - OkexName = "Okex" + // OkxName defines the Okx exchange name + OkxName = "Okx" // XExchangeName defines the XExchange name XExchangeName = "XExchange" + // EVMGasPriceStation defines an EVM gas station that will push gas prices as a full token pair price + EVMGasPriceStation = "EVM gas price station" ) // ImplementedFetchers is the map of all implemented exchange fetchers @@ -33,6 +35,6 @@ var ImplementedFetchers = map[string]struct{}{ HitbtcName: {}, HuobiName: {}, KrakenName: {}, - OkexName: {}, + OkxName: {}, XExchangeName: {}, } diff --git a/aggregator/fetchers/errors.go b/aggregator/fetchers/errors.go index 27fc3322..29c41929 100644 --- a/aggregator/fetchers/errors.go +++ b/aggregator/fetchers/errors.go @@ -3,11 +3,12 @@ package fetchers import "errors" var ( - errInvalidResponseData = errors.New("invalid response data") - errInvalidFetcherName = errors.New("invalid fetcher name") - errNilResponseGetter = errors.New("nil response getter") - errNilGraphqlGetter = errors.New("nil graphql getter") - errNilXExchangeTokensMap = errors.New("nil xexchange tokens map") - errInvalidPair = errors.New("invalid pair") - errInvalidGraphqlResponse = errors.New("invalid graphql response") + errInvalidResponseData = errors.New("invalid response data") + errInvalidFetcherName = errors.New("invalid fetcher name") + errNilResponseGetter = errors.New("nil response getter") + errNilGraphqlGetter = errors.New("nil graphql getter") + errNilXExchangeTokensMap = errors.New("nil xexchange tokens map") + errInvalidPair = errors.New("invalid pair") + errInvalidGraphqlResponse = errors.New("invalid graphql response") + errInvalidGasPriceSelector = errors.New("invalid gas price selector") ) diff --git a/aggregator/fetchers/evmGasPriceFetcher.go b/aggregator/fetchers/evmGasPriceFetcher.go new file mode 100644 index 00000000..6f64817a --- /dev/null +++ b/aggregator/fetchers/evmGasPriceFetcher.go @@ -0,0 +1,76 @@ +package fetchers + +import ( + "context" + "fmt" + + "github.com/multiversx/mx-sdk-go/aggregator" +) + +const ( + evmFastGasPrice = "FastGasPrice" + evmSafeGasPrice = "SafeGasPrice" + evmProposeGasPrice = "ProposeGasPrice" +) + +// EVMGasPriceFetcherConfig represents the config DTO used for the gas price fetcher +type EVMGasPriceFetcherConfig struct { + ApiURL string + Selector string +} + +type gasStationResponse struct { + Status string `json:"status"` + Message string `json:"message"` + Result struct { + LastBlock string `json:"LastBlock"` + SafeGasPrice string `json:"SafeGasPrice"` + ProposeGasPrice string `json:"ProposeGasPrice"` + FastGasPrice string `json:"FastGasPrice"` + SuggestBaseFee string `json:"suggestBaseFee"` + GasUsedRatio string `json:"gasUsedRatio"` + } `json:"result"` +} + +type evmGasPriceFetcher struct { + aggregator.ResponseGetter + config EVMGasPriceFetcherConfig + baseFetcher +} + +// FetchPrice will fetch the price using the http client +func (fetcher *evmGasPriceFetcher) FetchPrice(ctx context.Context, base string, quote string) (float64, error) { + if !fetcher.hasPair(base, quote) { + return 0, aggregator.ErrPairNotSupported + } + + response := &gasStationResponse{} + err := fetcher.ResponseGetter.Get(ctx, fmt.Sprintf(fetcher.config.ApiURL), response) + if err != nil { + return 0, err + } + + latestGasPrice := 0 + switch fetcher.config.Selector { + case evmFastGasPrice: + _, err = fmt.Sscanf(response.Result.FastGasPrice, "%d", &latestGasPrice) + case evmProposeGasPrice: + _, err = fmt.Sscanf(response.Result.ProposeGasPrice, "%d", &latestGasPrice) + case evmSafeGasPrice: + _, err = fmt.Sscanf(response.Result.SafeGasPrice, "%d", &latestGasPrice) + default: + err = fmt.Errorf("%w: %q", errInvalidGasPriceSelector, fetcher.config.Selector) + } + + return float64(latestGasPrice), err +} + +// Name returns the name +func (fetcher *evmGasPriceFetcher) Name() string { + return fmt.Sprintf("%s when using selector %s", EVMGasPriceStation, fetcher.config.Selector) +} + +// IsInterfaceNil returns true if there is no value under the interface +func (fetcher *evmGasPriceFetcher) IsInterfaceNil() bool { + return fetcher == nil +} diff --git a/aggregator/fetchers/evmGasPriceFetcher_test.go b/aggregator/fetchers/evmGasPriceFetcher_test.go new file mode 100644 index 00000000..50c930bf --- /dev/null +++ b/aggregator/fetchers/evmGasPriceFetcher_test.go @@ -0,0 +1,135 @@ +package fetchers + +import ( + "context" + "errors" + "testing" + + "github.com/multiversx/mx-sdk-go/aggregator" + "github.com/multiversx/mx-sdk-go/aggregator/mock" + "github.com/stretchr/testify/assert" +) + +func createMockEVMGasPriceFetcher() *evmGasPriceFetcher { + return &evmGasPriceFetcher{ + ResponseGetter: &mock.HttpResponseGetterStub{}, + config: EVMGasPriceFetcherConfig{ + ApiURL: "api-url", + Selector: "SafeGasPrice", + }, + baseFetcher: newBaseFetcher(), + } +} + +func TestEvmGasPriceFetcher_IsInterfaceNil(t *testing.T) { + t.Parallel() + + var instance *evmGasPriceFetcher + assert.True(t, instance.IsInterfaceNil()) + + instance = &evmGasPriceFetcher{} + assert.False(t, instance.IsInterfaceNil()) +} + +func TestEvmGasPriceFetcher_Name(t *testing.T) { + t.Parallel() + + fetcher := &evmGasPriceFetcher{ + config: EVMGasPriceFetcherConfig{ + Selector: "test selector", + }, + } + + assert.Equal(t, "EVM gas price station when using selector test selector", fetcher.Name()) +} + +func TestKraken_FetchPrice(t *testing.T) { + t.Parallel() + + expectedErr := errors.New("expected error") + t.Run("un-added pair should error", func(t *testing.T) { + t.Parallel() + + fetcher := createMockEVMGasPriceFetcher() + value, err := fetcher.FetchPrice(context.Background(), "missing", "pair") + assert.Zero(t, value) + assert.Equal(t, aggregator.ErrPairNotSupported, err) + }) + t.Run("HTTP getter fails, should error", func(t *testing.T) { + t.Parallel() + + fetcher := createMockEVMGasPriceFetcher() + fetcher.AddPair("test", "pair") + fetcher.ResponseGetter = &mock.HttpResponseGetterStub{ + GetCalled: func(ctx context.Context, url string, response interface{}) error { + assert.Equal(t, "api-url", url) + return expectedErr + }, + } + value, err := fetcher.FetchPrice(context.Background(), "test", "pair") + assert.Zero(t, value) + assert.Equal(t, expectedErr, err) + }) + t.Run("invalid selector, should error", func(t *testing.T) { + t.Parallel() + + fetcher := createMockEVMGasPriceFetcher() + fetcher.config.Selector = "invalid-selector" + fetcher.AddPair("test", "pair") + fetcher.ResponseGetter = &mock.HttpResponseGetterStub{ + GetCalled: func(ctx context.Context, url string, response interface{}) error { + assert.Equal(t, "api-url", url) + return nil + }, + } + value, err := fetcher.FetchPrice(context.Background(), "test", "pair") + assert.Zero(t, value) + assert.ErrorIs(t, err, errInvalidGasPriceSelector) + assert.Contains(t, err.Error(), "invalid-selector") + }) + testHTTPResponseGetter := &mock.HttpResponseGetterStub{ + GetCalled: func(ctx context.Context, url string, response interface{}) error { + assert.Equal(t, "api-url", url) + gasStationResp := response.(*gasStationResponse) + gasStationResp.Result.SafeGasPrice = "37" + gasStationResp.Result.ProposeGasPrice = "38" + gasStationResp.Result.FastGasPrice = "39" + + return nil + }, + } + + t.Run("with SafeGasPrice should work", func(t *testing.T) { + t.Parallel() + + fetcher := createMockEVMGasPriceFetcher() + fetcher.config.Selector = evmSafeGasPrice + fetcher.AddPair("test", "pair") + fetcher.ResponseGetter = testHTTPResponseGetter + value, err := fetcher.FetchPrice(context.Background(), "test", "pair") + assert.Nil(t, err) + assert.Equal(t, float64(37), value) + }) + t.Run("with ProposeGasPrice should work", func(t *testing.T) { + t.Parallel() + + fetcher := createMockEVMGasPriceFetcher() + fetcher.config.Selector = evmProposeGasPrice + fetcher.AddPair("test", "pair") + fetcher.ResponseGetter = testHTTPResponseGetter + value, err := fetcher.FetchPrice(context.Background(), "test", "pair") + assert.Nil(t, err) + assert.Equal(t, float64(38), value) + }) + t.Run("with FastGasPrice should work", func(t *testing.T) { + t.Parallel() + + fetcher := createMockEVMGasPriceFetcher() + fetcher.config.Selector = evmFastGasPrice + fetcher.AddPair("test", "pair") + fetcher.ResponseGetter = testHTTPResponseGetter + value, err := fetcher.FetchPrice(context.Background(), "test", "pair") + assert.Nil(t, err) + assert.Equal(t, float64(39), value) + }) +} diff --git a/aggregator/fetchers/factory.go b/aggregator/fetchers/factory.go index 29d1bdc5..63a57a11 100644 --- a/aggregator/fetchers/factory.go +++ b/aggregator/fetchers/factory.go @@ -13,7 +13,7 @@ type XExchangeTokensPair struct { } // NewPriceFetcher returns a new price fetcher of the type provided -func NewPriceFetcher(fetcherName string, responseGetter aggregator.ResponseGetter, graphqlGetter aggregator.GraphqlGetter, xExchangeTokensMap map[string]XExchangeTokensPair) (aggregator.PriceFetcher, error) { +func NewPriceFetcher(fetcherName string, responseGetter aggregator.ResponseGetter, graphqlGetter aggregator.GraphqlGetter, xExchangeTokensMap map[string]XExchangeTokensPair, config EVMGasPriceFetcherConfig) (aggregator.PriceFetcher, error) { if responseGetter == nil { return nil, errNilResponseGetter } @@ -24,10 +24,10 @@ func NewPriceFetcher(fetcherName string, responseGetter aggregator.ResponseGette return nil, errNilXExchangeTokensMap } - return createFetcher(fetcherName, responseGetter, graphqlGetter, xExchangeTokensMap) + return createFetcher(fetcherName, responseGetter, graphqlGetter, xExchangeTokensMap, config) } -func createFetcher(fetcherName string, responseGetter aggregator.ResponseGetter, graphqlGetter aggregator.GraphqlGetter, xExchangeTokensMap map[string]XExchangeTokensPair) (aggregator.PriceFetcher, error) { +func createFetcher(fetcherName string, responseGetter aggregator.ResponseGetter, graphqlGetter aggregator.GraphqlGetter, xExchangeTokensMap map[string]XExchangeTokensPair, config EVMGasPriceFetcherConfig) (aggregator.PriceFetcher, error) { switch fetcherName { case BinanceName: return &binance{ @@ -64,8 +64,8 @@ func createFetcher(fetcherName string, responseGetter aggregator.ResponseGetter, ResponseGetter: responseGetter, baseFetcher: newBaseFetcher(), }, nil - case OkexName: - return &okex{ + case OkxName: + return &okx{ ResponseGetter: responseGetter, baseFetcher: newBaseFetcher(), }, nil @@ -75,6 +75,12 @@ func createFetcher(fetcherName string, responseGetter aggregator.ResponseGetter, baseFetcher: newBaseFetcher(), xExchangeTokensMap: xExchangeTokensMap, }, nil + case EVMGasPriceStation: + return &evmGasPriceFetcher{ + ResponseGetter: responseGetter, + config: config, + baseFetcher: newBaseFetcher(), + }, nil } return nil, fmt.Errorf("%w, fetcherName %s", errInvalidFetcherName, fetcherName) } diff --git a/aggregator/fetchers/factory_test.go b/aggregator/fetchers/factory_test.go index 59b98393..7490b29a 100644 --- a/aggregator/fetchers/factory_test.go +++ b/aggregator/fetchers/factory_test.go @@ -17,7 +17,7 @@ func TestNewPriceFetcher(t *testing.T) { t.Parallel() name := "invalid name" - pf, err := NewPriceFetcher(name, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, nil) + pf, err := NewPriceFetcher(name, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, nil, EVMGasPriceFetcherConfig{}) assert.Nil(t, pf) assert.True(t, errors.Is(err, errInvalidFetcherName)) assert.True(t, strings.Contains(err.Error(), name)) @@ -25,52 +25,52 @@ func TestNewPriceFetcher(t *testing.T) { t.Run("nil responseGetter should error", func(t *testing.T) { t.Parallel() - pf, err := NewPriceFetcher(BinanceName, nil, &mock.GraphqlResponseGetterStub{}, nil) + pf, err := NewPriceFetcher(BinanceName, nil, &mock.GraphqlResponseGetterStub{}, nil, EVMGasPriceFetcherConfig{}) assert.Nil(t, pf) assert.Equal(t, errNilResponseGetter, err) }) t.Run("nil graphqlGetter should error", func(t *testing.T) { t.Parallel() - pf, err := NewPriceFetcher(XExchangeName, &mock.HttpResponseGetterStub{}, nil, nil) + pf, err := NewPriceFetcher(XExchangeName, &mock.HttpResponseGetterStub{}, nil, nil, EVMGasPriceFetcherConfig{}) assert.Nil(t, pf) assert.True(t, errors.Is(err, errNilGraphqlGetter)) }) t.Run("nil map for xExchange should error", func(t *testing.T) { t.Parallel() - pf, err := NewPriceFetcher(XExchangeName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, nil) + pf, err := NewPriceFetcher(XExchangeName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, nil, EVMGasPriceFetcherConfig{}) assert.Nil(t, pf) assert.True(t, errors.Is(err, errNilXExchangeTokensMap)) }) t.Run("should work", func(t *testing.T) { t.Parallel() - pf, err := NewPriceFetcher(BinanceName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap()) + pf, err := NewPriceFetcher(BinanceName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) assert.Equal(t, "*fetchers.binance", fmt.Sprintf("%T", pf)) assert.Nil(t, err) - pf, err = NewPriceFetcher(BitfinexName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap()) + pf, err = NewPriceFetcher(BitfinexName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) assert.Equal(t, "*fetchers.bitfinex", fmt.Sprintf("%T", pf)) assert.Nil(t, err) - pf, err = NewPriceFetcher(CryptocomName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap()) + pf, err = NewPriceFetcher(CryptocomName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) assert.Equal(t, "*fetchers.cryptocom", fmt.Sprintf("%T", pf)) assert.Nil(t, err) - pf, err = NewPriceFetcher(GeminiName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap()) + pf, err = NewPriceFetcher(GeminiName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) assert.Equal(t, "*fetchers.gemini", fmt.Sprintf("%T", pf)) assert.Nil(t, err) - pf, err = NewPriceFetcher(HitbtcName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap()) + pf, err = NewPriceFetcher(HitbtcName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) assert.Equal(t, "*fetchers.hitbtc", fmt.Sprintf("%T", pf)) assert.Nil(t, err) - pf, err = NewPriceFetcher(HuobiName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap()) + pf, err = NewPriceFetcher(HuobiName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) assert.Equal(t, "*fetchers.huobi", fmt.Sprintf("%T", pf)) assert.Nil(t, err) - pf, err = NewPriceFetcher(KrakenName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap()) + pf, err = NewPriceFetcher(KrakenName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) assert.Equal(t, "*fetchers.kraken", fmt.Sprintf("%T", pf)) assert.Nil(t, err) - pf, err = NewPriceFetcher(OkexName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap()) - assert.Equal(t, "*fetchers.okex", fmt.Sprintf("%T", pf)) + pf, err = NewPriceFetcher(OkxName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) + assert.Equal(t, "*fetchers.okx", fmt.Sprintf("%T", pf)) assert.Nil(t, err) - pf, err = NewPriceFetcher(XExchangeName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap()) + pf, err = NewPriceFetcher(XExchangeName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) assert.Equal(t, "*fetchers.xExchange", fmt.Sprintf("%T", pf)) assert.Nil(t, err) }) diff --git a/aggregator/fetchers/fetchers_test.go b/aggregator/fetchers/fetchers_test.go index b7a508e3..151d72a4 100644 --- a/aggregator/fetchers/fetchers_test.go +++ b/aggregator/fetchers/fetchers_test.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "strconv" + "sync" "testing" "time" @@ -32,7 +33,7 @@ const networkAddress = "https://testnet-gateway.multiversx.com" func createMockMap() map[string]XExchangeTokensPair { return map[string]XExchangeTokensPair{ "ETH-USD": { - Base: "WEGLD-bd4d79", // for tests only until we have an ETH id + Base: "WETH-b4ca29", Quote: "USDC-c76f1f", }, "EGLD-USD": { @@ -91,6 +92,8 @@ func createAuthClient() (authentication.AuthClient, error) { func Test_FunctionalTesting(t *testing.T) { t.Parallel() + t.Skip("this test should be run only when doing debugging work on the component") + responseGetter, err := aggregator.NewHttpResponseGetter() require.Nil(t, err) @@ -100,21 +103,67 @@ func Test_FunctionalTesting(t *testing.T) { graphqlGetter, err := aggregator.NewGraphqlResponseGetter(authClient) require.Nil(t, err) - for f := range ImplementedFetchers { - fetcherName := f - t.Run("Test_FunctionalTesting_"+fetcherName, func(t *testing.T) { - t.Skip("this test should be run only when doing debugging work on the component") - - t.Parallel() - fetcher, _ := NewPriceFetcher(fetcherName, responseGetter, graphqlGetter, createMockMap()) + wg := sync.WaitGroup{} + wg.Add(len(ImplementedFetchers)) + for name := range ImplementedFetchers { + go func(fetcherName string) { + fetcher, _ := NewPriceFetcher(fetcherName, responseGetter, graphqlGetter, createMockMap(), EVMGasPriceFetcherConfig{}) ethTicker := "ETH" fetcher.AddPair(ethTicker, quoteUSDFiat) price, fetchErr := fetcher.FetchPrice(context.Background(), ethTicker, quoteUSDFiat) require.Nil(t, fetchErr) fmt.Printf("price between %s and %s is: %v from %s\n", ethTicker, quoteUSDFiat, price, fetcherName) require.True(t, price > 0) - }) + wg.Done() + }(name) } + + wg.Wait() +} + +func Test_FunctionalTestingForEVMGasPrice(t *testing.T) { + t.Parallel() + + t.Skip("this test should be run only when doing debugging work on the component") + + responseGetter, err := aggregator.NewHttpResponseGetter() + require.Nil(t, err) + + authClient, err := createAuthClient() + require.Nil(t, err) + + graphqlGetter, err := aggregator.NewGraphqlResponseGetter(authClient) + require.Nil(t, err) + + fetcher, _ := NewPriceFetcher( + EVMGasPriceStation, + responseGetter, + graphqlGetter, + createMockMap(), + EVMGasPriceFetcherConfig{ + ApiURL: "https://api.bscscan.com/api?module=gastracker&action=gasoracle", + Selector: "SafeGasPrice", + }) + fetcher.AddPair("BSC", "gas") + price, fetchErr := fetcher.FetchPrice(context.Background(), "BSC", "gas") + require.Nil(t, fetchErr) + fmt.Printf("gas price for %s and is: %v from %s\n", "BSC-gas", price, fetcher.Name()) + require.True(t, price > 0) + + fetcher, _ = NewPriceFetcher( + EVMGasPriceStation, + responseGetter, + graphqlGetter, + createMockMap(), + EVMGasPriceFetcherConfig{ + ApiURL: "https://api.etherscan.io/api?module=gastracker&action=gasoracle", + Selector: "SafeGasPrice", + }) + fetcher.AddPair("ETH", "gas") + price, fetchErr = fetcher.FetchPrice(context.Background(), "ETH", "gas") + require.Nil(t, fetchErr) + fmt.Printf("gas price for %s and is: %v from %s\n", "ETH-gas", price, fetcher.Name()) + require.True(t, price > 0) } func Test_FetchPriceErrors(t *testing.T) { @@ -137,13 +186,13 @@ func Test_FetchPriceErrors(t *testing.T) { }, &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, expectedError), - }, createMockMap()) + }, createMockMap(), EVMGasPriceFetcherConfig{}) assert.False(t, check.IfNil(fetcher)) fetcher.AddPair(ethTicker, quoteUSDFiat) price, err := fetcher.FetchPrice(context.Background(), ethTicker, quoteUSDFiat) - if err == errShouldSkipTest { + if errors.Is(err, errShouldSkipTest) { return } require.Equal(t, expectedError, err) @@ -159,13 +208,13 @@ func Test_FetchPriceErrors(t *testing.T) { }, &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, nil), - }, createMockMap()) + }, createMockMap(), EVMGasPriceFetcherConfig{}) assert.False(t, check.IfNil(fetcher)) fetcher.AddPair(ethTicker, quoteUSDFiat) price, err := fetcher.FetchPrice(context.Background(), ethTicker, quoteUSDFiat) - if err == errShouldSkipTest { + if errors.Is(err, errShouldSkipTest) { return } require.Equal(t, errInvalidResponseData, err) @@ -181,13 +230,13 @@ func Test_FetchPriceErrors(t *testing.T) { }, &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, nil), - }, createMockMap()) + }, createMockMap(), EVMGasPriceFetcherConfig{}) assert.False(t, check.IfNil(fetcher)) fetcher.AddPair(ethTicker, quoteUSDFiat) price, err := fetcher.FetchPrice(context.Background(), ethTicker, quoteUSDFiat) - if err == errShouldSkipTest { + if errors.Is(err, errShouldSkipTest) { return } require.Equal(t, errInvalidResponseData, err) @@ -203,13 +252,13 @@ func Test_FetchPriceErrors(t *testing.T) { }, &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, nil), - }, createMockMap()) + }, createMockMap(), EVMGasPriceFetcherConfig{}) assert.False(t, check.IfNil(fetcher)) fetcher.AddPair(ethTicker, quoteUSDFiat) price, err := fetcher.FetchPrice(context.Background(), ethTicker, quoteUSDFiat) - if err == errShouldSkipTest { + if errors.Is(err, errShouldSkipTest) { return } require.NotNil(t, err) @@ -228,14 +277,14 @@ func Test_FetchPriceErrors(t *testing.T) { &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, nil), - }, createMockMap()) + }, createMockMap(), EVMGasPriceFetcherConfig{}) assert.False(t, check.IfNil(fetcher)) missingTicker := "missing ticker" fetcher.AddPair(missingTicker, quoteUSDFiat) price, err := fetcher.FetchPrice(context.Background(), missingTicker, quoteUSDFiat) - if err == errShouldSkipTest { + if errors.Is(err, errShouldSkipTest) { return } assert.Equal(t, errInvalidPair, err) @@ -254,13 +303,13 @@ func Test_FetchPriceErrors(t *testing.T) { GetCalled: func(ctx context.Context, url string, query string, variables string) ([]byte, error) { return make([]byte, 0), nil }, - }, createMockMap()) + }, createMockMap(), EVMGasPriceFetcherConfig{}) assert.False(t, check.IfNil(fetcher)) fetcher.AddPair(ethTicker, quoteUSDFiat) price, err := fetcher.FetchPrice(context.Background(), ethTicker, quoteUSDFiat) - if err == errShouldSkipTest { + if errors.Is(err, errShouldSkipTest) { return } assert.Equal(t, errInvalidGraphqlResponse, err) @@ -276,12 +325,12 @@ func Test_FetchPriceErrors(t *testing.T) { }, &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, nil), - }, createMockMap()) + }, createMockMap(), EVMGasPriceFetcherConfig{}) assert.False(t, check.IfNil(fetcher)) price, err := fetcher.FetchPrice(context.Background(), ethTicker, quoteUSDFiat) - if err == errShouldSkipTest { + if errors.Is(err, errShouldSkipTest) { return } require.Equal(t, aggregator.ErrPairNotSupported, err) @@ -298,13 +347,13 @@ func Test_FetchPriceErrors(t *testing.T) { }, &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, nil), - }, createMockMap()) + }, createMockMap(), EVMGasPriceFetcherConfig{}) assert.False(t, check.IfNil(fetcher)) fetcher.AddPair(ethTicker, quoteUSDFiat) price, err := fetcher.FetchPrice(context.Background(), ethTicker, quoteUSDFiat) - if err == errShouldSkipTest { + if errors.Is(err, errShouldSkipTest) { return } require.Nil(t, err) @@ -323,12 +372,12 @@ func Test_FetchPriceErrors(t *testing.T) { }, &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, nil), - }, createMockMap()) + }, createMockMap(), EVMGasPriceFetcherConfig{}) assert.False(t, check.IfNil(fetcher)) fetcher.AddPair(btcTicker, quoteUSDFiat) price, err := fetcher.FetchPrice(context.Background(), btcTicker, quoteUSDFiat) - if err == errShouldSkipTest { + if errors.Is(err, errShouldSkipTest) { return } require.Nil(t, err) @@ -418,10 +467,10 @@ func getFuncGetCalled(name, returnPrice, pair string, returnErr error) func(ctx } return returnErr } - case OkexName: + case OkxName: return func(ctx context.Context, url string, response interface{}) error { - cast, _ := response.(*okexPriceRequest) - cast.Data = []okexTicker{{returnPrice}} + cast, _ := response.(*okxPriceRequest) + cast.Data = []okxTicker{{returnPrice}} return returnErr } } diff --git a/aggregator/fetchers/okex.go b/aggregator/fetchers/okx.go similarity index 56% rename from aggregator/fetchers/okex.go rename to aggregator/fetchers/okx.go index b95d4c39..4286e149 100644 --- a/aggregator/fetchers/okex.go +++ b/aggregator/fetchers/okx.go @@ -8,32 +8,32 @@ import ( ) const ( - okexPriceUrl = "https://www.okex.com/api/v5/market/ticker?instId=%s-%s" + okxPriceUrl = "https://www.okx.com/api/v5/market/ticker?instId=%s-%s" ) -type okexPriceRequest struct { - Data []okexTicker +type okxPriceRequest struct { + Data []okxTicker } -type okexTicker struct { +type okxTicker struct { Price string `json:"last"` } -type okex struct { +type okx struct { aggregator.ResponseGetter baseFetcher } // FetchPrice will fetch the price using the http client -func (o *okex) FetchPrice(ctx context.Context, base string, quote string) (float64, error) { +func (o *okx) FetchPrice(ctx context.Context, base string, quote string) (float64, error) { if !o.hasPair(base, quote) { return 0, aggregator.ErrPairNotSupported } - quote = o.normalizeQuoteName(quote, OkexName) + quote = o.normalizeQuoteName(quote, OkxName) - var opr okexPriceRequest - err := o.ResponseGetter.Get(ctx, fmt.Sprintf(okexPriceUrl, base, quote), &opr) + var opr okxPriceRequest + err := o.ResponseGetter.Get(ctx, fmt.Sprintf(okxPriceUrl, base, quote), &opr) if err != nil { return 0, err } @@ -48,11 +48,11 @@ func (o *okex) FetchPrice(ctx context.Context, base string, quote string) (float } // Name returns the name -func (o *okex) Name() string { - return OkexName +func (o *okx) Name() string { + return OkxName } // IsInterfaceNil returns true if there is no value under the interface -func (o *okex) IsInterfaceNil() bool { +func (o *okx) IsInterfaceNil() bool { return o == nil } diff --git a/examples/examplesPriceAggregator/main.go b/examples/examplesPriceAggregator/main.go index 5fa3c673..1e8ae391 100644 --- a/examples/examplesPriceAggregator/main.go +++ b/examples/examplesPriceAggregator/main.go @@ -161,9 +161,7 @@ func addPairToFetchers(pair *aggregator.ArgsPair, priceFetchers []aggregator.Pri func createXExchangeMap() map[string]fetchers.XExchangeTokensPair { return map[string]fetchers.XExchangeTokensPair{ "ETH-USD": { - // for tests only until we have an ETH id - // the price will be dropped as it is extreme compared to real price - Base: "WEGLD-bd4d79", + Base: "WETH-b4ca29", Quote: "USDC-c76f1f", }, } @@ -184,7 +182,7 @@ func createPriceFetchers() ([]aggregator.PriceFetcher, error) { } for exchangeName := range exchanges { - priceFetcher, errFetch := fetchers.NewPriceFetcher(exchangeName, httpResponseGetter, graphqlResponseGetter, createXExchangeMap()) + priceFetcher, errFetch := fetchers.NewPriceFetcher(exchangeName, httpResponseGetter, graphqlResponseGetter, createXExchangeMap(), fetchers.EVMGasPriceFetcherConfig{}) if errFetch != nil { return nil, errFetch } From 32a522731a97faf8ca4970a9630df6863bf1d5a9 Mon Sep 17 00:00:00 2001 From: Iulian Pascalau Date: Wed, 11 Sep 2024 18:58:23 +0300 Subject: [PATCH 2/2] - fix after review --- aggregator/fetchers/factory.go | 49 ++++--- aggregator/fetchers/factory_test.go | 148 +++++++++++++++----- aggregator/fetchers/fetchers_test.go | 167 ++++++++++++++--------- examples/examplesPriceAggregator/main.go | 9 +- 4 files changed, 257 insertions(+), 116 deletions(-) diff --git a/aggregator/fetchers/factory.go b/aggregator/fetchers/factory.go index 63a57a11..749b505b 100644 --- a/aggregator/fetchers/factory.go +++ b/aggregator/fetchers/factory.go @@ -12,75 +12,84 @@ type XExchangeTokensPair struct { Quote string } +// ArgsPriceFetcher represents the arguments for the NewPriceFetcher function +type ArgsPriceFetcher struct { + FetcherName string + ResponseGetter aggregator.ResponseGetter + GraphqlGetter aggregator.GraphqlGetter + XExchangeTokensMap map[string]XExchangeTokensPair + EVMGasConfig EVMGasPriceFetcherConfig +} + // NewPriceFetcher returns a new price fetcher of the type provided -func NewPriceFetcher(fetcherName string, responseGetter aggregator.ResponseGetter, graphqlGetter aggregator.GraphqlGetter, xExchangeTokensMap map[string]XExchangeTokensPair, config EVMGasPriceFetcherConfig) (aggregator.PriceFetcher, error) { - if responseGetter == nil { +func NewPriceFetcher(args ArgsPriceFetcher) (aggregator.PriceFetcher, error) { + if args.ResponseGetter == nil { return nil, errNilResponseGetter } - if graphqlGetter == nil { + if args.GraphqlGetter == nil { return nil, errNilGraphqlGetter } - if xExchangeTokensMap == nil && fetcherName == XExchangeName { + if args.XExchangeTokensMap == nil && args.FetcherName == XExchangeName { return nil, errNilXExchangeTokensMap } - return createFetcher(fetcherName, responseGetter, graphqlGetter, xExchangeTokensMap, config) + return createFetcher(args) } -func createFetcher(fetcherName string, responseGetter aggregator.ResponseGetter, graphqlGetter aggregator.GraphqlGetter, xExchangeTokensMap map[string]XExchangeTokensPair, config EVMGasPriceFetcherConfig) (aggregator.PriceFetcher, error) { - switch fetcherName { +func createFetcher(args ArgsPriceFetcher) (aggregator.PriceFetcher, error) { + switch args.FetcherName { case BinanceName: return &binance{ - ResponseGetter: responseGetter, + ResponseGetter: args.ResponseGetter, baseFetcher: newBaseFetcher(), }, nil case BitfinexName: return &bitfinex{ - ResponseGetter: responseGetter, + ResponseGetter: args.ResponseGetter, baseFetcher: newBaseFetcher(), }, nil case CryptocomName: return &cryptocom{ - ResponseGetter: responseGetter, + ResponseGetter: args.ResponseGetter, baseFetcher: newBaseFetcher(), }, nil case GeminiName: return &gemini{ - ResponseGetter: responseGetter, + ResponseGetter: args.ResponseGetter, baseFetcher: newBaseFetcher(), }, nil case HitbtcName: return &hitbtc{ - ResponseGetter: responseGetter, + ResponseGetter: args.ResponseGetter, baseFetcher: newBaseFetcher(), }, nil case HuobiName: return &huobi{ - ResponseGetter: responseGetter, + ResponseGetter: args.ResponseGetter, baseFetcher: newBaseFetcher(), }, nil case KrakenName: return &kraken{ - ResponseGetter: responseGetter, + ResponseGetter: args.ResponseGetter, baseFetcher: newBaseFetcher(), }, nil case OkxName: return &okx{ - ResponseGetter: responseGetter, + ResponseGetter: args.ResponseGetter, baseFetcher: newBaseFetcher(), }, nil case XExchangeName: return &xExchange{ - GraphqlGetter: graphqlGetter, + GraphqlGetter: args.GraphqlGetter, baseFetcher: newBaseFetcher(), - xExchangeTokensMap: xExchangeTokensMap, + xExchangeTokensMap: args.XExchangeTokensMap, }, nil case EVMGasPriceStation: return &evmGasPriceFetcher{ - ResponseGetter: responseGetter, - config: config, + ResponseGetter: args.ResponseGetter, + config: args.EVMGasConfig, baseFetcher: newBaseFetcher(), }, nil } - return nil, fmt.Errorf("%w, fetcherName %s", errInvalidFetcherName, fetcherName) + return nil, fmt.Errorf("%w, fetcherName %s", errInvalidFetcherName, args.FetcherName) } diff --git a/aggregator/fetchers/factory_test.go b/aggregator/fetchers/factory_test.go index 7490b29a..7b7a9634 100644 --- a/aggregator/fetchers/factory_test.go +++ b/aggregator/fetchers/factory_test.go @@ -10,68 +10,150 @@ import ( "github.com/stretchr/testify/assert" ) +func createMockArgsPriceFetcher() ArgsPriceFetcher { + return ArgsPriceFetcher{ + FetcherName: BinanceName, + ResponseGetter: &mock.HttpResponseGetterStub{}, + GraphqlGetter: &mock.GraphqlResponseGetterStub{}, + XExchangeTokensMap: createMockMap(), + EVMGasConfig: EVMGasPriceFetcherConfig{}, + } +} + func TestNewPriceFetcher(t *testing.T) { t.Parallel() t.Run("invalid fetcher name should error", func(t *testing.T) { t.Parallel() - name := "invalid name" - pf, err := NewPriceFetcher(name, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, nil, EVMGasPriceFetcherConfig{}) + args := createMockArgsPriceFetcher() + args.FetcherName = "invalid name" + pf, err := NewPriceFetcher(args) assert.Nil(t, pf) assert.True(t, errors.Is(err, errInvalidFetcherName)) - assert.True(t, strings.Contains(err.Error(), name)) + assert.True(t, strings.Contains(err.Error(), args.FetcherName)) }) t.Run("nil responseGetter should error", func(t *testing.T) { t.Parallel() - pf, err := NewPriceFetcher(BinanceName, nil, &mock.GraphqlResponseGetterStub{}, nil, EVMGasPriceFetcherConfig{}) + args := createMockArgsPriceFetcher() + args.ResponseGetter = nil + pf, err := NewPriceFetcher(args) assert.Nil(t, pf) assert.Equal(t, errNilResponseGetter, err) }) t.Run("nil graphqlGetter should error", func(t *testing.T) { t.Parallel() - pf, err := NewPriceFetcher(XExchangeName, &mock.HttpResponseGetterStub{}, nil, nil, EVMGasPriceFetcherConfig{}) + args := createMockArgsPriceFetcher() + args.FetcherName = XExchangeName + args.GraphqlGetter = nil + pf, err := NewPriceFetcher(args) assert.Nil(t, pf) assert.True(t, errors.Is(err, errNilGraphqlGetter)) }) t.Run("nil map for xExchange should error", func(t *testing.T) { t.Parallel() - pf, err := NewPriceFetcher(XExchangeName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, nil, EVMGasPriceFetcherConfig{}) + args := createMockArgsPriceFetcher() + args.FetcherName = XExchangeName + args.XExchangeTokensMap = nil + pf, err := NewPriceFetcher(args) assert.Nil(t, pf) assert.True(t, errors.Is(err, errNilXExchangeTokensMap)) }) t.Run("should work", func(t *testing.T) { t.Parallel() - pf, err := NewPriceFetcher(BinanceName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) - assert.Equal(t, "*fetchers.binance", fmt.Sprintf("%T", pf)) - assert.Nil(t, err) - pf, err = NewPriceFetcher(BitfinexName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) - assert.Equal(t, "*fetchers.bitfinex", fmt.Sprintf("%T", pf)) - assert.Nil(t, err) - pf, err = NewPriceFetcher(CryptocomName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) - assert.Equal(t, "*fetchers.cryptocom", fmt.Sprintf("%T", pf)) - assert.Nil(t, err) - pf, err = NewPriceFetcher(GeminiName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) - assert.Equal(t, "*fetchers.gemini", fmt.Sprintf("%T", pf)) - assert.Nil(t, err) - pf, err = NewPriceFetcher(HitbtcName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) - assert.Equal(t, "*fetchers.hitbtc", fmt.Sprintf("%T", pf)) - assert.Nil(t, err) - pf, err = NewPriceFetcher(HuobiName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) - assert.Equal(t, "*fetchers.huobi", fmt.Sprintf("%T", pf)) - assert.Nil(t, err) - pf, err = NewPriceFetcher(KrakenName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) - assert.Equal(t, "*fetchers.kraken", fmt.Sprintf("%T", pf)) - assert.Nil(t, err) - pf, err = NewPriceFetcher(OkxName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) - assert.Equal(t, "*fetchers.okx", fmt.Sprintf("%T", pf)) - assert.Nil(t, err) - pf, err = NewPriceFetcher(XExchangeName, &mock.HttpResponseGetterStub{}, &mock.GraphqlResponseGetterStub{}, createMockMap(), EVMGasPriceFetcherConfig{}) - assert.Equal(t, "*fetchers.xExchange", fmt.Sprintf("%T", pf)) - assert.Nil(t, err) + t.Run("Binance", func(t *testing.T) { + t.Parallel() + + args := createMockArgsPriceFetcher() + args.FetcherName = BinanceName + pf, err := NewPriceFetcher(args) + assert.Equal(t, "*fetchers.binance", fmt.Sprintf("%T", pf)) + assert.Nil(t, err) + }) + t.Run("Bitfinex", func(t *testing.T) { + t.Parallel() + + args := createMockArgsPriceFetcher() + args.FetcherName = BitfinexName + pf, err := NewPriceFetcher(args) + assert.Equal(t, "*fetchers.bitfinex", fmt.Sprintf("%T", pf)) + assert.Nil(t, err) + }) + t.Run("CryptoCom", func(t *testing.T) { + t.Parallel() + + args := createMockArgsPriceFetcher() + args.FetcherName = CryptocomName + pf, err := NewPriceFetcher(args) + assert.Equal(t, "*fetchers.cryptocom", fmt.Sprintf("%T", pf)) + assert.Nil(t, err) + }) + t.Run("Gemini", func(t *testing.T) { + t.Parallel() + + args := createMockArgsPriceFetcher() + args.FetcherName = GeminiName + pf, err := NewPriceFetcher(args) + assert.Equal(t, "*fetchers.gemini", fmt.Sprintf("%T", pf)) + assert.Nil(t, err) + }) + t.Run("Hitbtc", func(t *testing.T) { + t.Parallel() + + args := createMockArgsPriceFetcher() + args.FetcherName = HitbtcName + pf, err := NewPriceFetcher(args) + assert.Equal(t, "*fetchers.hitbtc", fmt.Sprintf("%T", pf)) + assert.Nil(t, err) + }) + t.Run("Huobi", func(t *testing.T) { + t.Parallel() + + args := createMockArgsPriceFetcher() + args.FetcherName = HuobiName + pf, err := NewPriceFetcher(args) + assert.Equal(t, "*fetchers.huobi", fmt.Sprintf("%T", pf)) + assert.Nil(t, err) + }) + t.Run("Kraken", func(t *testing.T) { + t.Parallel() + + args := createMockArgsPriceFetcher() + args.FetcherName = KrakenName + pf, err := NewPriceFetcher(args) + assert.Equal(t, "*fetchers.kraken", fmt.Sprintf("%T", pf)) + assert.Nil(t, err) + }) + t.Run("Okx", func(t *testing.T) { + t.Parallel() + + args := createMockArgsPriceFetcher() + args.FetcherName = OkxName + pf, err := NewPriceFetcher(args) + assert.Equal(t, "*fetchers.okx", fmt.Sprintf("%T", pf)) + assert.Nil(t, err) + }) + t.Run("xExchange", func(t *testing.T) { + t.Parallel() + + args := createMockArgsPriceFetcher() + args.FetcherName = XExchangeName + pf, err := NewPriceFetcher(args) + assert.Equal(t, "*fetchers.xExchange", fmt.Sprintf("%T", pf)) + assert.Nil(t, err) + }) + t.Run("EVM gas price", func(t *testing.T) { + t.Parallel() + + args := createMockArgsPriceFetcher() + args.FetcherName = EVMGasPriceStation + pf, err := NewPriceFetcher(args) + assert.Equal(t, "*fetchers.evmGasPriceFetcher", fmt.Sprintf("%T", pf)) + assert.Nil(t, err) + }) }) } diff --git a/aggregator/fetchers/fetchers_test.go b/aggregator/fetchers/fetchers_test.go index 151d72a4..770ef8fa 100644 --- a/aggregator/fetchers/fetchers_test.go +++ b/aggregator/fetchers/fetchers_test.go @@ -107,7 +107,14 @@ func Test_FunctionalTesting(t *testing.T) { wg.Add(len(ImplementedFetchers)) for name := range ImplementedFetchers { go func(fetcherName string) { - fetcher, _ := NewPriceFetcher(fetcherName, responseGetter, graphqlGetter, createMockMap(), EVMGasPriceFetcherConfig{}) + args := ArgsPriceFetcher{ + FetcherName: fetcherName, + ResponseGetter: responseGetter, + GraphqlGetter: graphqlGetter, + XExchangeTokensMap: createMockMap(), + EVMGasConfig: EVMGasPriceFetcherConfig{}, + } + fetcher, _ := NewPriceFetcher(args) ethTicker := "ETH" fetcher.AddPair(ethTicker, quoteUSDFiat) price, fetchErr := fetcher.FetchPrice(context.Background(), ethTicker, quoteUSDFiat) @@ -135,30 +142,29 @@ func Test_FunctionalTestingForEVMGasPrice(t *testing.T) { graphqlGetter, err := aggregator.NewGraphqlResponseGetter(authClient) require.Nil(t, err) - fetcher, _ := NewPriceFetcher( - EVMGasPriceStation, - responseGetter, - graphqlGetter, - createMockMap(), - EVMGasPriceFetcherConfig{ + // IMPORTANT: on the API URL value we should append &apikey= + // with api keys created on the bscscan.io & etherscan.io. + // free plan should suffice for intended purpose + + args := ArgsPriceFetcher{ + FetcherName: EVMGasPriceStation, + ResponseGetter: responseGetter, + GraphqlGetter: graphqlGetter, + XExchangeTokensMap: createMockMap(), + EVMGasConfig: EVMGasPriceFetcherConfig{ ApiURL: "https://api.bscscan.com/api?module=gastracker&action=gasoracle", Selector: "SafeGasPrice", - }) + }, + } + fetcher, _ := NewPriceFetcher(args) fetcher.AddPair("BSC", "gas") price, fetchErr := fetcher.FetchPrice(context.Background(), "BSC", "gas") require.Nil(t, fetchErr) fmt.Printf("gas price for %s and is: %v from %s\n", "BSC-gas", price, fetcher.Name()) require.True(t, price > 0) - fetcher, _ = NewPriceFetcher( - EVMGasPriceStation, - responseGetter, - graphqlGetter, - createMockMap(), - EVMGasPriceFetcherConfig{ - ApiURL: "https://api.etherscan.io/api?module=gastracker&action=gasoracle", - Selector: "SafeGasPrice", - }) + args.EVMGasConfig.ApiURL = "https://api.etherscan.io/api?module=gastracker&action=gasoracle" + fetcher, _ = NewPriceFetcher(args) fetcher.AddPair("ETH", "gas") price, fetchErr = fetcher.FetchPrice(context.Background(), "ETH", "gas") require.Nil(t, fetchErr) @@ -172,22 +178,26 @@ func Test_FetchPriceErrors(t *testing.T) { ethTicker := "ETH" pair := ethTicker + quoteUSDFiat + expectedError := errors.New("expected error") for f := range ImplementedFetchers { fetcherName := f t.Run("response getter errors should error "+fetcherName, func(t *testing.T) { t.Parallel() - expectedError := errors.New("expected error") returnPrice := "" - fetcher, _ := NewPriceFetcher(fetcherName, - &mock.HttpResponseGetterStub{ + args := ArgsPriceFetcher{ + FetcherName: fetcherName, + ResponseGetter: &mock.HttpResponseGetterStub{ GetCalled: getFuncGetCalled(fetcherName, returnPrice, pair, expectedError), }, - &mock.GraphqlResponseGetterStub{ + GraphqlGetter: &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, expectedError), - }, createMockMap(), EVMGasPriceFetcherConfig{}) - + }, + XExchangeTokensMap: createMockMap(), + EVMGasConfig: EVMGasPriceFetcherConfig{}, + } + fetcher, _ := NewPriceFetcher(args) assert.False(t, check.IfNil(fetcher)) fetcher.AddPair(ethTicker, quoteUSDFiat) @@ -202,14 +212,18 @@ func Test_FetchPriceErrors(t *testing.T) { t.Parallel() returnPrice := "" - fetcher, _ := NewPriceFetcher(fetcherName, - &mock.HttpResponseGetterStub{ + args := ArgsPriceFetcher{ + FetcherName: fetcherName, + ResponseGetter: &mock.HttpResponseGetterStub{ GetCalled: getFuncGetCalled(fetcherName, returnPrice, pair, nil), }, - &mock.GraphqlResponseGetterStub{ + GraphqlGetter: &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, nil), - }, createMockMap(), EVMGasPriceFetcherConfig{}) - + }, + XExchangeTokensMap: createMockMap(), + EVMGasConfig: EVMGasPriceFetcherConfig{}, + } + fetcher, _ := NewPriceFetcher(args) assert.False(t, check.IfNil(fetcher)) fetcher.AddPair(ethTicker, quoteUSDFiat) @@ -224,14 +238,18 @@ func Test_FetchPriceErrors(t *testing.T) { t.Parallel() returnPrice := "-1" - fetcher, _ := NewPriceFetcher(fetcherName, - &mock.HttpResponseGetterStub{ + args := ArgsPriceFetcher{ + FetcherName: fetcherName, + ResponseGetter: &mock.HttpResponseGetterStub{ GetCalled: getFuncGetCalled(fetcherName, returnPrice, pair, nil), }, - &mock.GraphqlResponseGetterStub{ + GraphqlGetter: &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, nil), - }, createMockMap(), EVMGasPriceFetcherConfig{}) - + }, + XExchangeTokensMap: createMockMap(), + EVMGasConfig: EVMGasPriceFetcherConfig{}, + } + fetcher, _ := NewPriceFetcher(args) assert.False(t, check.IfNil(fetcher)) fetcher.AddPair(ethTicker, quoteUSDFiat) @@ -246,14 +264,18 @@ func Test_FetchPriceErrors(t *testing.T) { t.Parallel() returnPrice := "not a number" - fetcher, _ := NewPriceFetcher(fetcherName, - &mock.HttpResponseGetterStub{ + args := ArgsPriceFetcher{ + FetcherName: fetcherName, + ResponseGetter: &mock.HttpResponseGetterStub{ GetCalled: getFuncGetCalled(fetcherName, returnPrice, pair, nil), }, - &mock.GraphqlResponseGetterStub{ + GraphqlGetter: &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, nil), - }, createMockMap(), EVMGasPriceFetcherConfig{}) - + }, + XExchangeTokensMap: createMockMap(), + EVMGasConfig: EVMGasPriceFetcherConfig{}, + } + fetcher, _ := NewPriceFetcher(args) assert.False(t, check.IfNil(fetcher)) fetcher.AddPair(ethTicker, quoteUSDFiat) @@ -273,12 +295,16 @@ func Test_FetchPriceErrors(t *testing.T) { } returnPrice := "4714.05000000" - fetcher, _ := NewPriceFetcher(fetcherName, - &mock.HttpResponseGetterStub{}, - &mock.GraphqlResponseGetterStub{ + args := ArgsPriceFetcher{ + FetcherName: fetcherName, + ResponseGetter: &mock.HttpResponseGetterStub{}, + GraphqlGetter: &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, nil), - }, createMockMap(), EVMGasPriceFetcherConfig{}) - + }, + XExchangeTokensMap: createMockMap(), + EVMGasConfig: EVMGasPriceFetcherConfig{}, + } + fetcher, _ := NewPriceFetcher(args) assert.False(t, check.IfNil(fetcher)) missingTicker := "missing ticker" @@ -297,14 +323,18 @@ func Test_FetchPriceErrors(t *testing.T) { return } - fetcher, _ := NewPriceFetcher(fetcherName, - &mock.HttpResponseGetterStub{}, - &mock.GraphqlResponseGetterStub{ + args := ArgsPriceFetcher{ + FetcherName: fetcherName, + ResponseGetter: &mock.HttpResponseGetterStub{}, + GraphqlGetter: &mock.GraphqlResponseGetterStub{ GetCalled: func(ctx context.Context, url string, query string, variables string) ([]byte, error) { return make([]byte, 0), nil }, - }, createMockMap(), EVMGasPriceFetcherConfig{}) - + }, + XExchangeTokensMap: createMockMap(), + EVMGasConfig: EVMGasPriceFetcherConfig{}, + } + fetcher, _ := NewPriceFetcher(args) assert.False(t, check.IfNil(fetcher)) fetcher.AddPair(ethTicker, quoteUSDFiat) @@ -319,14 +349,18 @@ func Test_FetchPriceErrors(t *testing.T) { t.Parallel() returnPrice := "" - fetcher, _ := NewPriceFetcher(fetcherName, - &mock.HttpResponseGetterStub{ + args := ArgsPriceFetcher{ + FetcherName: fetcherName, + ResponseGetter: &mock.HttpResponseGetterStub{ GetCalled: getFuncGetCalled(fetcherName, returnPrice, pair, nil), }, - &mock.GraphqlResponseGetterStub{ + GraphqlGetter: &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, nil), - }, createMockMap(), EVMGasPriceFetcherConfig{}) - + }, + XExchangeTokensMap: createMockMap(), + EVMGasConfig: EVMGasPriceFetcherConfig{}, + } + fetcher, _ := NewPriceFetcher(args) assert.False(t, check.IfNil(fetcher)) price, err := fetcher.FetchPrice(context.Background(), ethTicker, quoteUSDFiat) @@ -341,14 +375,18 @@ func Test_FetchPriceErrors(t *testing.T) { t.Parallel() returnPrice := "4714.05000000" - fetcher, _ := NewPriceFetcher(fetcherName, - &mock.HttpResponseGetterStub{ + args := ArgsPriceFetcher{ + FetcherName: fetcherName, + ResponseGetter: &mock.HttpResponseGetterStub{ GetCalled: getFuncGetCalled(fetcherName, returnPrice, pair, nil), }, - &mock.GraphqlResponseGetterStub{ + GraphqlGetter: &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, nil), - }, createMockMap(), EVMGasPriceFetcherConfig{}) - + }, + XExchangeTokensMap: createMockMap(), + EVMGasConfig: EVMGasPriceFetcherConfig{}, + } + fetcher, _ := NewPriceFetcher(args) assert.False(t, check.IfNil(fetcher)) fetcher.AddPair(ethTicker, quoteUSDFiat) @@ -366,13 +404,18 @@ func Test_FetchPriceErrors(t *testing.T) { btcTicker := "BTC" btcUsdPair := btcTicker + quoteUSDFiat returnPrice := "4714.05000000" - fetcher, _ := NewPriceFetcher(fetcherName, - &mock.HttpResponseGetterStub{ + args := ArgsPriceFetcher{ + FetcherName: fetcherName, + ResponseGetter: &mock.HttpResponseGetterStub{ GetCalled: getFuncGetCalled(fetcherName, returnPrice, btcUsdPair, nil), }, - &mock.GraphqlResponseGetterStub{ + GraphqlGetter: &mock.GraphqlResponseGetterStub{ GetCalled: getFuncQueryCalled(fetcherName, returnPrice, nil), - }, createMockMap(), EVMGasPriceFetcherConfig{}) + }, + XExchangeTokensMap: createMockMap(), + EVMGasConfig: EVMGasPriceFetcherConfig{}, + } + fetcher, _ := NewPriceFetcher(args) assert.False(t, check.IfNil(fetcher)) fetcher.AddPair(btcTicker, quoteUSDFiat) diff --git a/examples/examplesPriceAggregator/main.go b/examples/examplesPriceAggregator/main.go index 1e8ae391..2c49406b 100644 --- a/examples/examplesPriceAggregator/main.go +++ b/examples/examplesPriceAggregator/main.go @@ -182,7 +182,14 @@ func createPriceFetchers() ([]aggregator.PriceFetcher, error) { } for exchangeName := range exchanges { - priceFetcher, errFetch := fetchers.NewPriceFetcher(exchangeName, httpResponseGetter, graphqlResponseGetter, createXExchangeMap(), fetchers.EVMGasPriceFetcherConfig{}) + args := fetchers.ArgsPriceFetcher{ + FetcherName: exchangeName, + ResponseGetter: httpResponseGetter, + GraphqlGetter: graphqlResponseGetter, + XExchangeTokensMap: createXExchangeMap(), + EVMGasConfig: fetchers.EVMGasPriceFetcherConfig{}, + } + priceFetcher, errFetch := fetchers.NewPriceFetcher(args) if errFetch != nil { return nil, errFetch }