feat: Filter by collectibles (#4028)

This commit is contained in:
Cuteivist 2023-09-21 08:58:36 +02:00 committed by GitHub
parent b3213172a7
commit bc4093299e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 133 additions and 3 deletions

View File

@ -311,8 +311,9 @@ const (
fromTrType = byte(1)
toTrType = byte(2)
noEntriesInTmpTableSQLValues = "(NULL)"
noEntriesInTwoColumnsTmpTableSQLValues = "(NULL, NULL)"
noEntriesInTmpTableSQLValues = "(NULL)"
noEntriesInTwoColumnsTmpTableSQLValues = "(NULL, NULL)"
noEntriesInThreeColumnsTmpTableSQLValues = "(NULL, NULL, NULL)"
)
//go:embed filter.sql
@ -374,6 +375,21 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
}
}
includeAllCollectibles := len(filter.Collectibles) == 0 && !filter.FilterOutCollectibles
assetsERC721 := noEntriesInThreeColumnsTmpTableSQLValues
if !includeAllCollectibles && !filter.FilterOutCollectibles {
assetsERC721 = joinItems(filter.Collectibles, func(item Token) string {
// SQL HEX() (Blob->Hex) conversion returns uppercase digits with no 0x prefix
tokenID := strings.ToUpper(item.TokenID.String()[2:])
address := strings.ToUpper(item.Address.Hex()[2:])
if len(tokenID)%2 == 1 {
// Hex length must be divisable by 2, otherwise append '0' at the beginning
tokenID = "0" + tokenID
}
return fmt.Sprintf("%d, '%s', '%s'", item.ChainID, tokenID, address)
})
}
// construct chain IDs
includeAllNetworks := len(chainIDs) == 0
networks := noEntriesInTmpTableSQLValues
@ -414,7 +430,7 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
return strconv.Itoa(int(t))
})
queryString := fmt.Sprintf(queryFormatString, involvedAddresses, toAddresses, assetsTokenCodes, assetsERC20, networks,
queryString := fmt.Sprintf(queryFormatString, involvedAddresses, toAddresses, assetsTokenCodes, assetsERC20, assetsERC721, networks,
layer2Networks, joinedMTTypes)
// The duplicated temporary table UNION with CTE acts as an optimization
@ -435,6 +451,7 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
includeAllStatuses, filterStatusCompleted, filterStatusFailed, filterStatusFinalized, filterStatusPending,
FailedAS, CompleteAS, FinalizedAS, PendingAS,
includeAllTokenTypeAssets,
includeAllCollectibles,
includeAllNetworks,
transactions.Pending,
deps.currentTimestamp(),

View File

@ -38,6 +38,15 @@ func tokenFromSymbol(chainID *common.ChainID, symbol string) *Token {
return nil
}
func tokenFromCollectible(c *transfer.TestCollectible) Token {
return Token{
TokenType: Erc721,
ChainID: c.ChainID,
Address: c.TokenAddress,
TokenID: (*hexutil.Big)(c.TokenID),
}
}
func setupTestActivityDBStorageChoice(tb testing.TB, inMemory bool) (deps FilterDependencies, close func()) {
var db *sql.DB
var err error
@ -951,6 +960,60 @@ func TestGetActivityEntriesFilterByTokenType(t *testing.T) {
require.Nil(t, entries[0].tokenOut)
}
func TestGetActivityEntriesFilterByCollectibles(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
// Adds 4 extractable transactions 2 transactions (ETH/Goerli, ETH/Optimism), one MT USDC to DAI and another MT USDC to SNT
td, fromTds, toTds := fillTestData(t, deps.db)
// Add 4 transactions with collectibles
trs, fromTrs, toTrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, 4)
for i := range trs {
collectibleData := transfer.TestCollectibles[i]
trs[i].ChainID = collectibleData.ChainID
transfer.InsertTestTransferWithOptions(t, deps.db, trs[i].To, &trs[i], &transfer.TestTransferOptions{
TokenAddress: collectibleData.TokenAddress,
TokenID: collectibleData.TokenID,
})
}
allAddresses := append(append(append(fromTds, toTds...), fromTrs...), toTrs...)
var filter Filter
filter.FilterOutCollectibles = true
entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 0, len(entries))
filter.FilterOutCollectibles = false
filter.Collectibles = allTokensFilter()
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 8, len(entries))
// Search for a specific collectible
filter.Collectibles = []Token{tokenFromCollectible(&transfer.TestCollectibles[0])}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 1, len(entries))
require.Equal(t, entries[0].tokenIn.Address, transfer.TestCollectibles[0].TokenAddress)
require.Equal(t, entries[0].tokenIn.TokenID, (*hexutil.Big)(transfer.TestCollectibles[0].TokenID))
// Search for a specific collectible
filter.Collectibles = []Token{tokenFromCollectible(&transfer.TestCollectibles[3])}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 1, len(entries))
require.Equal(t, entries[0].tokenIn.Address, transfer.TestCollectibles[3].TokenAddress)
require.Equal(t, entries[0].tokenIn.TokenID, (*hexutil.Big)(transfer.TestCollectibles[3].TokenID))
// Search for a multiple collectibles
filter.Collectibles = []Token{tokenFromCollectible(&transfer.TestCollectibles[1]), tokenFromCollectible(&transfer.TestCollectibles[2])}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 2, len(entries))
}
func TestGetActivityEntriesFilterByToAddresses(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()

View File

@ -38,6 +38,7 @@ WITH filter_conditions AS (
? AS statusFinalized,
? AS statusPending,
? AS includeAllTokenTypeAssets,
? AS includeAllCollectibles,
? AS includeAllNetworks,
? AS pendingStatus,
? AS nowTimestamp,
@ -87,6 +88,10 @@ assets_erc20(chain_id, token_address) AS (
VALUES
%s
),
assets_erc721(chain_id, token_id, token_address) AS (
VALUES
%s
),
filter_networks(network_id) AS (
VALUES
%s
@ -291,6 +296,19 @@ WHERE
)
)
)
AND (
includeAllCollectibles
OR (
transfers.type = "erc721"
AND (
(
transfers.network_id,
HEX(transfers.token_id),
HEX(transfers.token_address)
) IN assets_erc721
)
)
)
AND (
includeAllNetworks
OR (transfers.network_id IN filter_networks)
@ -360,6 +378,7 @@ WHERE
filterAllActivityStatus
OR filterStatusPending
)
AND includeAllCollectibles
AND (
(
startFilterDisabled
@ -458,6 +477,7 @@ WHERE
OR multi_transactions.timestamp <= endTimestamp
)
)
AND includeAllCollectibles
AND (
filterActivityTypeAll
OR (multi_transactions.type IN (%s))

View File

@ -8,6 +8,7 @@ import (
eth_common "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/services/wallet/common"
w_common "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/testutils"
@ -141,6 +142,35 @@ func GenerateTestTransfers(tb testing.TB, db *sql.DB, firstStartIndex int, count
return
}
type TestCollectible struct {
TokenAddress eth_common.Address
TokenID *big.Int
ChainID common.ChainID
}
var TestCollectibles = []TestCollectible{
TestCollectible{
TokenAddress: eth_common.HexToAddress("0x97a04fda4d97c6e3547d66b572e29f4a4ff40392"),
TokenID: big.NewInt(1),
ChainID: 1,
},
TestCollectible{ // Same token ID as above but different address
TokenAddress: eth_common.HexToAddress("0x2cec8879915cdbd80c88d8b1416aa9413a24ddfa"),
TokenID: big.NewInt(1),
ChainID: 1,
},
TestCollectible{
TokenAddress: eth_common.HexToAddress("0x1dea7a3e04849840c0eb15fd26a55f6c40c4a69b"),
TokenID: big.NewInt(11),
ChainID: 5,
},
TestCollectible{ // Same address as above but different token ID
TokenAddress: eth_common.HexToAddress("0x1dea7a3e04849840c0eb15fd26a55f6c40c4a69b"),
TokenID: big.NewInt(12),
ChainID: 5,
},
}
var EthMainnet = token.Token{
Address: eth_common.HexToAddress("0x"),
Name: "Ether",