github.com/status-im/status-go@v1.1.0/services/wallet/token/token_test.go (about)

     1  package token
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math/big"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/golang/mock/gomock"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	"github.com/ethereum/go-ethereum/common"
    15  	"github.com/ethereum/go-ethereum/event"
    16  	gethrpc "github.com/ethereum/go-ethereum/rpc"
    17  
    18  	"github.com/status-im/status-go/appdatabase"
    19  	"github.com/status-im/status-go/multiaccounts/accounts"
    20  	"github.com/status-im/status-go/params"
    21  	"github.com/status-im/status-go/rpc"
    22  	"github.com/status-im/status-go/rpc/network"
    23  	mediaserver "github.com/status-im/status-go/server"
    24  	"github.com/status-im/status-go/services/accounts/accountsevent"
    25  	"github.com/status-im/status-go/services/wallet/bigint"
    26  	"github.com/status-im/status-go/services/wallet/community"
    27  
    28  	"github.com/status-im/status-go/t/helpers"
    29  	"github.com/status-im/status-go/t/utils"
    30  	"github.com/status-im/status-go/transactions/fake"
    31  	"github.com/status-im/status-go/walletdatabase"
    32  )
    33  
    34  func setupTestTokenDB(t *testing.T) (*Manager, func()) {
    35  	db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
    36  	require.NoError(t, err)
    37  
    38  	return &Manager{
    39  			db:                   db,
    40  			RPCClient:            nil,
    41  			ContractMaker:        nil,
    42  			networkManager:       nil,
    43  			stores:               nil,
    44  			communityTokensDB:    nil,
    45  			communityManager:     nil,
    46  			tokenBalancesStorage: NewPersistence(db),
    47  		}, func() {
    48  			require.NoError(t, db.Close())
    49  		}
    50  }
    51  
    52  func upsertCommunityToken(t *testing.T, token *Token, manager *Manager) {
    53  	require.NotNil(t, token.CommunityData)
    54  
    55  	err := manager.UpsertCustom(*token)
    56  	require.NoError(t, err)
    57  
    58  	// Community ID is only discovered by calling contract, so must be updated manually
    59  	_, err = manager.db.Exec("UPDATE tokens SET community_id = ? WHERE address = ?", token.CommunityData.ID, token.Address)
    60  	require.NoError(t, err)
    61  }
    62  
    63  func TestCustoms(t *testing.T) {
    64  	manager, stop := setupTestTokenDB(t)
    65  	defer stop()
    66  
    67  	rst, err := manager.GetCustoms(false)
    68  	require.NoError(t, err)
    69  	require.Nil(t, rst)
    70  
    71  	token := Token{
    72  		Address:  common.Address{1},
    73  		Name:     "Zilliqa",
    74  		Symbol:   "ZIL",
    75  		Decimals: 12,
    76  		ChainID:  777,
    77  	}
    78  
    79  	err = manager.UpsertCustom(token)
    80  	require.NoError(t, err)
    81  
    82  	rst, err = manager.GetCustoms(false)
    83  	require.NoError(t, err)
    84  	require.Equal(t, 1, len(rst))
    85  	require.Equal(t, token, *rst[0])
    86  
    87  	err = manager.DeleteCustom(777, token.Address)
    88  	require.NoError(t, err)
    89  
    90  	rst, err = manager.GetCustoms(false)
    91  	require.NoError(t, err)
    92  	require.Equal(t, 0, len(rst))
    93  }
    94  
    95  func TestCommunityTokens(t *testing.T) {
    96  	manager, stop := setupTestTokenDB(t)
    97  	defer stop()
    98  
    99  	rst, err := manager.GetCustoms(true)
   100  	require.NoError(t, err)
   101  	require.Nil(t, rst)
   102  
   103  	token := Token{
   104  		Address:  common.Address{1},
   105  		Name:     "Zilliqa",
   106  		Symbol:   "ZIL",
   107  		Decimals: 12,
   108  		ChainID:  777,
   109  	}
   110  
   111  	err = manager.UpsertCustom(token)
   112  	require.NoError(t, err)
   113  
   114  	communityToken := Token{
   115  		Address:  common.Address{2},
   116  		Name:     "Communitia",
   117  		Symbol:   "COM",
   118  		Decimals: 12,
   119  		ChainID:  777,
   120  		CommunityData: &community.Data{
   121  			ID: "random_community_id",
   122  		},
   123  	}
   124  
   125  	upsertCommunityToken(t, &communityToken, manager)
   126  
   127  	rst, err = manager.GetCustoms(false)
   128  	require.NoError(t, err)
   129  	require.Equal(t, 2, len(rst))
   130  	require.Equal(t, token, *rst[0])
   131  	require.Equal(t, communityToken, *rst[1])
   132  
   133  	rst, err = manager.GetCustoms(true)
   134  	require.NoError(t, err)
   135  	require.Equal(t, 1, len(rst))
   136  	require.Equal(t, communityToken, *rst[0])
   137  }
   138  
   139  func toTokenMap(tokens []*Token) storeMap {
   140  	tokenMap := storeMap{}
   141  
   142  	for _, token := range tokens {
   143  		addTokMap := tokenMap[token.ChainID]
   144  		if addTokMap == nil {
   145  			addTokMap = make(addressTokenMap)
   146  		}
   147  
   148  		addTokMap[token.Address] = token
   149  		tokenMap[token.ChainID] = addTokMap
   150  	}
   151  
   152  	return tokenMap
   153  }
   154  
   155  func TestTokenOverride(t *testing.T) {
   156  	networks := []params.Network{
   157  		{
   158  			ChainID:   1,
   159  			ChainName: "TestChain1",
   160  			TokenOverrides: []params.TokenOverride{
   161  				{
   162  					Symbol:  "SNT",
   163  					Address: common.Address{11},
   164  				},
   165  			},
   166  		}, {
   167  			ChainID:   2,
   168  			ChainName: "TestChain2",
   169  			TokenOverrides: []params.TokenOverride{
   170  				{
   171  					Symbol:  "STT",
   172  					Address: common.Address{33},
   173  				},
   174  			},
   175  		},
   176  	}
   177  
   178  	tokenList := []*Token{
   179  		&Token{
   180  			Address: common.Address{1},
   181  			Symbol:  "SNT",
   182  			ChainID: 1,
   183  		},
   184  		&Token{
   185  			Address: common.Address{2},
   186  			Symbol:  "TNT",
   187  			ChainID: 1,
   188  		},
   189  		&Token{
   190  			Address: common.Address{3},
   191  			Symbol:  "STT",
   192  			ChainID: 2,
   193  		},
   194  		&Token{
   195  			Address: common.Address{4},
   196  			Symbol:  "TTT",
   197  			ChainID: 2,
   198  		},
   199  	}
   200  	testStore := &DefaultStore{
   201  		tokenList,
   202  	}
   203  
   204  	overrideTokensInPlace(networks, tokenList)
   205  	tokens := testStore.GetTokens()
   206  	tokenMap := toTokenMap(tokens)
   207  	_, found := tokenMap[1][common.Address{1}]
   208  	require.False(t, found)
   209  	require.Equal(t, common.Address{11}, tokenMap[1][common.Address{11}].Address)
   210  	require.Equal(t, common.Address{2}, tokenMap[1][common.Address{2}].Address)
   211  	_, found = tokenMap[2][common.Address{3}]
   212  	require.False(t, found)
   213  	require.Equal(t, common.Address{33}, tokenMap[2][common.Address{33}].Address)
   214  	require.Equal(t, common.Address{4}, tokenMap[2][common.Address{4}].Address)
   215  }
   216  
   217  func TestMarkAsPreviouslyOwnedToken(t *testing.T) {
   218  	manager, stop := setupTestTokenDB(t)
   219  	defer stop()
   220  
   221  	owner := common.HexToAddress("0x1234567890abcdef")
   222  	token := &Token{
   223  		Address:  common.HexToAddress("0xabcdef1234567890"),
   224  		Name:     "TestToken",
   225  		Symbol:   "TT",
   226  		Decimals: 18,
   227  		ChainID:  1,
   228  	}
   229  
   230  	isFirst, err := manager.MarkAsPreviouslyOwnedToken(nil, owner)
   231  	require.Error(t, err)
   232  	require.False(t, isFirst)
   233  
   234  	isFirst, err = manager.MarkAsPreviouslyOwnedToken(token, common.Address{})
   235  	require.Error(t, err)
   236  	require.False(t, isFirst)
   237  
   238  	isFirst, err = manager.MarkAsPreviouslyOwnedToken(token, owner)
   239  	require.NoError(t, err)
   240  	require.True(t, isFirst)
   241  
   242  	// Verify that the token balance was inserted correctly
   243  	var count int
   244  	err = manager.db.QueryRow(`SELECT count(*) FROM token_balances`).Scan(&count)
   245  	require.NoError(t, err)
   246  	require.Equal(t, 1, count)
   247  
   248  	token.Name = "123"
   249  
   250  	isFirst, err = manager.MarkAsPreviouslyOwnedToken(token, owner)
   251  	require.NoError(t, err)
   252  	require.False(t, isFirst)
   253  
   254  	// Not updated because already exists
   255  	err = manager.db.QueryRow(`SELECT count(*) FROM token_balances`).Scan(&count)
   256  	require.NoError(t, err)
   257  	require.Equal(t, 1, count)
   258  
   259  	token.ChainID = 2
   260  
   261  	isFirst, err = manager.MarkAsPreviouslyOwnedToken(token, owner)
   262  	require.NoError(t, err)
   263  
   264  	// Same token on different chains counts as different token
   265  	err = manager.db.QueryRow(`SELECT count(*) FROM token_balances`).Scan(&count)
   266  	require.NoError(t, err)
   267  	require.Equal(t, 2, count)
   268  	require.True(t, isFirst)
   269  }
   270  
   271  func TestGetTokenHistoricalBalance(t *testing.T) {
   272  	manager, stop := setupTestTokenDB(t)
   273  	defer stop()
   274  
   275  	account := common.HexToAddress("0x1234567890abcdef")
   276  	chainID := uint64(1)
   277  	testSymbol := "TEST"
   278  	block := int64(1)
   279  	timestamp := int64(1629878400) // Replace with desired timestamp
   280  	historyBalance := big.NewInt(0)
   281  
   282  	// Test case when no rows are returned
   283  	balance, err := manager.GetTokenHistoricalBalance(account, chainID, testSymbol, timestamp)
   284  	require.NoError(t, err)
   285  	require.Nil(t, balance)
   286  
   287  	// Test case when a row is returned
   288  	historyBalance.SetInt64(int64(100))
   289  	_, 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)
   290  	require.NoError(t, err)
   291  
   292  	expectedBalance := big.NewInt(100)
   293  	balance, err = manager.GetTokenHistoricalBalance(account, chainID, testSymbol, timestamp)
   294  	require.NoError(t, err)
   295  	require.Equal(t, expectedBalance, balance)
   296  
   297  	// Test multiple values. Must return the most recent one
   298  	historyBalance.SetInt64(int64(100))
   299  	_, 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)
   300  	require.NoError(t, err)
   301  
   302  	historyBalance.SetInt64(int64(50))
   303  	symbol := "TEST2"
   304  	_, 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)
   305  	require.NoError(t, err)
   306  
   307  	historyBalance.SetInt64(int64(50))
   308  	chainID = uint64(2)
   309  	_, 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)
   310  	require.NoError(t, err)
   311  
   312  	chainID = uint64(1)
   313  	balance, err = manager.GetTokenHistoricalBalance(account, chainID, testSymbol, timestamp)
   314  	require.NoError(t, err)
   315  	require.Equal(t, expectedBalance, balance)
   316  }
   317  
   318  func Test_removeTokenBalanceOnEventAccountRemoved(t *testing.T) {
   319  	appDB, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
   320  	require.NoError(t, err)
   321  
   322  	walletDB, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
   323  	require.NoError(t, err)
   324  
   325  	accountsDB, err := accounts.NewDB(appDB)
   326  	require.NoError(t, err)
   327  
   328  	address := common.HexToAddress("0x1234")
   329  	accountFeed := event.Feed{}
   330  	chainID := uint64(1)
   331  	txServiceMockCtrl := gomock.NewController(t)
   332  	server, _ := fake.NewTestServer(txServiceMockCtrl)
   333  	client := gethrpc.DialInProc(server)
   334  	rpcClient, _ := rpc.NewClient(client, chainID, params.UpstreamRPCConfig{}, nil, appDB, nil)
   335  	rpcClient.UpstreamChainID = chainID
   336  	nm := network.NewManager(appDB)
   337  	mediaServer, err := mediaserver.NewMediaServer(appDB, nil, nil, walletDB)
   338  	require.NoError(t, err)
   339  
   340  	manager := NewTokenManager(walletDB, rpcClient, nil, nm, appDB, mediaServer, nil, &accountFeed, accountsDB, NewPersistence(walletDB))
   341  
   342  	// Insert balances for address
   343  	marked, err := manager.MarkAsPreviouslyOwnedToken(&Token{
   344  		Address:  common.HexToAddress("0x1234"),
   345  		Symbol:   "Dummy",
   346  		Decimals: 18,
   347  		ChainID:  1,
   348  	}, address)
   349  	require.NoError(t, err)
   350  	require.True(t, marked)
   351  
   352  	tokenByAddress, err := manager.GetPreviouslyOwnedTokens()
   353  	require.NoError(t, err)
   354  	require.Len(t, tokenByAddress, 1)
   355  
   356  	// Start service
   357  	manager.startAccountsWatcher()
   358  
   359  	// Watching accounts must start before sending event.
   360  	// To avoid running goroutine immediately and let the controller subscribe first,
   361  	// use any delay.
   362  	group := sync.WaitGroup{}
   363  	group.Add(1)
   364  	go func() {
   365  		defer group.Done()
   366  		time.Sleep(1 * time.Millisecond)
   367  
   368  		accountFeed.Send(accountsevent.Event{
   369  			Type:     accountsevent.EventTypeRemoved,
   370  			Accounts: []common.Address{address},
   371  		})
   372  
   373  		require.NoError(t, utils.Eventually(func() error {
   374  			tokenByAddress, err := manager.GetPreviouslyOwnedTokens()
   375  			if err == nil && len(tokenByAddress) == 0 {
   376  				return nil
   377  			}
   378  			return errors.New("Token not removed")
   379  		}, 100*time.Millisecond, 10*time.Millisecond))
   380  	}()
   381  
   382  	group.Wait()
   383  
   384  	// Stop service
   385  	txServiceMockCtrl.Finish()
   386  	server.Stop()
   387  	manager.stopAccountsWatcher()
   388  }
   389  
   390  func Test_tokensListsValidity(t *testing.T) {
   391  	appDB, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
   392  	require.NoError(t, err)
   393  
   394  	walletDB, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
   395  	require.NoError(t, err)
   396  
   397  	accountsDB, err := accounts.NewDB(appDB)
   398  	require.NoError(t, err)
   399  
   400  	nm := network.NewManager(appDB)
   401  
   402  	manager := NewTokenManager(walletDB, nil, nil, nm, appDB, nil, nil, nil, accountsDB, NewPersistence(walletDB))
   403  	require.NotNil(t, manager)
   404  
   405  	tokensListWrapper := manager.GetList()
   406  	require.NotNil(t, tokensListWrapper)
   407  	allLists := tokensListWrapper.Data
   408  	require.Greater(t, len(allLists), 0)
   409  
   410  	tmpMap := make(map[string][]*Token)
   411  	for _, list := range allLists {
   412  		for _, token := range list.Tokens {
   413  			key := fmt.Sprintf("%d-%s", token.ChainID, token.Symbol)
   414  			if added, ok := tmpMap[key]; ok {
   415  				found := false
   416  				for _, a := range added {
   417  					if a.Address == token.Address {
   418  						found = true
   419  						break
   420  					}
   421  				}
   422  
   423  				require.True(t, found)
   424  			} else {
   425  				tmpMap[key] = []*Token{token}
   426  			}
   427  		}
   428  	}
   429  }