status-go/services/wallet/activity/activity_test.go
Stefan 70341f85a5 fix(wallet) send/receive for duplicate transactions
Brings consistency in case when sender and receiver are both in the
filter address list. This fixes the case of sender and receiver in
addresses and filters out duplicate entries.

Also

- refactor tests to provide support for owners
- adapt TestGetActivityEntriesWithSameTransactionForSenderAndReceiverInDB
  to the use of owner instead of from
2023-09-12 11:58:24 +02:00

1453 lines
54 KiB
Go

package activity
import (
"context"
"database/sql"
"fmt"
"math/big"
"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/status-im/status-go/t/helpers"
"github.com/status-im/status-go/walletdatabase"
eth "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
)
func tokenFromSymbol(chainID *common.ChainID, symbol string) *Token {
for i, t := range transfer.TestTokens {
if (chainID == nil || t.ChainID == uint64(*chainID)) && t.Symbol == symbol {
tokenType := Erc20
if testutils.SliceContains(transfer.NativeTokenIndices, i) {
tokenType = Native
}
return &Token{
TokenType: tokenType,
ChainID: common.ChainID(t.ChainID),
Address: t.Address,
}
}
}
return nil
}
func setupTestActivityDBStorageChoice(tb testing.TB, inMemory bool) (deps FilterDependencies, close func()) {
var db, appDb *sql.DB
var err error
cleanupDB := func() error { return nil }
cleanupWalletDB := func() error { return nil }
if inMemory {
db, err = helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(tb, err)
appDb, err = helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
require.NoError(tb, err)
} else {
db, cleanupWalletDB, err = helpers.SetupTestSQLDB(walletdatabase.DbInitializer{}, "wallet-activity-tests")
require.NoError(tb, err)
appDb, cleanupDB, err = helpers.SetupTestSQLDB(appdatabase.DbInitializer{}, "wallet-activity-tests")
require.NoError(tb, err)
}
accountsDb, err := accounts.NewDB(appDb)
require.NoError(tb, err)
deps = FilterDependencies{
db: db,
accountsDb: accountsDb,
tokenSymbol: func(token Token) string {
switch token.TokenType {
case Native:
for i, t := range transfer.TestTokens {
if t.ChainID == uint64(token.ChainID) && testutils.SliceContains(transfer.NativeTokenIndices, i) {
return t.Symbol
}
}
case Erc20:
for _, t := range transfer.TestTokens {
if t.ChainID == uint64(token.ChainID) && t.Address == token.Address {
return t.Symbol
}
}
}
// In case of ERC721 and ERC1155 we don't have a symbol and they are not yet handled
return ""
},
// tokenFromSymbol nil chainID accepts first symbol found
tokenFromSymbol: tokenFromSymbol,
}
return deps, func() {
require.NoError(tb, cleanupDB())
require.NoError(tb, cleanupWalletDB())
}
}
func setupTestActivityDB(tb testing.TB) (deps FilterDependencies, close func()) {
return setupTestActivityDBStorageChoice(tb, true)
}
type testData struct {
tr1 transfer.TestTransfer // index 1, ETH/Goerli
pendingTr transfer.TestTransfer // index 2, ETH/Optimism
multiTx1Tr1 transfer.TestTransfer // index 3, USDC/Mainnet
multiTx2Tr1 transfer.TestTransfer // index 4, USDC/Goerli
multiTx1Tr2 transfer.TestTransfer // index 5, USDC/Optimism
multiTx2Tr2 transfer.TestTransfer // index 6, SNT/Mainnet
multiTx2PendingTr transfer.TestTransfer // index 7, DAI/Mainnet
multiTx1 transfer.TestMultiTransaction
multiTx1ID transfer.MultiTransactionIDType
multiTx2 transfer.TestMultiTransaction
multiTx2ID transfer.MultiTransactionIDType
nextIndex int
}
func mockTestAccountsWithAddresses(tb testing.TB, db *accounts.Database, addresses []eth.Address) {
mockedAccounts := []*accounts.Account{}
for _, address := range addresses {
mockedAccounts = append(mockedAccounts, &accounts.Account{
Address: types.Address(address),
Type: accounts.AccountTypeWatch,
})
}
accounts.MockTestAccounts(tb, db, mockedAccounts)
}
// Generates and adds to the DB 7 transfers and 2 multitransactions.
// There are only 4 extractable activity entries (transactions + multi-transactions) with timestamps 1-4. The others are associated with a multi-transaction
func fillTestData(t *testing.T, db *sql.DB) (td testData, fromAddresses, toAddresses []eth.Address) {
// Generates ETH/Goerli, ETH/Optimism, USDC/Mainnet, USDC/Goerli, USDC/Optimism, SNT/Mainnet, DAI/Mainnet
trs, fromAddresses, toAddresses := transfer.GenerateTestTransfers(t, db, 1, 7)
// Plain transfer
td.tr1 = trs[0]
transfer.InsertTestTransfer(t, db, td.tr1.To, &td.tr1)
// Pending transfer
td.pendingTr = trs[1]
transfer.InsertTestPendingTransaction(t, db, &td.pendingTr)
// Send Multitransaction containing 2 x Plain transfers
td.multiTx1Tr1 = trs[2]
td.multiTx1Tr2 = trs[4]
td.multiTx1 = transfer.GenerateTestSendMultiTransaction(td.multiTx1Tr1)
td.multiTx1.ToToken = testutils.DaiSymbol
td.multiTx1ID = transfer.InsertTestMultiTransaction(t, db, &td.multiTx1)
td.multiTx1Tr1.MultiTransactionID = td.multiTx1ID
transfer.InsertTestTransfer(t, db, td.multiTx1Tr1.To, &td.multiTx1Tr1)
td.multiTx1Tr2.MultiTransactionID = td.multiTx1ID
transfer.InsertTestTransfer(t, db, td.multiTx1Tr2.To, &td.multiTx1Tr2)
// Send Multitransaction containing 2 x Plain transfers + 1 x Pending transfer
td.multiTx2Tr1 = trs[3]
td.multiTx2Tr2 = trs[5]
td.multiTx2PendingTr = trs[6]
td.multiTx2 = transfer.GenerateTestSendMultiTransaction(td.multiTx2Tr1)
td.multiTx2.ToToken = testutils.SntSymbol
td.multiTx2ID = transfer.InsertTestMultiTransaction(t, db, &td.multiTx2)
td.multiTx2Tr1.MultiTransactionID = td.multiTx2ID
transfer.InsertTestTransfer(t, db, td.multiTx2Tr1.To, &td.multiTx2Tr1)
td.multiTx2Tr2.MultiTransactionID = td.multiTx2ID
transfer.InsertTestTransfer(t, db, td.multiTx2Tr2.To, &td.multiTx2Tr2)
td.multiTx2PendingTr.MultiTransactionID = td.multiTx2ID
transfer.InsertTestPendingTransaction(t, db, &td.multiTx2PendingTr)
td.nextIndex = 8
return td, fromAddresses, toAddresses
}
func TTrToToken(t *testing.T, tt *transfer.TestTransaction) *Token {
token, isNative := transfer.TestTrToToken(t, tt)
tokenType := Erc20
if isNative {
tokenType = Native
}
return &Token{
TokenType: tokenType,
ChainID: common.ChainID(token.ChainID),
Address: token.Address,
}
}
func expectedTokenType(tokenAddress eth.Address) *TransferType {
transferType := new(TransferType)
if (tokenAddress != eth.Address{}) {
*transferType = TransferTypeErc20
} else {
*transferType = TransferTypeEth
}
return transferType
}
func TestGetActivityEntriesAll(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
td, fromAddresses, toAddresses := fillTestData(t, deps.db)
var filter Filter
entries, err := getActivityEntries(context.Background(), deps, append(toAddresses, fromAddresses...), []common.ChainID{}, filter, 0, 10)
require.NoError(t, err)
require.Equal(t, 4, len(entries))
// Ensure we have the correct order
var curTimestamp int64 = 4
for _, entry := range entries {
require.Equal(t, curTimestamp, entry.timestamp, "entries are sorted by timestamp; expected %d, got %d", curTimestamp, entry.timestamp)
curTimestamp--
}
require.Equal(t, Entry{
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: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(td.tr1.Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &td.tr1.TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("ETH"),
sender: &td.tr1.From,
recipient: &td.tr1.To,
chainIDOut: nil,
chainIDIn: &td.tr1.ChainID,
transferType: expectedTokenType(td.tr1.Token.Address),
}, entries[3])
require.Equal(t, Entry{
payloadType: PendingTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: td.pendingTr.ChainID, Hash: td.pendingTr.Hash},
id: td.pendingTr.MultiTransactionID,
timestamp: td.pendingTr.Timestamp,
activityType: SendAT,
activityStatus: PendingAS,
amountOut: (*hexutil.Big)(big.NewInt(td.pendingTr.Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
tokenOut: TTrToToken(t, &td.pendingTr.TestTransaction),
tokenIn: nil,
symbolOut: common.NewAndSet("ETH"),
symbolIn: nil,
sender: &td.pendingTr.From,
recipient: &td.pendingTr.To,
chainIDOut: &td.pendingTr.ChainID,
chainIDIn: nil,
transferType: expectedTokenType(eth.Address{}),
}, entries[2])
require.Equal(t, Entry{
payloadType: MultiTransactionPT,
transaction: nil,
id: td.multiTx1ID,
timestamp: td.multiTx1.Timestamp,
activityType: SendAT,
activityStatus: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(td.multiTx1.FromAmount)),
amountIn: (*hexutil.Big)(big.NewInt(td.multiTx1.ToAmount)),
tokenOut: tokenFromSymbol(nil, td.multiTx1.FromToken),
tokenIn: tokenFromSymbol(nil, td.multiTx1.ToToken),
symbolOut: common.NewAndSet("USDC"),
symbolIn: common.NewAndSet("DAI"),
sender: &td.multiTx1.FromAddress,
recipient: &td.multiTx1.ToAddress,
}, entries[1])
require.Equal(t, Entry{
payloadType: MultiTransactionPT,
transaction: nil,
id: td.multiTx2ID,
timestamp: td.multiTx2.Timestamp,
activityType: SendAT,
activityStatus: PendingAS,
amountOut: (*hexutil.Big)(big.NewInt(td.multiTx2.FromAmount)),
amountIn: (*hexutil.Big)(big.NewInt(td.multiTx2.ToAmount)),
symbolOut: common.NewAndSet("USDC"),
symbolIn: common.NewAndSet("SNT"),
tokenOut: tokenFromSymbol(nil, td.multiTx2.FromToken),
tokenIn: tokenFromSymbol(nil, td.multiTx2.ToToken),
sender: &td.multiTx2.FromAddress,
recipient: &td.multiTx2.ToAddress,
}, entries[0])
}
// TestGetActivityEntriesWithSenderFilter covers the corner-case of having both sender and receiver in the filter.
// In this specific case we expect that there will be two transactions (one probably backed by a multi-transaction)
// In case of both sender and receiver are included we validate we receive both entries otherwise only the "owned"
// transactions should be retrieved by the filter
func TestGetActivityEntriesWithSameTransactionForSenderAndReceiverInDB(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
// Add 4 extractable transactions with timestamps 1-4
td, fromAddresses, toAddresses := fillTestData(t, deps.db)
mockTestAccountsWithAddresses(t, deps.accountsDb, append(fromAddresses, toAddresses...))
// Add another transaction with owner reversed
senderTr := td.tr1
// Ensure we have a consistent order
senderTr.Timestamp++
// add sender as owner, fillTestData adds receiver as owner
transfer.InsertTestTransfer(t, deps.db, senderTr.From, &senderTr)
var filter Filter
entries, err := getActivityEntries(context.Background(), deps, []eth.Address{td.tr1.To, senderTr.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, SendAT, entries[0].activityType)
require.Equal(t, senderTr.From, entries[0].transaction.Address)
require.Equal(t, senderTr.From, *entries[0].sender)
require.Equal(t, senderTr.To, *entries[0].recipient)
require.Equal(t, ReceiveAT, entries[1].activityType)
require.Equal(t, td.tr1.To, *entries[1].recipient)
require.Equal(t, td.tr1.From, *entries[1].sender)
require.Equal(t, td.tr1.To, *entries[1].recipient)
}
func TestGetActivityEntriesFilterByTime(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
td, fromTds, toTds := fillTestData(t, deps.db)
// Add 6 extractable transactions with timestamps 6-12
trs, fromTrs, toTrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, 6)
for i := range trs {
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
}
mockTestAccountsWithAddresses(t, deps.accountsDb, append(append(append(fromTds, toTds...), fromTrs...), toTrs...))
// Test start only
var filter Filter
filter.Period.StartTimestamp = td.multiTx1.Timestamp
filter.Period.EndTimestamp = NoLimitTimestampForPeriod
entries, err := getActivityEntries(context.Background(), deps, []eth.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{
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: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[5].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[5].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("USDC"),
sender: &trs[5].From,
recipient: &trs[5].To,
chainIDOut: nil,
chainIDIn: &trs[5].ChainID,
transferType: expectedTokenType(trs[5].Token.Address),
}, entries[0])
require.Equal(t, Entry{
payloadType: MultiTransactionPT,
transaction: nil,
id: td.multiTx1ID,
timestamp: td.multiTx1.Timestamp,
activityType: SendAT,
activityStatus: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(td.multiTx1.FromAmount)),
amountIn: (*hexutil.Big)(big.NewInt(td.multiTx1.ToAmount)),
tokenOut: tokenFromSymbol(nil, td.multiTx1.FromToken),
tokenIn: tokenFromSymbol(nil, td.multiTx1.ToToken),
symbolOut: common.NewAndSet("USDC"),
symbolIn: common.NewAndSet("DAI"),
sender: &td.multiTx1.FromAddress,
recipient: &td.multiTx1.ToAddress,
chainIDOut: nil,
chainIDIn: nil,
transferType: nil,
}, entries[7])
// Test complete interval
filter.Period.EndTimestamp = trs[2].Timestamp
entries, err = getActivityEntries(context.Background(), deps, []eth.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{
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: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[2].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[2].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("ETH"),
sender: &trs[2].From,
recipient: &trs[2].To,
chainIDOut: nil,
chainIDIn: &trs[2].ChainID,
transferType: expectedTokenType(trs[2].Token.Address),
}, entries[0])
require.Equal(t, Entry{
payloadType: MultiTransactionPT,
transaction: nil,
id: td.multiTx1ID,
timestamp: td.multiTx1.Timestamp,
activityType: SendAT,
activityStatus: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(td.multiTx1.FromAmount)),
amountIn: (*hexutil.Big)(big.NewInt(td.multiTx1.ToAmount)),
tokenOut: tokenFromSymbol(nil, td.multiTx1.FromToken),
tokenIn: tokenFromSymbol(nil, td.multiTx1.ToToken),
symbolOut: common.NewAndSet("USDC"),
symbolIn: common.NewAndSet("DAI"),
sender: &td.multiTx1.FromAddress,
recipient: &td.multiTx1.ToAddress,
chainIDOut: nil,
chainIDIn: nil,
transferType: nil,
}, entries[4])
// Test end only
filter.Period.StartTimestamp = NoLimitTimestampForPeriod
entries, err = getActivityEntries(context.Background(), deps, []eth.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{
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: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[2].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[2].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("ETH"),
sender: &trs[2].From,
recipient: &trs[2].To,
chainIDOut: nil,
chainIDIn: &trs[2].ChainID,
transferType: expectedTokenType(trs[2].Token.Address),
}, entries[0])
require.Equal(t, Entry{
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: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(td.tr1.Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &td.tr1.TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("ETH"),
sender: &td.tr1.From,
recipient: &td.tr1.To,
chainIDOut: nil,
chainIDIn: &td.tr1.ChainID,
transferType: expectedTokenType(td.tr1.Token.Address),
}, entries[6])
}
func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
// Add 10 extractable transactions with timestamps 1-10
trs, fromTrs, toTrs := transfer.GenerateTestTransfers(t, deps.db, 1, 10)
for i := range trs {
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
}
mockTestAccountsWithAddresses(t, deps.accountsDb, append(fromTrs, toTrs...))
var filter Filter
// Get all
entries, err := getActivityEntries(context.Background(), deps, []eth.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(context.Background(), deps, []eth.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{
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: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[8].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[8].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("ETH"),
sender: &trs[8].From,
recipient: &trs[8].To,
chainIDOut: nil,
chainIDIn: &trs[8].ChainID,
transferType: expectedTokenType(trs[8].Token.Address),
}, entries[0])
require.Equal(t, Entry{
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: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[6].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[6].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("DAI"),
sender: &trs[6].From,
recipient: &trs[6].To,
chainIDOut: nil,
chainIDIn: &trs[6].ChainID,
transferType: expectedTokenType(trs[6].Token.Address),
}, entries[2])
// Move window 2 entries forward
entries, err = getActivityEntries(context.Background(), deps, []eth.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{
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: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[6].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[6].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("DAI"),
sender: &trs[6].From,
recipient: &trs[6].To,
chainIDOut: nil,
chainIDIn: &trs[6].ChainID,
transferType: expectedTokenType(trs[6].Token.Address),
}, entries[0])
require.Equal(t, Entry{
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: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[4].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[4].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("USDC"),
sender: &trs[4].From,
recipient: &trs[4].To,
chainIDOut: nil,
chainIDIn: &trs[4].ChainID,
transferType: expectedTokenType(trs[4].Token.Address),
}, entries[2])
// Move window 4 more entries to test filter cap
entries, err = getActivityEntries(context.Background(), deps, []eth.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{
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: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[2].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[2].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("USDC"),
sender: &trs[2].From,
recipient: &trs[2].To,
chainIDOut: nil,
chainIDIn: &trs[2].ChainID,
transferType: expectedTokenType(trs[2].Token.Address),
}, entries[0])
}
func countTypes(entries []Entry) (sendCount, receiveCount, contractCount, mintCount, swapCount, buyCount, bridgeCount int) {
for _, entry := range entries {
switch entry.activityType {
case SendAT:
sendCount++
case ReceiveAT:
receiveCount++
case SwapAT:
swapCount++
case BuyAT:
buyCount++
case BridgeAT:
bridgeCount++
case ContractDeploymentAT:
contractCount++
case MintAT:
mintCount++
}
}
return
}
func TestGetActivityEntriesFilterByType(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
// Adds 4 extractable transactions
td, _, _ := fillTestData(t, deps.db)
// Add 5 extractable transactions: one MultiTransactionSwap, two MultiTransactionBridge and two MultiTransactionSend
multiTxs := make([]transfer.TestMultiTransaction, 5)
trs, _, _ := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, len(multiTxs)*2)
multiTxs[0] = transfer.GenerateTestBridgeMultiTransaction(trs[0], trs[1])
multiTxs[1] = transfer.GenerateTestSwapMultiTransaction(trs[2], testutils.SntSymbol, 100) // trs[3]
multiTxs[2] = transfer.GenerateTestSendMultiTransaction(trs[4]) // trs[5]
multiTxs[3] = transfer.GenerateTestBridgeMultiTransaction(trs[6], trs[7])
multiTxs[4] = transfer.GenerateTestSendMultiTransaction(trs[8]) // trs[9]
var lastMT transfer.MultiTransactionIDType
for i := range trs {
if i%2 == 0 {
lastMT = transfer.InsertTestMultiTransaction(t, deps.db, &multiTxs[i/2])
}
trs[i].MultiTransactionID = lastMT
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, 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, 11, len(entries))
filter.Types = []Type{SendAT, SwapAT}
entries, err = getActivityEntries(context.Background(), deps, addresses, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
// 3 from td Send + 2 trs MT Send + 1 (swap)
require.Equal(t, 6, len(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)
filter.Types = []Type{BridgeAT, ReceiveAT}
entries, err = getActivityEntries(context.Background(), deps, addresses, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 3, len(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) {
deps, close := setupTestActivityDB(t)
defer close()
// Adds 4 extractable transactions
td, fromTds, toTds := fillTestData(t, deps.db)
trs, fromTrs, toTrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, 6)
for i := range trs {
transfer.InsertTestTransfer(t, deps.db, trs[i].From, &trs[i])
}
mockTestAccountsWithAddresses(t, deps.accountsDb, append(append(append(fromTds, toTds...), fromTrs...), toTrs...))
var filter Filter
addressesFilter := allAddressesFilter()
entries, err := getActivityEntries(context.Background(), deps, addressesFilter, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 10, len(entries))
addressesFilter = []eth.Address{td.multiTx1.ToAddress, td.multiTx2.FromAddress, trs[1].From, trs[4].From, trs[3].To}
// The td.multiTx1.ToAddress and trs[3].To are missing not having them as owner address
entries, err = getActivityEntries(context.Background(), deps, addressesFilter, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 3, len(entries))
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[4].ChainID, Hash: trs[4].Hash, Address: trs[4].From},
id: 0,
timestamp: trs[4].Timestamp,
activityType: SendAT,
activityStatus: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(trs[4].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
tokenOut: TTrToToken(t, &trs[4].TestTransaction),
tokenIn: nil,
symbolOut: common.NewAndSet("USDC"),
symbolIn: nil,
sender: &trs[4].From,
recipient: &trs[4].To,
chainIDOut: &trs[4].ChainID,
chainIDIn: nil,
transferType: expectedTokenType(trs[4].Token.Address),
}, entries[0])
require.Equal(t, Entry{
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: CompleteAS,
amountOut: (*hexutil.Big)(big.NewInt(trs[1].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
tokenOut: TTrToToken(t, &trs[1].TestTransaction),
tokenIn: nil,
symbolOut: common.NewAndSet("ETH"),
symbolIn: nil,
sender: &trs[1].From,
recipient: &trs[1].To,
chainIDOut: &trs[1].ChainID,
chainIDIn: nil,
transferType: expectedTokenType(trs[1].Token.Address),
}, entries[1])
require.Equal(t, Entry{
payloadType: MultiTransactionPT,
transaction: nil,
id: td.multiTx2ID,
timestamp: td.multiTx2.Timestamp,
activityType: SendAT,
activityStatus: PendingAS,
amountOut: (*hexutil.Big)(big.NewInt(td.multiTx2.FromAmount)),
amountIn: (*hexutil.Big)(big.NewInt(td.multiTx2.ToAmount)),
tokenOut: tokenFromSymbol(nil, td.multiTx2.FromToken),
tokenIn: tokenFromSymbol(nil, td.multiTx2.ToToken),
symbolOut: common.NewAndSet("USDC"),
symbolIn: common.NewAndSet("SNT"),
sender: &td.multiTx2.FromAddress,
recipient: &td.multiTx2.ToAddress,
chainIDOut: nil,
chainIDIn: nil,
}, entries[2])
}
func TestGetActivityEntriesFilterByStatus(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
// Adds 4 extractable transactions: 1 T, 1 T pending, 1 MT pending, 1 MT with 2xT success
td, fromTds, toTds := fillTestData(t, deps.db)
// Add 7 extractable transactions: 1 pending, 1 Tr failed, 1 MT failed, 4 success
trs, fromTrs, toTrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, 7)
multiTx := transfer.GenerateTestSendMultiTransaction(trs[6])
failedMTID := transfer.InsertTestMultiTransaction(t, deps.db, &multiTx)
trs[6].MultiTransactionID = failedMTID
for i := range trs {
if i == 1 {
transfer.InsertTestPendingTransaction(t, deps.db, &trs[i])
} else {
trs[i].Success = i != 3 && i != 6
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
}
}
mockTestAccountsWithAddresses(t, deps.accountsDb, append(append(append(fromTds, toTds...), fromTrs...), toTrs...))
var filter Filter
filter.Statuses = allActivityStatusesFilter()
entries, err := getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 11, len(entries))
filter.Statuses = []Status{PendingAS}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 3, len(entries))
require.Equal(t, td.pendingTr.Hash, entries[2].transaction.Hash)
require.Equal(t, td.multiTx2ID, entries[1].id)
require.Equal(t, trs[1].Hash, entries[0].transaction.Hash)
filter.Statuses = []Status{FailedAS}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 2, len(entries))
filter.Statuses = []Status{CompleteAS}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 6, len(entries))
// Finalized is treated as Complete, would need dynamic blockchain status to track the Finalized level
filter.Statuses = []Status{FinalizedAS}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 6, len(entries))
// Combined filter
filter.Statuses = []Status{FailedAS, PendingAS}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 5, len(entries))
}
func TestGetActivityEntriesFilterByTokenType(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 9 transactions DAI/Goerli, ETH/Mainnet, ETH/Goerli, ETH/Optimism, USDC/Mainnet, USDC/Goerli, USDC/Optimism, SNT/Mainnet, DAI/Mainnet
trs, fromTrs, toTrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, 9)
for i := range trs {
tokenAddr := transfer.TestTokens[i].Address
trs[i].ChainID = common.ChainID(transfer.TestTokens[i].ChainID)
transfer.InsertTestTransferWithOptions(t, deps.db, trs[i].To, &trs[i], &transfer.TestTransferOptions{
TokenAddress: tokenAddr,
})
}
mockTestAccountsWithAddresses(t, deps.accountsDb, append(append(append(fromTds, toTds...), fromTrs...), toTrs...))
var filter Filter
filter.FilterOutAssets = true
entries, err := getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 0, len(entries))
filter.FilterOutAssets = false
filter.Assets = allTokensFilter()
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 13, len(entries))
// Native tokens are network agnostic, hence all are returned
filter.Assets = []Token{{TokenType: Native, ChainID: common.ChainID(transfer.EthMainnet.ChainID)}}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 5, len(entries))
// Test that it doesn't break the filter
filter.Assets = []Token{{TokenType: Erc1155}}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 0, len(entries))
filter.Assets = []Token{{
TokenType: Erc20,
ChainID: common.ChainID(transfer.UsdcMainnet.ChainID),
Address: transfer.UsdcMainnet.Address,
}}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
// Two MT for which ChainID is ignored and one transfer on the main net and the Goerli is ignored
require.Equal(t, 3, len(entries))
require.Equal(t, Erc20, entries[0].tokenIn.TokenType)
require.Equal(t, transfer.UsdcMainnet.Address, entries[0].tokenIn.Address)
require.Nil(t, entries[0].tokenOut)
// MT has only symbol, the first token is lookup by symbol for both entries
require.Equal(t, Erc20, entries[1].tokenOut.TokenType)
require.Equal(t, transfer.UsdcMainnet.Address, entries[1].tokenOut.Address)
require.Equal(t, Erc20, entries[1].tokenIn.TokenType)
require.Equal(t, transfer.SntMainnet.Address, entries[1].tokenIn.Address)
require.Equal(t, Erc20, entries[2].tokenOut.TokenType)
require.Equal(t, transfer.UsdcMainnet.Address, entries[1].tokenOut.Address)
require.Equal(t, Erc20, entries[2].tokenIn.TokenType)
require.Equal(t, transfer.UsdcMainnet.Address, entries[1].tokenOut.Address)
filter.Assets = []Token{{
TokenType: Erc20,
ChainID: common.ChainID(transfer.UsdcMainnet.ChainID),
Address: transfer.UsdcMainnet.Address,
}, {
TokenType: Erc20,
ChainID: common.ChainID(transfer.UsdcGoerli.ChainID),
Address: transfer.UsdcGoerli.Address,
}}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
// Two MT for which ChainID is ignored and two transfers on the main net and Goerli
require.Equal(t, 4, len(entries))
require.Equal(t, Erc20, entries[0].tokenIn.TokenType)
require.Equal(t, transfer.UsdcGoerli.Address, entries[0].tokenIn.Address)
require.Nil(t, entries[0].tokenOut)
}
func TestGetActivityEntriesFilterByToAddresses(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
// Adds 4 extractable transactions
td, fromTds, toTds := fillTestData(t, deps.db)
// Add 6 extractable transactions
trs, fromTrs, toTrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, 6)
for i := range trs {
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
}
mockTestAccountsWithAddresses(t, deps.accountsDb, append(append(append(fromTds, toTds...), fromTrs...), toTrs...))
var filter Filter
filter.CounterpartyAddresses = allAddressesFilter()
entries, err := getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 10, len(entries))
filter.CounterpartyAddresses = []eth.Address{eth.HexToAddress("0x567890")}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 0, len(entries))
filter.CounterpartyAddresses = []eth.Address{td.pendingTr.To, td.multiTx2.ToAddress, trs[3].To}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 3, len(entries))
filter.CounterpartyAddresses = []eth.Address{td.tr1.To, td.pendingTr.From, trs[3].From, trs[5].To}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 2, len(entries))
}
func TestGetActivityEntriesFilterByNetworks(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
// Adds 4 extractable transactions
td, fromTds, toTds := fillTestData(t, deps.db)
chainToEntryCount := make(map[common.ChainID]map[int]int)
recordPresence := func(chainID common.ChainID, entry int) {
if _, ok := chainToEntryCount[chainID]; !ok {
chainToEntryCount[chainID] = make(map[int]int)
chainToEntryCount[chainID][entry] = 1
} else {
if _, ok := chainToEntryCount[chainID][entry]; !ok {
chainToEntryCount[chainID][entry] = 1
} else {
chainToEntryCount[chainID][entry]++
}
}
}
recordPresence(td.tr1.ChainID, 0)
recordPresence(td.pendingTr.ChainID, 1)
recordPresence(td.multiTx1Tr1.ChainID, 2)
if td.multiTx1Tr2.ChainID != td.multiTx1Tr1.ChainID {
recordPresence(td.multiTx1Tr2.ChainID, 2)
}
recordPresence(td.multiTx2Tr1.ChainID, 3)
if td.multiTx2Tr2.ChainID != td.multiTx2Tr1.ChainID {
recordPresence(td.multiTx2Tr2.ChainID, 3)
}
if td.multiTx2PendingTr.ChainID != td.multiTx2Tr1.ChainID && td.multiTx2PendingTr.ChainID != td.multiTx2Tr2.ChainID {
recordPresence(td.multiTx2PendingTr.ChainID, 3)
}
// Add 6 extractable transactions
trs, fromTrs, toTrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, 6)
for i := range trs {
recordPresence(trs[i].ChainID, 4+i)
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
}
mockTestAccountsWithAddresses(t, deps.accountsDb, append(append(append(fromTds, toTds...), fromTrs...), toTrs...))
var filter Filter
chainIDs := allNetworksFilter()
entries, err := getActivityEntries(context.Background(), deps, []eth.Address{}, chainIDs, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 10, len(entries))
chainIDs = []common.ChainID{5674839210}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, chainIDs, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 0, len(entries))
chainIDs = []common.ChainID{td.pendingTr.ChainID, td.multiTx2Tr1.ChainID, trs[3].ChainID}
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{}, chainIDs, filter, 0, 15)
require.NoError(t, err)
expectedResults := make(map[int]int)
for _, chainID := range chainIDs {
for entry := range chainToEntryCount[chainID] {
if _, ok := expectedResults[entry]; !ok {
expectedResults[entry]++
}
}
}
require.Equal(t, len(expectedResults), len(entries))
}
func TestGetActivityEntriesFilterByNetworksOfSubTransactions(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
// Add 6 extractable transactions
trs, _, toTrs := transfer.GenerateTestTransfers(t, deps.db, 0, 5)
trs[0].ChainID = 1231
trs[1].ChainID = 1232
trs[2].ChainID = 1233
mt1 := transfer.GenerateTestBridgeMultiTransaction(trs[0], trs[1])
trs[0].MultiTransactionID = transfer.InsertTestMultiTransaction(t, deps.db, &mt1)
trs[1].MultiTransactionID = mt1.MultiTransactionID
trs[2].MultiTransactionID = mt1.MultiTransactionID
trs[3].ChainID = 1234
mt2 := transfer.GenerateTestSwapMultiTransaction(trs[3], testutils.SntSymbol, 100)
// insertMultiTransaction will insert 0 instead of NULL
mt2.FromNetworkID = common.NewAndSet(uint64(0))
mt2.ToNetworkID = common.NewAndSet(uint64(0))
trs[3].MultiTransactionID = transfer.InsertTestMultiTransaction(t, deps.db, &mt2)
for i := range trs {
if i == 2 {
transfer.InsertTestPendingTransaction(t, deps.db, &trs[i])
} else {
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
}
}
var filter Filter
chainIDs := allNetworksFilter()
entries, err := getActivityEntries(context.Background(), deps, toTrs, chainIDs, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 3, len(entries))
chainIDs = []common.ChainID{trs[0].ChainID, trs[1].ChainID}
entries, err = getActivityEntries(context.Background(), deps, toTrs, chainIDs, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 1, len(entries))
require.Equal(t, entries[0].id, mt1.MultiTransactionID)
// Filter by pending_transactions sub-transacitons
chainIDs = []common.ChainID{trs[2].ChainID}
entries, err = getActivityEntries(context.Background(), deps, toTrs, chainIDs, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 1, len(entries))
require.Equal(t, entries[0].id, mt1.MultiTransactionID)
chainIDs = []common.ChainID{trs[3].ChainID}
entries, err = getActivityEntries(context.Background(), deps, toTrs, chainIDs, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 1, len(entries))
require.Equal(t, entries[0].id, mt2.MultiTransactionID)
}
func TestGetActivityEntriesCheckToAndFrom(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
// Adds 6 transactions from which 4 are filtered out
td, _, _ := fillTestData(t, deps.db)
// Add extra transactions to test To address
trs, _, _ := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, 2)
transfer.InsertTestTransfer(t, deps.db, trs[0].To, &trs[0])
transfer.InsertTestPendingTransaction(t, deps.db, &trs[1])
addresses := []eth.Address{td.tr1.To, td.pendingTr.To,
td.multiTx1.FromAddress, td.multiTx2.FromAddress, trs[0].To, trs[1].To}
var filter Filter
entries, err := getActivityEntries(context.Background(), deps, addresses, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 6, len(entries))
require.Equal(t, ReceiveAT, entries[5].activityType) // td.tr1
require.NotEqual(t, eth.Address{}, entries[5].transaction.Address) // td.tr1
require.Equal(t, td.tr1.To, *entries[5].recipient) // td.tr1
require.Equal(t, ReceiveAT, entries[4].activityType) // td.pendingTr
// Multi-transactions are always considered as SendAT
require.Equal(t, SendAT, entries[3].activityType) // td.multiTx1
require.Equal(t, SendAT, entries[2].activityType) // td.multiTx2
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)
}
func TestGetActivityEntriesCheckContextCancellation(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
_, _, _ = fillTestData(t, deps.db)
cancellableCtx, cancelFn := context.WithCancel(context.Background())
cancelFn()
activities, err := getActivityEntries(cancellableCtx, deps, []eth.Address{}, []common.ChainID{}, Filter{}, 0, 10)
require.ErrorIs(t, err, context.Canceled)
require.Equal(t, 0, len(activities))
}
func TestGetActivityEntriesNullAddresses(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
trs, _, _ := transfer.GenerateTestTransfers(t, deps.db, 0, 4)
multiTx := transfer.GenerateTestBridgeMultiTransaction(trs[0], trs[1])
multiTx.ToAddress = eth.Address{}
trs[0].MultiTransactionID = transfer.InsertTestMultiTransaction(t, deps.db, &multiTx)
trs[1].MultiTransactionID = trs[0].MultiTransactionID
for i := 0; i < 3; i++ {
transfer.InsertTestTransferWithOptions(t, deps.db, trs[i].To, &trs[i], &transfer.TestTransferOptions{
NullifyAddresses: []eth.Address{trs[i].To},
})
}
trs[3].To = eth.Address{}
transfer.InsertTestPendingTransaction(t, deps.db, &trs[3])
mockTestAccountsWithAddresses(t, deps.accountsDb, []eth.Address{trs[0].From, trs[1].From, trs[2].From, trs[3].From})
activities, err := getActivityEntries(context.Background(), deps, allAddressesFilter(), allNetworksFilter(), Filter{}, 0, 10)
require.NoError(t, err)
require.Equal(t, 3, len(activities))
}
func TestGetTxDetails(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, _, _ := fillTestData(t, deps.db)
_, err := getTxDetails(context.Background(), deps.db, "")
require.EqualError(t, err, "invalid tx id")
details, err := getTxDetails(context.Background(), deps.db, td.tr1.Hash.String())
require.NoError(t, err)
require.Equal(t, td.tr1.Hash.String(), details.ID)
require.Equal(t, 0, details.MultiTxID)
require.Equal(t, td.tr1.Nonce, details.Nonce)
require.Equal(t, td.tr1.BlkNumber, details.BlockNumber)
require.Equal(t, td.tr1.Contract, *details.Contract)
}
func TestGetMultiTxDetails(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, _, _ := fillTestData(t, deps.db)
_, err := getMultiTxDetails(context.Background(), deps.db, 0)
require.EqualError(t, err, "invalid tx id")
details, err := getMultiTxDetails(context.Background(), deps.db, int(td.multiTx1.MultiTransactionID))
require.NoError(t, err)
require.Equal(t, "", details.ID)
require.Equal(t, int(td.multiTx1.MultiTransactionID), details.MultiTxID)
require.Equal(t, td.multiTx1Tr2.Nonce, details.Nonce)
require.Equal(t, td.multiTx1Tr2.BlkNumber, details.BlockNumber)
require.Equal(t, td.multiTx1Tr1.Contract, *details.Contract)
}
func setupBenchmark(b *testing.B, inMemory bool, resultCount int) (deps FilterDependencies, close func(), accounts []eth.Address) {
deps, close = setupTestActivityDBStorageChoice(b, inMemory)
const transactionCount = 100000
const mtSendRatio = 0.2 // 20%
const mtSwapRatio = 0.1 // 10%
const mtBridgeRatio = 0.1 // 10%
const pendingCount = 10
const mtSendCount = int(float64(transactionCount) * mtSendRatio)
const mtSwapCount = int(float64(transactionCount) * mtSwapRatio)
// Bridge requires two transactions
const mtBridgeCount = int(float64(transactionCount) * (mtBridgeRatio / 2))
trs, _, _ := transfer.GenerateTestTransfers(b, deps.db, 0, transactionCount)
accounts = []eth.Address{trs[0].From, trs[1].From, trs[2].From, trs[3].To, trs[4].To, trs[5].To}
i := 0
multiTxs := make([]transfer.TestMultiTransaction, mtSendCount+mtSwapCount+mtBridgeCount)
for ; i < mtSendCount; i++ {
multiTxs[i] = transfer.GenerateTestSendMultiTransaction(trs[i])
trs[i].From = accounts[i%len(accounts)]
multiTxs[i].FromAddress = trs[i].From
multiTxs[i].MultiTransactionID = transfer.InsertTestMultiTransaction(b, deps.db, &multiTxs[i])
trs[i].MultiTransactionID = multiTxs[i].MultiTransactionID
}
for j := 0; j < mtSwapCount; i, j = i+1, j+1 {
multiTxs[i] = transfer.GenerateTestSwapMultiTransaction(trs[i], testutils.SntSymbol, int64(i))
trs[i].From = accounts[i%len(accounts)]
multiTxs[i].FromAddress = trs[i].From
multiTxs[i].MultiTransactionID = transfer.InsertTestMultiTransaction(b, deps.db, &multiTxs[i])
trs[i].MultiTransactionID = multiTxs[i].MultiTransactionID
}
for mtIdx := 0; mtIdx < mtBridgeCount; i, mtIdx = i+2, mtIdx+1 {
firstTrIdx := i
secondTrIdx := i + 1
multiTxs[mtIdx] = transfer.GenerateTestBridgeMultiTransaction(trs[firstTrIdx], trs[secondTrIdx])
trs[firstTrIdx].From = accounts[i%len(accounts)]
trs[secondTrIdx].To = accounts[(i+3)%len(accounts)]
multiTxs[mtIdx].FromAddress = trs[firstTrIdx].From
multiTxs[mtIdx].ToAddress = trs[secondTrIdx].To
multiTxs[mtIdx].FromAddress = trs[i].From
multiTxs[mtIdx].MultiTransactionID = transfer.InsertTestMultiTransaction(b, deps.db, &multiTxs[mtIdx])
trs[firstTrIdx].MultiTransactionID = multiTxs[mtIdx].MultiTransactionID
trs[secondTrIdx].MultiTransactionID = multiTxs[mtIdx].MultiTransactionID
}
for i = 0; i < transactionCount-pendingCount; i++ {
trs[i].From = accounts[i%len(accounts)]
transfer.InsertTestTransfer(b, deps.db, trs[i].To, &trs[i])
}
for ; i < transactionCount; i++ {
trs[i].From = accounts[i%len(accounts)]
transfer.InsertTestPendingTransaction(b, deps.db, &trs[i])
}
mockTestAccountsWithAddresses(b, deps.accountsDb, accounts)
return
}
func BenchmarkGetActivityEntries(bArg *testing.B) {
type params struct {
inMemory bool
resultCount int
generateTestParameters func([]eth.Address) (addresses []eth.Address, filter *Filter, startIndex int)
}
testCases := []struct {
name string
params params
}{
{
"RAM_NoFilter",
params{
true,
10,
func([]eth.Address) ([]eth.Address, *Filter, int) {
return allAddressesFilter(), &Filter{}, 0
},
},
},
{
"SSD_NoFilter",
params{
false,
10,
func([]eth.Address) ([]eth.Address, *Filter, int) {
return allAddressesFilter(), &Filter{}, 0
},
},
},
{
"SSD_MovingWindow",
params{
false,
10,
func(addresses []eth.Address) ([]eth.Address, *Filter, int) {
return allAddressesFilter(), &Filter{}, 200
},
},
},
{
"SSD_AllAddresses",
params{
false,
10,
func(addresses []eth.Address) ([]eth.Address, *Filter, int) {
return addresses, &Filter{}, 0
},
},
},
{
"SSD_AllAddresses_AllTos",
params{
false,
10,
func(addresses []eth.Address) ([]eth.Address, *Filter, int) {
return addresses, &Filter{CounterpartyAddresses: addresses[3:]}, 0
},
},
},
{
"SSD_OneAddress",
params{
false,
10,
func(addresses []eth.Address) ([]eth.Address, *Filter, int) {
return addresses[0:1], &Filter{}, 0
},
},
},
}
deps, closeFn, accounts := setupBenchmark(bArg, true, 10)
defer closeFn()
const resultCount = 10
for _, tc := range testCases {
addresses, filter, startIndex := tc.params.generateTestParameters(accounts)
bArg.Run(tc.name, func(b *testing.B) {
// Reset timer after setup
b.ResetTimer()
// Run benchmark
for i := 0; i < b.N; i++ {
res, err := getActivityEntries(context.Background(), deps, addresses, allNetworksFilter(), *filter, startIndex, resultCount)
if err != nil || len(res) != resultCount {
b.Error(err)
}
}
})
}
}
func TestUpdateWalletKeypairsAccountsTable(t *testing.T) {
// initialize
appDb, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
require.NoError(t, err)
walletDb, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err)
accountsAppDb, err := accounts.NewDB(appDb)
require.NoError(t, err)
accountsWalletDb, err := accounts.NewDB(walletDb)
require.NoError(t, err)
// Check initially empty
addressesApp, err := accountsAppDb.GetWalletAddresses()
require.NoError(t, err)
require.Empty(t, addressesApp)
addressesWallet, err := accountsWalletDb.GetWalletAddresses()
require.Error(t, err) // no such table error
require.Empty(t, addressesWallet)
// Insert 2 addresses in app db, but only 1 is a wallet
addresses := []types.Address{{0x01}, {0x02}, {0x03}}
accounts := []*accounts.Account{
{Address: addresses[0], Chat: true, Wallet: true},
{Address: addresses[1], Wallet: true},
{Address: addresses[2]},
}
err = accountsAppDb.SaveOrUpdateAccounts(accounts, false)
require.NoError(t, err)
// Check only 2 wallet accs is returned in app db
addressesApp, err = accountsAppDb.GetWalletAddresses()
require.NoError(t, err)
require.Len(t, addressesApp, 2)
// update wallet DB
err = updateKeypairsAccountsTable(accountsAppDb, walletDb)
require.NoError(t, err)
// Check only 2 wallet acc is returned in wallet db
var count int
err = walletDb.QueryRow(fmt.Sprintf("SELECT count(address) FROM %s", keypairAccountsTable)).Scan(&count)
require.NoError(t, err)
require.Equal(t, count, 2)
// Compare addresses between app and wallet db
rows, err := walletDb.Query(fmt.Sprintf("SELECT address FROM %s", keypairAccountsTable))
require.NoError(t, err)
for rows.Next() {
var address types.Address
err = rows.Scan(&address)
require.NoError(t, err)
require.Contains(t, addresses, address)
}
defer rows.Close()
}