Skip to content

Commit

Permalink
Introduce price oracle fix
Browse files Browse the repository at this point in the history
  • Loading branch information
R-Santev committed Jan 28, 2025
1 parent 58df7ac commit b696820
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 40 deletions.
6 changes: 5 additions & 1 deletion chain/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ const (
QuorumCalcAlignment = "quorumcalcalignment"
TxHashWithType = "txHashWithType"
LondonFix = "londonfix"
PriceOracleFix = "priceOracleFix"
)

// Forks is map which contains all forks and their starting blocks from genesis
Expand Down Expand Up @@ -128,6 +129,7 @@ func (f *Forks) At(block uint64) ForksInTime {
QuorumCalcAlignment: f.IsActive(QuorumCalcAlignment, block),
TxHashWithType: f.IsActive(TxHashWithType, block),
LondonFix: f.IsActive(LondonFix, block),
PriceOracleFix: f.IsActive(PriceOracleFix, block),
}
}

Expand Down Expand Up @@ -180,7 +182,8 @@ type ForksInTime struct {
EIP155,
QuorumCalcAlignment,
TxHashWithType,
LondonFix bool
LondonFix,
PriceOracleFix bool
}

// AllForksEnabled should contain all supported forks by current edge version
Expand All @@ -197,4 +200,5 @@ var AllForksEnabled = &Forks{
QuorumCalcAlignment: NewFork(0),
TxHashWithType: NewFork(0),
LondonFix: NewFork(0),
PriceOracleFix: NewFork(0),
}
89 changes: 51 additions & 38 deletions price-oracle/price_feed.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"encoding/json"
"fmt"
"math/big"
"net/http"
"time"

