From e77fc59f5e13877c5d62e58213cd12ba88415939 Mon Sep 17 00:00:00 2001 From: Cuteivist Date: Mon, 11 Sep 2023 11:54:37 +0200 Subject: [PATCH] feat: Add activity filtering by contract deploy and minting (#4009) --- services/wallet/activity/activity.go | 7 ++- services/wallet/activity/activity_test.go | 58 +++++++++++++++++++++-- services/wallet/activity/filter.sql | 36 ++++++++++++-- services/wallet/collectibles/manager.go | 3 ++ services/wallet/transfer/testutils.go | 2 +- 5 files changed, 95 insertions(+), 11 deletions(-) diff --git a/services/wallet/activity/activity.go b/services/wallet/activity/activity.go index 5998c4fe4..ba5613b36 100644 --- a/services/wallet/activity/activity.go +++ b/services/wallet/activity/activity.go @@ -420,6 +420,7 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses rows, err := deps.db.QueryContext(ctx, queryString, startFilterDisabled, filter.Period.StartTimestamp, endFilterDisabled, filter.Period.EndTimestamp, filterActivityTypeAll, sliceContains(filter.Types, SendAT), sliceContains(filter.Types, ReceiveAT), + sliceContains(filter.Types, ContractDeploymentAT), sliceContains(filter.Types, MintAT), fromTrType, toTrType, filterAllAddresses, filterAllToAddresses, includeAllStatuses, filterStatusCompleted, filterStatusFailed, filterStatusFinalized, filterStatusPending, @@ -519,7 +520,7 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses ) // Extract tokens - if activityType == SendAT { + if activityType == SendAT || activityType == ContractDeploymentAT { entry.tokenOut = involvedToken outChainID = new(common.ChainID) *outChainID = common.ChainID(chainID.Int64) @@ -624,10 +625,14 @@ func getTrInAndOutAmounts(activityType Type, trAmount sql.NullString) (inAmount amount, ok := new(big.Int).SetString(trAmount.String, 16) if ok { switch activityType { + case ContractDeploymentAT: + fallthrough case SendAT: inAmount = (*hexutil.Big)(big.NewInt(0)) outAmount = (*hexutil.Big)(amount) return + case MintAT: + fallthrough case ReceiveAT: inAmount = (*hexutil.Big)(amount) outAmount = (*hexutil.Big)(big.NewInt(0)) diff --git a/services/wallet/activity/activity_test.go b/services/wallet/activity/activity_test.go index c4c4c9892..0e489e771 100644 --- a/services/wallet/activity/activity_test.go +++ b/services/wallet/activity/activity_test.go @@ -620,7 +620,7 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) { }, entries[0]) } -func countTypes(entries []Entry) (sendCount, receiveCount, swapCount, buyCount, bridgeCount int) { +func countTypes(entries []Entry) (sendCount, receiveCount, contractCount, mintCount, swapCount, buyCount, bridgeCount int) { for _, entry := range entries { switch entry.activityType { case SendAT: @@ -633,6 +633,10 @@ func countTypes(entries []Entry) (sendCount, receiveCount, swapCount, buyCount, buyCount++ case BridgeAT: bridgeCount++ + case ContractDeploymentAT: + contractCount++ + case MintAT: + mintCount++ } } return @@ -662,15 +666,29 @@ func TestGetActivityEntriesFilterByType(t *testing.T) { transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i]) } + trsSpecial, _, _ := transfer.GenerateTestTransfers(t, deps.db, 100, 2) + // Insert MintAT + trsSpecial[0].From = eth.HexToAddress("0x0") + transfer.InsertTestTransferWithOptions(t, deps.db, trsSpecial[0].To, &trsSpecial[0], &transfer.TestTransferOptions{ + TokenAddress: eth.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), + TokenID: (big.NewInt(1318)), + }) + + // Insert ContractDeploymentAt + trsSpecial[1].To = eth.HexToAddress("0x0") + transfer.InsertTestTransferWithOptions(t, deps.db, trsSpecial[1].From, &trsSpecial[1], &transfer.TestTransferOptions{ + NullifyAddresses: []eth.Address{trsSpecial[1].To}, + }) + // Test filtering out without address involved var filter Filter filter.Types = allActivityTypesFilter() // Set tr1 to Receive and pendingTr to Send; rest of two MT remain default Send - addresses := []eth.Address{td.tr1.To, td.pendingTr.From, td.multiTx1.FromAddress, td.multiTx2.FromAddress, trs[0].From, trs[2].From, trs[4].From, trs[6].From, trs[8].From} + addresses := []eth.Address{td.tr1.To, td.pendingTr.From, td.multiTx1.FromAddress, td.multiTx2.FromAddress, trs[0].From, trs[2].From, trs[4].From, trs[6].From, trs[8].From, trsSpecial[0].To, trsSpecial[1].From} entries, err := getActivityEntries(context.Background(), deps, addresses, []common.ChainID{}, filter, 0, 15) require.NoError(t, err) - require.Equal(t, 9, len(entries)) + require.Equal(t, 11, len(entries)) filter.Types = []Type{SendAT, SwapAT} entries, err = getActivityEntries(context.Background(), deps, addresses, []common.ChainID{}, filter, 0, 15) @@ -678,10 +696,12 @@ func TestGetActivityEntriesFilterByType(t *testing.T) { // 3 from td Send + 2 trs MT Send + 1 (swap) require.Equal(t, 6, len(entries)) - sendCount, receiveCount, swapCount, _, bridgeCount := countTypes(entries) + sendCount, receiveCount, contractCount, mintCount, swapCount, _, bridgeCount := countTypes(entries) require.Equal(t, 5, sendCount) require.Equal(t, 0, receiveCount) + require.Equal(t, 0, contractCount) + require.Equal(t, 0, mintCount) require.Equal(t, 1, swapCount) require.Equal(t, 0, bridgeCount) @@ -690,11 +710,39 @@ func TestGetActivityEntriesFilterByType(t *testing.T) { require.NoError(t, err) require.Equal(t, 3, len(entries)) - sendCount, receiveCount, swapCount, _, bridgeCount = countTypes(entries) + sendCount, receiveCount, contractCount, mintCount, swapCount, _, bridgeCount = countTypes(entries) require.Equal(t, 0, sendCount) require.Equal(t, 1, receiveCount) + require.Equal(t, 0, contractCount) + require.Equal(t, 0, mintCount) require.Equal(t, 0, swapCount) require.Equal(t, 2, bridgeCount) + + filter.Types = []Type{MintAT} + entries, err = getActivityEntries(context.Background(), deps, addresses, []common.ChainID{}, filter, 0, 15) + require.NoError(t, err) + require.Equal(t, 1, len(entries)) + + sendCount, receiveCount, contractCount, mintCount, swapCount, _, bridgeCount = countTypes(entries) + require.Equal(t, 0, sendCount) + require.Equal(t, 0, receiveCount) + require.Equal(t, 0, contractCount) + require.Equal(t, 1, mintCount) + require.Equal(t, 0, swapCount) + require.Equal(t, 0, bridgeCount) + + filter.Types = []Type{ContractDeploymentAT} + entries, err = getActivityEntries(context.Background(), deps, addresses, []common.ChainID{}, filter, 0, 15) + require.NoError(t, err) + require.Equal(t, 1, len(entries)) + + sendCount, receiveCount, contractCount, mintCount, swapCount, _, bridgeCount = countTypes(entries) + require.Equal(t, 0, sendCount) + require.Equal(t, 0, receiveCount) + require.Equal(t, 1, contractCount) + require.Equal(t, 0, mintCount) + require.Equal(t, 0, swapCount) + require.Equal(t, 0, bridgeCount) } func TestGetActivityEntriesFilterByAddresses(t *testing.T) { diff --git a/services/wallet/activity/filter.sql b/services/wallet/activity/filter.sql index 256ec9423..33ca516a9 100644 --- a/services/wallet/activity/filter.sql +++ b/services/wallet/activity/filter.sql @@ -10,6 +10,8 @@ -- -- Only status FailedAS, PendingAS and CompleteAS are returned. FinalizedAS requires correlation with blockchain current state. As an optimization we approximate it by using timestamp information; see startTimestamp and endTimestamp -- +-- ContractDeploymentAT is subtype of SendAT and MintAT is subtype of ReceiveAT. It means query must prevent returning MintAT when filtering by ReceiveAT or ContractDeploymentAT when filtering by SendAT. That required duplicated code in filter by type query, to maintain perforamnce. +-- -- Token filtering has two parts -- 1. Filtering by symbol (multi_transactions and pending_transactions tables) where the chain ID is ignored, basically the filter_networks will account for that -- 2. Filtering by token identity (chain and address for transfers table) where the symbol is ignored and all the token identities must be provided @@ -23,6 +25,8 @@ WITH filter_conditions AS ( ? AS filterActivityTypeAll, ? AS filterActivityTypeSend, ? AS filterActivityTypeReceive, + ? AS filterActivityTypeContractDeployment, + ? AS filterActivityTypeMint, ? AS fromTrType, ? AS toTrType, ? AS filterAllAddresses, @@ -37,7 +41,8 @@ WITH filter_conditions AS ( ? AS statusPending, ? AS includeAllTokenTypeAssets, ? AS includeAllNetworks, - ? AS pendingStatus + ? AS pendingStatus, + "0000000000000000000000000000000000000000" AS zeroAddress ), filter_addresses(address) AS ( SELECT @@ -195,15 +200,16 @@ WHERE ) ) AND ( + -- Check description at the top of the file why code below is duplicated filterActivityTypeAll OR ( filterActivityTypeSend AND ( filterAllAddresses - OR ( - HEX(transfers.tx_from_address) IN filter_addresses - ) + OR (HEX(transfers.tx_from_address) IN filter_addresses) ) + AND NOT (tr_type = fromTrType and transfers.tx_to_address IS NULL AND transfers.type = "eth" + AND transfers.contract_address IS NOT NULL AND HEX(transfers.contract_address) != zeroAddress) ) OR ( filterActivityTypeReceive @@ -211,6 +217,28 @@ WHERE filterAllAddresses OR (HEX(transfers.tx_to_address) IN filter_addresses) ) + AND NOT (tr_type = toTrType AND transfers.type = "erc721" AND ( + transfers.tx_from_address IS NULL + OR HEX(transfers.tx_from_address) = zeroAddress)) + ) + OR ( + filterActivityTypeContractDeployment + AND tr_type = fromTrType AND transfers.tx_to_address IS NULL AND transfers.type = "eth" + AND transfers.contract_address IS NOT NULL AND HEX(transfers.contract_address) != zeroAddress + AND ( + filterAllAddresses + OR (HEX(transfers.address) IN filter_addresses) + ) + ) + OR ( + filterActivityTypeMint + AND tr_type = toTrType AND transfers.type = "erc721" AND ( + transfers.tx_from_address IS NULL + OR HEX(transfers.tx_from_address) = zeroAddress + ) AND ( + filterAllAddresses + OR (HEX(transfers.address) IN filter_addresses) + ) ) ) AND ( diff --git a/services/wallet/collectibles/manager.go b/services/wallet/collectibles/manager.go index fbdad0834..aab86ee7a 100644 --- a/services/wallet/collectibles/manager.go +++ b/services/wallet/collectibles/manager.go @@ -380,6 +380,9 @@ func isMetadataEmpty(asset thirdparty.CollectibleData) bool { } func (o *Manager) fetchTokenURI(id thirdparty.CollectibleUniqueID) (string, error) { + if id.TokenID == nil { + return "", errors.New("empty token ID") + } backend, err := o.rpcClient.EthClient(uint64(id.ContractID.ChainID)) if err != nil { return "", err diff --git a/services/wallet/transfer/testutils.go b/services/wallet/transfer/testutils.go index f58fa4a79..01a3449a4 100644 --- a/services/wallet/transfer/testutils.go +++ b/services/wallet/transfer/testutils.go @@ -242,7 +242,7 @@ func InsertTestTransferWithOptions(tb testing.TB, db *sql.DB, address eth_common block := blockDBFields{ chainID: uint64(tr.ChainID), - account: tr.To, + account: address, blockNumber: big.NewInt(tr.BlkNumber), blockHash: blkHash, }