feat: load token list in code

This commit is contained in:
Anthony Laibe 2023-06-14 15:16:15 +02:00 committed by Anthony Laibe
parent 51e3d800bb
commit 2375b10b03
7 changed files with 6980 additions and 125 deletions

View File

@ -0,0 +1,117 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"html/template"
"io/ioutil"
"net/http"
"os"
"time"
"github.com/status-im/status-go/services/wallet/token"
"github.com/xeipuuv/gojsonschema"
)
const uniswapTokensURL = "https://gateway.ipfs.io/ipns/tokens.uniswap.org" // nolint:gosec
const tokenListSchemaURL = "https://uniswap.org/tokenlist.schema.json" // nolint:gosec
const templateText = `
package token
import (
"github.com/ethereum/go-ethereum/common"
)
var uniswapTokens = []*Token{
{{ range $token := .Tokens }}
{
Address: common.HexToAddress("{{ $token.Address }}"),
Name: "{{ $token.Name }}",
Symbol: "{{ $token.Symbol }}",
Color: "{{ $token.Color }}",
Decimals: {{ $token.Decimals }},
ChainID: {{ $token.ChainID }},
PegSymbol: "{{ $token.PegSymbol }}",
},
{{ end }}
}
`
func validateDocument(doc string, schemaURL string) (bool, error) {
schemaLoader := gojsonschema.NewReferenceLoader(schemaURL)
docLoader := gojsonschema.NewStringLoader(doc)
result, err := gojsonschema.Validate(schemaLoader, docLoader)
if err != nil {
return false, err
}
if !result.Valid() {
return false, errors.New("Token list does not match schema")
}
return true, nil
}
func bytesToTokens(tokenListData []byte) ([]*token.Token, error) {
var objmap map[string]json.RawMessage
err := json.Unmarshal(tokenListData, &objmap)
if err != nil {
return nil, err
}
var tokens []*token.Token
err = json.Unmarshal(objmap["tokens"], &tokens)
if err != nil {
return nil, err
}
return tokens, nil
}
func main() {
client := &http.Client{Timeout: time.Minute}
response, err := client.Get(uniswapTokensURL)
if err != nil {
fmt.Printf("Failed to fetch tokens: %v\n", err)
return
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
fmt.Printf("Failed to read tokens: %v\n", err)
return
}
_, err = validateDocument(string(body), tokenListSchemaURL)
if err != nil {
fmt.Printf("Failed to validate token list against schema: %v\n", err)
return
}
tokens, err := bytesToTokens(body)
if err != nil {
fmt.Printf("Failed to parse token list: %v\n", err)
return
}
tmpl := template.Must(template.New("tokens").Parse(templateText))
// Create the output Go file
file, err := os.Create("uniswap.go")
if err != nil {
fmt.Printf("Failed to create go file: %v\n", err)
return
}
defer file.Close()
// Execute the template with the tokens data and write the result to the file
err = tmpl.Execute(file, struct{ Tokens []*token.Token }{Tokens: tokens})
if err != nil {
fmt.Printf("Failed to write file: %v\n", err)
return
}
}

View File

@ -0,0 +1,3 @@
package token
//go:generate go run ./downloader/main.go

View File

