status-go/services/wallet/token/token_test.go
Ivan Belyakov d07f9b5b16 fix(wallet)_: no balance chart for tokens, due to wrong symbol (ETH) used
instead.

Fixed padding points being removed from final result, regression.
Edge points not added per address as it does not make sense.
Fixed padding points number with respect to edge points number.
Padding points now duplicate previous entry.
Fixed timestamp boundaries to ignore addresses, as we want the whole
range for all passed addresses.
Fixed missing indices in balance_history table and clean up of
duplicate rows.
Removed ERC1155 from balance history sql query
2024-07-18 13:11:02 +02:00

430 lines
12 KiB
Go

package token
import (
"errors"
"fmt"
"math/big"
"sync"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
gethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/rpc/network"
mediaserver "github.com/status-im/status-go/server"
"github.com/status-im/status-go/services/accounts/accountsevent"
"github.com/status-im/status-go/services/wallet/bigint"
"github.com/status-im/status-go/services/wallet/community"
"github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/t/utils"
"github.com/status-im/status-go/transactions/fake"
"github.com/status-im/status-go/walletdatabase"
)
func setupTestTokenDB(t *testing.T) (*Manager, func()) {
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err)
return &Manager{
db: db,
RPCClient: nil,
ContractMaker: nil,
networkManager: nil,
stores: nil,
communityTokensDB: nil,
communityManager: nil,
tokenBalancesStorage: NewPersistence(db),
}, func() {
require.NoError(t, db.Close())
}
}
func upsertCommunityToken(t *testing.T, token *Token, manager *Manager) {
require.NotNil(t, token.CommunityData)
err := manager.UpsertCustom(*token)
require.NoError(t, err)
// Community ID is only discovered by calling contract, so must be updated manually
_, err = manager.db.Exec("UPDATE tokens SET community_id = ? WHERE address = ?", token.CommunityData.ID, token.Address)
require.NoError(t, err)
}
func TestCustoms(t *testing.T) {
manager, stop := setupTestTokenDB(t)
defer stop()
rst, err := manager.GetCustoms(false)
require.NoError(t, err)
require.Nil(t, rst)
token := Token{
Address: common.Address{1},
Name: "Zilliqa",
Symbol: "ZIL",
Decimals: 12,
ChainID: 777,
}
err = manager.UpsertCustom(token)
require.NoError(t, err)
rst, err = manager.GetCustoms(false)
require.NoError(t, err)
require.Equal(t, 1, len(rst))
require.Equal(t, token, *rst[0])
err = manager.DeleteCustom(777, token.Address)
require.NoError(t, err)
rst, err = manager.GetCustoms(false)
require.NoError(t, err)
require.Equal(t, 0, len(rst))
}
func TestCommunityTokens(t *testing.T) {
manager, stop := setupTestTokenDB(t)
defer stop()
rst, err := manager.GetCustoms(true)
require.NoError(t, err)
require.Nil(t, rst)
token := Token{
Address: common.Address{1},
Name: "Zilliqa",
Symbol: "ZIL",
Decimals: 12,
ChainID: 777,
}
err = manager.UpsertCustom(token)
require.NoError(t, err)
communityToken := Token{
Address: common.Address{2},
Name: "Communitia",
Symbol: "COM",
Decimals: 12,
ChainID: 777,
CommunityData: &community.Data{
ID: "random_community_id",
},
}
upsertCommunityToken(t, &communityToken, manager)
rst, err = manager.GetCustoms(false)
require.NoError(t, err)
require.Equal(t, 2, len(rst))
require.Equal(t, token, *rst[0])
require.Equal(t, communityToken, *rst[1])
rst, err = manager.GetCustoms(true)
require.NoError(t, err)
require.Equal(t, 1, len(rst))
require.Equal(t, communityToken, *rst[0])
}
func toTokenMap(tokens []*Token) storeMap {
tokenMap := storeMap{}
for _, token := range tokens {
addTokMap := tokenMap[token.ChainID]
if addTokMap == nil {
addTokMap = make(addressTokenMap)
}
addTokMap[token.Address] = token
tokenMap[token.ChainID] = addTokMap
}
return tokenMap
}
func TestTokenOverride(t *testing.T) {
networks := []params.Network{
{
ChainID: 1,
ChainName: "TestChain1",
TokenOverrides: []params.TokenOverride{
{
Symbol: "SNT",
Address: common.Address{11},
},
},
}, {
ChainID: 2,
ChainName: "TestChain2",
TokenOverrides: []params.TokenOverride{
{
Symbol: "STT",
Address: common.Address{33},
},
},
},
}
tokenList := []*Token{
&Token{
Address: common.Address{1},
Symbol: "SNT",
ChainID: 1,
},
&Token{
Address: common.Address{2},
Symbol: "TNT",
ChainID: 1,
},
&Token{
Address: common.Address{3},
Symbol: "STT",
ChainID: 2,
},
&Token{
Address: common.Address{4},
Symbol: "TTT",
ChainID: 2,
},
}
testStore := &DefaultStore{
tokenList,
}
overrideTokensInPlace(networks, tokenList)
tokens := testStore.GetTokens()
tokenMap := toTokenMap(tokens)
_, found := tokenMap[1][common.Address{1}]
require.False(t, found)
require.Equal(t, common.Address{11}, tokenMap[1][common.Address{11}].Address)
require.Equal(t, common.Address{2}, tokenMap[1][common.Address{2}].Address)
_, found = tokenMap[2][common.Address{3}]
require.False(t, found)
require.Equal(t, common.Address{33}, tokenMap[2][common.Address{33}].Address)
require.Equal(t, common.Address{4}, tokenMap[2][common.Address{4}].Address)
}
func TestMarkAsPreviouslyOwnedToken(t *testing.T) {
manager, stop := setupTestTokenDB(t)
defer stop()
owner := common.HexToAddress("0x1234567890abcdef")
token := &Token{
Address: common.HexToAddress("0xabcdef1234567890"),
Name: "TestToken",
Symbol: "TT",
Decimals: 18,
ChainID: 1,
}
isFirst, err := manager.MarkAsPreviouslyOwnedToken(nil, owner)
require.Error(t, err)
require.False(t, isFirst)
isFirst, err = manager.MarkAsPreviouslyOwnedToken(token, common.Address{})
require.Error(t, err)
require.False(t, isFirst)
isFirst, err = manager.MarkAsPreviouslyOwnedToken(token, owner)
require.NoError(t, err)
require.True(t, isFirst)
// Verify that the token balance was inserted correctly
var count int
err = manager.db.QueryRow(`SELECT count(*) FROM token_balances`).Scan(&count)
require.NoError(t, err)
require.Equal(t, 1, count)
token.Name = "123"
isFirst, err = manager.MarkAsPreviouslyOwnedToken(token, owner)
require.NoError(t, err)
require.False(t, isFirst)
// Not updated because already exists
err = manager.db.QueryRow(`SELECT count(*) FROM token_balances`).Scan(&count)
require.NoError(t, err)
require.Equal(t, 1, count)
token.ChainID = 2
isFirst, err = manager.MarkAsPreviouslyOwnedToken(token, owner)
require.NoError(t, err)
// Same token on different chains counts as different token
err = manager.db.QueryRow(`SELECT count(*) FROM token_balances`).Scan(&count)
require.NoError(t, err)
require.Equal(t, 2, count)
require.True(t, isFirst)
}
func TestGetTokenHistoricalBalance(t *testing.T) {
manager, stop := setupTestTokenDB(t)
defer stop()
account := common.HexToAddress("0x1234567890abcdef")
chainID := uint64(1)
testSymbol := "TEST"
block := int64(1)
timestamp := int64(1629878400) // Replace with desired timestamp
historyBalance := big.NewInt(0)
// Test case when no rows are returned
balance, err := manager.GetTokenHistoricalBalance(account, chainID, testSymbol, timestamp)
require.NoError(t, err)
require.Nil(t, balance)
// Test case when a row is returned
historyBalance.SetInt64(int64(100))
_, err = manager.db.Exec("INSERT INTO balance_history (currency, chain_id, address, timestamp, balance, block) VALUES (?, ?, ?, ?, ?, ?)", testSymbol, chainID, account, timestamp-100, (*bigint.SQLBigIntBytes)(historyBalance), block)
require.NoError(t, err)
expectedBalance := big.NewInt(100)
balance, err = manager.GetTokenHistoricalBalance(account, chainID, testSymbol, timestamp)
require.NoError(t, err)
require.Equal(t, expectedBalance, balance)
// Test multiple values. Must return the most recent one
historyBalance.SetInt64(int64(100))
_, err = manager.db.Exec("INSERT INTO balance_history (currency, chain_id, address, timestamp, balance, block) VALUES (?, ?, ?, ?, ?, ?)", testSymbol, chainID, account, timestamp-200, (*bigint.SQLBigIntBytes)(historyBalance), block+1)
require.NoError(t, err)
historyBalance.SetInt64(int64(50))
symbol := "TEST2"
_, err = manager.db.Exec("INSERT INTO balance_history (currency, chain_id, address, timestamp, balance, block) VALUES (?, ?, ?, ?, ?, ?)", symbol, chainID, account, timestamp-1, (*bigint.SQLBigIntBytes)(historyBalance), block)
require.NoError(t, err)
historyBalance.SetInt64(int64(50))
chainID = uint64(2)
_, err = manager.db.Exec("INSERT INTO balance_history (currency, chain_id, address, timestamp, balance, block) VALUES (?, ?, ?, ?, ?, ?)", testSymbol, chainID, account, timestamp-1, (*bigint.SQLBigIntBytes)(historyBalance), block+2)
require.NoError(t, err)
chainID = uint64(1)
balance, err = manager.GetTokenHistoricalBalance(account, chainID, testSymbol, timestamp)
require.NoError(t, err)
require.Equal(t, expectedBalance, balance)
}
func Test_removeTokenBalanceOnEventAccountRemoved(t *testing.T) {
appDB, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
require.NoError(t, err)
walletDB, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err)
accountsDB, err := accounts.NewDB(appDB)
require.NoError(t, err)
address := common.HexToAddress("0x1234")
accountFeed := event.Feed{}
chainID := uint64(1)
txServiceMockCtrl := gomock.NewController(t)
server, _ := fake.NewTestServer(txServiceMockCtrl)
client := gethrpc.DialInProc(server)
rpcClient, _ := rpc.NewClient(client, chainID, params.UpstreamRPCConfig{}, nil, appDB)
rpcClient.UpstreamChainID = chainID
nm := network.NewManager(appDB)
mediaServer, err := mediaserver.NewMediaServer(appDB, nil, nil, walletDB)
require.NoError(t, err)
manager := NewTokenManager(walletDB, rpcClient, nil, nm, appDB, mediaServer, nil, &accountFeed, accountsDB, NewPersistence(walletDB))
// Insert balances for address
marked, err := manager.MarkAsPreviouslyOwnedToken(&Token{
Address: common.HexToAddress("0x1234"),
Symbol: "Dummy",
Decimals: 18,
ChainID: 1,
}, address)
require.NoError(t, err)
require.True(t, marked)
tokenByAddress, err := manager.GetPreviouslyOwnedTokens()
require.NoError(t, err)
require.Len(t, tokenByAddress, 1)
// Start service
manager.startAccountsWatcher()
// Watching accounts must start before sending event.
// To avoid running goroutine immediately and let the controller subscribe first,
// use any delay.
group := sync.WaitGroup{}
group.Add(1)
go func() {
defer group.Done()
time.Sleep(1 * time.Millisecond)
accountFeed.Send(accountsevent.Event{
Type: accountsevent.EventTypeRemoved,
Accounts: []common.Address{address},
})
require.NoError(t, utils.Eventually(func() error {
tokenByAddress, err := manager.GetPreviouslyOwnedTokens()
if err == nil && len(tokenByAddress) == 0 {
return nil
}
return errors.New("Token not removed")
}, 100*time.Millisecond, 10*time.Millisecond))
}()
group.Wait()
// Stop service
txServiceMockCtrl.Finish()
server.Stop()
manager.stopAccountsWatcher()
}
func Test_tokensListsValidity(t *testing.T) {
appDB, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
require.NoError(t, err)
walletDB, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err)
accountsDB, err := accounts.NewDB(appDB)
require.NoError(t, err)
nm := network.NewManager(appDB)
manager := NewTokenManager(walletDB, nil, nil, nm, appDB, nil, nil, nil, accountsDB, NewPersistence(walletDB))
require.NotNil(t, manager)
tokensListWrapper := manager.GetList()
require.NotNil(t, tokensListWrapper)
allLists := tokensListWrapper.Data
require.Greater(t, len(allLists), 0)
tmpMap := make(map[string][]*Token)
for _, list := range allLists {
for _, token := range list.Tokens {
key := fmt.Sprintf("%d-%s", token.ChainID, token.Symbol)
if added, ok := tmpMap[key]; ok {
found := false
for _, a := range added {
if a.Address == token.Address {
found = true
break
}
}
require.True(t, found)
} else {
tmpMap[key] = []*Token{token}
}
}
}
}