This commit is contained in:
Dario Gabriel Lipicar 2024-09-10 22:19:38 -03:00
parent fb150f3d16
commit 24a2c4cd89
No known key found for this signature in database
GPG Key ID: 9625E9494309D203
48 changed files with 1174 additions and 148 deletions

View File

@ -130,6 +130,7 @@ func (api *API) DeployCollectibles(ctx context.Context, chainID uint64, deployme
transactions.DeployCommunityToken, transactions.DeployCommunityToken,
transactions.Keep, transactions.Keep,
"", "",
tx.To(),
) )
if err != nil { if err != nil {
log.Error("TrackPendingTransaction error", "error", err) log.Error("TrackPendingTransaction error", "error", err)
@ -242,6 +243,7 @@ func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64,
transactions.DeployOwnerToken, transactions.DeployOwnerToken,
transactions.Keep, transactions.Keep,
"", "",
tx.To(),
) )
if err != nil { if err != nil {
log.Error("TrackPendingTransaction error", "error", err) log.Error("TrackPendingTransaction error", "error", err)
@ -305,6 +307,7 @@ func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentPara
transactions.DeployCommunityToken, transactions.DeployCommunityToken,
transactions.Keep, transactions.Keep,
"", "",
tx.To(),
) )
if err != nil { if err != nil {
log.Error("TrackPendingTransaction error", "error", err) log.Error("TrackPendingTransaction error", "error", err)
@ -402,6 +405,7 @@ func (api *API) MintTokens(ctx context.Context, chainID uint64, contractAddress
transactions.AirdropCommunityToken, transactions.AirdropCommunityToken,
transactions.Keep, transactions.Keep,
"", "",
tx.To(),
) )
if err != nil { if err != nil {
log.Error("TrackPendingTransaction error", "error", err) log.Error("TrackPendingTransaction error", "error", err)
@ -469,6 +473,7 @@ func (api *API) RemoteBurn(ctx context.Context, chainID uint64, contractAddress
transactions.RemoteDestructCollectible, transactions.RemoteDestructCollectible,
transactions.Keep, transactions.Keep,
additionalData, additionalData,
tx.To(),
) )
if err != nil { if err != nil {
log.Error("TrackPendingTransaction error", "error", err) log.Error("TrackPendingTransaction error", "error", err)
@ -521,6 +526,7 @@ func (api *API) Burn(ctx context.Context, chainID uint64, contractAddress string
transactions.BurnCommunityToken, transactions.BurnCommunityToken,
transactions.Keep, transactions.Keep,
"", "",
tx.To(),
) )
if err != nil { if err != nil {
log.Error("TrackPendingTransaction error", "error", err) log.Error("TrackPendingTransaction error", "error", err)

View File

@ -526,6 +526,7 @@ func (s *Service) SetSignerPubKey(ctx context.Context, chainID uint64, contractA
transactions.SetSignerPublicKey, transactions.SetSignerPublicKey,
transactions.Keep, transactions.Keep,
"", "",
tx.To(),
) )
if err != nil { if err != nil {
log.Error("TrackPendingTransaction error", "error", err) log.Error("TrackPendingTransaction error", "error", err)
@ -711,17 +712,19 @@ func (s *Service) ReTrackOwnerTokenDeploymentTransaction(ctx context.Context, ch
transactionType = transactions.DeployOwnerToken transactionType = transactions.DeployOwnerToken
} }
deployerAddress := common.HexToAddress(communityToken.Deployer)
_, err = s.pendingTracker.GetPendingEntry(wcommon.ChainID(chainID), common.HexToHash(hashString)) _, err = s.pendingTracker.GetPendingEntry(wcommon.ChainID(chainID), common.HexToHash(hashString))
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
// start only if no pending transaction in database // start only if no pending transaction in database
err = s.pendingTracker.TrackPendingTransaction( err = s.pendingTracker.TrackPendingTransaction(
wcommon.ChainID(chainID), wcommon.ChainID(chainID),
common.HexToHash(hashString), common.HexToHash(hashString),
common.HexToAddress(communityToken.Deployer), deployerAddress,
common.Address{}, common.Address{},
transactionType, transactionType,
transactions.Keep, transactions.Keep,
"", "",
&deployerAddress,
) )
log.Debug("retracking pending transaction with hashId ", hashString) log.Debug("retracking pending transaction with hashId ", hashString)
} else { } else {

View File

@ -361,6 +361,7 @@ func (api *API) Release(ctx context.Context, chainID uint64, txArgs transactions
transactions.ReleaseENS, transactions.ReleaseENS,
transactions.AutoDelete, transactions.AutoDelete,
"", "",
tx.To(),
) )
if err != nil { if err != nil {
log.Error("TrackPendingTransaction error", "error", err) log.Error("TrackPendingTransaction error", "error", err)
@ -490,6 +491,7 @@ func (api *API) Register(ctx context.Context, chainID uint64, txArgs transaction
transactions.RegisterENS, transactions.RegisterENS,
transactions.AutoDelete, transactions.AutoDelete,
"", "",
tx.To(),
) )
if err != nil { if err != nil {
log.Error("TrackPendingTransaction error", "error", err) log.Error("TrackPendingTransaction error", "error", err)
@ -608,6 +610,7 @@ func (api *API) SetPubKey(ctx context.Context, chainID uint64, txArgs transactio
transactions.SetPubKey, transactions.SetPubKey,
transactions.AutoDelete, transactions.AutoDelete,
"", "",
tx.To(),
) )
if err != nil { if err != nil {
log.Error("TrackPendingTransaction error", "error", err) log.Error("TrackPendingTransaction error", "error", err)

View File

@ -67,8 +67,9 @@ type Entry struct {
chainIDOut *common.ChainID chainIDOut *common.ChainID
chainIDIn *common.ChainID chainIDIn *common.ChainID
transferType *TransferType transferType *TransferType
contractAddress *eth.Address deployedContractAddress *eth.Address
communityID *string communityID *string
interactedContractAddress *eth.Address
isNew bool // isNew is used to indicate if the entry is newer than session start (changed state also) isNew bool // isNew is used to indicate if the entry is newer than session start (changed state also)
} }
@ -92,8 +93,9 @@ type EntryData struct {
ChainIDOut *common.ChainID `json:"chainIdOut,omitempty"` ChainIDOut *common.ChainID `json:"chainIdOut,omitempty"`
ChainIDIn *common.ChainID `json:"chainIdIn,omitempty"` ChainIDIn *common.ChainID `json:"chainIdIn,omitempty"`
TransferType *TransferType `json:"transferType,omitempty"` TransferType *TransferType `json:"transferType,omitempty"`
ContractAddress *eth.Address `json:"contractAddress,omitempty"` DeployedContractAddress *eth.Address `json:"contractAddress,omitempty"`
CommunityID *string `json:"communityId,omitempty"` CommunityID *string `json:"communityId,omitempty"`
InteractedContractAddress *eth.Address `json:"interactedContractAddress,omitempty"`
IsNew *bool `json:"isNew,omitempty"` IsNew *bool `json:"isNew,omitempty"`
@ -117,8 +119,9 @@ func (e *Entry) MarshalJSON() ([]byte, error) {
ChainIDOut: e.chainIDOut, ChainIDOut: e.chainIDOut,
ChainIDIn: e.chainIDIn, ChainIDIn: e.chainIDIn,
TransferType: e.transferType, TransferType: e.transferType,
ContractAddress: e.contractAddress, DeployedContractAddress: e.deployedContractAddress,
CommunityID: e.communityID, CommunityID: e.communityID,
InteractedContractAddress: e.interactedContractAddress,
} }
if e.payloadType == MultiTransactionPT { if e.payloadType == MultiTransactionPT {
@ -486,6 +489,7 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
startFilterDisabled, filter.Period.StartTimestamp, endFilterDisabled, filter.Period.EndTimestamp, startFilterDisabled, filter.Period.StartTimestamp, endFilterDisabled, filter.Period.EndTimestamp,
filterActivityTypeAll, sliceContains(filter.Types, SendAT), sliceContains(filter.Types, ReceiveAT), filterActivityTypeAll, sliceContains(filter.Types, SendAT), sliceContains(filter.Types, ReceiveAT),
sliceContains(filter.Types, ContractDeploymentAT), sliceContains(filter.Types, MintAT), sliceContains(filter.Types, ContractDeploymentAT), sliceContains(filter.Types, MintAT),
sliceContains(filter.Types, SwapAT), sliceContains(filter.Types, ApproveAT),
transfer.MultiTransactionSend, transfer.MultiTransactionSend,
fromTrType, toTrType, fromTrType, toTrType,
allAddresses, filterAllToAddresses, allAddresses, filterAllToAddresses,
@ -511,21 +515,36 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
var timestamp int64 var timestamp int64
var dbMtType, dbTrType sql.NullByte var dbMtType, dbTrType sql.NullByte
var toAddress, fromAddress eth.Address var toAddress, fromAddress eth.Address
var toAddressDB, ownerAddressDB, contractAddressDB, dbTokenID sql.RawBytes var toAddressDB, ownerAddressDB, deployedContractAddressDB, dbTokenID sql.RawBytes
var tokenAddress, contractAddress *eth.Address var tokenAddress, deployedContractAddress, interactedContractAddress *eth.Address
var aggregatedStatus int var aggregatedStatus int
var dbTrAmount sql.NullString var dbTrAmount sql.NullString
dbPTrAmount := new(big.Int) dbPTrAmount := new(big.Int)
var dbMtFromAmount, dbMtToAmount, contractType sql.NullString var dbMtFromAmount, dbMtToAmount, dbContractType sql.NullString
var contractType string
var tokenCode, fromTokenCode, toTokenCode sql.NullString var tokenCode, fromTokenCode, toTokenCode sql.NullString
var methodHash, communityID sql.NullString var methodHash, communityID sql.NullString
var transferType *TransferType var transferType *TransferType
var communityMintEventDB sql.NullBool var communityMintEventDB sql.NullBool
var communityMintEvent bool var communityMintEvent bool
var dbInteractedContractAddress sql.RawBytes
// Transaction Input parameters
var dbTrInputApprovalSpender sql.RawBytes
var dbTrInputApprovalAmount = new(big.Int)
var dbTrInputFromAsset sql.NullString
var dbTrInputFromAmount = new(big.Int)
var dbTrInputToAsset sql.NullString
var dbTrInputToAmount = new(big.Int)
var dbTrInputSide sql.NullInt64
var dbTrInputSlippageBps sql.NullInt64
err := rows.Scan(&transferHash, &pendingHash, &chainID, &multiTxID, &timestamp, &dbMtType, &dbTrType, &fromAddress, err := rows.Scan(&transferHash, &pendingHash, &chainID, &multiTxID, &timestamp, &dbMtType, &dbTrType, &fromAddress,
&toAddressDB, &ownerAddressDB, &dbTrAmount, (*bigint.SQLBigIntBytes)(dbPTrAmount), &dbMtFromAmount, &dbMtToAmount, &aggregatedStatus, &aggregatedCount, &toAddressDB, &ownerAddressDB, &dbTrAmount, (*bigint.SQLBigIntBytes)(dbPTrAmount), &dbMtFromAmount, &dbMtToAmount, &aggregatedStatus, &aggregatedCount,
&tokenAddress, &dbTokenID, &tokenCode, &fromTokenCode, &toTokenCode, &outChainIDDB, &inChainIDDB, &contractType, &tokenAddress, &dbTokenID, &tokenCode, &fromTokenCode, &toTokenCode, &outChainIDDB, &inChainIDDB, &dbContractType,
&contractAddressDB, &methodHash, &communityMintEventDB, &communityID) &deployedContractAddressDB, &methodHash, &communityMintEventDB, &communityID,
&dbTrInputApprovalSpender, (*bigint.SQLBigIntBytes)(dbTrInputApprovalAmount), &dbTrInputFromAsset, (*bigint.SQLBigIntBytes)(dbTrInputFromAmount),
&dbTrInputToAsset, (*bigint.SQLBigIntBytes)(dbTrInputToAmount), &dbTrInputSide, &dbTrInputSlippageBps,
&dbInteractedContractAddress)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -534,24 +553,32 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
toAddress = eth.BytesToAddress(toAddressDB) toAddress = eth.BytesToAddress(toAddressDB)
} }
if contractType.Valid { if dbContractType.Valid {
transferType = contractTypeFromDBType(contractType.String) contractType = dbContractType.String
transferType = contractTypeFromDBType(contractType)
} }
if communityMintEventDB.Valid { if communityMintEventDB.Valid {
communityMintEvent = communityMintEventDB.Bool communityMintEvent = communityMintEventDB.Bool
} }
if len(contractAddressDB) > 0 { if len(deployedContractAddressDB) > 0 {
contractAddress = new(eth.Address) deployedContractAddress = new(eth.Address)
*contractAddress = eth.BytesToAddress(contractAddressDB) *deployedContractAddress = eth.BytesToAddress(deployedContractAddressDB)
}
if len(dbInteractedContractAddress) > 0 {
interactedContractAddress := new(eth.Address)
*interactedContractAddress = eth.BytesToAddress(dbInteractedContractAddress)
} }
getActivityType := func(trType sql.NullByte) (activityType Type, filteredAddress eth.Address) { getActivityType := func(trType sql.NullByte) (activityType Type, filteredAddress eth.Address) {
if trType.Valid { if trType.Valid {
if trType.Byte == fromTrType { if trType.Byte == fromTrType {
if toAddress == ZeroAddress && transferType != nil && *transferType == TransferTypeEth && contractAddress != nil && *contractAddress != ZeroAddress { if toAddress == ZeroAddress && transferType != nil && *transferType == TransferTypeEth && deployedContractAddress != nil && *deployedContractAddress != ZeroAddress {
return ContractDeploymentAT, fromAddress return ContractDeploymentAT, fromAddress
} else if contractType == string(common.Erc20Approval) {
return ApproveAT, fromAddress
} }
return SendAT, fromAddress return SendAT, fromAddress
} else if trType.Byte == toTrType { } else if trType.Byte == toTrType {
@ -624,15 +651,12 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
entry.amountIn = inAmount entry.amountIn = inAmount
} else if pendingHash != nil && chainID.Valid { } else if pendingHash != nil && chainID.Valid {
// Process `pending_transactions` row // Process `pending_transactions` row
// Extract activity type: SendAT/ReceiveAT
activityType, _ := getActivityType(dbTrType)
inAmount, outAmount := getTrInAndOutAmounts(activityType, dbTrAmount, dbPTrAmount)
outChainID = new(common.ChainID) outChainID = new(common.ChainID)
*outChainID = common.ChainID(chainID.Int64) *outChainID = common.ChainID(chainID.Int64)
// Extract activity type: SendAT/ReceiveAT/ApproveAT
activityType, _ := getActivityType(dbTrType)
entry = newActivityEntryWithPendingTransaction( entry = newActivityEntryWithPendingTransaction(
&transfer.TransactionIdentity{ChainID: common.ChainID(chainID.Int64), &transfer.TransactionIdentity{ChainID: common.ChainID(chainID.Int64),
Hash: eth.BytesToHash(pendingHash), Hash: eth.BytesToHash(pendingHash),
@ -640,6 +664,25 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
timestamp, activityType, activityStatus, timestamp, activityType, activityStatus,
) )
if activityType == ApproveAT {
// Extract Approve token
if dbTrInputFromAsset.Valid {
entry.tokenOut = deps.tokenFromSymbol(outChainID, dbTrInputFromAsset.String)
entry.symbolOut = common.NewAndSet(dbTrInputFromAsset.String)
}
// Extract Approve amount
if dbTrInputApprovalAmount != nil {
entry.amountOut = (*hexutil.Big)(dbTrInputApprovalAmount)
}
// Extract Approve spender
if len(dbTrInputApprovalSpender) > 0 {
toAddress = eth.BytesToAddress(dbTrInputApprovalSpender)
}
} else {
inAmount, outAmount := getTrInAndOutAmounts(activityType, dbTrAmount, dbPTrAmount)
// Extract tokens // Extract tokens
if tokenCode.Valid { if tokenCode.Valid {
cID := common.ChainID(chainID.Int64) cID := common.ChainID(chainID.Int64)
@ -650,7 +693,7 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
// Complete the data // Complete the data
entry.amountOut = outAmount entry.amountOut = outAmount
entry.amountIn = inAmount entry.amountIn = inAmount
}
} else if multiTxID.Valid { } else if multiTxID.Valid {
// Process `multi_transactions` row // Process `multi_transactions` row
@ -693,12 +736,12 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
} }
// Complete common data // Complete common data
entry.recipient = &toAddress
entry.sender = &fromAddress entry.sender = &fromAddress
entry.recipient = &toAddress entry.recipient = &toAddress
entry.chainIDOut = outChainID entry.chainIDOut = outChainID
entry.chainIDIn = inChainID entry.chainIDIn = inChainID
entry.transferType = transferType entry.transferType = transferType
entry.interactedContractAddress = interactedContractAddress
entries = append(entries, entry) entries = append(entries, entry)
} }

View File

@ -675,12 +675,14 @@ func TestGetActivityEntriesFilterByType(t *testing.T) {
// Add 6 extractable transactions: one MultiTransactionSwap, two MultiTransactionBridge, two MultiTransactionSend and one MultiTransactionApprove // Add 6 extractable transactions: one MultiTransactionSwap, two MultiTransactionBridge, two MultiTransactionSend and one MultiTransactionApprove
multiTxs := make([]transfer.MultiTransaction, 6) multiTxs := make([]transfer.MultiTransaction, 6)
trs, fromAddrs, toAddrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, len(multiTxs)*2) trs, fromAddrs, toAddrs := transfer.GenerateTestTransfers(t, deps.db, td.nextIndex, len(multiTxs)*2)
approveTxs, approveFromAddrs := transfer.GenerateTestApproves(t, deps.db, td.nextIndex+len(trs), 2)
multiTxs[0] = transfer.GenerateTestBridgeMultiTransaction(trs[0], trs[1]) multiTxs[0] = transfer.GenerateTestBridgeMultiTransaction(trs[0], trs[1])
multiTxs[1] = transfer.GenerateTestSwapMultiTransaction(trs[2], testutils.SntSymbol, 100) // trs[3] multiTxs[1] = transfer.GenerateTestSwapMultiTransaction(trs[2], testutils.SntSymbol, 100) // trs[3]
multiTxs[2] = transfer.GenerateTestSendMultiTransaction(trs[4]) // trs[5] multiTxs[2] = transfer.GenerateTestSendMultiTransaction(trs[4]) // trs[5]
multiTxs[3] = transfer.GenerateTestBridgeMultiTransaction(trs[6], trs[7]) multiTxs[3] = transfer.GenerateTestBridgeMultiTransaction(trs[6], trs[7])
multiTxs[4] = transfer.GenerateTestSendMultiTransaction(trs[8]) // trs[9] multiTxs[4] = transfer.GenerateTestSendMultiTransaction(trs[8]) // trs[9]
multiTxs[5] = transfer.GenerateTestApproveMultiTransaction(trs[10]) // trs[11] multiTxs[5] = transfer.GenerateTestApproveMultiTransaction(approveTxs[1])
var lastMT common.MultiTransactionIDType var lastMT common.MultiTransactionIDType
for i := range trs { for i := range trs {
@ -695,6 +697,7 @@ func TestGetActivityEntriesFilterByType(t *testing.T) {
// Here not to include the modified To and From addresses // Here not to include the modified To and From addresses
allAddresses := append(append(append(append(append(tdFromAdds, tdToAddrs...), fromAddrs...), toAddrs...), fromSpecial...), toSpecial...) allAddresses := append(append(append(append(append(tdFromAdds, tdToAddrs...), fromAddrs...), toAddrs...), fromSpecial...), toSpecial...)
allAddresses = append(allAddresses, approveFromAddrs...)
// Insert MintAT Collectible // Insert MintAT Collectible
trsSpecial[0].From = eth.HexToAddress("0x0") trsSpecial[0].From = eth.HexToAddress("0x0")

View File

@ -23,6 +23,8 @@ WITH filter_conditions AS (
? AS filterActivityTypeReceive, ? AS filterActivityTypeReceive,
? AS filterActivityTypeContractDeployment, ? AS filterActivityTypeContractDeployment,
? AS filterActivityTypeMint, ? AS filterActivityTypeMint,
? AS filterActivityTypeSwap,
? AS filterActivityTypeApprove,
? AS mTTypeSend, ? AS mTTypeSend,
? AS fromTrType, ? AS fromTrType,
? AS toTrType, ? AS toTrType,
@ -221,12 +223,22 @@ SELECT
WHEN transfers.type = 'erc20' THEN (SELECT community_id FROM tokens WHERE transfers.token_address = tokens.address AND transfers.network_id = tokens.network_id) WHEN transfers.type = 'erc20' THEN (SELECT community_id FROM tokens WHERE transfers.token_address = tokens.address AND transfers.network_id = tokens.network_id)
WHEN transfers.type = 'erc721' OR transfers.type = 'erc1155' THEN (SELECT community_id FROM collectible_data_cache WHERE transfers.token_address = collectible_data_cache.contract_address AND transfers.network_id = collectible_data_cache.chain_id) WHEN transfers.type = 'erc721' OR transfers.type = 'erc1155' THEN (SELECT community_id FROM collectible_data_cache WHERE transfers.token_address = collectible_data_cache.contract_address AND transfers.network_id = collectible_data_cache.chain_id)
ELSE NULL ELSE NULL
END AS community_id END AS community_id,
transaction_input_data.approval_spender as input_approval_spender,
transaction_input_data.approval_amount as input_approval_amount,
transaction_input_data.from_asset as input_from_asset,
transaction_input_data.from_amount as input_from_amount,
transaction_input_data.to_asset as input_to_asset,
transaction_input_data.to_amount as input_to_amount,
transaction_input_data.side as input_side,
transaction_input_data.slippage_bps as input_slippage_bps,
transfers.transaction_to as interacted_contract_address
FROM FROM
transfers transfers
CROSS JOIN filter_conditions CROSS JOIN filter_conditions
LEFT JOIN filter_addresses from_join ON transfers.tx_from_address = from_join.address LEFT JOIN filter_addresses from_join ON transfers.tx_from_address = from_join.address
LEFT JOIN filter_addresses to_join ON transfers.tx_to_address = to_join.address LEFT JOIN filter_addresses to_join ON transfers.tx_to_address = to_join.address
LEFT JOIN transaction_input_data ON transfers.network_id = transaction_input_data.chain_id AND transfers.tx_hash = transaction_input_data.tx_hash
WHERE WHERE
transfers.loaded == 1 transfers.loaded == 1
AND transfers.multi_transaction_id = 0 AND transfers.multi_transaction_id = 0
@ -273,6 +285,11 @@ WHERE
) )
) )
) )
OR (
filterActivityTypeApprove
AND tr_type = fromTrType
AND transfers.type = 'Erc20Approval'
)
OR ( OR (
filterActivityTypeContractDeployment filterActivityTypeContractDeployment
AND tr_type = fromTrType AND tr_type = fromTrType
@ -391,12 +408,22 @@ SELECT
NULL as contract_address, NULL as contract_address,
NULL AS method_hash, NULL AS method_hash,
NULL AS community_mint_event, NULL AS community_mint_event,
NULL AS community_id NULL AS community_id,
transaction_input_data.approval_spender as input_approval_spender,
transaction_input_data.approval_amount as input_approval_amount,
transaction_input_data.from_asset as input_from_asset,
transaction_input_data.from_amount as input_from_amount,
transaction_input_data.to_asset as input_to_asset,
transaction_input_data.to_amount as input_to_amount,
transaction_input_data.side as input_side,
transaction_input_data.slippage_bps as input_slippage_bps,
pending_transactions.transaction_to as interacted_contract_address
FROM FROM
pending_transactions pending_transactions
CROSS JOIN filter_conditions CROSS JOIN filter_conditions
LEFT JOIN filter_addresses from_join ON pending_transactions.from_address = from_join.address LEFT JOIN filter_addresses from_join ON pending_transactions.from_address = from_join.address
LEFT JOIN filter_addresses to_join ON pending_transactions.to_address = to_join.address LEFT JOIN filter_addresses to_join ON pending_transactions.to_address = to_join.address
LEFT JOIN transaction_input_data ON pending_transactions.network_id = transaction_input_data.chain_id AND pending_transactions.hash = transaction_input_data.tx_hash
WHERE WHERE
pending_transactions.multi_transaction_id = 0 pending_transactions.multi_transaction_id = 0
AND pending_transactions.status = pendingStatus AND pending_transactions.status = pendingStatus
@ -484,7 +511,16 @@ SELECT
NULL as contract_address, NULL as contract_address,
NULL AS method_hash, NULL AS method_hash,
NULL AS community_mint_event, NULL AS community_mint_event,
NULL AS community_id NULL AS community_id,
NULL as input_approval_spender,
NULL as input_approval_amount,
NULL as input_from_asset,
NULL as input_from_amount,
NULL as input_to_asset,
NULL as input_to_amount,
NULL as input_side,
NULL as input_slippage_bps,
NULL as interacted_contract_address
FROM FROM
multi_transactions multi_transactions
CROSS JOIN filter_conditions CROSS JOIN filter_conditions

View File

@ -15,6 +15,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/accounts"
protocolCommon "github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/services/wallet/async" "github.com/status-im/status-go/services/wallet/async"
"github.com/status-im/status-go/services/wallet/collectibles" "github.com/status-im/status-go/services/wallet/collectibles"
w_common "github.com/status-im/status-go/services/wallet/common" w_common "github.com/status-im/status-go/services/wallet/common"
@ -74,13 +75,22 @@ type Service struct {
debounceDuration time.Duration debounceDuration time.Duration
pendingTracker *transactions.PendingTxTracker pendingTracker *transactions.PendingTxTracker
featureFlags *protocolCommon.FeatureFlags
} }
func (s *Service) nextSessionID() SessionID { func (s *Service) nextSessionID() SessionID {
return SessionID(s.lastSessionID.Add(1)) return SessionID(s.lastSessionID.Add(1))
} }
func NewService(db *sql.DB, accountsDB *accounts.Database, tokenManager token.ManagerInterface, collectibles collectibles.ManagerInterface, eventFeed *event.Feed, pendingTracker *transactions.PendingTxTracker) *Service { func NewService(
db *sql.DB,
accountsDB *accounts.Database,
tokenManager token.ManagerInterface,
collectibles collectibles.ManagerInterface,
eventFeed *event.Feed,
pendingTracker *transactions.PendingTxTracker,
featureFlags *protocolCommon.FeatureFlags) *Service {
return &Service{ return &Service{
db: db, db: db,
accountsDB: accountsDB, accountsDB: accountsDB,
@ -94,6 +104,8 @@ func NewService(db *sql.DB, accountsDB *accounts.Database, tokenManager token.Ma
debounceDuration: 1 * time.Second, debounceDuration: 1 * time.Second,
pendingTracker: pendingTracker, pendingTracker: pendingTracker,
featureFlags: featureFlags,
} }
} }

View File

@ -468,7 +468,7 @@ func TestService_IncrementalUpdateOnTop(t *testing.T) {
ChainID: 5, ChainID: 5,
}, newTx.tokenOut) }, newTx.tokenOut)
require.Equal(t, (*Token)(nil), newTx.tokenIn) require.Equal(t, (*Token)(nil), newTx.tokenIn)
require.Equal(t, (*eth.Address)(nil), newTx.contractAddress) require.Equal(t, (*eth.Address)(nil), newTx.deployedContractAddress)
// Check the order of the following transaction data // Check the order of the following transaction data
require.Equal(t, SimpleTransactionPT, payload.Activities[1].payloadType) require.Equal(t, SimpleTransactionPT, payload.Activities[1].payloadType)

View File

@ -29,9 +29,11 @@ const (
UniswapV3Swap Type = "uniswapV3Swap" UniswapV3Swap Type = "uniswapV3Swap"
HopBridgeFrom Type = "HopBridgeFrom" HopBridgeFrom Type = "HopBridgeFrom"
HopBridgeTo Type = "HopBridgeTo" HopBridgeTo Type = "HopBridgeTo"
Erc20Approval Type = "Erc20Approval"
unknownTransaction Type = "unknown" unknownTransaction Type = "unknown"
// Event types // Event types
Erc20ApprovalEventType EventType = "erc20ApprovalEvent"
WETHDepositEventType EventType = "wethDepositEvent" WETHDepositEventType EventType = "wethDepositEvent"
WETHWithdrawalEventType EventType = "wethWithdrawalEvent" WETHWithdrawalEventType EventType = "wethWithdrawalEvent"
Erc20TransferEventType EventType = "erc20Event" Erc20TransferEventType EventType = "erc20Event"
@ -46,6 +48,10 @@ const (
HopBridgeTransferSentEventType EventType = "hopBridgeTransferSentEvent" HopBridgeTransferSentEventType EventType = "hopBridgeTransferSentEvent"
UnknownEventType EventType = "unknownEvent" UnknownEventType EventType = "unknownEvent"
// ERC20: Approval (index_topic_1 address owner, index_topic_2 address spender, uint256 value)
Erc20_721ApprovalEventSignature = "Approval(address,address,uint256)"
erc20ApprovalEventIndexedParameters = 3 // signature, owner, spender
// Deposit (index_topic_1 address dst, uint256 wad) // Deposit (index_topic_1 address dst, uint256 wad)
wethDepositEventSignature = "Deposit(address,uint256)" wethDepositEventSignature = "Deposit(address,uint256)"
// Withdrawal (index_topic_1 address src, uint256 wad) // Withdrawal (index_topic_1 address src, uint256 wad)
@ -83,6 +89,7 @@ var (
// Detect event type for a cetain item from the Events Log // Detect event type for a cetain item from the Events Log
func GetEventType(log *types.Log) EventType { func GetEventType(log *types.Log) EventType {
erc20_721ApprovalEventSignatureHash := GetEventSignatureHash(Erc20_721ApprovalEventSignature)
wethDepositEventSignatureHash := GetEventSignatureHash(wethDepositEventSignature) wethDepositEventSignatureHash := GetEventSignatureHash(wethDepositEventSignature)
wethWithdrawalEventSignatureHash := GetEventSignatureHash(wethWithdrawalEventSignature) wethWithdrawalEventSignatureHash := GetEventSignatureHash(wethWithdrawalEventSignature)
erc20_721TransferEventSignatureHash := GetEventSignatureHash(Erc20_721TransferEventSignature) erc20_721TransferEventSignatureHash := GetEventSignatureHash(Erc20_721TransferEventSignature)
@ -97,6 +104,11 @@ func GetEventType(log *types.Log) EventType {
if len(log.Topics) > 0 { if len(log.Topics) > 0 {
switch log.Topics[0] { switch log.Topics[0] {
case erc20_721ApprovalEventSignatureHash:
switch len(log.Topics) {
case erc20ApprovalEventIndexedParameters:
return Erc20ApprovalEventType
}
case wethDepositEventSignatureHash: case wethDepositEventSignatureHash:
return WETHDepositEventType return WETHDepositEventType
case wethWithdrawalEventSignatureHash: case wethWithdrawalEventSignatureHash:
@ -132,6 +144,8 @@ func GetEventType(log *types.Log) EventType {
func EventTypeToSubtransactionType(eventType EventType) Type { func EventTypeToSubtransactionType(eventType EventType) Type {
switch eventType { switch eventType {
case Erc20ApprovalEventType:
return Erc20Approval
case Erc20TransferEventType: case Erc20TransferEventType:
return Erc20Transfer return Erc20Transfer
case Erc721TransferEventType: case Erc721TransferEventType:
@ -309,6 +323,35 @@ func getFromToAddresses(ethlog types.Log) (from, to common.Address, err error) {
return from, to, fmt.Errorf("unsupported event type to get from/to adddresses %s", eventType) return from, to, fmt.Errorf("unsupported event type to get from/to adddresses %s", eventType)
} }
func ParseErc20ApprovalLog(ethlog *types.Log) (owner, spender common.Address, value *big.Int, err error) {
value = new(big.Int)
if len(ethlog.Topics) < erc20ApprovalEventIndexedParameters {
err = fmt.Errorf("not enough topics for erc20 approval %s, %v", "topics", ethlog.Topics)
log.Error("log_parser::ParseErc20ApprovalLog", "err", err)
return
}
err = checkTopicsLength(*ethlog, 1, erc20ApprovalEventIndexedParameters)
if err != nil {
return
}
addressIdx := common.HashLength - common.AddressLength
copy(owner[:], ethlog.Topics[1][addressIdx:])
copy(spender[:], ethlog.Topics[2][addressIdx:])
if len(ethlog.Data) != common.HashLength {
err = fmt.Errorf("data is not padded to 32 byts big int %s, %v", "data", ethlog.Data)
log.Error("log_parser::ParseErc20ApprovalLog", "err", err)
return
}
value.SetBytes(ethlog.Data[:common.HashLength])
return
}
func ParseTransferLog(ethlog types.Log) (from, to common.Address, txIDs []common.Hash, tokenIDs, values []*big.Int, err error) { func ParseTransferLog(ethlog types.Log) (from, to common.Address, txIDs []common.Hash, tokenIDs, values []*big.Int, err error) {
eventType := GetEventType(&ethlog) eventType := GetEventType(&ethlog)

View File

@ -11,6 +11,8 @@ var (
ZeroBigIntValue = big.NewInt(0) ZeroBigIntValue = big.NewInt(0)
) )
type SwapSide uint8
const ( const (
IncreaseEstimatedGasFactor = 1.1 IncreaseEstimatedGasFactor = 1.1
SevenDaysInSeconds = 60 * 60 * 24 * 7 SevenDaysInSeconds = 60 * 60 * 24 * 7
@ -31,6 +33,9 @@ const (
ProcessorENSReleaseName = "ENSRelease" ProcessorENSReleaseName = "ENSRelease"
ProcessorENSPublicKeyName = "ENSPublicKey" ProcessorENSPublicKeyName = "ENSPublicKey"
ProcessorStickersBuyName = "StickersBuy" ProcessorStickersBuyName = "StickersBuy"
SwapSideBuy SwapSide = 0
SwapSideSell SwapSide = 1
) )
func IsProcessorBridge(name string) bool { func IsProcessorBridge(name string) bool {

View File

@ -47,6 +47,7 @@ var (
ErrPriceTimeout = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-037"), Details: "price timeout"} ErrPriceTimeout = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-037"), Details: "price timeout"}
ErrNotEnoughLiquidity = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-038"), Details: "not enough liquidity"} ErrNotEnoughLiquidity = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-038"), Details: "not enough liquidity"}
ErrPriceImpactTooHigh = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-039"), Details: "price impact too high"} ErrPriceImpactTooHigh = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-039"), Details: "price impact too high"}
ErrFromTokenShouldNotBeNative = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-040"), Details: "from token should not be the native token"}
) )
func createErrorResponse(processorName string, err error) error { func createErrorResponse(processorName string, err error) error {

View File

@ -211,3 +211,18 @@ func (mr *MockPathProcessorClearableMockRecorder) Clear() *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockPathProcessorClearable)(nil).Clear)) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockPathProcessorClearable)(nil).Clear))
} }
// GetTransactionInputData mocks base method.
func (m *MockPathProcessor) GetTransactionInputData(sendArgs *pathprocessor.MultipathProcessorTxArgs) (*pathprocessor.TransactionInputData, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetTransactionInputData", sendArgs)
ret0, _ := ret[0].(*pathprocessor.TransactionInputData)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Send indicates an expected call of Send.
func (mr *MockPathProcessorMockRecorder) GetTransactionInputData(sendArgs interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTransactionInputData", reflect.TypeOf((*MockPathProcessor)(nil).GetTransactionInputData), sendArgs)
}

View File

@ -11,6 +11,7 @@ type MultipathProcessorTxArgs struct {
Name string `json:"bridgeName"` Name string `json:"bridgeName"`
ChainID uint64 ChainID uint64
TransferTx *transactions.SendTxArgs TransferTx *transactions.SendTxArgs
ApproveTx *ApproveTxArgs
HopTx *HopBridgeTxArgs HopTx *HopBridgeTxArgs
CbridgeTx *CelerBridgeTxArgs CbridgeTx *CelerBridgeTxArgs
ERC721TransferTx *ERC721TxArgs ERC721TransferTx *ERC721TxArgs

View File

@ -31,6 +31,8 @@ type PathProcessor interface {
GetContractAddress(params ProcessorInputParams) (common.Address, error) GetContractAddress(params ProcessorInputParams) (common.Address, error)
// BuildTransaction builds the transaction based on MultipathProcessorTxArgs, returns the transaction and the used nonce (lastUsedNonce is -1 if it's the first tx) // BuildTransaction builds the transaction based on MultipathProcessorTxArgs, returns the transaction and the used nonce (lastUsedNonce is -1 if it's the first tx)
BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error)
// GetTransactionInputData returns the input data used to build the transaction
GetTransactionInputData(sendArgs *MultipathProcessorTxArgs) (*TransactionInputData, error)
} }
type PathProcessorClearable interface { type PathProcessorClearable interface {

View File

@ -0,0 +1,12 @@
package pathprocessor
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// Placeholder for Approve transaction until handling gets fully done status-go side
type ApproveTxArgs struct {
ApprovalSpender common.Address `json:"approvalSpender"`
ApprovalAmount *hexutil.Big `json:"approvalAmount"`
}

View File

@ -388,3 +388,7 @@ func (s *CelerBridgeProcessor) CalculateAmountOut(params ProcessorInputParams) (
amountOut, _ := new(big.Int).SetString(amt.EqValueTokenAmt, 10) amountOut, _ := new(big.Int).SetString(amt.EqValueTokenAmt, 10)
return amountOut, nil return amountOut, nil
} }
func (s *CelerBridgeProcessor) GetTransactionInputData(sendArgs *MultipathProcessorTxArgs) (*TransactionInputData, error) {
return nil, nil
}

View File

@ -651,3 +651,7 @@ func (h *HopBridgeProcessor) sendL2BridgeTx(contractAddress common.Address, ethC
return tx, ErrTxForChainNotSupported return tx, ErrTxForChainNotSupported
} }
func (s *HopBridgeProcessor) GetTransactionInputData(sendArgs *MultipathProcessorTxArgs) (*TransactionInputData, error) {
return nil, nil
}

View File

@ -125,3 +125,7 @@ func (s *ENSPublicKeyProcessor) GetContractAddress(params ProcessorInputParams)
} }
return *addr, nil return *addr, nil
} }
func (s *ENSPublicKeyProcessor) GetTransactionInputData(sendArgs *MultipathProcessorTxArgs) (*TransactionInputData, error) {
return nil, nil
}

View File

@ -154,3 +154,7 @@ func (s *ENSRegisterProcessor) CalculateAmountOut(params ProcessorInputParams) (
func (s *ENSRegisterProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { func (s *ENSRegisterProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) {
return snt.ContractAddress(params.FromChain.ChainID) return snt.ContractAddress(params.FromChain.ChainID)
} }
func (s *ENSRegisterProcessor) GetTransactionInputData(sendArgs *MultipathProcessorTxArgs) (*TransactionInputData, error) {
return nil, nil
}

View File

@ -124,3 +124,7 @@ func (s *ENSReleaseProcessor) GetContractAddress(params ProcessorInputParams) (c
} }
return addr, nil return addr, nil
} }
func (s *ENSReleaseProcessor) GetTransactionInputData(sendArgs *MultipathProcessorTxArgs) (*TransactionInputData, error) {
return nil, nil
}

View File

@ -170,3 +170,7 @@ func (s *ERC1155Processor) CalculateAmountOut(params ProcessorInputParams) (*big
func (s *ERC1155Processor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { func (s *ERC1155Processor) GetContractAddress(params ProcessorInputParams) (common.Address, error) {
return params.FromToken.Address, nil return params.FromToken.Address, nil
} }
func (s *ERC1155Processor) GetTransactionInputData(sendArgs *MultipathProcessorTxArgs) (*TransactionInputData, error) {
return nil, nil
}

View File

@ -227,3 +227,7 @@ func (s *ERC721Processor) CalculateAmountOut(params ProcessorInputParams) (*big.
func (s *ERC721Processor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { func (s *ERC721Processor) GetContractAddress(params ProcessorInputParams) (common.Address, error) {
return params.FromToken.Address, nil return params.FromToken.Address, nil
} }
func (s *ERC721Processor) GetTransactionInputData(sendArgs *MultipathProcessorTxArgs) (*TransactionInputData, error) {
return nil, nil
}

View File

@ -146,3 +146,7 @@ func (s *StickersBuyProcessor) CalculateAmountOut(params ProcessorInputParams) (
func (s *StickersBuyProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { func (s *StickersBuyProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) {
return snt.ContractAddress(params.FromChain.ChainID) return snt.ContractAddress(params.FromChain.ChainID)
} }
func (s *StickersBuyProcessor) GetTransactionInputData(sendArgs *MultipathProcessorTxArgs) (*TransactionInputData, error) {
return nil, nil
}

View File

@ -208,15 +208,25 @@ func (s *SwapParaswapProcessor) GetContractAddress(params ProcessorInputParams)
return priceRoute.TokenTransferProxy, nil return priceRoute.TokenTransferProxy, nil
} }
func (s *SwapParaswapProcessor) prepareTransaction(sendArgs *MultipathProcessorTxArgs) error { func (s *SwapParaswapProcessor) getPriceRoute(sendArgs *MultipathProcessorTxArgs) (*paraswap.Route, error) {
slippageBP := uint(sendArgs.SwapTx.SlippagePercentage * 100) // convert to basis points
key := makeKey(sendArgs.SwapTx.ChainID, sendArgs.SwapTx.ChainIDTo, sendArgs.SwapTx.TokenIDFrom, sendArgs.SwapTx.TokenIDTo) key := makeKey(sendArgs.SwapTx.ChainID, sendArgs.SwapTx.ChainIDTo, sendArgs.SwapTx.TokenIDFrom, sendArgs.SwapTx.TokenIDTo)
priceRouteIns, ok := s.priceRoute.Load(key) priceRouteIns, ok := s.priceRoute.Load(key)
if !ok { if !ok {
return ErrPriceRouteNotFound return nil, ErrPriceRouteNotFound
}
return priceRouteIns.(*paraswap.Route), nil
}
func percentageToBasisPoints(percentage float32) uint {
return uint(percentage * 100)
}
func (s *SwapParaswapProcessor) prepareTransaction(sendArgs *MultipathProcessorTxArgs) error {
slippageBP := percentageToBasisPoints(sendArgs.SwapTx.SlippagePercentage)
priceRoute, err := s.getPriceRoute(sendArgs)
if err != nil {
return statusErrors.CreateErrorResponseFromError(err)
} }
priceRoute := priceRouteIns.(*paraswap.Route)
tx, err := s.paraswapClient.BuildTransaction(context.Background(), priceRoute.SrcTokenAddress, priceRoute.SrcTokenDecimals, priceRoute.SrcAmount.Int, tx, err := s.paraswapClient.BuildTransaction(context.Background(), priceRoute.SrcTokenAddress, priceRoute.SrcTokenDecimals, priceRoute.SrcAmount.Int,
priceRoute.DestTokenAddress, priceRoute.DestTokenDecimals, priceRoute.DestAmount.Int, slippageBP, priceRoute.DestTokenAddress, priceRoute.DestTokenDecimals, priceRoute.DestAmount.Int, slippageBP,
@ -284,3 +294,31 @@ func (s *SwapParaswapProcessor) CalculateAmountOut(params ProcessorInputParams)
return destAmount, nil return destAmount, nil
} }
func paraswapSidetoCommon(side paraswap.SwapSide) SwapSide {
if side == paraswap.BuySide {
return SwapSideBuy
}
return SwapSideSell
}
func (s *SwapParaswapProcessor) GetTransactionInputData(sendArgs *MultipathProcessorTxArgs) (*TransactionInputData, error) {
slippageBP := (uint16)(percentageToBasisPoints(sendArgs.SwapTx.SlippagePercentage))
priceRoute, err := s.getPriceRoute(sendArgs)
if err != nil {
return nil, err
}
swapSide := paraswapSidetoCommon(priceRoute.Side)
ret := &TransactionInputData{
ProcessorName: s.Name(),
FromAsset: &sendArgs.SwapTx.TokenIDFrom,
FromAmount: (*hexutil.Big)(priceRoute.SrcAmount.Int),
ToAsset: &sendArgs.SwapTx.TokenIDTo,
ToAmount: (*hexutil.Big)(priceRoute.DestAmount.Int),
Side: &swapSide,
SlippageBps: &slippageBP,
}
return ret, nil
}

View File

@ -128,3 +128,18 @@ func (s *TransferProcessor) CalculateAmountOut(params ProcessorInputParams) (*bi
func (s *TransferProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { func (s *TransferProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) {
return common.Address{}, nil return common.Address{}, nil
} }
func (s *TransferProcessor) GetTransactionInputData(sendArgs *MultipathProcessorTxArgs) (*TransactionInputData, error) {
if sendArgs.ApproveTx == nil {
return nil, nil
}
ret := &TransactionInputData{
ProcessorName: s.Name(),
FromAsset: &sendArgs.TransferTx.Symbol,
ApprovalAmount: sendArgs.ApproveTx.ApprovalAmount,
ApprovalSpender: &sendArgs.ApproveTx.ApprovalSpender,
}
return ret, nil
}

View File

@ -0,0 +1,28 @@
package pathprocessor
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
type TransactionInputData struct {
ProcessorName string
FromAsset *string
FromAmount *hexutil.Big
ToAsset *string
ToAmount *hexutil.Big
Side *SwapSide
SlippageBps *uint16
ApprovalAmount *hexutil.Big
ApprovalSpender *common.Address
}
func NewInputData() *TransactionInputData {
return &TransactionInputData{
FromAmount: (*hexutil.Big)(new(big.Int)),
ToAmount: (*hexutil.Big)(new(big.Int)),
ApprovalAmount: (*hexutil.Big)(new(big.Int)),
}
}

View File

@ -0,0 +1,110 @@
package pathprocessor
import (
"database/sql"
sq "github.com/Masterminds/squirrel"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/services/wallet/bigint"
w_common "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/sqlite"
)
type TransactionInputDataDB struct {
db *sql.DB
}
func NewTransactionInputDataDB(db *sql.DB) *TransactionInputDataDB {
return &TransactionInputDataDB{
db: db,
}
}
func (iDB *TransactionInputDataDB) UpsertInputData(chainID w_common.ChainID, txHash types.Hash, inputData TransactionInputData) error {
return upsertInputData(iDB.db, chainID, txHash, inputData)
}
func (iDB *TransactionInputDataDB) ReadInputData(chainID w_common.ChainID, txHash types.Hash) (*TransactionInputData, error) {
return readInputData(iDB.db, chainID, txHash)
}
func upsertInputData(creator sqlite.StatementCreator, chainID w_common.ChainID, txHash types.Hash, inputData TransactionInputData) error {
q := sq.Replace("transaction_input_data").
SetMap(sq.Eq{
"chain_id": chainID,
"tx_hash": txHash.Bytes(),
"processor_name": inputData.ProcessorName,
"from_asset": inputData.FromAsset,
"from_amount": (*bigint.SQLBigIntBytes)(inputData.FromAmount),
"to_asset": inputData.ToAsset,
"to_amount": (*bigint.SQLBigIntBytes)(inputData.ToAmount),
"side": inputData.Side,
"slippage_bps": inputData.SlippageBps,
"approval_amount": (*bigint.SQLBigIntBytes)(inputData.ApprovalAmount),
"approval_spender": inputData.ApprovalSpender,
})
query, args, err := q.ToSql()
if err != nil {
return err
}
stmt, err := creator.Prepare(query)
if err != nil {
return err
}
defer stmt.Close()
_, err = stmt.Exec(args...)
return err
}
func readInputData(creator sqlite.StatementCreator, chainID w_common.ChainID, txHash types.Hash) (*TransactionInputData, error) {
q := sq.Select(
"processor_name",
"from_asset",
"from_amount",
"to_asset",
"to_amount",
"side",
"slippage_bps",
"approval_amount",
"approval_spender",
).
From("transaction_input_data").
Where(sq.Eq{"chain_id": chainID, "tx_hash": txHash.Bytes()})
query, args, err := q.ToSql()
if err != nil {
return nil, err
}
stmt, err := creator.Prepare(query)
if err != nil {
return nil, err
}
defer stmt.Close()
inputData := NewInputData()
err = stmt.QueryRow(args...).Scan(
&inputData.ProcessorName,
&inputData.FromAsset,
(*bigint.SQLBigIntBytes)(inputData.FromAmount),
&inputData.ToAsset,
(*bigint.SQLBigIntBytes)(inputData.ToAmount),
&inputData.Side,
&inputData.SlippageBps,
(*bigint.SQLBigIntBytes)(inputData.ApprovalAmount),
&inputData.ApprovalSpender,
)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}
return inputData, nil
}

View File

@ -0,0 +1,95 @@
package pathprocessor
import (
"fmt"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/google/uuid"
"github.com/status-im/status-go/eth-node/types"
w_common "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/walletdatabase"
"github.com/stretchr/testify/require"
)
func setupTxInputDataDBTest(t *testing.T) (*TransactionInputDataDB, func()) {
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err)
return NewTransactionInputDataDB(db), func() {
require.NoError(t, db.Close())
}
}
type testTxInputData struct {
ChainID w_common.ChainID
TxHash types.Hash
InputData TransactionInputData
}
func generateTestTxInputData(offset int, count int) []testTxInputData {
ret := make([]testTxInputData, 0, count)
for i := offset; i < offset+count; i++ {
inputData := NewInputData()
inputDataIdx := i % 3
inputData.ProcessorName = fmt.Sprintf("processor_name_%d", inputDataIdx)
fromAsset := fmt.Sprintf("from_asset_%d", inputDataIdx)
fromAmount := new(big.Int).SetInt64(int64(inputDataIdx))
toAsset := fmt.Sprintf("to_asset_%d", inputDataIdx)
toAmount := new(big.Int).SetInt64(int64(inputDataIdx * 2))
side := SwapSide(i % 2)
slippageBps := uint16(i % 100)
approvalAmount := new(big.Int).SetInt64(int64(inputDataIdx * 3))
approvalSpender := common.HexToAddress(fmt.Sprintf("0x%d", inputDataIdx*4))
switch inputDataIdx {
case 0:
inputData.FromAsset = &fromAsset
inputData.FromAmount = (*hexutil.Big)(fromAmount)
case 1:
inputData.FromAsset = &fromAsset
inputData.FromAmount = (*hexutil.Big)(fromAmount)
inputData.ToAsset = &toAsset
inputData.ToAmount = (*hexutil.Big)(toAmount)
inputData.Side = &side
inputData.SlippageBps = &slippageBps
case 2:
inputData.FromAsset = &fromAsset
inputData.FromAmount = (*hexutil.Big)(fromAmount)
inputData.ApprovalAmount = (*hexutil.Big)(approvalAmount)
inputData.ApprovalSpender = &approvalSpender
}
testInputData := testTxInputData{
ChainID: w_common.ChainID(i % 3),
TxHash: types.HexToHash(uuid.New().String()),
InputData: *inputData,
}
ret = append(ret, testInputData)
}
return ret
}
func TestUpsertTxInputData(t *testing.T) {
iDB, cleanup := setupTxInputDataDBTest(t)
defer cleanup()
testData := generateTestTxInputData(0, 15)
for _, data := range testData {
err := iDB.UpsertInputData(data.ChainID, data.TxHash, data.InputData)
require.NoError(t, err)
}
for _, data := range testData {
readData, err := iDB.ReadInputData(data.ChainID, data.TxHash)
require.NoError(t, err)
require.NotNil(t, readData)
require.Equal(t, data.InputData, *readData)
}
}

View File

@ -30,6 +30,7 @@ import (
"github.com/status-im/status-go/services/wallet/history" "github.com/status-im/status-go/services/wallet/history"
"github.com/status-im/status-go/services/wallet/market" "github.com/status-im/status-go/services/wallet/market"
"github.com/status-im/status-go/services/wallet/onramp" "github.com/status-im/status-go/services/wallet/onramp"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
"github.com/status-im/status-go/services/wallet/thirdparty" "github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/thirdparty/alchemy" "github.com/status-im/status-go/services/wallet/thirdparty/alchemy"
"github.com/status-im/status-go/services/wallet/thirdparty/coingecko" "github.com/status-im/status-go/services/wallet/thirdparty/coingecko"
@ -112,7 +113,16 @@ func NewService(
cryptoOnRampManager := onramp.NewManager(cryptoOnRampProviders) cryptoOnRampManager := onramp.NewManager(cryptoOnRampProviders)
savedAddressesManager := &SavedAddressesManager{db: db} savedAddressesManager := &SavedAddressesManager{db: db}
transactionManager := transfer.NewTransactionManager(transfer.NewMultiTransactionDB(db), gethManager, transactor, config, accountsDB, pendingTxManager, feed) transactionManager := transfer.NewTransactionManager(
transfer.NewMultiTransactionDB(db),
pathprocessor.NewTransactionInputDataDB(db),
gethManager,
transactor,
config,
accountsDB,
pendingTxManager,
feed,
)
blockChainState := blockchainstate.NewBlockChainState() blockChainState := blockchainstate.NewBlockChainState()
transferController := transfer.NewTransferController(db, accountsDB, rpcClient, accountFeed, feed, transactionManager, pendingTxManager, transferController := transfer.NewTransferController(db, accountsDB, rpcClient, accountFeed, feed, transactionManager, pendingTxManager,
tokenManager, balanceCacher, blockChainState) tokenManager, balanceCacher, blockChainState)
@ -181,12 +191,7 @@ func NewService(
) )
collectibles := collectibles.NewService(db, feed, accountsDB, accountFeed, settingsFeed, communityManager, rpcClient.NetworkManager, collectiblesManager) collectibles := collectibles.NewService(db, feed, accountsDB, accountFeed, settingsFeed, communityManager, rpcClient.NetworkManager, collectiblesManager)
activity := activity.NewService(db, accountsDB, tokenManager, collectiblesManager, feed, pendingTxManager) activity := activity.NewService(db, accountsDB, tokenManager, collectiblesManager, feed, pendingTxManager, featureFlags)
featureFlags := &protocolCommon.FeatureFlags{}
if config.WalletConfig.EnableCelerBridge {
featureFlags.EnableCelerBridge = true
}
return &Service{ return &Service{
db: db, db: db,

View File

@ -37,6 +37,7 @@ import (
"github.com/status-im/status-go/services/wallet/balance" "github.com/status-im/status-go/services/wallet/balance"
"github.com/status-im/status-go/services/wallet/blockchainstate" "github.com/status-im/status-go/services/wallet/blockchainstate"
"github.com/status-im/status-go/services/wallet/community" "github.com/status-im/status-go/services/wallet/community"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
"github.com/status-im/status-go/t/helpers" "github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/t/utils" "github.com/status-im/status-go/t/utils"
@ -1322,7 +1323,7 @@ func TestFetchTransfersForLoadedBlocks(t *testing.T) {
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err) require.NoError(t, err)
tm := &TransactionManager{NewMultiTransactionDB(db), nil, nil, nil, nil, nil, nil, nil, nil, nil} tm := &TransactionManager{NewMultiTransactionDB(db), pathprocessor.NewTransactionInputDataDB(db), nil, nil, nil, nil, nil, nil, nil, nil, nil}
mediaServer, err := server.NewMediaServer(appdb, nil, nil, db) mediaServer, err := server.NewMediaServer(appdb, nil, nil, db)
require.NoError(t, err) require.NoError(t, err)

View File

@ -18,6 +18,7 @@ import (
"github.com/status-im/status-go/services/accounts/accountsevent" "github.com/status-im/status-go/services/accounts/accountsevent"
"github.com/status-im/status-go/services/wallet/blockchainstate" "github.com/status-im/status-go/services/wallet/blockchainstate"
wallet_common "github.com/status-im/status-go/services/wallet/common" wallet_common "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
"github.com/status-im/status-go/t/helpers" "github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/walletdatabase" "github.com/status-im/status-go/walletdatabase"
) )
@ -36,7 +37,7 @@ func TestController_watchAccountsChanges(t *testing.T) {
bcstate := blockchainstate.NewBlockChainState() bcstate := blockchainstate.NewBlockChainState()
SetMultiTransactionIDGenerator(StaticIDCounter()) // to have different multi-transaction IDs even with fast execution SetMultiTransactionIDGenerator(StaticIDCounter()) // to have different multi-transaction IDs even with fast execution
transactionManager := NewTransactionManager(NewInMemMultiTransactionStorage(), nil, nil, nil, accountsDB, nil, nil) transactionManager := NewTransactionManager(NewInMemMultiTransactionStorage(), NewInMemTransactionInputDataStorage(), nil, nil, nil, accountsDB, nil, nil)
c := NewTransferController( c := NewTransferController(
walletDB, walletDB,
accountsDB, accountsDB,
@ -239,7 +240,7 @@ func TestController_cleanupAccountLeftovers(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Len(t, storedAccs, 1) require.Len(t, storedAccs, 1)
transactionManager := NewTransactionManager(NewMultiTransactionDB(walletDB), nil, nil, nil, accountsDB, nil, nil) transactionManager := NewTransactionManager(NewMultiTransactionDB(walletDB), pathprocessor.NewTransactionInputDataDB(walletDB), nil, nil, nil, accountsDB, nil, nil)
bcstate := blockchainstate.NewBlockChainState() bcstate := blockchainstate.NewBlockChainState()
c := NewTransferController( c := NewTransferController(
walletDB, walletDB,

View File

@ -359,8 +359,23 @@ func updateOrInsertTransfers(chainID uint64, creator statementCreator, transfers
var tokenID *big.Int var tokenID *big.Int
var txFrom *common.Address var txFrom *common.Address
var txTo *common.Address var txTo *common.Address
var transactionTo *common.Address
var eventLogAddress *common.Address
if t.Transaction != nil { if t.Transaction != nil {
if t.Log != nil { if t.Log != nil {
eventType := w_common.GetEventType(t.Log)
switch eventType {
case w_common.Erc20ApprovalEventType:
tokenAddress = &t.Log.Address
owner, spender, value, err := w_common.ParseErc20ApprovalLog(t.Log)
if err != nil {
log.Error("can't parse erc20 approval log", "err", err)
continue
}
txFrom = &owner
txTo = &spender
txValue = value
default:
_, tokenAddress, txFrom, txTo = w_common.ExtractTokenTransferData(t.Type, t.Log, t.Transaction) _, tokenAddress, txFrom, txTo = w_common.ExtractTokenTransferData(t.Type, t.Log, t.Transaction)
tokenID = t.TokenID tokenID = t.TokenID
// Zero tokenID can be used for ERC721 and ERC1155 transfers but when serialzed/deserialized it becomes nil // Zero tokenID can be used for ERC721 and ERC1155 transfers but when serialzed/deserialized it becomes nil
@ -369,6 +384,8 @@ func updateOrInsertTransfers(chainID uint64, creator statementCreator, transfers
tokenID = big.NewInt(0) tokenID = big.NewInt(0)
} }
txValue = t.TokenValue txValue = t.TokenValue
}
eventLogAddress = &t.Log.Address
} else { } else {
txValue = new(big.Int).Set(t.Transaction.Value()) txValue = new(big.Int).Set(t.Transaction.Value())
txFrom = &t.From txFrom = &t.From
@ -388,6 +405,7 @@ func updateOrInsertTransfers(chainID uint64, creator statementCreator, transfers
*txNonce = t.Transaction.Nonce() *txNonce = t.Transaction.Nonce()
txSize = new(uint64) txSize = new(uint64)
*txSize = t.Transaction.Size() *txSize = t.Transaction.Size()
transactionTo = t.Transaction.To()
} }
dbFields := transferDBFields{ dbFields := transferDBFields{
@ -426,6 +444,8 @@ func updateOrInsertTransfers(chainID uint64, creator statementCreator, transfers
tokenID: tokenID, tokenID: tokenID,
txFrom: txFrom, txFrom: txFrom,
txTo: txTo, txTo: txTo,
transactionTo: transactionTo,
eventLogAddress: eventLogAddress,
} }
txsDBFields = append(txsDBFields, dbFields) txsDBFields = append(txsDBFields, dbFields)
} }
@ -469,15 +489,18 @@ type transferDBFields struct {
tokenID *big.Int tokenID *big.Int
txFrom *common.Address txFrom *common.Address
txTo *common.Address txTo *common.Address
transactionTo *common.Address
eventLogAddress *common.Address
} }
func updateOrInsertTransfersDBFields(creator statementCreator, transfers []transferDBFields) error { func updateOrInsertTransfersDBFields(creator statementCreator, transfers []transferDBFields) error {
insert, err := creator.Prepare(`INSERT OR REPLACE INTO transfers insert, err := creator.Prepare(`INSERT OR REPLACE INTO transfers
(network_id, hash, blk_hash, blk_number, timestamp, address, tx, sender, receipt, log, type, loaded, base_gas_fee, multi_transaction_id, (network_id, hash, blk_hash, blk_number, timestamp, address, tx, sender, receipt, log, type, loaded, base_gas_fee, multi_transaction_id,
status, receipt_type, tx_hash, log_index, block_hash, cumulative_gas_used, contract_address, gas_used, tx_index, status, receipt_type, tx_hash, log_index, block_hash, cumulative_gas_used, contract_address, gas_used, tx_index,
tx_type, protected, gas_limit, gas_price_clamped64, gas_tip_cap_clamped64, gas_fee_cap_clamped64, amount_padded128hex, account_nonce, size, token_address, token_id, tx_from_address, tx_to_address) tx_type, protected, gas_limit, gas_price_clamped64, gas_tip_cap_clamped64, gas_fee_cap_clamped64, amount_padded128hex, account_nonce, size, token_address, token_id, tx_from_address, tx_to_address,
transaction_to, event_log_address)
VALUES VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`) (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
if err != nil { if err != nil {
return err return err
} }
@ -489,7 +512,8 @@ func updateOrInsertTransfersDBFields(creator statementCreator, transfers []trans
_, err = insert.Exec(t.chainID, t.id, t.blockHash, (*bigint.SQLBigInt)(t.blockNumber), t.timestamp, t.address, &JSONBlob{t.transaction}, t.sender, &JSONBlob{t.receipt}, &JSONBlob{t.log}, t.transferType, t.baseGasFees, t.multiTransactionID, _, err = insert.Exec(t.chainID, t.id, t.blockHash, (*bigint.SQLBigInt)(t.blockNumber), t.timestamp, t.address, &JSONBlob{t.transaction}, t.sender, &JSONBlob{t.receipt}, &JSONBlob{t.log}, t.transferType, t.baseGasFees, t.multiTransactionID,
t.receiptStatus, t.receiptType, t.txHash, t.logIndex, t.receiptBlockHash, t.cumulativeGasUsed, t.contractAddress, t.gasUsed, t.transactionIndex, t.receiptStatus, t.receiptType, t.txHash, t.logIndex, t.receiptBlockHash, t.cumulativeGasUsed, t.contractAddress, t.gasUsed, t.transactionIndex,
t.txType, t.txProtected, t.txGas, txGasPrice, txGasTipCap, txGasFeeCap, txValue, t.txNonce, t.txSize, t.tokenAddress, (*bigint.SQLBigIntBytes)(t.tokenID), t.txFrom, t.txTo) t.txType, t.txProtected, t.txGas, txGasPrice, txGasTipCap, txGasFeeCap, txValue, t.txNonce, t.txSize, t.tokenAddress, (*bigint.SQLBigIntBytes)(t.tokenID), t.txFrom, t.txTo,
t.transactionTo, t.eventLogAddress)
if err != nil { if err != nil {
log.Error("can't save transfer", "b-hash", t.blockHash, "b-n", t.blockNumber, "a", t.address, "h", t.id) log.Error("can't save transfer", "b-hash", t.blockHash, "b-n", t.blockNumber, "a", t.address, "h", t.id)
return err return err

View File

@ -434,7 +434,8 @@ func (d *ETHDownloader) subTransactionsFromTransactionData(address, from common.
for _, txLog := range receipt.Logs { for _, txLog := range receipt.Logs {
eventType := w_common.GetEventType(txLog) eventType := w_common.GetEventType(txLog)
switch eventType { switch eventType {
case w_common.UniswapV2SwapEventType, w_common.UniswapV3SwapEventType, case w_common.Erc20ApprovalEventType,
w_common.UniswapV2SwapEventType, w_common.UniswapV3SwapEventType,
w_common.HopBridgeTransferSentToL2EventType, w_common.HopBridgeTransferFromL1CompletedEventType, w_common.HopBridgeTransferSentToL2EventType, w_common.HopBridgeTransferFromL1CompletedEventType,
w_common.HopBridgeWithdrawalBondedEventType, w_common.HopBridgeTransferSentEventType: w_common.HopBridgeWithdrawalBondedEventType, w_common.HopBridgeTransferSentEventType:
transfer := Transfer{ transfer := Transfer{

View File

@ -154,13 +154,16 @@ func updateDataFromMultiTx(data []*pathprocessor.MultipathProcessorTxArgs, multi
} }
} }
func sendTransactions(data []*pathprocessor.MultipathProcessorTxArgs, pathProcessors map[string]pathprocessor.PathProcessor, account *account.SelectedExtKey) ( func sendTransactions(
data []*pathprocessor.MultipathProcessorTxArgs,
pathProcessors map[string]pathprocessor.PathProcessor,
account *account.SelectedExtKey,
txInputDataStorage TransactionInputDataStorage) (
map[uint64][]types.Hash, error) { map[uint64][]types.Hash, error) {
hashes := make(map[uint64][]types.Hash) hashes := make(map[uint64][]types.Hash)
usedNonces := make(map[uint64]int64) usedNonces := make(map[uint64]int64)
for _, tx := range data { for _, tx := range data {
lastUsedNonce := int64(-1) lastUsedNonce := int64(-1)
if nonce, ok := usedNonces[tx.ChainID]; ok { if nonce, ok := usedNonces[tx.ChainID]; ok {
lastUsedNonce = nonce lastUsedNonce = nonce
@ -171,6 +174,18 @@ func sendTransactions(data []*pathprocessor.MultipathProcessorTxArgs, pathProces
return nil, err // TODO: One of transfers within transaction could have been sent. Need to notify user about it return nil, err // TODO: One of transfers within transaction could have been sent. Need to notify user about it
} }
// Store transaction input data
txInputData, err := pathProcessors[tx.Name].GetTransactionInputData(tx)
if err != nil {
return nil, err
}
if txInputData != nil {
err = txInputDataStorage.UpsertInputData(wallet_common.ChainID(tx.ChainID), hash, *txInputData)
if err != nil {
return nil, err
}
}
hashes[tx.ChainID] = append(hashes[tx.ChainID], hash) hashes[tx.ChainID] = append(hashes[tx.ChainID], hash)
usedNonces[tx.ChainID] = int64(usedNonce) usedNonces[tx.ChainID] = int64(usedNonce)
} }

View File

@ -9,9 +9,12 @@ import (
eth_common "github.com/ethereum/go-ethereum/common" eth_common "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
eth_types "github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/services/wallet/bigint" "github.com/status-im/status-go/services/wallet/bigint"
"github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/common"
wallet_common "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
"github.com/status-im/status-go/services/wallet/testutils" "github.com/status-im/status-go/services/wallet/testutils"
"github.com/status-im/status-go/services/wallet/token" "github.com/status-im/status-go/services/wallet/token"
@ -42,6 +45,13 @@ type TestCollectibleTransfer struct {
TestCollectible TestCollectible
} }
type TestApprove struct {
TestTransaction
Spender eth_common.Address // [address]
Amount int64
Token *token.Token
}
func SeedToToken(seed int) *token.Token { func SeedToToken(seed int) *token.Token {
tokenIndex := seed % len(TestTokens) tokenIndex := seed % len(TestTokens)
return TestTokens[tokenIndex] return TestTokens[tokenIndex]
@ -106,6 +116,17 @@ func generateTestCollectibleTransfer(seed int) TestCollectibleTransfer {
return tr return tr
} }
func generateTestApprove(seed int) TestApprove {
tokenIndex := seed % len(TestTokens)
token := TestTokens[tokenIndex]
return TestApprove{
TestTransaction: generateTestTransaction(seed),
Spender: eth_common.HexToAddress(fmt.Sprintf("0x3%d", seed)),
Amount: int64(seed),
Token: token,
}
}
func GenerateTestSendMultiTransaction(tr TestTransfer) MultiTransaction { func GenerateTestSendMultiTransaction(tr TestTransfer) MultiTransaction {
return MultiTransaction{ return MultiTransaction{
ID: multiTransactionIDGenerator(), ID: multiTransactionIDGenerator(),
@ -148,15 +169,15 @@ func GenerateTestBridgeMultiTransaction(fromTr, toTr TestTransfer) MultiTransact
} }
} }
func GenerateTestApproveMultiTransaction(tr TestTransfer) MultiTransaction { func GenerateTestApproveMultiTransaction(tr TestApprove) MultiTransaction {
return MultiTransaction{ return MultiTransaction{
ID: multiTransactionIDGenerator(), ID: multiTransactionIDGenerator(),
Type: MultiTransactionApprove, Type: MultiTransactionApprove,
FromAddress: tr.From, FromAddress: tr.From,
ToAddress: tr.To, ToAddress: tr.Spender,
FromAsset: tr.Token.Symbol, FromAsset: tr.Token.Symbol,
ToAsset: tr.Token.Symbol, ToAsset: tr.Token.Symbol,
FromAmount: (*hexutil.Big)(big.NewInt(tr.Value)), FromAmount: (*hexutil.Big)(big.NewInt(tr.Amount)),
ToAmount: (*hexutil.Big)(big.NewInt(0)), ToAmount: (*hexutil.Big)(big.NewInt(0)),
Timestamp: uint64(tr.Timestamp), Timestamp: uint64(tr.Timestamp),
} }
@ -174,6 +195,17 @@ func GenerateTestTransfers(tb testing.TB, db *sql.DB, firstStartIndex int, count
return return
} }
// GenerateTesApproves will generate transaction based on the TestTokens index and roll over if there are more than
// len(TestTokens) transactions
func GenerateTestApproves(tb testing.TB, db *sql.DB, firstStartIndex int, count int) (result []TestApprove, fromAddresses []eth_common.Address) {
for i := firstStartIndex; i < (firstStartIndex + count); i++ {
tr := generateTestApprove(i)
fromAddresses = append(fromAddresses, tr.From)
result = append(result, tr)
}
return
}
type TestCollectible struct { type TestCollectible struct {
TokenAddress eth_common.Address TokenAddress eth_common.Address
TokenID *big.Int TokenID *big.Int
@ -361,6 +393,14 @@ func InsertTestTransferWithOptions(tb testing.TB, db *sql.DB, address eth_common
} }
} }
var transactionTo *eth_common.Address
if opt.Tx != nil {
transactionTo = opt.Tx.To()
}
var eventLogAddress *eth_common.Address
if opt.Receipt != nil {
eventLogAddress = &opt.Receipt.Logs[0].Address
}
transfer := transferDBFields{ transfer := transferDBFields{
chainID: uint64(tr.ChainID), chainID: uint64(tr.ChainID),
id: tr.Hash, id: tr.Hash,
@ -383,6 +423,8 @@ func InsertTestTransferWithOptions(tb testing.TB, db *sql.DB, address eth_common
tokenID: opt.TokenID, tokenID: opt.TokenID,
transaction: opt.Tx, transaction: opt.Tx,
receipt: opt.Receipt, receipt: opt.Receipt,
transactionTo: transactionTo,
eventLogAddress: eventLogAddress,
} }
err = updateOrInsertTransfersDBFields(tx, []transferDBFields{transfer}) err = updateOrInsertTransfersDBFields(tx, []transferDBFields{transfer})
require.NoError(tb, err) require.NoError(tb, err)
@ -391,9 +433,9 @@ func InsertTestTransferWithOptions(tb testing.TB, db *sql.DB, address eth_common
func InsertTestPendingTransaction(tb testing.TB, db *sql.DB, tr *TestTransfer) { func InsertTestPendingTransaction(tb testing.TB, db *sql.DB, tr *TestTransfer) {
_, err := db.Exec(` _, err := db.Exec(`
INSERT INTO pending_transactions (network_id, hash, timestamp, from_address, to_address, INSERT INTO pending_transactions (network_id, hash, timestamp, from_address, to_address,
symbol, gas_price, gas_limit, value, data, type, additional_data, multi_transaction_id symbol, gas_price, gas_limit, value, data, type, additional_data, multi_transaction_id, transaction_to,
) VALUES (?, ?, ?, ?, ?, 'ETH', 0, 0, ?, '', 'eth', '', ?)`, ) VALUES (?, ?, ?, ?, ?, 'ETH', 0, 0, ?, '', 'eth', '', ?, ?)`,
tr.ChainID, tr.Hash, tr.Timestamp, tr.From, tr.To, (*bigint.SQLBigIntBytes)(big.NewInt(tr.Value)), tr.MultiTransactionID) tr.ChainID, tr.Hash, tr.Timestamp, tr.From, tr.To, (*bigint.SQLBigIntBytes)(big.NewInt(tr.Value)), tr.MultiTransactionID, tr.Token.Address)
require.NoError(tb, err) require.NoError(tb, err)
} }
@ -499,3 +541,19 @@ func (s *InMemMultiTransactionStorage) ReadMultiTransactions(details *MultiTxDet
} }
return multiTxs, nil return multiTxs, nil
} }
type InMemTransactionInputDataStorage struct {
storage map[string]*pathprocessor.TransactionInputData
}
func NewInMemTransactionInputDataStorage() *InMemTransactionInputDataStorage {
return &InMemTransactionInputDataStorage{
storage: make(map[string]*pathprocessor.TransactionInputData),
}
}
func (s *InMemTransactionInputDataStorage) UpsertInputData(chainID wallet_common.ChainID, txHash eth_types.Hash, inputData pathprocessor.TransactionInputData) error {
key := fmt.Sprintf("%d-%s", chainID, txHash.String())
s.storage[key] = &inputData
return nil
}

View File

@ -35,6 +35,7 @@ type TransactionDescription struct {
type TransactionManager struct { type TransactionManager struct {
storage MultiTransactionStorage storage MultiTransactionStorage
txInputDataStorage TransactionInputDataStorage
gethManager *account.GethManager gethManager *account.GethManager
transactor transactions.TransactorIface transactor transactions.TransactorIface
config *params.NodeConfig config *params.NodeConfig
@ -54,8 +55,13 @@ type MultiTransactionStorage interface {
DeleteMultiTransaction(id wallet_common.MultiTransactionIDType) error DeleteMultiTransaction(id wallet_common.MultiTransactionIDType) error
} }
type TransactionInputDataStorage interface {
UpsertInputData(chainID wallet_common.ChainID, txHash types.Hash, inputData pathprocessor.TransactionInputData) error
}
func NewTransactionManager( func NewTransactionManager(
storage MultiTransactionStorage, storage MultiTransactionStorage,
txInputDataStorage TransactionInputDataStorage,
gethManager *account.GethManager, gethManager *account.GethManager,
transactor transactions.TransactorIface, transactor transactions.TransactorIface,
config *params.NodeConfig, config *params.NodeConfig,
@ -65,6 +71,7 @@ func NewTransactionManager(
) *TransactionManager { ) *TransactionManager {
return &TransactionManager{ return &TransactionManager{
storage: storage, storage: storage,
txInputDataStorage: txInputDataStorage,
gethManager: gethManager, gethManager: gethManager,
transactor: transactor, transactor: transactor,
config: config, config: config,

View File

@ -90,7 +90,7 @@ func (tm *TransactionManager) SendTransactionForSigningToKeycard(ctx context.Con
func (tm *TransactionManager) SendTransactions(ctx context.Context, multiTransaction *MultiTransaction, data []*pathprocessor.MultipathProcessorTxArgs, pathProcessors map[string]pathprocessor.PathProcessor, account *account.SelectedExtKey) (*MultiTransactionCommandResult, error) { func (tm *TransactionManager) SendTransactions(ctx context.Context, multiTransaction *MultiTransaction, data []*pathprocessor.MultipathProcessorTxArgs, pathProcessors map[string]pathprocessor.PathProcessor, account *account.SelectedExtKey) (*MultiTransactionCommandResult, error) {
updateDataFromMultiTx(data, multiTransaction) updateDataFromMultiTx(data, multiTransaction)
hashes, err := sendTransactions(data, pathProcessors, account) hashes, err := sendTransactions(data, pathProcessors, account, tm.txInputDataStorage)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -57,7 +57,7 @@ func setupTransactionManager(t *testing.T) (*TransactionManager, *mock_transacto
// Create a mock transactor // Create a mock transactor
transactor := mock_transactor.NewMockTransactorIface(ctrl) transactor := mock_transactor.NewMockTransactorIface(ctrl)
// Create a new instance of the TransactionManager // Create a new instance of the TransactionManager
tm := NewTransactionManager(NewInMemMultiTransactionStorage(), nil, transactor, nil, nil, nil, nil) tm := NewTransactionManager(NewInMemMultiTransactionStorage(), NewInMemTransactionInputDataStorage(), nil, transactor, nil, nil, nil, nil)
return tm, transactor, ctrl return tm, transactor, ctrl
} }

View File

@ -343,7 +343,7 @@ func (tm *PendingTxTracker) emitNotifications(chainID common.ChainID, changes []
} }
// PendingTransaction called with autoDelete = false will keep the transaction in the database until it is confirmed by the caller using Delete // PendingTransaction called with autoDelete = false will keep the transaction in the database until it is confirmed by the caller using Delete
func (tm *PendingTxTracker) TrackPendingTransaction(chainID common.ChainID, hash eth.Hash, from eth.Address, to eth.Address, trType PendingTrxType, autoDelete AutoDeleteType, additionalData string) error { func (tm *PendingTxTracker) TrackPendingTransaction(chainID common.ChainID, hash eth.Hash, from eth.Address, to eth.Address, trType PendingTrxType, autoDelete AutoDeleteType, additionalData string, transactionTo *eth.Address) error {
err := tm.addPending(&PendingTransaction{ err := tm.addPending(&PendingTransaction{
ChainID: chainID, ChainID: chainID,
Hash: hash, Hash: hash,
@ -353,6 +353,7 @@ func (tm *PendingTxTracker) TrackPendingTransaction(chainID common.ChainID, hash
Type: trType, Type: trType,
AutoDelete: &autoDelete, AutoDelete: &autoDelete,
AdditionalData: additionalData, AdditionalData: additionalData,
TransactionTo: transactionTo,
}) })
if err != nil { if err != nil {
return err return err
@ -419,6 +420,7 @@ type PendingTransaction struct {
GasLimit bigint.BigInt `json:"gasLimit"` GasLimit bigint.BigInt `json:"gasLimit"`
Type PendingTrxType `json:"type"` Type PendingTrxType `json:"type"`
AdditionalData string `json:"additionalData"` AdditionalData string `json:"additionalData"`
TransactionTo *eth.Address `json:"transactionTo"`
ChainID common.ChainID `json:"network_id"` ChainID common.ChainID `json:"network_id"`
MultiTransactionID wallet_common.MultiTransactionIDType `json:"multi_transaction_id"` MultiTransactionID wallet_common.MultiTransactionIDType `json:"multi_transaction_id"`
Nonce uint64 `json:"nonce"` Nonce uint64 `json:"nonce"`
@ -430,7 +432,7 @@ type PendingTransaction struct {
} }
const selectFromPending = `SELECT hash, timestamp, value, from_address, to_address, data, const selectFromPending = `SELECT hash, timestamp, value, from_address, to_address, data,
symbol, gas_price, gas_limit, type, additional_data, symbol, gas_price, gas_limit, type, additional_data, transaction_to,
network_id, COALESCE(multi_transaction_id, 0), status, auto_delete, nonce network_id, COALESCE(multi_transaction_id, 0), status, auto_delete, nonce
FROM pending_transactions FROM pending_transactions
` `
@ -456,6 +458,7 @@ func rowsToTransactions(rows *sql.Rows) (transactions []*PendingTransaction, err
(*bigint.SQLBigIntBytes)(transaction.GasLimit.Int), (*bigint.SQLBigIntBytes)(transaction.GasLimit.Int),
&transaction.Type, &transaction.Type,
&transaction.AdditionalData, &transaction.AdditionalData,
&transaction.TransactionTo,
&transaction.ChainID, &transaction.ChainID,
&transaction.MultiTransactionID, &transaction.MultiTransactionID,
transaction.Status, transaction.Status,
@ -608,10 +611,10 @@ func (tm *PendingTxTracker) addPending(transaction *PendingTransaction) error {
var insert *sql.Stmt var insert *sql.Stmt
insert, err = tx.Prepare(`INSERT OR REPLACE INTO pending_transactions insert, err = tx.Prepare(`INSERT OR REPLACE INTO pending_transactions
(network_id, hash, timestamp, value, from_address, to_address, (network_id, hash, timestamp, value, from_address, to_address,
data, symbol, gas_price, gas_limit, type, additional_data, multi_transaction_id, status, data, symbol, gas_price, gas_limit, type, additional_data, transaction_to, multi_transaction_id, status,
auto_delete, nonce) auto_delete, nonce)
VALUES VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? , ?, ?)`) (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? , ?, ?, ?)`)
if err != nil { if err != nil {
return err return err
} }
@ -630,6 +633,7 @@ func (tm *PendingTxTracker) addPending(transaction *PendingTransaction) error {
(*bigint.SQLBigIntBytes)(transaction.GasLimit.Int), (*bigint.SQLBigIntBytes)(transaction.GasLimit.Int),
transaction.Type, transaction.Type,
transaction.AdditionalData, transaction.AdditionalData,
transaction.TransactionTo,
transaction.MultiTransactionID, transaction.MultiTransactionID,
transaction.Status, transaction.Status,
transaction.AutoDelete, transaction.AutoDelete,

View File

@ -302,7 +302,7 @@ func TestPendingTxTracker_MultipleClients(t *testing.T) {
sub := eventFeed.Subscribe(eventChan) sub := eventFeed.Subscribe(eventChan)
for i := range txs { for i := range txs {
err := m.TrackPendingTransaction(txs[i].ChainID, txs[i].Hash, txs[i].From, txs[i].To, txs[i].Type, AutoDelete, "") err := m.TrackPendingTransaction(txs[i].ChainID, txs[i].Hash, txs[i].From, txs[i].To, txs[i].Type, AutoDelete, "", txs[i].TransactionTo)
require.NoError(t, err) require.NoError(t, err)
} }
@ -377,7 +377,7 @@ func TestPendingTxTracker_Watch(t *testing.T) {
sub := eventFeed.Subscribe(eventChan) sub := eventFeed.Subscribe(eventChan)
// Track the first transaction // Track the first transaction
err := m.TrackPendingTransaction(txs[1].ChainID, txs[1].Hash, txs[1].From, txs[1].To, txs[1].Type, Keep, "") err := m.TrackPendingTransaction(txs[1].ChainID, txs[1].Hash, txs[1].From, txs[1].To, txs[1].Type, Keep, "", txs[1].TransactionTo)
require.NoError(t, err) require.NoError(t, err)
// Store the confirmed already // Store the confirmed already
@ -476,7 +476,7 @@ func TestPendingTxTracker_Watch_StatusChangeIncrementally(t *testing.T) {
for i := range txs { for i := range txs {
// Track the first transaction // Track the first transaction
err := m.TrackPendingTransaction(txs[i].ChainID, txs[i].Hash, txs[i].From, txs[i].To, txs[i].Type, Keep, "") err := m.TrackPendingTransaction(txs[i].ChainID, txs[i].Hash, txs[i].From, txs[i].To, txs[i].Type, Keep, "", txs[i].TransactionTo)
require.NoError(t, err) require.NoError(t, err)
} }

View File

@ -67,12 +67,14 @@ func GenerateTestPendingTransactions(start int, count int) []PendingTransaction
txs := make([]PendingTransaction, count) txs := make([]PendingTransaction, count)
for i := start; i < count; i++ { for i := start; i < count; i++ {
transactionTo := eth.HexToAddress(fmt.Sprintf("0x4%d", i))
txs[i] = PendingTransaction{ txs[i] = PendingTransaction{
Hash: eth.HexToHash(fmt.Sprintf("0x1%d", i)), Hash: eth.HexToHash(fmt.Sprintf("0x1%d", i)),
From: eth.HexToAddress(fmt.Sprintf("0x2%d", i)), From: eth.HexToAddress(fmt.Sprintf("0x2%d", i)),
To: eth.HexToAddress(fmt.Sprintf("0x3%d", i)), To: eth.HexToAddress(fmt.Sprintf("0x3%d", i)),
Type: RegisterENS, Type: RegisterENS,
AdditionalData: "someuser.stateofus.eth", AdditionalData: "someuser.stateofus.eth",
TransactionTo: &transactionTo,
Value: bigint.BigInt{Int: big.NewInt(int64(i))}, Value: bigint.BigInt{Int: big.NewInt(int64(i))},
GasLimit: bigint.BigInt{Int: big.NewInt(21000)}, GasLimit: bigint.BigInt{Int: big.NewInt(21000)},
GasPrice: bigint.BigInt{Int: big.NewInt(int64(i))}, GasPrice: bigint.BigInt{Int: big.NewInt(int64(i))},

View File

@ -197,6 +197,7 @@ func createPendingTransaction(from common.Address, symbol string, chainID uint64
MultiTransactionID: multiTransactionID, MultiTransactionID: multiTransactionID,
Symbol: symbol, Symbol: symbol,
AutoDelete: new(bool), AutoDelete: new(bool),
TransactionTo: tx.To(),
} }
// Transaction downloader will delete pending transaction as soon as it is confirmed // Transaction downloader will delete pending transaction as soon as it is confirmed
*pTx.AutoDelete = false *pTx.AutoDelete = false

View File

@ -2,11 +2,19 @@ package walletdatabase
import ( import (
"database/sql" "database/sql"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/status-im/status-go/sqlite" "github.com/status-im/status-go/sqlite"
"github.com/status-im/status-go/walletdatabase/migrations" "github.com/status-im/status-go/walletdatabase/migrations"
) )
const (
batchSize = 1000
)
type DbInitializer struct { type DbInitializer struct {
} }
@ -14,7 +22,9 @@ func (a DbInitializer) Initialize(path, password string, kdfIterationsNumber int
return InitializeDB(path, password, kdfIterationsNumber) return InitializeDB(path, password, kdfIterationsNumber)
} }
var walletCustomSteps = []*sqlite.PostStep{} var walletCustomSteps = []*sqlite.PostStep{
{Version: 1721166023, CustomMigration: migrateWalletTransactionToAndEventLogAddress, RollBackVersion: 1720206965},
}
func doMigration(db *sql.DB) error { func doMigration(db *sql.DB) error {
// Run all the new migrations // Run all the new migrations
@ -35,3 +45,105 @@ func InitializeDB(path, password string, kdfIterationsNumber int) (*sql.DB, erro
return db, nil return db, nil
} }
func migrateWalletTransactionToAndEventLogAddress(sqlTx *sql.Tx) error {
var batchEntries [][]interface{}
// Extract Transaction To addresses and Event Log Address and
// add the information into the new columns
newColumnsAndIndexSetup := `
ALTER TABLE transfers ADD COLUMN transaction_to BLOB;
ALTER TABLE transfers ADD COLUMN event_log_address BLOB;`
rowIndex := 0
mightHaveRows := true
_, err := sqlTx.Exec(newColumnsAndIndexSetup)
if err != nil {
return err
}
for mightHaveRows {
var chainID uint64
var hash common.Hash
var address common.Address
rows, err := sqlTx.Query(`SELECT hash, address, network_id, tx, log FROM transfers WHERE tx IS NOT NULL LIMIT ? OFFSET ?`, batchSize, rowIndex)
if err != nil {
return err
}
curProcessed := 0
for rows.Next() {
tx := &types.Transaction{}
l := &types.Log{}
// Scan row data into the transaction and log objects
nullableTx := sqlite.JSONBlob{Data: tx}
nullableL := sqlite.JSONBlob{Data: l}
err = rows.Scan(&hash, &address, &chainID, &nullableTx, &nullableL)
if err != nil {
if strings.Contains(err.Error(), "missing required field") {
// Some Arb and Opt transaction types don't contain all required fields
continue
}
rows.Close()
return err
}
var currentRow []interface{}
var transactionTo *common.Address
var eventLogAddress *common.Address
if nullableTx.Valid {
transactionTo = tx.To()
currentRow = append(currentRow, transactionTo)
} else {
currentRow = append(currentRow, nil)
}
if nullableL.Valid {
eventLogAddress = &l.Address
currentRow = append(currentRow, eventLogAddress)
} else {
currentRow = append(currentRow, nil)
}
currentRow = append(currentRow, hash, address, chainID)
batchEntries = append(batchEntries, currentRow)
curProcessed++
}
rowIndex += curProcessed
// Check if there was an error in the last rows.Next()
rows.Close()
if err = rows.Err(); err != nil {
return err
}
mightHaveRows = (curProcessed == batchSize)
// insert extracted data into the new columns
if len(batchEntries) > 0 {
var stmt *sql.Stmt
stmt, err = sqlTx.Prepare(`UPDATE transfers SET transaction_to = ?, event_log_address = ?
WHERE hash = ? AND address = ? AND network_id = ?`)
if err != nil {
return err
}
for _, dataEntry := range batchEntries {
_, err = stmt.Exec(dataEntry...)
if err != nil {
return err
}
}
// Reset placeHolders and batchEntries for the next batch
batchEntries = [][]interface{}{}
}
}
return nil
}

View File

@ -0,0 +1,258 @@
package walletdatabase
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"strconv"
"testing"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/status-im/status-go/common/dbsetup"
w_common "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/sqlite"
"github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/walletdatabase/migrations"
)
const (
erc20ReceiptTestDataTemplate = `{"type":"0x2","root":"0x","status":"0x%d","cumulativeGasUsed":"0x10f8d2c","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000001008000000000000000000000000000000000000002000000000020000000000000000000800000000000000000000000010000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000800000000000000000000","logs":[{"address":"0x98339d8c260052b7ad81c28c16c0b98420f2b46a","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000e2d622c817878da5143bbe06866ca8e35273ba8a"],"data":"0x0000000000000000000000000000000000000000000000000000000000989680","blockNumber":"0x825527","transactionHash":"0xdcaa0fc7fe2e0d1f1343d1f36807344bb4fd26cda62ad8f9d8700e2c458cc79a","transactionIndex":"0x6c","blockHash":"0x69e0f829a557052c134cd7e21c220507d91bc35c316d3c47217e9bd362270274","logIndex":"0xcd","removed":false}],"transactionHash":"0xdcaa0fc7fe2e0d1f1343d1f36807344bb4fd26cda62ad8f9d8700e2c458cc79a","contractAddress":"0x0000000000000000000000000000000000000000","gasUsed":"0x8623","blockHash":"0x69e0f829a557052c134cd7e21c220507d91bc35c316d3c47217e9bd362270274","blockNumber":"0x825527","transactionIndex":"0x6c"}`
erc20TxTestData = `{"type":"0x2","nonce":"0x3d","gasPrice":"0x0","maxPriorityFeePerGas":"0x8c347c90","maxFeePerGas":"0x45964d43a4","gas":"0x8623","value":"0x0","input":"0x40c10f19000000000000000000000000e2d622c817878da5143bbe06866ca8e35273ba8a0000000000000000000000000000000000000000000000000000000000989680","v":"0x0","r":"0xbcac4bb290d48b467bb18ac67e98050b5f316d2c66b2f75dcc1d63a45c905d21","s":"0x10c15517ea9cabd7fe134b270daabf5d2e8335e935d3e021f54a4efaffb37cd2","to":"0x98339d8c260052b7ad81c28c16c0b98420f2b46a","chainId":"0x5","accessList":[],"hash":"0xdcaa0fc7fe2e0d1f1343d1f36807344bb4fd26cda62ad8f9d8700e2c458cc79a"}`
erc20LogTestData = `{"address":"0x98339d8c260052b7ad81c28c16c0b98420f2b46a","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000000000000000000000000000000000000000000000","0x000000000000000000000000e2d622c817878da5143bbe06866ca8e35273ba8a"],"data":"0x0000000000000000000000000000000000000000000000000000000000989680","blockNumber":"0x825527","transactionHash":"0xdcaa0fc7fe2e0d1f1343d1f36807344bb4fd26cda62ad8f9d8700e2c458cc79a","transactionIndex":"0x6c","blockHash":"0x69e0f829a557052c134cd7e21c220507d91bc35c316d3c47217e9bd362270274","logIndex":"0xcd","removed":false}`
ethReceiptTestData = `{
"type": "0x2",
"root": "0x",
"status": "0x1",
"cumulativeGasUsed": "0x2b461",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"logs": [],
"transactionHash": "0x4ac700ee2a1702f82b3cfdc88fd4d91f767b87fea9b929bd6223c6471a5e05b4",
"contractAddress": "0x0000000000000000000000000000000000000000",
"gasUsed": "0x5208",
"blockHash": "0x25fe164361c1cb4ed1b46996f7b5236d3118144529b31fca037fcda1d8ee684d",
"blockNumber": "0x5e3294",
"transactionIndex": "0x3"
}`
ethTxTestData = `{
"type": "0x2",
"nonce": "0x1",
"gasPrice": "0x0",
"maxPriorityFeePerGas": "0x33",
"maxFeePerGas": "0x3b9aca00",
"gas": "0x55f0",
"value": "0x%s",
"input": "0x",
"v": "0x0",
"r": "0xacc277ce156382d6f333cc8d75a56250778b17f1c6d1676af63cf68d53713986",
"s": "0x32417261484e9796390abb8db13f993965d917836be5cd96df25b9b581de91ec",
"to": "0xbd54a96c0ae19a220c8e1234f54c940dfab34639",
"chainId": "0x1a4",
"accessList": [],
"hash": "0x4ac700ee2a1702f82b3cfdc88fd4d91f767b87fea9b929bd6223c6471a5e05b4"
}`
)
func TestMigrateWalletTransactionToAndEventLogAddress(t *testing.T) {
openDB := func() (*sql.DB, error) {
return sqlite.OpenDB(sqlite.InMemoryPath, "1234567890", dbsetup.ReducedKDFIterationsNumber)
}
db, err := openDB()
require.NoError(t, err)
// Migrate until before custom step
err = migrations.MigrateTo(db, walletCustomSteps, 1720206965)
require.NoError(t, err)
// Validate that transfers table has no status column
exists, err := helpers.ColumnExists(db, "transfers", "transaction_to")
require.NoError(t, err)
require.False(t, exists)
exists, err = helpers.ColumnExists(db, "transfers", "event_log_address")
require.NoError(t, err)
require.False(t, exists)
insertTestTransaction := func(index int, txBlob string, receiptBlob string, logBlob string, ethType bool) error {
indexStr := strconv.Itoa(index)
senderStr := strconv.Itoa(index + 1)
var txValue *string
if txBlob != "" {
txValue = &txBlob
}
var receiptValue *string
if receiptBlob != "" {
receiptValue = &receiptBlob
}
var logValue *string
if logBlob != "" {
logValue = &logBlob
}
entryType := "eth"
if !ethType {
entryType = "erc20"
}
_, err = db.Exec(`INSERT OR IGNORE INTO blocks(network_id, address, blk_number, blk_hash) VALUES (?, ?, ?, ?);
INSERT INTO transfers (hash, address, sender, network_id, tx, receipt, log, blk_hash, type, blk_number, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
index, common.HexToAddress(indexStr), index, common.HexToHash(indexStr),
common.HexToHash(indexStr), common.HexToAddress(indexStr), common.HexToAddress(senderStr), index, txValue, receiptValue, logValue, common.HexToHash(indexStr), entryType, index, index)
return err
}
// Empty transaction, found the usecase in the test DB
erc20FailReceiptJSON := fmt.Sprintf(erc20ReceiptTestDataTemplate, 0)
erc20SuccessReceiptJSON := fmt.Sprintf(erc20ReceiptTestDataTemplate, 1)
err = insertTestTransaction(2, erc20TxTestData, erc20FailReceiptJSON, erc20LogTestData, false)
require.NoError(t, err)
err = insertTestTransaction(3, erc20TxTestData, erc20SuccessReceiptJSON, erc20LogTestData, false)
require.NoError(t, err)
ethZeroValueTxTestData := fmt.Sprintf(ethTxTestData, "0")
ethVeryBigValueTxTestData := fmt.Sprintf(ethTxTestData, "12345678901234567890")
ethOriginalTxTestData := fmt.Sprintf(ethTxTestData, "2386f26fc10000")
err = insertTestTransaction(4, ethZeroValueTxTestData, ethReceiptTestData, "", true)
require.NoError(t, err)
err = insertTestTransaction(5, ethVeryBigValueTxTestData, "", "", true)
require.NoError(t, err)
err = insertTestTransaction(6, ethOriginalTxTestData, ethReceiptTestData, "", true)
require.NoError(t, err)
failMigrationSteps := []*sqlite.PostStep{
{
Version: walletCustomSteps[0].Version,
CustomMigration: func(sqlTx *sql.Tx) error {
return errors.New("failed to run custom migration")
},
RollBackVersion: walletCustomSteps[0].RollBackVersion,
},
}
// Attempt to run test migration 1721166023 and fail in custom step
err = migrations.MigrateTo(db, failMigrationSteps, walletCustomSteps[0].Version)
require.Error(t, err)
exists, err = helpers.ColumnExists(db, "transfers", "transaction_to")
require.NoError(t, err)
require.False(t, exists)
// Run test migration 1721166023_extract_tx_to_and_event_log_address.<up/down>.sql
err = migrations.MigrateTo(db, walletCustomSteps, walletCustomSteps[0].Version)
require.NoError(t, err)
// Validate that the migration was run and transfers table has now new columns
exists, err = helpers.ColumnExists(db, "transfers", "transaction_to")
require.NoError(t, err)
require.True(t, exists)
exists, err = helpers.ColumnExists(db, "transfers", "event_log_address")
require.NoError(t, err)
require.True(t, exists)
var (
transactionTo sql.RawBytes
eventLogAddress sql.RawBytes
)
rows, err := db.Query(`SELECT transaction_to, event_log_address
FROM transfers ORDER BY timestamp ASC`)
require.NoError(t, err)
scanNextData := func() error {
rows.Next()
if rows.Err() != nil {
return rows.Err()
}
err := rows.Scan(&transactionTo, &eventLogAddress)
if err != nil {
return err
}
return nil
}
validateTransaction := func(tt *types.Transaction, expectedEntryType w_common.Type, tl *types.Log) {
if tt == nil {
require.Empty(t, transactionTo)
require.Empty(t, eventLogAddress)
} else {
if expectedEntryType == w_common.EthTransfer {
require.NotEmpty(t, transactionTo)
parsedTransactionTo := common.BytesToAddress(transactionTo)
require.Equal(t, *tt.To(), parsedTransactionTo)
} else {
require.NotEmpty(t, transactionTo)
parsedTransactionTo := common.BytesToAddress(transactionTo)
require.Equal(t, *tt.To(), parsedTransactionTo)
require.NotEmpty(t, eventLogAddress)
parsedEventLogAddress := common.BytesToAddress(eventLogAddress)
require.Equal(t, tl.Address, parsedEventLogAddress)
}
}
}
var successReceipt types.Receipt
err = json.Unmarshal([]byte(erc20SuccessReceiptJSON), &successReceipt)
require.NoError(t, err)
var failReceipt types.Receipt
err = json.Unmarshal([]byte(erc20FailReceiptJSON), &failReceipt)
require.NoError(t, err)
var erc20Log types.Log
err = json.Unmarshal([]byte(erc20LogTestData), &erc20Log)
require.NoError(t, err)
var erc20Tx types.Transaction
err = json.Unmarshal([]byte(erc20TxTestData), &erc20Tx)
require.NoError(t, err)
err = scanNextData()
require.NoError(t, err)
validateTransaction(&erc20Tx, w_common.Erc20Transfer, &erc20Log)
err = scanNextData()
require.NoError(t, err)
validateTransaction(&erc20Tx, w_common.Erc20Transfer, &erc20Log)
var zeroTestTx types.Transaction
err = json.Unmarshal([]byte(ethZeroValueTxTestData), &zeroTestTx)
require.NoError(t, err)
var ethReceipt types.Receipt
err = json.Unmarshal([]byte(ethReceiptTestData), &ethReceipt)
require.NoError(t, err)
err = scanNextData()
require.NoError(t, err)
validateTransaction(&zeroTestTx, w_common.EthTransfer, nil)
var bigTestTx types.Transaction
err = json.Unmarshal([]byte(ethVeryBigValueTxTestData), &bigTestTx)
require.NoError(t, err)
err = scanNextData()
require.NoError(t, err)
validateTransaction(&bigTestTx, w_common.EthTransfer, nil)
var ethOriginalTestTx types.Transaction
err = json.Unmarshal([]byte(ethOriginalTxTestData), &ethOriginalTestTx)
require.NoError(t, err)
err = scanNextData()
require.NoError(t, err)
validateTransaction(&ethOriginalTestTx, w_common.EthTransfer, nil)
err = scanNextData()
// Validate that we processed all data (no more rows expected)
require.Error(t, err)
db.Close()
}

View File

@ -0,0 +1,19 @@
-- transaction_input_data table stores data for each transaction generated by the wallet
CREATE TABLE IF NOT EXISTS transaction_input_data (
chain_id UNSIGNED BIGINT NOT NULL,
tx_hash BLOB NOT NULL,
processor_name TEXT NOT NULL,
from_asset TEXT,
from_amount BLOB,
to_asset TEXT,
to_amount BLOB,
side INTEGER,
slippage_bps INTEGER,
approval_amount BLOB,
approval_spender BLOB
);
CREATE UNIQUE INDEX IF NOT EXISTS transaction_input_data_identify_entry ON transaction_input_data (chain_id, tx_hash);
-- ALTER TABLE transfers ADD COLUMN approval_amount BLOB;
-- ALTER TABLE transfers ADD COLUMN approval_spender BLOB;

View File

@ -0,0 +1 @@
-- This migration is in GO code. If GO migration fails this entry serves as rollback to the previous one

View File

@ -0,0 +1,13 @@
-- This migration is done in GO code as a custom step.
-- This file serves as an anchor for the migration system
-- Check migrateWalletTransactionToAndEventLogAddress from walletdatabase/database.go
-- The following steps are done in GO code:
-- ALTER TABLE transfers ADD COLUMN transaction_to BLOB;
-- ALTER TABLE transfers ADD COLUMN event_log_address BLOB;
-- Extract the following:
-- 1) Transaction To field (Receiver address for ETH transfers or
-- address of the contract interacted with)
-- 2) Event Log Address (Address of the contract that emitted the event)

View File

@ -0,0 +1 @@
ALTER TABLE pending_transactions ADD COLUMN transaction_to BLOB;