feat(Wallet) complete the filter API
It uses the current data only and doesn't extend with new types or include new features in activity sources DBs. Major changes: - Partially filter by chain IDs - Partially filter by Status if it is the case - Partially filter by token types - Filter by counterparty addresses - Use wallet accounts for TO/FROM instead of filters Closes: #10634
This commit is contained in:
parent
5777bb429a
commit
e78a73bd9f
|
@ -0,0 +1,19 @@
|
|||
package accounts
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func AddTestAccounts(t *testing.T, db *sql.DB, accounts []*Account) {
|
||||
d, err := NewDB(db)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = d.SaveAccounts(accounts)
|
||||
require.NoError(t, err)
|
||||
res, err := d.GetAccounts()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, accounts, res)
|
||||
}
|
|
@ -9,12 +9,19 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
eth "github.com/ethereum/go-ethereum/common"
|
||||
|
||||
"github.com/status-im/status-go/services/wallet/common"
|
||||
"github.com/status-im/status-go/services/wallet/transfer"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
type PayloadType = int
|
||||
|
||||
// Beware if adding/removing please check if affected and update the functions below
|
||||
// - NewActivityEntryWithTransaction
|
||||
// - multiTransactionTypeToActivityType
|
||||
const (
|
||||
MultiTransactionPT PayloadType = iota + 1
|
||||
SimpleTransactionPT
|
||||
|
@ -22,29 +29,34 @@ const (
|
|||
)
|
||||
|
||||
type Entry struct {
|
||||
// TODO: rename in payloadType
|
||||
transactionType PayloadType
|
||||
transaction *transfer.TransactionIdentity
|
||||
id transfer.MultiTransactionIDType
|
||||
timestamp int64
|
||||
activityType Type
|
||||
payloadType PayloadType
|
||||
transaction *transfer.TransactionIdentity
|
||||
id transfer.MultiTransactionIDType
|
||||
timestamp int64
|
||||
activityType Type
|
||||
activityStatus Status
|
||||
tokenType TokenType
|
||||
}
|
||||
|
||||
type jsonSerializationTemplate struct {
|
||||
TransactionType PayloadType `json:"transactionType"`
|
||||
Transaction *transfer.TransactionIdentity `json:"transaction"`
|
||||
ID transfer.MultiTransactionIDType `json:"id"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
ActivityType Type `json:"activityType"`
|
||||
PayloadType PayloadType `json:"payloadType"`
|
||||
Transaction *transfer.TransactionIdentity `json:"transaction"`
|
||||
ID transfer.MultiTransactionIDType `json:"id"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
ActivityType Type `json:"activityType"`
|
||||
ActivityStatus Status `json:"activityStatus"`
|
||||
TokenType TokenType `json:"tokenType"`
|
||||
}
|
||||
|
||||
func (e *Entry) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(jsonSerializationTemplate{
|
||||
TransactionType: e.transactionType,
|
||||
Transaction: e.transaction,
|
||||
ID: e.id,
|
||||
Timestamp: e.timestamp,
|
||||
ActivityType: e.activityType,
|
||||
PayloadType: e.payloadType,
|
||||
Transaction: e.transaction,
|
||||
ID: e.id,
|
||||
Timestamp: e.timestamp,
|
||||
ActivityType: e.activityType,
|
||||
ActivityStatus: e.activityStatus,
|
||||
TokenType: e.tokenType,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -55,7 +67,7 @@ func (e *Entry) UnmarshalJSON(data []byte) error {
|
|||
return err
|
||||
}
|
||||
|
||||
e.transactionType = aux.TransactionType
|
||||
e.payloadType = aux.PayloadType
|
||||
e.transaction = aux.Transaction
|
||||
e.id = aux.ID
|
||||
e.timestamp = aux.Timestamp
|
||||
|
@ -63,31 +75,35 @@ func (e *Entry) UnmarshalJSON(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func NewActivityEntryWithTransaction(transactionType PayloadType, transaction *transfer.TransactionIdentity, timestamp int64, activityType Type) Entry {
|
||||
if transactionType != SimpleTransactionPT && transactionType != PendingTransactionPT {
|
||||
func NewActivityEntryWithTransaction(payloadType PayloadType, transaction *transfer.TransactionIdentity, timestamp int64, activityType Type, activityStatus Status) Entry {
|
||||
if payloadType != SimpleTransactionPT && payloadType != PendingTransactionPT {
|
||||
panic("invalid transaction type")
|
||||
}
|
||||
|
||||
return Entry{
|
||||
transactionType: transactionType,
|
||||
transaction: transaction,
|
||||
id: 0,
|
||||
timestamp: timestamp,
|
||||
activityType: activityType,
|
||||
payloadType: payloadType,
|
||||
transaction: transaction,
|
||||
id: 0,
|
||||
timestamp: timestamp,
|
||||
activityType: activityType,
|
||||
activityStatus: activityStatus,
|
||||
tokenType: AssetTT,
|
||||
}
|
||||
}
|
||||
|
||||
func NewActivityEntryWithMultiTransaction(id transfer.MultiTransactionIDType, timestamp int64, activityType Type) Entry {
|
||||
func NewActivityEntryWithMultiTransaction(id transfer.MultiTransactionIDType, timestamp int64, activityType Type, activityStatus Status) Entry {
|
||||
return Entry{
|
||||
transactionType: MultiTransactionPT,
|
||||
id: id,
|
||||
timestamp: timestamp,
|
||||
activityType: activityType,
|
||||
payloadType: MultiTransactionPT,
|
||||
id: id,
|
||||
timestamp: timestamp,
|
||||
activityType: activityType,
|
||||
activityStatus: activityStatus,
|
||||
tokenType: AssetTT,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Entry) TransactionType() PayloadType {
|
||||
return e.transactionType
|
||||
func (e *Entry) PayloadType() PayloadType {
|
||||
return e.payloadType
|
||||
}
|
||||
|
||||
func multiTransactionTypeToActivityType(mtType transfer.MultiTransactionType) Type {
|
||||
|
@ -101,7 +117,7 @@ func multiTransactionTypeToActivityType(mtType transfer.MultiTransactionType) Ty
|
|||
panic("unknown multi transaction type")
|
||||
}
|
||||
|
||||
func typesContain(slice []Type, item Type) bool {
|
||||
func sliceContains[T constraints.Ordered](slice []T, item T) bool {
|
||||
for _, a := range slice {
|
||||
if a == item {
|
||||
return true
|
||||
|
@ -110,31 +126,33 @@ func typesContain(slice []Type, item Type) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func joinMTTypes(types []transfer.MultiTransactionType) string {
|
||||
var sb strings.Builder
|
||||
for i, val := range types {
|
||||
if i > 0 {
|
||||
sb.WriteString(",")
|
||||
}
|
||||
sb.WriteString(strconv.Itoa(int(val)))
|
||||
func joinItems[T interface{}](items []T, itemConversion func(T) string) string {
|
||||
if len(items) == 0 {
|
||||
return ""
|
||||
}
|
||||
var sb strings.Builder
|
||||
if itemConversion == nil {
|
||||
itemConversion = func(item T) string {
|
||||
return fmt.Sprintf("%v", item)
|
||||
}
|
||||
}
|
||||
for i, item := range items {
|
||||
if i == 0 {
|
||||
sb.WriteString("(")
|
||||
} else {
|
||||
sb.WriteString("),(")
|
||||
}
|
||||
sb.WriteString(itemConversion(item))
|
||||
}
|
||||
sb.WriteString(")")
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func joinAddresses(addresses []common.Address) string {
|
||||
var sb strings.Builder
|
||||
for i, address := range addresses {
|
||||
if i == 0 {
|
||||
sb.WriteString("('")
|
||||
} else {
|
||||
sb.WriteString("'),('")
|
||||
}
|
||||
sb.WriteString(strings.ToUpper(hex.EncodeToString(address[:])))
|
||||
}
|
||||
sb.WriteString("')")
|
||||
|
||||
return sb.String()
|
||||
func joinAddresses(addresses []eth.Address) string {
|
||||
return joinItems(addresses, func(a eth.Address) string {
|
||||
return fmt.Sprintf("'%s'", strings.ToUpper(hex.EncodeToString(a[:])))
|
||||
})
|
||||
}
|
||||
|
||||
func activityTypesToMultiTransactionTypes(trTypes []Type) []transfer.MultiTransactionType {
|
||||
|
@ -155,11 +173,25 @@ func activityTypesToMultiTransactionTypes(trTypes []Type) []transfer.MultiTransa
|
|||
return mtTypes
|
||||
}
|
||||
|
||||
// TODO: extend with SEND/RECEIVE for transfers and pending_transactions
|
||||
// TODO: clarify if we include sender and receiver in pending_transactions as we do for transfers
|
||||
// TODO optimization: consider implementing nullable []byte instead of using strings for addresses
|
||||
// Query includes duplicates, will return multiple rows for the same transaction
|
||||
const queryFormatString = `
|
||||
const (
|
||||
fromTrType = byte(1)
|
||||
//toTrType = byte(2)
|
||||
|
||||
// TODO: Multi-transaction network information is missing in filtering
|
||||
// TODO: extract token code for non transfer type eth
|
||||
// TODO optimization: consider implementing nullable []byte instead of using strings for addresses
|
||||
//
|
||||
// Query includes duplicates, will return multiple rows for the same transaction if both to and from addresses
|
||||
// are in the address list.
|
||||
//
|
||||
// The addresses list will have priority in deciding the source of the duplicate transaction. However, if the
|
||||
// if the addresses list is empty, and all addresses should be included, the accounts table will be used
|
||||
// see filter_addresses temp table is used
|
||||
// The switch for tr_type is used to de-conflict the source for the two entries for the same transaction
|
||||
//
|
||||
// UNION ALL is used to avoid the overhead of DISTINCT given that we don't expect to have duplicate entries outside
|
||||
// the sender and receiver addresses being in the list which is handled separately
|
||||
queryFormatString = `
|
||||
WITH filter_conditions AS (
|
||||
SELECT
|
||||
? AS startFilterDisabled,
|
||||
|
@ -171,9 +203,26 @@ const queryFormatString = `
|
|||
? AS filterActivityTypeSend,
|
||||
? AS filterActivityTypeReceive,
|
||||
|
||||
? AS filterAllAddresses
|
||||
? AS filterAllAddresses,
|
||||
? AS filterAllToAddresses,
|
||||
? AS filterAllActivityStatus,
|
||||
? AS includeAllTokenTypeAssets,
|
||||
? AS statusIsPending,
|
||||
|
||||
? AS includeAllNetworks
|
||||
),
|
||||
filter_addresses(address) AS (
|
||||
SELECT HEX(address) FROM accounts WHERE (SELECT filterAllAddresses FROM filter_conditions) != 0
|
||||
UNION ALL
|
||||
SELECT * FROM (VALUES %s) WHERE (SELECT filterAllAddresses FROM filter_conditions) = 0
|
||||
),
|
||||
filter_to_addresses(address) AS (
|
||||
VALUES %s
|
||||
),
|
||||
filter_assets(token_code) AS (
|
||||
VALUES %s
|
||||
),
|
||||
filter_networks(network_id) AS (
|
||||
VALUES %s
|
||||
)
|
||||
SELECT
|
||||
|
@ -183,12 +232,48 @@ const queryFormatString = `
|
|||
0 AS multi_tx_id,
|
||||
transfers.timestamp AS timestamp,
|
||||
NULL AS mt_type,
|
||||
HEX(transfers.address) AS owner_address
|
||||
|
||||
CASE
|
||||
WHEN from_join.address IS NOT NULL AND to_join.address IS NULL THEN 1
|
||||
WHEN to_join.address IS NOT NULL AND from_join.address IS NULL THEN 2
|
||||
WHEN from_join.address IS NOT NULL AND to_join.address IS NOT NULL THEN
|
||||
CASE
|
||||
WHEN from_join.address < to_join.address THEN 1
|
||||
ELSE 2
|
||||
END
|
||||
ELSE NULL
|
||||
END as tr_type,
|
||||
|
||||
transfers.sender AS from_address,
|
||||
transfers.address AS to_address
|
||||
FROM transfers, filter_conditions
|
||||
LEFT JOIN
|
||||
filter_addresses from_join ON HEX(transfers.sender) = from_join.address
|
||||
LEFT JOIN
|
||||
filter_addresses to_join ON HEX(transfers.address) = to_join.address
|
||||
WHERE transfers.multi_transaction_id = 0
|
||||
AND ((startFilterDisabled OR timestamp >= startTimestamp) AND (endFilterDisabled OR timestamp <= endTimestamp))
|
||||
AND (filterActivityTypeAll OR (filterActivityTypeSend AND (filterAllAddresses OR (HEX(transfers.sender) IN filter_addresses))) OR (filterActivityTypeReceive AND (filterAllAddresses OR (HEX(transfers.address) IN filter_addresses))))
|
||||
AND (filterAllAddresses OR (HEX(transfers.sender) IN filter_addresses) OR (HEX(transfers.address) IN filter_addresses))
|
||||
AND ((startFilterDisabled OR timestamp >= startTimestamp)
|
||||
AND (endFilterDisabled OR timestamp <= endTimestamp)
|
||||
)
|
||||
AND (filterActivityTypeAll
|
||||
OR (filterActivityTypeSend
|
||||
AND (filterAllAddresses
|
||||
OR (HEX(transfers.sender) IN filter_addresses)
|
||||
)
|
||||
)
|
||||
OR (filterActivityTypeReceive
|
||||
AND (filterAllAddresses OR (HEX(transfers.address) IN filter_addresses))
|
||||
)
|
||||
)
|
||||
AND (filterAllAddresses
|
||||
OR (HEX(transfers.sender) IN filter_addresses)
|
||||
OR (HEX(transfers.address) IN filter_addresses)
|
||||
)
|
||||
AND (filterAllToAddresses
|
||||
OR (HEX(transfers.address) IN filter_to_addresses)
|
||||
)
|
||||
AND (includeAllTokenTypeAssets OR (transfers.type = "eth" AND ("ETH" IN filter_assets)))
|
||||
AND (includeAllNetworks OR (transfers.network_id IN filter_networks))
|
||||
|
||||
UNION ALL
|
||||
|
||||
|
@ -199,12 +284,40 @@ const queryFormatString = `
|
|||
0 AS multi_tx_id,
|
||||
pending_transactions.timestamp AS timestamp,
|
||||
NULL AS mt_type,
|
||||
NULL AS owner_address
|
||||
|
||||
CASE
|
||||
WHEN from_join.address IS NOT NULL AND to_join.address IS NULL THEN 1
|
||||
WHEN to_join.address IS NOT NULL AND from_join.address IS NULL THEN 2
|
||||
WHEN from_join.address IS NOT NULL AND to_join.address IS NOT NULL THEN
|
||||
CASE
|
||||
WHEN from_join.address < to_join.address THEN 1
|
||||
ELSE 2
|
||||
END
|
||||
ELSE NULL
|
||||
END as tr_type,
|
||||
|
||||
pending_transactions.from_address AS from_address,
|
||||
pending_transactions.to_address AS to_address
|
||||
FROM pending_transactions, filter_conditions
|
||||
LEFT JOIN
|
||||
filter_addresses from_join ON HEX(pending_transactions.from_address) = from_join.address
|
||||
LEFT JOIN
|
||||
filter_addresses to_join ON HEX(pending_transactions.to_address) = to_join.address
|
||||
WHERE pending_transactions.multi_transaction_id = 0
|
||||
AND ((startFilterDisabled OR timestamp >= startTimestamp) AND (endFilterDisabled OR timestamp <= endTimestamp))
|
||||
AND (filterAllActivityStatus OR statusIsPending)
|
||||
AND ((startFilterDisabled OR timestamp >= startTimestamp)
|
||||
AND (endFilterDisabled OR timestamp <= endTimestamp)
|
||||
)
|
||||
AND (filterActivityTypeAll OR filterActivityTypeSend)
|
||||
AND (filterAllAddresses OR (HEX(pending_transactions.from_address) IN filter_addresses) OR (HEX(pending_transactions.to_address) IN filter_addresses))
|
||||
AND (filterAllAddresses
|
||||
OR (HEX(pending_transactions.from_address) IN filter_addresses)
|
||||
OR (HEX(pending_transactions.to_address) IN filter_addresses)
|
||||
)
|
||||
AND (filterAllToAddresses
|
||||
OR (HEX(pending_transactions.to_address) IN filter_to_addresses)
|
||||
)
|
||||
AND (includeAllTokenTypeAssets OR (UPPER(pending_transactions.symbol) IN filter_assets))
|
||||
AND (includeAllNetworks OR (pending_transactions.network_id IN filter_networks))
|
||||
|
||||
UNION ALL
|
||||
|
||||
|
@ -215,41 +328,89 @@ const queryFormatString = `
|
|||
multi_transactions.ROWID AS multi_tx_id,
|
||||
multi_transactions.timestamp AS timestamp,
|
||||
multi_transactions.type AS mt_type,
|
||||
NULL AS owner_address
|
||||
NULL as tr_type,
|
||||
multi_transactions.from_address AS from_address,
|
||||
multi_transactions.to_address AS to_address
|
||||
FROM multi_transactions, filter_conditions
|
||||
WHERE ((startFilterDisabled OR timestamp >= startTimestamp) AND (endFilterDisabled OR timestamp <= endTimestamp))
|
||||
WHERE ((startFilterDisabled OR timestamp >= startTimestamp)
|
||||
AND (endFilterDisabled OR timestamp <= endTimestamp)
|
||||
)
|
||||
AND (filterActivityTypeAll OR (multi_transactions.type IN (%s)))
|
||||
AND (filterAllAddresses OR (HEX(multi_transactions.from_address) IN filter_addresses) OR (HEX(multi_transactions.to_address) IN filter_addresses))
|
||||
AND (filterAllAddresses
|
||||
OR (HEX(multi_transactions.from_address) IN filter_addresses)
|
||||
OR (HEX(multi_transactions.to_address) IN filter_addresses)
|
||||
)
|
||||
AND (filterAllToAddresses
|
||||
OR (HEX(multi_transactions.to_address) IN filter_to_addresses)
|
||||
)
|
||||
AND (includeAllTokenTypeAssets OR (UPPER(multi_transactions.from_asset) IN filter_assets) OR (UPPER(multi_transactions.to_asset) IN filter_assets))
|
||||
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT ? OFFSET ?`
|
||||
|
||||
func GetActivityEntries(db *sql.DB, addresses []common.Address, chainIDs []uint64, filter Filter, offset int, limit int) ([]Entry, error) {
|
||||
// Query the transfers, pending_transactions, and multi_transactions tables ordered by timestamp column
|
||||
noEntriesInTmpTableSQLValues = "(NULL)"
|
||||
)
|
||||
|
||||
// GetActivityEntries returns query the transfers, pending_transactions, and multi_transactions tables
|
||||
// based on filter parameters and arguments
|
||||
// it returns metadata for all entries ordered by timestamp column
|
||||
//
|
||||
// Adding a no-limit option was never considered or required.
|
||||
func GetActivityEntries(db *sql.DB, addresses []eth.Address, chainIDs []common.ChainID, filter Filter, offset int, limit int) ([]Entry, error) {
|
||||
// TODO: filter collectibles after they are added to multi_transactions table
|
||||
if len(filter.Tokens.EnabledTypes) > 0 && !sliceContains(filter.Tokens.EnabledTypes, AssetTT) {
|
||||
// For now we deal only with assets so return empty result
|
||||
return []Entry{}, nil
|
||||
}
|
||||
|
||||
includeAllTokenTypeAssets := (len(filter.Tokens.EnabledTypes) == 0 ||
|
||||
sliceContains(filter.Tokens.EnabledTypes, AssetTT)) && len(filter.Tokens.Assets) == 0
|
||||
|
||||
assets := noEntriesInTmpTableSQLValues
|
||||
if !includeAllTokenTypeAssets {
|
||||
assets = joinItems(filter.Tokens.Assets, func(item TokenCode) string { return fmt.Sprintf("'%v'", item) })
|
||||
}
|
||||
|
||||
includeAllNetworks := len(chainIDs) == 0
|
||||
networks := noEntriesInTmpTableSQLValues
|
||||
if !includeAllNetworks {
|
||||
networks = joinItems(chainIDs, nil)
|
||||
}
|
||||
|
||||
// TODO: finish filter: chainIDs, statuses, tokenTypes, counterpartyAddresses
|
||||
// TODO: use all accounts list for detecting SEND/RECEIVE instead of the current addresses list; also change activityType detection in transfer part
|
||||
startFilterDisabled := !(filter.Period.StartTimestamp > 0)
|
||||
endFilterDisabled := !(filter.Period.EndTimestamp > 0)
|
||||
filterActivityTypeAll := typesContain(filter.Types, AllAT) || len(filter.Types) == 0
|
||||
filterActivityTypeAll := len(filter.Types) == 0
|
||||
filterAllAddresses := len(addresses) == 0
|
||||
filterAllToAddresses := len(filter.CounterpartyAddresses) == 0
|
||||
includeAllStatuses := len(filter.Statuses) == 0
|
||||
|
||||
//fmt.Println("@dd filter: timeEnabled", filter.Period.StartTimestamp, filter.Period.EndTimestamp, "; type", filter.Types, "offset", offset, "limit", limit)
|
||||
statusIsPending := false
|
||||
if !includeAllStatuses {
|
||||
statusIsPending = sliceContains(filter.Statuses, PendingAS)
|
||||
}
|
||||
|
||||
joinedAddresses := "(NULL)"
|
||||
involvedAddresses := noEntriesInTmpTableSQLValues
|
||||
if !filterAllAddresses {
|
||||
joinedAddresses = joinAddresses(addresses)
|
||||
involvedAddresses = joinAddresses(addresses)
|
||||
}
|
||||
toAddresses := noEntriesInTmpTableSQLValues
|
||||
if !filterAllToAddresses {
|
||||
toAddresses = joinAddresses(filter.CounterpartyAddresses)
|
||||
}
|
||||
|
||||
mtTypes := activityTypesToMultiTransactionTypes(filter.Types)
|
||||
joinedMTTypes := joinMTTypes(mtTypes)
|
||||
joinedMTTypes := joinItems(mtTypes, func(t transfer.MultiTransactionType) string {
|
||||
return strconv.Itoa(int(t))
|
||||
})
|
||||
|
||||
queryString := fmt.Sprintf(queryFormatString, joinedAddresses, joinedMTTypes)
|
||||
queryString := fmt.Sprintf(queryFormatString, involvedAddresses, toAddresses, assets, networks,
|
||||
joinedMTTypes)
|
||||
|
||||
rows, err := db.Query(queryString,
|
||||
startFilterDisabled, filter.Period.StartTimestamp, endFilterDisabled, filter.Period.EndTimestamp,
|
||||
filterActivityTypeAll, typesContain(filter.Types, SendAT), typesContain(filter.Types, ReceiveAT),
|
||||
filterAllAddresses,
|
||||
filterActivityTypeAll, sliceContains(filter.Types, SendAT), sliceContains(filter.Types, ReceiveAT),
|
||||
filterAllAddresses, filterAllToAddresses, includeAllStatuses, includeAllTokenTypeAssets, statusIsPending,
|
||||
includeAllNetworks,
|
||||
limit, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -261,30 +422,41 @@ func GetActivityEntries(db *sql.DB, addresses []common.Address, chainIDs []uint6
|
|||
var transferHash, pendingHash []byte
|
||||
var chainID, multiTxID sql.NullInt64
|
||||
var timestamp int64
|
||||
var dbActivityType sql.NullByte
|
||||
var dbAddress sql.NullString
|
||||
err := rows.Scan(&transferHash, &pendingHash, &chainID, &multiTxID, ×tamp, &dbActivityType, &dbAddress)
|
||||
var dbMtType, dbTrType sql.NullByte
|
||||
var toAddress, fromAddress eth.Address
|
||||
err := rows.Scan(&transferHash, &pendingHash, &chainID, &multiTxID, ×tamp, &dbMtType, &dbTrType, &fromAddress, &toAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
getActivityType := func(trType sql.NullByte) (activityType Type, filteredAddress eth.Address) {
|
||||
if trType.Valid && trType.Byte == fromTrType {
|
||||
return SendAT, fromAddress
|
||||
}
|
||||
// Don't expect this to happen due to trType = NULL outside of tests
|
||||
return ReceiveAT, toAddress
|
||||
}
|
||||
|
||||
var entry Entry
|
||||
if transferHash != nil && chainID.Valid {
|
||||
var activityType Type = SendAT
|
||||
thisAddress := common.HexToAddress(dbAddress.String)
|
||||
for _, address := range addresses {
|
||||
if address == thisAddress {
|
||||
activityType = ReceiveAT
|
||||
}
|
||||
}
|
||||
entry = NewActivityEntryWithTransaction(SimpleTransactionPT, &transfer.TransactionIdentity{ChainID: uint64(chainID.Int64), Hash: common.BytesToHash(transferHash), Address: thisAddress}, timestamp, activityType)
|
||||
// TODO: extend DB with status in order to filter by status. The status has to be extracted from the receipt upon downloading
|
||||
activityStatus := FinalizedAS
|
||||
activityType, filteredAddress := getActivityType(dbTrType)
|
||||
entry = NewActivityEntryWithTransaction(SimpleTransactionPT,
|
||||
&transfer.TransactionIdentity{ChainID: common.ChainID(chainID.Int64), Hash: eth.BytesToHash(transferHash), Address: filteredAddress},
|
||||
timestamp, activityType, activityStatus)
|
||||
} else if pendingHash != nil && chainID.Valid {
|
||||
var activityType Type = SendAT
|
||||
entry = NewActivityEntryWithTransaction(PendingTransactionPT, &transfer.TransactionIdentity{ChainID: uint64(chainID.Int64), Hash: common.BytesToHash(pendingHash)}, timestamp, activityType)
|
||||
activityStatus := PendingAS
|
||||
activityType, _ := getActivityType(dbTrType)
|
||||
entry = NewActivityEntryWithTransaction(PendingTransactionPT,
|
||||
&transfer.TransactionIdentity{ChainID: common.ChainID(chainID.Int64), Hash: eth.BytesToHash(pendingHash)},
|
||||
timestamp, activityType, activityStatus)
|
||||
} else if multiTxID.Valid {
|
||||
activityType := multiTransactionTypeToActivityType(transfer.MultiTransactionType(dbActivityType.Byte))
|
||||
activityType := multiTransactionTypeToActivityType(transfer.MultiTransactionType(dbMtType.Byte))
|
||||
// TODO: aggregate status from all sub-transactions
|
||||
activityStatus := FinalizedAS
|
||||
entry = NewActivityEntryWithMultiTransaction(transfer.MultiTransactionIDType(multiTxID.Int64),
|
||||
timestamp, activityType)
|
||||
timestamp, activityType, activityStatus)
|
||||
} else {
|
||||
return nil, errors.New("invalid row data")
|
||||
}
|
||||
|
|
|
@ -5,10 +5,14 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/status-im/status-go/appdatabase"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/services/wallet/common"
|
||||
"github.com/status-im/status-go/services/wallet/testutils"
|
||||
"github.com/status-im/status-go/services/wallet/transfer"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
eth "github.com/ethereum/go-ethereum/common"
|
||||
eth_common "github.com/ethereum/go-ethereum/common"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -22,15 +26,6 @@ func setupTestActivityDB(t *testing.T) (db *sql.DB, close func()) {
|
|||
}
|
||||
}
|
||||
|
||||
func insertTestPendingTransaction(t *testing.T, db *sql.DB, tr *transfer.TestTransaction) {
|
||||
_, err := db.Exec(`
|
||||
INSERT INTO pending_transactions (network_id, hash, timestamp, from_address, to_address,
|
||||
symbol, gas_price, gas_limit, value, data, type, additional_data, multi_transaction_id
|
||||
) VALUES (?, ?, ?, ?, ?, 'ETH', 0, 0, ?, '', 'test', '', ?)`,
|
||||
tr.ChainID, tr.Hash, tr.Timestamp, tr.From, tr.To, tr.Value, tr.MultiTransactionID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
type testData struct {
|
||||
tr1 transfer.TestTransaction // index 1
|
||||
pendingTr transfer.TestTransaction // index 2
|
||||
|
@ -51,12 +46,15 @@ func fillTestData(t *testing.T, db *sql.DB) (td testData) {
|
|||
transfer.InsertTestTransfer(t, db, &td.tr1)
|
||||
|
||||
td.pendingTr = trs[1]
|
||||
insertTestPendingTransaction(t, db, &td.pendingTr)
|
||||
transfer.InsertTestPendingTransaction(t, db, &td.pendingTr)
|
||||
|
||||
td.singletonMTr = trs[2]
|
||||
td.singletonMTr.FromToken = testutils.SntSymbol
|
||||
td.singletonMTr.ToToken = testutils.DaiSymbol
|
||||
td.singletonMTID = transfer.InsertTestMultiTransaction(t, db, &td.singletonMTr)
|
||||
|
||||
td.mTr = trs[3]
|
||||
td.mTr.ToToken = testutils.SntSymbol
|
||||
td.mTrID = transfer.InsertTestMultiTransaction(t, db, &td.mTr)
|
||||
|
||||
td.subTr = trs[4]
|
||||
|
@ -65,7 +63,7 @@ func fillTestData(t *testing.T, db *sql.DB) (td testData) {
|
|||
|
||||
td.subPendingTr = trs[5]
|
||||
td.subPendingTr.MultiTransactionID = td.mTrID
|
||||
insertTestPendingTransaction(t, db, &td.subPendingTr)
|
||||
transfer.InsertTestPendingTransaction(t, db, &td.subPendingTr)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -76,7 +74,7 @@ func TestGetActivityEntriesAll(t *testing.T) {
|
|||
td := fillTestData(t, db)
|
||||
|
||||
var filter Filter
|
||||
entries, err := GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 10)
|
||||
entries, err := GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 10)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 4, len(entries))
|
||||
|
||||
|
@ -88,48 +86,60 @@ func TestGetActivityEntriesAll(t *testing.T) {
|
|||
}
|
||||
|
||||
require.True(t, testutils.StructExistsInSlice(Entry{
|
||||
transactionType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: td.tr1.ChainID, Hash: td.tr1.Hash, Address: td.tr1.To},
|
||||
id: td.tr1.MultiTransactionID,
|
||||
timestamp: td.tr1.Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: td.tr1.ChainID, Hash: td.tr1.Hash, Address: td.tr1.To},
|
||||
id: td.tr1.MultiTransactionID,
|
||||
timestamp: td.tr1.Timestamp,
|
||||
activityType: ReceiveAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries))
|
||||
require.True(t, testutils.StructExistsInSlice(Entry{
|
||||
transactionType: PendingTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: td.pendingTr.ChainID, Hash: td.pendingTr.Hash},
|
||||
id: td.pendingTr.MultiTransactionID,
|
||||
timestamp: td.pendingTr.Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: PendingTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: td.pendingTr.ChainID, Hash: td.pendingTr.Hash},
|
||||
id: td.pendingTr.MultiTransactionID,
|
||||
timestamp: td.pendingTr.Timestamp,
|
||||
activityType: ReceiveAT,
|
||||
activityStatus: PendingAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries))
|
||||
require.True(t, testutils.StructExistsInSlice(Entry{
|
||||
transactionType: MultiTransactionPT,
|
||||
transaction: nil,
|
||||
id: td.singletonMTID,
|
||||
timestamp: td.singletonMTr.Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: MultiTransactionPT,
|
||||
transaction: nil,
|
||||
id: td.singletonMTID,
|
||||
timestamp: td.singletonMTr.Timestamp,
|
||||
activityType: SendAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries))
|
||||
require.True(t, testutils.StructExistsInSlice(Entry{
|
||||
transactionType: MultiTransactionPT,
|
||||
transaction: nil,
|
||||
id: td.mTrID,
|
||||
timestamp: td.mTr.Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: MultiTransactionPT,
|
||||
transaction: nil,
|
||||
id: td.mTrID,
|
||||
timestamp: td.mTr.Timestamp,
|
||||
activityType: SendAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries))
|
||||
|
||||
// Ensure the sub-transactions of the multi-transactions are not returned
|
||||
require.False(t, testutils.StructExistsInSlice(Entry{
|
||||
transactionType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: td.subTr.ChainID, Hash: td.subTr.Hash, Address: td.subTr.To},
|
||||
id: td.subTr.MultiTransactionID,
|
||||
timestamp: td.subTr.Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: td.subTr.ChainID, Hash: td.subTr.Hash, Address: td.subTr.To},
|
||||
id: td.subTr.MultiTransactionID,
|
||||
timestamp: td.subTr.Timestamp,
|
||||
activityType: SendAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries))
|
||||
require.False(t, testutils.StructExistsInSlice(Entry{
|
||||
transactionType: PendingTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: td.subPendingTr.ChainID, Hash: td.subPendingTr.Hash},
|
||||
id: td.subPendingTr.MultiTransactionID,
|
||||
timestamp: td.subPendingTr.Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: PendingTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: td.subPendingTr.ChainID, Hash: td.subPendingTr.Hash},
|
||||
id: td.subPendingTr.MultiTransactionID,
|
||||
timestamp: td.subPendingTr.Timestamp,
|
||||
activityType: SendAT,
|
||||
activityStatus: PendingAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries))
|
||||
}
|
||||
|
||||
|
@ -146,14 +156,40 @@ func TestGetActivityEntriesWithSameTransactionForSenderAndReceiverInDB(t *testin
|
|||
prevTo := receiverTr.To
|
||||
receiverTr.To = td.tr1.From
|
||||
receiverTr.From = prevTo
|
||||
|
||||
// TODO: test also when there is a transaction in the other direction
|
||||
|
||||
// Ensure they are the oldest transactions (last in the list) and we have a consistent order
|
||||
receiverTr.Timestamp--
|
||||
transfer.InsertTestTransfer(t, db, &receiverTr)
|
||||
|
||||
var filter Filter
|
||||
entries, err := GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 10)
|
||||
entries, err := GetActivityEntries(db, []eth.Address{td.tr1.From, receiverTr.From}, []common.ChainID{}, filter, 0, 10)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(entries))
|
||||
|
||||
// Check that the transaction are labeled alternatively as send and receive
|
||||
require.Equal(t, ReceiveAT, entries[1].activityType)
|
||||
require.NotEqual(t, eth.Address{}, entries[1].transaction.Address)
|
||||
require.Equal(t, receiverTr.To, entries[1].transaction.Address)
|
||||
|
||||
require.Equal(t, SendAT, entries[0].activityType)
|
||||
require.NotEqual(t, eth.Address{}, entries[0].transaction.Address)
|
||||
require.Equal(t, td.tr1.From, entries[0].transaction.Address)
|
||||
|
||||
// add accounts to DB for proper detection of sender/receiver in all cases
|
||||
accounts.AddTestAccounts(t, db, []*accounts.Account{
|
||||
{Address: types.Address(td.tr1.From), Chat: false, Wallet: true},
|
||||
{Address: types.Address(receiverTr.From)},
|
||||
})
|
||||
|
||||
entries, err = GetActivityEntries(db, []eth.Address{}, []common.ChainID{}, filter, 0, 10)
|
||||
require.NoError(t, err)
|
||||
// TODO: decide how should we handle this case filter out or include it in the result
|
||||
// For now we include both. Can be changed by using UNION instead of UNION ALL in the query or by filtering out
|
||||
require.Equal(t, 5, len(entries))
|
||||
|
||||
// Check that the transaction are labeled alternatively as send and receive
|
||||
require.Equal(t, ReceiveAT, entries[4].activityType)
|
||||
require.Equal(t, SendAT, entries[3].activityType)
|
||||
}
|
||||
|
||||
func TestGetActivityEntriesFilterByTime(t *testing.T) {
|
||||
|
@ -170,65 +206,78 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
|
|||
// Test start only
|
||||
var filter Filter
|
||||
filter.Period.StartTimestamp = td.singletonMTr.Timestamp
|
||||
entries, err := GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 15)
|
||||
filter.Period.EndTimestamp = NoLimitTimestampForPeriod
|
||||
entries, err := GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 8, len(entries))
|
||||
// Check start and end content
|
||||
require.Equal(t, Entry{
|
||||
transactionType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[5].ChainID, Hash: trs[5].Hash, Address: trs[5].To},
|
||||
id: 0,
|
||||
timestamp: trs[5].Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[5].ChainID, Hash: trs[5].Hash, Address: trs[5].To},
|
||||
id: 0,
|
||||
timestamp: trs[5].Timestamp,
|
||||
activityType: ReceiveAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries[0])
|
||||
require.Equal(t, Entry{
|
||||
transactionType: MultiTransactionPT,
|
||||
transaction: nil,
|
||||
id: td.singletonMTID,
|
||||
timestamp: td.singletonMTr.Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: MultiTransactionPT,
|
||||
transaction: nil,
|
||||
id: td.singletonMTID,
|
||||
timestamp: td.singletonMTr.Timestamp,
|
||||
activityType: SendAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries[7])
|
||||
|
||||
// Test complete interval
|
||||
filter.Period.EndTimestamp = trs[2].Timestamp
|
||||
entries, err = GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 15)
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 5, len(entries))
|
||||
// Check start and end content
|
||||
require.Equal(t, Entry{
|
||||
transactionType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
|
||||
id: 0,
|
||||
timestamp: trs[2].Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
|
||||
id: 0,
|
||||
timestamp: trs[2].Timestamp,
|
||||
activityType: ReceiveAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries[0])
|
||||
require.Equal(t, Entry{
|
||||
transactionType: MultiTransactionPT,
|
||||
transaction: nil,
|
||||
id: td.singletonMTID,
|
||||
timestamp: td.singletonMTr.Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: MultiTransactionPT,
|
||||
transaction: nil,
|
||||
id: td.singletonMTID,
|
||||
timestamp: td.singletonMTr.Timestamp,
|
||||
activityType: SendAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries[4])
|
||||
|
||||
// Test end only
|
||||
filter.Period.StartTimestamp = 0
|
||||
entries, err = GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 15)
|
||||
filter.Period.StartTimestamp = NoLimitTimestampForPeriod
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 7, len(entries))
|
||||
// Check start and end content
|
||||
require.Equal(t, Entry{
|
||||
transactionType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
|
||||
id: 0,
|
||||
timestamp: trs[2].Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
|
||||
id: 0,
|
||||
timestamp: trs[2].Timestamp,
|
||||
activityType: ReceiveAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries[0])
|
||||
require.Equal(t, Entry{
|
||||
transactionType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: td.tr1.ChainID, Hash: td.tr1.Hash, Address: td.tr1.To},
|
||||
id: 0,
|
||||
timestamp: td.tr1.Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: td.tr1.ChainID, Hash: td.tr1.Hash, Address: td.tr1.To},
|
||||
id: 0,
|
||||
timestamp: td.tr1.Timestamp,
|
||||
activityType: ReceiveAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries[6])
|
||||
}
|
||||
|
||||
|
@ -244,63 +293,73 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
|
|||
|
||||
var filter Filter
|
||||
// Get all
|
||||
entries, err := GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 5)
|
||||
entries, err := GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 5)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 5, len(entries))
|
||||
|
||||
// Get time based interval
|
||||
filter.Period.StartTimestamp = trs[2].Timestamp
|
||||
filter.Period.EndTimestamp = trs[8].Timestamp
|
||||
entries, err = GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 3)
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 3)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(entries))
|
||||
// Check start and end content
|
||||
require.Equal(t, Entry{
|
||||
transactionType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[8].ChainID, Hash: trs[8].Hash, Address: trs[8].To},
|
||||
id: 0,
|
||||
timestamp: trs[8].Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[8].ChainID, Hash: trs[8].Hash, Address: trs[8].To},
|
||||
id: 0,
|
||||
timestamp: trs[8].Timestamp,
|
||||
activityType: ReceiveAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries[0])
|
||||
require.Equal(t, Entry{
|
||||
transactionType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[6].ChainID, Hash: trs[6].Hash, Address: trs[6].To},
|
||||
id: 0,
|
||||
timestamp: trs[6].Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[6].ChainID, Hash: trs[6].Hash, Address: trs[6].To},
|
||||
id: 0,
|
||||
timestamp: trs[6].Timestamp,
|
||||
activityType: ReceiveAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries[2])
|
||||
|
||||
// Move window 2 entries forward
|
||||
entries, err = GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 2, 3)
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 2, 3)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(entries))
|
||||
// Check start and end content
|
||||
require.Equal(t, Entry{
|
||||
transactionType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[6].ChainID, Hash: trs[6].Hash, Address: trs[6].To},
|
||||
id: 0,
|
||||
timestamp: trs[6].Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[6].ChainID, Hash: trs[6].Hash, Address: trs[6].To},
|
||||
id: 0,
|
||||
timestamp: trs[6].Timestamp,
|
||||
activityType: ReceiveAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries[0])
|
||||
require.Equal(t, Entry{
|
||||
transactionType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[4].ChainID, Hash: trs[4].Hash, Address: trs[4].To},
|
||||
id: 0,
|
||||
timestamp: trs[4].Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[4].ChainID, Hash: trs[4].Hash, Address: trs[4].To},
|
||||
id: 0,
|
||||
timestamp: trs[4].Timestamp,
|
||||
activityType: ReceiveAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries[2])
|
||||
|
||||
// Move window 4 more entries to test filter cap
|
||||
entries, err = GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 6, 3)
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 6, 3)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(entries))
|
||||
// Check start and end content
|
||||
require.Equal(t, Entry{
|
||||
transactionType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
|
||||
id: 0,
|
||||
timestamp: trs[2].Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
|
||||
id: 0,
|
||||
timestamp: trs[2].Timestamp,
|
||||
activityType: ReceiveAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries[0])
|
||||
}
|
||||
|
||||
|
@ -310,7 +369,7 @@ func TestGetActivityEntriesFilterByType(t *testing.T) {
|
|||
|
||||
// Adds 4 extractable transactions
|
||||
fillTestData(t, db)
|
||||
// Add 6 extractable transactions: one MultiTransactionSwap, two MultiTransactionBridge rest Send
|
||||
// Add 6 extractable transactions: one MultiTransactionSwap, two MultiTransactionBridge rest MultiTransactionSend
|
||||
trs := transfer.GenerateTestTransactions(t, db, 6, 6)
|
||||
trs[1].MultiTransactionType = transfer.MultiTransactionBridge
|
||||
trs[3].MultiTransactionType = transfer.MultiTransactionSwap
|
||||
|
@ -326,33 +385,43 @@ func TestGetActivityEntriesFilterByType(t *testing.T) {
|
|||
|
||||
// Test filtering out without address involved
|
||||
var filter Filter
|
||||
// TODO: add more types to cover all cases
|
||||
|
||||
filter.Types = allActivityTypesFilter()
|
||||
entries, err := GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 10, len(entries))
|
||||
|
||||
filter.Types = []Type{SendAT, SwapAT}
|
||||
entries, err := GetActivityEntries(db, []common.Address{}, []uint64{}, filter, 0, 15)
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 8, len(entries))
|
||||
swapCount := 0
|
||||
sendCount := 0
|
||||
receiveCount := 0
|
||||
for _, entry := range entries {
|
||||
if entry.activityType == SendAT {
|
||||
sendCount++
|
||||
}
|
||||
if entry.activityType == ReceiveAT {
|
||||
receiveCount++
|
||||
}
|
||||
if entry.activityType == SwapAT {
|
||||
swapCount++
|
||||
}
|
||||
}
|
||||
require.Equal(t, 7, sendCount)
|
||||
require.Equal(t, 2, sendCount)
|
||||
require.Equal(t, 5, receiveCount)
|
||||
require.Equal(t, 1, swapCount)
|
||||
|
||||
// Test filtering out with address involved
|
||||
filter.Types = []Type{BridgeAT, ReceiveAT}
|
||||
// Include one "to" from transfers to be detected as receive
|
||||
addresses := []common.Address{trs[0].To, trs[1].To, trs[2].From, trs[3].From, trs[5].From}
|
||||
entries, err = GetActivityEntries(db, addresses, []uint64{}, filter, 0, 15)
|
||||
addresses := []eth_common.Address{trs[0].To, trs[1].To, trs[2].From, trs[3].From, trs[5].From}
|
||||
entries, err = GetActivityEntries(db, addresses, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(entries))
|
||||
bridgeCount := 0
|
||||
receiveCount := 0
|
||||
receiveCount = 0
|
||||
for _, entry := range entries {
|
||||
if entry.activityType == BridgeAT {
|
||||
bridgeCount++
|
||||
|
@ -365,42 +434,241 @@ func TestGetActivityEntriesFilterByType(t *testing.T) {
|
|||
require.Equal(t, 1, receiveCount)
|
||||
}
|
||||
|
||||
func TestGetActivityEntriesFilterByAddress(t *testing.T) {
|
||||
func TestGetActivityEntriesFilterByAddresses(t *testing.T) {
|
||||
db, close := setupTestActivityDB(t)
|
||||
defer close()
|
||||
|
||||
// Adds 4 extractable transactions
|
||||
td := fillTestData(t, db)
|
||||
// Add 6 extractable transactions: one MultiTransactionSwap, two MultiTransactionBridge rest Send
|
||||
trs := transfer.GenerateTestTransactions(t, db, 7, 6)
|
||||
for i := range trs {
|
||||
transfer.InsertTestTransfer(t, db, &trs[i])
|
||||
}
|
||||
|
||||
var filter Filter
|
||||
addressesFilter := []common.Address{td.mTr.To, trs[1].From, trs[4].To}
|
||||
entries, err := GetActivityEntries(db, addressesFilter, []uint64{}, filter, 0, 15)
|
||||
|
||||
addressesFilter := allAddressesFilter()
|
||||
entries, err := GetActivityEntries(db, addressesFilter, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 10, len(entries))
|
||||
|
||||
addressesFilter = []eth_common.Address{td.mTr.To, trs[1].From, trs[4].To}
|
||||
entries, err = GetActivityEntries(db, addressesFilter, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(entries))
|
||||
require.Equal(t, Entry{
|
||||
transactionType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[4].ChainID, Hash: trs[4].Hash, Address: trs[4].To},
|
||||
id: 0,
|
||||
timestamp: trs[4].Timestamp,
|
||||
activityType: ReceiveAT,
|
||||
payloadType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[4].ChainID, Hash: trs[4].Hash, Address: trs[4].To},
|
||||
id: 0,
|
||||
timestamp: trs[4].Timestamp,
|
||||
activityType: ReceiveAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries[0])
|
||||
require.Equal(t, Entry{
|
||||
transactionType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[1].ChainID, Hash: trs[1].Hash, Address: trs[1].To},
|
||||
id: 0,
|
||||
timestamp: trs[1].Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: SimpleTransactionPT,
|
||||
transaction: &transfer.TransactionIdentity{ChainID: trs[1].ChainID, Hash: trs[1].Hash, Address: trs[1].From},
|
||||
id: 0,
|
||||
timestamp: trs[1].Timestamp,
|
||||
activityType: SendAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries[1])
|
||||
require.Equal(t, Entry{
|
||||
transactionType: MultiTransactionPT,
|
||||
transaction: nil,
|
||||
id: td.mTrID,
|
||||
timestamp: td.mTr.Timestamp,
|
||||
activityType: SendAT,
|
||||
payloadType: MultiTransactionPT,
|
||||
transaction: nil,
|
||||
id: td.mTrID,
|
||||
timestamp: td.mTr.Timestamp,
|
||||
activityType: SendAT,
|
||||
activityStatus: FinalizedAS,
|
||||
tokenType: AssetTT,
|
||||
}, entries[2])
|
||||
}
|
||||
|
||||
func TestGetActivityEntriesFilterByStatus(t *testing.T) {
|
||||
db, close := setupTestActivityDB(t)
|
||||
defer close()
|
||||
|
||||
// Adds 4 extractable transactions
|
||||
fillTestData(t, db)
|
||||
// Add 6 extractable transactions
|
||||
trs := transfer.GenerateTestTransactions(t, db, 7, 6)
|
||||
for i := range trs {
|
||||
transfer.InsertTestTransfer(t, db, &trs[i])
|
||||
}
|
||||
|
||||
var filter Filter
|
||||
filter.Statuses = []Status{}
|
||||
entries, err := GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 10, len(entries))
|
||||
|
||||
filter.Statuses = allActivityStatusesFilter()
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 10, len(entries))
|
||||
|
||||
// TODO: enabled and finish tests after extending DB with transaction status
|
||||
//
|
||||
// filter.Statuses = []Status{PendingAS}
|
||||
// entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, 1, len(entries))
|
||||
|
||||
// filter.Statuses = []Status{FailedAS, CompleteAS}
|
||||
// entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, 9, len(entries))
|
||||
}
|
||||
|
||||
func TestGetActivityEntriesFilterByTokenType(t *testing.T) {
|
||||
db, close := setupTestActivityDB(t)
|
||||
defer close()
|
||||
|
||||
// Adds 4 extractable transactions 2 transactions ETH, one MT SNT to DAI and another MT ETH to SNT
|
||||
fillTestData(t, db)
|
||||
// Add 6 extractable transactions with USDC (only erc20 as type in DB)
|
||||
trs := transfer.GenerateTestTransactions(t, db, 7, 6)
|
||||
for i := range trs {
|
||||
trs[i].FromToken = "USDC"
|
||||
transfer.InsertTestTransfer(t, db, &trs[i])
|
||||
}
|
||||
|
||||
var filter Filter
|
||||
filter.Tokens = noAssetsFilter()
|
||||
entries, err := GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(entries))
|
||||
|
||||
filter.Tokens = allTokensFilter()
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 10, len(entries))
|
||||
|
||||
// Regression when collectibles is nil
|
||||
filter.Tokens = Tokens{[]TokenCode{}, nil, []TokenType{}}
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 10, len(entries))
|
||||
|
||||
filter.Tokens = Tokens{Assets: []TokenCode{"ETH"}, EnabledTypes: []TokenType{AssetTT}}
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(entries))
|
||||
|
||||
// TODO: update tests after adding token type to transfers
|
||||
filter.Tokens = Tokens{Assets: []TokenCode{"USDC", "DAI"}, EnabledTypes: []TokenType{AssetTT}}
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(entries))
|
||||
|
||||
// Regression when EnabledTypes ar empty
|
||||
filter.Tokens = Tokens{Assets: []TokenCode{"USDC", "DAI"}, EnabledTypes: []TokenType{}}
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(entries))
|
||||
}
|
||||
|
||||
func TestGetActivityEntriesFilterByToAddresses(t *testing.T) {
|
||||
db, close := setupTestActivityDB(t)
|
||||
defer close()
|
||||
|
||||
// Adds 4 extractable transactions
|
||||
td := fillTestData(t, db)
|
||||
// Add 6 extractable transactions
|
||||
trs := transfer.GenerateTestTransactions(t, db, 7, 6)
|
||||
for i := range trs {
|
||||
transfer.InsertTestTransfer(t, db, &trs[i])
|
||||
}
|
||||
|
||||
var filter Filter
|
||||
filter.CounterpartyAddresses = allAddressesFilter()
|
||||
entries, err := GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 10, len(entries))
|
||||
|
||||
filter.CounterpartyAddresses = []eth_common.Address{eth_common.HexToAddress("0x567890")}
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(entries))
|
||||
|
||||
filter.CounterpartyAddresses = []eth_common.Address{td.pendingTr.To, td.mTr.To, trs[3].To}
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(entries))
|
||||
|
||||
filter.CounterpartyAddresses = []eth_common.Address{td.tr1.To, td.pendingTr.From, trs[3].From, trs[5].To}
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(entries))
|
||||
}
|
||||
func TestGetActivityEntriesFilterByNetworks(t *testing.T) {
|
||||
db, close := setupTestActivityDB(t)
|
||||
defer close()
|
||||
|
||||
// Adds 4 extractable transactions
|
||||
td := fillTestData(t, db)
|
||||
// Add 6 extractable transactions
|
||||
trs := transfer.GenerateTestTransactions(t, db, 7, 6)
|
||||
for i := range trs {
|
||||
transfer.InsertTestTransfer(t, db, &trs[i])
|
||||
}
|
||||
|
||||
var filter Filter
|
||||
chainIDs := allNetworksFilter()
|
||||
entries, err := GetActivityEntries(db, []eth_common.Address{}, chainIDs, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 10, len(entries))
|
||||
|
||||
chainIDs = []common.ChainID{5674839210}
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, chainIDs, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
// TODO: update after multi-transactions are filterable by ChainID
|
||||
require.Equal(t, 2 /*0*/, len(entries))
|
||||
|
||||
chainIDs = []common.ChainID{td.pendingTr.ChainID, td.mTr.ChainID, trs[3].ChainID}
|
||||
entries, err = GetActivityEntries(db, []eth_common.Address{}, chainIDs, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
// TODO: update after multi-transactions are filterable by ChainID
|
||||
require.Equal(t, 4 /*3*/, len(entries))
|
||||
}
|
||||
|
||||
func TestGetActivityEntriesCheckToAndFrom(t *testing.T) {
|
||||
db, close := setupTestActivityDB(t)
|
||||
defer close()
|
||||
|
||||
// Adds 6 transactions from which 4 are filered out
|
||||
td := fillTestData(t, db)
|
||||
|
||||
// Add extra transactions to test To address
|
||||
trs := transfer.GenerateTestTransactions(t, db, 7, 2)
|
||||
transfer.InsertTestTransfer(t, db, &trs[0])
|
||||
transfer.InsertTestPendingTransaction(t, db, &trs[1])
|
||||
|
||||
addresses := []eth_common.Address{td.tr1.From, td.pendingTr.From,
|
||||
td.singletonMTr.From, td.mTr.To, trs[0].To, trs[1].To}
|
||||
|
||||
var filter Filter
|
||||
entries, err := GetActivityEntries(db, addresses, []common.ChainID{}, filter, 0, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 6, len(entries))
|
||||
|
||||
require.Equal(t, SendAT, entries[5].activityType) // td.tr1
|
||||
require.NotEqual(t, eth.Address{}, entries[5].transaction.Address) // td.tr1
|
||||
require.Equal(t, td.tr1.From, entries[5].transaction.Address) // td.tr1
|
||||
|
||||
require.Equal(t, SendAT, entries[4].activityType) // td.pendingTr
|
||||
|
||||
// Multi-transactions are always considered as SendAT
|
||||
require.Equal(t, SendAT, entries[3].activityType) // td.singletonMTr
|
||||
require.Equal(t, SendAT, entries[2].activityType) // td.mTr
|
||||
|
||||
require.Equal(t, ReceiveAT, entries[1].activityType) // trs[0]
|
||||
require.NotEqual(t, eth.Address{}, entries[1].transaction.Address) // trs[0]
|
||||
require.Equal(t, trs[0].To, entries[1].transaction.Address) // trs[0]
|
||||
|
||||
require.Equal(t, ReceiveAT, entries[0].activityType) // trs[1] (pending)
|
||||
|
||||
// TODO: add accounts to DB for proper detection of sender/receiver
|
||||
// TODO: Test with all addresses
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
package activity
|
||||
|
||||
import "github.com/ethereum/go-ethereum/common"
|
||||
import (
|
||||
eth_common "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/status-im/status-go/services/wallet/common"
|
||||
)
|
||||
|
||||
const NoLimitTimestampForPeriod = 0
|
||||
|
||||
type Period struct {
|
||||
// 0 means no limit
|
||||
StartTimestamp int64 `json:"startTimestamp"`
|
||||
EndTimestamp int64 `json:"endTimestamp"`
|
||||
}
|
||||
|
@ -11,36 +15,69 @@ type Period struct {
|
|||
type Type int
|
||||
|
||||
const (
|
||||
AllAT Type = iota
|
||||
SendAT
|
||||
SendAT Type = iota
|
||||
ReceiveAT
|
||||
BuyAT
|
||||
SwapAT
|
||||
BridgeAT
|
||||
)
|
||||
|
||||
func allActivityTypesFilter() []Type {
|
||||
return []Type{}
|
||||
}
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
AllAS Status = iota
|
||||
FailedAS
|
||||
FailedAS Status = iota
|
||||
PendingAS
|
||||
CompleteAS
|
||||
FinalizedAS
|
||||
)
|
||||
|
||||
func allActivityStatusesFilter() []Status {
|
||||
return []Status{}
|
||||
}
|
||||
|
||||
type TokenType int
|
||||
|
||||
const (
|
||||
AllTT TokenType = iota
|
||||
AssetTT
|
||||
AssetTT TokenType = iota
|
||||
CollectiblesTT
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
Period Period `json:"period"`
|
||||
Types []Type `json:"types"`
|
||||
Statuses []Status `json:"statuses"`
|
||||
TokenTypes []TokenType `json:"tokenTypes"`
|
||||
CounterpartyAddresses []common.Address `json:"counterpartyAddresses"`
|
||||
type TokenCode string
|
||||
|
||||
// Tokens the following rules apply for its members:
|
||||
// empty member: none is selected
|
||||
// nil means all
|
||||
// see allTokensFilter and noTokensFilter
|
||||
type Tokens struct {
|
||||
Assets []TokenCode `json:"assets"`
|
||||
Collectibles []eth_common.Address `json:"collectibles"`
|
||||
EnabledTypes []TokenType `json:"enabledTypes"`
|
||||
}
|
||||
|
||||
func noAssetsFilter() Tokens {
|
||||
return Tokens{[]TokenCode{}, []eth_common.Address{}, []TokenType{CollectiblesTT}}
|
||||
}
|
||||
|
||||
func allTokensFilter() Tokens {
|
||||
return Tokens{}
|
||||
}
|
||||
|
||||
func allAddressesFilter() []eth_common.Address {
|
||||
return []eth_common.Address{}
|
||||
}
|
||||
|
||||
func allNetworksFilter() []common.ChainID {
|
||||
return []common.ChainID{}
|
||||
}
|
||||
|
||||
type Filter struct {
|
||||
Period Period `json:"period"`
|
||||
Types []Type `json:"types"`
|
||||
Statuses []Status `json:"statuses"`
|
||||
Tokens Tokens `json:"tokens"`
|
||||
CounterpartyAddresses []eth_common.Address `json:"counterpartyAddresses"`
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ import (
|
|||
"github.com/status-im/status-go/services/wallet/thirdparty/opensea"
|
||||
"github.com/status-im/status-go/services/wallet/token"
|
||||
"github.com/status-im/status-go/services/wallet/transfer"
|
||||
|
||||
wallet_common "github.com/status-im/status-go/services/wallet/common"
|
||||
)
|
||||
|
||||
func NewAPI(s *Service) *API {
|
||||
|
@ -247,7 +249,7 @@ func (api *API) GetPendingTransactionsForIdentities(ctx context.Context, identit
|
|||
result = make([]*transfer.PendingTransaction, 0, len(identities))
|
||||
var pt *transfer.PendingTransaction
|
||||
for _, identity := range identities {
|
||||
pt, err = api.s.transactionManager.GetPendingEntry(identity.ChainID, identity.Hash)
|
||||
pt, err = api.s.transactionManager.GetPendingEntry(uint64(identity.ChainID), identity.Hash)
|
||||
result = append(result, pt)
|
||||
}
|
||||
|
||||
|
@ -525,7 +527,7 @@ func (api *API) FetchAllCurrencyFormats() (currency.FormatPerSymbol, error) {
|
|||
return api.s.currency.FetchAllCurrencyFormats()
|
||||
}
|
||||
|
||||
func (api *API) GetActivityEntries(addresses []common.Address, chainIDs []uint64, filter activity.Filter, offset int, limit int) ([]activity.Entry, error) {
|
||||
func (api *API) GetActivityEntries(addresses []common.Address, chainIDs []wallet_common.ChainID, filter activity.Filter, offset int, limit int) ([]activity.Entry, error) {
|
||||
log.Debug("call to GetActivityEntries")
|
||||
return activity.GetActivityEntries(api.s.db, addresses, chainIDs, filter, offset, limit)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,10 @@ package testutils
|
|||
|
||||
import "reflect"
|
||||
|
||||
const EthSymbol = "ETH"
|
||||
const SntSymbol = "SNT"
|
||||
const DaiSymbol = "DAI"
|
||||
|
||||
func StructExistsInSlice[T any](target T, slice []T) bool {
|
||||
for _, item := range slice {
|
||||
if reflect.DeepEqual(target, item) {
|
||||
|
|
|
@ -279,7 +279,7 @@ func (db *Database) GetTransfersForIdentities(ctx context.Context, identities []
|
|||
for _, identity := range identities {
|
||||
subQuery := newSubQuery()
|
||||
// TODO optimization: consider using tuples in sqlite and IN operator
|
||||
subQuery = subQuery.FilterNetwork(identity.ChainID).FilterTransactionHash(identity.Hash).FilterAddress(identity.Address)
|
||||
subQuery = subQuery.FilterNetwork(uint64(identity.ChainID)).FilterTransactionHash(identity.Hash).FilterAddress(identity.Address)
|
||||
query.addSubQuery(subQuery, OrSeparator)
|
||||
}
|
||||
rows, err := db.client.QueryContext(ctx, query.String(), query.Args()...)
|
||||
|
|
|
@ -223,8 +223,8 @@ func TestGetTransfersForIdentities(t *testing.T) {
|
|||
require.Equal(t, big.NewInt(trs[3].BlkNumber), entries[1].BlockNumber)
|
||||
require.Equal(t, uint64(trs[1].Timestamp), entries[0].Timestamp)
|
||||
require.Equal(t, uint64(trs[3].Timestamp), entries[1].Timestamp)
|
||||
require.Equal(t, trs[1].ChainID, entries[0].NetworkID)
|
||||
require.Equal(t, trs[3].ChainID, entries[1].NetworkID)
|
||||
require.Equal(t, uint64(trs[1].ChainID), entries[0].NetworkID)
|
||||
require.Equal(t, uint64(trs[3].ChainID), entries[1].NetworkID)
|
||||
require.Equal(t, MultiTransactionIDType(0), entries[0].MultiTransactionID)
|
||||
require.Equal(t, MultiTransactionIDType(0), entries[1].MultiTransactionID)
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ func (q *transfersQuery) addWhereSeparator(separator SeparatorType) {
|
|||
|
||||
type SeparatorType int
|
||||
|
||||
// Beware if changing this enum please update addWhereSeparator as well
|
||||
const (
|
||||
NoSeparator SeparatorType = iota + 1
|
||||
OrSeparator
|
||||
|
|
|
@ -3,18 +3,23 @@ package transfer
|
|||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
eth_common "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/status-im/status-go/services/wallet/common"
|
||||
"github.com/status-im/status-go/services/wallet/testutils"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type TestTransaction struct {
|
||||
Hash common.Hash
|
||||
ChainID uint64
|
||||
From common.Address // [sender]
|
||||
To common.Address // [address]
|
||||
Hash eth_common.Hash
|
||||
ChainID common.ChainID
|
||||
From eth_common.Address // [sender]
|
||||
To eth_common.Address // [address]
|
||||
FromToken string // used to detect type in transfers table
|
||||
ToToken string // only used in multi_transactions table
|
||||
Timestamp int64
|
||||
Value int64
|
||||
BlkNumber int64
|
||||
|
@ -25,10 +30,10 @@ type TestTransaction struct {
|
|||
func GenerateTestTransactions(t *testing.T, db *sql.DB, firstStartIndex int, count int) (result []TestTransaction) {
|
||||
for i := firstStartIndex; i < (firstStartIndex + count); i++ {
|
||||
tr := TestTransaction{
|
||||
Hash: common.HexToHash(fmt.Sprintf("0x1%d", i)),
|
||||
ChainID: uint64(i),
|
||||
From: common.HexToAddress(fmt.Sprintf("0x2%d", i)),
|
||||
To: common.HexToAddress(fmt.Sprintf("0x3%d", i)),
|
||||
Hash: eth_common.HexToHash(fmt.Sprintf("0x1%d", i)),
|
||||
ChainID: common.ChainID(i),
|
||||
From: eth_common.HexToAddress(fmt.Sprintf("0x2%d", i)),
|
||||
To: eth_common.HexToAddress(fmt.Sprintf("0x3%d", i)),
|
||||
Timestamp: int64(i),
|
||||
Value: int64(i),
|
||||
BlkNumber: int64(i),
|
||||
|
@ -42,7 +47,11 @@ func GenerateTestTransactions(t *testing.T, db *sql.DB, firstStartIndex int, cou
|
|||
|
||||
func InsertTestTransfer(t *testing.T, db *sql.DB, tr *TestTransaction) {
|
||||
// Respect `FOREIGN KEY(network_id,address,blk_hash)` of `transfers` table
|
||||
blkHash := common.HexToHash("4")
|
||||
tokenType := "eth"
|
||||
if tr.FromToken != "" && strings.ToUpper(tr.FromToken) != testutils.EthSymbol {
|
||||
tokenType = "erc20"
|
||||
}
|
||||
blkHash := eth_common.HexToHash("4")
|
||||
_, err := db.Exec(`
|
||||
INSERT OR IGNORE INTO blocks(
|
||||
network_id, address, blk_number, blk_hash
|
||||
|
@ -50,17 +59,34 @@ func InsertTestTransfer(t *testing.T, db *sql.DB, tr *TestTransaction) {
|
|||
INSERT INTO transfers (network_id, hash, address, blk_hash, tx,
|
||||
sender, receipt, log, type, blk_number, timestamp, loaded,
|
||||
multi_transaction_id, base_gas_fee
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, "test", ?, ?, 0, ?, 0)`,
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, 0)`,
|
||||
tr.ChainID, tr.To, tr.BlkNumber, blkHash,
|
||||
tr.ChainID, tr.Hash, tr.To, blkHash, &JSONBlob{}, tr.From, &JSONBlob{}, &JSONBlob{}, tr.BlkNumber, tr.Timestamp, tr.MultiTransactionID)
|
||||
tr.ChainID, tr.Hash, tr.To, blkHash, &JSONBlob{}, tr.From, &JSONBlob{}, &JSONBlob{}, tokenType, tr.BlkNumber, tr.Timestamp, tr.MultiTransactionID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func InsertTestPendingTransaction(t *testing.T, db *sql.DB, tr *TestTransaction) {
|
||||
_, err := db.Exec(`
|
||||
INSERT INTO pending_transactions (network_id, hash, timestamp, from_address, to_address,
|
||||
symbol, gas_price, gas_limit, value, data, type, additional_data, multi_transaction_id
|
||||
) VALUES (?, ?, ?, ?, ?, 'ETH', 0, 0, ?, '', 'test', '', ?)`,
|
||||
tr.ChainID, tr.Hash, tr.Timestamp, tr.From, tr.To, tr.Value, tr.MultiTransactionID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func InsertTestMultiTransaction(t *testing.T, db *sql.DB, tr *TestTransaction) MultiTransactionIDType {
|
||||
fromTokenType := tr.FromToken
|
||||
if tr.FromToken == "" {
|
||||
fromTokenType = testutils.EthSymbol
|
||||
}
|
||||
toTokenType := tr.ToToken
|
||||
if tr.ToToken == "" {
|
||||
toTokenType = testutils.EthSymbol
|
||||
}
|
||||
result, err := db.Exec(`
|
||||
INSERT INTO multi_transactions (from_address, from_asset, from_amount, to_address, to_asset, type, timestamp
|
||||
) VALUES (?, 'ETH', 0, ?, 'SNT', ?, ?)`,
|
||||
tr.From, tr.To, tr.MultiTransactionType, tr.Timestamp)
|
||||
) VALUES (?, ?, 0, ?, ?, ?, ?)`,
|
||||
tr.From, fromTokenType, tr.To, toTokenType, tr.MultiTransactionType, tr.Timestamp)
|
||||
require.NoError(t, err)
|
||||
rowID, err := result.LastInsertId()
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/status-im/status-go/services/wallet/async"
|
||||
"github.com/status-im/status-go/services/wallet/bigint"
|
||||
"github.com/status-im/status-go/services/wallet/bridge"
|
||||
wallet_common "github.com/status-im/status-go/services/wallet/common"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
)
|
||||
|
||||
|
@ -96,9 +97,9 @@ type PendingTransaction struct {
|
|||
}
|
||||
|
||||
type TransactionIdentity struct {
|
||||
ChainID uint64 `json:"chainId"`
|
||||
Hash common.Hash `json:"hash"`
|
||||
Address common.Address `json:"address"`
|
||||
ChainID wallet_common.ChainID `json:"chainId"`
|
||||
Hash common.Hash `json:"hash"`
|
||||
Address common.Address `json:"address"`
|
||||
}
|
||||
|
||||
const selectFromPending = `SELECT hash, timestamp, value, from_address, to_address, data,
|
||||
|
|
Loading…
Reference in New Issue