github.com/status-im/status-go@v1.1.0/services/wallet/collectibles/ownership_db_test.go (about) 1 package collectibles 2 3 import ( 4 "math/big" 5 "testing" 6 7 "github.com/ethereum/go-ethereum/common" 8 9 "github.com/status-im/status-go/services/wallet/bigint" 10 w_common "github.com/status-im/status-go/services/wallet/common" 11 "github.com/status-im/status-go/services/wallet/thirdparty" 12 "github.com/status-im/status-go/services/wallet/transfer" 13 "github.com/status-im/status-go/t/helpers" 14 "github.com/status-im/status-go/walletdatabase" 15 16 "github.com/stretchr/testify/require" 17 ) 18 19 func setupOwnershipDBTest(t *testing.T) (*OwnershipDB, func()) { 20 db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) 21 require.NoError(t, err) 22 return NewOwnershipDB(db), func() { 23 require.NoError(t, db.Close()) 24 } 25 } 26 27 func generateTestCollectibles(offset int, count int) (result thirdparty.TokenBalancesPerContractAddress) { 28 result = make(thirdparty.TokenBalancesPerContractAddress) 29 for i := offset; i < offset+count; i++ { 30 contractAddress := common.BigToAddress(big.NewInt(int64(i % 10))) 31 tokenID := &bigint.BigInt{Int: big.NewInt(int64(i))} 32 33 result[contractAddress] = append(result[contractAddress], thirdparty.TokenBalance{ 34 TokenID: tokenID, 35 Balance: &bigint.BigInt{Int: big.NewInt(int64(i%5 + 1))}, 36 }) 37 } 38 return result 39 } 40 41 func testCollectiblesToList(chainID w_common.ChainID, balances thirdparty.TokenBalancesPerContractAddress) (result []thirdparty.CollectibleUniqueID) { 42 result = make([]thirdparty.CollectibleUniqueID, 0, len(balances)) 43 for contractAddress, balances := range balances { 44 for _, balance := range balances { 45 newCollectible := thirdparty.CollectibleUniqueID{ 46 ContractID: thirdparty.ContractID{ 47 ChainID: chainID, 48 Address: contractAddress, 49 }, 50 TokenID: balance.TokenID, 51 } 52 result = append(result, newCollectible) 53 } 54 } 55 return result 56 } 57 58 func TestUpdateOwnership(t *testing.T) { 59 oDB, cleanDB := setupOwnershipDBTest(t) 60 defer cleanDB() 61 62 chainID0 := w_common.ChainID(0) 63 chainID1 := w_common.ChainID(1) 64 chainID2 := w_common.ChainID(2) 65 66 ownerAddress1 := common.HexToAddress("0x1234") 67 ownedBalancesChain0 := generateTestCollectibles(0, 10) 68 ownedListChain0 := testCollectiblesToList(chainID0, ownedBalancesChain0) 69 timestampChain0 := int64(1234567890) 70 ownedBalancesChain1 := generateTestCollectibles(0, 15) 71 ownedListChain1 := testCollectiblesToList(chainID1, ownedBalancesChain1) 72 timestampChain1 := int64(1234567891) 73 74 ownedList1 := append(ownedListChain0, ownedListChain1...) 75 76 ownerAddress2 := common.HexToAddress("0x5678") 77 ownedBalancesChain2 := generateTestCollectibles(0, 20) 78 ownedListChain2 := testCollectiblesToList(chainID2, ownedBalancesChain2) 79 timestampChain2 := int64(1234567892) 80 81 ownedList2 := ownedListChain2 82 83 ownerAddress3 := common.HexToAddress("0xABCD") 84 ownedBalancesChain1b := generateTestCollectibles(len(ownedListChain1), 5) 85 ownedListChain1b := testCollectiblesToList(chainID1, ownedBalancesChain1b) 86 timestampChain1b := timestampChain1 - 100 87 ownedBalancesChain2b := generateTestCollectibles(len(ownedListChain2), 20) 88 // Add one collectible that is already owned by ownerAddress2 89 commonChainID := chainID2 90 var commonContractAddress common.Address 91 var commonTokenID *bigint.BigInt 92 var commonBalanceAddress2 *bigint.BigInt 93 commonBalanceAddress3 := &bigint.BigInt{Int: big.NewInt(5)} 94 95 for contractAddress, balances := range ownedBalancesChain2 { 96 for _, balance := range balances { 97 commonContractAddress = contractAddress 98 commonTokenID = balance.TokenID 99 commonBalanceAddress2 = balance.Balance 100 101 newBalance := thirdparty.TokenBalance{ 102 TokenID: commonTokenID, 103 Balance: commonBalanceAddress3, 104 } 105 ownedBalancesChain2b[commonContractAddress] = append(ownedBalancesChain2b[commonContractAddress], newBalance) 106 break 107 } 108 break 109 } 110 111 ownedListChain2b := testCollectiblesToList(chainID2, ownedBalancesChain2b) 112 timestampChain2b := timestampChain2 + 100 113 114 ownedList3 := append(ownedListChain1b, ownedListChain2b...) 115 116 allChains := []w_common.ChainID{chainID0, chainID1, chainID2} 117 allOwnerAddresses := []common.Address{ownerAddress1, ownerAddress2, ownerAddress3} 118 allCollectibles := append(ownedList1[1:], ownedList2...) 119 allCollectibles = append(allCollectibles, ownedList3[:len(ownedList3)-1]...) // the last element of ownerdList3 is a duplicate of the first element of ownedList2 120 121 randomAddress := common.HexToAddress("0xFFFF") 122 123 var err error 124 var removedIDs, updatedIDs, insertedIDs []thirdparty.CollectibleUniqueID 125 126 var loadedTimestamp int64 127 var loadedList []thirdparty.CollectibleUniqueID 128 129 loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID0) 130 require.NoError(t, err) 131 require.Equal(t, InvalidTimestamp, loadedTimestamp) 132 133 loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID0) 134 require.NoError(t, err) 135 require.Equal(t, InvalidTimestamp, loadedTimestamp) 136 137 loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID1) 138 require.NoError(t, err) 139 require.Equal(t, InvalidTimestamp, loadedTimestamp) 140 141 loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID1) 142 require.NoError(t, err) 143 require.Equal(t, InvalidTimestamp, loadedTimestamp) 144 145 loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress2, chainID2) 146 require.NoError(t, err) 147 require.Equal(t, InvalidTimestamp, loadedTimestamp) 148 149 loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID2) 150 require.NoError(t, err) 151 require.Equal(t, InvalidTimestamp, loadedTimestamp) 152 153 removedIDs, updatedIDs, insertedIDs, err = oDB.Update(chainID0, ownerAddress1, ownedBalancesChain0, timestampChain0) 154 require.NoError(t, err) 155 require.Empty(t, removedIDs) 156 require.Empty(t, updatedIDs) 157 require.ElementsMatch(t, ownedListChain0, insertedIDs) 158 159 loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID0) 160 require.NoError(t, err) 161 require.Equal(t, timestampChain0, loadedTimestamp) 162 163 loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID0) 164 require.NoError(t, err) 165 require.Equal(t, timestampChain0, loadedTimestamp) 166 167 loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID1) 168 require.NoError(t, err) 169 require.Equal(t, InvalidTimestamp, loadedTimestamp) 170 171 loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID1) 172 require.NoError(t, err) 173 require.Equal(t, InvalidTimestamp, loadedTimestamp) 174 175 loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress2, chainID2) 176 require.NoError(t, err) 177 require.Equal(t, InvalidTimestamp, loadedTimestamp) 178 179 loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID2) 180 require.NoError(t, err) 181 require.Equal(t, InvalidTimestamp, loadedTimestamp) 182 183 removedIDs, updatedIDs, insertedIDs, err = oDB.Update(chainID1, ownerAddress1, ownedBalancesChain1, timestampChain1) 184 require.NoError(t, err) 185 require.Empty(t, removedIDs) 186 require.Empty(t, updatedIDs) 187 require.ElementsMatch(t, ownedListChain1, insertedIDs) 188 189 removedIDs, updatedIDs, insertedIDs, err = oDB.Update(chainID2, ownerAddress2, ownedBalancesChain2, timestampChain2) 190 require.NoError(t, err) 191 require.Empty(t, removedIDs) 192 require.Empty(t, updatedIDs) 193 require.ElementsMatch(t, ownedListChain2, insertedIDs) 194 195 removedIDs, updatedIDs, insertedIDs, err = oDB.Update(chainID1, ownerAddress3, ownedBalancesChain1b, timestampChain1b) 196 require.NoError(t, err) 197 require.Empty(t, removedIDs) 198 require.Empty(t, updatedIDs) 199 require.ElementsMatch(t, ownedListChain1b, insertedIDs) 200 201 removedIDs, updatedIDs, insertedIDs, err = oDB.Update(chainID2, ownerAddress3, ownedBalancesChain2b, timestampChain2b) 202 require.NoError(t, err) 203 require.Empty(t, removedIDs) 204 require.Empty(t, updatedIDs) 205 require.ElementsMatch(t, ownedListChain2b, insertedIDs) 206 207 loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID0) 208 require.NoError(t, err) 209 require.Equal(t, timestampChain0, loadedTimestamp) 210 211 loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID0) 212 require.NoError(t, err) 213 require.Equal(t, timestampChain0, loadedTimestamp) 214 215 loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress1, chainID1) 216 require.NoError(t, err) 217 require.Equal(t, timestampChain1, loadedTimestamp) 218 219 loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress3, chainID1) 220 require.NoError(t, err) 221 require.Equal(t, timestampChain1b, loadedTimestamp) 222 223 loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID1) 224 require.NoError(t, err) 225 require.Equal(t, timestampChain1, loadedTimestamp) 226 227 loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress2, chainID2) 228 require.NoError(t, err) 229 require.Equal(t, timestampChain2, loadedTimestamp) 230 231 loadedTimestamp, err = oDB.GetOwnershipUpdateTimestamp(ownerAddress3, chainID2) 232 require.NoError(t, err) 233 require.Equal(t, timestampChain2b, loadedTimestamp) 234 235 loadedTimestamp, err = oDB.GetLatestOwnershipUpdateTimestamp(chainID2) 236 require.NoError(t, err) 237 require.Equal(t, timestampChain2b, loadedTimestamp) 238 239 loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID0}, []common.Address{ownerAddress1}, 0, len(allCollectibles)) 240 require.NoError(t, err) 241 require.ElementsMatch(t, ownedListChain0, loadedList) 242 243 loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID0, chainID1}, []common.Address{ownerAddress1, randomAddress}, 0, len(allCollectibles)) 244 require.NoError(t, err) 245 require.ElementsMatch(t, ownedList1, loadedList) 246 247 loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID2}, []common.Address{ownerAddress2}, 0, len(allCollectibles)) 248 require.NoError(t, err) 249 require.ElementsMatch(t, ownedList2, loadedList) 250 251 loadedList, err = oDB.GetOwnedCollectibles(allChains, allOwnerAddresses, 0, len(allCollectibles)) 252 require.NoError(t, err) 253 require.Equal(t, len(allCollectibles), len(loadedList)) 254 255 loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID0}, []common.Address{randomAddress}, 0, len(allCollectibles)) 256 require.NoError(t, err) 257 require.Empty(t, loadedList) 258 259 // Test GetOwnership for common token 260 commonID := thirdparty.CollectibleUniqueID{ 261 ContractID: thirdparty.ContractID{ 262 ChainID: commonChainID, 263 Address: commonContractAddress, 264 }, 265 TokenID: commonTokenID, 266 } 267 loadedOwnership, err := oDB.GetOwnership(commonID) 268 require.NoError(t, err) 269 270 expectedOwnership := []thirdparty.AccountBalance{ 271 { 272 Address: ownerAddress2, 273 Balance: commonBalanceAddress2, 274 TxTimestamp: InvalidTimestamp, 275 }, 276 { 277 Address: ownerAddress3, 278 Balance: commonBalanceAddress3, 279 TxTimestamp: InvalidTimestamp, 280 }, 281 } 282 283 require.ElementsMatch(t, expectedOwnership, loadedOwnership) 284 285 // Test GetOwnership for random token 286 randomID := thirdparty.CollectibleUniqueID{ 287 ContractID: thirdparty.ContractID{ 288 ChainID: 0xABCDEF, 289 Address: common.BigToAddress(big.NewInt(int64(123456789))), 290 }, 291 TokenID: &bigint.BigInt{Int: big.NewInt(int64(987654321))}, 292 } 293 294 loadedOwnership, err = oDB.GetOwnership(randomID) 295 require.NoError(t, err) 296 require.Empty(t, loadedOwnership) 297 } 298 299 func TestUpdateOwnershipChanges(t *testing.T) { 300 oDB, cleanDB := setupOwnershipDBTest(t) 301 defer cleanDB() 302 303 chainID0 := w_common.ChainID(0) 304 ownerAddress1 := common.HexToAddress("0x1234") 305 ownedBalancesChain0 := generateTestCollectibles(0, 10) 306 ownedListChain0 := testCollectiblesToList(chainID0, ownedBalancesChain0) 307 timestampChain0 := int64(1234567890) 308 309 var err error 310 var removedIDs, updatedIDs, insertedIDs []thirdparty.CollectibleUniqueID 311 312 var loadedList []thirdparty.CollectibleUniqueID 313 314 removedIDs, updatedIDs, insertedIDs, err = oDB.Update(chainID0, ownerAddress1, ownedBalancesChain0, timestampChain0) 315 require.NoError(t, err) 316 require.Empty(t, removedIDs) 317 require.Empty(t, updatedIDs) 318 require.ElementsMatch(t, ownedListChain0, insertedIDs) 319 320 loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID0}, []common.Address{ownerAddress1}, 0, len(ownedListChain0)) 321 require.NoError(t, err) 322 require.ElementsMatch(t, ownedListChain0, loadedList) 323 324 // Remove one collectible and change balance of another 325 var removedID, updatedID thirdparty.CollectibleUniqueID 326 327 count := 0 328 for contractAddress, balances := range ownedBalancesChain0 { 329 for i, balance := range balances { 330 if count == 0 { 331 count++ 332 ownedBalancesChain0[contractAddress] = ownedBalancesChain0[contractAddress][1:] 333 removedID = thirdparty.CollectibleUniqueID{ 334 ContractID: thirdparty.ContractID{ 335 ChainID: chainID0, 336 Address: contractAddress, 337 }, 338 TokenID: balance.TokenID, 339 } 340 } else if count == 1 { 341 count++ 342 ownedBalancesChain0[contractAddress][i].Balance = &bigint.BigInt{Int: big.NewInt(100)} 343 updatedID = thirdparty.CollectibleUniqueID{ 344 ContractID: thirdparty.ContractID{ 345 ChainID: chainID0, 346 Address: contractAddress, 347 }, 348 TokenID: balance.TokenID, 349 } 350 } 351 } 352 } 353 ownedListChain0 = testCollectiblesToList(chainID0, ownedBalancesChain0) 354 355 removedIDs, updatedIDs, insertedIDs, err = oDB.Update(chainID0, ownerAddress1, ownedBalancesChain0, timestampChain0) 356 require.NoError(t, err) 357 require.ElementsMatch(t, []thirdparty.CollectibleUniqueID{removedID}, removedIDs) 358 require.ElementsMatch(t, []thirdparty.CollectibleUniqueID{updatedID}, updatedIDs) 359 require.Empty(t, insertedIDs) 360 361 loadedList, err = oDB.GetOwnedCollectibles([]w_common.ChainID{chainID0}, []common.Address{ownerAddress1}, 0, len(ownedListChain0)) 362 require.NoError(t, err) 363 require.ElementsMatch(t, ownedListChain0, loadedList) 364 } 365 366 func TestLargeTokenID(t *testing.T) { 367 oDB, cleanDB := setupOwnershipDBTest(t) 368 defer cleanDB() 369 370 ownerAddress := common.HexToAddress("0xABCD") 371 chainID := w_common.ChainID(0) 372 contractAddress := common.HexToAddress("0x1234") 373 tokenID := &bigint.BigInt{Int: big.NewInt(0).SetBytes([]byte("0x1234567890123456789012345678901234567890"))} 374 balance := &bigint.BigInt{Int: big.NewInt(100)} 375 376 ownedBalancesChain := thirdparty.TokenBalancesPerContractAddress{ 377 contractAddress: []thirdparty.TokenBalance{ 378 { 379 TokenID: tokenID, 380 Balance: balance, 381 }, 382 }, 383 } 384 ownedListChain := testCollectiblesToList(chainID, ownedBalancesChain) 385 386 ownership := []thirdparty.AccountBalance{ 387 { 388 Address: ownerAddress, 389 Balance: balance, 390 TxTimestamp: InvalidTimestamp, 391 }, 392 } 393 394 timestamp := int64(1234567890) 395 396 var err error 397 398 _, _, _, err = oDB.Update(chainID, ownerAddress, ownedBalancesChain, timestamp) 399 require.NoError(t, err) 400 401 loadedList, err := oDB.GetOwnedCollectibles([]w_common.ChainID{chainID}, []common.Address{ownerAddress}, 0, len(ownedListChain)) 402 require.NoError(t, err) 403 require.Equal(t, ownedListChain, loadedList) 404 405 // Test GetOwnership 406 id := thirdparty.CollectibleUniqueID{ 407 ContractID: thirdparty.ContractID{ 408 ChainID: chainID, 409 Address: contractAddress, 410 }, 411 TokenID: tokenID, 412 } 413 loadedOwnership, err := oDB.GetOwnership(id) 414 require.NoError(t, err) 415 require.Equal(t, ownership, loadedOwnership) 416 } 417 418 func TestCollectibleTransferID(t *testing.T) { 419 oDB, cleanDB := setupOwnershipDBTest(t) 420 defer cleanDB() 421 422 chainID0 := w_common.ChainID(0) 423 ownerAddress1 := common.HexToAddress("0x1234") 424 ownedBalancesChain0 := generateTestCollectibles(0, 10) 425 ownedListChain0 := testCollectiblesToList(chainID0, ownedBalancesChain0) 426 timestampChain0 := int64(1234567890) 427 428 var err error 429 var changed bool 430 431 _, _, _, err = oDB.Update(chainID0, ownerAddress1, ownedBalancesChain0, timestampChain0) 432 require.NoError(t, err) 433 434 loadedList, err := oDB.GetCollectiblesWithNoTransferID(ownerAddress1, chainID0) 435 require.NoError(t, err) 436 require.ElementsMatch(t, ownedListChain0, loadedList) 437 438 for _, id := range ownedListChain0 { 439 loadedTransferID, err := oDB.GetTransferID(ownerAddress1, id) 440 require.NoError(t, err) 441 require.Nil(t, loadedTransferID) 442 } 443 444 randomAddress := common.HexToAddress("0xFFFF") 445 randomCollectibleID := thirdparty.CollectibleUniqueID{ 446 ContractID: thirdparty.ContractID{ 447 ChainID: 0xABCDEF, 448 Address: common.BigToAddress(big.NewInt(int64(123456789))), 449 }, 450 TokenID: &bigint.BigInt{Int: big.NewInt(int64(987654321))}, 451 } 452 randomTxID := common.HexToHash("0xEEEE") 453 changed, err = oDB.SetTransferID(randomAddress, randomCollectibleID, randomTxID) 454 require.NoError(t, err) 455 require.False(t, changed) 456 457 firstCollectibleID := ownedListChain0[0] 458 firstTxID := common.HexToHash("0x1234") 459 changed, err = oDB.SetTransferID(ownerAddress1, firstCollectibleID, firstTxID) 460 require.NoError(t, err) 461 require.True(t, changed) 462 463 for _, id := range ownedListChain0 { 464 loadedTransferID, err := oDB.GetTransferID(ownerAddress1, id) 465 require.NoError(t, err) 466 if id == firstCollectibleID { 467 require.Equal(t, firstTxID, *loadedTransferID) 468 } else { 469 require.Nil(t, loadedTransferID) 470 } 471 } 472 473 // Even though the first collectible has a TransferID set, since there's no matching entry in the transfers table it 474 // should return InvalidTimestamp 475 firstOwnership, err := oDB.GetOwnership(firstCollectibleID) 476 require.NoError(t, err) 477 require.Equal(t, InvalidTimestamp, firstOwnership[0].TxTimestamp) 478 479 trs, _, _ := transfer.GenerateTestTransfers(t, oDB.db, 1, 5) 480 trs[0].To = ownerAddress1 481 trs[0].ChainID = chainID0 482 trs[0].Hash = firstTxID 483 484 for i := range trs { 485 if i == 0 { 486 transfer.InsertTestTransferWithOptions(t, oDB.db, trs[i].To, &trs[i], &transfer.TestTransferOptions{ 487 TokenAddress: firstCollectibleID.ContractID.Address, 488 TokenID: firstCollectibleID.TokenID.Int, 489 }) 490 } else { 491 transfer.InsertTestTransfer(t, oDB.db, trs[i].To, &trs[i]) 492 } 493 } 494 495 // There should now be a valid timestamp 496 firstOwnership, err = oDB.GetOwnership(firstCollectibleID) 497 require.NoError(t, err) 498 require.Equal(t, trs[0].Timestamp, firstOwnership[0].TxTimestamp) 499 }