From 05baec8bece7d29530a3a8c77522f1d3ba110b93 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Thu, 2 Nov 2023 12:35:28 +0100 Subject: [PATCH] fix: estimate gas function resoved that it uses real network estimation instead of hardcoded value --- services/wallet/api.go | 6 +- services/wallet/bridge/bridge.go | 4 +- services/wallet/bridge/cbridge.go | 72 ++++++++++++- services/wallet/bridge/erc721_transfer.go | 77 +++++++++----- services/wallet/bridge/hop.go | 122 +++++++++++++++------- services/wallet/bridge/transfer.go | 12 +-- services/wallet/router.go | 15 +-- transactions/transactor.go | 16 +++ 8 files changed, 239 insertions(+), 85 deletions(-) diff --git a/services/wallet/api.go b/services/wallet/api.go index f19dc11e3..ab367fae3 100644 --- a/services/wallet/api.go +++ b/services/wallet/api.go @@ -451,7 +451,8 @@ func (api *API) GetTransactionEstimatedTime(ctx context.Context, chainID uint64, func (api *API) GetSuggestedRoutes( ctx context.Context, sendType SendType, - account common.Address, + addrFrom common.Address, + addrTo common.Address, amountIn *hexutil.Big, tokenID string, disabledFromChainIDs, @@ -461,7 +462,8 @@ func (api *API) GetSuggestedRoutes( fromLockedAmount map[uint64]*hexutil.Big, ) (*SuggestedRoutes, error) { log.Debug("call to GetSuggestedRoutes") - return api.router.suggestedRoutes(ctx, sendType, account, amountIn.ToInt(), tokenID, disabledFromChainIDs, disabledToChaindIDs, preferedChainIDs, gasFeeMode, fromLockedAmount) + return api.router.suggestedRoutes(ctx, sendType, addrFrom, addrTo, amountIn.ToInt(), tokenID, disabledFromChainIDs, + disabledToChaindIDs, preferedChainIDs, gasFeeMode, fromLockedAmount) } // Generates addresses for the provided paths, response doesn't include `HasActivity` value (if you need it check `GetAddressDetails` function) diff --git a/services/wallet/bridge/bridge.go b/services/wallet/bridge/bridge.go index 86a20812a..057e45ab6 100644 --- a/services/wallet/bridge/bridge.go +++ b/services/wallet/bridge/bridge.go @@ -14,6 +14,8 @@ import ( "github.com/status-im/status-go/transactions" ) +const IncreaseEstimatedGasFactor = 1.1 + func getSigner(chainID uint64, from types.Address, verifiedAccount *account.SelectedExtKey) bind.SignerFn { return func(addr common.Address, tx *ethTypes.Transaction) (*ethTypes.Transaction, error) { s := ethTypes.NewLondonSigner(new(big.Int).SetUint64(chainID)) @@ -90,7 +92,7 @@ type Bridge interface { Name() string Can(from *params.Network, to *params.Network, token *token.Token, balance *big.Int) (bool, error) CalculateFees(from, to *params.Network, token *token.Token, amountIn *big.Int, nativeTokenPrice, tokenPrice float64, gasPrice *big.Float) (*big.Int, *big.Int, error) - EstimateGas(from *params.Network, to *params.Network, account common.Address, token *token.Token, amountIn *big.Int) (uint64, error) + EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) (uint64, error) 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 diff --git a/services/wallet/bridge/cbridge.go b/services/wallet/bridge/cbridge.go index 0d2a9f7c1..c075a326e 100644 --- a/services/wallet/bridge/cbridge.go +++ b/services/wallet/bridge/cbridge.go @@ -1,14 +1,18 @@ package bridge import ( + "context" "encoding/json" "errors" "fmt" "io/ioutil" "math/big" "net/http" + "strings" "time" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -217,13 +221,71 @@ func (s *CBridge) CalculateFees(from, to *params.Network, token *token.Token, am return big.NewInt(0), new(big.Int).Add(baseFee, percFee), nil } -func (s *CBridge) EstimateGas(from, to *params.Network, account common.Address, token *token.Token, amountIn *big.Int) (uint64, error) { - // TODO: replace by estimate function - if token.IsNative() { - return 22000, nil // default gas limit for eth transaction +func (s *CBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) (uint64, error) { + var input []byte + value := new(big.Int) + + abi, err := abi.JSON(strings.NewReader(celer.CelerABI)) + if err != nil { + return 0, err } - return 200000, nil //default gas limit for erc20 transaction + if token.IsNative() { + input, err = abi.Pack("sendNative", + to, + amountIn, + toNetwork.ChainID, + uint64(time.Now().UnixMilli()), + 500, + ) + if err != nil { + return 0, err + } + } else { + input, err = abi.Pack("send", + to, + token.Address, + amountIn, + toNetwork.ChainID, + uint64(time.Now().UnixMilli()), + 500, + ) + if err != nil { + return 0, err + } + } + + contractAddress := s.GetContractAddress(fromNetwork, nil) + if contractAddress == nil { + return 0, errors.New("contract not found") + } + + ethClient, err := s.rpcClient.EthClient(fromNetwork.ChainID) + if err != nil { + return 0, err + } + + ctx := context.Background() + + if code, err := ethClient.PendingCodeAt(ctx, *contractAddress); err != nil { + return 0, err + } else if len(code) == 0 { + return 0, bind.ErrNoCode + } + + msg := ethereum.CallMsg{ + From: from, + To: contractAddress, + Value: value, + Data: input, + } + + estimation, err := ethClient.EstimateGas(ctx, msg) + if err != nil { + return 0, err + } + increasedEstimation := float64(estimation) * IncreaseEstimatedGasFactor + return uint64(increasedEstimation), nil } func (s *CBridge) GetContractAddress(network *params.Network, token *token.Token) *common.Address { diff --git a/services/wallet/bridge/erc721_transfer.go b/services/wallet/bridge/erc721_transfer.go index a907770b5..99e62859b 100644 --- a/services/wallet/bridge/erc721_transfer.go +++ b/services/wallet/bridge/erc721_transfer.go @@ -1,8 +1,12 @@ package bridge import ( + "context" "math/big" + "strings" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -43,37 +47,52 @@ func (s *ERC721TransferBridge) CalculateFees(from, to *params.Network, token *to return big.NewInt(0), big.NewInt(0), nil } -func (s *ERC721TransferBridge) EstimateGas(from, to *params.Network, account common.Address, token *token.Token, amountIn *big.Int) (uint64, error) { - // ethClient, err := s.rpcClient.EthClient(from.ChainID) - // if err != nil { - // return 0, err - // } - // collectiblesABI, err := abi.JSON(strings.NewReader(collectibles.CollectiblesABI)) - // if err != nil { - // return 0, err - // } +func (s *ERC721TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) (uint64, error) { + ethClient, err := s.rpcClient.EthClient(fromNetwork.ChainID) + if err != nil { + return 0, err + } - // toAddress := common.HexToAddress("0x0") - // tokenID, success := new(big.Int).SetString(token.Symbol, 10) - // if !success { - // return 0, err - // } + var input []byte + value := new(big.Int) - // data, err := collectiblesABI.Pack("safeTransferFrom", account, toAddress, tokenID) - // if err != nil { - // return 0, err - // } - // estimate, err := ethClient.EstimateGas(context.Background(), ethereum.CallMsg{ - // From: account, - // To: &toAddress, - // Value: big.NewInt(0), - // Data: data, - // }) - // if err != nil { - // return 0, err - // } - // return estimate + 1000, nil - return 80000, nil + contractAddress := to + + abi, err := abi.JSON(strings.NewReader(collectibles.CollectiblesMetaData.ABI)) + if err != nil { + return 0, err + } + + input, err = abi.Pack("safeTransferFrom", + from, + to, + new(big.Int)) + + if err != nil { + return 0, err + } + + ctx := context.Background() + + if code, err := ethClient.PendingCodeAt(ctx, contractAddress); err != nil { + return 0, err + } else if len(code) == 0 { + return 0, bind.ErrNoCode + } + + msg := ethereum.CallMsg{ + From: from, + To: &contractAddress, + Value: value, + Data: input, + } + + estimation, err := ethClient.EstimateGas(ctx, msg) + if err != nil { + return 0, err + } + increasedEstimation := float64(estimation) * IncreaseEstimatedGasFactor + return uint64(increasedEstimation), nil } func (s *ERC721TransferBridge) sendOrBuild(sendArgs *TransactionBridge, signerFn bind.SignerFn) (tx *ethTypes.Transaction, err error) { diff --git a/services/wallet/bridge/hop.go b/services/wallet/bridge/hop.go index 8cb9f09a3..0e9fc0236 100644 --- a/services/wallet/bridge/hop.go +++ b/services/wallet/bridge/hop.go @@ -2,10 +2,14 @@ package bridge import ( "context" + "errors" "math" "math/big" + "strings" "time" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -13,6 +17,8 @@ import ( "github.com/status-im/status-go/account" "github.com/status-im/status-go/contracts" "github.com/status-im/status-go/contracts/hop" + hopBridge "github.com/status-im/status-go/contracts/hop/bridge" + hopWrapper "github.com/status-im/status-go/contracts/hop/wrapper" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/params" "github.com/status-im/status-go/rpc" @@ -126,41 +132,87 @@ func (h *HopBridge) Can(from, to *params.Network, token *token.Token, balance *b return true, nil } -func (h *HopBridge) EstimateGas(from, to *params.Network, account common.Address, token *token.Token, amountIn *big.Int) (uint64, error) { - // TODO: find why this doesn't work - // ethClient, err := s.contractMaker.RPCClient.EthClient(from.ChainID) - // if err != nil { - // return 0, err - // } - // zero := common.HexToAddress("0x0") - // zeroInt := big.NewInt(0) - // var data []byte - // if from.Layer == 1 { - // bridgeABI, err := abi.JSON(strings.NewReader(hopBridge.HopBridgeABI)) - // if err != nil { - // return 0, err - // } - // data, err = bridgeABI.Pack("sendToL2", big.NewInt(int64(to.ChainID)), zero, amountIn, zeroInt, zeroInt, zero, zeroInt) - // if err != nil { - // return 0, err - // } - // } else { - // wrapperABI, err := abi.JSON(strings.NewReader(hopWrapper.HopWrapperABI)) - // if err != nil { - // return 0, err - // } - // data, err = wrapperABI.Pack("swapAndSend", big.NewInt(int64(to.ChainID)), zero, amountIn, zeroInt, zeroInt, zeroInt, zeroInt, zeroInt) - // if err != nil { - // return 0, err - // } - // } - // estimate, err := ethClient.EstimateGas(context.Background(), ethereum.CallMsg{ - // From: zero, - // To: &token.Address, - // Value: big.NewInt(0), - // Data: data, - // }) - return 500000 + 1000, nil +func (h *HopBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) (uint64, error) { + var input []byte + value := new(big.Int) + + now := time.Now() + deadline := big.NewInt(now.Unix() + 604800) + + if token.IsNative() { + value = amountIn + } + + contractAddress := h.GetContractAddress(fromNetwork, token) + if contractAddress == nil { + return 0, errors.New("contract not found") + } + + ctx := context.Background() + + if fromNetwork.Layer == 1 { + ABI, err := abi.JSON(strings.NewReader(hopBridge.HopBridgeABI)) + if err != nil { + return 0, err + } + + input, err = ABI.Pack("sendToL2", + big.NewInt(int64(toNetwork.ChainID)), + to, + amountIn, + big.NewInt(0), + deadline, + common.HexToAddress("0x0"), + big.NewInt(0)) + + if err != nil { + return 0, err + } + } else { + ABI, err := abi.JSON(strings.NewReader(hopWrapper.HopWrapperABI)) + if err != nil { + return 0, err + } + + input, err = ABI.Pack("swapAndSend", + big.NewInt(int64(toNetwork.ChainID)), + to, + amountIn, + big.NewInt(0), + big.NewInt(0), + deadline, + big.NewInt(0), + deadline) + + if err != nil { + return 0, err + } + } + + ethClient, err := h.contractMaker.RPCClient.EthClient(fromNetwork.ChainID) + if err != nil { + return 0, err + } + + if code, err := ethClient.PendingCodeAt(ctx, *contractAddress); err != nil { + return 0, err + } else if len(code) == 0 { + return 0, bind.ErrNoCode + } + + msg := ethereum.CallMsg{ + From: from, + To: contractAddress, + Value: value, + Data: input, + } + + estimation, err := ethClient.EstimateGas(ctx, msg) + if err != nil { + return 0, err + } + increasedEstimation := float64(estimation) * IncreaseEstimatedGasFactor + return uint64(increasedEstimation), nil } func (h *HopBridge) GetContractAddress(network *params.Network, token *token.Token) *common.Address { diff --git a/services/wallet/bridge/transfer.go b/services/wallet/bridge/transfer.go index 2b374d8d6..39b03b28a 100644 --- a/services/wallet/bridge/transfer.go +++ b/services/wallet/bridge/transfer.go @@ -32,13 +32,13 @@ func (s *TransferBridge) CalculateFees(from, to *params.Network, token *token.To return big.NewInt(0), big.NewInt(0), nil } -func (s *TransferBridge) EstimateGas(from, to *params.Network, account common.Address, token *token.Token, amountIn *big.Int) (uint64, error) { - // TODO: replace by estimate function - if token.IsNative() { - return 22000, nil // default gas limit for eth transaction +func (s *TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) (uint64, error) { + estimation, err := s.transactor.EstimateGas(fromNetwork, from, to, amountIn, []byte("eth_sendRawTransaction")) + if err != nil { + return 0, err } - - return 200000, nil //default gas limit for erc20 transaction + increasedEstimation := float64(estimation) * IncreaseEstimatedGasFactor + return uint64(increasedEstimation), nil } func (s *TransferBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error) { diff --git a/services/wallet/router.go b/services/wallet/router.go index e8db60302..07b93c48d 100644 --- a/services/wallet/router.go +++ b/services/wallet/router.go @@ -510,7 +510,8 @@ func (r *Router) getBalance(ctx context.Context, network *params.Network, token func (r *Router) suggestedRoutes( ctx context.Context, sendType SendType, - account common.Address, + addrFrom common.Address, + addrTo common.Address, amountIn *big.Int, tokenID string, disabledFromChainIDs, @@ -553,7 +554,7 @@ func (r *Router) suggestedRoutes( continue } - token := sendType.FindToken(r.s, account, network, tokenID) + token := sendType.FindToken(r.s, addrFrom, network, tokenID) if token == nil { continue } @@ -572,7 +573,7 @@ func (r *Router) suggestedRoutes( // Default value is 1 as in case of erc721 as we built the token we are sure the account owns it balance := big.NewInt(1) if sendType != ERC721Transfer { - balance, err = r.getBalance(ctx, network, token, account) + balance, err = r.getBalance(ctx, network, token, addrFrom) if err != nil { return err } @@ -586,7 +587,7 @@ func (r *Router) suggestedRoutes( maxAmountIn = amount } - nativeBalance, err := r.getBalance(ctx, network, nativeToken, account) + nativeBalance, err := r.getBalance(ctx, network, nativeToken, addrFrom) if err != nil { return err } @@ -640,12 +641,12 @@ func (r *Router) suggestedRoutes( } gasLimit := uint64(0) if sendType.isTransfer() { - gasLimit, err = bridge.EstimateGas(network, dest, account, token, amountIn) + gasLimit, err = bridge.EstimateGas(network, dest, addrFrom, addrTo, token, amountIn) if err != nil { continue } } else { - gasLimit = sendType.EstimateGas(r.s, network, account, tokenID) + gasLimit = sendType.EstimateGas(r.s, network, addrFrom, tokenID) } requiredNativeBalance := new(big.Int).Mul(gweiToWei(maxFees), big.NewInt(int64(gasLimit))) // Removed the required fees from maxAMount in case of native token tx @@ -655,7 +656,7 @@ func (r *Router) suggestedRoutes( if nativeBalance.Cmp(requiredNativeBalance) <= 0 { continue } - approvalRequired, approvalAmountRequired, approvalGasLimit, approvalContractAddress, err := r.requireApproval(ctx, sendType, bridge, account, network, token, amountIn) + approvalRequired, approvalAmountRequired, approvalGasLimit, approvalContractAddress, err := r.requireApproval(ctx, sendType, bridge, addrFrom, network, token, amountIn) if err != nil { continue } diff --git a/transactions/transactor.go b/transactions/transactor.go index 00768fcb2..04b4ff321 100644 --- a/transactions/transactor.go +++ b/transactions/transactor.go @@ -17,6 +17,7 @@ import ( "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/params" "github.com/status-im/status-go/rpc" ) @@ -80,6 +81,21 @@ func (t *Transactor) NextNonce(rpcClient *rpc.Client, chainID uint64, from types wrapper := newRPCWrapper(rpcClient, chainID) return t.nonce.Next(wrapper, from) } +func (t *Transactor) EstimateGas(network *params.Network, from common.Address, to common.Address, value *big.Int, input []byte) (uint64, error) { + rpcWrapper := newRPCWrapper(t.rpcWrapper.RPCClient, network.ChainID) + + ctx, cancel := context.WithTimeout(context.Background(), t.rpcCallTimeout) + defer cancel() + + msg := ethereum.CallMsg{ + From: from, + To: &to, + Value: value, + Data: input, + } + + return rpcWrapper.EstimateGas(ctx, msg) +} // SendTransaction is an implementation of eth_sendTransaction. It queues the tx to the sign queue. func (t *Transactor) SendTransaction(sendArgs SendTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) {