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.Keep,
"",
tx.To(),
)
if err != nil {
log.Error("TrackPendingTransaction error", "error", err)
@ -242,6 +243,7 @@ func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64,
transactions.DeployOwnerToken,
transactions.Keep,
"",
tx.To(),
)
if err != nil {
log.Error("TrackPendingTransaction error", "error", err)
@ -305,6 +307,7 @@ func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentPara
transactions.DeployCommunityToken,
transactions.Keep,
"",
tx.To(),
)
if err != nil {
log.Error("TrackPendingTransaction error", "error", err)
@ -402,6 +405,7 @@ func (api *API) MintTokens(ctx context.Context, chainID uint64, contractAddress
transactions.AirdropCommunityToken,
transactions.Keep,
"",
tx.To(),
)
if err != nil {
log.Error("TrackPendingTransaction error", "error", err)
@ -469,6 +473,7 @@ func (api *API) RemoteBurn(ctx context.Context, chainID uint64, contractAddress
transactions.RemoteDestructCollectible,
transactions.Keep,
additionalData,
tx.To(),
)
if err != nil {
log.Error("TrackPendingTransaction error", "error", err)
@ -521,6 +526,7 @@ func (api *API) Burn(ctx context.Context, chainID uint64, contractAddress string
transactions.BurnCommunityToken,
transactions.Keep,
"",
tx.To(),
)
if err != nil {
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.Keep,
"",
tx.To(),
)
if err != nil {
log.Error("TrackPendingTransaction error", "error", err)
@ -711,17 +712,19 @@ func (s *Service) ReTrackOwnerTokenDeploymentTransaction(ctx context.Context, ch
transactionType = transactions.DeployOwnerToken
}
deployerAddress := common.HexToAddress(communityToken.Deployer)
_, err = s.pendingTracker.GetPendingEntry(wcommon.ChainID(chainID), common.HexToHash(hashString))
if errors.Is(err, sql.ErrNoRows) {
// start only if no pending transaction in database
err = s.pendingTracker.TrackPendingTransaction(
wcommon.ChainID(chainID),
common.HexToHash(hashString),
common.HexToAddress(communityToken.Deployer),
deployerAddress,
common.Address{},
transactionType,
transactions.Keep,
"",
&deployerAddress,
)
log.Debug("retracking pending transaction with hashId ", hashString)
} else {

View File

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

View File

@ -50,50 +50,52 @@ const (
)
type Entry struct {
payloadType PayloadType
transaction *transfer.TransactionIdentity
id common.MultiTransactionIDType
timestamp int64
activityType Type
activityStatus Status
amountOut *hexutil.Big // Used for activityType SendAT, SwapAT, BridgeAT
amountIn *hexutil.Big // Used for activityType ReceiveAT, BuyAT, SwapAT, BridgeAT, ApproveAT
tokenOut *Token // Used for activityType SendAT, SwapAT, BridgeAT
tokenIn *Token // Used for activityType ReceiveAT, BuyAT, SwapAT, BridgeAT, ApproveAT
symbolOut *string
symbolIn *string
sender *eth.Address
recipient *eth.Address
chainIDOut *common.ChainID
chainIDIn *common.ChainID
transferType *TransferType
contractAddress *eth.Address
communityID *string
payloadType PayloadType
transaction *transfer.TransactionIdentity
id common.MultiTransactionIDType
timestamp int64
activityType Type
activityStatus Status
amountOut *hexutil.Big // Used for activityType SendAT, SwapAT, BridgeAT
amountIn *hexutil.Big // Used for activityType ReceiveAT, BuyAT, SwapAT, BridgeAT, ApproveAT
tokenOut *Token // Used for activityType SendAT, SwapAT, BridgeAT
tokenIn *Token // Used for activityType ReceiveAT, BuyAT, SwapAT, BridgeAT, ApproveAT
symbolOut *string
symbolIn *string
sender *eth.Address
recipient *eth.Address
chainIDOut *common.ChainID
chainIDIn *common.ChainID
transferType *TransferType
deployedContractAddress *eth.Address
communityID *string
interactedContractAddress *eth.Address
isNew bool // isNew is used to indicate if the entry is newer than session start (changed state also)
}
// Only used for JSON marshalling
type EntryData struct {
PayloadType PayloadType `json:"payloadType"`
Transaction *transfer.TransactionIdentity `json:"transaction,omitempty"`
ID *common.MultiTransactionIDType `json:"id,omitempty"`
Timestamp *int64 `json:"timestamp,omitempty"`
ActivityType *Type `json:"activityType,omitempty"`
ActivityStatus *Status `json:"activityStatus,omitempty"`
AmountOut *hexutil.Big `json:"amountOut,omitempty"`
AmountIn *hexutil.Big `json:"amountIn,omitempty"`
TokenOut *Token `json:"tokenOut,omitempty"`
TokenIn *Token `json:"tokenIn,omitempty"`
SymbolOut *string `json:"symbolOut,omitempty"`
SymbolIn *string `json:"symbolIn,omitempty"`
Sender *eth.Address `json:"sender,omitempty"`
Recipient *eth.Address `json:"recipient,omitempty"`
ChainIDOut *common.ChainID `json:"chainIdOut,omitempty"`
ChainIDIn *common.ChainID `json:"chainIdIn,omitempty"`
TransferType *TransferType `json:"transferType,omitempty"`
ContractAddress *eth.Address `json:"contractAddress,omitempty"`
CommunityID *string `json:"communityId,omitempty"`
PayloadType PayloadType `json:"payloadType"`
Transaction *transfer.TransactionIdentity `json:"transaction,omitempty"`
ID *common.MultiTransactionIDType `json:"id,omitempty"`
Timestamp *int64 `json:"timestamp,omitempty"`
ActivityType *Type `json:"activityType,omitempty"`
ActivityStatus *Status `json:"activityStatus,omitempty"`
AmountOut *hexutil.Big `json:"amountOut,omitempty"`
AmountIn *hexutil.Big `json:"amountIn,omitempty"`
TokenOut *Token `json:"tokenOut,omitempty"`
TokenIn *Token `json:"tokenIn,omitempty"`
SymbolOut *string `json:"symbolOut,omitempty"`
SymbolIn *string `json:"symbolIn,omitempty"`
Sender *eth.Address `json:"sender,omitempty"`
Recipient *eth.Address `json:"recipient,omitempty"`
ChainIDOut *common.ChainID `json:"chainIdOut,omitempty"`
ChainIDIn *common.ChainID `json:"chainIdIn,omitempty"`
TransferType *TransferType `json:"transferType,omitempty"`
DeployedContractAddress *eth.Address `json:"contractAddress,omitempty"`
CommunityID *string `json:"communityId,omitempty"`
InteractedContractAddress *eth.Address `json:"interactedContractAddress,omitempty"`
IsNew *bool `json:"isNew,omitempty"`
@ -103,22 +105,23 @@ type EntryData struct {
func (e *Entry) MarshalJSON() ([]byte, error) {
data := EntryData{
Timestamp: &e.timestamp,
ActivityType: &e.activityType,
ActivityStatus: &e.activityStatus,
AmountOut: e.amountOut,
AmountIn: e.amountIn,
TokenOut: e.tokenOut,
TokenIn: e.tokenIn,
SymbolOut: e.symbolOut,
SymbolIn: e.symbolIn,
Sender: e.sender,
Recipient: e.recipient,
ChainIDOut: e.chainIDOut,
ChainIDIn: e.chainIDIn,
TransferType: e.transferType,
ContractAddress: e.contractAddress,
CommunityID: e.communityID,
Timestamp: &e.timestamp,
ActivityType: &e.activityType,
ActivityStatus: &e.activityStatus,
AmountOut: e.amountOut,
AmountIn: e.amountIn,
TokenOut: e.tokenOut,
TokenIn: e.tokenIn,
SymbolOut: e.symbolOut,
SymbolIn: e.symbolIn,
Sender: e.sender,
Recipient: e.recipient,
ChainIDOut: e.chainIDOut,
ChainIDIn: e.chainIDIn,
TransferType: e.transferType,
DeployedContractAddress: e.deployedContractAddress,
CommunityID: e.communityID,
InteractedContractAddress: e.interactedContractAddress,
}
if e.payloadType == MultiTransactionPT {
@ -486,6 +489,7 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
startFilterDisabled, filter.Period.StartTimestamp, endFilterDisabled, filter.Period.EndTimestamp,
filterActivityTypeAll, sliceContains(filter.Types, SendAT), sliceContains(filter.Types, ReceiveAT),
sliceContains(filter.Types, ContractDeploymentAT), sliceContains(filter.Types, MintAT),
sliceContains(filter.Types, SwapAT), sliceContains(filter.Types, ApproveAT),
transfer.MultiTransactionSend,
fromTrType, toTrType,
allAddresses, filterAllToAddresses,
@ -511,21 +515,36 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
var timestamp int64
var dbMtType, dbTrType sql.NullByte
var toAddress, fromAddress eth.Address
var toAddressDB, ownerAddressDB, contractAddressDB, dbTokenID sql.RawBytes
var tokenAddress, contractAddress *eth.Address
var toAddressDB, ownerAddressDB, deployedContractAddressDB, dbTokenID sql.RawBytes
var tokenAddress, deployedContractAddress, interactedContractAddress *eth.Address
var aggregatedStatus int
var dbTrAmount sql.NullString
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 methodHash, communityID sql.NullString
var transferType *TransferType
var communityMintEventDB sql.NullBool
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,
&toAddressDB, &ownerAddressDB, &dbTrAmount, (*bigint.SQLBigIntBytes)(dbPTrAmount), &dbMtFromAmount, &dbMtToAmount, &aggregatedStatus, &aggregatedCount,
&tokenAddress, &dbTokenID, &tokenCode, &fromTokenCode, &toTokenCode, &outChainIDDB, &inChainIDDB, &contractType,
&contractAddressDB, &methodHash, &communityMintEventDB, &communityID)
&tokenAddress, &dbTokenID, &tokenCode, &fromTokenCode, &toTokenCode, &outChainIDDB, &inChainIDDB, &dbContractType,
&deployedContractAddressDB, &methodHash, &communityMintEventDB, &communityID,
&dbTrInputApprovalSpender, (*bigint.SQLBigIntBytes)(dbTrInputApprovalAmount), &dbTrInputFromAsset, (*bigint.SQLBigIntBytes)(dbTrInputFromAmount),
&dbTrInputToAsset, (*bigint.SQLBigIntBytes)(dbTrInputToAmount), &dbTrInputSide, &dbTrInputSlippageBps,
&dbInteractedContractAddress)
if err != nil {
return nil, err
}
@ -534,24 +553,32 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
toAddress = eth.BytesToAddress(toAddressDB)
}
if contractType.Valid {
transferType = contractTypeFromDBType(contractType.String)
if dbContractType.Valid {
contractType = dbContractType.String
transferType = contractTypeFromDBType(contractType)
}
if communityMintEventDB.Valid {
communityMintEvent = communityMintEventDB.Bool
}
if len(contractAddressDB) > 0 {
contractAddress = new(eth.Address)
*contractAddress = eth.BytesToAddress(contractAddressDB)
if len(deployedContractAddressDB) > 0 {
deployedContractAddress = new(eth.Address)
*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) {
if trType.Valid {
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
} else if contractType == string(common.Erc20Approval) {
return ApproveAT, fromAddress
}
return SendAT, fromAddress
} else if trType.Byte == toTrType {
@ -624,15 +651,12 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
entry.amountIn = inAmount
} else if pendingHash != nil && chainID.Valid {
// Process `pending_transactions` row
// Extract activity type: SendAT/ReceiveAT
activityType, _ := getActivityType(dbTrType)
inAmount, outAmount := getTrInAndOutAmounts(activityType, dbTrAmount, dbPTrAmount)
outChainID = new(common.ChainID)
*outChainID = common.ChainID(chainID.Int64)
// Extract activity type: SendAT/ReceiveAT/ApproveAT
activityType, _ := getActivityType(dbTrType)
entry = newActivityEntryWithPendingTransaction(
&transfer.TransactionIdentity{ChainID: common.ChainID(chainID.Int64),
Hash: eth.BytesToHash(pendingHash),
@ -640,17 +664,36 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
timestamp, activityType, activityStatus,
)
// Extract tokens
if tokenCode.Valid {
cID := common.ChainID(chainID.Int64)
entry.tokenOut = deps.tokenFromSymbol(&cID, tokenCode.String)
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
if tokenCode.Valid {
cID := common.ChainID(chainID.Int64)
entry.tokenOut = deps.tokenFromSymbol(&cID, tokenCode.String)
}
entry.symbolOut, entry.symbolIn = lookupAndFillInTokens(deps, entry.tokenOut, nil)
// Complete the data
entry.amountOut = outAmount
entry.amountIn = inAmount
}
entry.symbolOut, entry.symbolIn = lookupAndFillInTokens(deps, entry.tokenOut, nil)
// Complete the data
entry.amountOut = outAmount
entry.amountIn = inAmount
} else if multiTxID.Valid {
// Process `multi_transactions` row
@ -693,12 +736,12 @@ func getActivityEntries(ctx context.Context, deps FilterDependencies, addresses
}
// Complete common data
entry.recipient = &toAddress
entry.sender = &fromAddress
entry.recipient = &toAddress
entry.chainIDOut = outChainID
entry.chainIDIn = inChainID
entry.transferType = transferType
entry.interactedContractAddress = interactedContractAddress
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
multiTxs := make([]transfer.MultiTransaction, 6)
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[1] = transfer.GenerateTestSwapMultiTransaction(trs[2], testutils.SntSymbol, 100) // trs[3]
multiTxs[2] = transfer.GenerateTestSendMultiTransaction(trs[4]) // trs[5]
multiTxs[3] = transfer.GenerateTestBridgeMultiTransaction(trs[6], trs[7])
multiTxs[4] = transfer.GenerateTestSendMultiTransaction(trs[8]) // trs[9]
multiTxs[5] = transfer.GenerateTestApproveMultiTransaction(trs[10]) // trs[11]
multiTxs[4] = transfer.GenerateTestSendMultiTransaction(trs[8]) // trs[9]
multiTxs[5] = transfer.GenerateTestApproveMultiTransaction(approveTxs[1])
var lastMT common.MultiTransactionIDType
for i := range trs {
@ -695,6 +697,7 @@ func TestGetActivityEntriesFilterByType(t *testing.T) {
// Here not to include the modified To and From addresses
allAddresses := append(append(append(append(append(tdFromAdds, tdToAddrs...), fromAddrs...), toAddrs...), fromSpecial...), toSpecial...)
allAddresses = append(allAddresses, approveFromAddrs...)
// Insert MintAT Collectible
trsSpecial[0].From = eth.HexToAddress("0x0")

View File

@ -23,6 +23,8 @@ WITH filter_conditions AS (
? AS filterActivityTypeReceive,
? AS filterActivityTypeContractDeployment,
? AS filterActivityTypeMint,
? AS filterActivityTypeSwap,
? AS filterActivityTypeApprove,
? AS mTTypeSend,
? AS fromTrType,
? 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 = '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
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
transfers
CROSS JOIN filter_conditions
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 transaction_input_data ON transfers.network_id = transaction_input_data.chain_id AND transfers.tx_hash = transaction_input_data.tx_hash
WHERE
transfers.loaded == 1
AND transfers.multi_transaction_id = 0
@ -273,6 +285,11 @@ WHERE
)
)
)
OR (
filterActivityTypeApprove
AND tr_type = fromTrType
AND transfers.type = 'Erc20Approval'
)
OR (
filterActivityTypeContractDeployment
AND tr_type = fromTrType
@ -391,12 +408,22 @@ SELECT
NULL as contract_address,
NULL AS method_hash,
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
pending_transactions
CROSS JOIN filter_conditions
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 transaction_input_data ON pending_transactions.network_id = transaction_input_data.chain_id AND pending_transactions.hash = transaction_input_data.tx_hash
WHERE
pending_transactions.multi_transaction_id = 0
AND pending_transactions.status = pendingStatus
@ -484,7 +511,16 @@ SELECT
NULL as contract_address,
NULL AS method_hash,
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
multi_transactions
CROSS JOIN filter_conditions

View File

@ -15,6 +15,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"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/collectibles"
w_common "github.com/status-im/status-go/services/wallet/common"
@ -74,13 +75,22 @@ type Service struct {
debounceDuration time.Duration
pendingTracker *transactions.PendingTxTracker
featureFlags *protocolCommon.FeatureFlags
}
func (s *Service) nextSessionID() SessionID {
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{
db: db,
accountsDB: accountsDB,
@ -94,6 +104,8 @@ func NewService(db *sql.DB, accountsDB *accounts.Database, tokenManager token.Ma
debounceDuration: 1 * time.Second,
pendingTracker: pendingTracker,
featureFlags: featureFlags,
}
}

View File

@ -468,7 +468,7 @@ func TestService_IncrementalUpdateOnTop(t *testing.T) {
ChainID: 5,
}, newTx.tokenOut)
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
require.Equal(t, SimpleTransactionPT, payload.Activities[1].payloadType)

View File

@ -29,9 +29,11 @@ const (
UniswapV3Swap Type = "uniswapV3Swap"
HopBridgeFrom Type = "HopBridgeFrom"
HopBridgeTo Type = "HopBridgeTo"
Erc20Approval Type = "Erc20Approval"
unknownTransaction Type = "unknown"
// Event types
Erc20ApprovalEventType EventType = "erc20ApprovalEvent"
WETHDepositEventType EventType = "wethDepositEvent"
WETHWithdrawalEventType EventType = "wethWithdrawalEvent"
Erc20TransferEventType EventType = "erc20Event"
@ -46,6 +48,10 @@ const (
HopBridgeTransferSentEventType EventType = "hopBridgeTransferSentEvent"
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)
wethDepositEventSignature = "Deposit(address,uint256)"
// Withdrawal (index_topic_1 address src, uint256 wad)
@ -83,6 +89,7 @@ var (
// Detect event type for a cetain item from the Events Log
func GetEventType(log *types.Log) EventType {
erc20_721ApprovalEventSignatureHash := GetEventSignatureHash(Erc20_721ApprovalEventSignature)
wethDepositEventSignatureHash := GetEventSignatureHash(wethDepositEventSignature)
wethWithdrawalEventSignatureHash := GetEventSignatureHash(wethWithdrawalEventSignature)
erc20_721TransferEventSignatureHash := GetEventSignatureHash(Erc20_721TransferEventSignature)
@ -97,6 +104,11 @@ func GetEventType(log *types.Log) EventType {
if len(log.Topics) > 0 {
switch log.Topics[0] {
case erc20_721ApprovalEventSignatureHash:
switch len(log.Topics) {
case erc20ApprovalEventIndexedParameters:
return Erc20ApprovalEventType
}
case wethDepositEventSignatureHash:
return WETHDepositEventType
case wethWithdrawalEventSignatureHash:
@ -132,6 +144,8 @@ func GetEventType(log *types.Log) EventType {
func EventTypeToSubtransactionType(eventType EventType) Type {
switch eventType {
case Erc20ApprovalEventType:
return Erc20Approval
case Erc20TransferEventType:
return Erc20Transfer
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)
}
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) {
eventType := GetEventType(&ethlog)

View File

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

View File

@ -47,6 +47,7 @@ var (
ErrPriceTimeout = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-037"), Details: "price timeout"}
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"}
ErrFromTokenShouldNotBeNative = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-040"), Details: "from token should not be the native token"}
)
func createErrorResponse(processorName string, err error) error {

View File

@ -211,3 +211,18 @@ func (mr *MockPathProcessorClearableMockRecorder) Clear() *gomock.Call {
mr.mock.ctrl.T.Helper()
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"`
ChainID uint64
TransferTx *transactions.SendTxArgs
ApproveTx *ApproveTxArgs
HopTx *HopBridgeTxArgs
CbridgeTx *CelerBridgeTxArgs
ERC721TransferTx *ERC721TxArgs

View File

@ -31,6 +31,8 @@ type PathProcessor interface {
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(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 {

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)
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
}
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
}
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) {
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
}
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) {
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) {
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) {
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
}
func (s *SwapParaswapProcessor) prepareTransaction(sendArgs *MultipathProcessorTxArgs) error {
slippageBP := uint(sendArgs.SwapTx.SlippagePercentage * 100) // convert to basis points
func (s *SwapParaswapProcessor) getPriceRoute(sendArgs *MultipathProcessorTxArgs) (*paraswap.Route, error) {
key := makeKey(sendArgs.SwapTx.ChainID, sendArgs.SwapTx.ChainIDTo, sendArgs.SwapTx.TokenIDFrom, sendArgs.SwapTx.TokenIDTo)
priceRouteIns, ok := s.priceRoute.Load(key)
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,
priceRoute.DestTokenAddress, priceRoute.DestTokenDecimals, priceRoute.DestAmount.Int, slippageBP,
@ -284,3 +294,31 @@ func (s *SwapParaswapProcessor) CalculateAmountOut(params ProcessorInputParams)
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) {
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/market"
"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/alchemy"
"github.com/status-im/status-go/services/wallet/thirdparty/coingecko"
@ -112,7 +113,16 @@ func NewService(
cryptoOnRampManager := onramp.NewManager(cryptoOnRampProviders)
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()
transferController := transfer.NewTransferController(db, accountsDB, rpcClient, accountFeed, feed, transactionManager, pendingTxManager,
tokenManager, balanceCacher, blockChainState)
@ -181,12 +191,7 @@ func NewService(
)
collectibles := collectibles.NewService(db, feed, accountsDB, accountFeed, settingsFeed, communityManager, rpcClient.NetworkManager, collectiblesManager)
activity := activity.NewService(db, accountsDB, tokenManager, collectiblesManager, feed, pendingTxManager)
featureFlags := &protocolCommon.FeatureFlags{}
if config.WalletConfig.EnableCelerBridge {
featureFlags.EnableCelerBridge = true
}
activity := activity.NewService(db, accountsDB, tokenManager, collectiblesManager, feed, pendingTxManager, featureFlags)
return &Service{
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/blockchainstate"
"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/utils"
@ -1322,7 +1323,7 @@ func TestFetchTransfersForLoadedBlocks(t *testing.T) {
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
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)
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/wallet/blockchainstate"
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/walletdatabase"
)
@ -36,7 +37,7 @@ func TestController_watchAccountsChanges(t *testing.T) {
bcstate := blockchainstate.NewBlockChainState()
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(
walletDB,
accountsDB,
@ -239,7 +240,7 @@ func TestController_cleanupAccountLeftovers(t *testing.T) {
require.NoError(t, err)
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()
c := NewTransferController(
walletDB,

View File

@ -359,16 +359,33 @@ func updateOrInsertTransfers(chainID uint64, creator statementCreator, transfers
var tokenID *big.Int
var txFrom *common.Address
var txTo *common.Address
var transactionTo *common.Address
var eventLogAddress *common.Address
if t.Transaction != nil {
if t.Log != nil {
_, tokenAddress, txFrom, txTo = w_common.ExtractTokenTransferData(t.Type, t.Log, t.Transaction)
tokenID = t.TokenID
// Zero tokenID can be used for ERC721 and ERC1155 transfers but when serialzed/deserialized it becomes nil
// as 0 value of big.Int bytes is nil.
if tokenID == nil && (t.Type == w_common.Erc721Transfer || t.Type == w_common.Erc1155Transfer) {
tokenID = big.NewInt(0)
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)
tokenID = t.TokenID
// Zero tokenID can be used for ERC721 and ERC1155 transfers but when serialzed/deserialized it becomes nil
// as 0 value of big.Int bytes is nil.
if tokenID == nil && (t.Type == w_common.Erc721Transfer || t.Type == w_common.Erc1155Transfer) {
tokenID = big.NewInt(0)
}
txValue = t.TokenValue
}
txValue = t.TokenValue
eventLogAddress = &t.Log.Address
} else {
txValue = new(big.Int).Set(t.Transaction.Value())
txFrom = &t.From
@ -388,6 +405,7 @@ func updateOrInsertTransfers(chainID uint64, creator statementCreator, transfers
*txNonce = t.Transaction.Nonce()
txSize = new(uint64)
*txSize = t.Transaction.Size()
transactionTo = t.Transaction.To()
}
dbFields := transferDBFields{
@ -426,6 +444,8 @@ func updateOrInsertTransfers(chainID uint64, creator statementCreator, transfers
tokenID: tokenID,
txFrom: txFrom,
txTo: txTo,
transactionTo: transactionTo,
eventLogAddress: eventLogAddress,
}
txsDBFields = append(txsDBFields, dbFields)
}
@ -469,15 +489,18 @@ type transferDBFields struct {
tokenID *big.Int
txFrom *common.Address
txTo *common.Address
transactionTo *common.Address
eventLogAddress *common.Address
}
func updateOrInsertTransfersDBFields(creator statementCreator, transfers []transferDBFields) error {
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,
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
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
if err != nil {
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,
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 {
log.Error("can't save transfer", "b-hash", t.blockHash, "b-n", t.blockNumber, "a", t.address, "h", t.id)
return err

View File

@ -434,7 +434,8 @@ func (d *ETHDownloader) subTransactionsFromTransactionData(address, from common.
for _, txLog := range receipt.Logs {
eventType := w_common.GetEventType(txLog)
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.HopBridgeWithdrawalBondedEventType, w_common.HopBridgeTransferSentEventType:
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) {
hashes := make(map[uint64][]types.Hash)
usedNonces := make(map[uint64]int64)
for _, tx := range data {
lastUsedNonce := int64(-1)
if nonce, ok := usedNonces[tx.ChainID]; ok {
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
}
// 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)
usedNonces[tx.ChainID] = int64(usedNonce)
}

View File

@ -9,9 +9,12 @@ import (
eth_common "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"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/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/token"
@ -42,6 +45,13 @@ type TestCollectibleTransfer struct {
TestCollectible
}
type TestApprove struct {
TestTransaction
Spender eth_common.Address // [address]
Amount int64
Token *token.Token
}
func SeedToToken(seed int) *token.Token {
tokenIndex := seed % len(TestTokens)
return TestTokens[tokenIndex]
@ -106,6 +116,17 @@ func generateTestCollectibleTransfer(seed int) TestCollectibleTransfer {
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 {
return MultiTransaction{
ID: multiTransactionIDGenerator(),
@ -148,15 +169,15 @@ func GenerateTestBridgeMultiTransaction(fromTr, toTr TestTransfer) MultiTransact
}
}
func GenerateTestApproveMultiTransaction(tr TestTransfer) MultiTransaction {
func GenerateTestApproveMultiTransaction(tr TestApprove) MultiTransaction {
return MultiTransaction{
ID: multiTransactionIDGenerator(),
Type: MultiTransactionApprove,
FromAddress: tr.From,
ToAddress: tr.To,
ToAddress: tr.Spender,
FromAsset: 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)),
Timestamp: uint64(tr.Timestamp),
}
@ -174,6 +195,17 @@ func GenerateTestTransfers(tb testing.TB, db *sql.DB, firstStartIndex int, count
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 {
TokenAddress eth_common.Address
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{
chainID: uint64(tr.ChainID),
id: tr.Hash,
@ -383,6 +423,8 @@ func InsertTestTransferWithOptions(tb testing.TB, db *sql.DB, address eth_common
tokenID: opt.TokenID,
transaction: opt.Tx,
receipt: opt.Receipt,
transactionTo: transactionTo,
eventLogAddress: eventLogAddress,
}
err = updateOrInsertTransfersDBFields(tx, []transferDBFields{transfer})
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) {
_, err := db.Exec(`
INSERT INTO pending_transactions (network_id, hash, timestamp, from_address, to_address,
symbol, gas_price, gas_limit, value, data, type, additional_data, multi_transaction_id
) VALUES (?, ?, ?, ?, ?, 'ETH', 0, 0, ?, '', 'eth', '', ?)`,
tr.ChainID, tr.Hash, tr.Timestamp, tr.From, tr.To, (*bigint.SQLBigIntBytes)(big.NewInt(tr.Value)), tr.MultiTransactionID)
symbol, gas_price, gas_limit, value, data, type, additional_data, multi_transaction_id, transaction_to,
) VALUES (?, ?, ?, ?, ?, 'ETH', 0, 0, ?, '', 'eth', '', ?, ?)`,
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)
}
@ -499,3 +541,19 @@ func (s *InMemMultiTransactionStorage) ReadMultiTransactions(details *MultiTxDet
}
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

@ -34,13 +34,14 @@ type TransactionDescription struct {
}
type TransactionManager struct {
storage MultiTransactionStorage
gethManager *account.GethManager
transactor transactions.TransactorIface
config *params.NodeConfig
accountsDB accounts.AccountsStorage
pendingTracker *transactions.PendingTxTracker
eventFeed *event.Feed
storage MultiTransactionStorage
txInputDataStorage TransactionInputDataStorage
gethManager *account.GethManager
transactor transactions.TransactorIface
config *params.NodeConfig
accountsDB accounts.AccountsStorage
pendingTracker *transactions.PendingTxTracker
eventFeed *event.Feed
multiTransactionForKeycardSigning *MultiTransaction
multipathTransactionsData []*pathprocessor.MultipathProcessorTxArgs
@ -54,8 +55,13 @@ type MultiTransactionStorage interface {
DeleteMultiTransaction(id wallet_common.MultiTransactionIDType) error
}
type TransactionInputDataStorage interface {
UpsertInputData(chainID wallet_common.ChainID, txHash types.Hash, inputData pathprocessor.TransactionInputData) error
}
func NewTransactionManager(
storage MultiTransactionStorage,
txInputDataStorage TransactionInputDataStorage,
gethManager *account.GethManager,
transactor transactions.TransactorIface,
config *params.NodeConfig,
@ -64,13 +70,14 @@ func NewTransactionManager(
eventFeed *event.Feed,
) *TransactionManager {
return &TransactionManager{
storage: storage,
gethManager: gethManager,
transactor: transactor,
config: config,
accountsDB: accountsDB,
pendingTracker: pendingTxManager,
eventFeed: eventFeed,
storage: storage,
txInputDataStorage: txInputDataStorage,
gethManager: gethManager,
transactor: transactor,
config: config,
accountsDB: accountsDB,
pendingTracker: pendingTxManager,
eventFeed: eventFeed,
}
}

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) {
updateDataFromMultiTx(data, multiTransaction)
hashes, err := sendTransactions(data, pathProcessors, account)
hashes, err := sendTransactions(data, pathProcessors, account, tm.txInputDataStorage)
if err != nil {
return nil, err
}

View File

@ -57,7 +57,7 @@ func setupTransactionManager(t *testing.T) (*TransactionManager, *mock_transacto
// Create a mock transactor
transactor := mock_transactor.NewMockTransactorIface(ctrl)
// 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
}

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
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{
ChainID: chainID,
Hash: hash,
@ -353,6 +353,7 @@ func (tm *PendingTxTracker) TrackPendingTransaction(chainID common.ChainID, hash
Type: trType,
AutoDelete: &autoDelete,
AdditionalData: additionalData,
TransactionTo: transactionTo,
})
if err != nil {
return err
@ -419,6 +420,7 @@ type PendingTransaction struct {
GasLimit bigint.BigInt `json:"gasLimit"`
Type PendingTrxType `json:"type"`
AdditionalData string `json:"additionalData"`
TransactionTo *eth.Address `json:"transactionTo"`
ChainID common.ChainID `json:"network_id"`
MultiTransactionID wallet_common.MultiTransactionIDType `json:"multi_transaction_id"`
Nonce uint64 `json:"nonce"`
@ -430,7 +432,7 @@ type PendingTransaction struct {
}
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
FROM pending_transactions
`
@ -456,6 +458,7 @@ func rowsToTransactions(rows *sql.Rows) (transactions []*PendingTransaction, err
(*bigint.SQLBigIntBytes)(transaction.GasLimit.Int),
&transaction.Type,
&transaction.AdditionalData,
&transaction.TransactionTo,
&transaction.ChainID,
&transaction.MultiTransactionID,
transaction.Status,
@ -608,10 +611,10 @@ func (tm *PendingTxTracker) addPending(transaction *PendingTransaction) error {
var insert *sql.Stmt
insert, err = tx.Prepare(`INSERT OR REPLACE INTO pending_transactions
(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)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? , ?, ?)`)
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? , ?, ?, ?)`)
if err != nil {
return err
}
@ -630,6 +633,7 @@ func (tm *PendingTxTracker) addPending(transaction *PendingTransaction) error {
(*bigint.SQLBigIntBytes)(transaction.GasLimit.Int),
transaction.Type,
transaction.AdditionalData,
transaction.TransactionTo,
transaction.MultiTransactionID,
transaction.Status,
transaction.AutoDelete,

View File

@ -302,7 +302,7 @@ func TestPendingTxTracker_MultipleClients(t *testing.T) {
sub := eventFeed.Subscribe(eventChan)
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)
}
@ -377,7 +377,7 @@ func TestPendingTxTracker_Watch(t *testing.T) {
sub := eventFeed.Subscribe(eventChan)
// 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)
// Store the confirmed already
@ -476,7 +476,7 @@ func TestPendingTxTracker_Watch_StatusChangeIncrementally(t *testing.T) {
for i := range txs {
// 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)
}

View File

@ -67,12 +67,14 @@ func GenerateTestPendingTransactions(start int, count int) []PendingTransaction
txs := make([]PendingTransaction, count)
for i := start; i < count; i++ {
transactionTo := eth.HexToAddress(fmt.Sprintf("0x4%d", i))
txs[i] = PendingTransaction{
Hash: eth.HexToHash(fmt.Sprintf("0x1%d", i)),
From: eth.HexToAddress(fmt.Sprintf("0x2%d", i)),
To: eth.HexToAddress(fmt.Sprintf("0x3%d", i)),
Type: RegisterENS,
AdditionalData: "someuser.stateofus.eth",
TransactionTo: &transactionTo,
Value: bigint.BigInt{Int: big.NewInt(int64(i))},
GasLimit: bigint.BigInt{Int: big.NewInt(21000)},
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,
Symbol: symbol,
AutoDelete: new(bool),
TransactionTo: tx.To(),
}
// Transaction downloader will delete pending transaction as soon as it is confirmed
*pTx.AutoDelete = false

View File

@ -2,11 +2,19 @@ package walletdatabase
import (
"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/walletdatabase/migrations"
)
const (
batchSize = 1000
)
type DbInitializer struct {
}
@ -14,7 +22,9 @@ func (a DbInitializer) Initialize(path, password string, kdfIterationsNumber int
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 {
// Run all the new migrations
@ -35,3 +45,105 @@ func InitializeDB(path, password string, kdfIterationsNumber int) (*sql.DB, erro
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;