@ -47,12 +47,13 @@ func (t *Token) IsNative() bool {
// Manager is used for accessing token store. It changes the token store based on overridden tokens
type Manager struct {
db *sql.DB
RPCClient *rpc.Client
networkManager *network.Manager
stores []store
tokenList []*Token
tokenMap storeMap
db *sql.DB
RPCClient *rpc.Client
networkManager *network.Manager
stores []store
tokenList []*Token
tokenMap storeMap
areTokensFetched bool
}
func NewTokenManager(
@ -61,8 +62,7 @@ func NewTokenManager(
networkManager *network.Manager,
) *Manager {
// Order of stores is important when merging token lists. The former prevale
tokenManager := &Manager{db, RPCClient, networkManager, []store{newUniswapStore(), newDefaultStore()}, nil, nil}
tokenManager := &Manager{db, RPCClient, networkManager, []store{newUniswapStore(), newDefaultStore()}, nil, nil, false}
return tokenManager
}
@ -104,7 +104,7 @@ func (tm *Manager) inStore(address common.Address, chainID uint64) bool {
return true
}
if !tm.areTokensFetched() {
if !tm.areTokensFetched {
tm.fetchTokens()
}
@ -117,16 +117,6 @@ func (tm *Manager) inStore(address common.Address, chainID uint64) bool {
return ok
}
func (tm *Manager) areTokensFetched() bool {
for _, store := range tm.stores {
if !store.areTokensFetched() {
return false
}
}
return true
}
func (tm *Manager) fetchTokens() {
tm.tokenList = nil
tm.tokenMap = nil
@ -137,11 +127,7 @@ func (tm *Manager) fetchTokens() {
}
for _, store := range tm.stores {
tokens, err := store.GetTokens()
if err != nil {
log.Error("can't fetch tokens from store", "error", err)
continue
}
tokens := store.GetTokens()
validTokens := make([]*Token, 0)
for _, token := range tokens {
for _, network := range networks {
@ -154,7 +140,7 @@ func (tm *Manager) fetchTokens() {
tm.tokenList = mergeTokenLists([][]*Token{tm.tokenList, validTokens})
}
tm.areTokensFetched = true
tm.tokenMap = toTokenMap(tm.tokenList)
}
@ -230,7 +216,7 @@ func (tm *Manager) GetAllTokensAndNativeCurrencies() ([]*Token, error) {
}
func (tm *Manager) GetAllTokens() ([]*Token, error) {
if !tm.areTokensFetched() {
if !tm.areTokensFetched {
tm.fetchTokens()
}
@ -259,7 +245,7 @@ func (tm *Manager) GetTokensByChainIDs(chainIDs []uint64) ([]*Token, error) {
}
func (tm *Manager) GetTokens(chainID uint64) ([]*Token, error) {
if !tm.areTokensFetched() {
if !tm.areTokensFetched {
tm.fetchTokens()
}

View File

@ -15,7 +15,7 @@ import (
func setupTestTokenDB(t *testing.T) (*Manager, func()) {
db, err := appdatabase.InitializeDB(":memory:", "wallet-token-tests", 1)
require.NoError(t, err)
return &Manager{db, nil, nil, nil, nil, nil}, func() {
return &Manager{db, nil, nil, nil, nil, nil, false}, func() {
require.NoError(t, db.Close())
}
}
@ -100,12 +100,10 @@ func TestTokenOverride(t *testing.T) {
}
testStore := &DefaultStore{
tokenList,
false,
}
overrideTokensInPlace(networks, testStore.tokenList)
tokens, err := testStore.GetTokens()
require.NoError(t, err)
tokens := testStore.GetTokens()
tokenMap := toTokenMap(tokens)
_, found := tokenMap[1][common.Address{1}]
require.False(t, found)

View File

@ -1,11 +1,6 @@
package token
import (
"encoding/json"
"errors"
"github.com/xeipuuv/gojsonschema"
"github.com/ethereum/go-ethereum/common"
)
@ -13,8 +8,7 @@ type addressTokenMap = map[common.Address]*Token
type storeMap = map[uint64]addressTokenMap
type store interface {
GetTokens() ([]*Token, error)
areTokensFetched() bool
GetTokens() []*Token
}
var tokenPeg = map[string]string{
@ -38,38 +32,6 @@ func GetTokenPegSymbol(symbol string) string {
return tokenPeg[symbol]
}
func validateDocument(doc string, schemaURL string) (bool, error) {
schemaLoader := gojsonschema.NewReferenceLoader(schemaURL)
docLoader := gojsonschema.NewStringLoader(doc)
result, err := gojsonschema.Validate(schemaLoader, docLoader)
if err != nil {
return false, err
}
if !result.Valid() {
return false, errors.New("Token list does not match schema")
}
return true, nil
}
func bytesToTokens(tokenListData []byte) ([]*Token, error) {
var objmap map[string]json.RawMessage
err := json.Unmarshal(tokenListData, &objmap)
if err != nil {
return nil, err
}
var tokens []*Token
err = json.Unmarshal(objmap["tokens"], &tokens)
if err != nil {
return nil, err
}
return tokens, nil
}
func toTokenMap(tokens []*Token) storeMap {
tokenMap := storeMap{}
@ -87,8 +49,7 @@ func toTokenMap(tokens []*Token) storeMap {
}
type DefaultStore struct {
tokenList []*Token
tokensFetched bool
tokenList []*Token
}
func newDefaultStore() *DefaultStore {
@ -1711,15 +1672,9 @@ func newDefaultStore() *DefaultStore {
ChainID: 421613,
},
},
tokensFetched: false,
}
}
func (ts *DefaultStore) GetTokens() ([]*Token, error) {
ts.tokensFetched = true
return ts.tokenList, nil
}
func (ts *DefaultStore) areTokensFetched() bool {
return ts.tokensFetched
func (ts *DefaultStore) GetTokens() []*Token {
return ts.tokenList
}

File diff suppressed because it is too large Load Diff

View File

@ -1,54 +1,12 @@
package token
import (
"io/ioutil"
"net/http"
"time"
)
type uniswapStore struct {
client *http.Client
tokensFetched bool
}
const uniswapTokensURL = "https://gateway.ipfs.io/ipns/tokens.uniswap.org" // nolint:gosec
const tokenListSchemaURL = "https://uniswap.org/tokenlist.schema.json" // nolint:gosec
func newUniswapStore() *uniswapStore {
return &uniswapStore{client: &http.Client{Timeout: time.Minute}, tokensFetched: false}
return &uniswapStore{}
}
func (ts *uniswapStore) doQuery(url string) (*http.Response, error) {
return ts.client.Get(url)
}
func (ts *uniswapStore) areTokensFetched() bool {
return ts.tokensFetched
}
func (ts *uniswapStore) GetTokens() ([]*Token, error) {
resp, err := ts.doQuery(uniswapTokensURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// In an unlikely case when token list is fetched fine,
// but fails to validate against the schema, we don't want
// to refetch the tokens on every GetTokens call as it will
// still fail but will be wasting CPU cycles until restart,
// so lets keep tokensFetched before validate() call
ts.tokensFetched = true
_, err = validateDocument(string(body), tokenListSchemaURL)
if err != nil {
return nil, err
}
return bytesToTokens(body)
func (ts *uniswapStore) GetTokens() []*Token {
return uniswapTokens
}