"github.com/0xPolygon/polygon-edge/chain"
"github.com/0xPolygon/polygon-edge/helper/common"
"github.com/0xPolygon/polygon-edge/secrets"
"github.com/0xPolygon/polygon-edge/types"
Expand All @@ -28,72 +30,83 @@ func (d *dummyPriceFeed) GetPrice(header *types.Header) (*big.Int, error) {

type priceFeed struct {
coinGeckoAPIKey string
forks *chain.Forks
}

func NewPriceFeed(secretsManagerConfig *secrets.SecretsManagerConfig) (PriceFeed, error) {
func NewPriceFeed(secretsManagerConfig *secrets.SecretsManagerConfig, forks *chain.Forks) (PriceFeed, error) {
apiKey, ok := secretsManagerConfig.Extra[secrets.CoinGeckoAPIKey].(string)
if !ok {
return nil, fmt.Errorf(secrets.CoinGeckoAPIKey + " is not a string")
}

return &priceFeed{coinGeckoAPIKey: apiKey}, nil
return &priceFeed{coinGeckoAPIKey: apiKey, forks: forks}, nil
}

func (p *priceFeed) GetPrice(header *types.Header) (*big.Int, error) {
price, err := getCoingeckoPrice(p.coinGeckoAPIKey)
forkConfig := p.forks.At(header.Number)

req, err := p.buildCoingeckoReq(forkConfig.PriceOracleFix)
if err != nil {
return nil, fmt.Errorf("generating CoinGecko request failed: %w", err)
}

body, err := common.FetchData(req)
if err != nil {
return nil, fmt.Errorf("get price from CoinGecko failed: %w", err)
}

var priceData PriceDataCoinGecko
err = json.Unmarshal(body, &priceData)
if err != nil {

Check failure on line 60 in price-oracle/price_feed.go

View workflow job for this annotation

GitHub Actions / golangci_lint

only one cuddle assignment allowed before if statement (wsl)

Check failure on line 60 in price-oracle/price_feed.go

View workflow job for this annotation

GitHub Actions / golangci_lint

only one cuddle assignment allowed before if statement (wsl)
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}

price, err := common.ConvertFloatToBigInt(priceData.MarketData.CurrentPrice.USD, 8)
if err != nil {
return nil, fmt.Errorf("failed to convert price to big.Int: %w", err)
}

return price, nil
}

type PriceDataCoinGecko struct {
ID string `json:"id"`
Symbol string `json:"symbol"`
Name string `json:"name"`
MarketData struct {
CurrentPrice struct {
USD float64 `json:"usd"`
} `json:"current_price"`
} `json:"market_data"`
}
// buildCoingeckoReq creates an HTTP request using either today or yesterday’s date, depending on isPriceOracleFixed.
func (p *priceFeed) buildCoingeckoReq(isPriceOracleFixed bool) (*http.Request, error) {
var date string
if isPriceOracleFixed {
date = getDateFormatted(timeNow().UTC())
} else {
date = getDateFormatted(timeNow().UTC().AddDate(0, 0, -1))
}

// getCoingeckoPrice fetches the current price of the Hydra cryptocurrency from the CoinGecko API.
// It takes a timeout as input to wait for the request, in minutes.
// It returns a big.Int representing the average price for the previous day.
func getCoingeckoPrice(apiKey string) (*big.Int, error) {
yesterday := getYesterdayFormatted()
apiURL := fmt.Sprintf(`https://api.coingecko.com/api/v3/coins/hydra/history?date=%s`, yesterday)
apiURL := fmt.Sprintf(`https://api.coingecko.com/api/v3/coins/hydra/history?date=%s`, date)

req, err := common.GenerateThirdPartyJSONRequest(apiURL)
if err != nil {
return nil, err
}

// Add the key in the header
req.Header.Add("x-cg-demo-api-key", apiKey)

body, err := common.FetchData(req)
if err != nil {
return nil, err
}

var priceData PriceDataCoinGecko

err = json.Unmarshal(body, &priceData)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
req.Header.Add("x-cg-demo-api-key", p.coinGeckoAPIKey)

price := priceData.MarketData.CurrentPrice.USD
return req, nil
}

return common.ConvertFloatToBigInt(price, 8)
type PriceDataCoinGecko struct {
ID string `json:"id"`
Symbol string `json:"symbol"`
Name string `json:"name"`
MarketData struct {
CurrentPrice struct {
USD float64 `json:"usd"`
} `json:"current_price"`
} `json:"market_data"`
}

// getYesterdayFormatted returns the date in the format dd-mm-yyyy for the previous day.
func getYesterdayFormatted() string {
yesterday := time.Now().UTC().AddDate(0, 0, -1)
// getDateFormatted returns the date in the format dd-mm-yyyy for a given time.
func getDateFormatted(time time.Time) string {
return time.Format("02-01-2006")
}

return yesterday.Format("02-01-2006")
var timeNow = func() time.Time {
return time.Now().UTC()
}
52 changes: 52 additions & 0 deletions price-oracle/price_feed_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package priceoracle

import (
"testing"
"time"

"github.com/stretchr/testify/require"
)

func TestBuildCoingeckoReq(t *testing.T) {
origTimeNow := timeNow
defer func() {
timeNow = origTimeNow
}()

// Fix the current time to 2025-01-23 10:00 UTC
fixedTime := time.Date(2025, 1, 23, 10, 0, 0, 0, time.UTC)
timeNow = func() time.Time {
return fixedTime
}

// Table of test cases
testCases := []struct {
name string
isPriceOracleFixed bool
expectedDate string // we expect this to appear in the URL
}{
{
name: "Fork not fixed => uses yesterday",
isPriceOracleFixed: false,
expectedDate: "22-01-2025", // one day before fixedTime
},
{
name: "Fork fixed => uses today",
isPriceOracleFixed: true,
expectedDate: "23-01-2025", // same as fixedTime
},
}

priceFeed := &priceFeed{}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
req, err := priceFeed.buildCoingeckoReq(tc.isPriceOracleFixed)
require.NoError(t, err, "building request should not fail")

url := req.URL.String()
require.Containsf(t, url, tc.expectedDate,
"URL %q does not contain expected date %q", url, tc.expectedDate)
})
}
}
4 changes: 3 additions & 1 deletion price-oracle/price_oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/0xPolygon/polygon-edge/blockchain"
"github.com/0xPolygon/polygon-edge/chain"
"github.com/0xPolygon/polygon-edge/consensus"
"github.com/0xPolygon/polygon-edge/consensus/polybft"
"github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi"
Expand Down Expand Up @@ -78,8 +79,9 @@ func NewPriceOracle(
jsonRPC string,
secretsManager secrets.SecretsManager,
secretsManagerConfig *secrets.SecretsManagerConfig,
forks *chain.Forks,
) (*PriceOracle, error) {
priceFeed, err := NewPriceFeed(secretsManagerConfig)
priceFeed, err := NewPriceFeed(secretsManagerConfig, forks)
if err != nil {
return nil, err
}
Expand Down
1 change: 1 addition & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ func NewServer(config *Config) (*Server, error) {
m.config.JSONRPC.JSONRPCAddr.String(),
m.secretsManager,
m.config.SecretsManager,
m.config.Chain.Params.Forks,
)
if err != nil {
return nil, err
Expand Down

0 comments on commit b696820

Please sign in to comment.