github.com/status-im/status-go@v1.1.0/services/communitytokens/estimations.go (about)

     1  package communitytokens
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     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/common"
    12  	"github.com/ethereum/go-ethereum/common/hexutil"
    13  	"github.com/ethereum/go-ethereum/log"
    14  	"github.com/ethereum/go-ethereum/params"
    15  	"github.com/status-im/status-go/contracts/community-tokens/assets"
    16  	"github.com/status-im/status-go/contracts/community-tokens/collectibles"
    17  	communitytokendeployer "github.com/status-im/status-go/contracts/community-tokens/deployer"
    18  	"github.com/status-im/status-go/eth-node/types"
    19  	"github.com/status-im/status-go/protocol/protobuf"
    20  	"github.com/status-im/status-go/services/wallet/bigint"
    21  	"github.com/status-im/status-go/services/wallet/router/fees"
    22  	"github.com/status-im/status-go/transactions"
    23  )
    24  
    25  type CommunityTokenFees struct {
    26  	GasUnits      uint64                  `json:"gasUnits"`
    27  	SuggestedFees *fees.SuggestedFeesGwei `json:"suggestedFees"`
    28  }
    29  
    30  func weiToGwei(val *big.Int) *big.Float {
    31  	result := new(big.Float)
    32  	result.SetInt(val)
    33  
    34  	unit := new(big.Int)
    35  	unit.SetInt64(params.GWei)
    36  
    37  	return result.Quo(result, new(big.Float).SetInt(unit))
    38  }
    39  
    40  func gweiToWei(val *big.Float) *big.Int {
    41  	res, _ := new(big.Float).Mul(val, big.NewFloat(1000000000)).Int(nil)
    42  	return res
    43  }
    44  
    45  func (s *Service) deployOwnerTokenEstimate(ctx context.Context, chainID uint64, fromAddress string,
    46  	ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters,
    47  	communityID string, signerPubKey string) (*CommunityTokenFees, error) {
    48  
    49  	gasUnits, err := s.deployOwnerTokenGasUnits(ctx, chainID, fromAddress, ownerTokenParameters, masterTokenParameters,
    50  		communityID, signerPubKey)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	deployerAddress, err := communitytokendeployer.ContractAddress(chainID)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	return s.prepareCommunityTokenFees(ctx, common.HexToAddress(fromAddress), &deployerAddress, gasUnits, chainID)
    61  }
    62  
    63  func (s *Service) deployCollectiblesEstimate(ctx context.Context, chainID uint64, fromAddress string) (*CommunityTokenFees, error) {
    64  	gasUnits, err := s.deployCollectiblesGasUnits(ctx, chainID, fromAddress)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	return s.prepareCommunityTokenFees(ctx, common.HexToAddress(fromAddress), nil, gasUnits, chainID)
    69  }
    70  
    71  func (s *Service) deployAssetsEstimate(ctx context.Context, chainID uint64, fromAddress string) (*CommunityTokenFees, error) {
    72  	gasUnits, err := s.deployAssetsGasUnits(ctx, chainID, fromAddress)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	return s.prepareCommunityTokenFees(ctx, common.HexToAddress(fromAddress), nil, gasUnits, chainID)
    77  }
    78  
    79  func (s *Service) mintTokensEstimate(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (*CommunityTokenFees, error) {
    80  	gasUnits, err := s.mintTokensGasUnits(ctx, chainID, contractAddress, fromAddress, walletAddresses, amount)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	toAddress := common.HexToAddress(contractAddress)
    85  	return s.prepareCommunityTokenFees(ctx, common.HexToAddress(fromAddress), &toAddress, gasUnits, chainID)
    86  }
    87  
    88  func (s *Service) remoteBurnEstimate(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, tokenIds []*bigint.BigInt) (*CommunityTokenFees, error) {
    89  	gasUnits, err := s.remoteBurnGasUnits(ctx, chainID, contractAddress, fromAddress, tokenIds)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	toAddress := common.HexToAddress(contractAddress)
    94  	return s.prepareCommunityTokenFees(ctx, common.HexToAddress(fromAddress), &toAddress, gasUnits, chainID)
    95  }
    96  
    97  func (s *Service) burnEstimate(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, burnAmount *bigint.BigInt) (*CommunityTokenFees, error) {
    98  	gasUnits, err := s.burnGasUnits(ctx, chainID, contractAddress, fromAddress, burnAmount)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	toAddress := common.HexToAddress(contractAddress)
   103  	return s.prepareCommunityTokenFees(ctx, common.HexToAddress(fromAddress), &toAddress, gasUnits, chainID)
   104  }
   105  
   106  func (s *Service) setSignerPubKeyEstimate(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, newSignerPubKey string) (*CommunityTokenFees, error) {
   107  	gasUnits, err := s.setSignerPubKeyGasUnits(ctx, chainID, contractAddress, fromAddress, newSignerPubKey)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	toAddress := common.HexToAddress(contractAddress)
   112  	return s.prepareCommunityTokenFees(ctx, common.HexToAddress(fromAddress), &toAddress, gasUnits, chainID)
   113  }
   114  
   115  func (s *Service) setSignerPubKeyGasUnits(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, newSignerPubKey string) (uint64, error) {
   116  	if len(newSignerPubKey) <= 0 {
   117  		return 0, fmt.Errorf("signerPubKey is empty")
   118  	}
   119  
   120  	contractInst, err := s.NewOwnerTokenInstance(chainID, contractAddress)
   121  	if err != nil {
   122  		return 0, err
   123  	}
   124  	ownerTokenInstance := &OwnerTokenInstance{instance: contractInst}
   125  
   126  	return s.estimateMethodForTokenInstance(ctx, ownerTokenInstance, chainID, contractAddress, fromAddress, "setSignerPublicKey", common.FromHex(newSignerPubKey))
   127  }
   128  
   129  func (s *Service) burnGasUnits(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, burnAmount *bigint.BigInt) (uint64, error) {
   130  	err := s.validateBurnAmount(ctx, burnAmount, chainID, contractAddress)
   131  	if err != nil {
   132  		return 0, err
   133  	}
   134  
   135  	newMaxSupply, err := s.prepareNewMaxSupply(ctx, chainID, contractAddress, burnAmount)
   136  	if err != nil {
   137  		return 0, err
   138  	}
   139  
   140  	return s.estimateMethod(ctx, chainID, contractAddress, fromAddress, "setMaxSupply", newMaxSupply)
   141  }
   142  
   143  func (s *Service) remoteBurnGasUnits(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, tokenIds []*bigint.BigInt) (uint64, error) {
   144  	err := s.validateTokens(tokenIds)
   145  	if err != nil {
   146  		return 0, err
   147  	}
   148  
   149  	var tempTokenIds []*big.Int
   150  	for _, v := range tokenIds {
   151  		tempTokenIds = append(tempTokenIds, v.Int)
   152  	}
   153  
   154  	return s.estimateMethod(ctx, chainID, contractAddress, fromAddress, "remoteBurn", tempTokenIds)
   155  }
   156  
   157  func (s *Service) deployOwnerTokenGasUnits(ctx context.Context, chainID uint64, fromAddress string,
   158  	ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters,
   159  	communityID string, signerPubKey string) (uint64, error) {
   160  	ethClient, err := s.manager.rpcClient.EthClient(chainID)
   161  	if err != nil {
   162  		log.Error(err.Error())
   163  		return 0, err
   164  	}
   165  
   166  	deployerAddress, err := communitytokendeployer.ContractAddress(chainID)
   167  	if err != nil {
   168  		return 0, err
   169  	}
   170  
   171  	deployerABI, err := abi.JSON(strings.NewReader(communitytokendeployer.CommunityTokenDeployerABI))
   172  	if err != nil {
   173  		return 0, err
   174  	}
   175  
   176  	ownerTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{
   177  		Name:    ownerTokenParameters.Name,
   178  		Symbol:  ownerTokenParameters.Symbol,
   179  		BaseURI: ownerTokenParameters.TokenURI,
   180  	}
   181  
   182  	masterTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{
   183  		Name:    masterTokenParameters.Name,
   184  		Symbol:  masterTokenParameters.Symbol,
   185  		BaseURI: masterTokenParameters.TokenURI,
   186  	}
   187  
   188  	signature, err := s.Messenger.CreateCommunityTokenDeploymentSignature(ctx, chainID, fromAddress, communityID)
   189  	if err != nil {
   190  		return 0, err
   191  	}
   192  
   193  	communitySignature, err := prepareDeploymentSignatureStruct(types.HexBytes(signature).String(), communityID, common.HexToAddress(fromAddress))
   194  	if err != nil {
   195  		return 0, err
   196  	}
   197  
   198  	data, err := deployerABI.Pack("deploy", ownerTokenConfig, masterTokenConfig, communitySignature, common.FromHex(signerPubKey))
   199  
   200  	if err != nil {
   201  		return 0, err
   202  	}
   203  
   204  	toAddr := deployerAddress
   205  	fromAddr := common.HexToAddress(fromAddress)
   206  
   207  	callMsg := ethereum.CallMsg{
   208  		From:  fromAddr,
   209  		To:    &toAddr,
   210  		Value: big.NewInt(0),
   211  		Data:  data,
   212  	}
   213  
   214  	estimate, err := ethClient.EstimateGas(ctx, callMsg)
   215  	if err != nil {
   216  		return 0, err
   217  	}
   218  
   219  	finalEstimation := estimate + uint64(float32(estimate)*0.1)
   220  	log.Debug("Owner token deployment gas estimation: ", finalEstimation)
   221  	return finalEstimation, nil
   222  }
   223  
   224  func (s *Service) deployCollectiblesGasUnits(ctx context.Context, chainID uint64, fromAddress string) (uint64, error) {
   225  	ethClient, err := s.manager.rpcClient.EthClient(chainID)
   226  	if err != nil {
   227  		log.Error(err.Error())
   228  		return 0, err
   229  	}
   230  
   231  	collectiblesABI, err := abi.JSON(strings.NewReader(collectibles.CollectiblesABI))
   232  	if err != nil {
   233  		return 0, err
   234  	}
   235  
   236  	// use random parameters, they will not have impact on deployment results
   237  	data, err := collectiblesABI.Pack("" /*constructor name is empty*/, "name", "SYMBOL", big.NewInt(20), true, false, "tokenUri",
   238  		common.HexToAddress("0x77b48394c650520012795a1a25696d7eb542d110"), common.HexToAddress("0x77b48394c650520012795a1a25696d7eb542d110"))
   239  	if err != nil {
   240  		return 0, err
   241  	}
   242  
   243  	callMsg := ethereum.CallMsg{
   244  		From:  common.HexToAddress(fromAddress),
   245  		To:    nil,
   246  		Value: big.NewInt(0),
   247  		Data:  append(common.FromHex(collectibles.CollectiblesBin), data...),
   248  	}
   249  	estimate, err := ethClient.EstimateGas(ctx, callMsg)
   250  	if err != nil {
   251  		return 0, err
   252  	}
   253  
   254  	finalEstimation := estimate + uint64(float32(estimate)*0.1)
   255  	log.Debug("Collectibles deployment gas estimation: ", finalEstimation)
   256  	return finalEstimation, nil
   257  }
   258  
   259  func (s *Service) deployAssetsGasUnits(ctx context.Context, chainID uint64, fromAddress string) (uint64, error) {
   260  	ethClient, err := s.manager.rpcClient.EthClient(chainID)
   261  	if err != nil {
   262  		log.Error(err.Error())
   263  		return 0, err
   264  	}
   265  
   266  	assetsABI, err := abi.JSON(strings.NewReader(assets.AssetsABI))
   267  	if err != nil {
   268  		return 0, err
   269  	}
   270  
   271  	// use random parameters, they will not have impact on deployment results
   272  	data, err := assetsABI.Pack("" /*constructor name is empty*/, "name", "SYMBOL", uint8(18), big.NewInt(20), "tokenUri",
   273  		common.HexToAddress("0x77b48394c650520012795a1a25696d7eb542d110"), common.HexToAddress("0x77b48394c650520012795a1a25696d7eb542d110"))
   274  	if err != nil {
   275  		return 0, err
   276  	}
   277  
   278  	callMsg := ethereum.CallMsg{
   279  		From:  common.HexToAddress(fromAddress),
   280  		To:    nil,
   281  		Value: big.NewInt(0),
   282  		Data:  append(common.FromHex(assets.AssetsBin), data...),
   283  	}
   284  	estimate, err := ethClient.EstimateGas(ctx, callMsg)
   285  	if err != nil {
   286  		return 0, err
   287  	}
   288  
   289  	finalEstimation := estimate + uint64(float32(estimate)*0.1)
   290  	log.Debug("Assets deployment gas estimation: ", finalEstimation)
   291  	return finalEstimation, nil
   292  }
   293  
   294  // if we want to mint 2 tokens to addresses ["a", "b"] we need to mint
   295  // twice to every address - we need to send to smart contract table ["a", "a", "b", "b"]
   296  func multiplyWalletAddresses(amount *bigint.BigInt, contractAddresses []string) []string {
   297  	var totalAddresses []string
   298  	for i := big.NewInt(1); i.Cmp(amount.Int) <= 0; {
   299  		totalAddresses = append(totalAddresses, contractAddresses...)
   300  		i.Add(i, big.NewInt(1))
   301  	}
   302  	return totalAddresses
   303  }
   304  
   305  func prepareMintCollectiblesData(walletAddresses []string, amount *bigint.BigInt) []common.Address {
   306  	totalAddresses := multiplyWalletAddresses(amount, walletAddresses)
   307  	var usersAddresses = []common.Address{}
   308  	for _, k := range totalAddresses {
   309  		usersAddresses = append(usersAddresses, common.HexToAddress(k))
   310  	}
   311  	return usersAddresses
   312  }
   313  
   314  func prepareMintAssetsData(walletAddresses []string, amount *bigint.BigInt) ([]common.Address, []*big.Int) {
   315  	var usersAddresses = []common.Address{}
   316  	var amountsList = []*big.Int{}
   317  	for _, k := range walletAddresses {
   318  		usersAddresses = append(usersAddresses, common.HexToAddress(k))
   319  		amountsList = append(amountsList, amount.Int)
   320  	}
   321  	return usersAddresses, amountsList
   322  }
   323  
   324  func (s *Service) mintCollectiblesGasUnits(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (uint64, error) {
   325  	err := s.ValidateWalletsAndAmounts(walletAddresses, amount)
   326  	if err != nil {
   327  		return 0, err
   328  	}
   329  	usersAddresses := prepareMintCollectiblesData(walletAddresses, amount)
   330  	return s.estimateMethod(ctx, chainID, contractAddress, fromAddress, "mintTo", usersAddresses)
   331  }
   332  
   333  func (s *Service) mintAssetsGasUnits(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (uint64, error) {
   334  	err := s.ValidateWalletsAndAmounts(walletAddresses, amount)
   335  	if err != nil {
   336  		return 0, err
   337  	}
   338  	usersAddresses, amountsList := prepareMintAssetsData(walletAddresses, amount)
   339  	return s.estimateMethod(ctx, chainID, contractAddress, fromAddress, "mintTo", usersAddresses, amountsList)
   340  }
   341  
   342  func (s *Service) mintTokensGasUnits(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (uint64, error) {
   343  	tokenType, err := s.db.GetTokenType(chainID, contractAddress)
   344  	if err != nil {
   345  		return 0, err
   346  	}
   347  
   348  	switch tokenType {
   349  	case protobuf.CommunityTokenType_ERC721:
   350  		return s.mintCollectiblesGasUnits(ctx, chainID, contractAddress, fromAddress, walletAddresses, amount)
   351  	case protobuf.CommunityTokenType_ERC20:
   352  		return s.mintAssetsGasUnits(ctx, chainID, contractAddress, fromAddress, walletAddresses, amount)
   353  	default:
   354  		return 0, fmt.Errorf("unknown token type: %v", tokenType)
   355  	}
   356  }
   357  
   358  func (s *Service) prepareCommunityTokenFees(ctx context.Context, from common.Address, to *common.Address, gasUnits uint64, chainID uint64) (*CommunityTokenFees, error) {
   359  	suggestedFees, err := s.feeManager.SuggestedFeesGwei(ctx, chainID)
   360  	if err != nil {
   361  		return nil, err
   362  	}
   363  
   364  	txArgs := s.suggestedFeesToSendTxArgs(from, to, gasUnits, suggestedFees)
   365  
   366  	l1Fee, err := s.estimateL1Fee(ctx, chainID, txArgs)
   367  	if err == nil {
   368  		suggestedFees.L1GasFee = weiToGwei(big.NewInt(int64(l1Fee)))
   369  	}
   370  	return &CommunityTokenFees{
   371  		GasUnits:      gasUnits,
   372  		SuggestedFees: suggestedFees,
   373  	}, nil
   374  }
   375  
   376  func (s *Service) suggestedFeesToSendTxArgs(from common.Address, to *common.Address, gas uint64, suggestedFees *fees.SuggestedFeesGwei) transactions.SendTxArgs {
   377  	sendArgs := transactions.SendTxArgs{}
   378  	sendArgs.From = types.Address(from)
   379  	sendArgs.To = (*types.Address)(to)
   380  	sendArgs.Gas = (*hexutil.Uint64)(&gas)
   381  	if suggestedFees.EIP1559Enabled {
   382  		sendArgs.MaxPriorityFeePerGas = (*hexutil.Big)(gweiToWei(suggestedFees.MaxPriorityFeePerGas))
   383  		sendArgs.MaxFeePerGas = (*hexutil.Big)(gweiToWei(suggestedFees.MaxFeePerGasMedium))
   384  	} else {
   385  		sendArgs.GasPrice = (*hexutil.Big)(gweiToWei(suggestedFees.GasPrice))
   386  	}
   387  	return sendArgs
   388  }
   389  
   390  func (s *Service) estimateL1Fee(ctx context.Context, chainID uint64, sendArgs transactions.SendTxArgs) (uint64, error) {
   391  	transaction, _, err := s.transactor.ValidateAndBuildTransaction(chainID, sendArgs, -1)
   392  	if err != nil {
   393  		return 0, err
   394  	}
   395  
   396  	data, err := transaction.MarshalBinary()
   397  	if err != nil {
   398  		return 0, err
   399  	}
   400  
   401  	return s.feeManager.GetL1Fee(ctx, chainID, data)
   402  }
   403  
   404  func (s *Service) estimateMethodForTokenInstance(ctx context.Context, contractInstance TokenInstance, chainID uint64, contractAddress string, fromAddress string, methodName string, args ...interface{}) (uint64, error) {
   405  	ethClient, err := s.manager.rpcClient.EthClient(chainID)
   406  	if err != nil {
   407  		log.Error(err.Error())
   408  		return 0, err
   409  	}
   410  
   411  	data, err := contractInstance.PackMethod(ctx, methodName, args...)
   412  
   413  	if err != nil {
   414  		return 0, err
   415  	}
   416  
   417  	toAddr := common.HexToAddress(contractAddress)
   418  	fromAddr := common.HexToAddress(fromAddress)
   419  
   420  	callMsg := ethereum.CallMsg{
   421  		From:  fromAddr,
   422  		To:    &toAddr,
   423  		Value: big.NewInt(0),
   424  		Data:  data,
   425  	}
   426  	estimate, err := ethClient.EstimateGas(ctx, callMsg)
   427  
   428  	if err != nil {
   429  		return 0, err
   430  	}
   431  	return estimate + uint64(float32(estimate)*0.1), nil
   432  }
   433  
   434  func (s *Service) estimateMethod(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, methodName string, args ...interface{}) (uint64, error) {
   435  	contractInst, err := NewTokenInstance(s, chainID, contractAddress)
   436  	if err != nil {
   437  		return 0, err
   438  	}
   439  	return s.estimateMethodForTokenInstance(ctx, contractInst, chainID, contractAddress, fromAddress, methodName, args...)
   440  }