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 }