2024-03-29 17:10:58 +00:00
|
|
|
// These tests are for development only to be run manually
|
|
|
|
// There is more work needed to automate them not to depend on an existing account and internet connection
|
|
|
|
|
|
|
|
package wallet
|
|
|
|
|
|
|
|
import (
|
|
|
|
"math/big"
|
2024-04-02 13:27:21 +00:00
|
|
|
"strings"
|
2024-03-29 17:10:58 +00:00
|
|
|
"testing"
|
2024-04-02 13:27:21 +00:00
|
|
|
"time"
|
2024-03-29 17:10:58 +00:00
|
|
|
|
2024-04-02 13:27:21 +00:00
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
2024-04-01 08:33:28 +00:00
|
|
|
eth "github.com/ethereum/go-ethereum/common"
|
2024-03-29 17:10:58 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
|
|
|
"github.com/status-im/status-desktop/test/status-go/integration/helpers"
|
|
|
|
|
2024-04-02 13:27:21 +00:00
|
|
|
"github.com/status-im/status-go/contracts/ierc20"
|
|
|
|
"github.com/status-im/status-go/eth-node/types"
|
2024-03-29 17:10:58 +00:00
|
|
|
"github.com/status-im/status-go/services/wallet"
|
2024-04-02 13:27:21 +00:00
|
|
|
"github.com/status-im/status-go/services/wallet/activity"
|
|
|
|
"github.com/status-im/status-go/services/wallet/bridge"
|
2024-04-01 08:33:28 +00:00
|
|
|
"github.com/status-im/status-go/services/wallet/common"
|
2024-04-02 13:27:21 +00:00
|
|
|
"github.com/status-im/status-go/services/wallet/transfer"
|
|
|
|
"github.com/status-im/status-go/services/wallet/walletevent"
|
|
|
|
"github.com/status-im/status-go/transactions"
|
2024-03-29 17:10:58 +00:00
|
|
|
)
|
|
|
|
|
2024-04-01 08:33:28 +00:00
|
|
|
type dataPayload struct {
|
|
|
|
transferType wallet.SendType
|
|
|
|
accountFrom eth.Address
|
|
|
|
accountTo eth.Address
|
|
|
|
amount *hexutil.Big
|
2024-04-02 13:27:21 +00:00
|
|
|
tokenIdentity string // Format: "<HexTokenAddress>:<TokenID>" or "<Symbol>"
|
2024-04-01 08:33:28 +00:00
|
|
|
disabledFromChainIDs []uint64
|
|
|
|
disabledToChainIDs []uint64
|
|
|
|
preferredChainIDs []uint64
|
|
|
|
gasFeeMode wallet.GasFeeMode
|
|
|
|
fromLockedAmount map[uint64]*hexutil.Big
|
|
|
|
}
|
|
|
|
|
|
|
|
func dataToCallingPayload(data dataPayload) []interface{} {
|
|
|
|
return []interface{}{
|
|
|
|
data.transferType,
|
|
|
|
data.accountFrom,
|
|
|
|
data.accountTo,
|
|
|
|
data.amount,
|
|
|
|
data.tokenIdentity,
|
|
|
|
data.disabledFromChainIDs,
|
|
|
|
data.disabledToChainIDs,
|
|
|
|
data.preferredChainIDs,
|
|
|
|
data.gasFeeMode,
|
|
|
|
data.fromLockedAmount,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func basicPayload() dataPayload {
|
|
|
|
defaultDisabled := []uint64{common.OptimismSepolia, common.ArbitrumSepolia}
|
|
|
|
return dataPayload{
|
|
|
|
transferType: wallet.Transfer,
|
|
|
|
tokenIdentity: "",
|
|
|
|
accountFrom: eth.HexToAddress("0xe2d622c817878da5143bbe06866ca8e35273ba8a"),
|
|
|
|
accountTo: eth.HexToAddress("0xbd54a96c0ae19a220c8e1234f54c940dfab34639"),
|
|
|
|
amount: (*hexutil.Big)(big.NewInt(1)),
|
|
|
|
disabledFromChainIDs: defaultDisabled,
|
|
|
|
disabledToChainIDs: defaultDisabled,
|
|
|
|
preferredChainIDs: []uint64{common.EthereumSepolia},
|
|
|
|
gasFeeMode: wallet.GasFeeMedium,
|
|
|
|
fromLockedAmount: map[uint64]*hexutil.Big{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func customBasicPayload(transType wallet.SendType, tokenIdentity string) dataPayload {
|
|
|
|
payload := basicPayload()
|
|
|
|
payload.transferType = transType
|
|
|
|
payload.tokenIdentity = tokenIdentity
|
|
|
|
return payload
|
|
|
|
}
|
|
|
|
|
|
|
|
func erc721Payload(tokenIdentity string) dataPayload {
|
|
|
|
return customBasicPayload(wallet.ERC721Transfer, tokenIdentity)
|
|
|
|
}
|
|
|
|
|
|
|
|
func erc1155Payload(tokenIdentity string) dataPayload {
|
|
|
|
return customBasicPayload(wallet.ERC1155Transfer, tokenIdentity)
|
|
|
|
}
|
|
|
|
|
2024-04-02 13:27:21 +00:00
|
|
|
func TestSendTransaction_Collectible_Routes(t *testing.T) {
|
2024-04-01 08:33:28 +00:00
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
data dataPayload
|
|
|
|
}{
|
|
|
|
{"ERC721", erc721Payload("0x9f64932be34d5d897c4253d17707b50921f372b6:37")},
|
|
|
|
{"ERC1155", erc1155Payload("0x1ed60fedff775d500dde21a974cd4e92e0047cc8:32")},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
_, close := setupAccountsAndTransactions(t)
|
|
|
|
defer close()
|
|
|
|
|
|
|
|
payload := dataToCallingPayload(tt.data)
|
|
|
|
res, err := helpers.CallPrivateMethodAndGetT[wallet.SuggestedRoutes]("wallet_getSuggestedRoutes", payload)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Greater(t, len(res.Candidates), 0)
|
|
|
|
})
|
2024-03-29 17:10:58 +00:00
|
|
|
}
|
|
|
|
}
|
2024-04-02 13:27:21 +00:00
|
|
|
|
|
|
|
func gweiToWei(gwei *big.Float) *big.Int {
|
|
|
|
weiMultiplier := big.NewFloat(1e9 /*10^9*/)
|
|
|
|
weiValue := new(big.Float).Mul(gwei, weiMultiplier)
|
|
|
|
res, _ := weiValue.Int(nil)
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestSendTransaction_Assets_KeepMTID is here to debug why the MTID is not kept
|
|
|
|
// when migrating a transaction from pending_transactions to transfers table
|
|
|
|
func TestSendTransaction_Assets_KeepMTID(t *testing.T) {
|
|
|
|
td, close := setupAccountsAndTransactions(t)
|
|
|
|
defer close()
|
|
|
|
|
|
|
|
chainID := common.OptimismSepolia
|
|
|
|
amount, ok := new(big.Int).SetString("0x3e8", 0)
|
|
|
|
require.True(t, ok)
|
|
|
|
info := dataPayload{
|
|
|
|
transferType: wallet.Transfer,
|
|
|
|
tokenIdentity: "USDC",
|
|
|
|
accountFrom: eth.HexToAddress("0xe2d622c817878da5143bbe06866ca8e35273ba8a"),
|
|
|
|
accountTo: eth.HexToAddress("0xbd54a96c0ae19a220c8e1234f54c940dfab34639"),
|
|
|
|
amount: (*hexutil.Big)(amount),
|
|
|
|
disabledFromChainIDs: []uint64{common.OptimismMainnet},
|
|
|
|
disabledToChainIDs: []uint64{common.OptimismMainnet},
|
|
|
|
preferredChainIDs: []uint64{chainID},
|
|
|
|
gasFeeMode: wallet.GasFeeHigh,
|
|
|
|
fromLockedAmount: map[uint64]*hexutil.Big{},
|
|
|
|
}
|
|
|
|
|
|
|
|
payload := dataToCallingPayload(info)
|
|
|
|
res, err := helpers.CallPrivateMethodAndGetT[wallet.SuggestedRoutes]("wallet_getSuggestedRoutes", payload)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Greater(t, len(res.Candidates), 0)
|
|
|
|
|
|
|
|
suggested := res.Candidates[0]
|
|
|
|
|
|
|
|
mtCmd := transfer.MultiTransactionCommand{
|
|
|
|
FromAddress: info.accountFrom,
|
|
|
|
ToAddress: info.accountTo,
|
|
|
|
FromAsset: info.tokenIdentity,
|
|
|
|
ToAsset: info.tokenIdentity,
|
|
|
|
FromAmount: info.amount,
|
|
|
|
Type: transfer.MultiTransactionSend,
|
|
|
|
}
|
|
|
|
|
|
|
|
abi, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI))
|
|
|
|
require.NoError(t, err)
|
|
|
|
input, err := abi.Pack("transfer",
|
|
|
|
info.accountTo,
|
|
|
|
info.amount.ToInt(),
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
txArgs := transactions.SendTxArgs{
|
|
|
|
From: types.Address(info.accountFrom),
|
|
|
|
To: common.NewAndSet(types.HexToAddress("0x5fd84259d66cd46123540766be93dfe6d43130d7")),
|
|
|
|
Gas: common.NewAndSet(hexutil.Uint64(suggested.GasAmount)),
|
|
|
|
GasPrice: (*hexutil.Big)(gweiToWei(suggested.GasFees.GasPrice)),
|
|
|
|
Value: (*hexutil.Big)(big.NewInt(0)),
|
|
|
|
MaxFeePerGas: (*hexutil.Big)(gweiToWei(suggested.GasFees.MaxFeePerGasHigh)),
|
|
|
|
MaxPriorityFeePerGas: (*hexutil.Big)(gweiToWei(suggested.GasFees.MaxPriorityFeePerGas)),
|
|
|
|
MultiTransactionID: common.NoMultiTransactionID,
|
|
|
|
Symbol: info.tokenIdentity,
|
|
|
|
Data: types.HexBytes(input),
|
|
|
|
}
|
|
|
|
|
|
|
|
bridge := []*bridge.TransactionBridge{{
|
|
|
|
BridgeName: suggested.BridgeName,
|
|
|
|
ChainID: chainID,
|
|
|
|
TransferTx: &txArgs,
|
|
|
|
}}
|
|
|
|
|
|
|
|
sendPayload := []interface{}{mtCmd, bridge, td.hashedPassword}
|
|
|
|
|
|
|
|
// Simulate the transaction creation as a result to SendModal user actions for an asset
|
|
|
|
sendRes, err := helpers.CallPrivateMethodAndGetT[transfer.MultiTransactionCommandResult]("wallet_createMultiTransaction", sendPayload)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, sendRes)
|
|
|
|
require.Greater(t, len(sendRes.Hashes), 0)
|
|
|
|
|
|
|
|
// Wait for transaction to be included in block
|
|
|
|
_, err = helpers.WaitForWalletEventsGetMap(
|
|
|
|
td.eventQueue, []walletevent.EventType{
|
|
|
|
transactions.EventPendingTransactionUpdate,
|
|
|
|
transactions.EventPendingTransactionStatusChanged,
|
|
|
|
},
|
|
|
|
20*time.Second,
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Start history download
|
|
|
|
_, err = helpers.CallPrivateMethod("wallet_checkRecentHistoryForChainIDs", []interface{}{[]uint64{chainID}, []types.Address{types.Address(info.accountFrom), types.Address(info.accountTo)}})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
newHash := eth.Hash(sendRes.Hashes[chainID][0])
|
|
|
|
// Wait for transaction to be deleted by downloader and also ensure we receive notification that transaction entries
|
|
|
|
// stored in transfers for the sender account
|
|
|
|
_, err = helpers.WaitForWalletEventsWithOptionals(
|
|
|
|
td.eventQueue,
|
|
|
|
[]walletevent.EventType{transactions.EventPendingTransactionUpdate, transfer.EventNewTransfers},
|
|
|
|
60*time.Second,
|
|
|
|
func(e *walletevent.Event) bool {
|
|
|
|
if e.Type == transactions.EventPendingTransactionUpdate {
|
|
|
|
update, err := walletevent.GetPayload[transactions.PendingTxUpdatePayload](*e)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
return update.Deleted && update.TxIdentity.Hash == newHash
|
|
|
|
} else if e.Type == transfer.EventNewTransfers {
|
|
|
|
for _, acc := range e.Accounts {
|
|
|
|
if acc == info.accountFrom {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if e.Type == transfer.EventFetchingHistoryError {
|
|
|
|
require.Fail(t, "History download failed")
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
[]walletevent.EventType{transfer.EventFetchingHistoryError},
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Ensure the transaction is not pending anymore
|
|
|
|
pendings, err := helpers.CallPrivateMethodAndGetSliceOfT[transactions.PendingTransaction]("wallet_getPendingTransactions", []interface{}{})
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, pendings, 0)
|
|
|
|
|
|
|
|
_, err = helpers.CallPrivateMethodAndGetT[activity.SessionID]("wallet_startActivityFilterSession", []interface{}{
|
|
|
|
[]eth.Address{info.accountFrom},
|
|
|
|
false, /* allAddresses */
|
|
|
|
[]common.ChainID{common.ChainID(chainID)},
|
|
|
|
activity.Filter{},
|
|
|
|
5,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
newMTID := sendRes.ID
|
|
|
|
// Confirm filtering results have a MT with expected MTID as last entry and the next one is not pending as reported
|
|
|
|
// by #14071 issue
|
|
|
|
resMap, err := helpers.WaitForWalletEventsGetMap(td.eventQueue, []walletevent.EventType{activity.EventActivityFilteringDone}, 5*time.Second)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, resMap, 1)
|
|
|
|
require.Equal(t, resMap[0].EventName, activity.EventActivityFilteringDone)
|
|
|
|
activityRes := resMap[0].JsonData
|
|
|
|
require.Equal(t, activity.ErrorCodeSuccess, activity.ErrorCode(int(activityRes["errorCode"].(float64))))
|
|
|
|
activities := activityRes["activities"].([](interface{}))
|
|
|
|
require.Greater(t, len(activities), 1)
|
|
|
|
lastEntry := activities[0].(map[string]interface{})
|
|
|
|
require.Equal(t, activity.MultiTransactionPT, activity.PayloadType(int(lastEntry["payloadType"].(float64))))
|
|
|
|
require.Equal(t, newMTID, int64(lastEntry["id"].(float64)))
|
|
|
|
if len(activities) > 1 {
|
|
|
|
secondEntry := activities[1].(map[string]interface{})
|
|
|
|
require.NotEqual(t, activity.PendingTransactionPT, activity.PayloadType(int(secondEntry["payloadType"].(float64))))
|
|
|
|
if tr, ok := secondEntry["transaction"]; ok {
|
|
|
|
require.NotEqual(t, newHash, tr.(map[string]interface{})["hash"].(eth.Hash))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|