feat: l1 gas price estimation when placing l2 transaction

This commit is contained in:
Sale Djenic 2024-03-25 13:40:00 +01:00 committed by saledjenic
parent 1a2880b365
commit 98c3be55b9
12 changed files with 1830 additions and 28 deletions

View File

@ -0,0 +1,24 @@
package gaspriceoracle
import (
"errors"
"github.com/ethereum/go-ethereum/common"
wallet_common "github.com/status-im/status-go/services/wallet/common"
)
var ErrorNotAvailableOnChainID = errors.New("not available for chainID")
var contractAddressByChainID = map[uint64]common.Address{
wallet_common.OptimismMainnet: common.HexToAddress("0x8527c030424728cF93E72bDbf7663281A44Eeb22"),
wallet_common.OptimismSepolia: common.HexToAddress("0x5230210c2b4995FD5084b0F5FD0D7457aebb5010"),
}
func ContractAddress(chainID uint64) (common.Address, error) {
addr, exists := contractAddressByChainID[chainID]
if !exists {
return *new(common.Address), ErrorNotAvailableOnChainID
}
return addr, nil
}

View File

@ -0,0 +1,3 @@
package gaspriceoracle
//go:generate abigen -abi gaspriceoracle.abi -pkg gaspriceoracle -out gaspriceoracle.go

View File

@ -0,0 +1,316 @@
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "DecimalsUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "GasPriceUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "L1BaseFeeUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "OverheadUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "ScalarUpdated",
"type": "event"
},
{
"inputs": [],
"name": "admin",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "adminCall",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gasPrice",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getAdmin",
"outputs": [
{
"internalType": "address",
"name": "adminAddress",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_data",
"type": "bytes"
}
],
"name": "getL1Fee",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "_data",
"type": "bytes"
}
],
"name": "getL1GasUsed",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "initPayload",
"type": "bytes"
}
],
"name": "init",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "l1BaseFee",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "overhead",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "scalar",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "adminAddress",
"type": "address"
}
],
"name": "setAdmin",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_decimals",
"type": "uint256"
}
],
"name": "setDecimals",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_gasPrice",
"type": "uint256"
}
],
"name": "setGasPrice",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_baseFee",
"type": "uint256"
}
],
"name": "setL1BaseFee",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_overhead",
"type": "uint256"
}
],
"name": "setOverhead",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_scalar",
"type": "uint256"
}
],
"name": "setScalar",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

File diff suppressed because it is too large Load Diff

View File

@ -106,4 +106,5 @@ type Bridge interface {
Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error)
GetContractAddress(network *params.Network, token *token.Token) *common.Address
BuildTransaction(sendArgs *TransactionBridge) (*ethTypes.Transaction, error)
BuildTx(network *params.Network, fromAddress common.Address, toAddress common.Address, token *token.Token, amountIn *big.Int) (*ethTypes.Transaction, error)
}

View File

