From ccce1860f9ff996084f7ac928f329050806a4615 Mon Sep 17 00:00:00 2001 From: Alex Johnson Date: Tue, 27 Aug 2024 09:09:00 -0400 Subject: [PATCH 1/3] add --- .../aggregator/mocks/mock_price_applier.go | 2 +- .../aggregator/mocks/mock_vote_aggregator.go | 2 +- .../codec/mocks/extended_commit_codec.go | 2 +- .../codec/mocks/vote_extension_codec.go | 5 +- .../mocks/mock_currency_pair_strategy.go | 2 +- .../currencypair/mocks/mock_oracle_keeper.go | 5 +- abci/types/mocks/mock_oracle_keeper.go | 5 +- oracle/config/api.go | 9 ++ oracle/config/api_test.go | 30 ++++ oracle/metrics/mocks/mock_metrics.go | 2 +- oracle/mocks/PriceAggregator.go | 70 ---------- oracle/mocks/mock_oracle.go | 130 ------------------ .../mocks/mock_cc_validator_store.go | 2 +- pkg/math/voteweighted/mocks/mock_validator.go | 2 +- .../mocks/mock_validator_store.go | 2 +- .../defi/ethmulticlient/mocks/EVMClient.go | 2 +- .../apis/defi/ethmulticlient/multi_client.go | 75 ++++++---- providers/apis/defi/osmosis/client.go | 82 +++++++---- providers/apis/defi/osmosis/mocks/client.go | 15 +- providers/apis/defi/osmosis/price_fetcher.go | 5 +- providers/apis/defi/osmosis/types.go | 6 + .../raydium/mocks/solana_jsonrpc_client.go | 2 +- providers/apis/defi/raydium/multi_client.go | 30 ++-- providers/apis/defi/raydium/types.go | 1 + providers/apis/defi/types/block_age.go | 42 ++++++ providers/apis/defi/types/block_age_test.go | 66 +++++++++ providers/apis/defi/uniswapv3/utils.go | 34 ++--- .../api/handlers/mocks/api_data_handler.go | 5 +- .../base/api/handlers/mocks/api_fetcher.go | 2 +- .../api/handlers/mocks/api_query_handler.go | 2 +- .../api/handlers/mocks/request_handler.go | 2 +- .../base/api/metrics/mocks/mock_metrics.go | 5 +- providers/base/metrics/mocks/mock_metrics.go | 5 +- .../handlers/mocks/web_socket_conn_handler.go | 5 +- .../handlers/mocks/web_socket_data_handler.go | 5 +- .../mocks/web_socket_query_handler.go | 5 +- .../websocket/metrics/mocks/mock_metrics.go | 5 +- providers/types/mocks/mock_provider.go | 122 ---------------- .../oracle/mocks/mock_oracle_client.go | 2 +- service/metrics/mocks/mock_metrics.go | 5 +- .../oracle/mocks/mock_oracle_service.go | 2 +- tests/integration/slinky_suite.go | 1 - x/marketmap/types/mocks/MarketMapHooks.go | 5 +- x/oracle/types/mocks/market_map_keeper.go | 5 +- 44 files changed, 349 insertions(+), 464 deletions(-) create mode 100644 providers/apis/defi/types/block_age.go create mode 100644 providers/apis/defi/types/block_age_test.go diff --git a/abci/strategies/aggregator/mocks/mock_price_applier.go b/abci/strategies/aggregator/mocks/mock_price_applier.go index 5559d6097..f817da3a3 100644 --- a/abci/strategies/aggregator/mocks/mock_price_applier.go +++ b/abci/strategies/aggregator/mocks/mock_price_applier.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks diff --git a/abci/strategies/aggregator/mocks/mock_vote_aggregator.go b/abci/strategies/aggregator/mocks/mock_vote_aggregator.go index 86dca5d1a..f9ec1b0fb 100644 --- a/abci/strategies/aggregator/mocks/mock_vote_aggregator.go +++ b/abci/strategies/aggregator/mocks/mock_vote_aggregator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks diff --git a/abci/strategies/codec/mocks/extended_commit_codec.go b/abci/strategies/codec/mocks/extended_commit_codec.go index bdcdd3878..fed278f25 100644 --- a/abci/strategies/codec/mocks/extended_commit_codec.go +++ b/abci/strategies/codec/mocks/extended_commit_codec.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks diff --git a/abci/strategies/codec/mocks/vote_extension_codec.go b/abci/strategies/codec/mocks/vote_extension_codec.go index dca4499c8..1780882d9 100644 --- a/abci/strategies/codec/mocks/vote_extension_codec.go +++ b/abci/strategies/codec/mocks/vote_extension_codec.go @@ -1,11 +1,10 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks import ( - mock "github.com/stretchr/testify/mock" - types "github.com/skip-mev/slinky/abci/ve/types" + mock "github.com/stretchr/testify/mock" ) // VoteExtensionCodec is an autogenerated mock type for the VoteExtensionCodec type diff --git a/abci/strategies/currencypair/mocks/mock_currency_pair_strategy.go b/abci/strategies/currencypair/mocks/mock_currency_pair_strategy.go index e54541f10..85c6f327c 100644 --- a/abci/strategies/currencypair/mocks/mock_currency_pair_strategy.go +++ b/abci/strategies/currencypair/mocks/mock_currency_pair_strategy.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks diff --git a/abci/strategies/currencypair/mocks/mock_oracle_keeper.go b/abci/strategies/currencypair/mocks/mock_oracle_keeper.go index 083441f14..f3ca5a254 100644 --- a/abci/strategies/currencypair/mocks/mock_oracle_keeper.go +++ b/abci/strategies/currencypair/mocks/mock_oracle_keeper.go @@ -1,11 +1,10 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks import ( - mock "github.com/stretchr/testify/mock" - oracletypes "github.com/skip-mev/slinky/x/oracle/types" + mock "github.com/stretchr/testify/mock" pkgtypes "github.com/skip-mev/slinky/pkg/types" diff --git a/abci/types/mocks/mock_oracle_keeper.go b/abci/types/mocks/mock_oracle_keeper.go index 38aa9340b..8753189cd 100644 --- a/abci/types/mocks/mock_oracle_keeper.go +++ b/abci/types/mocks/mock_oracle_keeper.go @@ -1,11 +1,10 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks import ( - mock "github.com/stretchr/testify/mock" - oracletypes "github.com/skip-mev/slinky/x/oracle/types" + mock "github.com/stretchr/testify/mock" pkgtypes "github.com/skip-mev/slinky/pkg/types" diff --git a/oracle/config/api.go b/oracle/config/api.go index 0eb839b15..5efdec5cc 100644 --- a/oracle/config/api.go +++ b/oracle/config/api.go @@ -40,6 +40,11 @@ type APIConfig struct { // Name is the name of the provider that corresponds to this config. Name string `json:"name"` + + // MaxBlockHeightAge is the oldest an update from an on-chain data source can be without having its + // block height incremented. In the case where a data source has exceeded this limit and the block + // height is not increasing, price reporting will be skipped until the block height increases. + MaxBlockHeightAge time.Duration `json:"maxBlockHeightAge"` } // Endpoint holds all data necessary for an API provider to connect to a given endpoint @@ -123,5 +128,9 @@ func (c *APIConfig) ValidateBasic() error { } } + if c.MaxBlockHeightAge < 0 { + return fmt.Errorf("max_block_height_age cannot be negative") + } + return nil } diff --git a/oracle/config/api_test.go b/oracle/config/api_test.go index 28d902ba6..a17507344 100644 --- a/oracle/config/api_test.go +++ b/oracle/config/api_test.go @@ -135,6 +135,36 @@ func TestAPIConfig(t *testing.T) { }, expectedErr: false, }, + { + name: "good config with max_block_height_age", + config: config.APIConfig{ + Enabled: true, + Timeout: time.Second, + Interval: time.Second, + ReconnectTimeout: time.Second, + MaxQueries: 1, + Name: "test", + Endpoints: []config.Endpoint{{URL: "http://test.com"}}, + BatchSize: 1, + MaxBlockHeightAge: 10 * time.Second, + }, + expectedErr: false, + }, + { + name: "bad config with negative max_block_height_age", + config: config.APIConfig{ + Enabled: true, + Timeout: time.Second, + Interval: time.Second, + ReconnectTimeout: time.Second, + MaxQueries: 1, + Name: "test", + Endpoints: []config.Endpoint{{URL: "http://test.com"}}, + BatchSize: 1, + MaxBlockHeightAge: -10 * time.Second, + }, + expectedErr: true, + }, { name: "bad config with invalid endpoint (no url)", config: config.APIConfig{ diff --git a/oracle/metrics/mocks/mock_metrics.go b/oracle/metrics/mocks/mock_metrics.go index 7db09e313..0e3a3f2ba 100644 --- a/oracle/metrics/mocks/mock_metrics.go +++ b/oracle/metrics/mocks/mock_metrics.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks diff --git a/oracle/mocks/PriceAggregator.go b/oracle/mocks/PriceAggregator.go index 22f701f1d..e69de29bb 100644 --- a/oracle/mocks/PriceAggregator.go +++ b/oracle/mocks/PriceAggregator.go @@ -1,70 +0,0 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. - -package mocks - -import ( - big "math/big" - - mock "github.com/stretchr/testify/mock" - - types "github.com/skip-mev/slinky/x/marketmap/types" -) - -// PriceAggregator is an autogenerated mock type for the PriceAggregator type -type PriceAggregator struct { - mock.Mock -} - -// AggregatePrices provides a mock function with given fields: -func (_m *PriceAggregator) AggregatePrices() { - _m.Called() -} - -// GetPrices provides a mock function with given fields: -func (_m *PriceAggregator) GetPrices() map[string]*big.Float { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for GetPrices") - } - - var r0 map[string]*big.Float - if rf, ok := ret.Get(0).(func() map[string]*big.Float); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(map[string]*big.Float) - } - } - - return r0 -} - -// Reset provides a mock function with given fields: -func (_m *PriceAggregator) Reset() { - _m.Called() -} - -// SetProviderPrices provides a mock function with given fields: provider, prices -func (_m *PriceAggregator) SetProviderPrices(provider string, prices map[string]*big.Float) { - _m.Called(provider, prices) -} - -// UpdateMarketMap provides a mock function with given fields: _a0 -func (_m *PriceAggregator) UpdateMarketMap(_a0 types.MarketMap) { - _m.Called(_a0) -} - -// NewPriceAggregator creates a new instance of PriceAggregator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewPriceAggregator(t interface { - mock.TestingT - Cleanup(func()) -}) *PriceAggregator { - mock := &PriceAggregator{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/oracle/mocks/mock_oracle.go b/oracle/mocks/mock_oracle.go index 26869dcc7..e69de29bb 100644 --- a/oracle/mocks/mock_oracle.go +++ b/oracle/mocks/mock_oracle.go @@ -1,130 +0,0 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. - -package mocks - -import ( - context "context" - big "math/big" - - mock "github.com/stretchr/testify/mock" - - time "time" - - types "github.com/skip-mev/slinky/x/marketmap/types" -) - -// Oracle is an autogenerated mock type for the Oracle type -type Oracle struct { - mock.Mock -} - -// GetLastSyncTime provides a mock function with given fields: -func (_m *Oracle) GetLastSyncTime() time.Time { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for GetLastSyncTime") - } - - var r0 time.Time - if rf, ok := ret.Get(0).(func() time.Time); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(time.Time) - } - - return r0 -} - -// GetMarketMap provides a mock function with given fields: -func (_m *Oracle) GetMarketMap() types.MarketMap { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for GetMarketMap") - } - - var r0 types.MarketMap - if rf, ok := ret.Get(0).(func() types.MarketMap); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(types.MarketMap) - } - - return r0 -} - -// GetPrices provides a mock function with given fields: -func (_m *Oracle) GetPrices() map[string]*big.Float { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for GetPrices") - } - - var r0 map[string]*big.Float - if rf, ok := ret.Get(0).(func() map[string]*big.Float); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(map[string]*big.Float) - } - } - - return r0 -} - -// IsRunning provides a mock function with given fields: -func (_m *Oracle) IsRunning() bool { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for IsRunning") - } - - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -// Start provides a mock function with given fields: ctx -func (_m *Oracle) Start(ctx context.Context) error { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for Start") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(ctx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Stop provides a mock function with given fields: -func (_m *Oracle) Stop() { - _m.Called() -} - -// NewOracle creates a new instance of Oracle. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewOracle(t interface { - mock.TestingT - Cleanup(func()) -}) *Oracle { - mock := &Oracle{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/math/voteweighted/mocks/mock_cc_validator_store.go b/pkg/math/voteweighted/mocks/mock_cc_validator_store.go index f6e7001be..ad4137c7b 100644 --- a/pkg/math/voteweighted/mocks/mock_cc_validator_store.go +++ b/pkg/math/voteweighted/mocks/mock_cc_validator_store.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks diff --git a/pkg/math/voteweighted/mocks/mock_validator.go b/pkg/math/voteweighted/mocks/mock_validator.go index 087d7e9af..ce26c98cd 100644 --- a/pkg/math/voteweighted/mocks/mock_validator.go +++ b/pkg/math/voteweighted/mocks/mock_validator.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks diff --git a/pkg/math/voteweighted/mocks/mock_validator_store.go b/pkg/math/voteweighted/mocks/mock_validator_store.go index 93bc07ed6..04dc67fd9 100644 --- a/pkg/math/voteweighted/mocks/mock_validator_store.go +++ b/pkg/math/voteweighted/mocks/mock_validator_store.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks diff --git a/providers/apis/defi/ethmulticlient/mocks/EVMClient.go b/providers/apis/defi/ethmulticlient/mocks/EVMClient.go index 35d02359d..735e512cf 100644 --- a/providers/apis/defi/ethmulticlient/mocks/EVMClient.go +++ b/providers/apis/defi/ethmulticlient/mocks/EVMClient.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks diff --git a/providers/apis/defi/ethmulticlient/multi_client.go b/providers/apis/defi/ethmulticlient/multi_client.go index d154ec124..afc89aa50 100644 --- a/providers/apis/defi/ethmulticlient/multi_client.go +++ b/providers/apis/defi/ethmulticlient/multi_client.go @@ -6,6 +6,8 @@ import ( "fmt" "sync" + "github.com/skip-mev/slinky/providers/apis/defi/types" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" "go.uber.org/zap" @@ -23,6 +25,8 @@ type MultiRPCClient struct { // underlying clients clients []EVMClient + + blockAgeChecker types.BlockAgeChecker } // NewMultiRPCClient returns a new MultiRPCClient. @@ -32,9 +36,10 @@ func NewMultiRPCClient( clients []EVMClient, ) EVMClient { return &MultiRPCClient{ - logger: logger, - clients: clients, - api: api, + logger: logger, + clients: clients, + api: api, + blockAgeChecker: types.NewBlockAgeChecker(api.MaxBlockHeightAge), } } @@ -81,12 +86,20 @@ func NewMultiRPCClientFromEndpoints( } return &MultiRPCClient{ - logger: logger.With(zap.String("multi_client", api.Name)), - api: api, - clients: clients, + logger: logger.With(zap.String("multi_client", api.Name)), + api: api, + clients: clients, + blockAgeChecker: types.NewBlockAgeChecker(api.MaxBlockHeightAge), }, nil } +// define a result struct that go routines will populate and append to a slice when they complete their request. +type result struct { + height uint64 + results []rpc.BatchElem + err error +} + // BatchCallContext injects a call to eth_blockNumber, and makes batch calls to the underlying EVMClients. // It returns the response that has the greatest height from the eth_blockNumber call. An error is returned // only when no client was able to successfully provide a height or errored when sending the BatchCall. @@ -95,15 +108,9 @@ func (m *MultiRPCClient) BatchCallContext(ctx context.Context, batchElems []rpc. m.logger.Debug("BatchCallContext called with 0 elems") return nil } - // define a result struct that go routines will populate and append to a slice when they complete their request. - type result struct { - height uint64 - results []rpc.BatchElem - } + results := make([]result, len(m.clients)) - // error slice to capture errors go routines encounter. - errs := make([]error, len(m.clients)) wg := new(sync.WaitGroup) // this is the index of where we will have an eth_blockNumber call. blockNumReqIndex := len(batchElems) @@ -124,7 +131,8 @@ func (m *MultiRPCClient) BatchCallContext(ctx context.Context, batchElems []rpc. // if there was an error, or if the block_num request didn't have result / errored // we log the error and append to error slice. if err != nil || req[blockNumReqIndex].Result == "" || req[blockNumReqIndex].Error != nil { - errs[i] = fmt.Errorf("endpoint request failed: %w, %w", err, req[blockNumReqIndex].Error) + resultErr := fmt.Errorf("endpoint request failed: %w, %w", err, req[blockNumReqIndex].Error) + results[i] = result{0, nil, resultErr} m.logger.Debug( "endpoint request failed", zap.Error(err), @@ -138,7 +146,8 @@ func (m *MultiRPCClient) BatchCallContext(ctx context.Context, batchElems []rpc. // try to get the block number. r, ok := req[blockNumReqIndex].Result.(*string) if !ok { - errs[i] = fmt.Errorf("result from eth_blockNumber was not a string") + resultErr := fmt.Errorf("result from eth_blockNumber was not a string") + results[i] = result{0, nil, resultErr} m.logger.Debug( "result from eth_blockNumber was not a string", zap.String("url", url), @@ -149,7 +158,8 @@ func (m *MultiRPCClient) BatchCallContext(ctx context.Context, batchElems []rpc. // decode the new height height, err := hexutil.DecodeUint64(*r) if err != nil { // if we can't decode the height, log an error. - errs[i] = fmt.Errorf("could not decode hex eth height: %w", err) + resultErr := fmt.Errorf("could not decode hex eth height: %w", err) + results[i] = result{0, nil, resultErr} m.logger.Debug( "could not decode hex eth height", zap.String("url", url), @@ -163,17 +173,31 @@ func (m *MultiRPCClient) BatchCallContext(ctx context.Context, batchElems []rpc. zap.String("url", url), ) // append the results, minus the appended eth_blockNumber request. - results[i] = result{height, req[:blockNumReqIndex]} + results[i] = result{height, req[:blockNumReqIndex], nil} }(clientIdx) } wg.Wait() + filtered, err := m.filterResponses(results) + if err != nil { + return fmt.Errorf("error filtering responses: %w", err) + } + + // copy the results from the results that had the largest height. + copy(batchElems, filtered) + return nil +} + +// filterAccountsResponses chooses the rpc response with the highest block number. +func (m *MultiRPCClient) filterResponses(responses []result) ([]rpc.BatchElem, error) { // see which of the results had the largest height, and store the index of that result. var ( maxHeight uint64 maxHeightIndex int + errs = make([]error, len(responses)) ) - for i, res := range results { + for i, res := range responses { + errs[i] = res.err if res.height > maxHeight { maxHeight = res.height maxHeightIndex = i @@ -183,12 +207,17 @@ func (m *MultiRPCClient) BatchCallContext(ctx context.Context, batchElems []rpc. if maxHeight == 0 { err := errors.Join(errs...) if err != nil { - return err + return nil, err } // this should never happen... but who knows. maybe something terrible happened. - return errors.New("no errors were encountered, however no go routine was able to report a height") + return nil, errors.New("no errors were encountered, however no go routine was able to report a height") + } - // copy the results from the results that had the largest height. - copy(batchElems, results[maxHeightIndex].results) - return nil + + // check the block height + if valid := m.blockAgeChecker.IsHeightValid(maxHeight); !valid { + return nil, fmt.Errorf("height %d is stale and older than %d", maxHeight, m.api.MaxBlockHeightAge) + } + + return responses[maxHeightIndex].results, nil } diff --git a/providers/apis/defi/osmosis/client.go b/providers/apis/defi/osmosis/client.go index f8869d573..beb92966c 100644 --- a/providers/apis/defi/osmosis/client.go +++ b/providers/apis/defi/osmosis/client.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "fmt" - "math/rand" + "strconv" "sync" "time" @@ -12,9 +12,14 @@ import ( "github.com/skip-mev/slinky/oracle/config" "github.com/skip-mev/slinky/pkg/http" + "github.com/skip-mev/slinky/providers/apis/defi/types" "github.com/skip-mev/slinky/providers/base/api/metrics" ) +const ( + headerBlockHeight = "grpc-metadata-x-cosmos-block-height" +) + var ( _ Client = &ClientImpl{} _ Client = &MultiClientImpl{} @@ -28,7 +33,7 @@ type Client interface { poolID uint64, baseAsset, quoteAsset string, - ) (SpotPriceResponse, error) + ) (WrappedSpotPriceResponse, error) } // ClientImpl is an implementation of a client to Osmosis using a @@ -74,7 +79,7 @@ func NewClient( } // SpotPrice uses the underlying x/poolmanager client to access spot prices. -func (c *ClientImpl) SpotPrice(ctx context.Context, poolID uint64, baseAsset, quoteAsset string) (SpotPriceResponse, error) { +func (c *ClientImpl) SpotPrice(ctx context.Context, poolID uint64, baseAsset, quoteAsset string) (WrappedSpotPriceResponse, error) { start := time.Now() defer func() { c.apiMetrics.ObserveProviderResponseLatency(c.api.Name, c.redactedURL, time.Since(start)) @@ -82,23 +87,35 @@ func (c *ClientImpl) SpotPrice(ctx context.Context, poolID uint64, baseAsset, qu url, err := CreateURL(c.endpoint.URL, poolID, baseAsset, quoteAsset) if err != nil { - return SpotPriceResponse{}, err + return WrappedSpotPriceResponse{}, err } resp, err := c.httpClient.GetWithContext(ctx, url) if err != nil { - return SpotPriceResponse{}, err + return WrappedSpotPriceResponse{}, err } c.apiMetrics.AddHTTPStatusCode(c.api.Name, resp) + var blockHeight uint64 + heightStr := resp.Header.Get(headerBlockHeight) + if heightStr != "" { + blockHeight, err = strconv.ParseUint(heightStr, 10, 64) + if err != nil { + return WrappedSpotPriceResponse{}, fmt.Errorf("failed to parse block height: %w", err) + } + } + var spotPriceResponse SpotPriceResponse if err := json.NewDecoder(resp.Body).Decode(&spotPriceResponse); err != nil { - return SpotPriceResponse{}, err + return WrappedSpotPriceResponse{}, err } c.apiMetrics.AddHTTPStatusCode(c.api.Name, resp) - return spotPriceResponse, nil + return WrappedSpotPriceResponse{ + SpotPriceResponse: spotPriceResponse, + BlockHeight: blockHeight, + }, nil } // MultiClientImpl is an Osmosis client that wraps a set of multiple Clients. @@ -108,6 +125,8 @@ type MultiClientImpl struct { apiMetrics metrics.APIMetrics clients []Client + + blockAgeChecker types.BlockAgeChecker } // NewMultiClient creates a new Client. @@ -134,10 +153,11 @@ func NewMultiClient( } return &MultiClientImpl{ - logger: logger, - api: api, - apiMetrics: apiMetrics, - clients: clients, + logger: logger, + api: api, + apiMetrics: apiMetrics, + clients: clients, + blockAgeChecker: types.NewBlockAgeChecker(api.MaxBlockHeightAge), }, nil } @@ -174,17 +194,18 @@ func NewMultiClientFromEndpoints( } return &MultiClientImpl{ - logger: logger, - api: api, - apiMetrics: apiMetrics, - clients: clients, + logger: logger, + api: api, + apiMetrics: apiMetrics, + clients: clients, + blockAgeChecker: types.NewBlockAgeChecker(api.MaxBlockHeightAge), }, nil } // SpotPrice delegates the request to all underlying clients and applies a filter to the // set of responses. -func (mc *MultiClientImpl) SpotPrice(ctx context.Context, poolID uint64, baseAsset, quoteAsset string) (SpotPriceResponse, error) { - resps := make([]SpotPriceResponse, len(mc.clients)) +func (mc *MultiClientImpl) SpotPrice(ctx context.Context, poolID uint64, baseAsset, quoteAsset string) (WrappedSpotPriceResponse, error) { + resps := make([]WrappedSpotPriceResponse, len(mc.clients)) var wg sync.WaitGroup wg.Add(len(mc.clients)) @@ -209,22 +230,29 @@ func (mc *MultiClientImpl) SpotPrice(ctx context.Context, poolID uint64, baseAss wg.Wait() - return filterSpotPriceResponses(resps) + return mc.filterSpotPriceResponses(resps) } -// filterSpotPriceResponses currently just chooses a random response as there is no way to differentiate. -func filterSpotPriceResponses(responses []SpotPriceResponse) (SpotPriceResponse, error) { +// filterSpotPriceResponses chooses the response with the highest block height. +func (mc *MultiClientImpl) filterSpotPriceResponses(responses []WrappedSpotPriceResponse) (WrappedSpotPriceResponse, error) { if len(responses) == 0 { - return SpotPriceResponse{}, fmt.Errorf("no responses found") + return WrappedSpotPriceResponse{}, fmt.Errorf("no responses found") } - perm := rand.Perm(len(responses)) - for _, i := range perm { - resp := responses[perm[i]] - if resp.SpotPrice != "" { - return resp, nil + highestHeight := uint64(0) + highestHeightIndex := 0 + + for i, resp := range responses { + if resp.BlockHeight > highestHeight { + highestHeight = resp.BlockHeight + highestHeightIndex = i } } - return SpotPriceResponse{}, fmt.Errorf("no responses found") + // check the block height + if valid := mc.blockAgeChecker.IsHeightValid(highestHeight); !valid { + return WrappedSpotPriceResponse{}, fmt.Errorf("height %d is stale and older than %d", highestHeight, mc.api.MaxBlockHeightAge) + } + + return responses[highestHeightIndex], nil } diff --git a/providers/apis/defi/osmosis/mocks/client.go b/providers/apis/defi/osmosis/mocks/client.go index 6495dc8ae..1ca847f06 100644 --- a/providers/apis/defi/osmosis/mocks/client.go +++ b/providers/apis/defi/osmosis/mocks/client.go @@ -1,13 +1,12 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks import ( context "context" - mock "github.com/stretchr/testify/mock" - osmosis "github.com/skip-mev/slinky/providers/apis/defi/osmosis" + mock "github.com/stretchr/testify/mock" ) // Client is an autogenerated mock type for the Client type @@ -16,22 +15,22 @@ type Client struct { } // SpotPrice provides a mock function with given fields: ctx, poolID, baseAsset, quoteAsset -func (_m *Client) SpotPrice(ctx context.Context, poolID uint64, baseAsset string, quoteAsset string) (osmosis.SpotPriceResponse, error) { +func (_m *Client) SpotPrice(ctx context.Context, poolID uint64, baseAsset string, quoteAsset string) (osmosis.WrappedSpotPriceResponse, error) { ret := _m.Called(ctx, poolID, baseAsset, quoteAsset) if len(ret) == 0 { panic("no return value specified for SpotPrice") } - var r0 osmosis.SpotPriceResponse + var r0 osmosis.WrappedSpotPriceResponse var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, string, string) (osmosis.SpotPriceResponse, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint64, string, string) (osmosis.WrappedSpotPriceResponse, error)); ok { return rf(ctx, poolID, baseAsset, quoteAsset) } - if rf, ok := ret.Get(0).(func(context.Context, uint64, string, string) osmosis.SpotPriceResponse); ok { + if rf, ok := ret.Get(0).(func(context.Context, uint64, string, string) osmosis.WrappedSpotPriceResponse); ok { r0 = rf(ctx, poolID, baseAsset, quoteAsset) } else { - r0 = ret.Get(0).(osmosis.SpotPriceResponse) + r0 = ret.Get(0).(osmosis.WrappedSpotPriceResponse) } if rf, ok := ret.Get(1).(func(context.Context, uint64, string, string) error); ok { diff --git a/providers/apis/defi/osmosis/price_fetcher.go b/providers/apis/defi/osmosis/price_fetcher.go index 3cb49c464..b354ab157 100644 --- a/providers/apis/defi/osmosis/price_fetcher.go +++ b/providers/apis/defi/osmosis/price_fetcher.go @@ -7,9 +7,8 @@ import ( "sync" "time" - "golang.org/x/sync/errgroup" - "go.uber.org/zap" + "golang.org/x/sync/errgroup" "github.com/skip-mev/slinky/oracle/config" oracletypes "github.com/skip-mev/slinky/oracle/types" @@ -203,6 +202,6 @@ func (pf *APIPriceFetcher) Fetch( return oracletypes.NewPriceResponse(resolved, unresolved) } -func calculatePrice(resp SpotPriceResponse) (*big.Float, error) { +func calculatePrice(resp WrappedSpotPriceResponse) (*big.Float, error) { return math.Float64StringToBigFloat(resp.SpotPrice) } diff --git a/providers/apis/defi/osmosis/types.go b/providers/apis/defi/osmosis/types.go index 3d0308291..7e97ca816 100644 --- a/providers/apis/defi/osmosis/types.go +++ b/providers/apis/defi/osmosis/types.go @@ -127,8 +127,14 @@ var DefaultAPIConfig = config.APIConfig{ URL: "https://osmosis-api.polkachu.com", }, }, + MaxBlockHeightAge: 30 * time.Second, } type SpotPriceResponse struct { SpotPrice string `json:"spot_price"` } + +type WrappedSpotPriceResponse struct { + SpotPriceResponse + BlockHeight uint64 `json:"block_height"` +} diff --git a/providers/apis/defi/raydium/mocks/solana_jsonrpc_client.go b/providers/apis/defi/raydium/mocks/solana_jsonrpc_client.go index a5777a424..7a4790063 100644 --- a/providers/apis/defi/raydium/mocks/solana_jsonrpc_client.go +++ b/providers/apis/defi/raydium/mocks/solana_jsonrpc_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks diff --git a/providers/apis/defi/raydium/multi_client.go b/providers/apis/defi/raydium/multi_client.go index c8e8bf232..b16988132 100644 --- a/providers/apis/defi/raydium/multi_client.go +++ b/providers/apis/defi/raydium/multi_client.go @@ -11,6 +11,7 @@ import ( "go.uber.org/zap" "github.com/skip-mev/slinky/oracle/config" + "github.com/skip-mev/slinky/providers/apis/defi/types" "github.com/skip-mev/slinky/providers/base/api/metrics" ) @@ -23,6 +24,8 @@ type MultiJSONRPCClient struct { // underlying clients clients []SolanaJSONRPCClient + + blockAgeChecker types.BlockAgeChecker } // NewMultiJSONRPCClient returns a new MultiJSONRPCClient. @@ -33,10 +36,11 @@ func NewMultiJSONRPCClient( clients []SolanaJSONRPCClient, ) SolanaJSONRPCClient { return &MultiJSONRPCClient{ - logger: logger, - api: api, - apiMetrics: apiMetrics, - clients: clients, + logger: logger, + api: api, + apiMetrics: apiMetrics, + clients: clients, + blockAgeChecker: types.NewBlockAgeChecker(api.MaxBlockHeightAge), } } @@ -80,10 +84,11 @@ func NewMultiJSONRPCClientFromEndpoints( } return &MultiJSONRPCClient{ - logger: logger.With(zap.String("multi_client", Name)), - api: api, - apiMetrics: apiMetrics, - clients: clients, + logger: logger.With(zap.String("multi_client", Name)), + api: api, + apiMetrics: apiMetrics, + clients: clients, + blockAgeChecker: types.NewBlockAgeChecker(api.MaxBlockHeightAge), }, nil } @@ -138,11 +143,11 @@ func (c *MultiJSONRPCClient) GetMultipleAccountsWithOpts( } // filter the responses - return filterAccountsResponses(responses) + return c.filterAccountsResponses(responses) } // filterAccountsResponses chooses the rpc response with the highest slot number. -func filterAccountsResponses(responses []*rpc.GetMultipleAccountsResult) (*rpc.GetMultipleAccountsResult, error) { +func (c *MultiJSONRPCClient) filterAccountsResponses(responses []*rpc.GetMultipleAccountsResult) (*rpc.GetMultipleAccountsResult, error) { var ( maxSlot uint64 maxResp *rpc.GetMultipleAccountsResult @@ -159,5 +164,10 @@ func filterAccountsResponses(responses []*rpc.GetMultipleAccountsResult) (*rpc.G } } + // check the block height (slot) + if valid := c.blockAgeChecker.IsHeightValid(maxSlot); !valid { + return nil, fmt.Errorf("height %d is stale and older than %d", maxSlot, c.api.MaxBlockHeightAge) + } + return maxResp, nil } diff --git a/providers/apis/defi/raydium/types.go b/providers/apis/defi/raydium/types.go index 3c02b175a..b23420d66 100644 --- a/providers/apis/defi/raydium/types.go +++ b/providers/apis/defi/raydium/types.go @@ -150,4 +150,5 @@ var DefaultAPIConfig = config.APIConfig{ URL: "https://api.mainnet-beta.solana.com", }, }, + MaxBlockHeightAge: 30 * time.Second, } diff --git a/providers/apis/defi/types/block_age.go b/providers/apis/defi/types/block_age.go new file mode 100644 index 000000000..f998fdc94 --- /dev/null +++ b/providers/apis/defi/types/block_age.go @@ -0,0 +1,42 @@ +package types + +import "time" + +// BlockAgeChecker is a utility type to check if incoming block heights are validly updating. +// If the block heights are not increasing and the time since the last update has exceeded +// a configurable duration, this type will report that the updates are invalid. +type BlockAgeChecker struct { + lastHeight uint64 + lastTimeStamp time.Time + maxAge time.Duration +} + +// NewBlockAgeChecker returns a zeroed BlockAgeChecker using the provided maxAge. +func NewBlockAgeChecker(maxAge time.Duration) BlockAgeChecker { + return BlockAgeChecker{ + lastHeight: 0, + lastTimeStamp: time.Now(), + maxAge: maxAge, + } +} + +// IsHeightValid returns true if: +// - the new height is greater than the last height OR +// - the time past the last block height update is less than the configured max age +// returns false if: +// - the time is past the configured max age. +func (bc *BlockAgeChecker) IsHeightValid(newHeight uint64) bool { + now := time.Now() + + if newHeight > bc.lastHeight { + bc.lastHeight = newHeight + bc.lastTimeStamp = now + return true + } + + if now.Sub(bc.lastTimeStamp) > bc.maxAge { + return false + } + + return true +} diff --git a/providers/apis/defi/types/block_age_test.go b/providers/apis/defi/types/block_age_test.go new file mode 100644 index 000000000..fdaf89b62 --- /dev/null +++ b/providers/apis/defi/types/block_age_test.go @@ -0,0 +1,66 @@ +package types_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/skip-mev/slinky/providers/apis/defi/types" +) + +func TestBlockAgeChecker_IsHeightValid(t *testing.T) { + tests := []struct { + name string + lastHeight uint64 + waitTime time.Duration + maxAge time.Duration + newHeight uint64 + isValid bool + }{ + { + name: "valid 0s no timeout", + lastHeight: 0, + waitTime: 0, + maxAge: 10 * time.Minute, + newHeight: 0, + isValid: true, + }, + { + name: "valid new height no timeout", + lastHeight: 0, + waitTime: 0, + maxAge: 10 * time.Minute, + newHeight: 0, + isValid: true, + }, + { + name: "invalid 0s due to timeout", + lastHeight: 0, + waitTime: 10 * time.Millisecond, + maxAge: 1 * time.Millisecond, + newHeight: 0, + isValid: false, + }, + { + name: "valid timeout but block height increase", + lastHeight: 0, + waitTime: 10 * time.Millisecond, + maxAge: 1 * time.Millisecond, + newHeight: 1, + isValid: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bc := types.NewBlockAgeChecker(tt.maxAge) + + got := bc.IsHeightValid(tt.lastHeight) + require.True(t, got) + time.Sleep(tt.waitTime) + + got = bc.IsHeightValid(tt.newHeight) + require.Equal(t, tt.isValid, got) + }) + } +} diff --git a/providers/apis/defi/uniswapv3/utils.go b/providers/apis/defi/uniswapv3/utils.go index 866d7632b..afd547999 100644 --- a/providers/apis/defi/uniswapv3/utils.go +++ b/providers/apis/defi/uniswapv3/utils.go @@ -92,26 +92,28 @@ var ( // DefaultETHAPIConfig is the default configuration for the Uniswap API. Specifically this is for // Ethereum mainnet. DefaultETHAPIConfig = config.APIConfig{ - Name: fmt.Sprintf("%s%s%s", BaseName, NameSeparator, constants.ETHEREUM), - Atomic: true, - Enabled: true, - Timeout: 1000 * time.Millisecond, - Interval: 2000 * time.Millisecond, - ReconnectTimeout: 2000 * time.Millisecond, - MaxQueries: 1, - Endpoints: []config.Endpoint{{URL: ETH_URL}}, + Name: fmt.Sprintf("%s%s%s", BaseName, NameSeparator, constants.ETHEREUM), + Atomic: true, + Enabled: true, + Timeout: 1000 * time.Millisecond, + Interval: 2000 * time.Millisecond, + ReconnectTimeout: 2000 * time.Millisecond, + MaxQueries: 1, + Endpoints: []config.Endpoint{{URL: ETH_URL}}, + MaxBlockHeightAge: 30 * time.Second, } // DefaultBaseAPIConfig is the default configuration for the Uniswap API. Specifically this is for // Base mainnet. DefaultBaseAPIConfig = config.APIConfig{ - Name: fmt.Sprintf("%s%s%s", BaseName, NameSeparator, constants.BASE), - Atomic: true, - Enabled: true, - Timeout: 1000 * time.Millisecond, - Interval: 2000 * time.Millisecond, - ReconnectTimeout: 2000 * time.Millisecond, - MaxQueries: 1, - Endpoints: []config.Endpoint{{URL: BASE_URL}}, + Name: fmt.Sprintf("%s%s%s", BaseName, NameSeparator, constants.BASE), + Atomic: true, + Enabled: true, + Timeout: 1000 * time.Millisecond, + Interval: 2000 * time.Millisecond, + ReconnectTimeout: 2000 * time.Millisecond, + MaxQueries: 1, + Endpoints: []config.Endpoint{{URL: BASE_URL}}, + MaxBlockHeightAge: 30 * time.Second, } ) diff --git a/providers/base/api/handlers/mocks/api_data_handler.go b/providers/base/api/handlers/mocks/api_data_handler.go index 0d42f3f81..0a7267ab6 100644 --- a/providers/base/api/handlers/mocks/api_data_handler.go +++ b/providers/base/api/handlers/mocks/api_data_handler.go @@ -1,13 +1,12 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks import ( http "net/http" - mock "github.com/stretchr/testify/mock" - types "github.com/skip-mev/slinky/providers/types" + mock "github.com/stretchr/testify/mock" ) // APIDataHandler is an autogenerated mock type for the APIDataHandler type diff --git a/providers/base/api/handlers/mocks/api_fetcher.go b/providers/base/api/handlers/mocks/api_fetcher.go index 5a2204e99..f757f844e 100644 --- a/providers/base/api/handlers/mocks/api_fetcher.go +++ b/providers/base/api/handlers/mocks/api_fetcher.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks diff --git a/providers/base/api/handlers/mocks/api_query_handler.go b/providers/base/api/handlers/mocks/api_query_handler.go index be1157b5f..916c8ae86 100644 --- a/providers/base/api/handlers/mocks/api_query_handler.go +++ b/providers/base/api/handlers/mocks/api_query_handler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks diff --git a/providers/base/api/handlers/mocks/request_handler.go b/providers/base/api/handlers/mocks/request_handler.go index 88f753811..e0ef3be12 100644 --- a/providers/base/api/handlers/mocks/request_handler.go +++ b/providers/base/api/handlers/mocks/request_handler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks diff --git a/providers/base/api/metrics/mocks/mock_metrics.go b/providers/base/api/metrics/mocks/mock_metrics.go index 9814d12c9..bc6bf2a0d 100644 --- a/providers/base/api/metrics/mocks/mock_metrics.go +++ b/providers/base/api/metrics/mocks/mock_metrics.go @@ -1,13 +1,12 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks import ( http "net/http" - mock "github.com/stretchr/testify/mock" - metrics "github.com/skip-mev/slinky/providers/base/api/metrics" + mock "github.com/stretchr/testify/mock" time "time" diff --git a/providers/base/metrics/mocks/mock_metrics.go b/providers/base/metrics/mocks/mock_metrics.go index 3f0fa1c83..63e10608f 100644 --- a/providers/base/metrics/mocks/mock_metrics.go +++ b/providers/base/metrics/mocks/mock_metrics.go @@ -1,11 +1,10 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks import ( - mock "github.com/stretchr/testify/mock" - metrics "github.com/skip-mev/slinky/providers/base/metrics" + mock "github.com/stretchr/testify/mock" types "github.com/skip-mev/slinky/providers/types" ) diff --git a/providers/base/websocket/handlers/mocks/web_socket_conn_handler.go b/providers/base/websocket/handlers/mocks/web_socket_conn_handler.go index edbda8225..155297da2 100644 --- a/providers/base/websocket/handlers/mocks/web_socket_conn_handler.go +++ b/providers/base/websocket/handlers/mocks/web_socket_conn_handler.go @@ -1,11 +1,10 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks import ( - mock "github.com/stretchr/testify/mock" - handlers "github.com/skip-mev/slinky/providers/base/websocket/handlers" + mock "github.com/stretchr/testify/mock" ) // WebSocketConnHandler is an autogenerated mock type for the WebSocketConnHandler type diff --git a/providers/base/websocket/handlers/mocks/web_socket_data_handler.go b/providers/base/websocket/handlers/mocks/web_socket_data_handler.go index 388a49cc9..5a153fdfb 100644 --- a/providers/base/websocket/handlers/mocks/web_socket_data_handler.go +++ b/providers/base/websocket/handlers/mocks/web_socket_data_handler.go @@ -1,11 +1,10 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks import ( - mock "github.com/stretchr/testify/mock" - handlers "github.com/skip-mev/slinky/providers/base/websocket/handlers" + mock "github.com/stretchr/testify/mock" types "github.com/skip-mev/slinky/providers/types" ) diff --git a/providers/base/websocket/handlers/mocks/web_socket_query_handler.go b/providers/base/websocket/handlers/mocks/web_socket_query_handler.go index 96a0914e6..f8fcdf6dd 100644 --- a/providers/base/websocket/handlers/mocks/web_socket_query_handler.go +++ b/providers/base/websocket/handlers/mocks/web_socket_query_handler.go @@ -1,13 +1,12 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks import ( context "context" - mock "github.com/stretchr/testify/mock" - handlers "github.com/skip-mev/slinky/providers/base/websocket/handlers" + mock "github.com/stretchr/testify/mock" types "github.com/skip-mev/slinky/providers/types" ) diff --git a/providers/base/websocket/metrics/mocks/mock_metrics.go b/providers/base/websocket/metrics/mocks/mock_metrics.go index 845374d72..79e97e0e8 100644 --- a/providers/base/websocket/metrics/mocks/mock_metrics.go +++ b/providers/base/websocket/metrics/mocks/mock_metrics.go @@ -1,11 +1,10 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks import ( - mock "github.com/stretchr/testify/mock" - metrics "github.com/skip-mev/slinky/providers/base/websocket/metrics" + mock "github.com/stretchr/testify/mock" time "time" ) diff --git a/providers/types/mocks/mock_provider.go b/providers/types/mocks/mock_provider.go index 837e052ea..e69de29bb 100644 --- a/providers/types/mocks/mock_provider.go +++ b/providers/types/mocks/mock_provider.go @@ -1,122 +0,0 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. - -package mocks - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" - - types "github.com/skip-mev/slinky/providers/types" -) - -// Provider is an autogenerated mock type for the Provider type -type Provider[K types.ResponseKey, V types.ResponseValue] struct { - mock.Mock -} - -// GetData provides a mock function with given fields: -func (_m *Provider[K, V]) GetData() map[K]types.ResolvedResult[V] { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for GetData") - } - - var r0 map[K]types.ResolvedResult[V] - if rf, ok := ret.Get(0).(func() map[K]types.ResolvedResult[V]); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(map[K]types.ResolvedResult[V]) - } - } - - return r0 -} - -// IsRunning provides a mock function with given fields: -func (_m *Provider[K, V]) IsRunning() bool { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for IsRunning") - } - - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -// Name provides a mock function with given fields: -func (_m *Provider[K, V]) Name() string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Name") - } - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// Start provides a mock function with given fields: _a0 -func (_m *Provider[K, V]) Start(_a0 context.Context) error { - ret := _m.Called(_a0) - - if len(ret) == 0 { - panic("no return value specified for Start") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(_a0) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Type provides a mock function with given fields: -func (_m *Provider[K, V]) Type() types.ProviderType { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Type") - } - - var r0 types.ProviderType - if rf, ok := ret.Get(0).(func() types.ProviderType); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(types.ProviderType) - } - - return r0 -} - -// NewProvider creates a new instance of Provider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewProvider[K types.ResponseKey, V types.ResponseValue](t interface { - mock.TestingT - Cleanup(func()) -}) *Provider[K, V] { - mock := &Provider[K, V]{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/service/clients/oracle/mocks/mock_oracle_client.go b/service/clients/oracle/mocks/mock_oracle_client.go index 651361d16..629259155 100644 --- a/service/clients/oracle/mocks/mock_oracle_client.go +++ b/service/clients/oracle/mocks/mock_oracle_client.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks diff --git a/service/metrics/mocks/mock_metrics.go b/service/metrics/mocks/mock_metrics.go index 5e1edb602..aee6f602e 100644 --- a/service/metrics/mocks/mock_metrics.go +++ b/service/metrics/mocks/mock_metrics.go @@ -1,11 +1,10 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks import ( - mock "github.com/stretchr/testify/mock" - metrics "github.com/skip-mev/slinky/service/metrics" + mock "github.com/stretchr/testify/mock" time "time" diff --git a/service/servers/oracle/mocks/mock_oracle_service.go b/service/servers/oracle/mocks/mock_oracle_service.go index 0115a04a7..24767d0b0 100644 --- a/service/servers/oracle/mocks/mock_oracle_service.go +++ b/service/servers/oracle/mocks/mock_oracle_service.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks diff --git a/tests/integration/slinky_suite.go b/tests/integration/slinky_suite.go index 96e86dee9..9490927a6 100644 --- a/tests/integration/slinky_suite.go +++ b/tests/integration/slinky_suite.go @@ -3,7 +3,6 @@ package integration import ( "context" "encoding/hex" - "math/big" "os" "os/signal" diff --git a/x/marketmap/types/mocks/MarketMapHooks.go b/x/marketmap/types/mocks/MarketMapHooks.go index 33dd9ddbd..e4dfdb59a 100644 --- a/x/marketmap/types/mocks/MarketMapHooks.go +++ b/x/marketmap/types/mocks/MarketMapHooks.go @@ -1,11 +1,10 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks import ( - mock "github.com/stretchr/testify/mock" - marketmaptypes "github.com/skip-mev/slinky/x/marketmap/types" + mock "github.com/stretchr/testify/mock" types "github.com/cosmos/cosmos-sdk/types" ) diff --git a/x/oracle/types/mocks/market_map_keeper.go b/x/oracle/types/mocks/market_map_keeper.go index 4eb76a915..6cba7ed50 100644 --- a/x/oracle/types/mocks/market_map_keeper.go +++ b/x/oracle/types/mocks/market_map_keeper.go @@ -1,11 +1,10 @@ -// Code generated by mockery v2.44.1. DO NOT EDIT. +// Code generated by mockery v2.46.0. DO NOT EDIT. package mocks import ( - mock "github.com/stretchr/testify/mock" - marketmaptypes "github.com/skip-mev/slinky/x/marketmap/types" + mock "github.com/stretchr/testify/mock" types "github.com/cosmos/cosmos-sdk/types" ) From 52e6c70097d727ad0e8c23efb2f941ac05bf1a7e Mon Sep 17 00:00:00 2001 From: aljo242 Date: Wed, 11 Dec 2024 12:19:03 -0500 Subject: [PATCH 2/3] fix mocks --- oracle/mocks/PriceAggregator.go | 216 ++++++++++++++++++ oracle/mocks/mock_oracle.go | 301 +++++++++++++++++++++++++ providers/types/mocks/mock_provider.go | 266 ++++++++++++++++++++++ 3 files changed, 783 insertions(+) diff --git a/oracle/mocks/PriceAggregator.go b/oracle/mocks/PriceAggregator.go index e69de29bb..2ef338965 100644 --- a/oracle/mocks/PriceAggregator.go +++ b/oracle/mocks/PriceAggregator.go @@ -0,0 +1,216 @@ +// Code generated by mockery v2.50.0. DO NOT EDIT. + +package mocks + +import ( + big "math/big" + + mock "github.com/stretchr/testify/mock" + + types "github.com/skip-mev/slinky/x/marketmap/types" +) + +// PriceAggregator is an autogenerated mock type for the PriceAggregator type +type PriceAggregator struct { + mock.Mock +} + +type PriceAggregator_Expecter struct { + mock *mock.Mock +} + +func (_m *PriceAggregator) EXPECT() *PriceAggregator_Expecter { + return &PriceAggregator_Expecter{mock: &_m.Mock} +} + +// AggregatePrices provides a mock function with no fields +func (_m *PriceAggregator) AggregatePrices() { + _m.Called() +} + +// PriceAggregator_AggregatePrices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AggregatePrices' +type PriceAggregator_AggregatePrices_Call struct { + *mock.Call +} + +// AggregatePrices is a helper method to define mock.On call +func (_e *PriceAggregator_Expecter) AggregatePrices() *PriceAggregator_AggregatePrices_Call { + return &PriceAggregator_AggregatePrices_Call{Call: _e.mock.On("AggregatePrices")} +} + +func (_c *PriceAggregator_AggregatePrices_Call) Run(run func()) *PriceAggregator_AggregatePrices_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *PriceAggregator_AggregatePrices_Call) Return() *PriceAggregator_AggregatePrices_Call { + _c.Call.Return() + return _c +} + +func (_c *PriceAggregator_AggregatePrices_Call) RunAndReturn(run func()) *PriceAggregator_AggregatePrices_Call { + _c.Run(run) + return _c +} + +// GetPrices provides a mock function with no fields +func (_m *PriceAggregator) GetPrices() map[string]*big.Float { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetPrices") + } + + var r0 map[string]*big.Float + if rf, ok := ret.Get(0).(func() map[string]*big.Float); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]*big.Float) + } + } + + return r0 +} + +// PriceAggregator_GetPrices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPrices' +type PriceAggregator_GetPrices_Call struct { + *mock.Call +} + +// GetPrices is a helper method to define mock.On call +func (_e *PriceAggregator_Expecter) GetPrices() *PriceAggregator_GetPrices_Call { + return &PriceAggregator_GetPrices_Call{Call: _e.mock.On("GetPrices")} +} + +func (_c *PriceAggregator_GetPrices_Call) Run(run func()) *PriceAggregator_GetPrices_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *PriceAggregator_GetPrices_Call) Return(_a0 map[string]*big.Float) *PriceAggregator_GetPrices_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *PriceAggregator_GetPrices_Call) RunAndReturn(run func() map[string]*big.Float) *PriceAggregator_GetPrices_Call { + _c.Call.Return(run) + return _c +} + +// Reset provides a mock function with no fields +func (_m *PriceAggregator) Reset() { + _m.Called() +} + +// PriceAggregator_Reset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Reset' +type PriceAggregator_Reset_Call struct { + *mock.Call +} + +// Reset is a helper method to define mock.On call +func (_e *PriceAggregator_Expecter) Reset() *PriceAggregator_Reset_Call { + return &PriceAggregator_Reset_Call{Call: _e.mock.On("Reset")} +} + +func (_c *PriceAggregator_Reset_Call) Run(run func()) *PriceAggregator_Reset_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *PriceAggregator_Reset_Call) Return() *PriceAggregator_Reset_Call { + _c.Call.Return() + return _c +} + +func (_c *PriceAggregator_Reset_Call) RunAndReturn(run func()) *PriceAggregator_Reset_Call { + _c.Run(run) + return _c +} + +// SetProviderPrices provides a mock function with given fields: provider, prices +func (_m *PriceAggregator) SetProviderPrices(provider string, prices map[string]*big.Float) { + _m.Called(provider, prices) +} + +// PriceAggregator_SetProviderPrices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetProviderPrices' +type PriceAggregator_SetProviderPrices_Call struct { + *mock.Call +} + +// SetProviderPrices is a helper method to define mock.On call +// - provider string +// - prices map[string]*big.Float +func (_e *PriceAggregator_Expecter) SetProviderPrices(provider interface{}, prices interface{}) *PriceAggregator_SetProviderPrices_Call { + return &PriceAggregator_SetProviderPrices_Call{Call: _e.mock.On("SetProviderPrices", provider, prices)} +} + +func (_c *PriceAggregator_SetProviderPrices_Call) Run(run func(provider string, prices map[string]*big.Float)) *PriceAggregator_SetProviderPrices_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(map[string]*big.Float)) + }) + return _c +} + +func (_c *PriceAggregator_SetProviderPrices_Call) Return() *PriceAggregator_SetProviderPrices_Call { + _c.Call.Return() + return _c +} + +func (_c *PriceAggregator_SetProviderPrices_Call) RunAndReturn(run func(string, map[string]*big.Float)) *PriceAggregator_SetProviderPrices_Call { + _c.Run(run) + return _c +} + +// UpdateMarketMap provides a mock function with given fields: _a0 +func (_m *PriceAggregator) UpdateMarketMap(_a0 types.MarketMap) { + _m.Called(_a0) +} + +// PriceAggregator_UpdateMarketMap_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateMarketMap' +type PriceAggregator_UpdateMarketMap_Call struct { + *mock.Call +} + +// UpdateMarketMap is a helper method to define mock.On call +// - _a0 types.MarketMap +func (_e *PriceAggregator_Expecter) UpdateMarketMap(_a0 interface{}) *PriceAggregator_UpdateMarketMap_Call { + return &PriceAggregator_UpdateMarketMap_Call{Call: _e.mock.On("UpdateMarketMap", _a0)} +} + +func (_c *PriceAggregator_UpdateMarketMap_Call) Run(run func(_a0 types.MarketMap)) *PriceAggregator_UpdateMarketMap_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(types.MarketMap)) + }) + return _c +} + +func (_c *PriceAggregator_UpdateMarketMap_Call) Return() *PriceAggregator_UpdateMarketMap_Call { + _c.Call.Return() + return _c +} + +func (_c *PriceAggregator_UpdateMarketMap_Call) RunAndReturn(run func(types.MarketMap)) *PriceAggregator_UpdateMarketMap_Call { + _c.Run(run) + return _c +} + +// NewPriceAggregator creates a new instance of PriceAggregator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewPriceAggregator(t interface { + mock.TestingT + Cleanup(func()) +}) *PriceAggregator { + mock := &PriceAggregator{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} \ No newline at end of file diff --git a/oracle/mocks/mock_oracle.go b/oracle/mocks/mock_oracle.go index e69de29bb..52bd18e9f 100644 --- a/oracle/mocks/mock_oracle.go +++ b/oracle/mocks/mock_oracle.go @@ -0,0 +1,301 @@ +// Code generated by mockery v2.50.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + big "math/big" + + mock "github.com/stretchr/testify/mock" + + time "time" + + types "github.com/skip-mev/slinky/x/marketmap/types" +) + +// Oracle is an autogenerated mock type for the Oracle type +type Oracle struct { + mock.Mock +} + +type Oracle_Expecter struct { + mock *mock.Mock +} + +func (_m *Oracle) EXPECT() *Oracle_Expecter { + return &Oracle_Expecter{mock: &_m.Mock} +} + +// GetLastSyncTime provides a mock function with no fields +func (_m *Oracle) GetLastSyncTime() time.Time { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetLastSyncTime") + } + + var r0 time.Time + if rf, ok := ret.Get(0).(func() time.Time); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Time) + } + + return r0 +} + +// Oracle_GetLastSyncTime_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLastSyncTime' +type Oracle_GetLastSyncTime_Call struct { + *mock.Call +} + +// GetLastSyncTime is a helper method to define mock.On call +func (_e *Oracle_Expecter) GetLastSyncTime() *Oracle_GetLastSyncTime_Call { + return &Oracle_GetLastSyncTime_Call{Call: _e.mock.On("GetLastSyncTime")} +} + +func (_c *Oracle_GetLastSyncTime_Call) Run(run func()) *Oracle_GetLastSyncTime_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Oracle_GetLastSyncTime_Call) Return(_a0 time.Time) *Oracle_GetLastSyncTime_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Oracle_GetLastSyncTime_Call) RunAndReturn(run func() time.Time) *Oracle_GetLastSyncTime_Call { + _c.Call.Return(run) + return _c +} + +// GetMarketMap provides a mock function with no fields +func (_m *Oracle) GetMarketMap() types.MarketMap { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetMarketMap") + } + + var r0 types.MarketMap + if rf, ok := ret.Get(0).(func() types.MarketMap); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(types.MarketMap) + } + + return r0 +} + +// Oracle_GetMarketMap_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetMarketMap' +type Oracle_GetMarketMap_Call struct { + *mock.Call +} + +// GetMarketMap is a helper method to define mock.On call +func (_e *Oracle_Expecter) GetMarketMap() *Oracle_GetMarketMap_Call { + return &Oracle_GetMarketMap_Call{Call: _e.mock.On("GetMarketMap")} +} + +func (_c *Oracle_GetMarketMap_Call) Run(run func()) *Oracle_GetMarketMap_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Oracle_GetMarketMap_Call) Return(_a0 types.MarketMap) *Oracle_GetMarketMap_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Oracle_GetMarketMap_Call) RunAndReturn(run func() types.MarketMap) *Oracle_GetMarketMap_Call { + _c.Call.Return(run) + return _c +} + +// GetPrices provides a mock function with no fields +func (_m *Oracle) GetPrices() map[string]*big.Float { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetPrices") + } + + var r0 map[string]*big.Float + if rf, ok := ret.Get(0).(func() map[string]*big.Float); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]*big.Float) + } + } + + return r0 +} + +// Oracle_GetPrices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPrices' +type Oracle_GetPrices_Call struct { + *mock.Call +} + +// GetPrices is a helper method to define mock.On call +func (_e *Oracle_Expecter) GetPrices() *Oracle_GetPrices_Call { + return &Oracle_GetPrices_Call{Call: _e.mock.On("GetPrices")} +} + +func (_c *Oracle_GetPrices_Call) Run(run func()) *Oracle_GetPrices_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Oracle_GetPrices_Call) Return(_a0 map[string]*big.Float) *Oracle_GetPrices_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Oracle_GetPrices_Call) RunAndReturn(run func() map[string]*big.Float) *Oracle_GetPrices_Call { + _c.Call.Return(run) + return _c +} + +// IsRunning provides a mock function with no fields +func (_m *Oracle) IsRunning() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for IsRunning") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Oracle_IsRunning_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsRunning' +type Oracle_IsRunning_Call struct { + *mock.Call +} + +// IsRunning is a helper method to define mock.On call +func (_e *Oracle_Expecter) IsRunning() *Oracle_IsRunning_Call { + return &Oracle_IsRunning_Call{Call: _e.mock.On("IsRunning")} +} + +func (_c *Oracle_IsRunning_Call) Run(run func()) *Oracle_IsRunning_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Oracle_IsRunning_Call) Return(_a0 bool) *Oracle_IsRunning_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Oracle_IsRunning_Call) RunAndReturn(run func() bool) *Oracle_IsRunning_Call { + _c.Call.Return(run) + return _c +} + +// Start provides a mock function with given fields: ctx +func (_m *Oracle) Start(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Start") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Oracle_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' +type Oracle_Start_Call struct { + *mock.Call +} + +// Start is a helper method to define mock.On call +// - ctx context.Context +func (_e *Oracle_Expecter) Start(ctx interface{}) *Oracle_Start_Call { + return &Oracle_Start_Call{Call: _e.mock.On("Start", ctx)} +} + +func (_c *Oracle_Start_Call) Run(run func(ctx context.Context)) *Oracle_Start_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Oracle_Start_Call) Return(_a0 error) *Oracle_Start_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Oracle_Start_Call) RunAndReturn(run func(context.Context) error) *Oracle_Start_Call { + _c.Call.Return(run) + return _c +} + +// Stop provides a mock function with no fields +func (_m *Oracle) Stop() { + _m.Called() +} + +// Oracle_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop' +type Oracle_Stop_Call struct { + *mock.Call +} + +// Stop is a helper method to define mock.On call +func (_e *Oracle_Expecter) Stop() *Oracle_Stop_Call { + return &Oracle_Stop_Call{Call: _e.mock.On("Stop")} +} + +func (_c *Oracle_Stop_Call) Run(run func()) *Oracle_Stop_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Oracle_Stop_Call) Return() *Oracle_Stop_Call { + _c.Call.Return() + return _c +} + +func (_c *Oracle_Stop_Call) RunAndReturn(run func()) *Oracle_Stop_Call { + _c.Run(run) + return _c +} + +// NewOracle creates a new instance of Oracle. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewOracle(t interface { + mock.TestingT + Cleanup(func()) +}) *Oracle { + mock := &Oracle{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} \ No newline at end of file diff --git a/providers/types/mocks/mock_provider.go b/providers/types/mocks/mock_provider.go index e69de29bb..3c35615b7 100644 --- a/providers/types/mocks/mock_provider.go +++ b/providers/types/mocks/mock_provider.go @@ -0,0 +1,266 @@ +// Code generated by mockery v2.50.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + types "github.com/skip-mev/slinky/providers/types" +) + +// Provider is an autogenerated mock type for the Provider type +type Provider[K types.ResponseKey, V types.ResponseValue] struct { + mock.Mock +} + +type Provider_Expecter[K types.ResponseKey, V types.ResponseValue] struct { + mock *mock.Mock +} + +func (_m *Provider[K, V]) EXPECT() *Provider_Expecter[K, V] { + return &Provider_Expecter[K, V]{mock: &_m.Mock} +} + +// GetData provides a mock function with no fields +func (_m *Provider[K, V]) GetData() map[K]types.ResolvedResult[V] { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetData") + } + + var r0 map[K]types.ResolvedResult[V] + if rf, ok := ret.Get(0).(func() map[K]types.ResolvedResult[V]); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[K]types.ResolvedResult[V]) + } + } + + return r0 +} + +// Provider_GetData_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetData' +type Provider_GetData_Call[K types.ResponseKey, V types.ResponseValue] struct { + *mock.Call +} + +// GetData is a helper method to define mock.On call +func (_e *Provider_Expecter[K, V]) GetData() *Provider_GetData_Call[K, V] { + return &Provider_GetData_Call[K, V]{Call: _e.mock.On("GetData")} +} + +func (_c *Provider_GetData_Call[K, V]) Run(run func()) *Provider_GetData_Call[K, V] { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Provider_GetData_Call[K, V]) Return(_a0 map[K]types.ResolvedResult[V]) *Provider_GetData_Call[K, V] { + _c.Call.Return(_a0) + return _c +} + +func (_c *Provider_GetData_Call[K, V]) RunAndReturn(run func() map[K]types.ResolvedResult[V]) *Provider_GetData_Call[K, V] { + _c.Call.Return(run) + return _c +} + +// IsRunning provides a mock function with no fields +func (_m *Provider[K, V]) IsRunning() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for IsRunning") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Provider_IsRunning_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsRunning' +type Provider_IsRunning_Call[K types.ResponseKey, V types.ResponseValue] struct { + *mock.Call +} + +// IsRunning is a helper method to define mock.On call +func (_e *Provider_Expecter[K, V]) IsRunning() *Provider_IsRunning_Call[K, V] { + return &Provider_IsRunning_Call[K, V]{Call: _e.mock.On("IsRunning")} +} + +func (_c *Provider_IsRunning_Call[K, V]) Run(run func()) *Provider_IsRunning_Call[K, V] { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Provider_IsRunning_Call[K, V]) Return(_a0 bool) *Provider_IsRunning_Call[K, V] { + _c.Call.Return(_a0) + return _c +} + +func (_c *Provider_IsRunning_Call[K, V]) RunAndReturn(run func() bool) *Provider_IsRunning_Call[K, V] { + _c.Call.Return(run) + return _c +} + +// Name provides a mock function with no fields +func (_m *Provider[K, V]) Name() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Name") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Provider_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name' +type Provider_Name_Call[K types.ResponseKey, V types.ResponseValue] struct { + *mock.Call +} + +// Name is a helper method to define mock.On call +func (_e *Provider_Expecter[K, V]) Name() *Provider_Name_Call[K, V] { + return &Provider_Name_Call[K, V]{Call: _e.mock.On("Name")} +} + +func (_c *Provider_Name_Call[K, V]) Run(run func()) *Provider_Name_Call[K, V] { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Provider_Name_Call[K, V]) Return(_a0 string) *Provider_Name_Call[K, V] { + _c.Call.Return(_a0) + return _c +} + +func (_c *Provider_Name_Call[K, V]) RunAndReturn(run func() string) *Provider_Name_Call[K, V] { + _c.Call.Return(run) + return _c +} + +// Start provides a mock function with given fields: _a0 +func (_m *Provider[K, V]) Start(_a0 context.Context) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Start") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Provider_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' +type Provider_Start_Call[K types.ResponseKey, V types.ResponseValue] struct { + *mock.Call +} + +// Start is a helper method to define mock.On call +// - _a0 context.Context +func (_e *Provider_Expecter[K, V]) Start(_a0 interface{}) *Provider_Start_Call[K, V] { + return &Provider_Start_Call[K, V]{Call: _e.mock.On("Start", _a0)} +} + +func (_c *Provider_Start_Call[K, V]) Run(run func(_a0 context.Context)) *Provider_Start_Call[K, V] { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Provider_Start_Call[K, V]) Return(_a0 error) *Provider_Start_Call[K, V] { + _c.Call.Return(_a0) + return _c +} + +func (_c *Provider_Start_Call[K, V]) RunAndReturn(run func(context.Context) error) *Provider_Start_Call[K, V] { + _c.Call.Return(run) + return _c +} + +// Type provides a mock function with no fields +func (_m *Provider[K, V]) Type() types.ProviderType { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Type") + } + + var r0 types.ProviderType + if rf, ok := ret.Get(0).(func() types.ProviderType); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(types.ProviderType) + } + + return r0 +} + +// Provider_Type_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Type' +type Provider_Type_Call[K types.ResponseKey, V types.ResponseValue] struct { + *mock.Call +} + +// Type is a helper method to define mock.On call +func (_e *Provider_Expecter[K, V]) Type() *Provider_Type_Call[K, V] { + return &Provider_Type_Call[K, V]{Call: _e.mock.On("Type")} +} + +func (_c *Provider_Type_Call[K, V]) Run(run func()) *Provider_Type_Call[K, V] { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Provider_Type_Call[K, V]) Return(_a0 types.ProviderType) *Provider_Type_Call[K, V] { + _c.Call.Return(_a0) + return _c +} + +func (_c *Provider_Type_Call[K, V]) RunAndReturn(run func() types.ProviderType) *Provider_Type_Call[K, V] { + _c.Call.Return(run) + return _c +} + +// NewProvider creates a new instance of Provider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewProvider[K types.ResponseKey, V types.ResponseValue](t interface { + mock.TestingT + Cleanup(func()) +}) *Provider[K, V] { + mock := &Provider[K, V]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} \ No newline at end of file From 8ab313ef300e522c3ebb068089ab41ff940a6dbe Mon Sep 17 00:00:00 2001 From: aljo242 Date: Wed, 11 Dec 2024 12:23:47 -0500 Subject: [PATCH 3/3] fix --- providers/apis/defi/osmosis/client_test.go | 22 ++++++------ .../apis/defi/osmosis/price_fetcher_test.go | 36 ++++++++++--------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/providers/apis/defi/osmosis/client_test.go b/providers/apis/defi/osmosis/client_test.go index 7f1d32a6d..dd8640dd4 100644 --- a/providers/apis/defi/osmosis/client_test.go +++ b/providers/apis/defi/osmosis/client_test.go @@ -82,15 +82,15 @@ func TestMultiClient(t *testing.T) { defer cancel() // mocks - client1.On("SpotPrice", mock.Anything, poolID, baseAsset, quoteAsset).Return(osmosis.SpotPriceResponse{ - SpotPrice: expectedPrice, + client1.On("SpotPrice", mock.Anything, poolID, baseAsset, quoteAsset).Return(osmosis.WrappedSpotPriceResponse{ + SpotPriceResponse: osmosis.SpotPriceResponse{SpotPrice: expectedPrice}, }, nil).Once() - client2.On("SpotPrice", mock.Anything, poolID, baseAsset, quoteAsset).Return(osmosis.SpotPriceResponse{ - SpotPrice: expectedPrice, + client2.On("SpotPrice", mock.Anything, poolID, baseAsset, quoteAsset).Return(osmosis.WrappedSpotPriceResponse{ + SpotPriceResponse: osmosis.SpotPriceResponse{SpotPrice: expectedPrice}, }, nil).Once() - client3.On("SpotPrice", mock.Anything, poolID, baseAsset, quoteAsset).Return(osmosis.SpotPriceResponse{}, + client3.On("SpotPrice", mock.Anything, poolID, baseAsset, quoteAsset).Return(osmosis.WrappedSpotPriceResponse{}, fmt.Errorf("error")).Once() resp, err := client.SpotPrice(ctx, poolID, baseAsset, quoteAsset) @@ -112,14 +112,14 @@ func TestMultiClient(t *testing.T) { defer cancel() // mocks - client1.On("SpotPrice", mock.Anything, poolID, baseAsset, quoteAsset).Return(osmosis.SpotPriceResponse{ - SpotPrice: expectedPrice, + client1.On("SpotPrice", mock.Anything, poolID, baseAsset, quoteAsset).Return(osmosis.WrappedSpotPriceResponse{ + SpotPriceResponse: osmosis.SpotPriceResponse{SpotPrice: expectedPrice}, }, nil).Once() - client2.On("SpotPrice", mock.Anything, poolID, baseAsset, quoteAsset).Return(osmosis.SpotPriceResponse{ - SpotPrice: expectedPrice, + client2.On("SpotPrice", mock.Anything, poolID, baseAsset, quoteAsset).Return(osmosis.WrappedSpotPriceResponse{ + SpotPriceResponse: osmosis.SpotPriceResponse{SpotPrice: expectedPrice}, }, nil).Once() - client3.On("SpotPrice", mock.Anything, poolID, baseAsset, quoteAsset).Return(osmosis.SpotPriceResponse{ - SpotPrice: expectedPrice, + client3.On("SpotPrice", mock.Anything, poolID, baseAsset, quoteAsset).Return(osmosis.WrappedSpotPriceResponse{ + SpotPriceResponse: osmosis.SpotPriceResponse{SpotPrice: expectedPrice}, }, nil).Once() resp, err := client.SpotPrice(ctx, poolID, baseAsset, quoteAsset) diff --git a/providers/apis/defi/osmosis/price_fetcher_test.go b/providers/apis/defi/osmosis/price_fetcher_test.go index cc4254e32..9ab70792b 100644 --- a/providers/apis/defi/osmosis/price_fetcher_test.go +++ b/providers/apis/defi/osmosis/price_fetcher_test.go @@ -203,8 +203,10 @@ func TestProviderFetch(t *testing.T) { client.On("SpotPrice", mock.Anything, btcUSDTMetadata.PoolID, btcUSDTMetadata.BaseTokenDenom, btcUSDTMetadata.QuoteTokenDenom, - ).Return(osmosis.SpotPriceResponse{ - SpotPrice: expectedBTCUSDTPrice, + ).Return(osmosis.WrappedSpotPriceResponse{ + SpotPriceResponse: osmosis.SpotPriceResponse{ + SpotPrice: expectedBTCUSDTPrice, + }, }, nil).Once() ts := defaultTickersToProviderTickers([]types.DefaultProviderTicker{tickers[0]}) @@ -223,8 +225,10 @@ func TestProviderFetch(t *testing.T) { err = fmt.Errorf("error") - client.On("SpotPrice", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(osmosis.SpotPriceResponse{ - SpotPrice: "", + client.On("SpotPrice", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(osmosis.WrappedSpotPriceResponse{ + SpotPriceResponse: osmosis.SpotPriceResponse{ + SpotPrice: "", + }, }, err).Times(3) ts := defaultTickersToProviderTickers(tickers) @@ -273,20 +277,20 @@ func TestProviderFetch(t *testing.T) { client.On("SpotPrice", mock.Anything, btcUSDTMetadata.PoolID, btcUSDTMetadata.BaseTokenDenom, btcUSDTMetadata.QuoteTokenDenom, - ).Return(osmosis.SpotPriceResponse{ - SpotPrice: "", + ).Return(osmosis.WrappedSpotPriceResponse{ + SpotPriceResponse: osmosis.SpotPriceResponse{SpotPrice: ""}, }, err).Once() client.On("SpotPrice", mock.Anything, ethUSDTMetadata.PoolID, ethUSDTMetadata.BaseTokenDenom, ethUSDTMetadata.QuoteTokenDenom, - ).Return(osmosis.SpotPriceResponse{ - SpotPrice: expectedETHUSDTPrice, + ).Return(osmosis.WrappedSpotPriceResponse{ + SpotPriceResponse: osmosis.SpotPriceResponse{SpotPrice: expectedETHUSDTPrice}, }, nil).Once() client.On("SpotPrice", mock.Anything, mogSOLMetadata.PoolID, mogSOLMetadata.BaseTokenDenom, mogSOLMetadata.QuoteTokenDenom, - ).Return(osmosis.SpotPriceResponse{ - SpotPrice: expectedMOGSOLPRICE, + ).Return(osmosis.WrappedSpotPriceResponse{ + SpotPriceResponse: osmosis.SpotPriceResponse{SpotPrice: expectedMOGSOLPRICE}, }, nil).Once() ts := defaultTickersToProviderTickers(tickers) @@ -310,20 +314,20 @@ func TestProviderFetch(t *testing.T) { client.On("SpotPrice", mock.Anything, btcUSDTMetadata.PoolID, btcUSDTMetadata.BaseTokenDenom, btcUSDTMetadata.QuoteTokenDenom, - ).Return(osmosis.SpotPriceResponse{ - SpotPrice: expectedBTCUSDTPrice, + ).Return(osmosis.WrappedSpotPriceResponse{ + SpotPriceResponse: osmosis.SpotPriceResponse{SpotPrice: expectedBTCUSDTPrice}, }, nil).Once() client.On("SpotPrice", mock.Anything, ethUSDTMetadata.PoolID, ethUSDTMetadata.BaseTokenDenom, ethUSDTMetadata.QuoteTokenDenom, - ).Return(osmosis.SpotPriceResponse{ - SpotPrice: expectedETHUSDTPrice, + ).Return(osmosis.WrappedSpotPriceResponse{ + SpotPriceResponse: osmosis.SpotPriceResponse{SpotPrice: expectedETHUSDTPrice}, }, nil).Once() client.On("SpotPrice", mock.Anything, mogSOLMetadata.PoolID, mogSOLMetadata.BaseTokenDenom, mogSOLMetadata.QuoteTokenDenom, - ).Return(osmosis.SpotPriceResponse{ - SpotPrice: expectedMOGSOLPRICE, + ).Return(osmosis.WrappedSpotPriceResponse{ + SpotPriceResponse: osmosis.SpotPriceResponse{SpotPrice: expectedMOGSOLPRICE}, }, nil).Once() ts := defaultTickersToProviderTickers(tickers)