diff --git a/api/geth_backend.go b/api/geth_backend.go index 8dd69cb24..b259f248f 100644 --- a/api/geth_backend.go +++ b/api/geth_backend.go @@ -1897,7 +1897,7 @@ func (b *GethStatusBackend) SendTransactionWithChainID(chainID uint64, sendArgs } func (b *GethStatusBackend) SendTransactionWithSignature(sendArgs transactions.SendTxArgs, sig []byte) (hash types.Hash, err error) { - hash, err = b.transactor.SendTransactionWithSignature(sendArgs, sig) + hash, err = b.transactor.SendTransactionWithSignature(b.transactor.NetworkID(), sendArgs, sig) if err != nil { return } diff --git a/go.mod b/go.mod index df8facdc7..b6b5f500f 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/status-im/status-go go 1.19 -replace github.com/ethereum/go-ethereum v1.10.26 => github.com/status-im/go-ethereum v1.10.25-status.7 +replace github.com/ethereum/go-ethereum v1.10.26 => github.com/status-im/go-ethereum v1.10.25-status.9 replace github.com/docker/docker => github.com/docker/engine v1.4.2-0.20190717161051-705d9623b7c1 diff --git a/go.sum b/go.sum index 95936f7ac..69ff26f2f 100644 --- a/go.sum +++ b/go.sum @@ -1982,8 +1982,8 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/status-im/doubleratchet v3.0.0+incompatible h1:aJ1ejcSERpSzmWZBgtfYtiU2nF0Q8ZkGyuEPYETXkCY= github.com/status-im/doubleratchet v3.0.0+incompatible/go.mod h1:1sqR0+yhiM/bd+wrdX79AOt2csZuJOni0nUDzKNuqOU= -github.com/status-im/go-ethereum v1.10.25-status.7 h1:egCCdvUQSdfnnDcv2vP1RCuIDr184eYlBvyucxuKJj8= -github.com/status-im/go-ethereum v1.10.25-status.7/go.mod h1:Dt4K5JYMhJRdtXJwBEyGZLZn9iz/chSOZyjVmt5ZhwQ= +github.com/status-im/go-ethereum v1.10.25-status.9 h1:NDuRs5TC4JjqPcYE8/sUtspdA+OwV1JRy3bbRLdIcL0= +github.com/status-im/go-ethereum v1.10.25-status.9/go.mod h1:Dt4K5JYMhJRdtXJwBEyGZLZn9iz/chSOZyjVmt5ZhwQ= github.com/status-im/go-multiaddr-ethv4 v1.2.5 h1:pN+ey6wYKbvNNu5/xq9+VL0N8Yq0pZUTbZp0URg+Yn4= github.com/status-im/go-multiaddr-ethv4 v1.2.5/go.mod h1:Fhe/18yWU5QwlAYiOO3Bb1BLe0bn5YobcNBHsjRr4kk= github.com/status-im/go-sqlcipher/v4 v4.5.4-status.2 h1:Oi9JTAI2DZEe5UKlpUcvKBCCSn3ULsLIrix7jPnEoPE= diff --git a/services/wallet/api.go b/services/wallet/api.go index a3dc136f9..00ecb3b8e 100644 --- a/services/wallet/api.go +++ b/services/wallet/api.go @@ -553,6 +553,11 @@ func (api *API) CreateMultiTransaction(ctx context.Context, multiTransactionComm return api.s.transactionManager.CreateMultiTransactionFromCommand(ctx, multiTransactionCommand, data, api.router.bridges, password) } +func (api *API) ProceedWithTransactionsSignatures(ctx context.Context, signatures map[string]transfer.SignatureDetails) (*transfer.MultiTransactionCommandResult, error) { + log.Debug("[WalletAPI:: ProceedWithTransactionsSignatures] sign with signatures and send multi transaction") + return api.s.transactionManager.ProceedWithTransactionsSignatures(ctx, signatures) +} + func (api *API) GetMultiTransactions(ctx context.Context, transactionIDs []transfer.MultiTransactionIDType) ([]*transfer.MultiTransaction, error) { log.Debug("wallet.api.GetMultiTransactions", "IDs.len", len(transactionIDs)) return api.s.transactionManager.GetMultiTransactions(ctx, transactionIDs) diff --git a/services/wallet/bridge/bridge.go b/services/wallet/bridge/bridge.go index 961fa83b9..86a20812a 100644 --- a/services/wallet/bridge/bridge.go +++ b/services/wallet/bridge/bridge.go @@ -94,4 +94,5 @@ type Bridge interface { CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error) GetContractAddress(network *params.Network, token *token.Token) *common.Address + BuildTransaction(sendArgs *TransactionBridge) (*ethTypes.Transaction, error) } diff --git a/services/wallet/bridge/cbridge.go b/services/wallet/bridge/cbridge.go index 0d10eac00..0d2a9f7c1 100644 --- a/services/wallet/bridge/cbridge.go +++ b/services/wallet/bridge/cbridge.go @@ -9,6 +9,7 @@ import ( "net/http" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethTypes "github.com/ethereum/go-ethereum/core/types" @@ -244,33 +245,32 @@ func (s *CBridge) GetContractAddress(network *params.Network, token *token.Token return nil } -func (s *CBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error) { +func (s *CBridge) sendOrBuild(sendArgs *TransactionBridge, signerFn bind.SignerFn) (*ethTypes.Transaction, error) { fromNetwork := s.rpcClient.NetworkManager.Find(sendArgs.ChainID) if fromNetwork == nil { - return types.HexToHash(""), errors.New("network not found") + return nil, errors.New("network not found") } tk := s.tokenManager.FindToken(fromNetwork, sendArgs.CbridgeTx.Symbol) if tk == nil { - return types.HexToHash(""), errors.New("token not found") + return nil, errors.New("token not found") } addrs := s.GetContractAddress(fromNetwork, nil) if addrs == nil { - return types.HexToHash(""), errors.New("contract not found") + return nil, errors.New("contract not found") } backend, err := s.rpcClient.EthClient(sendArgs.ChainID) if err != nil { - return types.HexToHash(""), err + return nil, err } contract, err := celer.NewCeler(*addrs, backend) if err != nil { - return types.HexToHash(""), err + return nil, err } - txOpts := sendArgs.CbridgeTx.ToTransactOpts(getSigner(sendArgs.ChainID, sendArgs.CbridgeTx.From, verifiedAccount)) - var tx *ethTypes.Transaction + txOpts := sendArgs.CbridgeTx.ToTransactOpts(signerFn) if tk.IsNative() { - tx, err = contract.SendNative( + return contract.SendNative( txOpts, sendArgs.CbridgeTx.Recipient, (*big.Int)(sendArgs.CbridgeTx.Amount), @@ -278,17 +278,21 @@ func (s *CBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.Sel uint64(time.Now().UnixMilli()), 500, ) - } else { - tx, err = contract.Send( - txOpts, - sendArgs.CbridgeTx.Recipient, - tk.Address, - (*big.Int)(sendArgs.CbridgeTx.Amount), - sendArgs.CbridgeTx.ChainID, - uint64(time.Now().UnixMilli()), - 500, - ) } + + return contract.Send( + txOpts, + sendArgs.CbridgeTx.Recipient, + tk.Address, + (*big.Int)(sendArgs.CbridgeTx.Amount), + sendArgs.CbridgeTx.ChainID, + uint64(time.Now().UnixMilli()), + 500, + ) +} + +func (s *CBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error) { + tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.CbridgeTx.From, verifiedAccount)) if err != nil { return types.HexToHash(""), err } @@ -296,6 +300,10 @@ func (s *CBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.Sel return types.Hash(tx.Hash()), nil } +func (s *CBridge) BuildTransaction(sendArgs *TransactionBridge) (*ethTypes.Transaction, error) { + return s.sendOrBuild(sendArgs, nil) +} + func (s *CBridge) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) { amt, err := s.estimateAmt(from, to, amountIn, symbol) if err != nil { diff --git a/services/wallet/bridge/erc721_transfer.go b/services/wallet/bridge/erc721_transfer.go index 80001d416..a907770b5 100644 --- a/services/wallet/bridge/erc721_transfer.go +++ b/services/wallet/bridge/erc721_transfer.go @@ -3,8 +3,10 @@ package bridge import ( "math/big" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/status-im/status-go/account" "github.com/status-im/status-go/contracts/community-tokens/collectibles" "github.com/status-im/status-go/eth-node/types" @@ -74,32 +76,43 @@ func (s *ERC721TransferBridge) EstimateGas(from, to *params.Network, account com return 80000, nil } -func (s *ERC721TransferBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) { +func (s *ERC721TransferBridge) sendOrBuild(sendArgs *TransactionBridge, signerFn bind.SignerFn) (tx *ethTypes.Transaction, err error) { ethClient, err := s.rpcClient.EthClient(sendArgs.ChainID) if err != nil { - return hash, err + return tx, err } + contract, err := collectibles.NewCollectibles(common.Address(*sendArgs.ERC721TransferTx.To), ethClient) if err != nil { - return hash, err + return tx, err } + nonce, unlock, err := s.transactor.NextNonce(s.rpcClient, sendArgs.ChainID, sendArgs.ERC721TransferTx.From) if err != nil { - return hash, err + return tx, err } defer func() { unlock(err == nil, nonce) }() argNonce := hexutil.Uint64(nonce) sendArgs.ERC721TransferTx.Nonce = &argNonce - txOpts := sendArgs.ERC721TransferTx.ToTransactOpts(getSigner(sendArgs.ChainID, sendArgs.ERC721TransferTx.From, verifiedAccount)) - tx, err := contract.SafeTransferFrom(txOpts, common.Address(sendArgs.ERC721TransferTx.From), sendArgs.ERC721TransferTx.Recipient, sendArgs.ERC721TransferTx.TokenID.ToInt()) + txOpts := sendArgs.ERC721TransferTx.ToTransactOpts(signerFn) + return contract.SafeTransferFrom(txOpts, common.Address(sendArgs.ERC721TransferTx.From), sendArgs.ERC721TransferTx.Recipient, + sendArgs.ERC721TransferTx.TokenID.ToInt()) +} + +func (s *ERC721TransferBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) { + tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.ERC721TransferTx.From, verifiedAccount)) if err != nil { return hash, err } return types.Hash(tx.Hash()), nil } +func (s *ERC721TransferBridge) BuildTransaction(sendArgs *TransactionBridge) (*ethTypes.Transaction, error) { + return s.sendOrBuild(sendArgs, nil) +} + func (s *ERC721TransferBridge) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) { return amountIn, nil } diff --git a/services/wallet/bridge/hop.go b/services/wallet/bridge/hop.go index 0e5aeadfb..8cb9f09a3 100644 --- a/services/wallet/bridge/hop.go +++ b/services/wallet/bridge/hop.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/status-im/status-go/account" "github.com/status-im/status-go/contracts" "github.com/status-im/status-go/contracts/hop" @@ -173,15 +174,15 @@ func (h *HopBridge) GetContractAddress(network *params.Network, token *token.Tok return &address } -func (h *HopBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) { +func (h *HopBridge) sendOrBuild(sendArgs *TransactionBridge, signerFn bind.SignerFn) (tx *ethTypes.Transaction, err error) { fromNetwork := h.contractMaker.RPCClient.NetworkManager.Find(sendArgs.ChainID) if fromNetwork == nil { - return hash, err + return tx, err } nonce, unlock, err := h.transactor.NextNonce(h.contractMaker.RPCClient, sendArgs.ChainID, sendArgs.HopTx.From) if err != nil { - return hash, err + return tx, err } defer func() { unlock(err == nil, nonce) @@ -191,25 +192,37 @@ func (h *HopBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.S token := h.tokenManager.FindToken(fromNetwork, sendArgs.HopTx.Symbol) if fromNetwork.Layer == 1 { - hash, err = h.sendToL2(sendArgs.ChainID, sendArgs.HopTx, verifiedAccount, token) - return hash, err + tx, err = h.sendToL2(sendArgs.ChainID, sendArgs.HopTx, signerFn, token) + return tx, err } - hash, err = h.swapAndSend(sendArgs.ChainID, sendArgs.HopTx, verifiedAccount, token) - return hash, err + tx, err = h.swapAndSend(sendArgs.ChainID, sendArgs.HopTx, signerFn, token) + return tx, err } -func (h *HopBridge) sendToL2(chainID uint64, hopArgs *HopTxArgs, verifiedAccount *account.SelectedExtKey, token *token.Token) (hash types.Hash, err error) { +func (h *HopBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) { + tx, err := h.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.HopTx.From, verifiedAccount)) + if err != nil { + return types.Hash{}, err + } + return types.Hash(tx.Hash()), nil +} + +func (h *HopBridge) BuildTransaction(sendArgs *TransactionBridge) (*ethTypes.Transaction, error) { + return h.sendOrBuild(sendArgs, nil) +} + +func (h *HopBridge) sendToL2(chainID uint64, hopArgs *HopTxArgs, signerFn bind.SignerFn, token *token.Token) (tx *ethTypes.Transaction, err error) { bridge, err := h.contractMaker.NewHopL1Bridge(chainID, hopArgs.Symbol) if err != nil { - return hash, err + return tx, err } - txOpts := hopArgs.ToTransactOpts(getSigner(chainID, hopArgs.From, verifiedAccount)) + txOpts := hopArgs.ToTransactOpts(signerFn) if token.IsNative() { txOpts.Value = (*big.Int)(hopArgs.Amount) } now := time.Now() deadline := big.NewInt(now.Unix() + 604800) - tx, err := bridge.SendToL2( + tx, err = bridge.SendToL2( txOpts, big.NewInt(int64(hopArgs.ChainID)), hopArgs.Recipient, @@ -220,25 +233,22 @@ func (h *HopBridge) sendToL2(chainID uint64, hopArgs *HopTxArgs, verifiedAccount big.NewInt(0), ) - if err != nil { - return hash, err - } - return types.Hash(tx.Hash()), nil + return tx, err } -func (h *HopBridge) swapAndSend(chainID uint64, hopArgs *HopTxArgs, verifiedAccount *account.SelectedExtKey, token *token.Token) (hash types.Hash, err error) { +func (h *HopBridge) swapAndSend(chainID uint64, hopArgs *HopTxArgs, signerFn bind.SignerFn, token *token.Token) (tx *ethTypes.Transaction, err error) { ammWrapper, err := h.contractMaker.NewHopL2AmmWrapper(chainID, hopArgs.Symbol) if err != nil { - return hash, err + return tx, err } - txOpts := hopArgs.ToTransactOpts(getSigner(chainID, hopArgs.From, verifiedAccount)) + txOpts := hopArgs.ToTransactOpts(signerFn) if token.IsNative() { txOpts.Value = (*big.Int)(hopArgs.Amount) } now := time.Now() deadline := big.NewInt(now.Unix() + 604800) - tx, err := ammWrapper.SwapAndSend( + tx, err = ammWrapper.SwapAndSend( txOpts, big.NewInt(int64(hopArgs.ChainID)), hopArgs.Recipient, @@ -250,11 +260,7 @@ func (h *HopBridge) swapAndSend(chainID uint64, hopArgs *HopTxArgs, verifiedAcco deadline, ) - if err != nil { - return hash, err - } - - return types.Hash(tx.Hash()), nil + return tx, err } // CalculateBonderFees logics come from: https://docs.hop.exchange/fee-calculation diff --git a/services/wallet/bridge/transfer.go b/services/wallet/bridge/transfer.go index 107ca5813..2b374d8d6 100644 --- a/services/wallet/bridge/transfer.go +++ b/services/wallet/bridge/transfer.go @@ -4,6 +4,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/status-im/status-go/account" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/params" @@ -44,6 +45,10 @@ func (s *TransferBridge) Send(sendArgs *TransactionBridge, verifiedAccount *acco return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, verifiedAccount) } +func (s *TransferBridge) BuildTransaction(sendArgs *TransactionBridge) (*ethTypes.Transaction, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx) +} + func (s *TransferBridge) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) { return amountIn, nil } diff --git a/services/wallet/transfer/commands_sequential_test.go b/services/wallet/transfer/commands_sequential_test.go index 39646e628..f1b5fe1eb 100644 --- a/services/wallet/transfer/commands_sequential_test.go +++ b/services/wallet/transfer/commands_sequential_test.go @@ -715,7 +715,7 @@ func TestFindBlocksCommand(t *testing.T) { db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) require.NoError(t, err) - tm := &TransactionManager{db, nil, nil, nil, nil, nil, nil} + tm := &TransactionManager{db, nil, nil, nil, nil, nil, nil, nil, nil, nil} wdb := NewDB(db) tc := &TestClient{ diff --git a/services/wallet/transfer/transaction.go b/services/wallet/transfer/transaction.go index 2ee1f4fe8..473403f61 100644 --- a/services/wallet/transfer/transaction.go +++ b/services/wallet/transfer/transaction.go @@ -3,17 +3,21 @@ package transfer import ( "context" "database/sql" + "encoding/hex" "errors" "fmt" "math/big" "strings" "time" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/status-im/status-go/account" + "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/params" @@ -21,6 +25,7 @@ import ( "github.com/status-im/status-go/services/wallet/bridge" wallet_common "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/walletevent" + "github.com/status-im/status-go/signal" "github.com/status-im/status-go/transactions" ) @@ -33,6 +38,18 @@ const ( EventMTTransactionUpdate walletevent.EventType = "multi-transaction-update" ) +type SignatureDetails struct { + R string `json:"r"` + S string `json:"s"` + V string `json:"v"` +} + +type TransactionDescription struct { + chainID uint64 + builtTx *ethTypes.Transaction + signature []byte +} + type TransactionManager struct { db *sql.DB gethManager *account.GethManager @@ -41,6 +58,10 @@ type TransactionManager struct { accountsDB *accounts.Database pendingTracker *transactions.PendingTxTracker eventFeed *event.Feed + + multiTransactionForKeycardSigning *MultiTransaction + transactionsBridgeData []*bridge.TransactionBridge + transactionsForKeycardSingning map[common.Hash]*TransactionDescription } func NewTransactionManager( @@ -271,6 +292,7 @@ func (tm *TransactionManager) UpdateMultiTransaction(multiTransaction *MultiTran return updateMultiTransaction(tm.db, multiTransaction) } +// In case of keycard account, password should be empty func (tm *TransactionManager) CreateMultiTransactionFromCommand(ctx context.Context, command *MultiTransactionCommand, data []*bridge.TransactionBridge, bridges map[string]bridge.Bridge, password string) (*MultiTransactionCommandResult, error) { @@ -286,6 +308,33 @@ func (tm *TransactionManager) CreateMultiTransactionFromCommand(ctx context.Cont } multiTransaction.ID = uint(multiTransactionID) + if password == "" { + acc, err := tm.accountsDB.GetAccountByAddress(types.Address(multiTransaction.FromAddress)) + if err != nil { + return nil, err + } + + kp, err := tm.accountsDB.GetKeypairByKeyUID(acc.KeyUID) + if err != nil { + return nil, err + } + + if !kp.MigratedToKeycard() { + return nil, fmt.Errorf("account being used is not migrated to a keycard, password is required") + } + + tm.multiTransactionForKeycardSigning = multiTransaction + tm.transactionsBridgeData = data + hashes, err := tm.buildTransactions(bridges) + if err != nil { + return nil, err + } + + signal.SendTransactionsForSigningEvent(hashes) + + return nil, nil + } + hashes, err := tm.sendTransactions(multiTransaction, data, bridges, password) if err != nil { return nil, err @@ -302,6 +351,61 @@ func (tm *TransactionManager) CreateMultiTransactionFromCommand(ctx context.Cont }, nil } +func (tm *TransactionManager) ProceedWithTransactionsSignatures(ctx context.Context, signatures map[string]SignatureDetails) (*MultiTransactionCommandResult, error) { + if tm.multiTransactionForKeycardSigning == nil { + return nil, errors.New("no multi transaction to proceed with") + } + if len(tm.transactionsBridgeData) == 0 { + return nil, errors.New("no transactions bridge data to proceed with") + } + if len(tm.transactionsForKeycardSingning) == 0 { + return nil, errors.New("no transactions to proceed with") + } + if len(signatures) != len(tm.transactionsForKeycardSingning) { + return nil, errors.New("not all transactions have been signed") + } + + // check if all transactions have been signed + for hash, desc := range tm.transactionsForKeycardSingning { + sigDetails, ok := signatures[hash.String()] + if !ok { + return nil, fmt.Errorf("missing signature for transaction %s", hash) + } + + rBytes, _ := hex.DecodeString(sigDetails.R) + sBytes, _ := hex.DecodeString(sigDetails.S) + vByte := byte(0) + if sigDetails.V == "1" { + vByte = 1 + } + + desc.signature = make([]byte, crypto.SignatureLength) + copy(desc.signature[32-len(rBytes):32], rBytes) + copy(desc.signature[64-len(rBytes):64], sBytes) + desc.signature[64] = vByte + } + + // send transactions + hashes := make(map[uint64][]types.Hash) + for _, desc := range tm.transactionsForKeycardSingning { + hash, err := tm.transactor.SendBuiltTransactionWithSignature(desc.chainID, desc.builtTx, desc.signature) + if err != nil { + return nil, err + } + hashes[desc.chainID] = append(hashes[desc.chainID], hash) + } + + err := tm.storePendingTransactions(tm.multiTransactionForKeycardSigning, hashes, tm.transactionsBridgeData) + if err != nil { + return nil, err + } + + return &MultiTransactionCommandResult{ + ID: int64(tm.multiTransactionForKeycardSigning.ID), + Hashes: hashes, + }, nil +} + func (tm *TransactionManager) storePendingTransactions(multiTransaction *MultiTransaction, hashes map[uint64][]types.Hash, data []*bridge.TransactionBridge) error { @@ -359,6 +463,29 @@ func multiTransactionFromCommand(command *MultiTransactionCommand) *MultiTransac return multiTransaction } +func (tm *TransactionManager) buildTransactions(bridges map[string]bridge.Bridge) ([]string, error) { + tm.transactionsForKeycardSingning = make(map[common.Hash]*TransactionDescription) + var hashes []string + for _, bridgeTx := range tm.transactionsBridgeData { + builtTx, err := bridges[bridgeTx.BridgeName].BuildTransaction(bridgeTx) + if err != nil { + return hashes, err + } + + signer := ethTypes.NewLondonSigner(big.NewInt(int64(bridgeTx.ChainID))) + txHash := signer.Hash(builtTx) + + tm.transactionsForKeycardSingning[txHash] = &TransactionDescription{ + chainID: bridgeTx.ChainID, + builtTx: builtTx, + } + + hashes = append(hashes, txHash.String()) + } + + return hashes, nil +} + func (tm *TransactionManager) sendTransactions(multiTransaction *MultiTransaction, data []*bridge.TransactionBridge, bridges map[string]bridge.Bridge, password string) ( map[uint64][]types.Hash, error) { diff --git a/services/wallet/transfer/transaction_test.go b/services/wallet/transfer/transaction_test.go index e839c8a3b..c45c8db93 100644 --- a/services/wallet/transfer/transaction_test.go +++ b/services/wallet/transfer/transaction_test.go @@ -17,7 +17,7 @@ import ( func setupTestTransactionDB(t *testing.T) (*TransactionManager, func()) { db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{}) require.NoError(t, err) - return &TransactionManager{db, nil, nil, nil, nil, nil, nil}, func() { + return &TransactionManager{db, nil, nil, nil, nil, nil, nil, nil, nil, nil}, func() { require.NoError(t, db.Close()) } } diff --git a/signal/events_wallet.go b/signal/events_wallet.go index 3cc493153..dac4ee1a1 100644 --- a/signal/events_wallet.go +++ b/signal/events_wallet.go @@ -4,7 +4,16 @@ const ( walletEvent = "wallet" ) +type UnsignedTransactions struct { + Type string `json:"type"` + Transactions []string `json:"transactions"` +} + // SendWalletEvent sends event from services/wallet/events. func SendWalletEvent(event interface{}) { send(walletEvent, event) } + +func SendTransactionsForSigningEvent(transactions []string) { + send(walletEvent, UnsignedTransactions{Type: "sing-transactions", Transactions: transactions}) +} diff --git a/transactions/transactor.go b/transactions/transactor.go index 79b27675d..00768fcb2 100644 --- a/transactions/transactor.go +++ b/transactions/transactor.go @@ -26,7 +26,7 @@ const ( defaultGas = 90000 - validSignatureSize = 65 + ValidSignatureSize = 65 ) // ErrInvalidSignatureSize is returned if a signature is not 65 bytes to avoid panic from go-ethereum @@ -93,23 +93,50 @@ func (t *Transactor) SendTransactionWithChainID(chainID uint64, sendArgs SendTxA return } +func (t *Transactor) ValidateAndBuildTransaction(chainID uint64, sendArgs SendTxArgs) (tx *gethtypes.Transaction, err error) { + wrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID) + tx, err = t.validateAndBuildTransaction(wrapper, sendArgs) + return +} + +func (t *Transactor) SendBuiltTransactionWithSignature(chainID uint64, tx *gethtypes.Transaction, sig []byte) (hash types.Hash, err error) { + if len(sig) != ValidSignatureSize { + return hash, ErrInvalidSignatureSize + } + + rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID) + chID := big.NewInt(int64(rpcWrapper.chainID)) + + signer := gethtypes.NewLondonSigner(chID) + signedTx, err := tx.WithSignature(signer, sig) + if err != nil { + return hash, err + } + + ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout) + defer cancel() + + if err := rpcWrapper.SendTransaction(ctx, signedTx); err != nil { + return hash, err + } + return types.Hash(signedTx.Hash()), nil +} + // SendTransactionWithSignature receive a transaction and a signature, serialize them together and propage it to the network. // It's different from eth_sendRawTransaction because it receives a signature and not a serialized transaction with signature. // Since the transactions is already signed, we assume it was validated and used the right nonce. -func (t *Transactor) SendTransactionWithSignature(args SendTxArgs, sig []byte) (hash types.Hash, err error) { +func (t *Transactor) SendTransactionWithSignature(chainID uint64, args SendTxArgs, sig []byte) (hash types.Hash, err error) { if !args.Valid() { return hash, ErrInvalidSendTxArgs } - if len(sig) != validSignatureSize { + if len(sig) != ValidSignatureSize { return hash, ErrInvalidSignatureSize } - chainID := big.NewInt(int64(t.networkID)) - signer := gethtypes.NewLondonSigner(chainID) - tx := t.buildTransaction(args) - expectedNonce, unlock, err := t.nonce.Next(t.rpcWrapper, args.From) + rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, chainID) + expectedNonce, unlock, err := t.nonce.Next(rpcWrapper, args.From) if err != nil { return hash, err } @@ -121,18 +148,7 @@ func (t *Transactor) SendTransactionWithSignature(args SendTxArgs, sig []byte) ( return hash, &ErrBadNonce{tx.Nonce(), expectedNonce} } - signedTx, err := tx.WithSignature(signer, sig) - if err != nil { - return hash, err - } - - ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout) - defer cancel() - - if err := t.rpcWrapper.SendTransaction(ctx, signedTx); err != nil { - return hash, err - } - return types.Hash(signedTx.Hash()), nil + return t.SendBuiltTransactionWithSignature(chainID, tx, sig) } func (t *Transactor) HashTransaction(args SendTxArgs) (validatedArgs SendTxArgs, hash types.Hash, err error) { @@ -235,18 +251,14 @@ func (t *Transactor) validateAccount(args SendTxArgs, selectedAccount *account.S return nil } -func (t *Transactor) validateAndPropagate(rpcWrapper *rpcWrapper, selectedAccount *account.SelectedExtKey, args SendTxArgs) (hash types.Hash, err error) { - if err = t.validateAccount(args, selectedAccount); err != nil { - return hash, err - } - +func (t *Transactor) validateAndBuildTransaction(rpcWrapper *rpcWrapper, args SendTxArgs) (tx *gethtypes.Transaction, err error) { if !args.Valid() { - return hash, ErrInvalidSendTxArgs + return tx, ErrInvalidSendTxArgs } nonce, unlock, err := t.nonce.Next(rpcWrapper, args.From) if err != nil { - return hash, err + return tx, err } if args.Nonce != nil { nonce = uint64(*args.Nonce) @@ -261,10 +273,10 @@ func (t *Transactor) validateAndPropagate(rpcWrapper *rpcWrapper, selectedAccoun if !args.IsDynamicFeeTx() && args.GasPrice == nil { gasPrice, err = rpcWrapper.SuggestGasPrice(ctx) if err != nil { - return hash, err + return tx, err } } - chainID := big.NewInt(int64(rpcWrapper.chainID)) + value := (*big.Int)(args.Value) var gas uint64 if args.Gas != nil { @@ -289,20 +301,34 @@ func (t *Transactor) validateAndPropagate(rpcWrapper *rpcWrapper, selectedAccoun Data: args.GetInput(), }) if err != nil { - return hash, err + return tx, err } if gas < defaultGas { t.log.Info("default gas will be used because estimated is lower", "estimated", gas, "default", defaultGas) gas = defaultGas } } - tx := t.buildTransactionWithOverrides(nonce, value, gas, gasPrice, args) + tx = t.buildTransactionWithOverrides(nonce, value, gas, gasPrice, args) + return tx, nil +} + +func (t *Transactor) validateAndPropagate(rpcWrapper *rpcWrapper, selectedAccount *account.SelectedExtKey, args SendTxArgs) (hash types.Hash, err error) { + if err = t.validateAccount(args, selectedAccount); err != nil { + return hash, err + } + + tx, err := t.validateAndBuildTransaction(rpcWrapper, args) + if err != nil { + return hash, err + } + + chainID := big.NewInt(int64(rpcWrapper.chainID)) signedTx, err := gethtypes.SignTx(tx, gethtypes.NewLondonSigner(chainID), selectedAccount.AccountKey.PrivateKey) if err != nil { return hash, err } - // ctx, cancel = context.WithTimeout(context.Background(), t.rpcCallTimeout) - // defer cancel() + ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout) + defer cancel() if err := rpcWrapper.SendTransaction(ctx, signedTx); err != nil { return hash, err diff --git a/transactions/transactor_test.go b/transactions/transactor_test.go index 549e7ca94..7b4b23760 100644 --- a/transactions/transactor_test.go +++ b/transactions/transactor_test.go @@ -374,7 +374,7 @@ func (s *TransactorSuite) TestSendTransactionWithSignature() { Return(common.Hash{}, nil) } - _, err = s.manager.SendTransactionWithSignature(args, sig) + _, err = s.manager.SendTransactionWithSignature(s.nodeConfig.NetworkID, args, sig) if scenario.expectError { s.Error(err) // local nonce should not be incremented @@ -393,7 +393,7 @@ func (s *TransactorSuite) TestSendTransactionWithSignature() { func (s *TransactorSuite) TestSendTransactionWithSignature_InvalidSignature() { args := SendTxArgs{} - _, err := s.manager.SendTransactionWithSignature(args, []byte{}) + _, err := s.manager.SendTransactionWithSignature(1, args, []byte{}) s.Equal(ErrInvalidSignatureSize, err) } diff --git a/transactions/types.go b/transactions/types.go index 6f83ce1b9..30705d0e6 100644 --- a/transactions/types.go +++ b/transactions/types.go @@ -106,6 +106,11 @@ func (args SendTxArgs) ToTransactOpts(signerFn bind.SignerFn) *bind.TransactOpts gasLimit = uint64(*args.Gas) } + var noSign = false + if signerFn == nil { + noSign = true + } + return &bind.TransactOpts{ From: common.Address(args.From), Signer: signerFn, @@ -114,6 +119,7 @@ func (args SendTxArgs) ToTransactOpts(signerFn bind.SignerFn) *bind.TransactOpts GasFeeCap: gasFeeCap, GasTipCap: gasTipCap, Nonce: nonce, + NoSign: noSign, } } diff --git a/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/base.go b/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/base.go index 88b997684..eef000a4c 100644 --- a/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/base.go +++ b/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/base.go @@ -61,6 +61,7 @@ type TransactOpts struct { Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) + NoSign bool // Do all transact steps but do not sign or send the transaction NoSend bool // Do all transact steps but do not send the transaction } @@ -387,6 +388,9 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i if err != nil { return nil, err } + if opts.NoSign { + return rawTx, nil + } // Sign the transaction and schedule it for execution if opts.Signer == nil { return nil, errors.New("no signer to authorize the transaction with") diff --git a/vendor/modules.txt b/vendor/modules.txt index 430906444..bc83aa8d0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -208,7 +208,7 @@ github.com/edsrzf/mmap-go ## explicit; go 1.14 github.com/elastic/gosigar github.com/elastic/gosigar/sys/windows -# github.com/ethereum/go-ethereum v1.10.26 => github.com/status-im/go-ethereum v1.10.25-status.7 +# github.com/ethereum/go-ethereum v1.10.26 => github.com/status-im/go-ethereum v1.10.25-status.9 ## explicit; go 1.17 github.com/ethereum/go-ethereum github.com/ethereum/go-ethereum/accounts