@ -288,6 +288,26 @@ func (s *CBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Net
return uint64(increasedEstimation), nil
}
func (s *CBridge) BuildTx(network *params.Network, fromAddress common.Address, toAddress common.Address, token *token.Token, amountIn *big.Int) (*ethTypes.Transaction, error) {
toAddr := types.Address(toAddress)
sendArgs := &TransactionBridge{
CbridgeTx: &CBridgeTxArgs{
SendTxArgs: transactions.SendTxArgs{
From: types.Address(fromAddress),
To: &toAddr,
Value: (*hexutil.Big)(amountIn),
Data: types.HexBytes("0x0"),
},
ChainID: network.ChainID,
Symbol: token.Symbol,
Recipient: toAddress,
Amount: (*hexutil.Big)(amountIn),
},
}
return s.BuildTransaction(sendArgs)
}
func (s *CBridge) GetContractAddress(network *params.Network, token *token.Token) *common.Address {
transferConfig, err := s.getTransferConfig(network.IsTest)
if err != nil {

View File

@ -101,6 +101,25 @@ func (s *ERC1155TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwo
return uint64(increasedEstimation), nil
}
func (s *ERC1155TransferBridge) BuildTx(network *params.Network, fromAddress common.Address, toAddress common.Address, token *token.Token, amountIn *big.Int) (*ethTypes.Transaction, error) {
toAddr := types.Address(toAddress)
sendArgs := &TransactionBridge{
ERC1155TransferTx: &ERC1155TransferTxArgs{
SendTxArgs: transactions.SendTxArgs{
From: types.Address(fromAddress),
To: &toAddr,
Value: (*hexutil.Big)(amountIn),
Data: types.HexBytes("0x0"),
},
TokenID: (*hexutil.Big)(big.NewInt(0)),
Recipient: toAddress,
Amount: (*hexutil.Big)(amountIn),
},
}
return s.BuildTransaction(sendArgs)
}
func (s *ERC1155TransferBridge) sendOrBuild(sendArgs *TransactionBridge, signerFn bind.SignerFn) (tx *ethTypes.Transaction, err error) {
ethClient, err := s.rpcClient.EthClient(sendArgs.ChainID)
if err != nil {

View File

@ -98,6 +98,24 @@ func (s *ERC721TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwor
return uint64(increasedEstimation), nil
}
func (s *ERC721TransferBridge) BuildTx(network *params.Network, fromAddress common.Address, toAddress common.Address, token *token.Token, amountIn *big.Int) (*ethTypes.Transaction, error) {
toAddr := types.Address(toAddress)
sendArgs := &TransactionBridge{
ERC721TransferTx: &ERC721TransferTxArgs{
SendTxArgs: transactions.SendTxArgs{
From: types.Address(fromAddress),
To: &toAddr,
Value: (*hexutil.Big)(amountIn),
Data: types.HexBytes("0x0"),
},
TokenID: (*hexutil.Big)(big.NewInt(0)),
Recipient: toAddress,
},
}
return s.BuildTransaction(sendArgs)
}
func (s *ERC721TransferBridge) sendOrBuild(sendArgs *TransactionBridge, signerFn bind.SignerFn) (tx *ethTypes.Transaction, err error) {
ethClient, err := s.rpcClient.EthClient(sendArgs.ChainID)
if err != nil {

View File

@ -215,6 +215,26 @@ func (h *HopBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.N
return uint64(increasedEstimation), nil
}
func (h *HopBridge) BuildTx(network *params.Network, fromAddress common.Address, toAddress common.Address, token *token.Token, amountIn *big.Int) (*ethTypes.Transaction, error) {
toAddr := types.Address(toAddress)
sendArgs := &TransactionBridge{
HopTx: &HopTxArgs{
SendTxArgs: transactions.SendTxArgs{
From: types.Address(fromAddress),
To: &toAddr,
Value: (*hexutil.Big)(amountIn),
Data: types.HexBytes("0x0"),
},
ChainID: network.ChainID,
Symbol: token.Symbol,
Recipient: toAddress,
Amount: (*hexutil.Big)(amountIn),
},
}
return h.BuildTransaction(sendArgs)
}
func (h *HopBridge) GetContractAddress(network *params.Network, token *token.Token) *common.Address {
var address common.Address
if network.Layer == 1 {

View File

@ -8,6 +8,7 @@ import (
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"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/ierc20"
@ -84,6 +85,21 @@ func (s *TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwork *par
return uint64(increasedEstimation), nil
}
func (s *TransferBridge) BuildTx(network *params.Network, fromAddress common.Address, toAddress common.Address, token *token.Token, amountIn *big.Int) (*ethTypes.Transaction, error) {
toAddr := types.Address(toAddress)
sendArgs := &TransactionBridge{
TransferTx: &transactions.SendTxArgs{
From: types.Address(fromAddress),
To: &toAddr,
Value: (*hexutil.Big)(amountIn),
Data: types.HexBytes("0x0"),
},
ChainID: network.ChainID,
}
return s.BuildTransaction(sendArgs)
}
func (s *TransferBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error) {
return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, verifiedAccount)
}

View File

@ -7,8 +7,11 @@ import (
"sort"
"strings"
"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"
)
@ -27,6 +30,7 @@ type SuggestedFees struct {
MaxFeePerGasLow *big.Float `json:"maxFeePerGasLow"`
MaxFeePerGasMedium *big.Float `json:"maxFeePerGasMedium"`
MaxFeePerGasHigh *big.Float `json:"maxFeePerGasHigh"`
L1GasFee *big.Float `json:"l1GasFee"`
EIP1559Enabled bool `json:"eip1559Enabled"`
}
@ -251,3 +255,35 @@ func (f *FeeManager) getFeeHistorySorted(chainID uint64) ([]*big.Int, error) {
sort.Slice(fees, func(i, j int) bool { return fees[i].Cmp(fees[j]) < 0 })
return fees, nil
}
func (f *FeeManager) getL1Fee(ctx context.Context, chainID uint64, tx *ethTypes.Transaction) (uint64, error) {
ethClient, err := f.RPCClient.EthClient(chainID)
if err != nil {
return 0, err
}
contractAddress, err := gaspriceoracle.ContractAddress(chainID)
if err != nil {
return 0, err
}
contract, err := gaspriceoracle.NewGaspriceoracleCaller(contractAddress, ethClient)
if err != nil {
return 0, err
}
callOpt := &bind.CallOpts{}
data, err := tx.MarshalBinary()
if err != nil {
return 0, err
}
result, err := contract.GetL1Fee(callOpt, data)
if err != nil {
return 0, err
}
return result.Uint64(), nil
}

View File

@ -16,6 +16,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/contracts"
gaspriceoracle "github.com/status-im/status-go/contracts/gas-price-oracle"
"github.com/status-im/status-go/contracts/ierc20"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
@ -450,27 +451,28 @@ type Router struct {
rpcClient *rpc.Client
}
func (r *Router) requireApproval(ctx context.Context, sendType SendType, bridge bridge.Bridge, account common.Address, network *params.Network, token *token.Token, amountIn *big.Int) (bool, *big.Int, uint64, *common.Address, error) {
func (r *Router) requireApproval(ctx context.Context, sendType SendType, bridge bridge.Bridge, account common.Address, network *params.Network, token *token.Token, amountIn *big.Int) (
bool, *big.Int, uint64, uint64, *common.Address, error) {
if sendType.IsCollectiblesTransfer() {
return false, nil, 0, nil, nil
return false, nil, 0, 0, nil, nil
}
if token.IsNative() {
return false, nil, 0, nil, nil
return false, nil, 0, 0, nil, nil
}
contractMaker, err := contracts.NewContractMaker(r.rpcClient)
if err != nil {
return false, nil, 0, nil, err
return false, nil, 0, 0, nil, err
}
bridgeAddress := bridge.GetContractAddress(network, token)
if bridgeAddress == nil {
return false, nil, 0, nil, nil
return false, nil, 0, 0, nil, nil
}
contract, err := contractMaker.NewERC20(network.ChainID, token.Address)
if err != nil {
return false, nil, 0, nil, err
return false, nil, 0, 0, nil, err
}
allowance, err := contract.Allowance(&bind.CallOpts{
@ -478,26 +480,26 @@ func (r *Router) requireApproval(ctx context.Context, sendType SendType, bridge
}, account, *bridgeAddress)
if err != nil {
return false, nil, 0, nil, err
return false, nil, 0, 0, nil, err
}
if allowance.Cmp(amountIn) >= 0 {
return false, nil, 0, nil, nil
return false, nil, 0, 0, nil, nil
}
ethClient, err := r.rpcClient.EthClient(network.ChainID)
if err != nil {
return false, nil, 0, nil, err
return false, nil, 0, 0, nil, err
}
erc20ABI, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI))
if err != nil {
return false, nil, 0, nil, err
return false, nil, 0, 0, nil, err
}
data, err := erc20ABI.Pack("approve", bridgeAddress, amountIn)
if err != nil {
return false, nil, 0, nil, err
return false, nil, 0, 0, nil, err
}
estimate, err := ethClient.EstimateGas(context.Background(), ethereum.CallMsg{
@ -507,11 +509,25 @@ func (r *Router) requireApproval(ctx context.Context, sendType SendType, bridge
Data: data,
})
if err != nil {
return false, nil, 0, nil, err
return false, nil, 0, 0, nil, err
}
return true, amountIn, estimate, bridgeAddress, nil
// fetching l1 fee
oracleContractAddress, err := gaspriceoracle.ContractAddress(network.ChainID)
if err != nil {
return false, nil, 0, 0, nil, err
}
oracleContract, err := gaspriceoracle.NewGaspriceoracleCaller(oracleContractAddress, ethClient)
if err != nil {
return false, nil, 0, 0, nil, err
}
callOpt := &bind.CallOpts{}
l1Fee, _ := oracleContract.GetL1Fee(callOpt, data)
return true, amountIn, estimate, l1Fee.Uint64(), bridgeAddress, nil
}
func (r *Router) getBalance(ctx context.Context, network *params.Network, token *token.Token, account common.Address) (*big.Int, error) {
@ -692,31 +708,45 @@ func (r *Router) suggestedRoutes(
gasLimit = sendType.EstimateGas(r.s, network, addrFrom, tokenID)
}
approvalRequired, approvalAmountRequired, approvalGasLimit, l1ApprovalFee, approvalContractAddress, err := r.requireApproval(ctx, sendType, bridge, addrFrom, network, token, amountIn)
if err != nil {
continue
}
tx, err := bridge.BuildTx(network, addrTo, addrFrom, token, amountIn)
if err != nil {
continue
}
l1GasFeeWei, _ := r.s.feesManager.getL1Fee(ctx, network.ChainID, tx)
l1GasFeeWei += l1ApprovalFee
gasFees.L1GasFee = weiToGwei(big.NewInt(int64(l1GasFeeWei)))
requiredNativeBalance := new(big.Int).Mul(gweiToWei(maxFees), big.NewInt(int64(gasLimit)))
requiredNativeBalance.Add(requiredNativeBalance, new(big.Int).Mul(gweiToWei(maxFees), big.NewInt(int64(approvalGasLimit))))
requiredNativeBalance.Add(requiredNativeBalance, big.NewInt(int64(l1GasFeeWei))) // add l1Fee to requiredNativeBalance, in case of L1 chain l1Fee is 0
if nativeBalance.Cmp(requiredNativeBalance) <= 0 {
continue
}
// Removed the required fees from maxAMount in case of native token tx
if token.IsNative() {
maxAmountIn = (*hexutil.Big)(new(big.Int).Sub(maxAmountIn.ToInt(), requiredNativeBalance))
}
if nativeBalance.Cmp(requiredNativeBalance) <= 0 {
continue
}
approvalRequired, approvalAmountRequired, approvalGasLimit, approvalContractAddress, err := r.requireApproval(ctx, sendType, bridge, addrFrom, network, token, amountIn)
if err != nil {
continue
}
ethPrice := big.NewFloat(prices["ETH"])
approvalGasFees := new(big.Float).Mul(gweiToEth(maxFees), big.NewFloat((float64(approvalGasLimit))))
approvalGasCost := new(big.Float)
approvalGasCost.Mul(
approvalGasFees,
big.NewFloat(prices["ETH"]),
)
approvalGasCost.Mul(approvalGasFees, ethPrice)
l1GasCost := new(big.Float)
l1GasCost.Mul(gasFees.L1GasFee, ethPrice)
gasCost := new(big.Float)
gasCost.Mul(
new(big.Float).Mul(gweiToEth(maxFees), big.NewFloat((float64(gasLimit)))),
big.NewFloat(prices["ETH"]),
)
gasCost.Mul(new(big.Float).Mul(gweiToEth(maxFees), big.NewFloat(float64(gasLimit))), ethPrice)
tokenFeesAsFloat := new(big.Float).Quo(
new(big.Float).SetInt(tokenFees),
@ -728,6 +758,7 @@ func (r *Router) suggestedRoutes(
cost := new(big.Float)
cost.Add(tokenCost, gasCost)
cost.Add(cost, approvalGasCost)
cost.Add(cost, l1GasCost)
mu.Lock()
candidates = append(candidates, &Path{
BridgeName: bridge.Name(),