mirror of
https://github.com/arnaucube/hermez-node.git
synced 2026-02-06 19:06:42 +01:00
Merge pull request #629 from hermeznetwork/feature/indivisual-token-config
Allow price update configuration to be specified per token
This commit is contained in:
@@ -306,7 +306,7 @@ func TestMain(m *testing.M) {
|
|||||||
USD: ðUSD,
|
USD: ðUSD,
|
||||||
USDUpdate: ðNow,
|
USDUpdate: ðNow,
|
||||||
})
|
})
|
||||||
err = api.h.UpdateTokenValue(test.EthToken.Symbol, ethUSD)
|
err = api.h.UpdateTokenValue(common.EmptyAddr, ethUSD)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -333,7 +333,7 @@ func TestMain(m *testing.M) {
|
|||||||
token.USD = &value
|
token.USD = &value
|
||||||
token.USDUpdate = &now
|
token.USDUpdate = &now
|
||||||
// Set value in DB
|
// Set value in DB
|
||||||
err = api.h.UpdateTokenValue(token.Symbol, value)
|
err = api.h.UpdateTokenValue(token.EthAddr, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,31 @@ SQLConnectionTimeout = "2s"
|
|||||||
|
|
||||||
[PriceUpdater]
|
[PriceUpdater]
|
||||||
Interval = "10s"
|
Interval = "10s"
|
||||||
URL = "https://api-pub.bitfinex.com/v2/"
|
URLBitfinexV2 = "https://api-pub.bitfinex.com/v2/"
|
||||||
Type = "bitfinexV2"
|
URLCoinGeckoV3 = "https://api.coingecko.com/api/v3/"
|
||||||
# URL = "https://api.coingecko.com/api/v3/"
|
# Available update methods:
|
||||||
# Type = "coingeckoV3"
|
# - coingeckoV3 (recommended): get price by SC addr using coingecko API
|
||||||
|
# - bitfinexV2: get price by token symbol using bitfinex API
|
||||||
|
# - static (recommended for blacklisting tokens): use the given StaticValue to set the price (if not provided 0 will be used)
|
||||||
|
# - ignore: don't update the price leave it as it is on the DB
|
||||||
|
DefaultUpdateMethod = "coingeckoV3" # Update method used for all the tokens registered on the network, and not listed in [[PriceUpdater.TokensConfig]]
|
||||||
|
[[PriceUpdater.TokensConfig]]
|
||||||
|
UpdateMethod = "bitfinexV2"
|
||||||
|
Symbol = "USDT"
|
||||||
|
Addr = "0xdac17f958d2ee523a2206206994597c13d831ec7"
|
||||||
|
[[PriceUpdater.TokensConfig]]
|
||||||
|
UpdateMethod = "coingeckoV3"
|
||||||
|
Symbol = "ETH"
|
||||||
|
Addr = "0x0000000000000000000000000000000000000000"
|
||||||
|
[[PriceUpdater.TokensConfig]]
|
||||||
|
UpdateMethod = "static"
|
||||||
|
Symbol = "UNI"
|
||||||
|
Addr = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"
|
||||||
|
StaticValue = 30.12
|
||||||
|
[[PriceUpdater.TokensConfig]]
|
||||||
|
UpdateMethod = "ignore"
|
||||||
|
Symbol = "SUSHI"
|
||||||
|
Addr = "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2"
|
||||||
|
|
||||||
[Debug]
|
[Debug]
|
||||||
APIAddress = "localhost:12345"
|
APIAddress = "localhost:12345"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/hermeznetwork/hermez-node/common"
|
"github.com/hermeznetwork/hermez-node/common"
|
||||||
|
"github.com/hermeznetwork/hermez-node/priceupdater"
|
||||||
"github.com/hermeznetwork/tracerr"
|
"github.com/hermeznetwork/tracerr"
|
||||||
"github.com/iden3/go-iden3-crypto/babyjub"
|
"github.com/iden3/go-iden3-crypto/babyjub"
|
||||||
"gopkg.in/go-playground/validator.v9"
|
"gopkg.in/go-playground/validator.v9"
|
||||||
@@ -219,11 +220,15 @@ type Coordinator struct {
|
|||||||
type Node struct {
|
type Node struct {
|
||||||
PriceUpdater struct {
|
PriceUpdater struct {
|
||||||
// Interval between price updater calls
|
// Interval between price updater calls
|
||||||
Interval Duration `valudate:"required"`
|
Interval Duration `validate:"required"`
|
||||||
// URL of the token prices provider
|
// URLBitfinexV2 is the URL of bitfinex V2 API
|
||||||
URL string `valudate:"required"`
|
URLBitfinexV2 string `validate:"required"`
|
||||||
// Type of the API of the token prices provider
|
// URLCoinGeckoV3 is the URL of coingecko V3 API
|
||||||
Type string `valudate:"required"`
|
URLCoinGeckoV3 string `validate:"required"`
|
||||||
|
// DefaultUpdateMethod to get token prices
|
||||||
|
DefaultUpdateMethod priceupdater.UpdateMethodType `validate:"required"`
|
||||||
|
// TokensConfig to specify how each token get it's price updated
|
||||||
|
TokensConfig []priceupdater.TokenConfig
|
||||||
} `validate:"required"`
|
} `validate:"required"`
|
||||||
StateDB struct {
|
StateDB struct {
|
||||||
// Path where the synchronizer StateDB is stored
|
// Path where the synchronizer StateDB is stored
|
||||||
|
|||||||
@@ -456,13 +456,10 @@ func (hdb *HistoryDB) addTokens(d meddler.DB, tokens []common.Token) error {
|
|||||||
|
|
||||||
// UpdateTokenValue updates the USD value of a token. Value is the price in
|
// UpdateTokenValue updates the USD value of a token. Value is the price in
|
||||||
// USD of a normalized token (1 token = 10^decimals units)
|
// USD of a normalized token (1 token = 10^decimals units)
|
||||||
func (hdb *HistoryDB) UpdateTokenValue(tokenSymbol string, value float64) error {
|
func (hdb *HistoryDB) UpdateTokenValue(tokenAddr ethCommon.Address, value float64) error {
|
||||||
// Sanitize symbol
|
|
||||||
tokenSymbol = strings.ToValidUTF8(tokenSymbol, " ")
|
|
||||||
|
|
||||||
_, err := hdb.dbWrite.Exec(
|
_, err := hdb.dbWrite.Exec(
|
||||||
"UPDATE token SET usd = $1 WHERE symbol = $2;",
|
"UPDATE token SET usd = $1 WHERE eth_addr = $2;",
|
||||||
value, tokenSymbol,
|
value, tokenAddr,
|
||||||
)
|
)
|
||||||
return tracerr.Wrap(err)
|
return tracerr.Wrap(err)
|
||||||
}
|
}
|
||||||
@@ -1161,7 +1158,7 @@ func (hdb *HistoryDB) GetTokensTest() ([]TokenWithUSD, error) {
|
|||||||
tokens := []*TokenWithUSD{}
|
tokens := []*TokenWithUSD{}
|
||||||
if err := meddler.QueryAll(
|
if err := meddler.QueryAll(
|
||||||
hdb.dbRead, &tokens,
|
hdb.dbRead, &tokens,
|
||||||
"SELECT * FROM TOKEN",
|
"SELECT * FROM token ORDER BY token_id ASC",
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, tracerr.Wrap(err)
|
return nil, tracerr.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ func TestBatches(t *testing.T) {
|
|||||||
if i%2 != 0 {
|
if i%2 != 0 {
|
||||||
// Set value to the token
|
// Set value to the token
|
||||||
value := (float64(i) + 5) * 5.389329
|
value := (float64(i) + 5) * 5.389329
|
||||||
assert.NoError(t, historyDB.UpdateTokenValue(token.Symbol, value))
|
assert.NoError(t, historyDB.UpdateTokenValue(token.EthAddr, value))
|
||||||
tokensValue[token.TokenID] = value / math.Pow(10, float64(token.Decimals))
|
tokensValue[token.TokenID] = value / math.Pow(10, float64(token.Decimals))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,7 +276,7 @@ func TestTokens(t *testing.T) {
|
|||||||
// Update token value
|
// Update token value
|
||||||
for i, token := range tokens {
|
for i, token := range tokens {
|
||||||
value := 1.01 * float64(i)
|
value := 1.01 * float64(i)
|
||||||
assert.NoError(t, historyDB.UpdateTokenValue(token.Symbol, value))
|
assert.NoError(t, historyDB.UpdateTokenValue(token.EthAddr, value))
|
||||||
}
|
}
|
||||||
// Fetch tokens
|
// Fetch tokens
|
||||||
fetchedTokens, err = historyDB.GetTokensTest()
|
fetchedTokens, err = historyDB.GetTokensTest()
|
||||||
@@ -302,7 +302,7 @@ func TestTokensUTF8(t *testing.T) {
|
|||||||
// Generate fake tokens
|
// Generate fake tokens
|
||||||
const nTokens = 5
|
const nTokens = 5
|
||||||
tokens, ethToken := test.GenTokens(nTokens, blocks)
|
tokens, ethToken := test.GenTokens(nTokens, blocks)
|
||||||
nonUTFTokens := make([]common.Token, len(tokens)+1)
|
nonUTFTokens := make([]common.Token, len(tokens))
|
||||||
// Force token.name and token.symbol to be non UTF-8 Strings
|
// Force token.name and token.symbol to be non UTF-8 Strings
|
||||||
for i, token := range tokens {
|
for i, token := range tokens {
|
||||||
token.Name = fmt.Sprint("NON-UTF8-NAME-\xc5-", i)
|
token.Name = fmt.Sprint("NON-UTF8-NAME-\xc5-", i)
|
||||||
@@ -332,7 +332,7 @@ func TestTokensUTF8(t *testing.T) {
|
|||||||
// Update token value
|
// Update token value
|
||||||
for i, token := range nonUTFTokens {
|
for i, token := range nonUTFTokens {
|
||||||
value := 1.01 * float64(i)
|
value := 1.01 * float64(i)
|
||||||
assert.NoError(t, historyDB.UpdateTokenValue(token.Symbol, value))
|
assert.NoError(t, historyDB.UpdateTokenValue(token.EthAddr, value))
|
||||||
}
|
}
|
||||||
// Fetch tokens
|
// Fetch tokens
|
||||||
fetchedTokens, err = historyDB.GetTokensTest()
|
fetchedTokens, err = historyDB.GetTokensTest()
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ func prepareHistoryDB(historyDB *historydb.HistoryDB) error {
|
|||||||
}
|
}
|
||||||
tokens[token.TokenID] = readToken
|
tokens[token.TokenID] = readToken
|
||||||
// Set value to the tokens
|
// Set value to the tokens
|
||||||
err := historyDB.UpdateTokenValue(readToken.Symbol, *readToken.USD)
|
err := historyDB.UpdateTokenValue(readToken.EthAddr, *readToken.USD)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tracerr.Wrap(err)
|
return tracerr.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -425,8 +425,13 @@ func NewNode(mode Mode, cfg *config.Node) (*Node, error) {
|
|||||||
if cfg.Debug.APIAddress != "" {
|
if cfg.Debug.APIAddress != "" {
|
||||||
debugAPI = debugapi.NewDebugAPI(cfg.Debug.APIAddress, stateDB, sync)
|
debugAPI = debugapi.NewDebugAPI(cfg.Debug.APIAddress, stateDB, sync)
|
||||||
}
|
}
|
||||||
priceUpdater, err := priceupdater.NewPriceUpdater(cfg.PriceUpdater.URL,
|
priceUpdater, err := priceupdater.NewPriceUpdater(
|
||||||
priceupdater.APIType(cfg.PriceUpdater.Type), historyDB)
|
cfg.PriceUpdater.DefaultUpdateMethod,
|
||||||
|
cfg.PriceUpdater.TokensConfig,
|
||||||
|
historyDB,
|
||||||
|
cfg.PriceUpdater.URLBitfinexV2,
|
||||||
|
cfg.PriceUpdater.URLCoinGeckoV3,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, tracerr.Wrap(err)
|
return nil, tracerr.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,57 +20,107 @@ const (
|
|||||||
defaultIdleConnTimeout = 2 * time.Second
|
defaultIdleConnTimeout = 2 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIType defines the token exchange API
|
// UpdateMethodType defines the token price update mechanism
|
||||||
type APIType string
|
type UpdateMethodType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// APITypeBitFinexV2 is the http API used by bitfinex V2
|
// UpdateMethodTypeBitFinexV2 is the http API used by bitfinex V2
|
||||||
APITypeBitFinexV2 APIType = "bitfinexV2"
|
UpdateMethodTypeBitFinexV2 UpdateMethodType = "bitfinexV2"
|
||||||
// APITypeCoingeckoV3 is the http API used by copingecko V3
|
// UpdateMethodTypeCoingeckoV3 is the http API used by copingecko V3
|
||||||
APITypeCoingeckoV3 APIType = "coingeckoV3"
|
UpdateMethodTypeCoingeckoV3 UpdateMethodType = "coingeckoV3"
|
||||||
|
// UpdateMethodTypeStatic is the value given by the configuration
|
||||||
|
UpdateMethodTypeStatic UpdateMethodType = "static"
|
||||||
|
// UpdateMethodTypeIgnore indicates to not update the value, to set value 0
|
||||||
|
// it's better to use UpdateMethodTypeStatic
|
||||||
|
UpdateMethodTypeIgnore UpdateMethodType = "ignore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t *APIType) valid() bool {
|
func (t *UpdateMethodType) valid() bool {
|
||||||
switch *t {
|
switch *t {
|
||||||
case APITypeBitFinexV2:
|
case UpdateMethodTypeBitFinexV2:
|
||||||
return true
|
return true
|
||||||
case APITypeCoingeckoV3:
|
case UpdateMethodTypeCoingeckoV3:
|
||||||
|
return true
|
||||||
|
case UpdateMethodTypeStatic:
|
||||||
|
return true
|
||||||
|
case UpdateMethodTypeIgnore:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TokenConfig specifies how a single token get its price updated
|
||||||
|
type TokenConfig struct {
|
||||||
|
UpdateMethod UpdateMethodType
|
||||||
|
StaticValue float64 // required by UpdateMethodTypeStatic
|
||||||
|
Symbol string
|
||||||
|
Addr ethCommon.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TokenConfig) valid() bool {
|
||||||
|
if (t.Addr == common.EmptyAddr && t.Symbol != "ETH") ||
|
||||||
|
(t.Symbol == "" && t.UpdateMethod == UpdateMethodTypeBitFinexV2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t.UpdateMethod.valid()
|
||||||
|
}
|
||||||
|
|
||||||
// PriceUpdater definition
|
// PriceUpdater definition
|
||||||
type PriceUpdater struct {
|
type PriceUpdater struct {
|
||||||
db *historydb.HistoryDB
|
db *historydb.HistoryDB
|
||||||
apiURL string
|
defaultUpdateMethod UpdateMethodType
|
||||||
apiType APIType
|
tokensList []historydb.TokenSymbolAndAddr
|
||||||
tokens []historydb.TokenSymbolAndAddr
|
tokensConfig map[ethCommon.Address]TokenConfig
|
||||||
|
clientCoingeckoV3 *sling.Sling
|
||||||
|
clientBitfinexV2 *sling.Sling
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPriceUpdater is the constructor for the updater
|
// NewPriceUpdater is the constructor for the updater
|
||||||
func NewPriceUpdater(apiURL string, apiType APIType, db *historydb.HistoryDB) (*PriceUpdater,
|
func NewPriceUpdater(
|
||||||
error) {
|
defaultUpdateMethodType UpdateMethodType,
|
||||||
if !apiType.valid() {
|
tokensConfig []TokenConfig,
|
||||||
return nil, tracerr.Wrap(fmt.Errorf("Invalid apiType: %v", apiType))
|
db *historydb.HistoryDB,
|
||||||
|
bitfinexV2URL, coingeckoV3URL string,
|
||||||
|
) (*PriceUpdater, error) {
|
||||||
|
// Validate params
|
||||||
|
if !defaultUpdateMethodType.valid() || defaultUpdateMethodType == UpdateMethodTypeStatic {
|
||||||
|
return nil, tracerr.Wrap(
|
||||||
|
fmt.Errorf("Invalid defaultUpdateMethodType: %v", defaultUpdateMethodType),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
tokensConfigMap := make(map[ethCommon.Address]TokenConfig)
|
||||||
|
for _, t := range tokensConfig {
|
||||||
|
if !t.valid() {
|
||||||
|
return nil, tracerr.Wrap(fmt.Errorf("Invalid tokensConfig, wrong entry: %+v", t))
|
||||||
|
}
|
||||||
|
tokensConfigMap[t.Addr] = t
|
||||||
|
}
|
||||||
|
// Init
|
||||||
|
tr := &http.Transport{
|
||||||
|
MaxIdleConns: defaultMaxIdleConns,
|
||||||
|
IdleConnTimeout: defaultIdleConnTimeout,
|
||||||
|
DisableCompression: true,
|
||||||
|
}
|
||||||
|
httpClient := &http.Client{Transport: tr}
|
||||||
return &PriceUpdater{
|
return &PriceUpdater{
|
||||||
db: db,
|
db: db,
|
||||||
apiURL: apiURL,
|
defaultUpdateMethod: defaultUpdateMethodType,
|
||||||
apiType: apiType,
|
tokensList: []historydb.TokenSymbolAndAddr{},
|
||||||
tokens: []historydb.TokenSymbolAndAddr{},
|
tokensConfig: tokensConfigMap,
|
||||||
|
clientCoingeckoV3: sling.New().Base(coingeckoV3URL).Client(httpClient),
|
||||||
|
clientBitfinexV2: sling.New().Base(bitfinexV2URL).Client(httpClient),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTokenPriceBitfinex(ctx context.Context, client *sling.Sling,
|
func (p *PriceUpdater) getTokenPriceBitfinex(ctx context.Context, tokenSymbol string) (float64, error) {
|
||||||
tokenSymbol string) (float64, error) {
|
|
||||||
state := [10]float64{}
|
state := [10]float64{}
|
||||||
req, err := client.New().Get("ticker/t" + tokenSymbol + "USD").Request()
|
url := "ticker/t" + tokenSymbol + "USD"
|
||||||
|
req, err := p.clientBitfinexV2.New().Get(url).Request()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, tracerr.Wrap(err)
|
return 0, tracerr.Wrap(err)
|
||||||
}
|
}
|
||||||
res, err := client.Do(req.WithContext(ctx), &state, nil)
|
res, err := p.clientBitfinexV2.Do(req.WithContext(ctx), &state, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, tracerr.Wrap(err)
|
return 0, tracerr.Wrap(err)
|
||||||
}
|
}
|
||||||
@@ -80,8 +130,7 @@ func getTokenPriceBitfinex(ctx context.Context, client *sling.Sling,
|
|||||||
return state[6], nil
|
return state[6], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTokenPriceCoingecko(ctx context.Context, client *sling.Sling,
|
func (p *PriceUpdater) getTokenPriceCoingecko(ctx context.Context, tokenAddr ethCommon.Address) (float64, error) {
|
||||||
tokenAddr ethCommon.Address) (float64, error) {
|
|
||||||
responseObject := make(map[string]map[string]float64)
|
responseObject := make(map[string]map[string]float64)
|
||||||
var url string
|
var url string
|
||||||
var id string
|
var id string
|
||||||
@@ -93,11 +142,11 @@ func getTokenPriceCoingecko(ctx context.Context, client *sling.Sling,
|
|||||||
url = "simple/token_price/ethereum?contract_addresses=" +
|
url = "simple/token_price/ethereum?contract_addresses=" +
|
||||||
id + "&vs_currencies=usd"
|
id + "&vs_currencies=usd"
|
||||||
}
|
}
|
||||||
req, err := client.New().Get(url).Request()
|
req, err := p.clientCoingeckoV3.New().Get(url).Request()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, tracerr.Wrap(err)
|
return 0, tracerr.Wrap(err)
|
||||||
}
|
}
|
||||||
res, err := client.Do(req.WithContext(ctx), &responseObject, nil)
|
res, err := p.clientCoingeckoV3.Do(req.WithContext(ctx), &responseObject, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, tracerr.Wrap(err)
|
return 0, tracerr.Wrap(err)
|
||||||
}
|
}
|
||||||
@@ -114,43 +163,50 @@ func getTokenPriceCoingecko(ctx context.Context, client *sling.Sling,
|
|||||||
// UpdatePrices is triggered by the Coordinator, and internally will update the
|
// UpdatePrices is triggered by the Coordinator, and internally will update the
|
||||||
// token prices in the db
|
// token prices in the db
|
||||||
func (p *PriceUpdater) UpdatePrices(ctx context.Context) {
|
func (p *PriceUpdater) UpdatePrices(ctx context.Context) {
|
||||||
tr := &http.Transport{
|
for _, token := range p.tokensConfig {
|
||||||
MaxIdleConns: defaultMaxIdleConns,
|
|
||||||
IdleConnTimeout: defaultIdleConnTimeout,
|
|
||||||
DisableCompression: true,
|
|
||||||
}
|
|
||||||
httpClient := &http.Client{Transport: tr}
|
|
||||||
client := sling.New().Base(p.apiURL).Client(httpClient)
|
|
||||||
|
|
||||||
for _, token := range p.tokens {
|
|
||||||
var tokenPrice float64
|
var tokenPrice float64
|
||||||
var err error
|
var err error
|
||||||
switch p.apiType {
|
switch token.UpdateMethod {
|
||||||
case APITypeBitFinexV2:
|
case UpdateMethodTypeBitFinexV2:
|
||||||
tokenPrice, err = getTokenPriceBitfinex(ctx, client, token.Symbol)
|
tokenPrice, err = p.getTokenPriceBitfinex(ctx, token.Symbol)
|
||||||
case APITypeCoingeckoV3:
|
case UpdateMethodTypeCoingeckoV3:
|
||||||
tokenPrice, err = getTokenPriceCoingecko(ctx, client, token.Addr)
|
tokenPrice, err = p.getTokenPriceCoingecko(ctx, token.Addr)
|
||||||
|
case UpdateMethodTypeStatic:
|
||||||
|
tokenPrice = token.StaticValue
|
||||||
|
case UpdateMethodTypeIgnore:
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnw("token price not updated (get error)",
|
log.Warnw("token price not updated (get error)",
|
||||||
"err", err, "token", token.Symbol, "apiType", p.apiType)
|
"err", err, "token", token.Symbol, "updateMethod", token.UpdateMethod)
|
||||||
}
|
}
|
||||||
if err = p.db.UpdateTokenValue(token.Symbol, tokenPrice); err != nil {
|
if err = p.db.UpdateTokenValue(token.Addr, tokenPrice); err != nil {
|
||||||
log.Errorw("token price not updated (db error)",
|
log.Errorw("token price not updated (db error)",
|
||||||
"err", err, "token", token.Symbol, "apiType", p.apiType)
|
"err", err, "token", token.Symbol, "updateMethod", token.UpdateMethod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateTokenList get the registered token symbols from HistoryDB
|
// UpdateTokenList get the registered token symbols from HistoryDB
|
||||||
func (p *PriceUpdater) UpdateTokenList() error {
|
func (p *PriceUpdater) UpdateTokenList() error {
|
||||||
tokens, err := p.db.GetTokenSymbolsAndAddrs()
|
dbTokens, err := p.db.GetTokenSymbolsAndAddrs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tracerr.Wrap(err)
|
return tracerr.Wrap(err)
|
||||||
}
|
}
|
||||||
p.tokens = tokens
|
// For each token from the DB
|
||||||
|
for _, dbToken := range dbTokens {
|
||||||
|
// If the token doesn't exists in the config list,
|
||||||
|
// add it with default update emthod
|
||||||
|
if _, ok := p.tokensConfig[dbToken.Addr]; !ok {
|
||||||
|
p.tokensConfig[dbToken.Addr] = TokenConfig{
|
||||||
|
UpdateMethod: p.defaultUpdateMethod,
|
||||||
|
Symbol: dbToken.Symbol,
|
||||||
|
Addr: dbToken.Addr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ import (
|
|||||||
|
|
||||||
var historyDB *historydb.HistoryDB
|
var historyDB *historydb.HistoryDB
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
const usdtAddr = "0xdac17f958d2ee523a2206206994597c13d831ec7"
|
||||||
|
|
||||||
|
func TestPriceUpdaterBitfinex(t *testing.T) {
|
||||||
// Init DB
|
// Init DB
|
||||||
pass := os.Getenv("POSTGRES_PASS")
|
pass := os.Getenv("POSTGRES_PASS")
|
||||||
db, err := dbUtils.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
|
db, err := dbUtils.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
|
||||||
@@ -29,60 +31,113 @@ func TestMain(m *testing.M) {
|
|||||||
// Populate DB
|
// Populate DB
|
||||||
// Gen blocks and add them to DB
|
// Gen blocks and add them to DB
|
||||||
blocks := test.GenBlocks(1, 2)
|
blocks := test.GenBlocks(1, 2)
|
||||||
err = historyDB.AddBlocks(blocks)
|
require.NoError(t, historyDB.AddBlocks(blocks))
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
// Gen tokens and add them to DB
|
// Gen tokens and add them to DB
|
||||||
tokens := []common.Token{}
|
tokens := []common.Token{
|
||||||
tokens = append(tokens, common.Token{
|
{
|
||||||
TokenID: 1,
|
TokenID: 1,
|
||||||
EthBlockNum: blocks[0].Num,
|
EthBlockNum: blocks[0].Num,
|
||||||
EthAddr: ethCommon.HexToAddress("0x6b175474e89094c44da98b954eedeac495271d0f"),
|
EthAddr: ethCommon.HexToAddress("0x1"),
|
||||||
Name: "DAI",
|
Name: "DAI",
|
||||||
Symbol: "DAI",
|
Symbol: "DAI",
|
||||||
Decimals: 18,
|
Decimals: 18,
|
||||||
})
|
}, // Used to test get by SC addr
|
||||||
err = historyDB.AddTokens(tokens)
|
{
|
||||||
if err != nil {
|
TokenID: 2,
|
||||||
panic(err)
|
EthBlockNum: blocks[0].Num,
|
||||||
|
EthAddr: ethCommon.HexToAddress(usdtAddr),
|
||||||
|
Name: "Tether",
|
||||||
|
Symbol: "USDT",
|
||||||
|
Decimals: 18,
|
||||||
|
}, // Used to test get by token symbol
|
||||||
|
{
|
||||||
|
TokenID: 3,
|
||||||
|
EthBlockNum: blocks[0].Num,
|
||||||
|
EthAddr: ethCommon.HexToAddress("0x2"),
|
||||||
|
Name: "FOO",
|
||||||
|
Symbol: "FOO",
|
||||||
|
Decimals: 18,
|
||||||
|
}, // Used to test ignore
|
||||||
|
{
|
||||||
|
TokenID: 4,
|
||||||
|
EthBlockNum: blocks[0].Num,
|
||||||
|
EthAddr: ethCommon.HexToAddress("0x3"),
|
||||||
|
Name: "BAR",
|
||||||
|
Symbol: "BAR",
|
||||||
|
Decimals: 18,
|
||||||
|
}, // Used to test static
|
||||||
|
{
|
||||||
|
TokenID: 5,
|
||||||
|
EthBlockNum: blocks[0].Num,
|
||||||
|
EthAddr: ethCommon.HexToAddress("0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"),
|
||||||
|
Name: "Uniswap",
|
||||||
|
Symbol: "UNI",
|
||||||
|
Decimals: 18,
|
||||||
|
}, // Used to test default
|
||||||
|
}
|
||||||
|
require.NoError(t, historyDB.AddTokens(tokens)) // ETH token exist in DB by default
|
||||||
|
// Update token price used to test ignore
|
||||||
|
ignoreValue := 44.44
|
||||||
|
require.NoError(t, historyDB.UpdateTokenValue(tokens[2].EthAddr, ignoreValue))
|
||||||
|
|
||||||
|
// Prepare token config
|
||||||
|
staticValue := 0.12345
|
||||||
|
tc := []TokenConfig{
|
||||||
|
// ETH and UNI tokens use default method
|
||||||
|
{ // DAI uses SC addr
|
||||||
|
UpdateMethod: UpdateMethodTypeBitFinexV2,
|
||||||
|
Addr: ethCommon.HexToAddress("0x1"),
|
||||||
|
Symbol: "DAI",
|
||||||
|
},
|
||||||
|
{ // USDT uses symbol
|
||||||
|
UpdateMethod: UpdateMethodTypeCoingeckoV3,
|
||||||
|
Addr: ethCommon.HexToAddress(usdtAddr),
|
||||||
|
},
|
||||||
|
{ // FOO uses ignore
|
||||||
|
UpdateMethod: UpdateMethodTypeIgnore,
|
||||||
|
Addr: ethCommon.HexToAddress("0x2"),
|
||||||
|
},
|
||||||
|
{ // BAR uses static
|
||||||
|
UpdateMethod: UpdateMethodTypeStatic,
|
||||||
|
Addr: ethCommon.HexToAddress("0x3"),
|
||||||
|
StaticValue: staticValue,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
result := m.Run()
|
bitfinexV2URL := "https://api-pub.bitfinex.com/v2/"
|
||||||
os.Exit(result)
|
coingeckoV3URL := "https://api.coingecko.com/api/v3/"
|
||||||
}
|
|
||||||
|
|
||||||
func TestPriceUpdaterBitfinex(t *testing.T) {
|
|
||||||
// Init price updater
|
// Init price updater
|
||||||
pu, err := NewPriceUpdater("https://api-pub.bitfinex.com/v2/", APITypeBitFinexV2, historyDB)
|
pu, err := NewPriceUpdater(
|
||||||
|
UpdateMethodTypeCoingeckoV3,
|
||||||
|
tc,
|
||||||
|
historyDB,
|
||||||
|
bitfinexV2URL,
|
||||||
|
coingeckoV3URL,
|
||||||
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// Update token list
|
// Update token list
|
||||||
assert.NoError(t, pu.UpdateTokenList())
|
require.NoError(t, pu.UpdateTokenList())
|
||||||
// Update prices
|
// Update prices
|
||||||
pu.UpdatePrices(context.Background())
|
pu.UpdatePrices(context.Background())
|
||||||
assertTokenHasPriceAndClean(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPriceUpdaterCoingecko(t *testing.T) {
|
// Check results: get tokens from DB
|
||||||
// Init price updater
|
|
||||||
pu, err := NewPriceUpdater("https://api.coingecko.com/api/v3/", APITypeCoingeckoV3, historyDB)
|
|
||||||
require.NoError(t, err)
|
|
||||||
// Update token list
|
|
||||||
assert.NoError(t, pu.UpdateTokenList())
|
|
||||||
// Update prices
|
|
||||||
pu.UpdatePrices(context.Background())
|
|
||||||
assertTokenHasPriceAndClean(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertTokenHasPriceAndClean(t *testing.T) {
|
|
||||||
// Check that prices have been updated
|
|
||||||
fetchedTokens, err := historyDB.GetTokensTest()
|
fetchedTokens, err := historyDB.GetTokensTest()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// TokenID 0 (ETH) is always on the DB
|
// Check that tokens that are updated via API have value:
|
||||||
assert.Equal(t, 2, len(fetchedTokens))
|
// ETH
|
||||||
for _, token := range fetchedTokens {
|
require.NotNil(t, fetchedTokens[0].USDUpdate)
|
||||||
require.NotNil(t, token.USD)
|
assert.Greater(t, *fetchedTokens[0].USD, 0.0)
|
||||||
require.NotNil(t, token.USDUpdate)
|
// DAI
|
||||||
assert.Greater(t, *token.USD, 0.0)
|
require.NotNil(t, fetchedTokens[1].USDUpdate)
|
||||||
}
|
assert.Greater(t, *fetchedTokens[1].USD, 0.0)
|
||||||
|
// USDT
|
||||||
|
require.NotNil(t, fetchedTokens[2].USDUpdate)
|
||||||
|
assert.Greater(t, *fetchedTokens[2].USD, 0.0)
|
||||||
|
// UNI
|
||||||
|
require.NotNil(t, fetchedTokens[5].USDUpdate)
|
||||||
|
assert.Greater(t, *fetchedTokens[5].USD, 0.0)
|
||||||
|
// Check ignored token
|
||||||
|
assert.Equal(t, ignoreValue, *fetchedTokens[3].USD)
|
||||||
|
// Check static value
|
||||||
|
assert.Equal(t, staticValue, *fetchedTokens[4].USD)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user