diff --git a/Makefile b/Makefile index 7d30d7a..bda50a8 100644 --- a/Makefile +++ b/Makefile @@ -19,12 +19,11 @@ check: gosec -exclude-dir=e2e ./... start: install - $(GOPATH)/bin/sygma-fee-oracle server -c $(makeFileDir)config.yaml -d $(makeFileDir)domain.json -r $(makeFileDir)resource.json -k $(makeFileDir)keyfile.priv -t secp256k1 + $(GOPATH)/bin/sygma-fee-oracle server -c $(makeFileDir)config.yaml -d $(makeFileDir)domain.json -k $(makeFileDir)keyfile.priv -t secp256k1 genmocks: mockgen -destination=./store/mock/store.go -source=./store/store.go mockgen -destination=./oracle/mock/oracle.go github.com/ChainSafe/sygma-fee-oracle/oracle GasPriceOracle,ConversionRateOracle - mockgen -destination=./config/mock/remoteParamOperator.go -source=./remoteParam/base.go test: go clean -testcache diff --git a/README.md b/README.md index 47e335d..2d12984 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Fee oracle needs three config files in the `./` dir of the codebase: Template of the config.yaml can be found in `./config/config.template.yaml`. ### Domain config -For domain configuration, it is posible to use local file, or the [shared configuration](https://github.com/sygmaprotocol/sygma-shared-configuration) file that is uploaded to some remote service (eg. ipfs). Depending if the URL or file path is provided to the flag `domain_config_path`, the aplication will use the domain configuration from local file or from the remote service. +For domain configuration, it is possible to use local file, or the [shared configuration](https://github.com/sygmaprotocol/sygma-shared-configuration) file that is uploaded to some remote service (eg. ipfs). Depending on if the URL or file path is provided to the flag `domain_config_path`, the application will use the domain configuration from local file or from the remote service. This file indicates all the domains and resources the fee oracle needs to fetch data for. Details need to be matched with Sygma core configuration, such as `id`. diff --git a/api/api.go b/api/api.go index 528b435..165fe26 100644 --- a/api/api.go +++ b/api/api.go @@ -69,7 +69,7 @@ func (h *Handler) getRate(c *gin.Context) { return } - gp, err := h.consensus.FilterLocalGasPriceData(h.gasPriceStore, toDomain.Name) + gp, err := h.consensus.FilterLocalGasPriceData(h.gasPriceStore, toDomain.ID) if err != nil { h.log.Errorf("get gasprice process failed: %v", err) ginErrorReturn(c, http.StatusInternalServerError, newReturnErrorResp(&oracleErrors.InternalServerError, err)) diff --git a/app/app.go b/app/app.go index 391d218..715365f 100644 --- a/app/app.go +++ b/app/app.go @@ -49,25 +49,39 @@ type FeeOracleApp struct { } func NewFeeOracleApp(appBase *base.FeeOracleAppBase) *FeeOracleApp { - // init concrete oracle services - coinMarketCap := oracle.NewCoinMarketCap(appBase.GetConfig(), appBase.GetLogger()) - etherscan := oracle.NewEtherscan(appBase.GetConfig(), appBase.GetLogger()) - polygonscan := oracle.NewPolygonscan(appBase.GetConfig(), appBase.GetLogger()) - moonscan := oracle.NewMoonscan(appBase.GetConfig(), appBase.GetLogger()) - - // register concrete oracle services in operator - coinMarketCapConversionRateOracle := oracle.NewConversionRateOracleOperator(appBase.GetLogger(), coinMarketCap) - etherscanGasPriceOracle := oracle.NewGasPriceOracleOperator(appBase.GetLogger(), etherscan) - polygonscanGasPriceOracle := oracle.NewGasPriceOracleOperator(appBase.GetLogger(), polygonscan) - moonscanGasPriceOracle := oracle.NewGasPriceOracleOperator(appBase.GetLogger(), moonscan) + // initialize gas price oracles and register concrete oracle services in operator + gasPriceOracles := make(map[string]*oracle.GasPriceOracleOperator) + for _, domain := range appBase.GetConfig().DomainsList { + for _, apiService := range domain.GasPriceApis { + if apiService.Enable { + var oracleInstance oracle.GasPriceOracle + switch apiService.Implementation { + case "etherscan": + oracleInstance = oracle.NewEtherscan(apiService.Source, appBase.GetConfig().GasPriceApikeyReload(domain.DomainID, apiService), domain.DomainID, appBase.GetLogger()) + case "moonscan": + oracleInstance = oracle.NewMoonscan(apiService.Source, appBase.GetConfig().GasPriceApikeyReload(domain.DomainID, apiService), domain.DomainID, appBase.GetLogger()) + default: + panic("unknown gas price oracle implementation") + } + gasPriceOracles[apiService.Source] = oracle.NewGasPriceOracleOperator(appBase.GetLogger(), oracleInstance) + } + } + } + // initialize conversion rate oracles and register concrete oracle services in operator conversionRateOracles := make(map[string]*oracle.ConversionRateOracleOperator) - conversionRateOracles[coinMarketCap.Name()] = coinMarketCapConversionRateOracle - - gasPriceOracles := make(map[string]*oracle.GasPriceOracleOperator) - gasPriceOracles[etherscan.Name()] = etherscanGasPriceOracle - gasPriceOracles[polygonscan.Name()] = polygonscanGasPriceOracle - gasPriceOracles[moonscan.Name()] = moonscanGasPriceOracle + for _, rateOracle := range appBase.GetConfig().ConversionRateApis { + if rateOracle.Enable { + var oracleInstance oracle.ConversionRateOracle + switch rateOracle.Implementation { + case "coinmarketcap": + oracleInstance = oracle.NewCoinMarketCap(rateOracle.Source, appBase.GetConfig().ConversionRateApikeyReload(rateOracle), appBase.GetLogger()) + default: + panic("unknown conversion rate oracle implementation") + } + conversionRateOracles[rateOracle.Source] = oracle.NewConversionRateOracleOperator(appBase.GetLogger(), oracleInstance) + } + } conversionRateStore := store.NewConversionRateStore(appBase.GetStore()) gasPriceStore := store.NewGasPriceStore(appBase.GetStore()) diff --git a/config.yaml b/config.yaml index 4d26848..07a8dc5 100644 --- a/config.yaml +++ b/config.yaml @@ -17,7 +17,7 @@ http_server: # time in second before server shutdown # this will allow server to finish running jobs before shutdown -finish_up_time: 10 +finish_up_time: 3 # internal scheduled cronjob cron_job: @@ -36,32 +36,44 @@ cron_job: store: path: ./lvldbdata -# supported oracles -# api key is just a placeholder, replace with your valid key -oracle: - etherscan: - enable: true - api_key: BZM7P395BQS1YFKQMA2AK2ACQKWGEW4JB3 - apis: - gas_price: https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey= - polygonscan: - enable: true - api_key: BZM7P395BQS1YFKQMA2AK2ACQKWGEW4JB3 - apis: - gas_price: https://api.polygonscan.com/api?module=gastracker&action=gasoracle&apikey= - moonscan: - enable: true - api_key: BZM7P395BQS1YFKQMA2AK2ACQKWGEW4JB3 - apis: - gas_price: https://api-moonbeam.moonscan.io/api?module=proxy&action=eth_gasPrice&apikey= - coinmarketcap: +# conversion_rate_apis contains the list of conversion rate api services +# implementation indicates the implementation of the oracle +# source is the source of the data, reflected the source of the api url +# url is the api url of gas price of the oracle +# api_key is the api key of the oracle +# enable is the flag to enable/disable the current api service, fee oracle will not instantiate this service if it is disabled +conversion_rate_apis: + - implementation: coinmarketcap + source: coinmarketcap enable: true + url: https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest? api_key: 1408daf0-0777-4916-9fe4-20da5ee77560 - apis: - query_rate: https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest? -gas_price_domains: - [ ethereum, polygon, moonbeam ] +domain_list: + - domain_id: 0 + gas_price_apis: + - implementation: etherscan + source: etherscan + enable: true + url: https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey= + api_key: BZM7P395BQS1YFKQMA2AK2ACQKWGEW4JB3 + decimals: 9 + - domain_id: 1 + gas_price_apis: + - implementation: etherscan + source: polygonscan + enable: true + url: https://api.polygonscan.com/api?module=gastracker&action=gasoracle&apikey= + api_key: BZM7P395BQS1YFKQMA2AK2ACQKWGEW4JB3 + decimals: 9 + - domain_id: 2 + gas_price_apis: + - implementation: moonscan + source: moonscan + enable: true + url: https://api-moonbeam.moonscan.io/api?module=proxy&action=eth_gasPrice&apikey= + api_key: BZM7P395BQS1YFKQMA2AK2ACQKWGEW4JB3 + decimals: # conversion_rate_pairs contains price pair for conversion rate # must be paired and follow the format of [ base, foreign, base, foreign, ... ] @@ -73,4 +85,4 @@ strategy: local: average # data_valid_interval defines how long the endpoint response data remains valid before sending it to fee handler contract -data_valid_interval: 3600 # second \ No newline at end of file +data_valid_interval: 3600 # second diff --git a/config/config.go b/config/config.go index faeaabc..ea370bb 100644 --- a/config/config.go +++ b/config/config.go @@ -42,8 +42,8 @@ type Config struct { FinishUpTime int64 `mapstructure:"finish_up_time"` CronJob cronJobConfig `mapstructure:"cron_job"` Store store `mapstructure:"store"` - Oracle oracle `mapstructure:"oracle"` - GasPriceDomains []string `mapstructure:"gas_price_domains"` + ConversionRateApis []ApiService `mapstructure:"conversion_rate_apis"` + DomainsList []domainList `mapstructure:"domain_list"` ConversionRatePairs []string `mapstructure:"conversion_rate_pairs"` Strategy strategyConfig `mapstructure:"strategy"` DataValidInterval int64 `mapstructure:"data_valid_interval"` @@ -55,11 +55,18 @@ type strategyConfig struct { Local string `mapstructure:"local"` } -type oracle struct { - Etherscan etherscan `mapstructure:"etherscan"` - Polygonscan polygonscan `mapstructure:"polygonscan"` - CoinMarketCap coinMarketCap `mapstructure:"coinmarketcap"` - Moonscan coinMarketCap `mapstructure:"moonscan"` +type domainList struct { + DomainID int `mapstructure:"domain_id"` + GasPriceApis []ApiService `mapstructure:"gas_price_apis"` +} + +type ApiService struct { + Implementation string `mapstructure:"implementation"` + Source string `mapstructure:"source"` + Enable bool `mapstructure:"enable"` + URL string `mapstructure:"url"` + ApiKey string `mapstructure:"api_key"` + Decimals int `mapstructure:"decimals"` } type store struct { @@ -83,29 +90,6 @@ type httpServerConfig struct { Port string `mapstructure:"port"` } -type polygonscan struct { - Enable bool `mapstructure:"enable"` - ApiKey string `mapstructure:"api_key"` - Apis apiUrls `mapstructure:"apis"` -} - -type etherscan struct { - Enable bool `mapstructure:"enable"` - ApiKey string `mapstructure:"api_key"` - Apis apiUrls `mapstructure:"apis"` -} - -type coinMarketCap struct { - Enable bool `mapstructure:"enable"` - ApiKey string `mapstructure:"api_key"` - Apis apiUrls `mapstructure:"apis"` -} - -type apiUrls struct { - GasPriceApiUrl string `mapstructure:"gas_price"` - QueryRate string `mapstructure:"query_rate"` -} - func (c *Config) LogLevel() (logrus.Level, error) { logLvl := os.Getenv("LOG_LEVEL") if logLvl == "" { @@ -175,28 +159,26 @@ func (c *Config) HttpServerConfig() httpServerConfig { return httpConfig } -func (c *Config) OracleConfig() oracle { - oracleConfig := c.Oracle +// GasPriceApikeyReload dynamically builds the env var key string and replaces the api key if the env var is set +// dynamic env var key string format: _API_KEY_ +func (c *Config) GasPriceApikeyReload(domainID int, apiService ApiService) ApiService { + loadedApiKey := os.Getenv(fmt.Sprintf("%s_API_KEY_%d", strings.ToUpper(apiService.Source), domainID)) + if loadedApiKey != "" { + apiService.ApiKey = loadedApiKey + } - etherscanAPIKey := os.Getenv("ETHERSCAN_API_KEY") - polygonscanAPIKey := os.Getenv("POLYGONSCAN_API_KEY") - coinMarketCapAPIKey := os.Getenv("COINMARKETCAP_API_KEY") - moonscanAPIKey := os.Getenv("MOONSCAN_API_KEY") + return apiService +} - if etherscanAPIKey != "" { - oracleConfig.Etherscan.ApiKey = etherscanAPIKey - } - if polygonscanAPIKey != "" { - oracleConfig.Polygonscan.ApiKey = polygonscanAPIKey - } - if coinMarketCapAPIKey != "" { - oracleConfig.CoinMarketCap.ApiKey = coinMarketCapAPIKey - } - if moonscanAPIKey != "" { - oracleConfig.Moonscan.ApiKey = moonscanAPIKey +// ConversionRateApikeyReload dynamically builds the env var key string and replaces the api key if the env var is set +// dynamic env var key string format: _API_KEY +func (c *Config) ConversionRateApikeyReload(apiService ApiService) ApiService { + loadedApiKey := os.Getenv(fmt.Sprintf("%s_API_KEY", strings.ToUpper(apiService.Source))) + if loadedApiKey != "" { + apiService.ApiKey = loadedApiKey } - return oracleConfig + return apiService } func (c *Config) CronJobConfig() cronJobConfig { diff --git a/config/config.template.yaml b/config/config.template.yaml index b49b7f5..c0b41f9 100644 --- a/config/config.template.yaml +++ b/config/config.template.yaml @@ -36,25 +36,38 @@ cron_job: store: path: ./lvldbdata -oracle: - etherscan: +conversion_rate_apis: + - implementation: coinmarketcap + source: coinmarketcap enable: true + url: api_key: - apis: - gas_price: - polygonscan: - enable: true - api_key: - apis: - gas_price: - coinmarketcap: - enable: true - api_key: - apis: - query_rate: -gas_price_domains: - [ ethereum ] +domain_list: + - domain_id: 0 + gas_price_apis: + - implementation: etherscan + source: etherscan + enable: true + url: + api_key: + decimals: 9 + - domain_id: 1 + gas_price_apis: + - implementation: etherscan + source: polygonscan + enable: true + url: + api_key: + decimals: 9 + - domain_id: 2 + gas_price_apis: + - implementation: moonscan + source: moonscan + enable: true + url: + api_key: + decimals: # conversion_rate_pairs contains price pair for conversion rate # must be paired and follow the format of [ base, foreign, base, foreign, ... ] @@ -66,4 +79,4 @@ strategy: local: average # data_valid_interval defines how long the endpoint response data remains valid before sending it to fee handler contract -data_valid_interval: 3600 # second \ No newline at end of file +data_valid_interval: 3600 # second diff --git a/consensus/base.go b/consensus/base.go index 81bb4bd..d5fc644 100644 --- a/consensus/base.go +++ b/consensus/base.go @@ -30,8 +30,8 @@ func (c *Consensus) GetStrategy() string { return c.strategy.Name() } -func (c *Consensus) FilterLocalGasPriceData(store *store.GasPriceStore, domainName string) (*types.GasPrices, error) { - return c.strategy.GasPrice(store, domainName) +func (c *Consensus) FilterLocalGasPriceData(store *store.GasPriceStore, domainID int) (*types.GasPrices, error) { + return c.strategy.GasPrice(store, domainID) } func (c *Consensus) FilterLocalConversionRateData(store *store.ConversionRateStore, base, foreign string) (*types.ConversionRate, error) { diff --git a/consensus/strategy/average.go b/consensus/strategy/average.go index 978955b..e43cf2a 100644 --- a/consensus/strategy/average.go +++ b/consensus/strategy/average.go @@ -22,8 +22,8 @@ func (a *Average) Name() string { return "average" } -func (a *Average) GasPrice(store *store.GasPriceStore, domainName string) (*types.GasPrices, error) { - re, err := store.GetGasPricesByDomain(domainName) +func (a *Average) GasPrice(store *store.GasPriceStore, domainID int) (*types.GasPrices, error) { + re, err := store.GetGasPricesByDomain(domainID) if err != nil { return nil, err } @@ -55,7 +55,7 @@ func (a *Average) GasPrice(store *store.GasPriceStore, domainName string) (*type SafeGasPrice: fmt.Sprintf("%d", int(math.Round(safe/dataSize))), ProposeGasPrice: fmt.Sprintf("%d", int(propose/dataSize)), FastGasPrice: fmt.Sprintf("%d", int(fast/dataSize)), - DomainName: domainName, + DomainID: domainID, Time: re[0].Time, // use the first data time for now }, nil diff --git a/consensus/strategy/average_test.go b/consensus/strategy/average_test.go index a02e612..75d2480 100644 --- a/consensus/strategy/average_test.go +++ b/consensus/strategy/average_test.go @@ -67,25 +67,25 @@ func (a *AverageStrategyTestSuite) TestConversionRate_With_Data() { var dataReceiver *types.ConversionRate result := make([]interface{}, 0) result = append(result, types.ConversionRate{ - Base: "eth", - Foreign: "matic", - Rate: 1000.0, - OracleName: "", - Time: 0, + Base: "eth", + Foreign: "matic", + Rate: 1000.0, + OracleSource: "", + Time: 0, }) result = append(result, types.ConversionRate{ - Base: "eth", - Foreign: "matic", - Rate: 2000.0, - OracleName: "", - Time: 0, + Base: "eth", + Foreign: "matic", + Rate: 2000.0, + OracleSource: "", + Time: 0, }) result = append(result, types.ConversionRate{ - Base: "eth", - Foreign: "matic", - Rate: 2100.0, - OracleName: "", - Time: 0, + Base: "eth", + Foreign: "matic", + Rate: 2100.0, + OracleSource: "", + Time: 0, }) a.db.EXPECT().GetByPrefix([]byte("conversionrate:"), dataReceiver).Return(result, nil) @@ -98,7 +98,7 @@ func (a *AverageStrategyTestSuite) TestGasPrice_DB_Error() { var dataReceiver *types.GasPrices a.db.EXPECT().GetByPrefix([]byte("gasprice:"), dataReceiver).Return(nil, errors.New("db error")) - re, err := a.strategy.GasPrice(a.gasPriceStore, "ethereum") + re, err := a.strategy.GasPrice(a.gasPriceStore, 1) a.Nil(re) a.EqualError(err, "db error") } @@ -108,7 +108,7 @@ func (a *AverageStrategyTestSuite) TestGasPrice_Without_Data() { var emptyResult []interface{} a.db.EXPECT().GetByPrefix([]byte("gasprice:"), dataReceiver).Return(emptyResult, nil) - re, err := a.strategy.GasPrice(a.gasPriceStore, "ethereum") + re, err := a.strategy.GasPrice(a.gasPriceStore, 1) a.Nil(re) a.EqualError(err, "no gas price data found") } @@ -120,29 +120,29 @@ func (a *AverageStrategyTestSuite) TestGasPrice_With_Data() { SafeGasPrice: "10", ProposeGasPrice: "15", FastGasPrice: "20", - OracleName: "", - DomainName: "ethereum", + OracleSource: "", + DomainID: 1, Time: 0, }) result = append(result, types.GasPrices{ SafeGasPrice: "20", ProposeGasPrice: "25", FastGasPrice: "30", - OracleName: "", - DomainName: "ethereum", + OracleSource: "", + DomainID: 1, Time: 0, }) result = append(result, types.GasPrices{ SafeGasPrice: "30", ProposeGasPrice: "35", FastGasPrice: "40", - OracleName: "", - DomainName: "ethereum", + OracleSource: "", + DomainID: 1, Time: 0, }) a.db.EXPECT().GetByPrefix([]byte("gasprice:"), dataReceiver).Return(result, nil) - re, err := a.strategy.GasPrice(a.gasPriceStore, "ethereum") + re, err := a.strategy.GasPrice(a.gasPriceStore, 1) a.Nil(err) a.EqualValues("20", re.SafeGasPrice) diff --git a/consensus/strategy/strategy.go b/consensus/strategy/strategy.go index 81366b1..5a9ebf6 100644 --- a/consensus/strategy/strategy.go +++ b/consensus/strategy/strategy.go @@ -10,6 +10,6 @@ import ( type Strategy interface { Name() string - GasPrice(*store.GasPriceStore, string) (*types.GasPrices, error) + GasPrice(*store.GasPriceStore, int) (*types.GasPrices, error) ConversionRate(*store.ConversionRateStore, string, string) (*types.ConversionRate, error) } diff --git a/cronjob/conversionRateJob.go b/cronjob/conversionRateJob.go index c62f168..61c355a 100644 --- a/cronjob/conversionRateJob.go +++ b/cronjob/conversionRateJob.go @@ -22,7 +22,7 @@ func ConversionRateJobOperation(c *Job) func() { for _, pricePair := range pricePairs { rateData, err := oracleOperator.Run(pricePair[0], pricePair[1]) if err != nil || rateData == nil { - c.log.Error(errors.Wrapf(err, "failed to fetch data from oracle: %s", oracleOperator.GetOracleName())) + c.log.Error(errors.Wrapf(err, "failed to fetch data from oracle: %s", oracleOperator.GetOracleSource())) continue } @@ -35,11 +35,11 @@ func ConversionRateJobOperation(c *Job) func() { } reverseRateData := &types.ConversionRate{ - Base: pricePair[1], - Foreign: pricePair[0], - Rate: 1 / rateData.Rate, - OracleName: rateData.OracleName, - Time: rateData.Time, + Base: pricePair[1], + Foreign: pricePair[0], + Rate: 1 / rateData.Rate, + OracleSource: rateData.OracleSource, + Time: rateData.Time, } err = c.cronBase.conversionRateStore.StoreConversionRate(reverseRateData) diff --git a/cronjob/conversionRateJob_test.go b/cronjob/conversionRateJob_test.go index f55207a..8a984fd 100644 --- a/cronjob/conversionRateJob_test.go +++ b/cronjob/conversionRateJob_test.go @@ -64,11 +64,11 @@ func (s *ConversionRateJobTestSuite) SetupTest() { s.db = mockStore.NewMockStore(gomockController) s.conversionRateStore = store.NewConversionRateStore(s.db) s.testdata = &types.ConversionRate{ - Base: "eth", - Foreign: "usd", - Rate: 3000, - OracleName: "cooinmarketcap", - Time: time.Time{}.UnixMilli(), + Base: "eth", + Foreign: "usd", + Rate: 3000, + OracleSource: "cooinmarketcap", + Time: time.Time{}.UnixMilli(), } conversionRateOracle := oracle.NewConversionRateOracleOperator(s.appBase.GetLogger(), s.oracle) @@ -93,7 +93,7 @@ func (s *ConversionRateJobTestSuite) TearDownTest() { func (s *ConversionRateJobTestSuite) TestJobOperation_Oracle_Disabled() { s.oracle.EXPECT().IsEnabled().Return(false) s.oracle.EXPECT().InquiryConversionRate(s.testdata.Base, s.testdata.Foreign).Return(nil, errors.New("error")).Times(0) - s.oracle.EXPECT().Name().Times(0) + s.oracle.EXPECT().Source().Times(0) cronjob.ConversionRateJobOperation(s.job)() } @@ -101,8 +101,8 @@ func (s *ConversionRateJobTestSuite) TestJobOperation_Oracle_Disabled() { func (s *ConversionRateJobTestSuite) TestJobOperation_Run_Failure() { s.oracle.EXPECT().IsEnabled().Return(true) s.oracle.EXPECT().InquiryConversionRate(s.testdata.Base, s.testdata.Foreign).Return(nil, errors.New("error")).Times(1) - s.oracle.EXPECT().Name().Return("test oracle").Times(1) - s.db.EXPECT().Set([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleName, s.testdata.Base, s.testdata.Foreign)), []byte("")).Return(nil).Times(0) + s.oracle.EXPECT().Source().Return("test oracle").Times(1) + s.db.EXPECT().Set([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleSource, s.testdata.Base, s.testdata.Foreign)), []byte("")).Return(nil).Times(0) cronjob.ConversionRateJobOperation(s.job)() } @@ -110,35 +110,35 @@ func (s *ConversionRateJobTestSuite) TestJobOperation_Run_Failure() { func (s *ConversionRateJobTestSuite) TestJobOperation_Run_Success_StoreConversionRate_Failure() { s.oracle.EXPECT().IsEnabled().Return(true) s.oracle.EXPECT().InquiryConversionRate(s.testdata.Base, s.testdata.Foreign).Return(s.testdata, nil).Times(1) - s.oracle.EXPECT().Name().Return("test oracle").Times(0) + s.oracle.EXPECT().Source().Return("test oracle").Times(0) dataBytes, err := json.Marshal(s.testdata) s.Nil(err) - s.db.EXPECT().Set([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleName, s.testdata.Base, s.testdata.Foreign)), dataBytes).Return(errors.New("error")).Times(1) + s.db.EXPECT().Set([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleSource, s.testdata.Base, s.testdata.Foreign)), dataBytes).Return(errors.New("error")).Times(1) cronjob.ConversionRateJobOperation(s.job)() } func (s *ConversionRateJobTestSuite) TestJobOperation_Run_Success_StoreConversionRate_Success() { s.oracle.EXPECT().IsEnabled().Return(true) s.oracle.EXPECT().InquiryConversionRate(s.testdata.Base, s.testdata.Foreign).Return(s.testdata, nil).Times(1) - s.oracle.EXPECT().Name().Return("test oracle").Times(0) + s.oracle.EXPECT().Source().Return("test oracle").Times(0) dataBytes, err := json.Marshal(s.testdata) s.Nil(err) - s.db.EXPECT().Set([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleName, s.testdata.Base, s.testdata.Foreign)), dataBytes).Return(nil).Times(1) + s.db.EXPECT().Set([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleSource, s.testdata.Base, s.testdata.Foreign)), dataBytes).Return(nil).Times(1) reverseData := &types.ConversionRate{ - Base: "usd", - Foreign: "eth", - Rate: 0.0003333333333333333, - OracleName: s.testdata.OracleName, - Time: s.testdata.Time, + Base: "usd", + Foreign: "eth", + Rate: 0.0003333333333333333, + OracleSource: s.testdata.OracleSource, + Time: s.testdata.Time, } reverseDataBytes, err := json.Marshal(reverseData) s.Nil(err) - s.db.EXPECT().Set([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleName, s.testdata.Foreign, s.testdata.Base)), reverseDataBytes).Return(nil).Times(1) + s.db.EXPECT().Set([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleSource, s.testdata.Foreign, s.testdata.Base)), reverseDataBytes).Return(nil).Times(1) cronjob.ConversionRateJobOperation(s.job)() } diff --git a/cronjob/gasPriceJob.go b/cronjob/gasPriceJob.go index 92444e2..6a1d1db 100644 --- a/cronjob/gasPriceJob.go +++ b/cronjob/gasPriceJob.go @@ -4,7 +4,6 @@ package cronjob import ( - "github.com/ChainSafe/sygma-fee-oracle/oracle" "github.com/pkg/errors" ) @@ -12,27 +11,24 @@ func GasPriceJobOperation(c *Job) func() { return func() { c.log.Debug("checking gas price") - // load gas price domains from config and run through each registered oracles + // run through each registered oracles and fetch gas price data for its associated supported domain for _, oracleOperator := range c.cronBase.gasPriceOracles { if !oracleOperator.IsOracleEnabled() { continue } - for _, domain := range c.cronBase.base.GetConfig().GasPriceDomains { - gasPriceData, err := oracleOperator.Run(domain) - if err != nil || gasPriceData == nil { - if err != oracle.ErrNotSupported { - c.log.Error(errors.Wrapf(err, "failed to fetch data from oracle: %s", oracleOperator.GetOracleName())) - } - continue - } - c.log.Debugf("gas price data: %+v\n", gasPriceData) + gasPriceData, err := oracleOperator.Run() + if err != nil || gasPriceData == nil { + c.log.Error(errors.Wrapf(err, "failed to fetch data from oracle: %s", oracleOperator.GetOracleSource())) + continue + } - err = c.cronBase.gasPriceStore.StoreGasPrice(gasPriceData) - if err != nil { - c.log.Error(errors.Wrap(err, "failed to store data into store")) - continue - } + c.log.Debugf("gas price data: %+v\n", gasPriceData) + + err = c.cronBase.gasPriceStore.StoreGasPrice(gasPriceData) + if err != nil { + c.log.Error(errors.Wrap(err, "failed to store data into store")) + continue } } } diff --git a/cronjob/gasPriceJob_test.go b/cronjob/gasPriceJob_test.go index d06770a..22a76cb 100644 --- a/cronjob/gasPriceJob_test.go +++ b/cronjob/gasPriceJob_test.go @@ -26,13 +26,14 @@ import ( type GasPriceJobTestSuite struct { suite.Suite - appBase *base.FeeOracleAppBase - gasPriceOperator *oracle.GasPriceOracleOperator - oracle *mockOracle.MockGasPriceOracle - gasPriceStore *store.GasPriceStore - db *mockStore.MockStore - job *cronjob.Job - testdata *types.GasPrices + appBase *base.FeeOracleAppBase + gasPriceOperator *oracle.GasPriceOracleOperator + oracle *mockOracle.MockGasPriceOracle + gasPriceStore *store.GasPriceStore + db *mockStore.MockStore + job *cronjob.Job + testdata *types.GasPrices + gasPriceDomainIds []int } func TestRunGasPriceJobTestSuite(t *testing.T) { @@ -67,14 +68,15 @@ func (s *GasPriceJobTestSuite) SetupTest() { SafeGasPrice: "1", ProposeGasPrice: "2", FastGasPrice: "3", - OracleName: "test oracle", - DomainName: "ethereum", + OracleSource: "etherscan", + DomainID: 1, Time: time.Now().UnixMilli(), } + s.gasPriceDomainIds = []int{1} gasPriceOracle := oracle.NewGasPriceOracleOperator(s.appBase.GetLogger(), s.oracle) gasPriceOracles := make(map[string]*oracle.GasPriceOracleOperator) - gasPriceOracles["test oracle"] = gasPriceOracle + gasPriceOracles["etherscan"] = gasPriceOracle gasPriceStore := store.NewGasPriceStore(s.db) @@ -93,41 +95,41 @@ func (s *GasPriceJobTestSuite) TearDownTest() { func (s *GasPriceJobTestSuite) TestJobOperation_Oracle_Disabled() { s.oracle.EXPECT().IsEnabled().Return(false) - s.oracle.EXPECT().InquiryGasPrice(s.testdata.DomainName).Return(nil, errors.New("error")).Times(0) - s.oracle.EXPECT().Name().Times(0) + s.oracle.EXPECT().InquiryGasPrice().Times(0) + s.oracle.EXPECT().Source().Times(0) cronjob.GasPriceJobOperation(s.job)() } func (s *GasPriceJobTestSuite) TestJobOperation_Run_Failure() { s.oracle.EXPECT().IsEnabled().Return(true) - s.oracle.EXPECT().InquiryGasPrice(s.testdata.DomainName).Return(nil, errors.New("error")).Times(1) - s.oracle.EXPECT().Name().Return("test oracle").Times(1) - s.db.EXPECT().Set([]byte(fmt.Sprintf("gasprice:%s:%s", s.testdata.OracleName, s.testdata.DomainName)), []byte("")).Return(nil).Times(0) + s.oracle.EXPECT().InquiryGasPrice().Return(nil, errors.New("error")).Times(1) + s.oracle.EXPECT().Source().Return("etherscan").Times(1) + s.db.EXPECT().Set([]byte(fmt.Sprintf("gasprice:%s:%d", s.testdata.OracleSource, s.testdata.DomainID)), []byte("")).Return(nil).Times(0) cronjob.GasPriceJobOperation(s.job)() } func (s *GasPriceJobTestSuite) TestJobOperation_Run_Success_StoreGasPrice_Failure() { s.oracle.EXPECT().IsEnabled().Return(true) - s.oracle.EXPECT().InquiryGasPrice(s.testdata.DomainName).Return(s.testdata, nil).Times(1) - s.oracle.EXPECT().Name().Return("test oracle").Times(0) + s.oracle.EXPECT().InquiryGasPrice().Return(s.testdata, nil).Times(1) + s.oracle.EXPECT().Source().Return("etherscan").Times(0) dataBytes, err := json.Marshal(s.testdata) s.Nil(err) - s.db.EXPECT().Set([]byte(fmt.Sprintf("gasprice:%s:%s", s.testdata.OracleName, s.testdata.DomainName)), dataBytes).Return(errors.New("error")).Times(1) + s.db.EXPECT().Set([]byte(fmt.Sprintf("gasprice:%s:%d", s.testdata.OracleSource, s.testdata.DomainID)), dataBytes).Return(errors.New("error")).Times(1) cronjob.GasPriceJobOperation(s.job)() } func (s *GasPriceJobTestSuite) TestJobOperation_Run_Success_StoreGasPrice_Success() { s.oracle.EXPECT().IsEnabled().Return(true) - s.oracle.EXPECT().InquiryGasPrice(s.testdata.DomainName).Return(s.testdata, nil).Times(1) - s.oracle.EXPECT().Name().Return("test oracle").Times(0) + s.oracle.EXPECT().InquiryGasPrice().Return(s.testdata, nil).Times(1) + s.oracle.EXPECT().Source().Return("etherscan").Times(0) dataBytes, err := json.Marshal(s.testdata) s.Nil(err) - s.db.EXPECT().Set([]byte(fmt.Sprintf("gasprice:%s:%s", s.testdata.OracleName, s.testdata.DomainName)), dataBytes).Return(nil).Times(1) + s.db.EXPECT().Set([]byte(fmt.Sprintf("gasprice:%s:%d", s.testdata.OracleSource, s.testdata.DomainID)), dataBytes).Return(nil).Times(1) cronjob.GasPriceJobOperation(s.job)() } diff --git a/e2e/config/config.yaml b/e2e/config/config.yaml index a717c04..7bd6943 100644 --- a/e2e/config/config.yaml +++ b/e2e/config/config.yaml @@ -33,25 +33,38 @@ cron_job: store: path: ./e2e/testdb -oracle: - etherscan: - enable: true - api_key: 473P55YZMBNSRR63YF78D2PMMIUYUQJMGY - apis: - gas_price: https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey= - polygonscan: - enable: true - api_key: BZM7P395BQS1YFKQMA2AK2ACQKWGEW4JB3 - apis: - gas_price: https://api.polygonscan.com/api?module=gastracker&action=gasoracle&apikey= - coinmarketcap: +conversion_rate_apis: + - implementation: coinmarketcap + source: coinmarketcap enable: true + url: https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest? api_key: 1408daf0-0777-4916-9fe4-20da5ee77560 - apis: - query_rate: https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest? -gas_price_domains: - [ ethereum, polygon ] +domain_list: + - domain_id: 0 + gas_price_apis: + - implementation: etherscan + source: etherscan + enable: true + url: https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey= + api_key: BZM7P395BQS1YFKQMA2AK2ACQKWGEW4JB3 + decimals: 9 + - domain_id: 1 + gas_price_apis: + - implementation: etherscan + source: polygonscan + enable: true + url: https://api.polygonscan.com/api?module=gastracker&action=gasoracle&apikey= + api_key: BZM7P395BQS1YFKQMA2AK2ACQKWGEW4JB3 + decimals: 9 + - domain_id: 2 + gas_price_apis: + - implementation: moonscan + source: moonscan + enable: true + url: https://api-moonbeam.moonscan.io/api?module=proxy&action=eth_gasPrice&apikey= + api_key: BZM7P395BQS1YFKQMA2AK2ACQKWGEW4JB3 + decimals: # conversion_rate_pairs contains price pair for conversion rate # must be paired and follow the format of [ base, foreign, base, foreign, ... ] @@ -63,4 +76,4 @@ strategy: local: average # data_valid_interval defines how long the endpoint response data remains valid before sending it to fee handler contract -data_valid_interval: 3600 # second \ No newline at end of file +data_valid_interval: 3600 # second diff --git a/e2e/setup/setup.go b/e2e/setup/setup.go index 7c0879c..c6097a5 100644 --- a/e2e/setup/setup.go +++ b/e2e/setup/setup.go @@ -212,11 +212,11 @@ func DataPrepare(path string) error { dataKey1 := bytes.Buffer{} dataKey1.WriteString(fmt.Sprintf("%s%s:%s:%s", "conversionrate:", "coinmarketcap", "matic", "eth")) dataValue1 := &types.ConversionRate{ - Base: "matic", - Foreign: "eth", - Rate: 0.000445, - OracleName: "coinmarketcap", - Time: time.Now().Unix(), + Base: "matic", + Foreign: "eth", + Rate: 0.000445, + OracleSource: "coinmarketcap", + Time: time.Now().Unix(), } data1, err := json.Marshal(dataValue1) if err != nil { @@ -230,11 +230,11 @@ func DataPrepare(path string) error { dataKey2 := bytes.Buffer{} dataKey2.WriteString(fmt.Sprintf("%s%s:%s:%s", "conversionrate:", "coinmarketcap", "matic", "usdt")) dataValue2 := &types.ConversionRate{ - Base: "matic", - Foreign: "usdt", - Rate: 8.948864, - OracleName: "coinmarketcap", - Time: time.Now().Unix(), + Base: "matic", + Foreign: "usdt", + Rate: 8.948864, + OracleSource: "coinmarketcap", + Time: time.Now().Unix(), } data2, err := json.Marshal(dataValue2) if err != nil { @@ -251,8 +251,8 @@ func DataPrepare(path string) error { SafeGasPrice: "9000000000", ProposeGasPrice: "0", FastGasPrice: "0", - OracleName: "polygonscan", - DomainName: "polygon", + OracleSource: "polygonscan", + DomainID: 1, Time: time.Now().Unix(), } data3, err := json.Marshal(dataValue3) diff --git a/go.mod b/go.mod index 4e20d12..70f131b 100644 --- a/go.mod +++ b/go.mod @@ -59,9 +59,11 @@ require ( github.com/minio/sha256-simd v1.0.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rjeczalik/notify v0.9.1 // indirect + github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cast v1.4.1 // indirect @@ -72,8 +74,10 @@ require ( github.com/tklauser/numcpus v0.2.2 // indirect github.com/ugorji/go/codec v1.1.7 // indirect golang.org/x/crypto v0.4.0 // indirect + golang.org/x/mod v0.7.0 // indirect golang.org/x/sys v0.3.0 // indirect golang.org/x/text v0.5.0 // indirect + golang.org/x/tools v0.3.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect diff --git a/go.sum b/go.sum index b807910..6132c2a 100644 --- a/go.sum +++ b/go.sum @@ -155,6 +155,7 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= @@ -444,6 +445,7 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -454,6 +456,7 @@ github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/libp2p/go-libp2p v0.26.2 h1:eHEoW/696FP7/6DxOvcrKfTD6Bi0DExxiMSZUJxswA0= github.com/libp2p/go-libp2p v0.26.2/go.mod h1:x75BN32YbwuY0Awm2Uix4d4KOz+/4piInkp4Wr3yOo8= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= @@ -511,10 +514,13 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -522,11 +528,13 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -581,6 +589,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989 h1:rq2/kILQnPtq5oL4+IAjgVOjh5e2yj2aaCYi7squEvI= +github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989/go.mod h1:i9l/TNj+yDFh9SZXUTvspXTjbFXgZGP/UvhU1S65A4A= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -615,6 +625,7 @@ github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57N github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -730,6 +741,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -954,6 +967,7 @@ golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200331202046-9d5940d49312/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -974,6 +988,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/oracle/base.go b/oracle/base.go index 5cc46c2..db43947 100644 --- a/oracle/base.go +++ b/oracle/base.go @@ -13,6 +13,7 @@ var ( ) type Oracle interface { - Name() string + // Source of the oracle. Because one oracle implementation can be used for different oracle sources + Source() string IsEnabled() bool } diff --git a/oracle/coinMarketCap.go b/oracle/coinMarketCap.go index 275fd98..c63d9d4 100644 --- a/oracle/coinMarketCap.go +++ b/oracle/coinMarketCap.go @@ -25,7 +25,7 @@ var _ ConversionRateOracle = (*CoinMarketCap)(nil) type CoinMarketCap struct { log *logrus.Entry - name string + source string apiKey string enable bool apis CoinMarketCapApis @@ -51,14 +51,14 @@ type CoinMarketCapConversionRateQuote struct { LastUpdated string `mapstructure:"last_updated"` } -func NewCoinMarketCap(conf *config.Config, log *logrus.Entry) *CoinMarketCap { +func NewCoinMarketCap(source string, apiService config.ApiService, log *logrus.Entry) *CoinMarketCap { return &CoinMarketCap{ - log: log.WithField("services", "coinMarketCap"), - name: "coinmarketcap", - apiKey: conf.OracleConfig().CoinMarketCap.ApiKey, - enable: conf.OracleConfig().CoinMarketCap.Enable, + log: log.WithField("services", source), + source: source, + apiKey: apiService.ApiKey, + enable: apiService.Enable, apis: CoinMarketCapApis{ - ConversionRateRequest: conf.OracleConfig().CoinMarketCap.Apis.QueryRate, + ConversionRateRequest: apiService.URL, }, } } @@ -107,16 +107,16 @@ func (c *CoinMarketCap) InquiryConversionRate(baseCurrency, foreignCurrency stri } return &types.ConversionRate{ - Base: baseCurrency, - Foreign: foreignCurrency, - Rate: cmccr.Price, - OracleName: c.name, - Time: time.Now().Unix(), + Base: baseCurrency, + Foreign: foreignCurrency, + Rate: cmccr.Price, + OracleSource: c.source, + Time: time.Now().Unix(), }, nil } -func (c *CoinMarketCap) Name() string { - return c.name +func (c *CoinMarketCap) Source() string { + return c.source } func (c *CoinMarketCap) IsEnabled() bool { diff --git a/oracle/conversionRateOracle.go b/oracle/conversionRateOracle.go index 1e29014..4f25514 100644 --- a/oracle/conversionRateOracle.go +++ b/oracle/conversionRateOracle.go @@ -30,8 +30,8 @@ func (c *ConversionRateOracleOperator) Run(baseCurrency, foreignCurrency string) return c.oracle.InquiryConversionRate(baseCurrency, foreignCurrency) } -func (c *ConversionRateOracleOperator) GetOracleName() string { - return c.oracle.Name() +func (c *ConversionRateOracleOperator) GetOracleSource() string { + return c.oracle.Source() } func (c *ConversionRateOracleOperator) IsOracleEnabled() bool { diff --git a/oracle/conversionRateOracle_test.go b/oracle/conversionRateOracle_test.go index 3b124aa..fb5c0bf 100644 --- a/oracle/conversionRateOracle_test.go +++ b/oracle/conversionRateOracle_test.go @@ -53,11 +53,11 @@ func (s *ConversionRateOracleTestSuite) SetupTest() { s.appBase = base.NewFeeOracleAppBase("../config/config.template.yaml", "../domain.json", "./keyfile.priv", "secp256k1") s.conversionRateOperator = oracle.NewConversionRateOracleOperator(s.appBase.GetLogger(), s.oracle) s.testdata = &types.ConversionRate{ - Base: "eth", - Foreign: "usdt", - Rate: 3000, - OracleName: "cooinmarketcap", - Time: time.Time{}.UnixMilli(), + Base: "eth", + Foreign: "usdt", + Rate: 3000, + OracleSource: "cooinmarketcap", + Time: time.Time{}.UnixMilli(), } } diff --git a/oracle/etherscan.go b/oracle/etherscan.go index 1a1069b..04207c6 100644 --- a/oracle/etherscan.go +++ b/oracle/etherscan.go @@ -6,15 +6,13 @@ package oracle import ( "encoding/json" "fmt" - "math/big" + "github.com/ChainSafe/sygma-fee-oracle/util" "net/http" - "strings" "time" "github.com/ChainSafe/sygma-fee-oracle/config" "github.com/ChainSafe/sygma-fee-oracle/oracle/client" "github.com/ChainSafe/sygma-fee-oracle/types" - "github.com/ChainSafe/sygma-fee-oracle/util" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -25,10 +23,12 @@ var _ GasPriceOracle = (*Etherscan)(nil) type Etherscan struct { log *logrus.Entry - name string - apiKey string - enable bool - apis EtherscanApis + source string + apiKey string + enable bool + apis EtherscanApis + domainID int + gasPriceDecimals int } type EtherscanApis struct { @@ -50,23 +50,21 @@ type EtherscanGasPricesResp struct { SuggestBaseFee string `mapstructure:"suggestBaseFee"` } -func NewEtherscan(conf *config.Config, log *logrus.Entry) *Etherscan { +func NewEtherscan(source string, apiService config.ApiService, domainID int, log *logrus.Entry) *Etherscan { return &Etherscan{ - log: log.WithField("services", "etherscan"), - name: "etherscan", - apiKey: conf.OracleConfig().Etherscan.ApiKey, - enable: conf.OracleConfig().Etherscan.Enable, + log: log.WithField("services", source), + source: source, + apiKey: apiService.ApiKey, + enable: apiService.Enable, apis: EtherscanApis{ - GasPriceRequest: fmt.Sprintf("%s%s", conf.OracleConfig().Etherscan.Apis.GasPriceApiUrl, conf.OracleConfig().Etherscan.ApiKey), + GasPriceRequest: fmt.Sprintf("%s%s", apiService.URL, apiService.ApiKey), }, + domainID: domainID, + gasPriceDecimals: apiService.Decimals, } } -func (e *Etherscan) InquiryGasPrice(domainName string) (*types.GasPrices, error) { - if strings.ToLower(domainName) != "ethereum" { - return nil, ErrNotSupported - } - +func (e *Etherscan) InquiryGasPrice() (*types.GasPrices, error) { statusCode, body, err := client.NewHttpRequestMessage(http.MethodGet, e.apis.GasPriceRequest, nil, nil, e.log).Request() if err != nil || statusCode != http.StatusOK { @@ -91,31 +89,32 @@ func (e *Etherscan) InquiryGasPrice(domainName string) (*types.GasPrices, error) return nil, errors.Wrap(err, "failed to decode gas price response") } - safeGasPriceValue, err := util.Str2BigInt(egp.SafeGasPrice) + // convert gas price data to wei in bigInt + safeGasPriceValue, err := util.Large2SmallUnitConverter(egp.SafeGasPrice, uint(e.gasPriceDecimals)) if err != nil { - return nil, errors.Wrap(err, "failed to convert gas price response value") + return nil, errors.Wrap(err, "failed to convert safe gasprice") } - proposeGasPriceValue, err := util.Str2BigInt(egp.ProposeGasPrice) + proposeGasPriceValue, err := util.Large2SmallUnitConverter(egp.ProposeGasPrice, uint(e.gasPriceDecimals)) if err != nil { - return nil, errors.Wrap(err, "failed to convert gas price response value") + return nil, errors.Wrap(err, "failed to convert propose gasprice") } - fastGasPriceValue, err := util.Str2BigInt(egp.FastGasPrice) + fastGasPriceValue, err := util.Large2SmallUnitConverter(egp.FastGasPrice, uint(e.gasPriceDecimals)) if err != nil { - return nil, errors.Wrap(err, "failed to convert gas price response value") + return nil, errors.Wrap(err, "failed to convert fast gasprice") } return &types.GasPrices{ - SafeGasPrice: new(big.Int).Mul(safeGasPriceValue, big.NewInt(types.GWei)).String(), - ProposeGasPrice: new(big.Int).Mul(proposeGasPriceValue, big.NewInt(types.GWei)).String(), - FastGasPrice: new(big.Int).Mul(fastGasPriceValue, big.NewInt(types.GWei)).String(), - OracleName: e.name, - DomainName: domainName, + SafeGasPrice: safeGasPriceValue.String(), + ProposeGasPrice: proposeGasPriceValue.String(), + FastGasPrice: fastGasPriceValue.String(), + OracleSource: e.source, + DomainID: e.domainID, Time: time.Now().Unix(), }, nil } -func (e *Etherscan) Name() string { - return e.name +func (e *Etherscan) Source() string { + return e.source } func (e *Etherscan) IsEnabled() bool { diff --git a/oracle/gasPriceOracle.go b/oracle/gasPriceOracle.go index dac8592..4942aa3 100644 --- a/oracle/gasPriceOracle.go +++ b/oracle/gasPriceOracle.go @@ -10,9 +10,11 @@ import ( type GasPriceOracle interface { Oracle - InquiryGasPrice(chainDomain string) (*types.GasPrices, error) + InquiryGasPrice() (*types.GasPrices, error) } +type GasPriceConverter func(gasPrices *types.GasPrices) (*types.GasPrices, error) + type GasPriceOracleOperator struct { log *logrus.Entry @@ -26,14 +28,18 @@ func NewGasPriceOracleOperator(log *logrus.Entry, oracle GasPriceOracle) *GasPri } } -func (g *GasPriceOracleOperator) Run(chainDomain string) (*types.GasPrices, error) { - return g.oracle.InquiryGasPrice(chainDomain) +func (g *GasPriceOracleOperator) Run() (*types.GasPrices, error) { + return g.oracle.InquiryGasPrice() } -func (g *GasPriceOracleOperator) GetOracleName() string { - return g.oracle.Name() +func (g *GasPriceOracleOperator) GetOracleSource() string { + return g.oracle.Source() } func (g *GasPriceOracleOperator) IsOracleEnabled() bool { return g.oracle.IsEnabled() } + +func (g *GasPriceOracleOperator) GetOracle() GasPriceOracle { + return g.oracle +} diff --git a/oracle/gasPriceOracle_test.go b/oracle/gasPriceOracle_test.go index 8e2d102..f0e73bd 100644 --- a/oracle/gasPriceOracle_test.go +++ b/oracle/gasPriceOracle_test.go @@ -57,8 +57,8 @@ func (s *GasPriceOracleTestSuite) SetupTest() { SafeGasPrice: "1", ProposeGasPrice: "2", FastGasPrice: "3", - OracleName: "etherscan", - DomainName: "ethereum", + OracleSource: "etherscan", + DomainID: 1, Time: time.Now().UnixMilli(), } } @@ -68,17 +68,18 @@ func (s *GasPriceOracleTestSuite) TearDownTest() { } func (s *GasPriceOracleTestSuite) TestInquiryGasPrice_Failure() { - s.oracle.EXPECT().InquiryGasPrice(s.testdata.DomainName).Return(nil, errors.New("error")) + s.oracle.EXPECT().InquiryGasPrice().Return(nil, errors.New("error")) - _, err := s.gasPriceOperator.Run(s.testdata.DomainName) + _, err := s.gasPriceOperator.Run() s.NotNil(err) } func (s *GasPriceOracleTestSuite) TestInquiryGasPrice_Success() { - s.oracle.EXPECT().InquiryGasPrice(s.testdata.DomainName).Return(s.testdata, nil) + s.oracle.EXPECT().InquiryGasPrice().Return(s.testdata, nil) + s.oracle.EXPECT().Source().Return("etherscan").Times(0) - _, err := s.gasPriceOperator.Run(s.testdata.DomainName) + _, err := s.gasPriceOperator.Run() s.Nil(err) } diff --git a/oracle/mock/oracle.go b/oracle/mock/oracle.go index 9af2ac6..c259d75 100644 --- a/oracle/mock/oracle.go +++ b/oracle/mock/oracle.go @@ -35,18 +35,18 @@ func (m *MockGasPriceOracle) EXPECT() *MockGasPriceOracleMockRecorder { } // InquiryGasPrice mocks base method. -func (m *MockGasPriceOracle) InquiryGasPrice(arg0 string) (*types.GasPrices, error) { +func (m *MockGasPriceOracle) InquiryGasPrice() (*types.GasPrices, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "InquiryGasPrice", arg0) + ret := m.ctrl.Call(m, "InquiryGasPrice") ret0, _ := ret[0].(*types.GasPrices) ret1, _ := ret[1].(error) return ret0, ret1 } // InquiryGasPrice indicates an expected call of InquiryGasPrice. -func (mr *MockGasPriceOracleMockRecorder) InquiryGasPrice(arg0 interface{}) *gomock.Call { +func (mr *MockGasPriceOracleMockRecorder) InquiryGasPrice() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InquiryGasPrice", reflect.TypeOf((*MockGasPriceOracle)(nil).InquiryGasPrice), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InquiryGasPrice", reflect.TypeOf((*MockGasPriceOracle)(nil).InquiryGasPrice)) } // IsEnabled mocks base method. @@ -63,18 +63,18 @@ func (mr *MockGasPriceOracleMockRecorder) IsEnabled() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsEnabled", reflect.TypeOf((*MockGasPriceOracle)(nil).IsEnabled)) } -// Name mocks base method. -func (m *MockGasPriceOracle) Name() string { +// Source mocks base method. +func (m *MockGasPriceOracle) Source() string { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Name") + ret := m.ctrl.Call(m, "Source") ret0, _ := ret[0].(string) return ret0 } -// Name indicates an expected call of Name. -func (mr *MockGasPriceOracleMockRecorder) Name() *gomock.Call { +// Source indicates an expected call of Source. +func (mr *MockGasPriceOracleMockRecorder) Source() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockGasPriceOracle)(nil).Name)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Source", reflect.TypeOf((*MockGasPriceOracle)(nil).Source)) } // MockConversionRateOracle is a mock of ConversionRateOracle interface. @@ -129,16 +129,16 @@ func (mr *MockConversionRateOracleMockRecorder) IsEnabled() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsEnabled", reflect.TypeOf((*MockConversionRateOracle)(nil).IsEnabled)) } -// Name mocks base method. -func (m *MockConversionRateOracle) Name() string { +// Source mocks base method. +func (m *MockConversionRateOracle) Source() string { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Name") + ret := m.ctrl.Call(m, "Source") ret0, _ := ret[0].(string) return ret0 } -// Name indicates an expected call of Name. -func (mr *MockConversionRateOracleMockRecorder) Name() *gomock.Call { +// Source indicates an expected call of Source. +func (mr *MockConversionRateOracleMockRecorder) Source() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockConversionRateOracle)(nil).Name)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Source", reflect.TypeOf((*MockConversionRateOracle)(nil).Source)) } diff --git a/oracle/moonscan.go b/oracle/moonscan.go index c7126e2..d573383 100644 --- a/oracle/moonscan.go +++ b/oracle/moonscan.go @@ -8,7 +8,6 @@ import ( "fmt" "math/big" "net/http" - "strings" "time" "github.com/ChainSafe/sygma-fee-oracle/config" @@ -23,10 +22,11 @@ var _ GasPriceOracle = (*Moonscan)(nil) type Moonscan struct { log *logrus.Entry - name string - apiKey string - enable bool - apis MoonscanApis + source string + apiKey string + enable bool + apis MoonscanApis + domainID int } type MoonscanApis struct { @@ -39,23 +39,20 @@ type MoonscanResp struct { Result string `json:"result"` } -func NewMoonscan(conf *config.Config, log *logrus.Entry) *Moonscan { +func NewMoonscan(source string, apiService config.ApiService, domainID int, log *logrus.Entry) *Moonscan { return &Moonscan{ - log: log.WithField("services", "moonscan"), - name: "moonscan", - apiKey: conf.OracleConfig().Moonscan.ApiKey, - enable: conf.OracleConfig().Moonscan.Enable, + log: log.WithField("services", source), + source: source, + apiKey: apiService.ApiKey, + enable: apiService.Enable, apis: MoonscanApis{ - GasPriceRequest: fmt.Sprintf("%s%s", conf.OracleConfig().Moonscan.Apis.GasPriceApiUrl, conf.OracleConfig().Moonscan.ApiKey), + GasPriceRequest: fmt.Sprintf("%s%s", apiService.URL, apiService.ApiKey), }, + domainID: domainID, } } -func (m *Moonscan) InquiryGasPrice(domainName string) (*types.GasPrices, error) { - if strings.ToLower(domainName) != "moonbeam" { - return nil, ErrNotSupported - } - +func (m *Moonscan) InquiryGasPrice() (*types.GasPrices, error) { // Moonscan doesn't support GasTracker API like Etherscan and Polygonscan does, // so we will use eth_gasPrice RPC call here // the JSON2.0 response will be different: if Message is empty, it means a successful call @@ -86,14 +83,14 @@ func (m *Moonscan) InquiryGasPrice(domainName string) (*types.GasPrices, error) SafeGasPrice: gp.String(), ProposeGasPrice: gp.String(), FastGasPrice: gp.String(), - OracleName: m.name, - DomainName: domainName, + OracleSource: m.source, + DomainID: m.domainID, Time: time.Now().Unix(), }, nil } -func (m *Moonscan) Name() string { - return m.name +func (m *Moonscan) Source() string { + return m.source } func (m *Moonscan) IsEnabled() bool { diff --git a/oracle/polygonscan.go b/oracle/polygonscan.go deleted file mode 100644 index 8826426..0000000 --- a/oracle/polygonscan.go +++ /dev/null @@ -1,130 +0,0 @@ -// The Licensed Work is (c) 2022 Sygma -// SPDX-License-Identifier: BUSL-1.1 - -package oracle - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - "time" - - "github.com/ChainSafe/sygma-fee-oracle/config" - "github.com/ChainSafe/sygma-fee-oracle/oracle/client" - "github.com/ChainSafe/sygma-fee-oracle/types" - "github.com/ChainSafe/sygma-fee-oracle/util" - "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -var _ GasPriceOracle = (*Polygonscan)(nil) - -type Polygonscan struct { - log *logrus.Entry - - name string - apiKey string - enable bool - apis PolygonscanApis -} - -type PolygonscanApis struct { - GasPriceRequest string -} - -type PolygonscanResp struct { - Statue string `json:"statue"` - Message string `json:"message"` - Result interface{} `json:"result"` -} - -type PolygonscanGasPricesResp struct { - FastGasPrice string `mapstructure:"FastGasPrice"` - LastBlock string `mapstructure:"LastBlock"` - ProposeGasPrice string `mapstructure:"ProposeGasPrice"` - SafeGasPrice string `mapstructure:"SafeGasPrice"` - GasUsedRatio string `mapstructure:"gasUsedRatio"` - SuggestBaseFee string `mapstructure:"suggestBaseFee"` -} - -func NewPolygonscan(conf *config.Config, log *logrus.Entry) *Polygonscan { - return &Polygonscan{ - log: log.WithField("services", "polygonscan"), - name: "polygonscan", - apiKey: conf.OracleConfig().Polygonscan.ApiKey, - enable: conf.OracleConfig().Polygonscan.Enable, - apis: PolygonscanApis{ - GasPriceRequest: fmt.Sprintf("%s%s", conf.OracleConfig().Polygonscan.Apis.GasPriceApiUrl, conf.OracleConfig().Polygonscan.ApiKey), - }, - } -} - -func (p *Polygonscan) InquiryGasPrice(domainName string) (*types.GasPrices, error) { - if strings.ToLower(domainName) != "polygon" { - return nil, ErrNotSupported - } - - statusCode, body, err := client.NewHttpRequestMessage(http.MethodGet, p.apis.GasPriceRequest, - nil, nil, p.log).Request() - if err != nil || statusCode != http.StatusOK { - p.log.Errorf("query gas price err: %v, statusCode: %d", err, statusCode) - return nil, errors.Wrap(err, "failed to query gas price") - } - - pr := &PolygonscanResp{} - prResult, err := pr.parsePolygonscanResp(body) - if err != nil { - p.log.Errorf("unmarshal response body err: %s ", err.Error()) - return nil, errors.Wrap(err, "failed to unmarshal resp body of querying gas price") - } - - if prResult.Message != "OK" { - return nil, errors.Errorf("failed to fetch gas price: %s", prResult.Result) - } - - var pgp PolygonscanGasPricesResp - err = mapstructure.Decode(pr.Result, &pgp) - if err != nil { - return nil, errors.Wrap(err, "failed to decode gas price response") - } - - safeGasPriceValue, err := util.Large2SmallUnitConverter(pgp.SafeGasPrice, types.GWeiDecimal) - if err != nil { - return nil, errors.Wrap(err, "failed to convert safe gasprice") - } - proposeGasPriceValue, err := util.Large2SmallUnitConverter(pgp.ProposeGasPrice, types.GWeiDecimal) - if err != nil { - return nil, errors.Wrap(err, "failed to convert propose gasprice") - } - fastGasPriceValue, err := util.Large2SmallUnitConverter(pgp.FastGasPrice, types.GWeiDecimal) - if err != nil { - return nil, errors.Wrap(err, "failed to convert fast gasprice") - } - - return &types.GasPrices{ - SafeGasPrice: safeGasPriceValue.String(), - ProposeGasPrice: proposeGasPriceValue.String(), - FastGasPrice: fastGasPriceValue.String(), - OracleName: p.name, - DomainName: domainName, - Time: time.Now().Unix(), - }, nil -} - -func (p *Polygonscan) Name() string { - return p.name -} - -func (p *Polygonscan) IsEnabled() bool { - return p.enable -} - -func (pr *PolygonscanResp) parsePolygonscanResp(body []byte) (PolygonscanResp, error) { - err := json.Unmarshal(body, pr) - if err != nil { - return PolygonscanResp{}, err - } - return *pr, nil -} diff --git a/store/conversionRateStore.go b/store/conversionRateStore.go index fbf60ee..0934ae6 100644 --- a/store/conversionRateStore.go +++ b/store/conversionRateStore.go @@ -32,11 +32,11 @@ func (c *ConversionRateStore) StoreConversionRate(conversionRate *types.Conversi if err != nil { return err } - return c.db.Set(c.storeKeyFormat(conversionRate.OracleName, conversionRate.Base, conversionRate.Foreign), data) + return c.db.Set(c.storeKeyFormat(conversionRate.OracleSource, conversionRate.Base, conversionRate.Foreign), data) } -func (c *ConversionRateStore) GetConversionRate(oracleName, baseCurrency, foreignCurrency string) (*types.ConversionRate, error) { - data, err := c.db.Get(c.storeKeyFormat(oracleName, baseCurrency, foreignCurrency)) +func (c *ConversionRateStore) GetConversionRate(OracleSource, baseCurrency, foreignCurrency string) (*types.ConversionRate, error) { + data, err := c.db.Get(c.storeKeyFormat(OracleSource, baseCurrency, foreignCurrency)) if err != nil { if errors.Is(err, leveldb.ErrNotFound) { return nil, ErrNotFound @@ -78,9 +78,9 @@ func (c *ConversionRateStore) GetConversionRatesByCurrencyPair(base, foreign str return re, nil } -func (c *ConversionRateStore) storeKeyFormat(oracleName, baseCurrency, foreignCurrency string) []byte { +func (c *ConversionRateStore) storeKeyFormat(oracleSource, baseCurrency, foreignCurrency string) []byte { key := bytes.Buffer{} - key.WriteString(fmt.Sprintf("%s%s:%s:%s", conversionRateStoreKeyPrefix, strings.ToLower(oracleName), + key.WriteString(fmt.Sprintf("%s%s:%s:%s", conversionRateStoreKeyPrefix, strings.ToLower(oracleSource), strings.ToLower(baseCurrency), strings.ToLower(foreignCurrency))) return key.Bytes() diff --git a/store/conversionRateStore_test.go b/store/conversionRateStore_test.go index 07464e2..aa3c3f6 100644 --- a/store/conversionRateStore_test.go +++ b/store/conversionRateStore_test.go @@ -36,11 +36,11 @@ func (s *ConversionRateStoreTestSuite) SetupTest() { s.db = mockStore.NewMockStore(gomockController) s.conversionRateStore = store.NewConversionRateStore(s.db) s.testdata = &types.ConversionRate{ - Base: "eth", - Foreign: "usdt", - Rate: 3000, - OracleName: "cooinmarketcap", - Time: time.Time{}.UnixMilli(), + Base: "eth", + Foreign: "usdt", + Rate: 3000, + OracleSource: "cooinmarketcap", + Time: time.Time{}.UnixMilli(), } } @@ -50,7 +50,7 @@ func (s *ConversionRateStoreTestSuite) TestStoreConversionRate_Failure() { dataBytes, err := json.Marshal(s.testdata) s.Nil(err) - s.db.EXPECT().Set([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleName, s.testdata.Base, s.testdata.Foreign)), dataBytes).Return(errors.New("error")) + s.db.EXPECT().Set([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleSource, s.testdata.Base, s.testdata.Foreign)), dataBytes).Return(errors.New("error")) err = s.conversionRateStore.StoreConversionRate(s.testdata) @@ -61,7 +61,7 @@ func (s *ConversionRateStoreTestSuite) TestStoreConversionRate_Success() { dataBytes, err := json.Marshal(s.testdata) s.Nil(err) - s.db.EXPECT().Set([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleName, s.testdata.Base, s.testdata.Foreign)), dataBytes).Return(nil) + s.db.EXPECT().Set([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleSource, s.testdata.Base, s.testdata.Foreign)), dataBytes).Return(nil) err = s.conversionRateStore.StoreConversionRate(s.testdata) @@ -69,9 +69,9 @@ func (s *ConversionRateStoreTestSuite) TestStoreConversionRate_Success() { } func (s *ConversionRateStoreTestSuite) TestGetConversionRate_Failure() { - s.db.EXPECT().Get([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleName, s.testdata.Base, s.testdata.Foreign))).Return(nil, errors.New("error")) + s.db.EXPECT().Get([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleSource, s.testdata.Base, s.testdata.Foreign))).Return(nil, errors.New("error")) - _, err := s.conversionRateStore.GetConversionRate(s.testdata.OracleName, s.testdata.Base, s.testdata.Foreign) + _, err := s.conversionRateStore.GetConversionRate(s.testdata.OracleSource, s.testdata.Base, s.testdata.Foreign) s.NotNil(err) } @@ -80,17 +80,17 @@ func (s *ConversionRateStoreTestSuite) TestGetConversionRate_Success() { dataBytes, err := json.Marshal(s.testdata) s.Nil(err) - s.db.EXPECT().Get([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleName, s.testdata.Base, s.testdata.Foreign))).Return(dataBytes, nil) + s.db.EXPECT().Get([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleSource, s.testdata.Base, s.testdata.Foreign))).Return(dataBytes, nil) - _, err = s.conversionRateStore.GetConversionRate(s.testdata.OracleName, s.testdata.Base, s.testdata.Foreign) + _, err = s.conversionRateStore.GetConversionRate(s.testdata.OracleSource, s.testdata.Base, s.testdata.Foreign) s.Nil(err) } func (s *ConversionRateStoreTestSuite) TestGetConversionRate_NotFound() { - s.db.EXPECT().Get([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleName, s.testdata.Base, s.testdata.Foreign))).Return(nil, store.ErrNotFound) + s.db.EXPECT().Get([]byte(fmt.Sprintf("conversionrate:%s:%s:%s", s.testdata.OracleSource, s.testdata.Base, s.testdata.Foreign))).Return(nil, store.ErrNotFound) - _, err := s.conversionRateStore.GetConversionRate(s.testdata.OracleName, s.testdata.Base, s.testdata.Foreign) + _, err := s.conversionRateStore.GetConversionRate(s.testdata.OracleSource, s.testdata.Base, s.testdata.Foreign) s.EqualError(err, store.ErrNotFound.Error()) } @@ -109,11 +109,11 @@ func (s *ConversionRateStoreTestSuite) TestGetConversionRateByCurrencyPair_Succe var dataReceiverInterface interface{} d1 := &types.ConversionRate{ - Base: "eth", - Foreign: "usdt", - Rate: 2345.987, - OracleName: "", - Time: time.Time{}.UnixMilli(), + Base: "eth", + Foreign: "usdt", + Rate: 2345.987, + OracleSource: "", + Time: time.Time{}.UnixMilli(), } jd1, err := json.Marshal(d1) s.Nil(err) @@ -124,11 +124,11 @@ func (s *ConversionRateStoreTestSuite) TestGetConversionRateByCurrencyPair_Succe re = append(re, dataReceiverInterface) d2 := &types.ConversionRate{ - Base: "eth", - Foreign: "usdt", - Rate: 2350.234, - OracleName: "", - Time: time.Time{}.UnixMilli(), + Base: "eth", + Foreign: "usdt", + Rate: 2350.234, + OracleSource: "", + Time: time.Time{}.UnixMilli(), } jd2, err := json.Marshal(d2) diff --git a/store/gasPriceStore.go b/store/gasPriceStore.go index 586444c..9c6b684 100644 --- a/store/gasPriceStore.go +++ b/store/gasPriceStore.go @@ -33,11 +33,11 @@ func (g *GasPriceStore) StoreGasPrice(gasPrice *types.GasPrices) error { return err } - return g.db.Set(g.storeKeyFormat(gasPrice.OracleName, gasPrice.DomainName), data) + return g.db.Set(g.storeKeyFormat(gasPrice.OracleSource, gasPrice.DomainID), data) } -func (g *GasPriceStore) GetGasPrice(oracleName, domainName string) (*types.GasPrices, error) { - gasPriceData, err := g.db.Get(g.storeKeyFormat(oracleName, domainName)) +func (g *GasPriceStore) GetGasPrice(OracleSource string, domainID int) (*types.GasPrices, error) { + gasPriceData, err := g.db.Get(g.storeKeyFormat(OracleSource, domainID)) if err != nil { if errors.Is(err, leveldb.ErrNotFound) { return nil, ErrNotFound @@ -54,7 +54,7 @@ func (g *GasPriceStore) GetGasPrice(oracleName, domainName string) (*types.GasPr return gasPrice, nil } -func (g *GasPriceStore) GetGasPricesByDomain(domainName string) ([]types.GasPrices, error) { +func (g *GasPriceStore) GetGasPricesByDomain(domainID int) ([]types.GasPrices, error) { key := bytes.Buffer{} key.WriteString(gasPriceStoreKeyPrefix) var dataReceiver *types.GasPrices @@ -71,7 +71,7 @@ func (g *GasPriceStore) GetGasPricesByDomain(domainName string) ([]types.GasPric if err != nil { return nil, err } - if gp.DomainName == domainName { + if gp.DomainID == domainID { re = append(re, gp) } } @@ -79,9 +79,9 @@ func (g *GasPriceStore) GetGasPricesByDomain(domainName string) ([]types.GasPric return re, nil } -func (g *GasPriceStore) storeKeyFormat(oracleName, domainID string) []byte { +func (g *GasPriceStore) storeKeyFormat(oracleSource string, domainID int) []byte { key := bytes.Buffer{} - key.WriteString(fmt.Sprintf("%s%s:%s", gasPriceStoreKeyPrefix, strings.ToLower(oracleName), strings.ToLower(domainID))) + key.WriteString(fmt.Sprintf("%s%s:%d", gasPriceStoreKeyPrefix, strings.ToLower(oracleSource), domainID)) return key.Bytes() } diff --git a/store/gasPriceStore_test.go b/store/gasPriceStore_test.go index a53300c..ed934cc 100644 --- a/store/gasPriceStore_test.go +++ b/store/gasPriceStore_test.go @@ -40,8 +40,8 @@ func (s *GasPriceStoreTestSuite) SetupTest() { SafeGasPrice: "1", ProposeGasPrice: "2", FastGasPrice: "3", - OracleName: "etherscan", - DomainName: "ethereum", + OracleSource: "etherscan", + DomainID: 1, Time: time.Now().UnixMilli(), } } @@ -52,7 +52,7 @@ func (s *GasPriceStoreTestSuite) TestStoreGasPrice_Failure() { dataBytes, err := json.Marshal(s.testdata) s.Nil(err) - s.db.EXPECT().Set([]byte(fmt.Sprintf("gasprice:%s:%s", s.testdata.OracleName, s.testdata.DomainName)), dataBytes).Return(errors.New("error")) + s.db.EXPECT().Set([]byte(fmt.Sprintf("gasprice:%s:%d", s.testdata.OracleSource, s.testdata.DomainID)), dataBytes).Return(errors.New("error")) err = s.gasPriceStore.StoreGasPrice(s.testdata) @@ -63,7 +63,7 @@ func (s *GasPriceStoreTestSuite) TestStoreGasPrice_Success() { dataBytes, err := json.Marshal(s.testdata) s.Nil(err) - s.db.EXPECT().Set([]byte(fmt.Sprintf("gasprice:%s:%s", s.testdata.OracleName, s.testdata.DomainName)), dataBytes).Return(nil) + s.db.EXPECT().Set([]byte(fmt.Sprintf("gasprice:%s:%d", s.testdata.OracleSource, s.testdata.DomainID)), dataBytes).Return(nil) err = s.gasPriceStore.StoreGasPrice(s.testdata) @@ -71,9 +71,9 @@ func (s *GasPriceStoreTestSuite) TestStoreGasPrice_Success() { } func (s *GasPriceStoreTestSuite) TestGetGasPrice_Failure() { - s.db.EXPECT().Get([]byte(fmt.Sprintf("gasprice:%s:%s", s.testdata.OracleName, s.testdata.DomainName))).Return(nil, errors.New("error")) + s.db.EXPECT().Get([]byte(fmt.Sprintf("gasprice:%s:%d", s.testdata.OracleSource, s.testdata.DomainID))).Return(nil, errors.New("error")) - _, err := s.gasPriceStore.GetGasPrice(s.testdata.OracleName, s.testdata.DomainName) + _, err := s.gasPriceStore.GetGasPrice(s.testdata.OracleSource, s.testdata.DomainID) s.NotNil(err) } @@ -82,17 +82,17 @@ func (s *GasPriceStoreTestSuite) TestGetGasPrice_Success() { dataBytes, err := json.Marshal(s.testdata) s.Nil(err) - s.db.EXPECT().Get([]byte(fmt.Sprintf("gasprice:%s:%s", s.testdata.OracleName, s.testdata.DomainName))).Return(dataBytes, nil) + s.db.EXPECT().Get([]byte(fmt.Sprintf("gasprice:%s:%d", s.testdata.OracleSource, s.testdata.DomainID))).Return(dataBytes, nil) - _, err = s.gasPriceStore.GetGasPrice(s.testdata.OracleName, s.testdata.DomainName) + _, err = s.gasPriceStore.GetGasPrice(s.testdata.OracleSource, s.testdata.DomainID) s.Nil(err) } func (s *GasPriceStoreTestSuite) TestGetGasPrice_NotFound() { - s.db.EXPECT().Get([]byte(fmt.Sprintf("gasprice:%s:%s", s.testdata.OracleName, s.testdata.DomainName))).Return(nil, store.ErrNotFound) + s.db.EXPECT().Get([]byte(fmt.Sprintf("gasprice:%s:%d", s.testdata.OracleSource, s.testdata.DomainID))).Return(nil, store.ErrNotFound) - _, err := s.gasPriceStore.GetGasPrice(s.testdata.OracleName, s.testdata.DomainName) + _, err := s.gasPriceStore.GetGasPrice(s.testdata.OracleSource, s.testdata.DomainID) s.EqualError(err, store.ErrNotFound.Error()) } @@ -101,7 +101,7 @@ func (s *GasPriceStoreTestSuite) TestGetGasPriceByDomain_NotFound() { var dataReceiver *types.GasPrices s.db.EXPECT().GetByPrefix([]byte("gasprice:"), dataReceiver).Return(nil, store.ErrNotFound) - _, err := s.gasPriceStore.GetGasPricesByDomain(s.testdata.DomainName) + _, err := s.gasPriceStore.GetGasPricesByDomain(s.testdata.DomainID) s.EqualError(err, store.ErrNotFound.Error()) } @@ -114,8 +114,8 @@ func (s *GasPriceStoreTestSuite) TestGetGasPriceByDomain_Success() { SafeGasPrice: "1", ProposeGasPrice: "2", FastGasPrice: "3", - OracleName: "", - DomainName: "ethereum", + OracleSource: "", + DomainID: 1, Time: time.Time{}.UnixMilli(), } jd1, err := json.Marshal(d1) @@ -128,8 +128,8 @@ func (s *GasPriceStoreTestSuite) TestGetGasPriceByDomain_Success() { SafeGasPrice: "2", ProposeGasPrice: "3", FastGasPrice: "4", - OracleName: "", - DomainName: "ethereum", + OracleSource: "", + DomainID: 1, Time: time.Time{}.UnixMilli(), } @@ -142,7 +142,7 @@ func (s *GasPriceStoreTestSuite) TestGetGasPriceByDomain_Success() { var dataReceiver *types.GasPrices s.db.EXPECT().GetByPrefix([]byte("gasprice:"), dataReceiver).Return(re, nil) - fetchedData, err := s.gasPriceStore.GetGasPricesByDomain(s.testdata.DomainName) + fetchedData, err := s.gasPriceStore.GetGasPricesByDomain(s.testdata.DomainID) s.Nil(err) s.Equal(2, len(fetchedData)) diff --git a/types/types.go b/types/types.go index c67027f..4d38309 100644 --- a/types/types.go +++ b/types/types.go @@ -14,19 +14,19 @@ type GasPrices struct { SafeGasPrice string `json:"safeGasPrice" mapstructure:"safeGasPrice"` ProposeGasPrice string `json:"proposeGasPrice" mapstructure:"proposeGasPrice"` FastGasPrice string `json:"fastGasPrice" mapstructure:"fastGasPrice"` - OracleName string `json:"oracleName" mapstructure:"oracleName"` - DomainName string `json:"domainName" mapstructure:"domainName"` + OracleSource string `json:"oracleSource" mapstructure:"oracleSource"` + DomainID int `json:"domainID" mapstructure:"domainID"` Time int64 `json:"time" mapstructure:"time"` } // ConversionRate is the type defined for general conversion rate // Rate = how much of Foreign is for 1 Base type ConversionRate struct { - Base string `json:"base" mapstructure:"base"` - Foreign string `json:"foreign" mapstructure:"foreign"` - Rate float64 `json:"rate" mapstructure:"rate"` - OracleName string `json:"oracleName" mapstructure:"oracleName"` - Time int64 `json:"time" mapstructure:"time"` + Base string `json:"base" mapstructure:"base"` + Foreign string `json:"foreign" mapstructure:"foreign"` + Rate float64 `json:"rate" mapstructure:"rate"` + OracleSource string `json:"oracleSource" mapstructure:"oracleSource"` + Time int64 `json:"time" mapstructure:"time"` } type Rate struct {