status-go/services/wallet/router/router_helper.go

301 lines
9.1 KiB
Go
Raw Normal View History

2024-08-28 08:59:19 +00:00
package router
import (
"context"
"errors"
"math/big"
"slices"
"strings"
2024-08-28 08:59:19 +00:00
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
2024-08-28 08:59:19 +00:00
"github.com/status-im/status-go/contracts"
gaspriceoracle "github.com/status-im/status-go/contracts/gas-price-oracle"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc/chain"
2024-08-28 08:59:19 +00:00
"github.com/status-im/status-go/services/wallet/bigint"
"github.com/status-im/status-go/services/wallet/collectibles"
2024-08-28 08:59:19 +00:00
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/market"
"github.com/status-im/status-go/services/wallet/router/fees"
2024-08-28 08:59:19 +00:00
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
routs "github.com/status-im/status-go/services/wallet/router/routes"
chore!: router code organization improvements It's a breaking change due to errors' changes, the list of mapped old error codes: - `"WR-001"` is now `"WRR-001"` - `"WR-002"` is now `"WRR-002"` - `"WR-003"` is now `"WRR-003"` - `"WR-004"` is now `"WRR-004"` - `"WR-005"` is now `"WRR-005"` - `"WR-006"` is now `"WRR-006"` - `"WR-007"` is now `"WRR-007"` - `"WR-008"` is now `"WRR-008"` - `"WR-009"` is now `"WRR-009"` - `"WR-010"` is now `"WRR-010"` - `"WR-011"` is now `"WRR-011"` - `"WR-012"` is now `"WRR-012"` - `"WR-013"` is now `"WRR-013"` - `"WR-014"` is now `"WRR-014"` - `"WR-015"` is now `"WRR-015"` - `"WR-019"` is now `"WRR-016"` - `"WR-020"` is now `"WRR-017"` - `"WR-021"` is now `"WRR-018"` - `"WR-025"` is now `"WRR-019"` - `"WR-016"` is now `"WR-001"` - `"WR-017"` is now `"WR-002"` - `"WR-018"` is now `"WR-003"` - `"WR-022"` is now `"WR-004"` - `"WR-023"` is now `"WR-005"` - `"WR-024"` is now `"WR-006"` - `"WR-026"` is now `"WR-007"` - `"WR-027"` is now `"WR-008"` Other changes: - `RouteInputParams` type moved to `requests` package and code updated accordingly - `SuggestedFees` type moved to a new `fees` package and code updated accordingly - `SendType` type moved to a new `sendtype` package and code updated accordingly - the following functions moved to `common` package - `ArrayContainsElement` - `ArraysWithSameElements` - `SameSingleChainTransfer` - `CopyMapGeneric` - `GweiToEth` - `WeiToGwei` - the following consts moved to `common` package - `HexAddressLength` - `SupportedNetworks` - `SupportedTestNetworks`
2024-08-28 11:17:59 +00:00
"github.com/status-im/status-go/services/wallet/router/sendtype"
2024-08-28 08:59:19 +00:00
"github.com/status-im/status-go/services/wallet/token"
)
chore!: router code organization improvements It's a breaking change due to errors' changes, the list of mapped old error codes: - `"WR-001"` is now `"WRR-001"` - `"WR-002"` is now `"WRR-002"` - `"WR-003"` is now `"WRR-003"` - `"WR-004"` is now `"WRR-004"` - `"WR-005"` is now `"WRR-005"` - `"WR-006"` is now `"WRR-006"` - `"WR-007"` is now `"WRR-007"` - `"WR-008"` is now `"WRR-008"` - `"WR-009"` is now `"WRR-009"` - `"WR-010"` is now `"WRR-010"` - `"WR-011"` is now `"WRR-011"` - `"WR-012"` is now `"WRR-012"` - `"WR-013"` is now `"WRR-013"` - `"WR-014"` is now `"WRR-014"` - `"WR-015"` is now `"WRR-015"` - `"WR-019"` is now `"WRR-016"` - `"WR-020"` is now `"WRR-017"` - `"WR-021"` is now `"WRR-018"` - `"WR-025"` is now `"WRR-019"` - `"WR-016"` is now `"WR-001"` - `"WR-017"` is now `"WR-002"` - `"WR-018"` is now `"WR-003"` - `"WR-022"` is now `"WR-004"` - `"WR-023"` is now `"WR-005"` - `"WR-024"` is now `"WR-006"` - `"WR-026"` is now `"WR-007"` - `"WR-027"` is now `"WR-008"` Other changes: - `RouteInputParams` type moved to `requests` package and code updated accordingly - `SuggestedFees` type moved to a new `fees` package and code updated accordingly - `SendType` type moved to a new `sendtype` package and code updated accordingly - the following functions moved to `common` package - `ArrayContainsElement` - `ArraysWithSameElements` - `SameSingleChainTransfer` - `CopyMapGeneric` - `GweiToEth` - `WeiToGwei` - the following consts moved to `common` package - `HexAddressLength` - `SupportedNetworks` - `SupportedTestNetworks`
2024-08-28 11:17:59 +00:00
func (r *Router) requireApproval(ctx context.Context, sendType sendtype.SendType, approvalContractAddress *common.Address, params pathprocessor.ProcessorInputParams) (
bool, *big.Int, error) {
2024-08-28 08:59:19 +00:00
if sendType.IsCollectiblesTransfer() || sendType.IsEnsTransfer() || sendType.IsStickersTransfer() {
return false, nil, nil
2024-08-28 08:59:19 +00:00
}
if params.FromToken.IsNative() {
return false, nil, nil
2024-08-28 08:59:19 +00:00
}
contractMaker, err := contracts.NewContractMaker(r.rpcClient)
if err != nil {
return false, nil, err
2024-08-28 08:59:19 +00:00
}
contract, err := contractMaker.NewERC20(params.FromChain.ChainID, params.FromToken.Address)
if err != nil {
return false, nil, err
2024-08-28 08:59:19 +00:00
}
if approvalContractAddress == nil || *approvalContractAddress == walletCommon.ZeroAddress() {
return false, nil, nil
2024-08-28 08:59:19 +00:00
}
if params.TestsMode {
return true, params.AmountIn, nil
2024-08-28 08:59:19 +00:00
}
allowance, err := contract.Allowance(&bind.CallOpts{
Context: ctx,
}, params.FromAddr, *approvalContractAddress)
if err != nil {
return false, nil, err
2024-08-28 08:59:19 +00:00
}
if allowance.Cmp(params.AmountIn) >= 0 {
return false, nil, nil
2024-08-28 08:59:19 +00:00
}
return true, params.AmountIn, nil
}
func (r *Router) estimateGasForApproval(params pathprocessor.ProcessorInputParams, approvalContractAddress *common.Address) (uint64, error) {
data, err := walletCommon.PackApprovalInputData(params.AmountIn, approvalContractAddress)
if err != nil {
return 0, err
}
ethClient, err := r.rpcClient.EthClient(params.FromChain.ChainID)
2024-08-28 08:59:19 +00:00
if err != nil {
return 0, err
2024-08-28 08:59:19 +00:00
}
return ethClient.EstimateGas(context.Background(), ethereum.CallMsg{
2024-08-28 08:59:19 +00:00
From: params.FromAddr,
To: &params.FromToken.Address,
Value: walletCommon.ZeroBigIntValue(),
2024-08-28 08:59:19 +00:00
Data: data,
})
}
func (r *Router) calculateApprovalL1Fee(amountIn *big.Int, chainID uint64, approvalContractAddress *common.Address) (uint64, error) {
ethClient, err := r.rpcClient.EthClient(chainID)
if err != nil {
return 0, err
}
return CalculateApprovalL1Fee(amountIn, chainID, approvalContractAddress, ethClient)
}
func CalculateApprovalL1Fee(amountIn *big.Int, chainID uint64, approvalContractAddress *common.Address, ethClient chain.ClientInterface) (uint64, error) {
data, err := walletCommon.PackApprovalInputData(amountIn, approvalContractAddress)
2024-08-28 08:59:19 +00:00
if err != nil {
return 0, err
2024-08-28 08:59:19 +00:00
}
var l1Fee uint64
oracleContractAddress, err := gaspriceoracle.ContractAddress(chainID)
2024-08-28 08:59:19 +00:00
if err == nil {
oracleContract, err := gaspriceoracle.NewGaspriceoracleCaller(oracleContractAddress, ethClient)
if err != nil {
return 0, err
2024-08-28 08:59:19 +00:00
}
callOpt := &bind.CallOpts{}
l1FeeResult, err := oracleContract.GetL1Fee(callOpt, data)
if err == nil {
l1Fee = l1FeeResult.Uint64()
}
2024-08-28 08:59:19 +00:00
}
// return 0 if we failed to get the fee
return l1Fee, nil
2024-08-28 08:59:19 +00:00
}
func (r *Router) getERC1155Balance(ctx context.Context, network *params.Network, token *token.Token, account common.Address) (*big.Int, error) {
tokenID, success := new(big.Int).SetString(token.Symbol, 10)
if !success {
return nil, errors.New("failed to convert token symbol to big.Int")
}
balances, err := r.collectiblesManager.FetchERC1155Balances(
ctx,
account,
walletCommon.ChainID(network.ChainID),
token.Address,
[]*bigint.BigInt{&bigint.BigInt{Int: tokenID}},
)
if err != nil {
return nil, err
}
if len(balances) != 1 || balances[0] == nil {
return nil, errors.New("invalid ERC1155 balance fetch response")
}
return balances[0].Int, nil
}
func (r *Router) getBalance(ctx context.Context, chainID uint64, token *token.Token, account common.Address) (*big.Int, error) {
client, err := r.rpcClient.EthClient(chainID)
if err != nil {
return nil, err
}
return r.tokenManager.GetBalance(ctx, client, account, token.Address)
}
func (r *Router) cacluateFees(ctx context.Context, path *routs.Path, fetchedFees *fees.SuggestedFees, testsMode bool, testApprovalL1Fee uint64) (err error) {
var (
l1ApprovalFee uint64
)
if path.ApprovalRequired {
if testsMode {
l1ApprovalFee = testApprovalL1Fee
} else {
l1ApprovalFee, err = r.calculateApprovalL1Fee(path.AmountIn.ToInt(), path.FromChain.ChainID, path.ApprovalContractAddress)
if err != nil {
return err
}
}
}
// 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
var l1FeeWei uint64 = 0
// if input.SendType.needL1Fee() {
// txInputData, err := pProcessor.PackTxInputData(processorInputParams)
// if err != nil {
// continue
// }
// l1FeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData)
// }
r.lastInputParamsMutex.Lock()
gasFeeMode := r.lastInputParams.GasFeeMode
r.lastInputParamsMutex.Unlock()
maxFeesPerGas := fetchedFees.FeeFor(gasFeeMode)
// calculate ETH fees
ethTotalFees := big.NewInt(0)
txFeeInWei := new(big.Int).Mul(maxFeesPerGas, big.NewInt(int64(path.TxGasAmount)))
ethTotalFees.Add(ethTotalFees, txFeeInWei)
txL1FeeInWei := big.NewInt(0)
if l1FeeWei > 0 {
txL1FeeInWei = big.NewInt(int64(l1FeeWei))
ethTotalFees.Add(ethTotalFees, txL1FeeInWei)
}
approvalFeeInWei := big.NewInt(0)
approvalL1FeeInWei := big.NewInt(0)
if path.ApprovalRequired {
approvalFeeInWei.Mul(maxFeesPerGas, big.NewInt(int64(path.ApprovalGasAmount)))
ethTotalFees.Add(ethTotalFees, approvalFeeInWei)
if l1ApprovalFee > 0 {
approvalL1FeeInWei = big.NewInt(int64(l1ApprovalFee))
ethTotalFees.Add(ethTotalFees, approvalL1FeeInWei)
}
}
// 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))
requiredNativeBalance := big.NewInt(0)
requiredTokenBalance := big.NewInt(0)
if path.FromToken.IsNative() {
requiredNativeBalance.Add(requiredNativeBalance, path.AmountIn.ToInt())
if !path.SubtractFees {
requiredNativeBalance.Add(requiredNativeBalance, ethTotalFees)
}
} else {
requiredTokenBalance.Add(requiredTokenBalance, path.AmountIn.ToInt())
requiredNativeBalance.Add(requiredNativeBalance, ethTotalFees)
}
// set the values
path.SuggestedLevelsForMaxFeesPerGas = fetchedFees.MaxFeesLevels
path.MaxFeesPerGas = (*hexutil.Big)(maxFeesPerGas)
path.TxBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
path.TxPriorityFee = (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas)
path.TxFee = (*hexutil.Big)(txFeeInWei)
path.TxL1Fee = (*hexutil.Big)(txL1FeeInWei)
path.ApprovalBaseFee = (*hexutil.Big)(fetchedFees.BaseFee)
path.ApprovalPriorityFee = (*hexutil.Big)(fetchedFees.MaxPriorityFeePerGas)
path.ApprovalFee = (*hexutil.Big)(approvalFeeInWei)
path.ApprovalL1Fee = (*hexutil.Big)(approvalL1FeeInWei)
path.TxTotalFee = (*hexutil.Big)(ethTotalFees)
path.RequiredTokenBalance = requiredTokenBalance
path.RequiredNativeBalance = requiredNativeBalance
return nil
}
func findToken(sendType sendtype.SendType, tokenManager *token.Manager, collectibles *collectibles.Service, account common.Address, network *params.Network, tokenID string) *token.Token {
if !sendType.IsCollectiblesTransfer() {
return tokenManager.FindToken(network, tokenID)
}
parts := strings.Split(tokenID, ":")
contractAddress := common.HexToAddress(parts[0])
collectibleTokenID, success := new(big.Int).SetString(parts[1], 10)
if !success {
return nil
}
uniqueID, err := collectibles.GetOwnedCollectible(walletCommon.ChainID(network.ChainID), account, contractAddress, collectibleTokenID)
if err != nil || uniqueID == nil {
return nil
}
return &token.Token{
Address: contractAddress,
Symbol: collectibleTokenID.String(),
Decimals: 0,
ChainID: network.ChainID,
}
}
func fetchPrices(sendType sendtype.SendType, marketManager *market.Manager, tokenIDs []string) (map[string]float64, error) {
nonUniqueSymbols := append(tokenIDs, "ETH")
// remove duplicate enteries
slices.Sort(nonUniqueSymbols)
symbols := slices.Compact(nonUniqueSymbols)
if sendType.IsCollectiblesTransfer() {
symbols = []string{"ETH"}
}
pricesMap, err := marketManager.GetOrFetchPrices(symbols, []string{"USD"}, market.MaxAgeInSecondsForFresh)
if err != nil {
return nil, err
}
prices := make(map[string]float64, 0)
for symbol, pricePerCurrency := range pricesMap {
prices[symbol] = pricePerCurrency["USD"].Price
}
if sendType.IsCollectiblesTransfer() {
for _, tokenID := range tokenIDs {
prices[tokenID] = 0
}
}
return prices, nil
}