saledjenic 79b1c547d1
Improvements on resolving nonce (#5658)
* chore_: unused `BuildTx` function removed from the processor interface and types that are implement it

Since the `BuildTx` function is not used anywhere, it's removed from the code.

* fix_: resolving nonce improvements

When the app sends more than a single tx from the same account on the same chain, some
chains do not return appropriate nonce (they do not consider pending txs), because of
that we place more tx with the same nonce, where all but the first one fail.

Changes in this PR keep track of nonces being used in the same sending/bridging flow, which means
for the first tx from the multi txs the app asks the chain for the nonce, and every next nonce is resolved
by incrementing the last used nonce by 1.
2024-08-12 08:07:32 -04:00

441 lines
16 KiB
Go

package communitytokens
import (
"context"
"fmt"
"math/big"
"strings"
"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"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/status-im/status-go/contracts/community-tokens/assets"
"github.com/status-im/status-go/contracts/community-tokens/collectibles"
communitytokendeployer "github.com/status-im/status-go/contracts/community-tokens/deployer"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/services/wallet/bigint"
"github.com/status-im/status-go/services/wallet/router"
"github.com/status-im/status-go/transactions"
)
type CommunityTokenFees struct {
GasUnits uint64 `json:"gasUnits"`
SuggestedFees *router.SuggestedFeesGwei `json:"suggestedFees"`
}
func weiToGwei(val *big.Int) *big.Float {
result := new(big.Float)
result.SetInt(val)
unit := new(big.Int)
unit.SetInt64(params.GWei)
return result.Quo(result, new(big.Float).SetInt(unit))
}
func gweiToWei(val *big.Float) *big.Int {
res, _ := new(big.Float).Mul(val, big.NewFloat(1000000000)).Int(nil)
return res
}
func (s *Service) deployOwnerTokenEstimate(ctx context.Context, chainID uint64, fromAddress string,
ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters,
communityID string, signerPubKey string) (*CommunityTokenFees, error) {
gasUnits, err := s.deployOwnerTokenGasUnits(ctx, chainID, fromAddress, ownerTokenParameters, masterTokenParameters,
communityID, signerPubKey)
if err != nil {
return nil, err
}
deployerAddress, err := communitytokendeployer.ContractAddress(chainID)
if err != nil {
return nil, err
}
return s.prepareCommunityTokenFees(ctx, common.HexToAddress(fromAddress), &deployerAddress, gasUnits, chainID)
}
func (s *Service) deployCollectiblesEstimate(ctx context.Context, chainID uint64, fromAddress string) (*CommunityTokenFees, error) {
gasUnits, err := s.deployCollectiblesGasUnits(ctx, chainID, fromAddress)
if err != nil {
return nil, err
}
return s.prepareCommunityTokenFees(ctx, common.HexToAddress(fromAddress), nil, gasUnits, chainID)
}
func (s *Service) deployAssetsEstimate(ctx context.Context, chainID uint64, fromAddress string) (*CommunityTokenFees, error) {
gasUnits, err := s.deployAssetsGasUnits(ctx, chainID, fromAddress)
if err != nil {
return nil, err
}
return s.prepareCommunityTokenFees(ctx, common.HexToAddress(fromAddress), nil, gasUnits, chainID)
}
func (s *Service) mintTokensEstimate(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (*CommunityTokenFees, error) {
gasUnits, err := s.mintTokensGasUnits(ctx, chainID, contractAddress, fromAddress, walletAddresses, amount)
if err != nil {
return nil, err
}
toAddress := common.HexToAddress(contractAddress)
return s.prepareCommunityTokenFees(ctx, common.HexToAddress(fromAddress), &toAddress, gasUnits, chainID)
}
func (s *Service) remoteBurnEstimate(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, tokenIds []*bigint.BigInt) (*CommunityTokenFees, error) {
gasUnits, err := s.remoteBurnGasUnits(ctx, chainID, contractAddress, fromAddress, tokenIds)
if err != nil {
return nil, err
}
toAddress := common.HexToAddress(contractAddress)
return s.prepareCommunityTokenFees(ctx, common.HexToAddress(fromAddress), &toAddress, gasUnits, chainID)
}
func (s *Service) burnEstimate(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, burnAmount *bigint.BigInt) (*CommunityTokenFees, error) {
gasUnits, err := s.burnGasUnits(ctx, chainID, contractAddress, fromAddress, burnAmount)
if err != nil {
return nil, err
}
toAddress := common.HexToAddress(contractAddress)
return s.prepareCommunityTokenFees(ctx, common.HexToAddress(fromAddress), &toAddress, gasUnits, chainID)
}
func (s *Service) setSignerPubKeyEstimate(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, newSignerPubKey string) (*CommunityTokenFees, error) {
gasUnits, err := s.setSignerPubKeyGasUnits(ctx, chainID, contractAddress, fromAddress, newSignerPubKey)
if err != nil {
return nil, err
}
toAddress := common.HexToAddress(contractAddress)
return s.prepareCommunityTokenFees(ctx, common.HexToAddress(fromAddress), &toAddress, gasUnits, chainID)
}
func (s *Service) setSignerPubKeyGasUnits(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, newSignerPubKey string) (uint64, error) {
if len(newSignerPubKey) <= 0 {
return 0, fmt.Errorf("signerPubKey is empty")
}
contractInst, err := s.NewOwnerTokenInstance(chainID, contractAddress)
if err != nil {
return 0, err
}
ownerTokenInstance := &OwnerTokenInstance{instance: contractInst}
return s.estimateMethodForTokenInstance(ctx, ownerTokenInstance, chainID, contractAddress, fromAddress, "setSignerPublicKey", common.FromHex(newSignerPubKey))
}
func (s *Service) burnGasUnits(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, burnAmount *bigint.BigInt) (uint64, error) {
err := s.validateBurnAmount(ctx, burnAmount, chainID, contractAddress)
if err != nil {
return 0, err
}
newMaxSupply, err := s.prepareNewMaxSupply(ctx, chainID, contractAddress, burnAmount)
if err != nil {
return 0, err
}
return s.estimateMethod(ctx, chainID, contractAddress, fromAddress, "setMaxSupply", newMaxSupply)
}
func (s *Service) remoteBurnGasUnits(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, tokenIds []*bigint.BigInt) (uint64, error) {
err := s.validateTokens(tokenIds)
if err != nil {
return 0, err
}
var tempTokenIds []*big.Int
for _, v := range tokenIds {
tempTokenIds = append(tempTokenIds, v.Int)
}
return s.estimateMethod(ctx, chainID, contractAddress, fromAddress, "remoteBurn", tempTokenIds)
}
func (s *Service) deployOwnerTokenGasUnits(ctx context.Context, chainID uint64, fromAddress string,
ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters,
communityID string, signerPubKey string) (uint64, error) {
ethClient, err := s.manager.rpcClient.EthClient(chainID)
if err != nil {
log.Error(err.Error())
return 0, err
}
deployerAddress, err := communitytokendeployer.ContractAddress(chainID)
if err != nil {
return 0, err
}
deployerABI, err := abi.JSON(strings.NewReader(communitytokendeployer.CommunityTokenDeployerABI))
if err != nil {
return 0, err
}
ownerTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{
Name: ownerTokenParameters.Name,
Symbol: ownerTokenParameters.Symbol,
BaseURI: ownerTokenParameters.TokenURI,
}
masterTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{
Name: masterTokenParameters.Name,
Symbol: masterTokenParameters.Symbol,
BaseURI: masterTokenParameters.TokenURI,
}
signature, err := s.Messenger.CreateCommunityTokenDeploymentSignature(ctx, chainID, fromAddress, communityID)
if err != nil {
return 0, err
}
communitySignature, err := prepareDeploymentSignatureStruct(types.HexBytes(signature).String(), communityID, common.HexToAddress(fromAddress))
if err != nil {
return 0, err
}
data, err := deployerABI.Pack("deploy", ownerTokenConfig, masterTokenConfig, communitySignature, common.FromHex(signerPubKey))
if err != nil {
return 0, err
}
toAddr := deployerAddress
fromAddr := common.HexToAddress(fromAddress)
callMsg := ethereum.CallMsg{
From: fromAddr,
To: &toAddr,
Value: big.NewInt(0),
Data: data,
}
estimate, err := ethClient.EstimateGas(ctx, callMsg)
if err != nil {
return 0, err
}
finalEstimation := estimate + uint64(float32(estimate)*0.1)
log.Debug("Owner token deployment gas estimation: ", finalEstimation)
return finalEstimation, nil
}
func (s *Service) deployCollectiblesGasUnits(ctx context.Context, chainID uint64, fromAddress string) (uint64, error) {
ethClient, err := s.manager.rpcClient.EthClient(chainID)
if err != nil {
log.Error(err.Error())
return 0, err
}
collectiblesABI, err := abi.JSON(strings.NewReader(collectibles.CollectiblesABI))
if err != nil {
return 0, err
}
// use random parameters, they will not have impact on deployment results
data, err := collectiblesABI.Pack("" /*constructor name is empty*/, "name", "SYMBOL", big.NewInt(20), true, false, "tokenUri",
common.HexToAddress("0x77b48394c650520012795a1a25696d7eb542d110"), common.HexToAddress("0x77b48394c650520012795a1a25696d7eb542d110"))
if err != nil {
return 0, err
}
callMsg := ethereum.CallMsg{
From: common.HexToAddress(fromAddress),
To: nil,
Value: big.NewInt(0),
Data: append(common.FromHex(collectibles.CollectiblesBin), data...),
}
estimate, err := ethClient.EstimateGas(ctx, callMsg)
if err != nil {
return 0, err
}
finalEstimation := estimate + uint64(float32(estimate)*0.1)
log.Debug("Collectibles deployment gas estimation: ", finalEstimation)
return finalEstimation, nil
}
func (s *Service) deployAssetsGasUnits(ctx context.Context, chainID uint64, fromAddress string) (uint64, error) {
ethClient, err := s.manager.rpcClient.EthClient(chainID)
if err != nil {
log.Error(err.Error())
return 0, err
}
assetsABI, err := abi.JSON(strings.NewReader(assets.AssetsABI))
if err != nil {
return 0, err
}
// use random parameters, they will not have impact on deployment results
data, err := assetsABI.Pack("" /*constructor name is empty*/, "name", "SYMBOL", uint8(18), big.NewInt(20), "tokenUri",
common.HexToAddress("0x77b48394c650520012795a1a25696d7eb542d110"), common.HexToAddress("0x77b48394c650520012795a1a25696d7eb542d110"))
if err != nil {
return 0, err
}
callMsg := ethereum.CallMsg{
From: common.HexToAddress(fromAddress),
To: nil,
Value: big.NewInt(0),
Data: append(common.FromHex(assets.AssetsBin), data...),
}
estimate, err := ethClient.EstimateGas(ctx, callMsg)
if err != nil {
return 0, err
}
finalEstimation := estimate + uint64(float32(estimate)*0.1)
log.Debug("Assets deployment gas estimation: ", finalEstimation)
return finalEstimation, nil
}
// if we want to mint 2 tokens to addresses ["a", "b"] we need to mint
// twice to every address - we need to send to smart contract table ["a", "a", "b", "b"]
func multiplyWalletAddresses(amount *bigint.BigInt, contractAddresses []string) []string {
var totalAddresses []string
for i := big.NewInt(1); i.Cmp(amount.Int) <= 0; {
totalAddresses = append(totalAddresses, contractAddresses...)
i.Add(i, big.NewInt(1))
}
return totalAddresses
}
func prepareMintCollectiblesData(walletAddresses []string, amount *bigint.BigInt) []common.Address {
totalAddresses := multiplyWalletAddresses(amount, walletAddresses)
var usersAddresses = []common.Address{}
for _, k := range totalAddresses {
usersAddresses = append(usersAddresses, common.HexToAddress(k))
}
return usersAddresses
}
func prepareMintAssetsData(walletAddresses []string, amount *bigint.BigInt) ([]common.Address, []*big.Int) {
var usersAddresses = []common.Address{}
var amountsList = []*big.Int{}
for _, k := range walletAddresses {
usersAddresses = append(usersAddresses, common.HexToAddress(k))
amountsList = append(amountsList, amount.Int)
}
return usersAddresses, amountsList
}
func (s *Service) mintCollectiblesGasUnits(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (uint64, error) {
err := s.ValidateWalletsAndAmounts(walletAddresses, amount)
if err != nil {
return 0, err
}
usersAddresses := prepareMintCollectiblesData(walletAddresses, amount)
return s.estimateMethod(ctx, chainID, contractAddress, fromAddress, "mintTo", usersAddresses)
}
func (s *Service) mintAssetsGasUnits(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (uint64, error) {
err := s.ValidateWalletsAndAmounts(walletAddresses, amount)
if err != nil {
return 0, err
}
usersAddresses, amountsList := prepareMintAssetsData(walletAddresses, amount)
return s.estimateMethod(ctx, chainID, contractAddress, fromAddress, "mintTo", usersAddresses, amountsList)
}
func (s *Service) mintTokensGasUnits(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (uint64, error) {
tokenType, err := s.db.GetTokenType(chainID, contractAddress)
if err != nil {
return 0, err
}
switch tokenType {
case protobuf.CommunityTokenType_ERC721:
return s.mintCollectiblesGasUnits(ctx, chainID, contractAddress, fromAddress, walletAddresses, amount)
case protobuf.CommunityTokenType_ERC20:
return s.mintAssetsGasUnits(ctx, chainID, contractAddress, fromAddress, walletAddresses, amount)
default:
return 0, fmt.Errorf("unknown token type: %v", tokenType)
}
}
func (s *Service) prepareCommunityTokenFees(ctx context.Context, from common.Address, to *common.Address, gasUnits uint64, chainID uint64) (*CommunityTokenFees, error) {
suggestedFees, err := s.feeManager.SuggestedFeesGwei(ctx, chainID)
if err != nil {
return nil, err
}
txArgs := s.suggestedFeesToSendTxArgs(from, to, gasUnits, suggestedFees)
l1Fee, err := s.estimateL1Fee(ctx, chainID, txArgs)
if err == nil {
suggestedFees.L1GasFee = weiToGwei(big.NewInt(int64(l1Fee)))
}
return &CommunityTokenFees{
GasUnits: gasUnits,
SuggestedFees: suggestedFees,
}, nil
}
func (s *Service) suggestedFeesToSendTxArgs(from common.Address, to *common.Address, gas uint64, suggestedFees *router.SuggestedFeesGwei) transactions.SendTxArgs {
sendArgs := transactions.SendTxArgs{}
sendArgs.From = types.Address(from)
sendArgs.To = (*types.Address)(to)
sendArgs.Gas = (*hexutil.Uint64)(&gas)
if suggestedFees.EIP1559Enabled {
sendArgs.MaxPriorityFeePerGas = (*hexutil.Big)(gweiToWei(suggestedFees.MaxPriorityFeePerGas))
sendArgs.MaxFeePerGas = (*hexutil.Big)(gweiToWei(suggestedFees.MaxFeePerGasMedium))
} else {
sendArgs.GasPrice = (*hexutil.Big)(gweiToWei(suggestedFees.GasPrice))
}
return sendArgs
}
func (s *Service) estimateL1Fee(ctx context.Context, chainID uint64, sendArgs transactions.SendTxArgs) (uint64, error) {
transaction, _, err := s.transactor.ValidateAndBuildTransaction(chainID, sendArgs, -1)
if err != nil {
return 0, err
}
data, err := transaction.MarshalBinary()
if err != nil {
return 0, err
}
return s.feeManager.GetL1Fee(ctx, chainID, data)
}
func (s *Service) estimateMethodForTokenInstance(ctx context.Context, contractInstance TokenInstance, chainID uint64, contractAddress string, fromAddress string, methodName string, args ...interface{}) (uint64, error) {
ethClient, err := s.manager.rpcClient.EthClient(chainID)
if err != nil {
log.Error(err.Error())
return 0, err
}
data, err := contractInstance.PackMethod(ctx, methodName, args...)
if err != nil {
return 0, err
}
toAddr := common.HexToAddress(contractAddress)
fromAddr := common.HexToAddress(fromAddress)
callMsg := ethereum.CallMsg{
From: fromAddr,
To: &toAddr,
Value: big.NewInt(0),
Data: data,
}
estimate, err := ethClient.EstimateGas(ctx, callMsg)
if err != nil {
return 0, err
}
return estimate + uint64(float32(estimate)*0.1), nil
}
func (s *Service) estimateMethod(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, methodName string, args ...interface{}) (uint64, error) {
contractInst, err := NewTokenInstance(s, chainID, contractAddress)
if err != nil {
return 0, err
}
return s.estimateMethodForTokenInstance(ctx, contractInst, chainID, contractAddress, fromAddress, methodName, args...)
}