feat: add transaction amounts to activity entry

This commit is contained in:
Dario Gabriel Lipicar 2023-06-14 16:43:28 -03:00 committed by dlipicar
parent 86350379b1
commit e26c2a7095
5 changed files with 175 additions and 13 deletions

View file

@ -7,10 +7,12 @@ import (
"encoding/json"
"errors"
"fmt"
"math/big"
"strconv"
"strings"
eth "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/services/wallet/common"
@ -36,6 +38,8 @@ type Entry struct {
activityType Type
activityStatus Status
tokenType TokenType
amountOut *hexutil.Big // Used for activityType SendAT, SwapAT, BridgeAT
amountIn *hexutil.Big // Used for activityType ReceiveAT, BuyAT, SwapAT, BridgeAT
}
type jsonSerializationTemplate struct {
@ -46,6 +50,8 @@ type jsonSerializationTemplate struct {
ActivityType Type `json:"activityType"`
ActivityStatus Status `json:"activityStatus"`
TokenType TokenType `json:"tokenType"`
AmountOut *hexutil.Big `json:"amountOut"`
AmountIn *hexutil.Big `json:"amountIn"`
}
func (e *Entry) MarshalJSON() ([]byte, error) {
@ -57,6 +63,8 @@ func (e *Entry) MarshalJSON() ([]byte, error) {
ActivityType: e.activityType,
ActivityStatus: e.activityStatus,
TokenType: e.tokenType,
AmountOut: e.amountOut,
AmountIn: e.amountIn,
})
}
@ -72,18 +80,20 @@ func (e *Entry) UnmarshalJSON(data []byte) error {
e.id = aux.ID
e.timestamp = aux.Timestamp
e.activityType = aux.ActivityType
e.amountOut = aux.AmountOut
e.amountIn = aux.AmountIn
return nil
}
func newActivityEntryWithPendingTransaction(transaction *transfer.TransactionIdentity, timestamp int64, activityType Type, activityStatus Status) Entry {
return newActivityEntryWithTransaction(true, transaction, timestamp, activityType, activityStatus)
func newActivityEntryWithPendingTransaction(transaction *transfer.TransactionIdentity, timestamp int64, activityType Type, activityStatus Status, amountIn *hexutil.Big, amountOut *hexutil.Big) Entry {
return newActivityEntryWithTransaction(true, transaction, timestamp, activityType, activityStatus, amountIn, amountOut)
}
func newActivityEntryWithSimpleTransaction(transaction *transfer.TransactionIdentity, timestamp int64, activityType Type, activityStatus Status) Entry {
return newActivityEntryWithTransaction(false, transaction, timestamp, activityType, activityStatus)
func newActivityEntryWithSimpleTransaction(transaction *transfer.TransactionIdentity, timestamp int64, activityType Type, activityStatus Status, amountIn *hexutil.Big, amountOut *hexutil.Big) Entry {
return newActivityEntryWithTransaction(false, transaction, timestamp, activityType, activityStatus, amountIn, amountOut)
}
func newActivityEntryWithTransaction(pending bool, transaction *transfer.TransactionIdentity, timestamp int64, activityType Type, activityStatus Status) Entry {
func newActivityEntryWithTransaction(pending bool, transaction *transfer.TransactionIdentity, timestamp int64, activityType Type, activityStatus Status, amountIn *hexutil.Big, amountOut *hexutil.Big) Entry {
payloadType := SimpleTransactionPT
if pending {
payloadType = PendingTransactionPT
@ -97,10 +107,12 @@ func newActivityEntryWithTransaction(pending bool, transaction *transfer.Transac
activityType: activityType,
activityStatus: activityStatus,
tokenType: AssetTT,
amountIn: amountIn,
amountOut: amountOut,
}
}
func NewActivityEntryWithMultiTransaction(id transfer.MultiTransactionIDType, timestamp int64, activityType Type, activityStatus Status) Entry {
func NewActivityEntryWithMultiTransaction(id transfer.MultiTransactionIDType, timestamp int64, activityType Type, activityStatus Status, amountIn *hexutil.Big, amountOut *hexutil.Big) Entry {
return Entry{
payloadType: MultiTransactionPT,
id: id,
@ -108,6 +120,8 @@ func NewActivityEntryWithMultiTransaction(id transfer.MultiTransactionIDType, ti
activityType: activityType,
activityStatus: activityStatus,
tokenType: AssetTT,
amountIn: amountIn,
amountOut: amountOut,
}
}
@ -290,6 +304,9 @@ const (
transfers.sender AS from_address,
transfers.address AS to_address,
transfers.amount_padded128hex AS tr_amount,
NULL AS mt_from_amount,
NULL AS mt_to_amount,
CASE
WHEN transfers.status IS 1 THEN statusSuccess
@ -352,6 +369,10 @@ const (
pending_transactions.from_address AS from_address,
pending_transactions.to_address AS to_address,
pending_transactions.value AS tr_amount,
NULL AS mt_from_amount,
NULL AS mt_to_amount,
statusPending AS agg_status,
1 AS agg_count
FROM pending_transactions, filter_conditions
@ -387,6 +408,9 @@ const (
NULL as tr_type,
multi_transactions.from_address AS from_address,
multi_transactions.to_address AS to_address,
NULL AS tr_amount,
multi_transactions.from_amount AS mt_from_amount,
multi_transactions.to_amount AS mt_to_amount,
CASE
WHEN tr_status.min_status = 1 AND pending_status.count IS NULL THEN statusSuccess
@ -423,6 +447,51 @@ const (
noEntriesInTmpTableSQLValues = "(NULL)"
)
func getTrInAndOutAmounts(activityType Type, trAmount sql.NullString) (inAmount *hexutil.Big, outAmount *hexutil.Big) {
if trAmount.Valid {
amount, ok := new(big.Int).SetString(trAmount.String, 16)
if ok {
switch activityType {
case SendAT:
inAmount = (*hexutil.Big)(big.NewInt(0))
outAmount = (*hexutil.Big)(amount)
return
case ReceiveAT:
inAmount = (*hexutil.Big)(amount)
outAmount = (*hexutil.Big)(big.NewInt(0))
return
default:
log.Warn(fmt.Sprintf("unexpected activity type %d", activityType))
}
} else {
log.Warn(fmt.Sprintf("could not parse amount %s", trAmount.String))
}
} else {
log.Warn(fmt.Sprintf("invalid transaction amount for type %d", activityType))
}
inAmount = (*hexutil.Big)(big.NewInt(0))
outAmount = (*hexutil.Big)(big.NewInt(0))
return
}
func getMtInAndOutAmounts(dbFromAmount sql.NullString, dbToAmount sql.NullString) (inAmount *hexutil.Big, outAmount *hexutil.Big) {
if dbFromAmount.Valid && dbToAmount.Valid {
fromAmount, frOk := new(big.Int).SetString(dbFromAmount.String, 16)
toAmount, toOk := new(big.Int).SetString(dbToAmount.String, 16)
if frOk && toOk {
inAmount = (*hexutil.Big)(toAmount)
outAmount = (*hexutil.Big)(fromAmount)
return
}
log.Warn(fmt.Sprintf("could not parse amounts %s %s", dbFromAmount.String, dbToAmount.String))
} else {
log.Warn("invalid transaction amounts")
}
inAmount = (*hexutil.Big)(big.NewInt(0))
outAmount = (*hexutil.Big)(big.NewInt(0))
return
}
// getActivityEntries queries the transfers, pending_transactions, and multi_transactions tables
// based on filter parameters and arguments
// it returns metadata for all entries ordered by timestamp column
@ -507,7 +576,9 @@ func getActivityEntries(ctx context.Context, db *sql.DB, addresses []eth.Address
var dbMtType, dbTrType sql.NullByte
var toAddress, fromAddress eth.Address
var aggregatedStatus int
err := rows.Scan(&transferHash, &pendingHash, &chainID, &multiTxID, &timestamp, &dbMtType, &dbTrType, &fromAddress, &toAddress, &aggregatedStatus, &aggregatedCount)
var dbTrAmount sql.NullString
var dbMtFromAmount, dbMtToAmount sql.NullString
err := rows.Scan(&transferHash, &pendingHash, &chainID, &multiTxID, &timestamp, &dbMtType, &dbTrType, &fromAddress, &toAddress, &dbTrAmount, &dbMtFromAmount, &dbMtToAmount, &aggregatedStatus, &aggregatedCount)
if err != nil {
return nil, err
}
@ -530,17 +601,20 @@ func getActivityEntries(ctx context.Context, db *sql.DB, addresses []eth.Address
var entry Entry
if transferHash != nil && chainID.Valid {
activityType, filteredAddress := getActivityType(dbTrType)
inAmount, outAmount := getTrInAndOutAmounts(activityType, dbTrAmount)
entry = newActivityEntryWithSimpleTransaction(
&transfer.TransactionIdentity{ChainID: common.ChainID(chainID.Int64), Hash: eth.BytesToHash(transferHash), Address: filteredAddress},
timestamp, activityType, activityStatus)
timestamp, activityType, activityStatus, inAmount, outAmount)
} else if pendingHash != nil && chainID.Valid {
activityType, _ := getActivityType(dbTrType)
inAmount, outAmount := getTrInAndOutAmounts(activityType, dbTrAmount)
entry = newActivityEntryWithPendingTransaction(&transfer.TransactionIdentity{ChainID: common.ChainID(chainID.Int64), Hash: eth.BytesToHash(pendingHash)},
timestamp, activityType, activityStatus)
timestamp, activityType, activityStatus, inAmount, outAmount)
} else if multiTxID.Valid {
mtInAmount, mtOutAmount := getMtInAndOutAmounts(dbMtFromAmount, dbMtToAmount)
activityType := multiTransactionTypeToActivityType(transfer.MultiTransactionType(dbMtType.Byte))
entry = NewActivityEntryWithMultiTransaction(transfer.MultiTransactionIDType(multiTxID.Int64),
timestamp, activityType, activityStatus)
timestamp, activityType, activityStatus, mtInAmount, mtOutAmount)
} else {
return nil, errors.New("invalid row data")
}

View file

@ -3,6 +3,7 @@ package activity
import (
"context"
"database/sql"
"math/big"
"testing"
"github.com/status-im/status-go/appdatabase"
@ -14,6 +15,7 @@ import (
eth "github.com/ethereum/go-ethereum/common"
eth_common "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
)
@ -133,6 +135,8 @@ func TestGetActivityEntriesAll(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(td.tr1.Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries))
require.True(t, testutils.StructExistsInSlice(Entry{
payloadType: PendingTransactionPT,
@ -142,6 +146,8 @@ func TestGetActivityEntriesAll(t *testing.T) {
activityType: SendAT,
activityStatus: PendingAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(td.pendingTr.Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries))
require.True(t, testutils.StructExistsInSlice(Entry{
payloadType: MultiTransactionPT,
@ -151,6 +157,8 @@ func TestGetActivityEntriesAll(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(td.multiTx1.FromAmount)),
amountIn: (*hexutil.Big)(big.NewInt(td.multiTx1.ToAmount)),
}, entries))
require.True(t, testutils.StructExistsInSlice(Entry{
payloadType: MultiTransactionPT,
@ -160,6 +168,8 @@ func TestGetActivityEntriesAll(t *testing.T) {
activityType: SendAT,
activityStatus: PendingAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(td.multiTx2.FromAmount)),
amountIn: (*hexutil.Big)(big.NewInt(td.multiTx2.ToAmount)),
}, entries))
// Ensure the sub-transactions of the multi-transactions are not returned
@ -171,6 +181,8 @@ func TestGetActivityEntriesAll(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(td.multiTx1Tr1.Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries))
require.False(t, testutils.StructExistsInSlice(Entry{
payloadType: SimpleTransactionPT,
@ -180,6 +192,8 @@ func TestGetActivityEntriesAll(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(td.multiTx1Tr2.Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries))
require.False(t, testutils.StructExistsInSlice(Entry{
payloadType: SimpleTransactionPT,
@ -189,6 +203,8 @@ func TestGetActivityEntriesAll(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(td.multiTx2Tr1.Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries))
require.False(t, testutils.StructExistsInSlice(Entry{
payloadType: SimpleTransactionPT,
@ -198,6 +214,8 @@ func TestGetActivityEntriesAll(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(td.multiTx2Tr2.Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries))
require.False(t, testutils.StructExistsInSlice(Entry{
payloadType: PendingTransactionPT,
@ -207,6 +225,8 @@ func TestGetActivityEntriesAll(t *testing.T) {
activityType: SendAT,
activityStatus: PendingAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(td.multiTx2PendingTr.Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries))
}
@ -286,6 +306,8 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(trs[5].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries[0])
require.Equal(t, Entry{
payloadType: MultiTransactionPT,
@ -295,6 +317,8 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(td.multiTx1.FromAmount)),
amountIn: (*hexutil.Big)(big.NewInt(td.multiTx1.ToAmount)),
}, entries[7])
// Test complete interval
@ -311,6 +335,8 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(trs[2].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries[0])
require.Equal(t, Entry{
payloadType: MultiTransactionPT,
@ -320,6 +346,8 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(td.multiTx1.FromAmount)),
amountIn: (*hexutil.Big)(big.NewInt(td.multiTx1.ToAmount)),
}, entries[4])
// Test end only
@ -336,6 +364,8 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(trs[2].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries[0])
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
@ -345,6 +375,8 @@ func TestGetActivityEntriesFilterByTime(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(td.tr1.Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries[6])
}
@ -381,6 +413,8 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(trs[8].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries[0])
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
@ -390,6 +424,8 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(trs[6].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries[2])
// Move window 2 entries forward
@ -405,6 +441,8 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(trs[6].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries[0])
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
@ -414,6 +452,8 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(trs[4].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries[2])
// Move window 4 more entries to test filter cap
@ -429,6 +469,8 @@ func TestGetActivityEntriesCheckOffsetAndLimit(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(trs[2].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries[0])
}
@ -541,6 +583,8 @@ func TestGetActivityEntriesFilterByAddresses(t *testing.T) {
activityType: ReceiveAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(0)),
amountIn: (*hexutil.Big)(big.NewInt(trs[4].Value)),
}, entries[0])
require.Equal(t, Entry{
payloadType: SimpleTransactionPT,
@ -550,6 +594,8 @@ func TestGetActivityEntriesFilterByAddresses(t *testing.T) {
activityType: SendAT,
activityStatus: CompleteAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(trs[1].Value)),
amountIn: (*hexutil.Big)(big.NewInt(0)),
}, entries[1])
require.Equal(t, Entry{
payloadType: MultiTransactionPT,
@ -559,6 +605,8 @@ func TestGetActivityEntriesFilterByAddresses(t *testing.T) {
activityType: SendAT,
activityStatus: PendingAS,
tokenType: AssetTT,
amountOut: (*hexutil.Big)(big.NewInt(td.multiTx2.FromAmount)),
amountIn: (*hexutil.Big)(big.NewInt(td.multiTx2.ToAmount)),
}, entries[2])
}

View file

@ -9,6 +9,7 @@ import (
eth_common "github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/testutils"
"github.com/status-im/status-go/sqlite"
"github.com/stretchr/testify/require"
)
@ -118,16 +119,18 @@ func InsertTestTransfer(t *testing.T, db *sql.DB, tr *TestTransfer) {
tokenType = "erc20"
}
blkHash := eth_common.HexToHash("4")
value := sqlite.Int64ToPadded128BitsStr(tr.Value)
_, err := db.Exec(`
INSERT OR IGNORE INTO blocks(
network_id, address, blk_number, blk_hash
) VALUES (?, ?, ?, ?);
INSERT INTO transfers (network_id, hash, address, blk_hash, tx,
sender, receipt, log, type, blk_number, timestamp, loaded,
multi_transaction_id, base_gas_fee, status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, 0, ?)`,
multi_transaction_id, base_gas_fee, status, amount_padded128hex
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, 0, ?, ?)`,
tr.ChainID, tr.To, tr.BlkNumber, blkHash,
tr.ChainID, tr.Hash, tr.To, blkHash, &JSONBlob{}, tr.From, &JSONBlob{}, &JSONBlob{}, tokenType, tr.BlkNumber, tr.Timestamp, tr.MultiTransactionID, tr.Success)
tr.ChainID, tr.Hash, tr.To, blkHash, &JSONBlob{}, tr.From, &JSONBlob{}, &JSONBlob{}, tokenType, tr.BlkNumber, tr.Timestamp, tr.MultiTransactionID, tr.Success, value)
require.NoError(t, err)
}

View file

@ -87,3 +87,8 @@ func BigIntToPadded128BitsStr(val *big.Int) *string {
*res = fmt.Sprintf("%032s", hexStr)
return res
}
func Int64ToPadded128BitsStr(val int64) *string {
res := fmt.Sprintf("%032x", val)
return &res
}

View file

@ -57,3 +57,35 @@ func TestBigIntToPadded128BitsStr(t *testing.T) {
})
}
}
func TestInt64ToPadded128BitsStr(t *testing.T) {
testCases := []struct {
name string
input int64
expected *string
}{
{
name: "case nonzero",
input: 123456,
expected: strToPtr("0000000000000000000000000001e240"),
},
{
name: "case zero",
input: 0,
expected: strToPtr("00000000000000000000000000000000"),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := Int64ToPadded128BitsStr(tc.input)
if result != nil && tc.expected != nil {
if *result != *tc.expected {
t.Errorf("expected %s, got %s", *tc.expected, *result)
}
} else if result != nil || tc.expected != nil {
t.Errorf("expected %v, got %v", tc.expected, result)
}
})
}
}