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  }