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 }