github.com/status-im/status-go@v1.1.0/services/wallet/transfer/transaction_manager_test.go (about) 1 package transfer 2 3 import ( 4 "context" 5 "crypto/ecdsa" 6 "crypto/elliptic" 7 "crypto/rand" 8 "fmt" 9 "math/big" 10 "reflect" 11 "testing" 12 13 "github.com/golang/mock/gomock" 14 "github.com/stretchr/testify/require" 15 16 gethtypes "github.com/ethereum/go-ethereum/core/types" 17 "github.com/status-im/status-go/eth-node/types" 18 "github.com/status-im/status-go/multiaccounts/accounts" 19 wallet_common "github.com/status-im/status-go/services/wallet/common" 20 "github.com/status-im/status-go/transactions" 21 "github.com/status-im/status-go/transactions/mock_transactor" 22 23 "github.com/ethereum/go-ethereum/common" 24 "github.com/ethereum/go-ethereum/common/hexutil" 25 ) 26 27 type dummyAccountsStorage struct { 28 keypair *accounts.Keypair 29 account *accounts.Account 30 } 31 32 func (d *dummyAccountsStorage) GetAccountByAddress(address types.Address) (*accounts.Account, error) { 33 if address != d.account.Address { 34 return nil, fmt.Errorf("address not found") 35 } 36 return d.account, nil 37 } 38 39 func (d *dummyAccountsStorage) GetKeypairByKeyUID(keyUID string) (*accounts.Keypair, error) { 40 if keyUID != d.keypair.KeyUID { 41 return nil, fmt.Errorf("keyUID not found") 42 } 43 return d.keypair, nil 44 } 45 46 func (d *dummyAccountsStorage) AddressExists(address types.Address) (bool, error) { 47 return d.account.Address == address, nil 48 } 49 50 type dummySigner struct{} 51 52 func (d *dummySigner) Hash(tx *gethtypes.Transaction) common.Hash { 53 return common.HexToHash("0xc8e7a34af766c4ba9dc9b3d49939806fbf41fa01250c5a26afa5659e87b2020b") 54 } 55 56 func setupTestSuite(t *testing.T) (*TransactionManager, *mock_transactor.MockTransactorIface) { 57 SetMultiTransactionIDGenerator(StaticIDCounter()) // to have different multi-transaction IDs even with fast execution 58 accountsDB := setupAccountsStorage() 59 ctrl := gomock.NewController(t) 60 defer ctrl.Finish() 61 62 transactor := mock_transactor.NewMockTransactorIface(ctrl) 63 return &TransactionManager{ 64 storage: NewInMemMultiTransactionStorage(), 65 accountsDB: accountsDB, 66 transactor: transactor, 67 }, transactor 68 } 69 70 func setupAccountsStorage() *dummyAccountsStorage { 71 return &dummyAccountsStorage{ 72 keypair: &accounts.Keypair{ 73 KeyUID: "keyUid", 74 }, 75 account: &accounts.Account{ 76 KeyUID: "keyUid", 77 Address: types.Address{1}, 78 }, 79 } 80 } 81 82 func areMultiTransactionsEqual(mt1, mt2 *MultiTransaction) bool { 83 return mt1.Timestamp == mt2.Timestamp && 84 mt1.FromNetworkID == mt2.FromNetworkID && 85 mt1.ToNetworkID == mt2.ToNetworkID && 86 mt1.FromTxHash == mt2.FromTxHash && 87 mt1.ToTxHash == mt2.ToTxHash && 88 mt1.FromAddress == mt2.FromAddress && 89 mt1.ToAddress == mt2.ToAddress && 90 mt1.FromAsset == mt2.FromAsset && 91 mt1.ToAsset == mt2.ToAsset && 92 mt1.FromAmount.String() == mt2.FromAmount.String() && 93 mt1.ToAmount.String() == mt2.ToAmount.String() && 94 mt1.Type == mt2.Type && 95 mt1.CrossTxID == mt2.CrossTxID 96 } 97 98 func TestBridgeMultiTransactions(t *testing.T) { 99 manager, _ := setupTestSuite(t) 100 101 trx1 := NewMultiTransaction( 102 /* Timestamp: */ 123, 103 /* FromNetworkID: */ 0, 104 /* ToNetworkID: */ 1, 105 /* FromTxHash: */ common.Hash{5}, 106 /* // Empty ToTxHash */ common.Hash{}, 107 /* FromAddress: */ common.Address{1}, 108 /* ToAddress: */ common.Address{2}, 109 /* FromAsset: */ "fromAsset", 110 /* ToAsset: */ "toAsset", 111 /* FromAmount: */ (*hexutil.Big)(big.NewInt(123)), 112 /* ToAmount: */ (*hexutil.Big)(big.NewInt(234)), 113 /* Type: */ MultiTransactionBridge, 114 /* CrossTxID: */ "crossTxD1", 115 ) 116 117 trx2 := NewMultiTransaction( 118 /* Timestamp: */ 321, 119 /* FromNetworkID: */ 1, 120 /* ToNetworkID: */ 0, 121 /* //Empty FromTxHash */ common.Hash{}, 122 /* ToTxHash: */ common.Hash{6}, 123 /* FromAddress: */ common.Address{2}, 124 /* ToAddress: */ common.Address{1}, 125 /* FromAsset: */ "fromAsset", 126 /* ToAsset: */ "toAsset", 127 /* FromAmount: */ (*hexutil.Big)(big.NewInt(123)), 128 /* ToAmount: */ (*hexutil.Big)(big.NewInt(234)), 129 /* Type: */ MultiTransactionBridge, 130 /* CrossTxID: */ "crossTxD2", 131 ) 132 133 trxs := []*MultiTransaction{trx1, trx2} 134 135 var err error 136 ids := make([]wallet_common.MultiTransactionIDType, len(trxs)) 137 for i, trx := range trxs { 138 ids[i], err = manager.InsertMultiTransaction(trx) 139 require.NoError(t, err) 140 } 141 142 rst, err := manager.GetBridgeOriginMultiTransaction(context.Background(), trx1.ToNetworkID, trx1.CrossTxID) 143 require.NoError(t, err) 144 require.NotEmpty(t, rst) 145 require.True(t, areMultiTransactionsEqual(trx1, rst)) 146 147 rst, err = manager.GetBridgeDestinationMultiTransaction(context.Background(), trx1.ToNetworkID, trx1.CrossTxID) 148 require.NoError(t, err) 149 require.Empty(t, rst) 150 151 rst, err = manager.GetBridgeOriginMultiTransaction(context.Background(), trx2.ToNetworkID, trx2.CrossTxID) 152 require.NoError(t, err) 153 require.Empty(t, rst) 154 155 rst, err = manager.GetBridgeDestinationMultiTransaction(context.Background(), trx2.ToNetworkID, trx2.CrossTxID) 156 require.NoError(t, err) 157 require.NotEmpty(t, rst) 158 require.True(t, areMultiTransactionsEqual(trx2, rst)) 159 } 160 161 func TestMultiTransactions(t *testing.T) { 162 manager, _ := setupTestSuite(t) 163 164 trx1 := *NewMultiTransaction( 165 /* Timestamp: */ 123, 166 /* FromNetworkID:*/ 0, 167 /* ToNetworkID: */ 1, 168 /* FromTxHash: */ common.Hash{5}, 169 /* ToTxHash: */ common.Hash{6}, 170 /* FromAddress: */ common.Address{1}, 171 /* ToAddress: */ common.Address{2}, 172 /* FromAsset: */ "fromAsset", 173 /* ToAsset: */ "toAsset", 174 /* FromAmount: */ (*hexutil.Big)(big.NewInt(123)), 175 /* ToAmount: */ (*hexutil.Big)(big.NewInt(234)), 176 /* Type: */ MultiTransactionBridge, 177 /* CrossTxID: */ "crossTxD", 178 ) 179 trx2 := trx1 180 trx2.FromAmount = (*hexutil.Big)(big.NewInt(456)) 181 trx2.ToAmount = (*hexutil.Big)(big.NewInt(567)) 182 trx2.ID = multiTransactionIDGenerator() 183 184 require.NotEqual(t, trx1.ID, trx2.ID) 185 186 trxs := []*MultiTransaction{&trx1, &trx2} 187 188 var err error 189 ids := make([]wallet_common.MultiTransactionIDType, len(trxs)) 190 for i, trx := range trxs { 191 ids[i], err = manager.InsertMultiTransaction(trx) 192 require.NoError(t, err) 193 } 194 195 rst, err := manager.GetMultiTransactions(context.Background(), []wallet_common.MultiTransactionIDType{ids[0], 555}) 196 require.NoError(t, err) 197 require.Equal(t, 1, len(rst)) 198 require.True(t, areMultiTransactionsEqual(trxs[0], rst[0])) 199 200 trx1.FromAmount = (*hexutil.Big)(big.NewInt(789)) 201 trx1.ToAmount = (*hexutil.Big)(big.NewInt(890)) 202 err = manager.UpdateMultiTransaction(&trx1) 203 require.NoError(t, err) 204 205 rst, err = manager.GetMultiTransactions(context.Background(), ids) 206 require.NoError(t, err) 207 require.Equal(t, len(ids), len(rst)) 208 209 for i, id := range ids { 210 found := false 211 for _, trx := range rst { 212 if id == trx.ID { 213 found = true 214 require.True(t, areMultiTransactionsEqual(trxs[i], trx)) 215 break 216 } 217 } 218 require.True(t, found, "result contains transaction with id %d", id) 219 } 220 } 221 222 func TestSignMessage(t *testing.T) { 223 tm, _ := setupTestSuite(t) 224 225 message := (types.HexBytes)(make([]byte, 32)) 226 privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 227 require.NoError(t, err) 228 account := &types.Key{ 229 PrivateKey: privateKey, 230 } 231 232 signature, err := tm.SignMessage(message, account) 233 require.NoError(t, err) 234 require.NotEmpty(t, signature) 235 } 236 237 func TestSignMessage_InvalidAccount(t *testing.T) { 238 tm, _ := setupTestSuite(t) 239 240 message := (types.HexBytes)(make([]byte, 32)) 241 account := &types.Key{ 242 PrivateKey: nil, 243 } 244 245 signature, err := tm.SignMessage(message, account) 246 require.Error(t, err) 247 require.Empty(t, signature) 248 } 249 250 func TestSignMessage_InvalidMessage(t *testing.T) { 251 tm, _ := setupTestSuite(t) 252 253 message := types.HexBytes{} 254 privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 255 require.NoError(t, err) 256 account := &types.Key{ 257 PrivateKey: privateKey, 258 } 259 260 signature, err := tm.SignMessage(message, account) 261 require.Error(t, err) 262 require.Equal(t, "0x", signature) 263 } 264 265 func TestBuildTransaction(t *testing.T) { 266 manager, transactor := setupTestSuite(t) 267 268 chainID := uint64(1) 269 nonce := uint64(1) 270 gas := uint64(21000) 271 sendArgs := transactions.SendTxArgs{ 272 From: types.Address{1}, 273 To: &types.Address{2}, 274 Value: (*hexutil.Big)(big.NewInt(123)), 275 Nonce: (*hexutil.Uint64)(&nonce), 276 Gas: (*hexutil.Uint64)(&gas), 277 GasPrice: (*hexutil.Big)(big.NewInt(1000000000)), 278 MaxFeePerGas: (*hexutil.Big)(big.NewInt(2000000000)), 279 MaxPriorityFeePerGas: (*hexutil.Big)(big.NewInt(1000000000)), 280 } 281 282 expectedTx := gethtypes.NewTransaction(nonce, common.Address(*sendArgs.To), sendArgs.Value.ToInt(), gas, sendArgs.GasPrice.ToInt(), nil) 283 transactor.EXPECT().ValidateAndBuildTransaction(chainID, sendArgs, int64(-1)).Return(expectedTx, uint64(0), nil) 284 285 response, err := manager.BuildTransaction(chainID, sendArgs) 286 require.NoError(t, err) 287 require.NotNil(t, response) 288 289 accDB := manager.accountsDB.(*dummyAccountsStorage) 290 signer := dummySigner{} 291 expectedKeyUID := accDB.keypair.KeyUID 292 expectedAddress := accDB.account.Address 293 expectedAddressPath := "" 294 expectedSignOnKeycard := false 295 expectedMessageToSign := signer.Hash(expectedTx) 296 297 require.Equal(t, expectedKeyUID, response.KeyUID) 298 require.Equal(t, expectedAddress, response.Address) 299 require.Equal(t, expectedAddressPath, response.AddressPath) 300 require.Equal(t, expectedSignOnKeycard, response.SignOnKeycard) 301 require.Equal(t, chainID, response.ChainID) 302 require.Equal(t, expectedMessageToSign, response.MessageToSign) 303 require.True(t, reflect.DeepEqual(sendArgs, response.TxArgs)) 304 } 305 306 func TestBuildTransaction_AccountNotFound(t *testing.T) { 307 manager, _ := setupTestSuite(t) 308 309 chainID := uint64(1) 310 nonce := uint64(1) 311 gas := uint64(21000) 312 sendArgs := transactions.SendTxArgs{ 313 From: types.Address{2}, 314 To: &types.Address{2}, 315 Value: (*hexutil.Big)(big.NewInt(123)), 316 Nonce: (*hexutil.Uint64)(&nonce), 317 Gas: (*hexutil.Uint64)(&gas), 318 GasPrice: (*hexutil.Big)(big.NewInt(1000000000)), 319 MaxFeePerGas: (*hexutil.Big)(big.NewInt(2000000000)), 320 MaxPriorityFeePerGas: (*hexutil.Big)(big.NewInt(1000000000)), 321 } 322 323 _, err := manager.BuildTransaction(chainID, sendArgs) 324 require.Error(t, err) 325 } 326 327 func TestBuildTransaction_InvalidSendTxArgs(t *testing.T) { 328 manager, transactor := setupTestSuite(t) 329 330 chainID := uint64(1) 331 sendArgs := transactions.SendTxArgs{ 332 From: types.Address{1}, 333 To: &types.Address{2}, 334 } 335 336 expectedErr := fmt.Errorf("invalid SendTxArgs") 337 transactor.EXPECT().ValidateAndBuildTransaction(chainID, sendArgs, int64(-1)).Return(nil, uint64(0), expectedErr) 338 tx, err := manager.BuildTransaction(chainID, sendArgs) 339 require.Equal(t, expectedErr, err) 340 require.Nil(t, tx) 341 } 342 343 func TestBuildRawTransaction(t *testing.T) { 344 manager, transactor := setupTestSuite(t) 345 346 chainID := uint64(1) 347 nonce := uint64(1) 348 gas := uint64(21000) 349 sendArgs := transactions.SendTxArgs{ 350 From: types.Address{1}, 351 To: &types.Address{2}, 352 Value: (*hexutil.Big)(big.NewInt(123)), 353 Nonce: (*hexutil.Uint64)(&nonce), 354 Gas: (*hexutil.Uint64)(&gas), 355 GasPrice: (*hexutil.Big)(big.NewInt(1000000000)), 356 MaxFeePerGas: (*hexutil.Big)(big.NewInt(2000000000)), 357 MaxPriorityFeePerGas: (*hexutil.Big)(big.NewInt(1000000000)), 358 } 359 360 expectedTx := gethtypes.NewTransaction(1, common.Address(*sendArgs.To), sendArgs.Value.ToInt(), 21000, sendArgs.GasPrice.ToInt(), nil) 361 signature := []byte("signature") 362 transactor.EXPECT().BuildTransactionWithSignature(chainID, sendArgs, signature).Return(expectedTx, nil) 363 364 response, err := manager.BuildRawTransaction(chainID, sendArgs, signature) 365 require.NoError(t, err) 366 require.NotNil(t, response) 367 368 expectedData, _ := expectedTx.MarshalBinary() 369 expectedHash := expectedTx.Hash() 370 371 require.Equal(t, chainID, response.ChainID) 372 require.Equal(t, sendArgs, response.TxArgs) 373 require.Equal(t, types.EncodeHex(expectedData), response.RawTx) 374 require.Equal(t, expectedHash, response.TxHash) 375 }