fix_: hop bridge improvements

- handling `null` values in the Hop response
- using data returned from the Hop api when preparing data for estimation and calling `swapAndSend` and `sendToL2`
- estimating gas for bridges implemented in the bridges implementation types, avoiding wrong gas for placing bridge transactions
This commit is contained in:
Sale Djenic 2024-05-21 16:33:36 +02:00 committed by saledjenic
parent 4e51b5ba24
commit c74931c333
12 changed files with 195 additions and 143 deletions

View File

@ -393,7 +393,12 @@ func (s *Service) estimateL1Fee(ctx context.Context, chainID uint64, sendArgs tr
return 0, err
}
return s.feeManager.GetL1Fee(ctx, chainID, transaction)
data, err := transaction.MarshalBinary()
if err != nil {
return 0, err
}
return s.feeManager.GetL1Fee(ctx, chainID, data)
}
func (s *Service) estimateMethodForTokenInstance(ctx context.Context, contractInstance TokenInstance, chainID uint64, contractAddress string, fromAddress string, methodName string, args ...interface{}) (uint64, error) {

View File

@ -107,6 +107,8 @@ type Bridge interface {
AvailableFor(from *params.Network, to *params.Network, token *token.Token, toToken *token.Token) (bool, error)
// calculates the fees for the bridge and returns the amount BonderFee and TokenFee (used for bridges)
CalculateFees(from, to *params.Network, token *token.Token, amountIn *big.Int) (*big.Int, *big.Int, error)
// Pack the method for sending tx and method call's data
PackTxInputData(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) ([]byte, error)
EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *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)

View File

@ -196,28 +196,22 @@ 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(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
var input []byte
value := new(big.Int)
func (c *CBridge) PackTxInputData(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) ([]byte, error) {
abi, err := abi.JSON(strings.NewReader(celer.CelerABI))
if err != nil {
return 0, err
return []byte{}, err
}
if token.IsNative() {
input, err = abi.Pack("sendNative",
return abi.Pack("sendNative",
to,
amountIn,
toNetwork.ChainID,
uint64(time.Now().UnixMilli()),
500,
)
if err != nil {
return 0, err
}
} else {
input, err = abi.Pack("send",
return abi.Pack("send",
to,
token.Address,
amountIn,
@ -225,10 +219,16 @@ func (s *CBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Net
uint64(time.Now().UnixMilli()),
500,
)
}
}
func (s *CBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
value := new(big.Int)
input, err := s.PackTxInputData(fromNetwork, toNetwork, from, to, token, amountIn)
if err != nil {
return 0, err
}
}
contractAddress := s.GetContractAddress(fromNetwork, nil)
if contractAddress == nil {

View File

@ -49,31 +49,35 @@ func (s *ERC1155TransferBridge) CalculateFees(from, to *params.Network, token *t
return big.NewInt(0), big.NewInt(0), nil
}
func (s *ERC1155TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
ethClient, err := s.rpcClient.EthClient(fromNetwork.ChainID)
if err != nil {
return 0, err
}
var input []byte
value := new(big.Int)
func (s *ERC1155TransferBridge) PackTxInputData(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) ([]byte, error) {
abi, err := abi.JSON(strings.NewReader(ierc1155.Ierc1155ABI))
if err != nil {
return 0, err
return []byte{}, err
}
id, success := big.NewInt(0).SetString(token.Symbol, 0)
if !success {
return 0, fmt.Errorf("failed to convert %s to big.Int", token.Symbol)
return []byte{}, fmt.Errorf("failed to convert %s to big.Int", token.Symbol)
}
input, err = abi.Pack("safeTransferFrom",
return abi.Pack("safeTransferFrom",
from,
to,
id,
amountIn,
[]byte{},
)
}
func (s *ERC1155TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
ethClient, err := s.rpcClient.EthClient(fromNetwork.ChainID)
if err != nil {
return 0, err
}
value := new(big.Int)
input, err := s.PackTxInputData(fromNetwork, toNetwork, from, to, token, amountIn)
if err != nil {
return 0, err
}

View File

@ -48,29 +48,33 @@ func (s *ERC721TransferBridge) CalculateFees(from, to *params.Network, token *to
return big.NewInt(0), big.NewInt(0), nil
}
func (s *ERC721TransferBridge) PackTxInputData(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) ([]byte, error) {
abi, err := abi.JSON(strings.NewReader(collectibles.CollectiblesMetaData.ABI))
if err != nil {
return []byte{}, err
}
id, success := big.NewInt(0).SetString(token.Symbol, 0)
if !success {
return []byte{}, fmt.Errorf("failed to convert %s to big.Int", token.Symbol)
}
return abi.Pack("safeTransferFrom",
from,
to,
id,
)
}
func (s *ERC721TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
ethClient, err := s.rpcClient.EthClient(fromNetwork.ChainID)
if err != nil {
return 0, err
}
var input []byte
value := new(big.Int)
abi, err := abi.JSON(strings.NewReader(collectibles.CollectiblesMetaData.ABI))
if err != nil {
return 0, err
}
id, success := big.NewInt(0).SetString(token.Symbol, 0)
if !success {
return 0, fmt.Errorf("failed to convert %s to big.Int", token.Symbol)
}
input, err = abi.Pack("safeTransferFrom",
from,
to,
id,
)
input, err := s.PackTxInputData(fromNetwork, toNetwork, from, to, token, amountIn)
if err != nil {
return 0, err
}

View File

@ -24,12 +24,15 @@ import (
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/wallet/bigint"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/transactions"
)
const SevenDaysInSeconds = 604800
type HopTxArgs struct {
transactions.SendTxArgs
ChainID uint64 `json:"chainId"`
@ -40,12 +43,12 @@ type HopTxArgs struct {
}
type BonderFee struct {
AmountIn *big.Int `json:"amountIn"`
AmountIn *bigint.BigInt `json:"amountIn"`
Slippage float32 `json:"slippage"`
AmountOutMin *big.Int `json:"amountOutMin"`
DestinationAmountOutMin *big.Int `json:"destinationAmountOutMin"`
BonderFee *big.Int `json:"bonderFee"`
EstimatedRecieved *big.Int `json:"estimatedRecieved"`
AmountOutMin *bigint.BigInt `json:"amountOutMin"`
DestinationAmountOutMin *bigint.BigInt `json:"destinationAmountOutMin"`
BonderFee *bigint.BigInt `json:"bonderFee"`
EstimatedRecieved *bigint.BigInt `json:"estimatedRecieved"`
Deadline int64 `json:"deadline"`
DestinationDeadline int64 `json:"destinationDeadline"`
}
@ -70,21 +73,23 @@ func (bf *BonderFee) UnmarshalJSON(data []byte) error {
return err
}
bf.AmountIn = new(big.Int)
bf.AmountIn = &bigint.BigInt{Int: new(big.Int)}
bf.AmountIn.SetString(aux.AmountIn, 10)
bf.AmountOutMin = new(big.Int)
bf.AmountOutMin = &bigint.BigInt{Int: new(big.Int)}
bf.AmountOutMin.SetString(aux.AmountOutMin, 10)
bf.DestinationAmountOutMin = new(big.Int)
bf.DestinationAmountOutMin = &bigint.BigInt{Int: new(big.Int)}
bf.DestinationAmountOutMin.SetString(aux.DestinationAmountOutMin, 10)
bf.BonderFee = new(big.Int)
bf.BonderFee = &bigint.BigInt{Int: new(big.Int)}
bf.BonderFee.SetString(aux.BonderFee, 10)
bf.EstimatedRecieved = new(big.Int)
bf.EstimatedRecieved = &bigint.BigInt{Int: new(big.Int)}
bf.EstimatedRecieved.SetString(aux.EstimatedRecieved, 10)
bf.Deadline = aux.Deadline
if aux.DestinationDeadline != nil {
bf.DestinationDeadline = *aux.DestinationDeadline
}
@ -126,12 +131,41 @@ func (h *HopBridge) AvailableFor(from, to *params.Network, token *token.Token, t
return true, nil
}
func (h *HopBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
var input []byte
value := new(big.Int)
func (h *HopBridge) PackTxInputData(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) ([]byte, error) {
if fromNetwork.Layer == 1 {
ABI, err := abi.JSON(strings.NewReader(hopBridge.HopBridgeABI))
if err != nil {
return []byte{}, err
}
now := time.Now()
deadline := big.NewInt(now.Unix() + 604800)
return ABI.Pack("sendToL2",
big.NewInt(int64(toNetwork.ChainID)),
to,
h.bonderFee.AmountIn.Int,
h.bonderFee.AmountOutMin.Int,
big.NewInt(h.bonderFee.Deadline),
common.HexToAddress("0x0"),
big.NewInt(0))
} else {
ABI, err := abi.JSON(strings.NewReader(hopWrapper.HopWrapperABI))
if err != nil {
return []byte{}, err
}
return ABI.Pack("swapAndSend",
big.NewInt(int64(toNetwork.ChainID)),
to,
h.bonderFee.AmountIn.Int,
h.bonderFee.BonderFee.Int,
h.bonderFee.AmountOutMin.Int,
big.NewInt(h.bonderFee.Deadline),
h.bonderFee.DestinationAmountOutMin.Int,
big.NewInt(h.bonderFee.DestinationDeadline))
}
}
func (h *HopBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
value := new(big.Int)
if token.IsNative() {
value = amountIn
@ -144,45 +178,11 @@ func (h *HopBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.N
ctx := context.Background()
if fromNetwork.Layer == 1 {
ABI, err := abi.JSON(strings.NewReader(hopBridge.HopBridgeABI))
input, err := h.PackTxInputData(fromNetwork, toNetwork, from, to, token, amountIn)
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
@ -286,14 +286,26 @@ func (h *HopBridge) sendToL2(chainID uint64, hopArgs *HopTxArgs, signerFn bind.S
if token.IsNative() {
txOpts.Value = (*big.Int)(hopArgs.Amount)
}
var (
deadline *big.Int
amountOutMin *big.Int
)
if h.bonderFee != nil {
deadline = big.NewInt(h.bonderFee.Deadline)
amountOutMin = h.bonderFee.AmountOutMin.Int
} else {
now := time.Now()
deadline := big.NewInt(now.Unix() + 604800)
deadline = big.NewInt(now.Unix() + SevenDaysInSeconds)
}
tx, err = bridge.SendToL2(
txOpts,
big.NewInt(int64(hopArgs.ChainID)),
hopArgs.Recipient,
hopArgs.Amount.ToInt(),
big.NewInt(0),
amountOutMin,
deadline,
common.HexToAddress("0x0"),
big.NewInt(0),
@ -317,14 +329,26 @@ func (h *HopBridge) swapAndSend(chainID uint64, hopArgs *HopTxArgs, signerFn bin
if token.IsNative() {
txOpts.Value = (*big.Int)(hopArgs.Amount)
}
now := time.Now()
deadline := big.NewInt(now.Unix() + 604800)
var deadline *big.Int
amountOutMin := big.NewInt(0)
destinationDeadline := big.NewInt(now.Unix() + 604800)
destinationDeadline := big.NewInt(0)
destinationAmountOutMin := big.NewInt(0)
if toNetwork.Layer == 1 {
destinationDeadline = big.NewInt(0)
// https://docs.hop.exchange/v/developer-docs/smart-contracts/integration#l2-greater-than-l1-and-l2-greater-than-l2
// Do not set `destinationAmountOutMin` and `destinationDeadline` when sending to L1 because there is no AMM on L1,
// otherwise the computed transferId will be invalid and the transfer will be unbondable. These parameters should be set to 0 when sending to L1.
if h.bonderFee != nil {
deadline = big.NewInt(h.bonderFee.Deadline)
amountOutMin = h.bonderFee.AmountOutMin.Int
destinationDeadline = big.NewInt(h.bonderFee.DestinationDeadline)
destinationAmountOutMin = h.bonderFee.DestinationAmountOutMin.Int
} else {
now := time.Now()
deadline = big.NewInt(now.Unix() + SevenDaysInSeconds)
if toNetwork.Layer != 1 {
destinationDeadline = big.NewInt(now.Unix() + SevenDaysInSeconds)
}
}
tx, err = ammWrapper.SwapAndSend(
@ -372,16 +396,17 @@ func (h *HopBridge) CalculateFees(from, to *params.Network, token *token.Token,
return nil, nil, err
}
h.bonderFee = &BonderFee{}
err = json.Unmarshal(response, h.bonderFee)
if err != nil {
return nil, nil, err
}
tokenFee := new(big.Int).Sub(h.bonderFee.AmountIn, h.bonderFee.EstimatedRecieved)
tokenFee := new(big.Int).Sub(h.bonderFee.AmountIn.Int, h.bonderFee.EstimatedRecieved.Int)
return h.bonderFee.BonderFee, tokenFee, nil
return h.bonderFee.BonderFee.Int, tokenFee, nil
}
func (h *HopBridge) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) {
return h.bonderFee.EstimatedRecieved, nil
return h.bonderFee.EstimatedRecieved.Int, nil
}

View File

@ -91,6 +91,11 @@ func (s *SwapParaswap) CalculateFees(from, to *params.Network, token *token.Toke
return big.NewInt(0), big.NewInt(0), nil
}
func (s *SwapParaswap) PackTxInputData(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) ([]byte, error) {
// not sure what we can do here since we're using the api to build the transaction
return []byte{}, nil
}
func (s *SwapParaswap) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
priceRoute, err := s.paraswapClient.FetchPriceRoute(context.Background(), token.Address, token.Decimals, toToken.Address, toToken.Decimals, amountIn, from, to)
if err != nil {

View File

@ -40,11 +40,32 @@ func (s *TransferBridge) CalculateFees(from, to *params.Network, token *token.To
return big.NewInt(0), big.NewInt(0), nil
}
func (s *TransferBridge) PackTxInputData(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) ([]byte, error) {
if token.Symbol == "ETH" {
return []byte("eth_sendRawTransaction"), nil
} else {
abi, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI))
if err != nil {
return []byte{}, err
}
return abi.Pack("transfer",
to,
amountIn,
)
}
}
func (s *TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
estimation := uint64(0)
var err error
input, err := s.PackTxInputData(fromNetwork, toNetwork, from, to, token, amountIn)
if err != nil {
return 0, err
}
if token.Symbol == "ETH" {
estimation, err = s.transactor.EstimateGas(fromNetwork, from, to, amountIn, []byte("eth_sendRawTransaction"))
estimation, err = s.transactor.EstimateGas(fromNetwork, from, to, amountIn, input)
if err != nil {
return 0, err
}
@ -54,19 +75,6 @@ func (s *TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwork *par
return 0, err
}
abi, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI))
if err != nil {
return 0, err
}
input, err := abi.Pack("transfer",
to,
amountIn,
)
if err != nil {
return 0, err
}
ctx := context.Background()
msg := ethereum.CallMsg{
@ -81,6 +89,7 @@ func (s *TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwork *par
}
}
increasedEstimation := float64(estimation) * IncreaseEstimatedGasFactor
return uint64(increasedEstimation), nil
}

View File

@ -9,7 +9,6 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/consensus/misc"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
gaspriceoracle "github.com/status-im/status-go/contracts/gas-price-oracle"
"github.com/status-im/status-go/rpc"
@ -330,7 +329,12 @@ func (f *FeeManager) getFeeHistorySorted(chainID uint64) ([]*big.Int, error) {
return fees, nil
}
func (f *FeeManager) GetL1Fee(ctx context.Context, chainID uint64, tx *ethTypes.Transaction) (uint64, error) {
// Returns L1 fee for placing a transaction to L1 chain, appicable only for txs made from L2.
func (f *FeeManager) GetL1Fee(ctx context.Context, chainID uint64, input []byte) (uint64, error) {
if chainID == common.EthereumMainnet || chainID == common.EthereumSepolia && chainID != common.EthereumGoerli {
return 0, nil
}
ethClient, err := f.RPCClient.EthClient(chainID)
if err != nil {
return 0, err
@ -348,12 +352,7 @@ func (f *FeeManager) GetL1Fee(ctx context.Context, chainID uint64, tx *ethTypes.
callOpt := &bind.CallOpts{}
data, err := tx.MarshalBinary()
if err != nil {
return 0, err
}
result, err := contract.GetL1Fee(callOpt, data)
result, err := contract.GetL1Fee(callOpt, input)
if err != nil {
return 0, err
}

View File

@ -591,7 +591,7 @@ func (r *Router) SuggestedRoutes(
}
}
gasLimit := uint64(0)
if sendType.isTransfer() {
if sendType.isTransfer(false) {
gasLimit, err = bridge.EstimateGas(network, dest, addrFrom, addrTo, token, toToken, amountIn)
if err != nil {
continue
@ -608,12 +608,12 @@ func (r *Router) SuggestedRoutes(
var l1GasFeeWei uint64
if sendType.needL1Fee() {
tx, err := bridge.BuildTx(network, dest, addrFrom, addrTo, token, amountIn, bonderFees)
txInputData, err := bridge.PackTxInputData(network, dest, addrFrom, addrTo, token, amountIn)
if err != nil {
continue
}
l1GasFeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, tx)
l1GasFeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData)
l1GasFeeWei += l1ApprovalFee
}

View File

@ -83,8 +83,11 @@ func (s SendType) FindToken(tokenManager *token.Manager, collectibles *collectib
}
}
func (s SendType) isTransfer() bool {
return s == Transfer || s == Swap || s.IsCollectiblesTransfer()
func (s SendType) isTransfer(routerV2Logic bool) bool {
return s == Transfer ||
s == Bridge && routerV2Logic ||
s == Swap ||
s.IsCollectiblesTransfer()
}
func (s SendType) needL1Fee() bool {

View File

@ -266,12 +266,8 @@ func findBestV2(routes [][]*PathV2, tokenPrice float64, nativeChainTokenPrice fl
for _, route := range routes {
currentCost := big.NewFloat(0)
for _, path := range route {
if path.FromToken.IsNative() {
path.requiredNativeBalance = new(big.Int).Set(path.AmountIn.ToInt())
} else {
path.requiredTokenBalance = new(big.Int).Set(path.AmountIn.ToInt())
path.requiredNativeBalance = big.NewInt(0)
}
// ecaluate the cost of the path
pathCost := big.NewFloat(0)
@ -434,7 +430,7 @@ func (r *Router) SuggestedRoutesV2(ctx context.Context, input *RouteInputParams)
}
gasLimit := uint64(0)
if input.SendType.isTransfer() {
if input.SendType.isTransfer(true) {
gasLimit, err = bridge.EstimateGas(network, dest, input.AddrFrom, input.AddrTo, token, toToken, amountToSend)
if err != nil {
continue
@ -452,12 +448,12 @@ func (r *Router) SuggestedRoutesV2(ctx context.Context, input *RouteInputParams)
var l1FeeWei uint64
if input.SendType.needL1Fee() {
tx, err := bridge.BuildTx(network, dest, input.AddrFrom, input.AddrTo, token, amountToSend, bonderFees)
txInputData, err := bridge.PackTxInputData(network, dest, input.AddrFrom, input.AddrTo, token, amountToSend)
if err != nil {
continue
}
l1FeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, tx)
l1FeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData)
}
baseFee, err := r.feesManager.getBaseFee(ctx, client)