github.com/status-im/status-go@v1.1.0/services/wallet/router/router_helper.go (about)

     1  package router
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"math/big"
     7  	"strings"
     8  
     9  	"github.com/ethereum/go-ethereum"
    10  	"github.com/ethereum/go-ethereum/accounts/abi"
    11  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    12  	"github.com/ethereum/go-ethereum/common"
    13  	"github.com/ethereum/go-ethereum/common/hexutil"
    14  	"github.com/status-im/status-go/contracts"
    15  	gaspriceoracle "github.com/status-im/status-go/contracts/gas-price-oracle"
    16  	"github.com/status-im/status-go/contracts/ierc20"
    17  	"github.com/status-im/status-go/params"
    18  	"github.com/status-im/status-go/services/wallet/bigint"
    19  	walletCommon "github.com/status-im/status-go/services/wallet/common"
    20  	"github.com/status-im/status-go/services/wallet/router/fees"
    21  	"github.com/status-im/status-go/services/wallet/router/pathprocessor"
    22  	routs "github.com/status-im/status-go/services/wallet/router/routes"
    23  	"github.com/status-im/status-go/services/wallet/router/sendtype"
    24  	"github.com/status-im/status-go/services/wallet/token"
    25  )
    26  
    27  func (r *Router) requireApproval(ctx context.Context, sendType sendtype.SendType, approvalContractAddress *common.Address, params pathprocessor.ProcessorInputParams) (
    28  	bool, *big.Int, error) {
    29  	if sendType.IsCollectiblesTransfer() || sendType.IsEnsTransfer() || sendType.IsStickersTransfer() {
    30  		return false, nil, nil
    31  	}
    32  
    33  	if params.FromToken.IsNative() {
    34  		return false, nil, nil
    35  	}
    36  
    37  	contractMaker, err := contracts.NewContractMaker(r.rpcClient)
    38  	if err != nil {
    39  		return false, nil, err
    40  	}
    41  
    42  	contract, err := contractMaker.NewERC20(params.FromChain.ChainID, params.FromToken.Address)
    43  	if err != nil {
    44  		return false, nil, err
    45  	}
    46  
    47  	if approvalContractAddress == nil || *approvalContractAddress == pathprocessor.ZeroAddress {
    48  		return false, nil, nil
    49  	}
    50  
    51  	if params.TestsMode {
    52  		return true, params.AmountIn, nil
    53  	}
    54  
    55  	allowance, err := contract.Allowance(&bind.CallOpts{
    56  		Context: ctx,
    57  	}, params.FromAddr, *approvalContractAddress)
    58  
    59  	if err != nil {
    60  		return false, nil, err
    61  	}
    62  
    63  	if allowance.Cmp(params.AmountIn) >= 0 {
    64  		return false, nil, nil
    65  	}
    66  
    67  	return true, params.AmountIn, nil
    68  }
    69  
    70  func (r *Router) packApprovalInputData(amountIn *big.Int, approvalContractAddress *common.Address) ([]byte, error) {
    71  	if approvalContractAddress == nil || *approvalContractAddress == pathprocessor.ZeroAddress {
    72  		return []byte{}, nil
    73  	}
    74  
    75  	erc20ABI, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI))
    76  	if err != nil {
    77  		return []byte{}, err
    78  	}
    79  
    80  	return erc20ABI.Pack("approve", approvalContractAddress, amountIn)
    81  }
    82  
    83  func (r *Router) estimateGasForApproval(params pathprocessor.ProcessorInputParams, approvalContractAddress *common.Address) (uint64, error) {
    84  	data, err := r.packApprovalInputData(params.AmountIn, approvalContractAddress)
    85  	if err != nil {
    86  		return 0, err
    87  	}
    88  
    89  	ethClient, err := r.rpcClient.EthClient(params.FromChain.ChainID)
    90  	if err != nil {
    91  		return 0, err
    92  	}
    93  
    94  	return ethClient.EstimateGas(context.Background(), ethereum.CallMsg{
    95  		From:  params.FromAddr,
    96  		To:    &params.FromToken.Address,
    97  		Value: pathprocessor.ZeroBigIntValue,
    98  		Data:  data,
    99  	})
   100  }
   101  
   102  func (r *Router) calculateApprovalL1Fee(amountIn *big.Int, chainID uint64, approvalContractAddress *common.Address) (uint64, error) {
   103  	data, err := r.packApprovalInputData(amountIn, approvalContractAddress)
   104  	if err != nil {
   105  		return 0, err
   106  	}
   107  
   108  	ethClient, err := r.rpcClient.EthClient(chainID)
   109  	if err != nil {
   110  		return 0, err
   111  	}
   112  
   113  	var l1Fee uint64
   114  	oracleContractAddress, err := gaspriceoracle.ContractAddress(chainID)
   115  	if err == nil {
   116  		oracleContract, err := gaspriceoracle.NewGaspriceoracleCaller(oracleContractAddress, ethClient)
   117  		if err != nil {
   118  			return 0, err
   119  		}
   120  
   121  		callOpt := &bind.CallOpts{}
   122  
   123  		l1FeeResult, _ := oracleContract.GetL1Fee(callOpt, data)
   124  		l1Fee = l1FeeResult.Uint64()
   125  	}
   126  
   127  	return l1Fee, nil
   128  }
   129  
   130  func (r *Router) getERC1155Balance(ctx context.Context, network *params.Network, token *token.Token, account common.Address) (*big.Int, error) {
   131  	tokenID, success := new(big.Int).SetString(token.Symbol, 10)
   132  	if !success {
   133  		return nil, errors.New("failed to convert token symbol to big.Int")
   134  	}
   135  
   136  	balances, err := r.collectiblesManager.FetchERC1155Balances(
   137  		ctx,
   138  		account,
   139  		walletCommon.ChainID(network.ChainID),
   140  		token.Address,
   141  		[]*bigint.BigInt{&bigint.BigInt{Int: tokenID}},
   142  	)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	if len(balances) != 1 || balances[0] == nil {
   148  		return nil, errors.New("invalid ERC1155 balance fetch response")
   149  	}
   150  
   151  	return balances[0].Int, nil
   152  }
   153  
   154  func (r *Router) getBalance(ctx context.Context, chainID uint64, token *token.Token, account common.Address) (*big.Int, error) {
   155  	client, err := r.rpcClient.EthClient(chainID)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	return r.tokenManager.GetBalance(ctx, client, account, token.Address)
   161  }
   162  
   163  func (r *Router) cacluateFees(ctx context.Context, path *routs.Path, fetchedFees *fees.SuggestedFees, testsMode bool, testApprovalL1Fee uint64) (err error) {
   164  
   165  	var (
   166  		l1ApprovalFee uint64
   167  	)
   168  	if path.ApprovalRequired {
   169  		if testsMode {
   170  			l1ApprovalFee = testApprovalL1Fee
   171  		} else {
   172  			l1ApprovalFee, err = r.calculateApprovalL1Fee(path.AmountIn.ToInt(), path.FromChain.ChainID, path.ApprovalContractAddress)
   173  			if err != nil {
   174  				return err
   175  			}
   176  		}
   177  	}
   178  
   179  	// TODO: keep l1 fees at 0 until we have the correct algorithm, as we do base fee x 2 that should cover the l1 fees
   180  	var l1FeeWei uint64 = 0
   181  	// if input.SendType.needL1Fee() {
   182  	// 	txInputData, err := pProcessor.PackTxInputData(processorInputParams)
   183  	// 	if err != nil {
   184  	// 		continue
   185  	// 	}
   186  
   187  	// 	l1FeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData)
   188  	// }
   189  
   190  	r.lastInputParamsMutex.Lock()
   191  	gasFeeMode := r.lastInputParams.GasFeeMode
   192  	r.lastInputParamsMutex.Unlock()
   193  	maxFeesPerGas := fetchedFees.FeeFor(gasFeeMode)
   194  
   195  	// calculate ETH fees
   196  	ethTotalFees := big.NewInt(0)
   197  	txFeeInWei := new(big.Int).Mul(maxFeesPerGas, big.NewInt(int64(path.TxGasAmount)))
   198  	ethTotalFees.Add(ethTotalFees, txFeeInWei)
   199  
   200  	txL1FeeInWei := big.NewInt(0)
   201  	if l1FeeWei > 0 {
   202  		txL1FeeInWei = big.NewInt(int64(l1FeeWei))
   203  		ethTotalFees.Add(ethTotalFees, txL1FeeInWei)
   204  	}
   205  
   206  	approvalFeeInWei := big.NewInt(0)
   207  	approvalL1FeeInWei := big.NewInt(0)
   208  	if path.ApprovalRequired {
   209  		approvalFeeInWei.Mul(maxFeesPerGas, big.NewInt(int64(path.ApprovalGasAmount)))
   210  		ethTotalFees.Add(ethTotalFees, approvalFeeInWei)
   211  
   212  		if l1ApprovalFee > 0 {
   213  			approvalL1FeeInWei = big.NewInt(int64(l1ApprovalFee))
   214  			ethTotalFees.Add(ethTotalFees, approvalL1FeeInWei)
   215  		}
   216  	}
   217  
   218  	// calculate required balances (bonder and token fees are already included in the amountIn by Hop bridge (once we include Celar we need to check how they handle the fees))
   219  	requiredNativeBalance := big.NewInt(0)
   220  	requiredTokenBalance := big.NewInt(0)
   221  
   222  	if path.FromToken.IsNative() {
   223  		requiredNativeBalance.Add(requiredNativeBalance, path.AmountIn.ToInt())
   224  		if !path.SubtractFees {
   225  			requiredNativeBalance.Add(requiredNativeBalance, ethTotalFees)
   226  		}
   227  	} else {
   228  		requiredTokenBalance.Add(requiredTokenBalance, path.AmountIn.ToInt())
   229  		requiredNativeBalance.Add(requiredNativeBalance, ethTotalFees)
   230  	}
   231  
   232  	// set the values
   233  	path.SuggestedLevelsForMaxFeesPerGas = fetchedFees.MaxFeesLevels
   234  	path.MaxFeesPerGas = (*hexutil.Big)(maxFeesPerGas)
   235  
   236  	path.TxBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
   237  	path.TxPriorityFee = (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas)
   238  
   239  	path.TxFee = (*hexutil.Big)(txFeeInWei)
   240  	path.TxL1Fee = (*hexutil.Big)(txL1FeeInWei)
   241  
   242  	path.ApprovalBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
   243  	path.ApprovalPriorityFee = (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas)
   244  
   245  	path.ApprovalFee = (*hexutil.Big)(approvalFeeInWei)
   246  	path.ApprovalL1Fee = (*hexutil.Big)(approvalL1FeeInWei)
   247  
   248  	path.TxTotalFee = (*hexutil.Big)(ethTotalFees)
   249  
   250  	path.RequiredTokenBalance = requiredTokenBalance
   251  	path.RequiredNativeBalance = requiredNativeBalance
   252  
   253  	return nil
   254  }