status-go/services/wallet/activity/activity_test.go

1519 lines
58 KiB
Go
Raw Normal View History

package activity
import (
"context"
"database/sql"
"encoding/hex"
"math/big"
"testing"
"time"
"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/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
)
var mockupTime = time.Unix(946724400, 0) // 2000-01-01 12:00:00
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
}
2023-09-21 06:58:36 +00:00
func tokenFromCollectible(c *transfer.TestCollectible) Token {
return Token{
TokenType: Erc721,
ChainID: c.ChainID,
Address: c.TokenAddress,
TokenID: (*hexutil.Big)(c.TokenID),
}
}
func setupTestActivityDBStorageChoice(tb testing.TB, inMemory bool) (deps FilterDependencies, close func()) {
var db *sql.DB
var err error
cleanupDB := func() error { return nil }
cleanupWalletDB := func() error { return nil }
if inMemory {
db, err = helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(tb, err)
} else {
db, cleanupWalletDB, err = helpers.SetupTestSQLDB(walletdatabase.DbInitializer{}, "wallet-activity-tests")
require.NoError(tb, err)
}
deps = FilterDependencies{
db: db,
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,
currentTimestamp: func() int64 {
return mockupTime.Unix()
},
}
return deps, func() {
require.NoError(tb, cleanupDB())
require.NoError(tb, cleanupWalletDB())
}
}
func setupTestActivityDB(tb testing.TB) (deps FilterDependencies, close func()) {
transfer.SetMultiTransactionIDGenerator(transfer.StaticIDCounter()) // to have different multi-transaction IDs even with fast execution
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
multiTx3Tr1 transfer.TestTransfer // index 8, DAI/Goerli
2023-06-14 16:10:20 +00:00
multiTx1 transfer.MultiTransaction
multiTx1ID common.MultiTransactionIDType
2023-06-14 16:10:20 +00:00
multiTx2 transfer.MultiTransaction
multiTx2ID common.MultiTransactionIDType
2023-06-14 16:10:20 +00:00
multiTx3 transfer.MultiTransaction
multiTx3ID common.MultiTransactionIDType
2023-06-14 16:10:20 +00:00
nextIndex int
}
// Generates and adds to the DB 8 transfers and 3 multitransactions.
// There are only 5 extractable activity entries (transactions + multi-transactions) with timestamps 1-5. The others are associated with a multi-transaction
2023-07-10 14:56:08 +00:00
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, DAI/Goerli
trs, fromAddresses, toAddresses := transfer.GenerateTestTransfers(t, db, 1, 8)
2023-06-14 16:10:20 +00:00
// Plain transfer
td.tr1 = trs[0]
2023-06-20 02:50:49 +00:00
transfer.InsertTestTransfer(t, db, td.tr1.To, &td.tr1)
2023-06-14 16:10:20 +00:00
// Pending transfer
td.pendingTr = trs[1]
transfer.InsertTestPendingTransaction(t, db, &td.pendingTr)
2023-06-14 16:10:20 +00:00
// Send Multitransaction containing 2 x Plain transfers
td.multiTx1Tr1 = trs[2]
td.multiTx1Tr2 = trs[7]
2023-06-14 16:10:20 +00:00
td.multiTx1 = transfer.GenerateTestSendMultiTransaction(td.multiTx1Tr1)
td.multiTx1.ToAsset = testutils.DaiSymbol
2023-06-14 16:10:20 +00:00
td.multiTx1ID = transfer.InsertTestMultiTransaction(t, db, &td.multiTx1)
td.multiTx1Tr1.MultiTransactionID = td.multiTx1ID
2023-06-20 02:50:49 +00:00
transfer.InsertTestTransfer(t, db, td.multiTx1Tr1.To, &td.multiTx1Tr1)
2023-06-14 16:10:20 +00:00
td.multiTx1Tr2.MultiTransactionID = td.multiTx1ID
2023-06-20 02:50:49 +00:00
transfer.InsertTestTransfer(t, db, td.multiTx1Tr2.To, &td.multiTx1Tr2)
2023-06-14 16:10:20 +00:00
// Send Multitransaction containing 2 x Plain transfers + 1 x Pending transfer
td.multiTx2Tr1 = trs[3]
td.multiTx2Tr2 = trs[5]
td.multiTx2PendingTr = trs[6]
2023-06-14 16:10:20 +00:00
td.multiTx2 = transfer.GenerateTestSendMultiTransaction(td.multiTx2Tr1)
td.multiTx2.ToAsset = testutils.SntSymbol
2023-06-14 16:10:20 +00:00
td.multiTx2ID = transfer.InsertTestMultiTransaction(t, db, &td.multiTx2)
2023-06-14 16:10:20 +00:00
td.multiTx2Tr1.MultiTransactionID = td.multiTx2ID
2023-06-20 02:50:49 +00:00
transfer.InsertTestTransfer(t, db, td.multiTx2Tr1.To, &td.multiTx2Tr1)
2023-06-14 16:10:20 +00:00
td.multiTx2Tr2.MultiTransactionID = td.multiTx2ID
2023-06-20 02:50:49 +00:00
transfer.InsertTestTransfer(t, db, td.multiTx2Tr2.To, &td.multiTx2Tr2)
2023-06-14 16:10:20 +00:00
td.multiTx2PendingTr.MultiTransactionID = td.multiTx2ID
transfer.InsertTestPendingTransaction(t, db, &td.multiTx2PendingTr)
// Approve Multitransaction containing 1 x Plain transfer
td.multiTx3Tr1 = trs[4]
td.multiTx3 = transfer.GenerateTestApproveMultiTransaction(td.multiTx3Tr1)
td.multiTx3ID = transfer.InsertTestMultiTransaction(t, db, &td.multiTx3)
td.multiTx3Tr1.MultiTransactionID = td.multiTx3ID
transfer.InsertTestTransfer(t, db, td.multiTx3Tr1.From, &td.multiTx3Tr1)
td.nextIndex = 9
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...), true, []common.ChainID{}, filter, 0, 10)
require.NoError(t, err)
require.Equal(t, 5, len(entries))
// Ensure we have the correct order
var curTimestamp int64 = 5
for _, entry := range entries {
require.Equal(t, curTimestamp, entry.timestamp, "entries are sorted by timestamp; expected %d, got %d", curTimestamp, entry.timestamp)
curTimestamp--
}
expectedEntries := []Entry{
Entry{
payloadType: MultiTransactionPT,
transaction: nil,
id: td.multiTx3ID,
timestamp: int64(td.multiTx3.Timestamp),
activityType: ApproveAT,
activityStatus: FinalizedAS,
amountOut: td.multiTx3.FromAmount,
amountIn: td.multiTx3.ToAmount,
tokenOut: tokenFromSymbol(nil, td.multiTx3.FromAsset),
tokenIn: tokenFromSymbol(nil, td.multiTx3.ToAsset),
symbolOut: common.NewAndSet("USDC"),
symbolIn: common.NewAndSet("USDC"),
sender: &td.multiTx3.FromAddress,
recipient: &td.multiTx3.ToAddress,
},
Entry{
payloadType: MultiTransactionPT,
transaction: nil,
id: td.multiTx2ID,
timestamp: int64(td.multiTx2.Timestamp),
activityType: SendAT,
activityStatus: PendingAS,
amountOut: td.multiTx2.FromAmount,
amountIn: td.multiTx2.ToAmount,
symbolOut: common.NewAndSet("USDC"),
symbolIn: common.NewAndSet("SNT"),
tokenOut: tokenFromSymbol(nil, td.multiTx2.FromAsset),
tokenIn: tokenFromSymbol(nil, td.multiTx2.ToAsset),
sender: &td.multiTx2.FromAddress,
recipient: &td.multiTx2.ToAddress,
},
Entry{
payloadType: MultiTransactionPT,
transaction: nil,
id: td.multiTx1ID,
timestamp: int64(td.multiTx1.Timestamp),
activityType: SendAT,
activityStatus: FinalizedAS,
amountOut: td.multiTx1.FromAmount,
amountIn: td.multiTx1.ToAmount,
tokenOut: tokenFromSymbol(nil, td.multiTx1.FromAsset),
tokenIn: tokenFromSymbol(nil, td.multiTx1.ToAsset),
symbolOut: common.NewAndSet("USDC"),
symbolIn: common.NewAndSet("DAI"),
sender: &td.multiTx1.FromAddress,
recipient: &td.multiTx1.ToAddress,
},
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{}),
},
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: FinalizedAS,
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),
},
}
for idx, expectedEntry := range expectedEntries {
require.Equal(t, expectedEntry, entries[idx], "entry %d", idx)
}
}
// 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, _, _ := fillTestData(t, deps.db)
// 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}, false, []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 7-13
trs, fromTrs, toTrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, 6)
for i := range trs {
2023-06-20 02:50:49 +00:00
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
}
allAddresses := append(append(append(fromTds, toTds...), fromTrs...), toTrs...)
// Test start only
var filter Filter
filter.Period.StartTimestamp = int64(td.multiTx1.Timestamp)
filter.Period.EndTimestamp = NoLimitTimestampForPeriod
entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 9, len(entries))
const simpleTrIndex = 5
// Check start and end content
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
transaction: &transfer.TransactionIdentity{ChainID: trs[simpleTrIndex].ChainID, Hash: trs[simpleTrIndex].Hash, Address: trs[simpleTrIndex].To},
id: 0,
timestamp: trs[simpleTrIndex].Timestamp,
activityType: ReceiveAT,
activityStatus: FinalizedAS,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[simpleTrIndex].Value)),
tokenOut: nil,
tokenIn: TTrToToken(t, &trs[simpleTrIndex].TestTransaction),
symbolOut: nil,
symbolIn: common.NewAndSet("USDC"),
sender: &trs[simpleTrIndex].From,
recipient: &trs[simpleTrIndex].To,
chainIDOut: nil,
chainIDIn: &trs[simpleTrIndex].ChainID,
transferType: expectedTokenType(trs[simpleTrIndex].Token.Address),
}, entries[0])
require.Equal(t, Entry{
payloadType: MultiTransactionPT,
transaction: nil,
2023-06-14 16:10:20 +00:00
id: td.multiTx1ID,
timestamp: int64(td.multiTx1.Timestamp),
activityType: SendAT,
activityStatus: FinalizedAS,
amountOut: td.multiTx1.FromAmount,
amountIn: td.multiTx1.ToAmount,
tokenOut: tokenFromSymbol(nil, td.multiTx1.FromAsset),
tokenIn: tokenFromSymbol(nil, td.multiTx1.ToAsset),
symbolOut: common.NewAndSet("USDC"),
symbolIn: common.NewAndSet("DAI"),
sender: &td.multiTx1.FromAddress,
recipient: &td.multiTx1.ToAddress,
chainIDOut: nil,
chainIDIn: nil,
transferType: nil,
}, entries[8])
// Test complete interval
filter.Period.EndTimestamp = trs[2].Timestamp
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 6, 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: FinalizedAS,
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,
2023-06-14 16:10:20 +00:00
id: td.multiTx1ID,
timestamp: int64(td.multiTx1.Timestamp),
activityType: SendAT,
activityStatus: FinalizedAS,
amountOut: td.multiTx1.FromAmount,
amountIn: td.multiTx1.ToAmount,
tokenOut: tokenFromSymbol(nil, td.multiTx1.FromAsset),
tokenIn: tokenFromSymbol(nil, td.multiTx1.ToAsset),
symbolOut: common.NewAndSet("USDC"),
symbolIn: common.NewAndSet("DAI"),
sender: &td.multiTx1.FromAddress,
recipient: &td.multiTx1.ToAddress,
chainIDOut: nil,
chainIDIn: nil,
transferType: nil,
}, entries[5])
// Test end only
filter.Period.StartTimestamp = NoLimitTimestampForPeriod
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []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[2].ChainID, Hash: trs[2].Hash, Address: trs[2].To},
id: 0,
timestamp: trs[2].Timestamp,
activityType: ReceiveAT,
activityStatus: FinalizedAS,
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: FinalizedAS,
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[7])
}
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 {
2023-06-20 02:50:49 +00:00
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
}
allAddresses := append(fromTrs, toTrs...)
var filter Filter
// Get all
entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, []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, allAddresses, true, []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: FinalizedAS,
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: FinalizedAS,
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, allAddresses, true, []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: FinalizedAS,
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: FinalizedAS,
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, allAddresses, true, []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: FinalizedAS,
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, approveCount 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++
case ApproveAT:
approveCount++
}
}
return
}
func TestGetActivityEntriesFilterByType(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
// Adds 4 extractable transactions
td, tdFromAdds, tdToAddrs := fillTestData(t, deps.db)
// Add 6 extractable transactions: one MultiTransactionSwap, two MultiTransactionBridge, two MultiTransactionSend and one MultiTransactionApprove
multiTxs := make([]transfer.MultiTransaction, 6)
trs, fromAddrs, toAddrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, len(multiTxs)*2)
2023-06-14 16:10:20 +00:00
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]
multiTxs[5] = transfer.GenerateTestApproveMultiTransaction(trs[10]) // trs[11]
var lastMT common.MultiTransactionIDType
for i := range trs {
if i%2 == 0 {
lastMT = transfer.InsertTestMultiTransaction(t, deps.db, &multiTxs[i/2])
}
2023-06-14 16:10:20 +00:00
trs[i].MultiTransactionID = lastMT
2023-06-20 02:50:49 +00:00
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
}
trsSpecial, fromSpecial, toSpecial := transfer.GenerateTestTransfers(t, deps.db, 100, 3)
chore(wallet) optimize the filer query Main changes: - Use tr_type instead of IN clause - Use binary (X'...' syntax) directly into the query instead of converting DB values to HEX - Found to be slightly faster than query parameters in the dedicated benchmark - Didn't see much improvement in filter benchmarks - Tried various combinations of optimizations but without impressive performance results Benchmark results: | Name | Original | tr_type | join | hex | no-db | db_only | last | net_j | |:-----------------------|:-----------|:----------|:---------|:---------|:----------|:----------|---------:|---------:| | RAM_NoFilter-10 | 49580229 | 51253242 | 51112462 | 50915133 | 121217817 | 141691008 | 50908642 | 50239712 | | SSD_NoFilter-10 | 49963604 | 51393588 | 51213038 | 50881483 | 120785679 | 141063467 | 50462767 | 49676867 | | SSD_MovingWindow-10 | 53695712 | 54155292 | 54161733 | 54061325 | 126966633 | 146866017 | 53479929 | 53350475 | | SSD_AllAddr_AllTos-10 | 41382804 | 41195225 | 51684175 | 52107262 | 64348100 | 97608833 | 50523529 | 49968321 | | SSD_OneAddress-10 | 34945275 | 35103850 | 31066429 | 31328762 | 50927300 | 54322971 | 30098529 | 30252546 | | FilterSend_AllAddr-10 | 39546808 | 37566604 | 38389725 | 38260738 | 114820458 | 125588408 | 37127625 | 36864575 | | FilterSend_6Addr-10 | 41221458 | 41111225 | 40848288 | 40135492 | 118629700 | 128200467 | 38942521 | 39012100 | | FilterThreeNetworks-10 | - | - | - | - | - | - | 50058929 | 49854450 | Update status-desktop: #11036
2023-09-14 21:50:51 +00:00
// Here not to include the modified To and From addresses
allAddresses := append(append(append(append(append(tdFromAdds, tdToAddrs...), fromAddrs...), toAddrs...), fromSpecial...), toSpecial...)
// Insert MintAT Collectible
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 MintAT Token
trsSpecial[1].From = eth.HexToAddress("0x0")
inputMethod, err := hex.DecodeString("1b5ee6ae")
require.NoError(t, err)
transfer.InsertTestTransferWithOptions(t, deps.db, trsSpecial[1].To, &trsSpecial[1], &transfer.TestTransferOptions{
TokenAddress: eth.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb49"),
Tx: transfer.GenerateTxField(inputMethod),
})
// Insert ContractDeploymentAt
trsSpecial[2].To = eth.HexToAddress("0x0")
transfer.InsertTestTransferWithOptions(t, deps.db, trsSpecial[2].From, &trsSpecial[2], &transfer.TestTransferOptions{
NullifyAddresses: []eth.Address{trsSpecial[2].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, trs[10].From, trsSpecial[0].To, trsSpecial[1].To, trsSpecial[2].From}
entries, err := getActivityEntries(context.Background(), deps, addresses, false, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 13, len(entries))
filter.Types = []Type{SendAT, SwapAT}
entries, err = getActivityEntries(context.Background(), deps, addresses, false, []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, approveCount := 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)
require.Equal(t, 0, approveCount)
filter.Types = []Type{BridgeAT, ReceiveAT}
entries, err = getActivityEntries(context.Background(), deps, addresses, false, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 3, len(entries))
sendCount, receiveCount, contractCount, mintCount, swapCount, _, bridgeCount, approveCount = 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)
require.Equal(t, 0, approveCount)
filter.Types = []Type{MintAT}
entries, err = getActivityEntries(context.Background(), deps, addresses, false, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 2, len(entries))
sendCount, receiveCount, contractCount, mintCount, swapCount, _, bridgeCount, approveCount = countTypes(entries)
require.Equal(t, 0, sendCount)
require.Equal(t, 0, receiveCount)
require.Equal(t, 0, contractCount)
require.Equal(t, 2, mintCount)
require.Equal(t, 0, swapCount)
require.Equal(t, 0, bridgeCount)
require.Equal(t, 0, approveCount)
filter.Types = []Type{ContractDeploymentAT}
entries, err = getActivityEntries(context.Background(), deps, addresses, false, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 1, len(entries))
sendCount, receiveCount, contractCount, mintCount, swapCount, _, bridgeCount, approveCount = 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)
require.Equal(t, 0, approveCount)
filter.Types = []Type{ApproveAT}
entries, err = getActivityEntries(context.Background(), deps, addresses, false, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 1, len(entries))
sendCount, receiveCount, contractCount, mintCount, swapCount, _, bridgeCount, approveCount = countTypes(entries)
require.Equal(t, 0, sendCount)
require.Equal(t, 0, receiveCount)
require.Equal(t, 0, contractCount)
require.Equal(t, 0, mintCount)
require.Equal(t, 0, swapCount)
require.Equal(t, 0, bridgeCount)
require.Equal(t, 1, approveCount)
// Filter with all addresses regression
filter.Types = []Type{SendAT}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
chore(wallet) optimize the filer query Main changes: - Use tr_type instead of IN clause - Use binary (X'...' syntax) directly into the query instead of converting DB values to HEX - Found to be slightly faster than query parameters in the dedicated benchmark - Didn't see much improvement in filter benchmarks - Tried various combinations of optimizations but without impressive performance results Benchmark results: | Name | Original | tr_type | join | hex | no-db | db_only | last | net_j | |:-----------------------|:-----------|:----------|:---------|:---------|:----------|:----------|---------:|---------:| | RAM_NoFilter-10 | 49580229 | 51253242 | 51112462 | 50915133 | 121217817 | 141691008 | 50908642 | 50239712 | | SSD_NoFilter-10 | 49963604 | 51393588 | 51213038 | 50881483 | 120785679 | 141063467 | 50462767 | 49676867 | | SSD_MovingWindow-10 | 53695712 | 54155292 | 54161733 | 54061325 | 126966633 | 146866017 | 53479929 | 53350475 | | SSD_AllAddr_AllTos-10 | 41382804 | 41195225 | 51684175 | 52107262 | 64348100 | 97608833 | 50523529 | 49968321 | | SSD_OneAddress-10 | 34945275 | 35103850 | 31066429 | 31328762 | 50927300 | 54322971 | 30098529 | 30252546 | | FilterSend_AllAddr-10 | 39546808 | 37566604 | 38389725 | 38260738 | 114820458 | 125588408 | 37127625 | 36864575 | | FilterSend_6Addr-10 | 41221458 | 41111225 | 40848288 | 40135492 | 118629700 | 128200467 | 38942521 | 39012100 | | FilterThreeNetworks-10 | - | - | - | - | - | - | 50058929 | 49854450 | Update status-desktop: #11036
2023-09-14 21:50:51 +00:00
// We have 6 but one is not matched because is a receive, having owner the to address
require.Equal(t, 5, len(entries))
}
func TestStatusMintCustomEvent(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
td, fromTds, toTds := fillTestData(t, deps.db)
trs, fromTrs, toTrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, 3)
allAddresses := append(append(append(fromTds, toTds...), fromTrs...), toTrs...)
trs[0].From = eth.HexToAddress("0x0")
transfer.InsertTestTransferWithOptions(t, deps.db, trs[0].To, &trs[0], &transfer.TestTransferOptions{
TokenAddress: eth.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"),
Receipt: &types.Receipt{
Logs: []*types.Log{
{Topics: []eth.Hash{eth.HexToHash("0xea667487ed28493de38fd2808b00affaee21d875a9e95aa01ef8352151292297")}},
{Topics: []eth.Hash{eth.HexToHash("0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925")}},
},
},
})
// StatusMint - 0x28c427b0611d99da5c4f7368abe57e86b045b483c4689ae93e90745802335b87
trs[1].From = eth.HexToAddress("0x0")
transfer.InsertTestTransferWithOptions(t, deps.db, trs[1].To, &trs[1], &transfer.TestTransferOptions{
TokenAddress: eth.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb49"),
Receipt: &types.Receipt{
Logs: []*types.Log{
{Topics: []eth.Hash{eth.HexToHash("0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925")}},
{Topics: []eth.Hash{eth.HexToHash("0x28c427b0611d99da5c4f7368abe57e86b045b483c4689ae93e90745802335b87")}},
},
},
})
// Log order should not matter
trs[2].From = eth.HexToAddress("0x0")
transfer.InsertTestTransferWithOptions(t, deps.db, trs[2].To, &trs[2], &transfer.TestTransferOptions{
TokenAddress: eth.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb49"),
Receipt: &types.Receipt{
Logs: []*types.Log{
{Topics: []eth.Hash{eth.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")}},
{Topics: []eth.Hash{eth.HexToHash("0x28c427b0611d99da5c4f7368abe57e86b045b483c4689ae93e90745802335b87")}},
{Topics: []eth.Hash{eth.HexToHash("0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925")}},
},
},
})
var filter Filter
entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 8, len(entries))
filter.Types = []Type{MintAT}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 2, len(entries))
require.Equal(t, trs[2].Hash, entries[0].transaction.Hash)
require.Equal(t, trs[1].Hash, entries[1].transaction.Hash)
}
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])
}
allAddresses := append(append(append(fromTds, toTds...), fromTrs...), toTrs...)
var filter Filter
entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 11, 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, false, []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: FinalizedAS,
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: FinalizedAS,
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,
2023-06-14 16:10:20 +00:00
id: td.multiTx2ID,
timestamp: int64(td.multiTx2.Timestamp),
activityType: SendAT,
activityStatus: PendingAS,
amountOut: td.multiTx2.FromAmount,
amountIn: td.multiTx2.ToAmount,
tokenOut: tokenFromSymbol(nil, td.multiTx2.FromAsset),
tokenIn: tokenFromSymbol(nil, td.multiTx2.ToAsset),
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 finalized
td, fromTds, toTds := fillTestData(t, deps.db)
// Add 7 extractable transactions: 1 pending, 1 Tr failed, 1 MT failed, 4 finalized
trs, fromTrs, toTrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, 7)
2023-06-14 16:10:20 +00:00
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
if trs[i].Success && (i == 2 || i == 5) {
// Finalize status depends on timestamp
trs[i].Timestamp = mockupTime.Unix() - 10
}
2023-06-20 02:50:49 +00:00
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
}
}
allAddresses := append(append(append(fromTds, toTds...), fromTrs...), toTrs...)
var filter Filter
filter.Statuses = allActivityStatusesFilter()
entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 12, len(entries))
filter.Statuses = []Status{PendingAS}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []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)
2023-06-14 16:10:20 +00:00
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, allAddresses, true, []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, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 2, len(entries))
filter.Statuses = []Status{FinalizedAS}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 5, len(entries))
// Combined filter
filter.Statuses = []Status{FailedAS, PendingAS}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []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,
})
}
allAddresses := append(append(append(fromTds, toTds...), fromTrs...), toTrs...)
var filter Filter
filter.FilterOutAssets = true
entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 0, len(entries))
filter.FilterOutAssets = false
filter.Assets = allTokensFilter()
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 14, 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, allAddresses, true, []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, allAddresses, true, []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, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
// Three MT for which ChainID is ignored and one transfer on the main net and the Goerli is ignored
require.Equal(t, 4, 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.UsdcMainnet.Address, entries[1].tokenIn.Address)
require.Equal(t, Erc20, entries[2].tokenOut.TokenType)
require.Equal(t, transfer.UsdcMainnet.Address, entries[2].tokenOut.Address)
require.Equal(t, Erc20, entries[2].tokenIn.TokenType)
require.Equal(t, transfer.SntMainnet.Address, entries[2].tokenIn.Address)
require.Equal(t, Erc20, entries[3].tokenOut.TokenType)
require.Equal(t, transfer.UsdcMainnet.Address, entries[3].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, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
// Three MT for which ChainID is ignored and two transfers on the main net and Goerli
require.Equal(t, 5, 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)
}
2023-09-21 06:58:36 +00:00
func TestGetActivityEntriesFilterByCollectibles(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
// Adds 4 extractable transactions 2 transactions (ETH/Goerli, ETH/Optimism), one MT USDC to DAI and another MT USDC to SNT
td, fromTds, toTds := fillTestData(t, deps.db)
// Add 4 transactions with collectibles
trs, fromTrs, toTrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, 4)
for i := range trs {
collectibleData := transfer.TestCollectibles[i]
trs[i].ChainID = collectibleData.ChainID
transfer.InsertTestTransferWithOptions(t, deps.db, trs[i].To, &trs[i], &transfer.TestTransferOptions{
TokenAddress: collectibleData.TokenAddress,
TokenID: collectibleData.TokenID,
})
}
allAddresses := append(append(append(fromTds, toTds...), fromTrs...), toTrs...)
var filter Filter
filter.FilterOutCollectibles = true
entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 0, len(entries))
filter.FilterOutCollectibles = false
filter.Collectibles = allTokensFilter()
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 9, len(entries))
2023-09-21 06:58:36 +00:00
// Search for a specific collectible
filter.Collectibles = []Token{tokenFromCollectible(&transfer.TestCollectibles[0])}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 1, len(entries))
require.Equal(t, entries[0].tokenIn.Address, transfer.TestCollectibles[0].TokenAddress)
require.Equal(t, entries[0].tokenIn.TokenID, (*hexutil.Big)(transfer.TestCollectibles[0].TokenID))
// Search for a specific collectible
filter.Collectibles = []Token{tokenFromCollectible(&transfer.TestCollectibles[3])}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 1, len(entries))
require.Equal(t, entries[0].tokenIn.Address, transfer.TestCollectibles[3].TokenAddress)
require.Equal(t, entries[0].tokenIn.TokenID, (*hexutil.Big)(transfer.TestCollectibles[3].TokenID))
// Search for a multiple collectibles
filter.Collectibles = []Token{tokenFromCollectible(&transfer.TestCollectibles[1]), tokenFromCollectible(&transfer.TestCollectibles[2])}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 2, len(entries))
require.Equal(t, entries[0].tokenIn.Address, transfer.TestCollectibles[2].TokenAddress)
require.True(t, (*big.Int)(entries[0].tokenIn.TokenID).Cmp(transfer.TestCollectibles[2].TokenID) == 0)
require.Equal(t, entries[1].tokenIn.Address, transfer.TestCollectibles[1].TokenAddress)
require.True(t, (*big.Int)(entries[1].tokenIn.TokenID).Cmp(transfer.TestCollectibles[1].TokenID) == 0)
2023-09-21 06:58:36 +00:00
}
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 {
2023-06-20 02:50:49 +00:00
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
}
allAddresses := append(append(append(fromTds, toTds...), fromTrs...), toTrs...)
var filter Filter
filter.CounterpartyAddresses = allAddresses
entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 11, len(entries))
2023-07-10 14:56:08 +00:00
filter.CounterpartyAddresses = []eth.Address{eth.HexToAddress("0x567890")}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 0, len(entries))
2023-07-10 14:56:08 +00:00
filter.CounterpartyAddresses = []eth.Address{td.pendingTr.To, td.multiTx2.ToAddress, trs[3].To}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []common.ChainID{}, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 3, len(entries))
2023-07-10 14:56:08 +00:00
filter.CounterpartyAddresses = []eth.Address{td.tr1.To, td.pendingTr.From, trs[3].From, trs[5].To}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, []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)
}
recordPresence(td.multiTx3Tr1.ChainID, 4)
// Add 6 extractable transactions
trs, fromTrs, toTrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, 6)
for i := range trs {
recordPresence(trs[i].ChainID, 5+i)
2023-06-20 02:50:49 +00:00
transfer.InsertTestTransfer(t, deps.db, trs[i].To, &trs[i])
}
allAddresses := append(append(append(fromTds, toTds...), fromTrs...), toTrs...)
var filter Filter
chainIDs := allNetworksFilter()
entries, err := getActivityEntries(context.Background(), deps, allAddresses, true, chainIDs, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 11, len(entries))
chainIDs = []common.ChainID{5674839210}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, 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, td.multiTx3Tr1.ChainID}
entries, err = getActivityEntries(context.Background(), deps, allAddresses, true, 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.ID
trs[2].MultiTransactionID = mt1.ID
trs[3].ChainID = 1234
mt2 := transfer.GenerateTestSwapMultiTransaction(trs[3], testutils.SntSymbol, 100)
// insertMultiTransaction will insert 0 instead of NULL
mt2.FromNetworkID = 0
mt2.ToNetworkID = 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, false, 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, false, chainIDs, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 1, len(entries))
require.Equal(t, entries[0].id, mt1.ID)
// Filter by pending_transactions sub-transacitons
chainIDs = []common.ChainID{trs[2].ChainID}
entries, err = getActivityEntries(context.Background(), deps, toTrs, false, chainIDs, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 1, len(entries))
require.Equal(t, entries[0].id, mt1.ID)
chainIDs = []common.ChainID{trs[3].ChainID}
entries, err = getActivityEntries(context.Background(), deps, toTrs, false, chainIDs, filter, 0, 15)
require.NoError(t, err)
require.Equal(t, 1, len(entries))
require.Equal(t, entries[0].id, mt2.ID)
}
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)
2023-06-20 02:50:49 +00:00
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, false, []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
2023-06-14 16:10:20 +00:00
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()
_, fromAddresses, toAddresses := fillTestData(t, deps.db)
cancellableCtx, cancelFn := context.WithCancel(context.Background())
cancelFn()
activities, err := getActivityEntries(cancellableCtx, deps, append(fromAddresses, toAddresses...), true, []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])
2023-07-10 14:56:08 +00:00
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].From, &trs[i], &transfer.TestTransferOptions{
2023-07-10 14:56:08 +00:00
NullifyAddresses: []eth.Address{trs[i].To},
})
}
2023-07-10 14:56:08 +00:00
trs[3].To = eth.Address{}
transfer.InsertTestPendingTransaction(t, deps.db, &trs[3])
addresses := []eth.Address{trs[0].From, trs[1].From, trs[2].From, trs[3].From}
activities, err := getActivityEntries(context.Background(), deps, addresses, false, allNetworksFilter(), Filter{}, 0, 10)
require.NoError(t, err)
require.Equal(t, 3, len(activities))
}
func TestGetActivityEntries_ErrorIfNoAddress(t *testing.T) {
_, err := getActivityEntries(context.Background(), FilterDependencies{}, []eth.Address{}, true, []common.ChainID{}, Filter{}, 0, 10)
require.EqualError(t, err, "no addresses provided")
}
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, len(details.ChainDetails), 1)
require.Equal(t, td.tr1.ChainID, common.ChainID(details.ChainDetails[0].ChainID))
require.Equal(t, td.tr1.BlkNumber, details.ChainDetails[0].BlockNumber)
require.Equal(t, td.tr1.Hash, details.ChainDetails[0].Hash)
require.Equal(t, td.tr1.Contract, *details.ChainDetails[0].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.ID))
require.NoError(t, err)
require.Equal(t, "", details.ID)
require.Equal(t, int(td.multiTx1.ID), details.MultiTxID)
require.Equal(t, td.multiTx1Tr2.Nonce, details.Nonce)
require.Equal(t, 2, len(details.ChainDetails))
require.Equal(t, td.multiTx1Tr1.ChainID, common.ChainID(details.ChainDetails[0].ChainID))
require.Equal(t, td.multiTx1Tr1.BlkNumber, details.ChainDetails[0].BlockNumber)
require.Equal(t, td.multiTx1Tr1.Hash, details.ChainDetails[0].Hash)
require.Equal(t, td.multiTx1Tr1.Contract, *details.ChainDetails[0].Contract)
require.Equal(t, td.multiTx1Tr2.ChainID, common.ChainID(details.ChainDetails[1].ChainID))
require.Equal(t, td.multiTx1Tr2.BlkNumber, details.ChainDetails[1].BlockNumber)
require.Equal(t, td.multiTx1Tr2.Hash, details.ChainDetails[1].Hash)
require.Equal(t, td.multiTx1Tr2.Contract, *details.ChainDetails[1].Contract)
}
func TestGetActivityEntriesSkipEthGasFeeOnlyTransfers(t *testing.T) {
deps, close := setupTestActivityDB(t)
defer close()
to := eth.Address{0x1}
from := eth.Address{0x2}
hash := eth.Hash{0x3}
blkNum := int64(1)
chainID := common.ChainID(1)
nonce := uint64(1)
// Insert 0-value gas-only ETH transfer as a result of token transfer's gas fee
transfer.InsertTestTransfer(t, deps.db, to, &transfer.TestTransfer{
TestTransaction: transfer.TestTransaction{
ChainID: chainID,
From: from,
Hash: hash,
BlkNumber: blkNum,
Nonce: nonce,
},
To: to,
Value: 0,
})
entries, err := getActivityEntries(context.Background(), deps, []eth.Address{to}, true, []common.ChainID{chainID}, Filter{}, 0, 10)
require.NoError(t, err)
require.Equal(t, 1, len(entries))
require.Equal(t, hash, entries[0].transaction.Hash)
// Insert token transfer
transfer.InsertTestTransferWithOptions(t, deps.db, to,
&transfer.TestTransfer{
TestTransaction: transfer.TestTransaction{
ChainID: chainID,
From: from,
Hash: hash,
BlkNumber: blkNum,
Nonce: nonce,
},
To: to,
Value: 1,
},
&transfer.TestTransferOptions{
TokenAddress: eth.Address{0x4},
},
)
// Gas-fee-only transfer should be removed, so we get only 1 transfer again
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{to}, true, []common.ChainID{chainID}, Filter{}, 0, 10)
require.NoError(t, err)
require.Equal(t, 1, len(entries))
require.Equal(t, contractTypeFromDBType("erc20"), entries[0].transferType)
// Insert real 0-value ETH transfer
transfer.InsertTestTransfer(t, deps.db, to, &transfer.TestTransfer{
TestTransaction: transfer.TestTransaction{
ChainID: chainID,
From: from,
Hash: eth.Hash{0x5}, // another hash
BlkNumber: blkNum,
Nonce: nonce + 1, // another nonce
},
To: to,
Value: 0, // 0-value as well
})
entries, err = getActivityEntries(context.Background(), deps, []eth.Address{to}, true, []common.ChainID{chainID}, Filter{}, 0, 10)
require.NoError(t, err)
require.Equal(t, 2, len(entries))
}