diff --git a/contracts/community-tokens/contracts.go b/contracts/community-tokens/contracts.go new file mode 100644 index 00000000000..2413a9a5930 --- /dev/null +++ b/contracts/community-tokens/contracts.go @@ -0,0 +1,78 @@ +package communitytokens + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "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/contracts/community-tokens/mastertoken" + "github.com/status-im/status-go/contracts/community-tokens/ownertoken" + communityownertokenregistry "github.com/status-im/status-go/contracts/community-tokens/registry" + "github.com/status-im/status-go/rpc" +) + +type CommunityTokensContractMaker struct { + RPCClient rpc.ClientInterface +} + +func NewCommunityTokensContractMakerMaker(client rpc.ClientInterface) (*CommunityTokensContractMaker, error) { + if client == nil { + return nil, errors.New("could not initialize CommunityTokensContractMaker with an rpc client") + } + return &CommunityTokensContractMaker{RPCClient: client}, nil +} + +func (c *CommunityTokensContractMaker) NewOwnerTokenInstance(chainID uint64, contractAddress common.Address, +) (*ownertoken.OwnerToken, error) { + backend, err := c.RPCClient.EthClient(chainID) + if err != nil { + return nil, err + } + return ownertoken.NewOwnerToken(contractAddress, backend) +} + +func (c *CommunityTokensContractMaker) NewMasterTokenInstance(chainID uint64, contractAddress common.Address, +) (*mastertoken.MasterToken, error) { + backend, err := c.RPCClient.EthClient(chainID) + if err != nil { + return nil, err + } + return mastertoken.NewMasterToken(contractAddress, backend) +} + +func (c *CommunityTokensContractMaker) NewCollectiblesInstance(chainID uint64, contractAddress common.Address, +) (*collectibles.Collectibles, error) { + backend, err := c.RPCClient.EthClient(chainID) + if err != nil { + return nil, err + } + return collectibles.NewCollectibles(contractAddress, backend) +} + +func (c *CommunityTokensContractMaker) NewAssetsInstance(chainID uint64, contractAddress common.Address, +) (*assets.Assets, error) { + backend, err := c.RPCClient.EthClient(chainID) + if err != nil { + return nil, err + } + return assets.NewAssets(contractAddress, backend) +} + +func (c *CommunityTokensContractMaker) NewCommunityTokenDeployerInstance(chainID uint64, contractAddress common.Address, +) (*communitytokendeployer.CommunityTokenDeployer, error) { + backend, err := c.RPCClient.EthClient(chainID) + if err != nil { + return nil, err + } + return communitytokendeployer.NewCommunityTokenDeployer(contractAddress, backend) +} + +func (c *CommunityTokensContractMaker) NewCommunityOwnerTokenRegistryInstance(chainID uint64, contractAddress common.Address) (*communityownertokenregistry.CommunityOwnerTokenRegistry, error) { + backend, err := c.RPCClient.EthClient(chainID) + if err != nil { + return nil, err + } + return communityownertokenregistry.NewCommunityOwnerTokenRegistry(contractAddress, backend) +} diff --git a/node/status_node_services.go b/node/status_node_services.go index 2d241f3d309..429c5169404 100644 --- a/node/status_node_services.go +++ b/node/status_node_services.go @@ -513,7 +513,7 @@ func (b *StatusNode) CommunityTokensService() *communitytokens.Service { func (b *StatusNode) CommunityTokensServiceV2() *communitytokensv2.Service { if b.communityTokensSrvcV2 == nil { - b.communityTokensSrvcV2 = communitytokensv2.NewService(b.rpcClient, b.gethAccountManager, b.pendingTracker, b.config, b.appDB, &b.walletFeed, b.transactor) + b.communityTokensSrvcV2 = communitytokensv2.NewService(b.rpcClient, b.gethAccountManager, b.config, b.appDB, &b.walletFeed, b.transactor) } return b.communityTokensSrvcV2 } diff --git a/services/communitytokens/api.go b/services/communitytokens/api.go index 75d22ef5f8a..58fa3218c2e 100644 --- a/services/communitytokens/api.go +++ b/services/communitytokens/api.go @@ -54,8 +54,8 @@ func (api *API) DeployCollectibles(ctx context.Context, chainID uint64, deployme address, tx, _, err := collectibles.DeployCollectibles(transactOpts, ethClient, deploymentParameters.Name, deploymentParameters.Symbol, deploymentParameters.GetSupply(), deploymentParameters.RemoteSelfDestruct, deploymentParameters.Transferable, - deploymentParameters.TokenURI, common.HexToAddress(deploymentParameters.OwnerTokenAddress), - common.HexToAddress(deploymentParameters.MasterTokenAddress)) + deploymentParameters.TokenURI, deploymentParameters.OwnerTokenAddress, + deploymentParameters.MasterTokenAddress) if err != nil { logutils.ZapLogger().Error(err.Error()) return responses.DeploymentDetails{}, err @@ -229,8 +229,8 @@ func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentPara address, tx, _, err := assets.DeployAssets(transactOpts, ethClient, deploymentParameters.Name, deploymentParameters.Symbol, decimals, deploymentParameters.GetSupply(), deploymentParameters.TokenURI, - common.HexToAddress(deploymentParameters.OwnerTokenAddress), - common.HexToAddress(deploymentParameters.MasterTokenAddress)) + deploymentParameters.OwnerTokenAddress, + deploymentParameters.MasterTokenAddress) if err != nil { logutils.ZapLogger().Error(err.Error()) return responses.DeploymentDetails{}, err diff --git a/services/communitytokens/service.go b/services/communitytokens/service.go index e7863d08c2a..6aa451ac981 100644 --- a/services/communitytokens/service.go +++ b/services/communitytokens/service.go @@ -142,7 +142,7 @@ func (s *Service) handleWalletEvent(event walletevent.Event) { errorStr = tokenErr.Error() } - signal.SendCommunityTokenTransactionStatusSignal(string(pendingTransaction.Type), p.Status == transactions.Success, pendingTransaction.Hash, + signal.SendCommunityTokenTransactionStatusSignal(0, p.Status == transactions.Success, pendingTransaction.Hash, communityToken, ownerToken, masterToken, errorStr) } } diff --git a/services/communitytokensv2/api.go b/services/communitytokensv2/api.go index d962a27b472..b9221ea7cbb 100644 --- a/services/communitytokensv2/api.go +++ b/services/communitytokensv2/api.go @@ -2,30 +2,17 @@ package communitytokensv2 import ( "context" - "fmt" "math/big" - "go.uber.org/zap" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "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/contracts/community-tokens/ownertoken" - communityownertokenregistry "github.com/status-im/status-go/contracts/community-tokens/registry" - "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" - "github.com/status-im/status-go/logutils" "github.com/status-im/status-go/protocol/communities/token" "github.com/status-im/status-go/protocol/protobuf" - "github.com/status-im/status-go/services/utils" "github.com/status-im/status-go/services/wallet/bigint" - wcommon "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/requests" - "github.com/status-im/status-go/services/wallet/wallettypes" - "github.com/status-im/status-go/transactions" + "github.com/status-im/status-go/services/wallet/responses" ) func NewAPI(s *Service) *API { @@ -38,321 +25,61 @@ type API struct { s *Service } -func (api *API) DeployCollectibles(ctx context.Context, chainID uint64, deploymentParameters requests.DeploymentParameters, txArgs wallettypes.SendTxArgs, password string) (requests.DeploymentDetails, error) { - err := deploymentParameters.Validate(false) - if err != nil { - return requests.DeploymentDetails{}, err - } - transactOpts := txArgs.ToTransactOpts(utils.VerifyPasswordAndGetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password)) - - ethClient, err := api.s.manager.rpcClient.EthClient(chainID) - if err != nil { - logutils.ZapLogger().Error(err.Error()) - return requests.DeploymentDetails{}, err - } - address, tx, _, err := collectibles.DeployCollectibles(transactOpts, ethClient, deploymentParameters.Name, - deploymentParameters.Symbol, deploymentParameters.GetSupply(), - deploymentParameters.RemoteSelfDestruct, deploymentParameters.Transferable, - deploymentParameters.TokenURI, common.HexToAddress(deploymentParameters.OwnerTokenAddress), - common.HexToAddress(deploymentParameters.MasterTokenAddress)) - if err != nil { - logutils.ZapLogger().Error(err.Error()) - return requests.DeploymentDetails{}, err - } - - err = api.s.pendingTracker.TrackPendingTransaction( - wcommon.ChainID(chainID), - tx.Hash(), - common.Address(txArgs.From), - address, - transactions.DeployCommunityToken, - transactions.Keep, - "", - ) - if err != nil { - logutils.ZapLogger().Error("TrackPendingTransaction error", zap.Error(err)) - return requests.DeploymentDetails{}, err - } +func (api *API) StoreDeployedCollectibles(ctx context.Context, addressFrom types.Address, addressTo types.Address, chainID uint64, + txHash common.Hash, deploymentParameters requests.DeploymentParameters) (responses.DeploymentDetails, error) { - savedCommunityToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), deploymentParameters, txArgs.From.Hex(), address.Hex(), - protobuf.CommunityTokenType_ERC721, token.CommunityLevel, tx.Hash().Hex()) + savedCommunityToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), deploymentParameters, addressFrom.Hex(), addressTo.Hex(), + protobuf.CommunityTokenType_ERC721, token.CommunityLevel, txHash.Hex()) if err != nil { - return requests.DeploymentDetails{}, err + return responses.DeploymentDetails{}, err } - return requests.DeploymentDetails{ - ContractAddress: address.Hex(), - TransactionHash: tx.Hash().Hex(), + return responses.DeploymentDetails{ + ContractAddress: addressTo.Hex(), + TransactionHash: txHash.Hex(), CommunityToken: savedCommunityToken}, nil } -func decodeSignature(sig []byte) (r [32]byte, s [32]byte, v uint8, err error) { - if len(sig) != crypto.SignatureLength { - return [32]byte{}, [32]byte{}, 0, fmt.Errorf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength) - } - copy(r[:], sig[:32]) - copy(s[:], sig[32:64]) - v = sig[64] + 27 - return r, s, v, nil -} +func (api *API) StoreDeployedOwnerToken(ctx context.Context, addressFrom types.Address, chainID uint64, txHash common.Hash, + ownerTokenParameters requests.DeploymentParameters, masterTokenParameters requests.DeploymentParameters) (responses.DeploymentDetails, error) { -func prepareDeploymentSignatureStruct(signature string, communityID string, addressFrom common.Address) (communitytokendeployer.CommunityTokenDeployerDeploymentSignature, error) { - r, s, v, err := decodeSignature(common.FromHex(signature)) + savedOwnerToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), ownerTokenParameters, addressFrom.Hex(), + api.s.TemporaryOwnerContractAddress(txHash.Hex()), protobuf.CommunityTokenType_ERC721, token.OwnerLevel, txHash.Hex()) if err != nil { - return communitytokendeployer.CommunityTokenDeployerDeploymentSignature{}, err - } - communityEthAddress, err := convert33BytesPubKeyToEthAddress(communityID) - if err != nil { - return communitytokendeployer.CommunityTokenDeployerDeploymentSignature{}, err - } - communitySignature := communitytokendeployer.CommunityTokenDeployerDeploymentSignature{ - V: v, - R: r, - S: s, - Deployer: addressFrom, - Signer: communityEthAddress, + return responses.DeploymentDetails{}, err } - return communitySignature, nil -} - -func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64, - ownerTokenParameters requests.DeploymentParameters, masterTokenParameters requests.DeploymentParameters, - signerPubKey string, txArgs wallettypes.SendTxArgs, password string) (requests.DeploymentDetails, error) { - err := ownerTokenParameters.Validate(false) + savedMasterToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), masterTokenParameters, addressFrom.Hex(), + api.s.TemporaryMasterContractAddress(txHash.Hex()), protobuf.CommunityTokenType_ERC721, token.MasterLevel, txHash.Hex()) if err != nil { - return requests.DeploymentDetails{}, err + return responses.DeploymentDetails{}, err } - if len(signerPubKey) <= 0 { - return requests.DeploymentDetails{}, fmt.Errorf("signerPubKey is empty") - } - - err = masterTokenParameters.Validate(false) - if err != nil { - return requests.DeploymentDetails{}, err - } - - transactOpts := txArgs.ToTransactOpts(utils.VerifyPasswordAndGetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password)) - - deployerContractInst, err := api.NewCommunityTokenDeployerInstance(chainID) - if err != nil { - return requests.DeploymentDetails{}, 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 := api.s.Messenger.CreateCommunityTokenDeploymentSignature(context.Background(), chainID, txArgs.From.Hex(), ownerTokenParameters.CommunityID) - if err != nil { - return requests.DeploymentDetails{}, err - } - - communitySignature, err := prepareDeploymentSignatureStruct(types.HexBytes(signature).String(), ownerTokenParameters.CommunityID, common.Address(txArgs.From)) - if err != nil { - return requests.DeploymentDetails{}, err - } - - logutils.ZapLogger().Debug("Prepare deployment", zap.Any("signature", communitySignature)) - - tx, err := deployerContractInst.Deploy(transactOpts, ownerTokenConfig, masterTokenConfig, communitySignature, common.FromHex(signerPubKey)) - - if err != nil { - logutils.ZapLogger().Error(err.Error()) - return requests.DeploymentDetails{}, err - } - - logutils.ZapLogger().Debug("Contract deployed", zap.Stringer("hash", tx.Hash())) - - err = api.s.pendingTracker.TrackPendingTransaction( - wcommon.ChainID(chainID), - tx.Hash(), - common.Address(txArgs.From), - common.Address{}, - transactions.DeployOwnerToken, - transactions.Keep, - "", - ) - if err != nil { - logutils.ZapLogger().Error("TrackPendingTransaction error", zap.Error(err)) - return requests.DeploymentDetails{}, err - } - - savedOwnerToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), ownerTokenParameters, txArgs.From.Hex(), - api.s.TemporaryOwnerContractAddress(tx.Hash().Hex()), protobuf.CommunityTokenType_ERC721, token.OwnerLevel, tx.Hash().Hex()) - if err != nil { - return requests.DeploymentDetails{}, err - } - savedMasterToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), masterTokenParameters, txArgs.From.Hex(), - api.s.TemporaryMasterContractAddress(tx.Hash().Hex()), protobuf.CommunityTokenType_ERC721, token.MasterLevel, tx.Hash().Hex()) - if err != nil { - return requests.DeploymentDetails{}, err - } - - return requests.DeploymentDetails{ + return responses.DeploymentDetails{ ContractAddress: "", - TransactionHash: tx.Hash().Hex(), + TransactionHash: txHash.Hex(), OwnerToken: savedOwnerToken, MasterToken: savedMasterToken}, nil } -// recovery function which starts transaction tracking again -func (api *API) ReTrackOwnerTokenDeploymentTransaction(ctx context.Context, chainID uint64, contractAddress string) error { - return api.s.ReTrackOwnerTokenDeploymentTransaction(ctx, chainID, contractAddress) -} - -func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentParameters requests.DeploymentParameters, txArgs wallettypes.SendTxArgs, password string) (requests.DeploymentDetails, error) { - - err := deploymentParameters.Validate(true) - if err != nil { - return requests.DeploymentDetails{}, err - } - - transactOpts := txArgs.ToTransactOpts(utils.VerifyPasswordAndGetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password)) - - ethClient, err := api.s.manager.rpcClient.EthClient(chainID) - if err != nil { - logutils.ZapLogger().Error(err.Error()) - return requests.DeploymentDetails{}, err - } - - const decimals = 18 - address, tx, _, err := assets.DeployAssets(transactOpts, ethClient, deploymentParameters.Name, - deploymentParameters.Symbol, decimals, deploymentParameters.GetSupply(), - deploymentParameters.TokenURI, - common.HexToAddress(deploymentParameters.OwnerTokenAddress), - common.HexToAddress(deploymentParameters.MasterTokenAddress)) - if err != nil { - logutils.ZapLogger().Error(err.Error()) - return requests.DeploymentDetails{}, err - } - - err = api.s.pendingTracker.TrackPendingTransaction( - wcommon.ChainID(chainID), - tx.Hash(), - common.Address(txArgs.From), - address, - transactions.DeployCommunityToken, - transactions.Keep, - "", - ) - if err != nil { - logutils.ZapLogger().Error("TrackPendingTransaction error", zap.Error(err)) - return requests.DeploymentDetails{}, err - } +func (api *API) StoreDeployedAssets(ctx context.Context, addressFrom types.Address, addressTo types.Address, chainID uint64, + txHash common.Hash, deploymentParameters requests.DeploymentParameters) (responses.DeploymentDetails, error) { - savedCommunityToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), deploymentParameters, txArgs.From.Hex(), address.Hex(), - protobuf.CommunityTokenType_ERC20, token.CommunityLevel, tx.Hash().Hex()) + savedCommunityToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), deploymentParameters, addressFrom.Hex(), addressTo.Hex(), + protobuf.CommunityTokenType_ERC20, token.CommunityLevel, txHash.Hex()) if err != nil { - return requests.DeploymentDetails{}, err + return responses.DeploymentDetails{}, err } - return requests.DeploymentDetails{ - ContractAddress: address.Hex(), - TransactionHash: tx.Hash().Hex(), + return responses.DeploymentDetails{ + ContractAddress: addressTo.Hex(), + TransactionHash: txHash.Hex(), CommunityToken: savedCommunityToken}, nil } -func (api *API) DeployCollectiblesEstimate(ctx context.Context, chainID uint64, fromAddress string) (*CommunityTokenFees, error) { - return api.s.deployCollectiblesEstimate(ctx, chainID, fromAddress) -} - -func (api *API) DeployAssetsEstimate(ctx context.Context, chainID uint64, fromAddress string) (*CommunityTokenFees, error) { - return api.s.deployAssetsEstimate(ctx, chainID, fromAddress) -} - -func (api *API) DeployOwnerTokenEstimate(ctx context.Context, chainID uint64, fromAddress string, - ownerTokenParameters requests.DeploymentParameters, masterTokenParameters requests.DeploymentParameters, - communityID string, signerPubKey string) (*CommunityTokenFees, error) { - return api.s.deployOwnerTokenEstimate(ctx, chainID, fromAddress, ownerTokenParameters, masterTokenParameters, communityID, signerPubKey) -} - -func (api *API) EstimateMintTokens(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (*CommunityTokenFees, error) { - return api.s.mintTokensEstimate(ctx, chainID, contractAddress, fromAddress, walletAddresses, amount) -} - -// This is only ERC721 function -func (api *API) EstimateRemoteBurn(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, tokenIds []*bigint.BigInt) (*CommunityTokenFees, error) { - return api.s.remoteBurnEstimate(ctx, chainID, contractAddress, fromAddress, tokenIds) -} - -func (api *API) EstimateBurn(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, burnAmount *bigint.BigInt) (*CommunityTokenFees, error) { - return api.s.burnEstimate(ctx, chainID, contractAddress, fromAddress, burnAmount) -} - -func (api *API) EstimateSetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, newSignerPubKey string) (*CommunityTokenFees, error) { - return api.s.setSignerPubKeyEstimate(ctx, chainID, contractAddress, fromAddress, newSignerPubKey) -} - -func (api *API) NewOwnerTokenInstance(chainID uint64, contractAddress string) (*ownertoken.OwnerToken, error) { - return api.s.NewOwnerTokenInstance(chainID, contractAddress) -} - -func (api *API) NewCommunityTokenDeployerInstance(chainID uint64) (*communitytokendeployer.CommunityTokenDeployer, error) { - return api.s.manager.NewCommunityTokenDeployerInstance(chainID) -} - -func (api *API) NewCommunityOwnerTokenRegistryInstance(chainID uint64, contractAddress string) (*communityownertokenregistry.CommunityOwnerTokenRegistry, error) { - return api.s.NewCommunityOwnerTokenRegistryInstance(chainID, contractAddress) -} - -func (api *API) NewCollectiblesInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) { - return api.s.manager.NewCollectiblesInstance(chainID, contractAddress) -} - -func (api *API) NewAssetsInstance(chainID uint64, contractAddress string) (*assets.Assets, error) { - return api.s.manager.NewAssetsInstance(chainID, contractAddress) -} - -// Universal minting function for every type of token. -func (api *API) MintTokens(ctx context.Context, chainID uint64, contractAddress string, txArgs wallettypes.SendTxArgs, password string, walletAddresses []string, amount *bigint.BigInt) (string, error) { - - err := api.s.ValidateWalletsAndAmounts(walletAddresses, amount) - if err != nil { - return "", err - } - - transactOpts := txArgs.ToTransactOpts(utils.VerifyPasswordAndGetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password)) - - contractInst, err := NewTokenInstance(api.s, chainID, contractAddress) - if err != nil { - return "", err - } - - tx, err := contractInst.Mint(transactOpts, walletAddresses, amount) - if err != nil { - return "", err - } - - err = api.s.pendingTracker.TrackPendingTransaction( - wcommon.ChainID(chainID), - tx.Hash(), - common.Address(txArgs.From), - common.HexToAddress(contractAddress), - transactions.AirdropCommunityToken, - transactions.Keep, - "", - ) - if err != nil { - logutils.ZapLogger().Error("TrackPendingTransaction error", zap.Error(err)) - return "", err - } - - return tx.Hash().Hex(), nil -} - // This is only ERC721 function func (api *API) RemoteDestructedAmount(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) { callOpts := &bind.CallOpts{Context: ctx, Pending: false} - contractInst, err := api.NewCollectiblesInstance(chainID, contractAddress) + contractInst, err := api.s.manager.NewCollectiblesInstance(chainID, common.HexToAddress(contractAddress)) if err != nil { return nil, err } @@ -375,99 +102,10 @@ func (api *API) RemoteDestructedAmount(ctx context.Context, chainID uint64, cont return &bigint.BigInt{Int: res}, nil } -// This is only ERC721 function -func (api *API) RemoteBurn(ctx context.Context, chainID uint64, contractAddress string, txArgs wallettypes.SendTxArgs, password string, tokenIds []*bigint.BigInt, additionalData string) (string, error) { - err := api.s.validateTokens(tokenIds) - if err != nil { - return "", err - } - - transactOpts := txArgs.ToTransactOpts(utils.VerifyPasswordAndGetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password)) - - var tempTokenIds []*big.Int - for _, v := range tokenIds { - tempTokenIds = append(tempTokenIds, v.Int) - } - - contractInst, err := NewTokenInstance(api.s, chainID, contractAddress) - if err != nil { - return "", err - } - - tx, err := contractInst.RemoteBurn(transactOpts, tempTokenIds) - if err != nil { - return "", err - } - - err = api.s.pendingTracker.TrackPendingTransaction( - wcommon.ChainID(chainID), - tx.Hash(), - common.Address(txArgs.From), - common.HexToAddress(contractAddress), - transactions.RemoteDestructCollectible, - transactions.Keep, - additionalData, - ) - if err != nil { - logutils.ZapLogger().Error("TrackPendingTransaction error", zap.Error(err)) - return "", err - } - - return tx.Hash().Hex(), nil -} - -func (api *API) GetCollectiblesContractInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) { - return api.s.manager.GetCollectiblesContractInstance(chainID, contractAddress) -} - -func (api *API) GetAssetContractInstance(chainID uint64, contractAddress string) (*assets.Assets, error) { - return api.s.manager.GetAssetContractInstance(chainID, contractAddress) -} - func (api *API) RemainingSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) { return api.s.remainingSupply(ctx, chainID, contractAddress) } -func (api *API) Burn(ctx context.Context, chainID uint64, contractAddress string, txArgs wallettypes.SendTxArgs, password string, burnAmount *bigint.BigInt) (string, error) { - err := api.s.validateBurnAmount(ctx, burnAmount, chainID, contractAddress) - if err != nil { - return "", err - } - - transactOpts := txArgs.ToTransactOpts(utils.VerifyPasswordAndGetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password)) - - newMaxSupply, err := api.s.prepareNewMaxSupply(ctx, chainID, contractAddress, burnAmount) - if err != nil { - return "", err - } - - contractInst, err := NewTokenInstance(api.s, chainID, contractAddress) - if err != nil { - return "", err - } - - tx, err := contractInst.SetMaxSupply(transactOpts, newMaxSupply) - if err != nil { - return "", err - } - - err = api.s.pendingTracker.TrackPendingTransaction( - wcommon.ChainID(chainID), - tx.Hash(), - common.Address(txArgs.From), - common.HexToAddress(contractAddress), - transactions.BurnCommunityToken, - transactions.Keep, - "", - ) - if err != nil { - logutils.ZapLogger().Error("TrackPendingTransaction error", zap.Error(err)) - return "", err - } - - return tx.Hash().Hex(), nil -} - // Gets signer public key from smart contract with a given chainId and address func (api *API) GetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string) (string, error) { return api.s.GetSignerPubKey(ctx, chainID, contractAddress) @@ -483,13 +121,9 @@ func (api *API) SafeGetOwnerTokenAddress(ctx context.Context, chainID uint64, co return api.s.SafeGetOwnerTokenAddress(ctx, chainID, communityID) } -func (api *API) SetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, txArgs wallettypes.SendTxArgs, password string, newSignerPubKey string) (string, error) { - return api.s.SetSignerPubKey(ctx, chainID, contractAddress, txArgs, password, newSignerPubKey) -} - func (api *API) OwnerTokenOwnerAddress(ctx context.Context, chainID uint64, contractAddress string) (string, error) { callOpts := &bind.CallOpts{Context: ctx, Pending: false} - contractInst, err := api.NewOwnerTokenInstance(chainID, contractAddress) + contractInst, err := api.s.manager.NewOwnerTokenInstance(chainID, common.HexToAddress(contractAddress)) if err != nil { return "", err } diff --git a/services/communitytokensv2/estimations.go b/services/communitytokensv2/estimations.go deleted file mode 100644 index 8e257f6416a..00000000000 --- a/services/communitytokensv2/estimations.go +++ /dev/null @@ -1,443 +0,0 @@ -package communitytokens - -import ( - "context" - "fmt" - "math/big" - "strings" - - "go.uber.org/zap" - - "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/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/logutils" - "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/requests" - "github.com/status-im/status-go/services/wallet/router/fees" - "github.com/status-im/status-go/services/wallet/wallettypes" -) - -type CommunityTokenFees struct { - GasUnits uint64 `json:"gasUnits"` - SuggestedFees *fees.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 requests.DeploymentParameters, masterTokenParameters requests.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 requests.DeploymentParameters, masterTokenParameters requests.DeploymentParameters, - communityID string, signerPubKey string) (uint64, error) { - ethClient, err := s.manager.rpcClient.EthClient(chainID) - if err != nil { - logutils.ZapLogger().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) - logutils.ZapLogger().Debug("Owner token deployment estimation", zap.Uint64("gas", 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 { - logutils.ZapLogger().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) - logutils.ZapLogger().Debug("Collectibles deployment estimation", zap.Uint64("gas", 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 { - logutils.ZapLogger().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) - logutils.ZapLogger().Debug("Assets deployment estimation: ", zap.Uint64("gas", 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 *fees.SuggestedFeesGwei) wallettypes.SendTxArgs { - sendArgs := wallettypes.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 wallettypes.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 { - logutils.ZapLogger().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...) -} diff --git a/services/communitytokensv2/manager.go b/services/communitytokensv2/manager.go index b2dd7d5d4fa..284529e88d7 100644 --- a/services/communitytokensv2/manager.go +++ b/services/communitytokensv2/manager.go @@ -8,9 +8,12 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/signer/core/apitypes" + communitytokens "github.com/status-im/status-go/contracts/community-tokens" "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/contracts/community-tokens/ownertoken" + communityownertokenregistry "github.com/status-im/status-go/contracts/community-tokens/registry" "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/protocol/communities" @@ -20,63 +23,45 @@ import ( ) type Manager struct { - rpcClient *rpc.Client + contractMaker *communitytokens.CommunityTokensContractMaker } func NewManager(rpcClient *rpc.Client) *Manager { return &Manager{ - rpcClient: rpcClient, + contractMaker: &communitytokens.CommunityTokensContractMaker{ + RPCClient: rpcClient, + }, } } -func (m *Manager) NewCollectiblesInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) { - backend, err := m.rpcClient.EthClient(chainID) - if err != nil { - return nil, err - } - return collectibles.NewCollectibles(common.HexToAddress(contractAddress), backend) +func (m *Manager) NewCollectiblesInstance(chainID uint64, contractAddress common.Address) (*collectibles.Collectibles, error) { + return m.contractMaker.NewCollectiblesInstance(chainID, contractAddress) } func (m *Manager) NewCommunityTokenDeployerInstance(chainID uint64) (*communitytokendeployer.CommunityTokenDeployer, error) { - backend, err := m.rpcClient.EthClient(chainID) - if err != nil { - return nil, err - } deployerAddr, err := communitytokendeployer.ContractAddress(chainID) if err != nil { return nil, err } - return communitytokendeployer.NewCommunityTokenDeployer(deployerAddr, backend) + return m.contractMaker.NewCommunityTokenDeployerInstance(chainID, deployerAddr) } -func (m *Manager) GetCollectiblesContractInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) { - contractInst, err := m.NewCollectiblesInstance(chainID, contractAddress) - if err != nil { - return nil, err - } - return contractInst, nil +func (m *Manager) NewAssetsInstance(chainID uint64, contractAddress common.Address) (*assets.Assets, error) { + return m.contractMaker.NewAssetsInstance(chainID, contractAddress) } -func (m *Manager) NewAssetsInstance(chainID uint64, contractAddress string) (*assets.Assets, error) { - backend, err := m.rpcClient.EthClient(chainID) - if err != nil { - return nil, err - } - return assets.NewAssets(common.HexToAddress(contractAddress), backend) +func (m *Manager) NewCommunityOwnerTokenRegistryInstance(chainID uint64, contractAddress common.Address) (*communityownertokenregistry.CommunityOwnerTokenRegistry, error) { + return m.contractMaker.NewCommunityOwnerTokenRegistryInstance(chainID, contractAddress) } -func (m *Manager) GetAssetContractInstance(chainID uint64, contractAddress string) (*assets.Assets, error) { - contractInst, err := m.NewAssetsInstance(chainID, contractAddress) - if err != nil { - return nil, err - } - return contractInst, nil +func (m *Manager) NewOwnerTokenInstance(chainID uint64, contractAddress common.Address) (*ownertoken.OwnerToken, error) { + return m.contractMaker.NewOwnerTokenInstance(chainID, contractAddress) } func (m *Manager) GetCollectibleContractData(chainID uint64, contractAddress string) (*communities.CollectibleContractData, error) { callOpts := &bind.CallOpts{Context: context.Background(), Pending: false} - contract, err := m.GetCollectiblesContractInstance(chainID, contractAddress) + contract, err := m.NewCollectiblesInstance(chainID, common.HexToAddress(contractAddress)) if err != nil { return nil, err } @@ -103,7 +88,7 @@ func (m *Manager) GetCollectibleContractData(chainID uint64, contractAddress str func (m *Manager) GetAssetContractData(chainID uint64, contractAddress string) (*communities.AssetContractData, error) { callOpts := &bind.CallOpts{Context: context.Background(), Pending: false} - contract, err := m.GetAssetContractInstance(chainID, contractAddress) + contract, err := m.NewAssetsInstance(chainID, common.HexToAddress(contractAddress)) if err != nil { return nil, err } diff --git a/services/communitytokensv2/service.go b/services/communitytokensv2/service.go index ed8c02c8b84..1ede591bdb6 100644 --- a/services/communitytokensv2/service.go +++ b/services/communitytokensv2/service.go @@ -6,10 +6,8 @@ import ( "encoding/json" "fmt" "math/big" - "strings" "github.com/pkg/errors" - "go.uber.org/zap" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -17,9 +15,6 @@ import ( "github.com/ethereum/go-ethereum/p2p" ethRpc "github.com/ethereum/go-ethereum/rpc" "github.com/status-im/status-go/account" - "github.com/status-im/status-go/contracts/community-tokens/mastertoken" - "github.com/status-im/status-go/contracts/community-tokens/ownertoken" - communityownertokenregistry "github.com/status-im/status-go/contracts/community-tokens/registry" "github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/logutils" @@ -30,13 +25,12 @@ import ( "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/rpc" "github.com/status-im/status-go/services/communitytokens/communitytokensdatabase" - "github.com/status-im/status-go/services/utils" "github.com/status-im/status-go/services/wallet/bigint" - wcommon "github.com/status-im/status-go/services/wallet/common" + walletCommon "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/requests" "github.com/status-im/status-go/services/wallet/router/fees" + "github.com/status-im/status-go/services/wallet/router/sendtype" "github.com/status-im/status-go/services/wallet/walletevent" - "github.com/status-im/status-go/services/wallet/wallettypes" "github.com/status-im/status-go/signal" "github.com/status-im/status-go/transactions" ) @@ -45,7 +39,6 @@ import ( type Service struct { manager *Manager accountsManager *account.GethManager - pendingTracker *transactions.PendingTxTracker config *params.NodeConfig db *communitytokensdatabase.Database Messenger *protocol.Messenger @@ -56,12 +49,11 @@ type Service struct { } // Returns a new Collectibles Service. -func NewService(rpcClient *rpc.Client, accountsManager *account.GethManager, pendingTracker *transactions.PendingTxTracker, - config *params.NodeConfig, appDb *sql.DB, walletFeed *event.Feed, transactor *transactions.Transactor) *Service { +func NewService(rpcClient *rpc.Client, accountsManager *account.GethManager, config *params.NodeConfig, appDb *sql.DB, + walletFeed *event.Feed, transactor *transactions.Transactor) *Service { return &Service{ - manager: &Manager{rpcClient: rpcClient}, + manager: NewManager(rpcClient), accountsManager: accountsManager, - pendingTracker: pendingTracker, config: config, db: communitytokensdatabase.NewCommunityTokensDatabase(appDb), walletFeed: walletFeed, @@ -107,50 +99,55 @@ func (s *Service) handleWalletEvent(event walletevent.Event) { if p.Status == transactions.Pending { return } - pendingTransaction, err := s.pendingTracker.GetPendingEntry(p.ChainID, p.Hash) - if err != nil { - logutils.ZapLogger().Error(errors.Wrap(err, fmt.Sprintf("no pending transaction with hash %v on chain %v\n", p.Hash, p.ChainID)).Error()) + if p.SendDetails == nil { + logutils.ZapLogger().Error(errors.Wrap(err, fmt.Sprintf("cannot resolve details for tx hash %v on chain %v\n", p.Hash, p.ChainID)).Error()) return } + addressTo := common.Address(p.SendDetails.ToAddress) + fromChainID := walletCommon.ChainID(p.SendDetails.FromChain) + txHash := p.Hash + for _, tx := range p.SentTransactions { + if common.Hash(tx.Hash) == p.Hash { + addressTo = common.Address(tx.ToAddress) + fromChainID = walletCommon.ChainID(tx.FromChain) + } + } + var communityToken, ownerToken, masterToken *token.CommunityToken = &token.CommunityToken{}, &token.CommunityToken{}, &token.CommunityToken{} var tokenErr error - switch pendingTransaction.Type { - case transactions.DeployCommunityToken: - communityToken, tokenErr = s.handleDeployCommunityToken(p.Status, pendingTransaction) - case transactions.AirdropCommunityToken: - communityToken, tokenErr = s.handleAirdropCommunityToken(p.Status, pendingTransaction) - case transactions.RemoteDestructCollectible: - communityToken, tokenErr = s.handleRemoteDestructCollectible(p.Status, pendingTransaction) - case transactions.BurnCommunityToken: - communityToken, tokenErr = s.handleBurnCommunityToken(p.Status, pendingTransaction) - case transactions.DeployOwnerToken: - ownerToken, masterToken, tokenErr = s.handleDeployOwnerToken(p.Status, pendingTransaction) - case transactions.SetSignerPublicKey: - communityToken, tokenErr = s.handleSetSignerPubKey(p.Status, pendingTransaction) + switch p.SendDetails.SendType { + case int(sendtype.CommunityDeployAssets), + int(sendtype.CommunityDeployCollectibles): + communityToken, tokenErr = s.handleDeployCommunityToken(p.Status, addressTo, fromChainID) + case int(sendtype.CommunityMintTokens): + communityToken, tokenErr = s.handleAirdropCommunityToken(p.Status, addressTo, fromChainID) + case int(sendtype.CommunityRemoteBurn): + communityToken, tokenErr = s.handleRemoteDestructCollectible(p.Status, addressTo, fromChainID) + case int(sendtype.CommunityBurn): + communityToken, tokenErr = s.handleBurnCommunityToken(p.Status, addressTo, fromChainID) + case int(sendtype.CommunityDeployOwnerToken): + ownerToken, masterToken, tokenErr = s.handleDeployOwnerToken(p.Status, fromChainID, txHash) + case int(sendtype.CommunitySetSignerPubKey): + communityToken, tokenErr = s.handleSetSignerPubKey(p.Status, addressTo, fromChainID) default: return } - err = s.pendingTracker.Delete(context.Background(), p.ChainID, p.Hash) - if err != nil { - logutils.ZapLogger().Error(errors.Wrap(err, fmt.Sprintf("can't delete pending transaction with hash %v on chain %v\n", p.Hash, p.ChainID)).Error()) - } - errorStr := "" if tokenErr != nil { errorStr = tokenErr.Error() } - signal.SendCommunityTokenTransactionStatusSignal(string(pendingTransaction.Type), p.Status == transactions.Success, pendingTransaction.Hash, + signal.SendCommunityTokenTransactionStatusSignal(p.SendDetails.SendType, p.Status == transactions.Success, txHash, communityToken, ownerToken, masterToken, errorStr) } } -func (s *Service) handleAirdropCommunityToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { - communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) +func (s *Service) handleAirdropCommunityToken(status string, toAddress common.Address, chainID walletCommon.ChainID) (*token.CommunityToken, error) { + communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(chainID), toAddress.String()) if communityToken == nil { - return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String()) + return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", chainID, toAddress.String()) } else { publishErr := s.publishTokenActionToPrivilegedMembers(communityToken.CommunityID, uint64(communityToken.ChainID), communityToken.Address, protobuf.CommunityTokenAction_AIRDROP) @@ -161,10 +158,10 @@ func (s *Service) handleAirdropCommunityToken(status string, pendingTransaction return communityToken, err } -func (s *Service) handleRemoteDestructCollectible(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { - communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) +func (s *Service) handleRemoteDestructCollectible(status string, toAddress common.Address, chainID walletCommon.ChainID) (*token.CommunityToken, error) { + communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(chainID), toAddress.String()) if communityToken == nil { - return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String()) + return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", chainID, toAddress.String()) } else { publishErr := s.publishTokenActionToPrivilegedMembers(communityToken.CommunityID, uint64(communityToken.ChainID), communityToken.Address, protobuf.CommunityTokenAction_REMOTE_DESTRUCT) @@ -175,23 +172,23 @@ func (s *Service) handleRemoteDestructCollectible(status string, pendingTransact return communityToken, err } -func (s *Service) handleBurnCommunityToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { +func (s *Service) handleBurnCommunityToken(status string, toAddress common.Address, chainID walletCommon.ChainID) (*token.CommunityToken, error) { if status == transactions.Success { // get new max supply and update database - newMaxSupply, err := s.maxSupply(context.Background(), uint64(pendingTransaction.ChainID), pendingTransaction.To.String()) + newMaxSupply, err := s.maxSupply(context.Background(), uint64(chainID), toAddress.String()) if err != nil { return nil, err } - err = s.Messenger.UpdateCommunityTokenSupply(int(pendingTransaction.ChainID), pendingTransaction.To.String(), &bigint.BigInt{Int: newMaxSupply}) + err = s.Messenger.UpdateCommunityTokenSupply(int(chainID), toAddress.String(), &bigint.BigInt{Int: newMaxSupply}) if err != nil { return nil, err } } - communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) + communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(chainID), toAddress.String()) if communityToken == nil { - return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String()) + return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", chainID, toAddress.String()) } else { publishErr := s.publishTokenActionToPrivilegedMembers(communityToken.CommunityID, uint64(communityToken.ChainID), communityToken.Address, protobuf.CommunityTokenAction_BURN) @@ -202,31 +199,31 @@ func (s *Service) handleBurnCommunityToken(status string, pendingTransaction *tr return communityToken, err } -func (s *Service) handleDeployOwnerToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, *token.CommunityToken, error) { - newMasterAddress, err := s.GetMasterTokenContractAddressFromHash(context.Background(), uint64(pendingTransaction.ChainID), pendingTransaction.Hash.Hex()) +func (s *Service) handleDeployOwnerToken(status string, chainID walletCommon.ChainID, txHash common.Hash) (*token.CommunityToken, *token.CommunityToken, error) { + newMasterAddress, err := s.GetMasterTokenContractAddressFromHash(context.Background(), uint64(chainID), txHash.Hex()) if err != nil { return nil, nil, err } - newOwnerAddress, err := s.GetOwnerTokenContractAddressFromHash(context.Background(), uint64(pendingTransaction.ChainID), pendingTransaction.Hash.Hex()) + newOwnerAddress, err := s.GetOwnerTokenContractAddressFromHash(context.Background(), uint64(chainID), txHash.Hex()) if err != nil { return nil, nil, err } - err = s.Messenger.UpdateCommunityTokenAddress(int(pendingTransaction.ChainID), s.TemporaryOwnerContractAddress(pendingTransaction.Hash.Hex()), newOwnerAddress) + err = s.Messenger.UpdateCommunityTokenAddress(int(chainID), s.TemporaryOwnerContractAddress(txHash.Hex()), newOwnerAddress) if err != nil { return nil, nil, err } - err = s.Messenger.UpdateCommunityTokenAddress(int(pendingTransaction.ChainID), s.TemporaryMasterContractAddress(pendingTransaction.Hash.Hex()), newMasterAddress) + err = s.Messenger.UpdateCommunityTokenAddress(int(chainID), s.TemporaryMasterContractAddress(txHash.Hex()), newMasterAddress) if err != nil { return nil, nil, err } - ownerToken, err := s.updateStateAndAddTokenToCommunityDescription(status, int(pendingTransaction.ChainID), newOwnerAddress) + ownerToken, err := s.updateStateAndAddTokenToCommunityDescription(status, int(chainID), newOwnerAddress) if err != nil { return nil, nil, err } - masterToken, err := s.updateStateAndAddTokenToCommunityDescription(status, int(pendingTransaction.ChainID), newMasterAddress) + masterToken, err := s.updateStateAndAddTokenToCommunityDescription(status, int(chainID), newMasterAddress) if err != nil { return nil, nil, err } @@ -261,18 +258,18 @@ func (s *Service) updateStateAndAddTokenToCommunityDescription(status string, ch return s.Messenger.GetCommunityTokenByChainAndAddress(chainID, address) } -func (s *Service) handleDeployCommunityToken(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { - return s.updateStateAndAddTokenToCommunityDescription(status, int(pendingTransaction.ChainID), pendingTransaction.To.String()) +func (s *Service) handleDeployCommunityToken(status string, toAddress common.Address, chainID walletCommon.ChainID) (*token.CommunityToken, error) { + return s.updateStateAndAddTokenToCommunityDescription(status, int(chainID), toAddress.String()) } -func (s *Service) handleSetSignerPubKey(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { +func (s *Service) handleSetSignerPubKey(status string, toAddress common.Address, chainID walletCommon.ChainID) (*token.CommunityToken, error) { - communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) + communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(chainID), toAddress.String()) if err != nil { return nil, err } if communityToken == nil { - return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String()) + return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", chainID, toAddress.String()) } if status == transactions.Success { @@ -294,53 +291,6 @@ func (s *Service) Init(messenger *protocol.Messenger) { s.Messenger = messenger } -func (s *Service) NewCommunityOwnerTokenRegistryInstance(chainID uint64, contractAddress string) (*communityownertokenregistry.CommunityOwnerTokenRegistry, error) { - backend, err := s.manager.rpcClient.EthClient(chainID) - if err != nil { - return nil, err - } - return communityownertokenregistry.NewCommunityOwnerTokenRegistry(common.HexToAddress(contractAddress), backend) -} - -func (s *Service) NewOwnerTokenInstance(chainID uint64, contractAddress string) (*ownertoken.OwnerToken, error) { - - backend, err := s.manager.rpcClient.EthClient(chainID) - if err != nil { - return nil, err - } - return ownertoken.NewOwnerToken(common.HexToAddress(contractAddress), backend) - -} - -func (s *Service) NewMasterTokenInstance(chainID uint64, contractAddress string) (*mastertoken.MasterToken, error) { - backend, err := s.manager.rpcClient.EthClient(chainID) - if err != nil { - return nil, err - } - return mastertoken.NewMasterToken(common.HexToAddress(contractAddress), backend) -} - -func (s *Service) validateTokens(tokenIds []*bigint.BigInt) error { - if len(tokenIds) == 0 { - return errors.New("token list is empty") - } - return nil -} - -func (s *Service) validateBurnAmount(ctx context.Context, burnAmount *bigint.BigInt, chainID uint64, contractAddress string) error { - if burnAmount.Cmp(big.NewInt(0)) <= 0 { - return errors.New("burnAmount is less than 0") - } - remainingSupply, err := s.remainingSupply(ctx, chainID, contractAddress) - if err != nil { - return err - } - if burnAmount.Cmp(remainingSupply.Int) > 1 { - return errors.New("burnAmount is bigger than remaining amount") - } - return nil -} - func (s *Service) remainingSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) { tokenType, err := s.db.GetTokenType(chainID, contractAddress) if err != nil { @@ -356,20 +306,10 @@ func (s *Service) remainingSupply(ctx context.Context, chainID uint64, contractA } } -func (s *Service) prepareNewMaxSupply(ctx context.Context, chainID uint64, contractAddress string, burnAmount *bigint.BigInt) (*big.Int, error) { - maxSupply, err := s.maxSupply(ctx, chainID, contractAddress) - if err != nil { - return nil, err - } - var newMaxSupply = new(big.Int) - newMaxSupply.Sub(maxSupply, burnAmount.Int) - return newMaxSupply, nil -} - // RemainingSupply = MaxSupply - MintedCount func (s *Service) remainingCollectiblesSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) { callOpts := &bind.CallOpts{Context: ctx, Pending: false} - contractInst, err := s.manager.NewCollectiblesInstance(chainID, contractAddress) + contractInst, err := s.manager.NewCollectiblesInstance(chainID, common.HexToAddress(contractAddress)) if err != nil { return nil, err } @@ -389,7 +329,7 @@ func (s *Service) remainingCollectiblesSupply(ctx context.Context, chainID uint6 // RemainingSupply = MaxSupply - TotalSupply func (s *Service) remainingAssetsSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) { callOpts := &bind.CallOpts{Context: ctx, Pending: false} - contractInst, err := s.manager.NewAssetsInstance(chainID, contractAddress) + contractInst, err := s.manager.NewAssetsInstance(chainID, common.HexToAddress(contractAddress)) if err != nil { return nil, err } @@ -419,7 +359,7 @@ func (s *Service) ValidateWalletsAndAmounts(walletAddresses []string, amount *bi func (s *Service) GetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string) (string, error) { callOpts := &bind.CallOpts{Context: ctx, Pending: false} - contractInst, err := s.NewOwnerTokenInstance(chainID, contractAddress) + contractInst, err := s.manager.NewOwnerTokenInstance(chainID, common.HexToAddress(contractAddress)) if err != nil { return "", err } @@ -451,7 +391,7 @@ func (s *Service) SafeGetOwnerTokenAddress(ctx context.Context, chainID uint64, if err != nil { return "", err } - registryContractInst, err := s.NewCommunityOwnerTokenRegistryInstance(chainID, registryAddr.Hex()) + registryContractInst, err := s.manager.NewCommunityOwnerTokenRegistryInstance(chainID, registryAddr) if err != nil { return "", err } @@ -503,44 +443,9 @@ func (s *Service) ProcessCommunityTokenAction(message *protobuf.CommunityTokenAc return nil } -func (s *Service) SetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, txArgs wallettypes.SendTxArgs, password string, newSignerPubKey string) (string, error) { - - if len(newSignerPubKey) <= 0 { - return "", fmt.Errorf("signerPubKey is empty") - } - - transactOpts := txArgs.ToTransactOpts(utils.VerifyPasswordAndGetSigner(chainID, s.accountsManager, s.config.KeyStoreDir, txArgs.From, password)) - - contractInst, err := s.NewOwnerTokenInstance(chainID, contractAddress) - if err != nil { - return "", err - } - - tx, err := contractInst.SetSignerPublicKey(transactOpts, common.FromHex(newSignerPubKey)) - if err != nil { - return "", err - } - - err = s.pendingTracker.TrackPendingTransaction( - wcommon.ChainID(chainID), - tx.Hash(), - common.Address(txArgs.From), - common.HexToAddress(contractAddress), - transactions.SetSignerPublicKey, - transactions.Keep, - "", - ) - if err != nil { - logutils.ZapLogger().Error("TrackPendingTransaction error", zap.Error(err)) - return "", err - } - - return tx.Hash().Hex(), nil -} - func (s *Service) maxSupplyCollectibles(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) { callOpts := &bind.CallOpts{Context: ctx, Pending: false} - contractInst, err := s.manager.NewCollectiblesInstance(chainID, contractAddress) + contractInst, err := s.manager.NewCollectiblesInstance(chainID, common.HexToAddress(contractAddress)) if err != nil { return nil, err } @@ -549,7 +454,7 @@ func (s *Service) maxSupplyCollectibles(ctx context.Context, chainID uint64, con func (s *Service) maxSupplyAssets(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) { callOpts := &bind.CallOpts{Context: ctx, Pending: false} - contractInst, err := s.manager.NewAssetsInstance(chainID, contractAddress) + contractInst, err := s.manager.NewAssetsInstance(chainID, common.HexToAddress(contractAddress)) if err != nil { return nil, err } @@ -617,17 +522,8 @@ func (s *Service) TemporaryOwnerContractAddress(hash string) string { return hash + OwnerSuffix } -func (s *Service) HashFromTemporaryContractAddress(address string) string { - if strings.HasSuffix(address, OwnerSuffix) { - return strings.TrimSuffix(address, OwnerSuffix) - } else if strings.HasSuffix(address, MasterSuffix) { - return strings.TrimSuffix(address, MasterSuffix) - } - return "" -} - func (s *Service) GetMasterTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) { - ethClient, err := s.manager.rpcClient.EthClient(chainID) + ethClient, err := s.manager.contractMaker.RPCClient.EthClient(chainID) if err != nil { return "", err } @@ -658,7 +554,7 @@ func (s *Service) GetMasterTokenContractAddressFromHash(ctx context.Context, cha } func (s *Service) GetOwnerTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) { - ethClient, err := s.manager.rpcClient.EthClient(chainID) + ethClient, err := s.manager.contractMaker.RPCClient.EthClient(chainID) if err != nil { return "", err } @@ -688,51 +584,6 @@ func (s *Service) GetOwnerTokenContractAddressFromHash(ctx context.Context, chai return "", fmt.Errorf("can't find owner token address in transaction: %v", txHash) } -func (s *Service) ReTrackOwnerTokenDeploymentTransaction(ctx context.Context, chainID uint64, contractAddress string) error { - communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(chainID), contractAddress) - if err != nil { - return err - } - if communityToken == nil { - return fmt.Errorf("can't find token with address %v on chain %v", contractAddress, chainID) - } - if communityToken.DeployState != token.InProgress { - return fmt.Errorf("token with address %v on chain %v is not in progress", contractAddress, chainID) - } - - hashString := communityToken.TransactionHash - if hashString == "" && (communityToken.PrivilegesLevel == token.OwnerLevel || communityToken.PrivilegesLevel == token.MasterLevel) { - hashString = s.HashFromTemporaryContractAddress(communityToken.Address) - } - - if hashString == "" { - return fmt.Errorf("can't find transaction hash for token with address %v on chain %v", contractAddress, chainID) - } - - transactionType := transactions.DeployCommunityToken - if communityToken.PrivilegesLevel == token.OwnerLevel || communityToken.PrivilegesLevel == token.MasterLevel { - transactionType = transactions.DeployOwnerToken - } - - _, err = s.pendingTracker.GetPendingEntry(wcommon.ChainID(chainID), common.HexToHash(hashString)) - if errors.Is(err, sql.ErrNoRows) { - // start only if no pending transaction in database - err = s.pendingTracker.TrackPendingTransaction( - wcommon.ChainID(chainID), - common.HexToHash(hashString), - common.HexToAddress(communityToken.Deployer), - common.Address{}, - transactionType, - transactions.Keep, - "", - ) - logutils.ZapLogger().Debug("retracking pending transaction", zap.String("hashId", hashString)) - } else { - logutils.ZapLogger().Debug("pending transaction already tracked", zap.String("hashId", hashString)) - } - return err -} - func (s *Service) publishTokenActionToPrivilegedMembers(communityID string, chainID uint64, contractAddress string, actionType protobuf.CommunityTokenAction_ActionType) error { decodedCommunityID, err := types.DecodeHex(communityID) if err != nil { diff --git a/services/communitytokensv2/token_instances.go b/services/communitytokensv2/token_instances.go deleted file mode 100644 index 247a49557be..00000000000 --- a/services/communitytokensv2/token_instances.go +++ /dev/null @@ -1,176 +0,0 @@ -package communitytokens - -import ( - "context" - "fmt" - "math/big" - "strings" - - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/core/types" - "github.com/status-im/status-go/contracts/community-tokens/assets" - "github.com/status-im/status-go/contracts/community-tokens/collectibles" - "github.com/status-im/status-go/contracts/community-tokens/mastertoken" - "github.com/status-im/status-go/contracts/community-tokens/ownertoken" - "github.com/status-im/status-go/protocol/communities/token" - "github.com/status-im/status-go/protocol/protobuf" - "github.com/status-im/status-go/services/wallet/bigint" -) - -type TokenInstance interface { - RemoteBurn(*bind.TransactOpts, []*big.Int) (*types.Transaction, error) - Mint(*bind.TransactOpts, []string, *bigint.BigInt) (*types.Transaction, error) - SetMaxSupply(*bind.TransactOpts, *big.Int) (*types.Transaction, error) - PackMethod(ctx context.Context, methodName string, args ...interface{}) ([]byte, error) -} - -// Owner Token -type OwnerTokenInstance struct { - TokenInstance - instance *ownertoken.OwnerToken -} - -func (t OwnerTokenInstance) RemoteBurn(transactOpts *bind.TransactOpts, tokenIds []*big.Int) (*types.Transaction, error) { - return nil, fmt.Errorf("remote destruction for owner token not implemented") -} - -func (t OwnerTokenInstance) Mint(transactOpts *bind.TransactOpts, walletAddresses []string, amount *bigint.BigInt) (*types.Transaction, error) { - return nil, fmt.Errorf("minting for owner token not implemented") -} - -func (t OwnerTokenInstance) SetMaxSupply(transactOpts *bind.TransactOpts, maxSupply *big.Int) (*types.Transaction, error) { - return nil, fmt.Errorf("setting max supply for owner token not implemented") -} - -func (t OwnerTokenInstance) PackMethod(ctx context.Context, methodName string, args ...interface{}) ([]byte, error) { - ownerTokenABI, err := abi.JSON(strings.NewReader(ownertoken.OwnerTokenABI)) - if err != nil { - return []byte{}, err - } - return ownerTokenABI.Pack(methodName, args...) -} - -// Master Token -type MasterTokenInstance struct { - TokenInstance - instance *mastertoken.MasterToken -} - -func (t MasterTokenInstance) RemoteBurn(transactOpts *bind.TransactOpts, tokenIds []*big.Int) (*types.Transaction, error) { - return t.instance.RemoteBurn(transactOpts, tokenIds) -} - -func (t MasterTokenInstance) Mint(transactOpts *bind.TransactOpts, walletAddresses []string, amount *bigint.BigInt) (*types.Transaction, error) { - usersAddresses := prepareMintCollectiblesData(walletAddresses, amount) - return t.instance.MintTo(transactOpts, usersAddresses) -} - -func (t MasterTokenInstance) SetMaxSupply(transactOpts *bind.TransactOpts, maxSupply *big.Int) (*types.Transaction, error) { - return t.instance.SetMaxSupply(transactOpts, maxSupply) -} - -func (t MasterTokenInstance) PackMethod(ctx context.Context, methodName string, args ...interface{}) ([]byte, error) { - masterTokenABI, err := abi.JSON(strings.NewReader(mastertoken.MasterTokenABI)) - if err != nil { - return []byte{}, err - } - return masterTokenABI.Pack(methodName, args...) -} - -// Collectible -type CollectibleInstance struct { - TokenInstance - instance *collectibles.Collectibles -} - -func (t CollectibleInstance) RemoteBurn(transactOpts *bind.TransactOpts, tokenIds []*big.Int) (*types.Transaction, error) { - return t.instance.RemoteBurn(transactOpts, tokenIds) -} - -func (t CollectibleInstance) Mint(transactOpts *bind.TransactOpts, walletAddresses []string, amount *bigint.BigInt) (*types.Transaction, error) { - usersAddresses := prepareMintCollectiblesData(walletAddresses, amount) - return t.instance.MintTo(transactOpts, usersAddresses) -} - -func (t CollectibleInstance) SetMaxSupply(transactOpts *bind.TransactOpts, maxSupply *big.Int) (*types.Transaction, error) { - return t.instance.SetMaxSupply(transactOpts, maxSupply) -} - -func (t CollectibleInstance) PackMethod(ctx context.Context, methodName string, args ...interface{}) ([]byte, error) { - collectiblesABI, err := abi.JSON(strings.NewReader(collectibles.CollectiblesABI)) - if err != nil { - return []byte{}, err - } - return collectiblesABI.Pack(methodName, args...) -} - -// Asset -type AssetInstance struct { - TokenInstance - instance *assets.Assets -} - -func (t AssetInstance) RemoteBurn(transactOpts *bind.TransactOpts, tokenIds []*big.Int) (*types.Transaction, error) { - return nil, fmt.Errorf("remote destruction for assets not implemented") -} - -// The amount should be in smallest denomination of the asset (like wei) with decimal = 18, eg. -// if we want to mint 2.34 of the token, then amount should be 234{16 zeros}. -func (t AssetInstance) Mint(transactOpts *bind.TransactOpts, walletAddresses []string, amount *bigint.BigInt) (*types.Transaction, error) { - usersAddresses, amountsList := prepareMintAssetsData(walletAddresses, amount) - return t.instance.MintTo(transactOpts, usersAddresses, amountsList) -} - -func (t AssetInstance) SetMaxSupply(transactOpts *bind.TransactOpts, maxSupply *big.Int) (*types.Transaction, error) { - return t.instance.SetMaxSupply(transactOpts, maxSupply) -} - -func (t AssetInstance) PackMethod(ctx context.Context, methodName string, args ...interface{}) ([]byte, error) { - assetsABI, err := abi.JSON(strings.NewReader(assets.AssetsABI)) - if err != nil { - return []byte{}, err - } - return assetsABI.Pack(methodName, args...) -} - -// creator - -func NewTokenInstance(s *Service, chainID uint64, contractAddress string) (TokenInstance, error) { - tokenType, err := s.db.GetTokenType(chainID, contractAddress) - if err != nil { - return nil, err - } - privLevel, err := s.db.GetTokenPrivilegesLevel(chainID, contractAddress) - if err != nil { - return nil, err - } - switch { - case privLevel == token.OwnerLevel: - contractInst, err := s.NewOwnerTokenInstance(chainID, contractAddress) - if err != nil { - return nil, err - } - return &OwnerTokenInstance{instance: contractInst}, nil - case privLevel == token.MasterLevel: - contractInst, err := s.NewMasterTokenInstance(chainID, contractAddress) - if err != nil { - return nil, err - } - return &MasterTokenInstance{instance: contractInst}, nil - case tokenType == protobuf.CommunityTokenType_ERC721: - contractInst, err := s.manager.NewCollectiblesInstance(chainID, contractAddress) - if err != nil { - return nil, err - } - return &CollectibleInstance{instance: contractInst}, nil - case tokenType == protobuf.CommunityTokenType_ERC20: - contractInst, err := s.manager.NewAssetsInstance(chainID, contractAddress) - if err != nil { - return nil, err - } - return &AssetInstance{instance: contractInst}, nil - } - - return nil, fmt.Errorf("unknown type of contract: chain=%v, address=%v", chainID, contractAddress) -} diff --git a/services/wallet/activity/activity_v2.go b/services/wallet/activity/activity_v2.go index 3c21e9b35cb..6245896efc2 100644 --- a/services/wallet/activity/activity_v2.go +++ b/services/wallet/activity/activity_v2.go @@ -214,7 +214,10 @@ func dataToEntriesV2(deps FilterDependencies, data []*entryDataV2) ([]Entry, err entry.symbolOut, entry.symbolIn = lookupAndFillInTokens(deps, entry.tokenOut, entry.tokenIn) if entry.transferType == nil || TokenType(*entry.transferType) != Native { - interactedAddress := eth.BytesToAddress(d.Tx.To().Bytes()) + var interactedAddress eth.Address + if d.Tx.To() != nil { + interactedAddress = eth.BytesToAddress(d.Tx.To().Bytes()) + } entry.interactedContractAddress = &interactedAddress } diff --git a/services/wallet/requests/router_input_community_params.go b/services/wallet/requests/router_input_community_params.go index 372997b47ce..241a6f937bd 100644 --- a/services/wallet/requests/router_input_community_params.go +++ b/services/wallet/requests/router_input_community_params.go @@ -4,19 +4,62 @@ import ( "fmt" "math/big" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/status-im/status-go/errors" "github.com/status-im/status-go/images" + communitiestoken "github.com/status-im/status-go/protocol/communities/token" + "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/sendtype" ) const maxSupply = 999999999 var ( - ErrEmptyCollectibleName = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-001"), Details: "empty collectible name"} - ErrEmptyCollectibleSymbol = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-002"), Details: "empty collectible symbol"} - ErrWrongSupplyValue = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-003"), Details: "wrong supply value: %v"} + ErrEmptyCollectibleName = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-001"), Details: "empty collectible name"} + ErrEmptyCollectibleSymbol = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-002"), Details: "empty collectible symbol"} + ErrWrongSupplyValue = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-003"), Details: "wrong supply value: %v"} + ErrWalletAddressesEmpty = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-004"), Details: "wallet addresses list is empty"} + ErrNoCommunityAmount = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-005"), Details: "amount is required"} + ErrCommunityAmountMustBePositive = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-006"), Details: "amount must be positive"} + ErrNoCommunityIdProvided = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-007"), Details: "community id is required for community related transfers"} + ErrNoCommunitySignerPubKey = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-008"), Details: "signer pub key is required"} + ErrNoCommunityTokenDeploymentSignature = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-009"), Details: "signature is required"} + ErrNoCommunityOwnerTokenParameters = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-010"), Details: "owner token parameters are required"} + ErrNoCommunityMasterTokenParameters = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-011"), Details: "master token parameters are required"} + ErrNoCommunityDeploymentParameters = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-012"), Details: "deployment parameters are required"} + ErrNoCommunityTransferDetails = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-013"), Details: "transfer details are required"} + ErrNoCommunityContractAddress = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-014"), Details: "contract address is required"} + ErrCommunityTokenIdsListEmpty = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-015"), Details: "token list is empty"} + ErrProvidedIndexForSettingInternalDataOutOfRange = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-016"), Details: "provided index for setting internal data is out of range"} + ErrSetSignerPubKeyWithMultipleTransferDetails = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-017"), Details: "signer pub key can be set only with one transfer detail"} ) +type CommunityRouteInputParams struct { + CommunityID string `json:"communityID"` + TransferDetails []*TransferDetails `json:"transferDetails"` + SignerPubKey string `json:"signerPubKey"` + TokenIds []*hexutil.Big `json:"tokenIds"` + WalletAddresses []common.Address `json:"walletAddresses"` + TokenDeploymentSignature string `json:"tokenDeploymentSignature"` + OwnerTokenParameters *DeploymentParameters `json:"ownerTokenParameters"` + MasterTokenParameters *DeploymentParameters `json:"masterTokenParameters"` + DeploymentParameters *DeploymentParameters `json:"deploymentParameters"` + // used internally + tokenContractAddress common.Address `json:"-"` // contract address used in a single processor + amount *hexutil.Big `json:"-"` // amount used in a single processor + tokenType protobuf.CommunityTokenType `json:"-"` + privilegeLevel communitiestoken.PrivilegesLevel `json:"-"` +} + +type TransferDetails struct { + TokenType protobuf.CommunityTokenType `json:"tokenType"` + PrivilegeLevel communitiestoken.PrivilegesLevel `json:"privilegeLevel"` + TokenContractAddress common.Address `json:"tokenContractAddress"` + Amount *hexutil.Big `json:"amount"` +} + type DeploymentParameters struct { Name string `json:"name"` Symbol string `json:"symbol"` @@ -25,8 +68,8 @@ type DeploymentParameters struct { Transferable bool `json:"transferable"` RemoteSelfDestruct bool `json:"remoteSelfDestruct"` TokenURI string `json:"tokenUri"` - OwnerTokenAddress string `json:"ownerTokenAddress"` - MasterTokenAddress string `json:"masterTokenAddress"` + OwnerTokenAddress common.Address `json:"ownerTokenAddress"` + MasterTokenAddress common.Address `json:"masterTokenAddress"` CommunityID string `json:"communityId"` Description string `json:"description"` CroppedImage *images.CroppedImage `json:"croppedImage,omitempty"` // for community tokens @@ -34,6 +77,43 @@ type DeploymentParameters struct { Decimals int `json:"decimals"` } +// ID that uniquely identifies community route input params +func (c *CommunityRouteInputParams) ID() string { + return c.CommunityID + "-" + c.tokenContractAddress.String() +} + +func (c *CommunityRouteInputParams) UseTransferDetails() bool { + return len(c.TransferDetails) > 0 +} + +func (c *CommunityRouteInputParams) SetInternalParams(detailsIndex int) error { + if detailsIndex < 0 || detailsIndex >= len(c.TransferDetails) { + return ErrProvidedIndexForSettingInternalDataOutOfRange + } + + c.tokenType = c.TransferDetails[detailsIndex].TokenType + c.privilegeLevel = c.TransferDetails[detailsIndex].PrivilegeLevel + c.tokenContractAddress = c.TransferDetails[detailsIndex].TokenContractAddress + c.amount = c.TransferDetails[detailsIndex].Amount + return nil +} + +func (c *CommunityRouteInputParams) GetTokenType() protobuf.CommunityTokenType { + return c.tokenType +} + +func (c *CommunityRouteInputParams) GetPrivilegeLevel() communitiestoken.PrivilegesLevel { + return c.privilegeLevel +} + +func (c *CommunityRouteInputParams) GetTokenContractAddress() common.Address { + return c.tokenContractAddress +} + +func (c *CommunityRouteInputParams) GetAmount() *big.Int { + return c.amount.ToInt() +} + func (d *DeploymentParameters) GetSupply() *big.Int { if d.InfiniteSupply { return d.GetInfiniteSupply() @@ -72,3 +152,187 @@ func (d *DeploymentParameters) Validate(isAsset bool) error { } return nil } + +func (c *CommunityRouteInputParams) validateCommunityRelatedInputs(sendType sendtype.SendType) error { + if c.CommunityID == "" { + return ErrNoCommunityIdProvided + } + + if sendType == sendtype.CommunityBurn { + if len(c.TransferDetails) == 0 { + return ErrNoCommunityTransferDetails + } + for _, td := range c.TransferDetails { + if td.TokenContractAddress.String() == "" || (td.TokenContractAddress == common.Address{}) { + return ErrNoCommunityContractAddress + } + if td.Amount == nil { + return ErrNoCommunityAmount + } + if td.Amount.ToInt().Cmp(big.NewInt(0)) <= 0 { + return ErrCommunityAmountMustBePositive + } + } + } + + if sendType == sendtype.CommunityDeployAssets { + if c.DeploymentParameters == nil { + return ErrNoCommunityDeploymentParameters + } + err := c.DeploymentParameters.Validate(true) + if err != nil { + return err + } + } + + if sendType == sendtype.CommunityDeployCollectibles { + if c.DeploymentParameters == nil { + return ErrNoCommunityDeploymentParameters + } + err := c.DeploymentParameters.Validate(false) + if err != nil { + return err + } + } + + if sendType == sendtype.CommunityDeployOwnerToken { + if c.SignerPubKey == "" { + return ErrNoCommunitySignerPubKey + } + if c.TokenDeploymentSignature == "" { + return ErrNoCommunityTokenDeploymentSignature + } + if c.OwnerTokenParameters == nil { + return ErrNoCommunityOwnerTokenParameters + } + if c.MasterTokenParameters == nil { + return ErrNoCommunityMasterTokenParameters + } + } + + if sendType == sendtype.CommunityMintTokens { + if len(c.WalletAddresses) == 0 { + return ErrWalletAddressesEmpty + } + if len(c.TransferDetails) == 0 { + return ErrNoCommunityTransferDetails + } + for _, td := range c.TransferDetails { + if td.TokenContractAddress.String() == "" || (td.TokenContractAddress == common.Address{}) { + return ErrNoCommunityContractAddress + } + if td.Amount == nil { + return ErrNoCommunityAmount + } + if td.Amount.ToInt().Cmp(big.NewInt(0)) <= 0 { + return ErrCommunityAmountMustBePositive + } + } + } + + if sendType == sendtype.CommunityRemoteBurn { + if len(c.TokenIds) == 0 { + return ErrCommunityTokenIdsListEmpty + } + } + + if sendType == sendtype.CommunitySetSignerPubKey { + if c.SignerPubKey == "" { + return ErrNoCommunitySignerPubKey + } + + if len(c.TransferDetails) != 1 { + return ErrSetSignerPubKeyWithMultipleTransferDetails + } + for _, td := range c.TransferDetails { + if td.TokenContractAddress.String() == "" || (td.TokenContractAddress == common.Address{}) { + return ErrNoCommunityContractAddress + } + } + } + + return nil +} + +func (td *TransferDetails) copy() *TransferDetails { + newParams := &TransferDetails{ + TokenType: td.TokenType, + PrivilegeLevel: td.PrivilegeLevel, + TokenContractAddress: td.TokenContractAddress, + } + + if td.Amount != nil { + newParams.Amount = (*hexutil.Big)(big.NewInt(0).Set(td.Amount.ToInt())) + } + return newParams +} + +func (d *DeploymentParameters) copy() *DeploymentParameters { + newParams := &DeploymentParameters{ + Name: d.Name, + Symbol: d.Symbol, + InfiniteSupply: d.InfiniteSupply, + Transferable: d.Transferable, + RemoteSelfDestruct: d.RemoteSelfDestruct, + TokenURI: d.TokenURI, + OwnerTokenAddress: d.OwnerTokenAddress, + MasterTokenAddress: d.MasterTokenAddress, + CommunityID: d.CommunityID, + Description: d.Description, + Base64Image: d.Base64Image, + Decimals: d.Decimals, + } + + if d.Supply != nil { + newParams.Supply = &bigint.BigInt{Int: new(big.Int).Set(d.Supply.Int)} + } + if d.CroppedImage != nil { + ci := *d.CroppedImage + newParams.CroppedImage = &ci + } + return newParams +} + +func (c *CommunityRouteInputParams) Copy() *CommunityRouteInputParams { + newParams := &CommunityRouteInputParams{ + CommunityID: c.CommunityID, + SignerPubKey: c.SignerPubKey, + TokenDeploymentSignature: c.TokenDeploymentSignature, + } + + if c.TokenIds != nil { + newParams.TokenIds = make([]*hexutil.Big, len(c.TokenIds)) + for i, id := range c.TokenIds { + newParams.TokenIds[i] = (*hexutil.Big)(big.NewInt(0).Set(id.ToInt())) + } + } + if c.TransferDetails != nil { + newParams.TransferDetails = make([]*TransferDetails, len(c.TransferDetails)) + for i, td := range c.TransferDetails { + newParams.TransferDetails[i] = td.copy() + } + } + if c.WalletAddresses != nil { + newParams.WalletAddresses = make([]common.Address, len(c.WalletAddresses)) + copy(newParams.WalletAddresses, c.WalletAddresses) + } + if c.OwnerTokenParameters != nil { + newParams.OwnerTokenParameters = c.OwnerTokenParameters.copy() + } + if c.MasterTokenParameters != nil { + newParams.MasterTokenParameters = c.MasterTokenParameters.copy() + } + if c.DeploymentParameters != nil { + newParams.DeploymentParameters = c.DeploymentParameters.copy() + } + + // internal fields + newParams.tokenType = c.tokenType + newParams.privilegeLevel = c.privilegeLevel + newParams.tokenContractAddress = c.tokenContractAddress + if c.amount != nil { + newParams.amount = (*hexutil.Big)(big.NewInt(0).Set(c.amount.ToInt())) + } + + return newParams +} diff --git a/services/wallet/requests/router_input_community_params_test.go b/services/wallet/requests/router_input_community_params_test.go index d847a9b0d51..c966be16bda 100644 --- a/services/wallet/requests/router_input_community_params_test.go +++ b/services/wallet/requests/router_input_community_params_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/ethereum/go-ethereum/common" "github.com/status-im/status-go/services/wallet/bigint" ) @@ -17,37 +18,37 @@ func TestDeploymentParameters(t *testing.T) { }{ { name: "emptyName", - parameters: DeploymentParameters{"", "SYMBOL", &bigint.BigInt{Int: big.NewInt(int64(123))}, false, false, false, "", "", "", "", "", nil, "", 0}, + parameters: DeploymentParameters{"", "SYMBOL", &bigint.BigInt{Int: big.NewInt(int64(123))}, false, false, false, "", common.Address{}, common.Address{}, "", "", nil, "", 0}, isError: true, }, { name: "emptySymbol", - parameters: DeploymentParameters{"NAME", "", &bigint.BigInt{Int: big.NewInt(123)}, false, false, false, "", "", "", "", "", nil, "", 0}, + parameters: DeploymentParameters{"NAME", "", &bigint.BigInt{Int: big.NewInt(123)}, false, false, false, "", common.Address{}, common.Address{}, "", "", nil, "", 0}, isError: true, }, { name: "negativeSupply", - parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(-123)}, false, false, false, "", "", "", "", "", nil, "", 0}, + parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(-123)}, false, false, false, "", common.Address{}, common.Address{}, "", "", nil, "", 0}, isError: true, }, { name: "zeroSupply", - parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(0)}, false, false, false, "", "", "", "", "", nil, "", 0}, + parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(0)}, false, false, false, "", common.Address{}, common.Address{}, "", "", nil, "", 0}, isError: false, }, { name: "negativeSupplyAndInfinite", - parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(-123)}, true, false, false, "", "", "", "", "", nil, "", 0}, + parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(-123)}, true, false, false, "", common.Address{}, common.Address{}, "", "", nil, "", 0}, isError: false, }, { name: "supplyGreaterThanMax", - parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(maxSupply + 1)}, false, false, false, "", "", "", "", "", nil, "", 0}, + parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(maxSupply + 1)}, false, false, false, "", common.Address{}, common.Address{}, "", "", nil, "", 0}, isError: true, }, { name: "supplyIsMax", - parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(maxSupply)}, false, false, false, "", "", "", "", "", nil, "", 0}, + parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(maxSupply)}, false, false, false, "", common.Address{}, common.Address{}, "", "", nil, "", 0}, isError: false, }, } @@ -63,10 +64,10 @@ func TestDeploymentParameters(t *testing.T) { }) } - notInfiniteSupplyParams := DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(123)}, false, false, false, "", "", "", "", "", nil, "", 0} + notInfiniteSupplyParams := DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(123)}, false, false, false, "", common.Address{}, common.Address{}, "", "", nil, "", 0} requiredSupply := big.NewInt(123) require.Equal(t, notInfiniteSupplyParams.GetSupply(), requiredSupply) - infiniteSupplyParams := DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(123)}, true, false, false, "", "", "", "", "", nil, "", 0} + infiniteSupplyParams := DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(123)}, true, false, false, "", common.Address{}, common.Address{}, "", "", nil, "", 0} requiredSupply = infiniteSupplyParams.GetInfiniteSupply() require.Equal(t, infiniteSupplyParams.GetSupply(), requiredSupply) } diff --git a/services/wallet/requests/router_input_params.go b/services/wallet/requests/router_input_params.go index f53757620ab..a0d938c00e1 100644 --- a/services/wallet/requests/router_input_params.go +++ b/services/wallet/requests/router_input_params.go @@ -2,6 +2,8 @@ package requests import ( "math/big" + "reflect" + "sort" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -32,6 +34,10 @@ var ( ErrENSSetPubKeyInvalidUsername = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-017"), Details: "a valid username, ending in '.eth', is required for ENSSetPubKey"} ErrLockedAmountExcludesAllSupported = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-018"), Details: "all supported chains are excluded, routing impossible"} ErrCannotCheckLockedAmounts = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-019"), Details: "cannot check locked amounts"} + ErrNoCommunityParametersProvided = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-020"), Details: "no community parameters provided"} + ErrNoFromChainProvided = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-021"), Details: "from chain not provided"} + ErrNoToChainProvided = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-022"), Details: "to chain not provided"} + ErrFromAndToChainMustBeTheSame = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-023"), Details: "from and to chain IDs must be the same"} ) type RouteInputParams struct { @@ -58,6 +64,9 @@ type RouteInputParams struct { // Used internally PathTxCustomParams map[string]*PathTxCustomParams `json:"-"` + // Community related params + CommunityRouteInputParams *CommunityRouteInputParams `json:"communityRouteInputParams"` + // TODO: Remove two fields below once we implement a better solution for tests // Currently used for tests only TestsMode bool @@ -81,6 +90,29 @@ type Estimation struct { Err error } +func slicesEqual(a, b []uint64) bool { + if len(a) != len(b) { + return false + } + + aCopy := make([]uint64, len(a)) + bCopy := make([]uint64, len(b)) + copy(aCopy, a) + copy(bCopy, b) + + sort.Slice(aCopy, func(i, j int) bool { return aCopy[i] < aCopy[j] }) + sort.Slice(bCopy, func(i, j int) bool { return bCopy[i] < bCopy[j] }) + + return reflect.DeepEqual(aCopy, bCopy) +} + +func (i *RouteInputParams) UseCommunityTransferDetails() bool { + if !i.SendType.IsCommunityRelatedTransfer() || i.CommunityRouteInputParams == nil { + return false + } + return i.CommunityRouteInputParams.UseTransferDetails() +} + func (i *RouteInputParams) Validate() error { if i.SendType == sendtype.ENSRegister { if i.Username == "" || i.PublicKey == "" { @@ -144,6 +176,23 @@ func (i *RouteInputParams) Validate() error { } } + if i.SendType.IsCommunityRelatedTransfer() { + if i.DisabledFromChainIDs == nil || len(i.DisabledFromChainIDs) == 0 { + return ErrNoFromChainProvided + } + if i.DisabledToChainIDs == nil || len(i.DisabledToChainIDs) == 0 { + return ErrNoToChainProvided + } + if !slicesEqual(i.DisabledFromChainIDs, i.DisabledToChainIDs) { + return ErrFromAndToChainMustBeTheSame + } + + if i.CommunityRouteInputParams == nil { + return ErrNoCommunityParametersProvided + } + return i.CommunityRouteInputParams.validateCommunityRelatedInputs(i.SendType) + } + return i.validateFromLockedAmount() } diff --git a/services/wallet/responses/router_transactions.go b/services/wallet/responses/router_transactions.go index 9a533c50467..589041afa4c 100644 --- a/services/wallet/responses/router_transactions.go +++ b/services/wallet/responses/router_transactions.go @@ -27,6 +27,9 @@ type SendDetails struct { Username string `json:"username"` PublicKey string `json:"publicKey"` PackID string `json:"packId"` + + // community related params + CommunityParams *requests.CommunityRouteInputParams `json:"communityParams"` } type SigningDetails struct { @@ -112,4 +115,8 @@ func (sd *SendDetails) UpdateFields(inputParams requests.RouteInputParams, fromC // Set chain IDs, in case of an error while sending a transaction sd.FromChain = fromChain sd.ToChain = toChain + + if inputParams.CommunityRouteInputParams != nil { + sd.CommunityParams = inputParams.CommunityRouteInputParams + } } diff --git a/services/wallet/routeexecution/manager.go b/services/wallet/routeexecution/manager.go index c4f3460b581..be81ca0f518 100644 --- a/services/wallet/routeexecution/manager.go +++ b/services/wallet/routeexecution/manager.go @@ -19,6 +19,7 @@ import ( "github.com/status-im/status-go/services/wallet/responses" "github.com/status-im/status-go/services/wallet/routeexecution/storage" "github.com/status-im/status-go/services/wallet/router" + "github.com/status-im/status-go/services/wallet/router/pathprocessor" pathProcessorCommon "github.com/status-im/status-go/services/wallet/router/pathprocessor/common" "github.com/status-im/status-go/services/wallet/router/routes" "github.com/status-im/status-go/services/wallet/router/sendtype" @@ -87,6 +88,13 @@ func (m *Manager) BuildTransactionsFromRoute(ctx context.Context, buildInputPara return } + // re-use path processor input params structure to pass extra params to transaction manager + var extraParams pathprocessor.ProcessorInputParams + extraParams, err = m.router.CreateProcessorInputParams(&routeInputParams, nil, nil, nil, nil, nil, buildInputParams.SlippagePercentage, 0) + if err != nil { + return + } + m.buildInputParams = buildInputParams fromChainID, toChainID := route.GetFirstPathChains() @@ -99,14 +107,7 @@ func (m *Manager) BuildTransactionsFromRoute(ctx context.Context, buildInputPara response.SigningDetails, fromChainID, toChainID, err = m.transactionManager.BuildTransactionsFromRoute( route, m.router.GetPathProcessors(), - transfer.BuildRouteExtraParams{ - AddressFrom: routeInputParams.AddrFrom, - AddressTo: routeInputParams.AddrTo, - Username: routeInputParams.Username, - PublicKey: routeInputParams.PublicKey, - PackID: routeInputParams.PackID.ToInt(), - SlippagePercentage: buildInputParams.SlippagePercentage, - }, + &extraParams, ) if err != nil { response.SendDetails.UpdateFields(routeInputParams, fromChainID, toChainID) diff --git a/services/wallet/router/errors.go b/services/wallet/router/errors.go index 8c504c6586b..9c708635d1e 100644 --- a/services/wallet/router/errors.go +++ b/services/wallet/router/errors.go @@ -6,18 +6,21 @@ import ( // Abbreviation `WR` for the error code stands for Wallet Router var ( - ErrNotEnoughTokenBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-001"), Details: "not enough token balance, token: %s, chainId: %d"} - ErrNotEnoughNativeBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-002"), Details: "not enough native balance, token: %s, chainId: %d"} - ErrNativeTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-003"), Details: "native token not found"} - ErrTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-004"), Details: "token not found"} - ErrNoBestRouteFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-005"), Details: "no best route found"} - ErrCannotCheckBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-006"), Details: "cannot check balance"} - ErrLowAmountInForHopBridge = &errors.ErrorResponse{Code: errors.ErrorCode("WR-007"), Details: "bonder fee greater than estimated received, a higher amount is needed to cover fees"} - ErrNoPositiveBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-008"), Details: "no positive balance"} - ErrCustomFeeModeCannotBeSetThisWay = &errors.ErrorResponse{Code: errors.ErrorCode("WR-009"), Details: "custom fee mode cannot be set this way"} - ErrOnlyCustomFeeModeCanBeSetThisWay = &errors.ErrorResponse{Code: errors.ErrorCode("WR-010"), Details: "only custom fee mode can be set this way"} - ErrTxIdentityNotProvided = &errors.ErrorResponse{Code: errors.ErrorCode("WR-011"), Details: "transaction identity not provided"} - ErrTxCustomParamsNotProvided = &errors.ErrorResponse{Code: errors.ErrorCode("WR-012"), Details: "transaction custom params not provided"} - ErrCannotCustomizeIfNoRoute = &errors.ErrorResponse{Code: errors.ErrorCode("WR-013"), Details: "cannot customize params if no route"} - ErrCannotFindPathForProvidedIdentity = &errors.ErrorResponse{Code: errors.ErrorCode("WR-014"), Details: "cannot find path for provided identity"} + ErrNotEnoughTokenBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-001"), Details: "not enough token balance, token: %s, chainId: %d"} + ErrNotEnoughNativeBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-002"), Details: "not enough native balance, token: %s, chainId: %d"} + ErrNativeTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-003"), Details: "native token not found"} + ErrTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-004"), Details: "token not found"} + ErrNoBestRouteFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-005"), Details: "no best route found"} + ErrCannotCheckBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-006"), Details: "cannot check balance"} + ErrLowAmountInForHopBridge = &errors.ErrorResponse{Code: errors.ErrorCode("WR-007"), Details: "bonder fee greater than estimated received, a higher amount is needed to cover fees"} + ErrNoPositiveBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-008"), Details: "no positive balance"} + ErrCustomFeeModeCannotBeSetThisWay = &errors.ErrorResponse{Code: errors.ErrorCode("WR-009"), Details: "custom fee mode cannot be set this way"} + ErrOnlyCustomFeeModeCanBeSetThisWay = &errors.ErrorResponse{Code: errors.ErrorCode("WR-010"), Details: "only custom fee mode can be set this way"} + ErrTxIdentityNotProvided = &errors.ErrorResponse{Code: errors.ErrorCode("WR-011"), Details: "transaction identity not provided"} + ErrTxCustomParamsNotProvided = &errors.ErrorResponse{Code: errors.ErrorCode("WR-012"), Details: "transaction custom params not provided"} + ErrCannotCustomizeIfNoRoute = &errors.ErrorResponse{Code: errors.ErrorCode("WR-013"), Details: "cannot customize params if no route"} + ErrCannotFindPathForProvidedIdentity = &errors.ErrorResponse{Code: errors.ErrorCode("WR-014"), Details: "cannot find path for provided identity"} + ErrPathNotSupportedForProvidedChain = &errors.ErrorResponse{Code: errors.ErrorCode("WR-015"), Details: "path not supported for provided chain"} + ErrPathNotSupportedBetweenProvidedChains = &errors.ErrorResponse{Code: errors.ErrorCode("WR-016"), Details: "path not supported between provided chains"} + ErrPathNotAvaliableForProvidedParameters = &errors.ErrorResponse{Code: errors.ErrorCode("WR-017"), Details: "path not available for provided parameters"} ) diff --git a/services/wallet/router/pathprocessor/common/constants.go b/services/wallet/router/pathprocessor/common/constants.go index 522fd32c2c2..4965c963cbe 100644 --- a/services/wallet/router/pathprocessor/common/constants.go +++ b/services/wallet/router/pathprocessor/common/constants.go @@ -4,14 +4,23 @@ const ( IncreaseEstimatedGasFactor = 1.2 SevenDaysInSeconds = 60 * 60 * 24 * 7 - ProcessorTransferName = "Transfer" - ProcessorBridgeHopName = "Hop" - ProcessorBridgeCelerName = "CBridge" - ProcessorSwapParaswapName = "Paraswap" - ProcessorERC721Name = "ERC721Transfer" - ProcessorERC1155Name = "ERC1155Transfer" - ProcessorENSRegisterName = "ENSRegister" - ProcessorENSReleaseName = "ENSRelease" - ProcessorENSPublicKeyName = "ENSPublicKey" - ProcessorStickersBuyName = "StickersBuy" + CommunityDeploymentTokenDecimals = uint8(18) + + ProcessorTransferName = "Transfer" + ProcessorBridgeHopName = "Hop" + ProcessorBridgeCelerName = "CBridge" + ProcessorSwapParaswapName = "Paraswap" + ProcessorERC721Name = "ERC721Transfer" + ProcessorERC1155Name = "ERC1155Transfer" + ProcessorENSRegisterName = "ENSRegister" + ProcessorENSReleaseName = "ENSRelease" + ProcessorENSPublicKeyName = "ENSPublicKey" + ProcessorStickersBuyName = "StickersBuy" + ProcessorCommunityDeployCollectiblesName = "CommunityDeployCollectibles" + ProcessorCommunityDeployOwnerTokenName = "CommunityDeployOwnerToken" + ProcessorCommunityBurnName = "CommunityBurn" + ProcessorCommunityDeployAssetsName = "CommunityDeployAssets" + ProcessorCommunityMintTokensName = "CommunityMintTokens" + ProcessorCommunityRemoteBurnName = "CommunityRemoteBurn" + ProcessorCommunitySetSignerPubKeyName = "CommunitySetSignerPubKey" ) diff --git a/services/wallet/router/pathprocessor/errors.go b/services/wallet/router/pathprocessor/errors.go index 0b9ae01f167..f16cd44b6ab 100644 --- a/services/wallet/router/pathprocessor/errors.go +++ b/services/wallet/router/pathprocessor/errors.go @@ -49,6 +49,9 @@ var ( ErrPriceTimeout = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-037"), Details: "price timeout"} ErrNotEnoughLiquidity = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-038"), Details: "not enough liquidity"} ErrPriceImpactTooHigh = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-039"), Details: "price impact too high"} + ErrBurnAmountTooHigh = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-040"), Details: "burn amount too high"} + ErrCommunityTokenType = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-041"), Details: "invalid community token type"} + ErrIncorrectSignatureFormat = &errors.ErrorResponse{Code: errors.ErrorCode("WPP-042"), Details: "incorrect signature: got %d, want %d"} ) func createErrorResponse(processorName string, err error) error { diff --git a/services/wallet/router/pathprocessor/processor.go b/services/wallet/router/pathprocessor/processor.go index 084064c1857..cf4f72a014d 100644 --- a/services/wallet/router/pathprocessor/processor.go +++ b/services/wallet/router/pathprocessor/processor.go @@ -44,6 +44,17 @@ type PathProcessorClearable interface { Clear() } +type ProcessorCommunityTokenParams struct { + Name string + Symbol string + TokenURI string + Transferable bool + RemoteSelfDestruct bool + Supply *big.Int + OwnerTokenAddress string + MasterTokenAddress string +} + type ProcessorInputParams struct { FromChain *params.Network ToChain *params.Network @@ -55,10 +66,14 @@ type ProcessorInputParams struct { AmountOut *big.Int // extra params - BonderFee *big.Int - Username string - PublicKey string - PackID *big.Int + BonderFee *big.Int + Username string + PublicKey string + PackID *big.Int + SlippagePercentage float32 + + // community related params + CommunityParams *requests.CommunityRouteInputParams // for testing purposes TestsMode bool diff --git a/services/wallet/router/pathprocessor/processor_community_burn.go b/services/wallet/router/pathprocessor/processor_community_burn.go new file mode 100644 index 00000000000..28bfcf52588 --- /dev/null +++ b/services/wallet/router/pathprocessor/processor_community_burn.go @@ -0,0 +1,234 @@ +package pathprocessor + +import ( + "context" + "math/big" + "strings" + + "go.uber.org/zap" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/status-im/status-go/account" + communitytokens "github.com/status-im/status-go/contracts/community-tokens" + "github.com/status-im/status-go/contracts/community-tokens/assets" + "github.com/status-im/status-go/contracts/community-tokens/collectibles" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/logutils" + "github.com/status-im/status-go/protocol/protobuf" + "github.com/status-im/status-go/rpc" + walletCommon "github.com/status-im/status-go/services/wallet/common" + pathProcessorCommon "github.com/status-im/status-go/services/wallet/router/pathprocessor/common" + "github.com/status-im/status-go/services/wallet/wallettypes" + "github.com/status-im/status-go/transactions" +) + +type CommunityBurnProcessor struct { + contractMaker *communitytokens.CommunityTokensContractMaker + transactor transactions.TransactorIface +} + +func NewCommunityBurnProcessor(rpcClient *rpc.Client, transactor transactions.TransactorIface) *CommunityBurnProcessor { + return &CommunityBurnProcessor{ + contractMaker: &communitytokens.CommunityTokensContractMaker{ + RPCClient: rpcClient, + }, + transactor: transactor, + } +} + +func createCommunityBurnErrorResponse(err error) error { + return createErrorResponse(pathProcessorCommon.ProcessorCommunityBurnName, err) +} + +func (s *CommunityBurnProcessor) Name() string { + return pathProcessorCommon.ProcessorCommunityBurnName +} + +func (s *CommunityBurnProcessor) AvailableFor(params ProcessorInputParams) (bool, error) { + return true, nil +} + +func (s *CommunityBurnProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { + return walletCommon.ZeroBigIntValue(), walletCommon.ZeroBigIntValue(), nil +} + +func (s *CommunityBurnProcessor) validateBurnAmount(ctx context.Context, params ProcessorInputParams) error { + remainingSupply, err := s.remainingSupply(ctx, params) + if err != nil { + return err + } + if params.CommunityParams.GetAmount().Cmp(remainingSupply) > 1 { + return ErrBurnAmountTooHigh + } + return nil +} + +func (s *CommunityBurnProcessor) remainingSupply(ctx context.Context, params ProcessorInputParams) (*big.Int, error) { + switch params.CommunityParams.GetTokenType() { + case protobuf.CommunityTokenType_ERC721: + return s.remainingCollectiblesSupply(ctx, params.FromChain.ChainID, params.CommunityParams.GetTokenContractAddress()) + case protobuf.CommunityTokenType_ERC20: + return s.remainingAssetsSupply(ctx, params.FromChain.ChainID, params.CommunityParams.GetTokenContractAddress()) + default: + return nil, ErrCommunityTokenType + } +} + +func (s *CommunityBurnProcessor) maxSupply(ctx context.Context, params ProcessorInputParams) (*big.Int, error) { + switch params.CommunityParams.GetTokenType() { + case protobuf.CommunityTokenType_ERC721: + return s.maxSupplyCollectibles(ctx, params.FromChain.ChainID, params.CommunityParams.GetTokenContractAddress()) + case protobuf.CommunityTokenType_ERC20: + return s.maxSupplyAssets(ctx, params.FromChain.ChainID, params.CommunityParams.GetTokenContractAddress()) + default: + return nil, ErrCommunityTokenType + } +} + +// RemainingSupply = MaxSupply - MintedCount +func (s *CommunityBurnProcessor) remainingCollectiblesSupply(ctx context.Context, chainID uint64, contractAddress common.Address) (*big.Int, error) { + callOpts := &bind.CallOpts{Context: ctx, Pending: false} + contractInst, err := s.contractMaker.NewCollectiblesInstance(chainID, contractAddress) + if err != nil { + return nil, err + } + maxSupply, err := contractInst.MaxSupply(callOpts) + if err != nil { + return nil, err + } + mintedCount, err := contractInst.MintedCount(callOpts) + if err != nil { + return nil, err + } + res := big.NewInt(0) + res.Sub(maxSupply, mintedCount) + return res, nil +} + +// RemainingSupply = MaxSupply - TotalSupply +func (s *CommunityBurnProcessor) remainingAssetsSupply(ctx context.Context, chainID uint64, contractAddress common.Address) (*big.Int, error) { + callOpts := &bind.CallOpts{Context: ctx, Pending: false} + contractInst, err := s.contractMaker.NewAssetsInstance(chainID, contractAddress) + if err != nil { + return nil, err + } + maxSupply, err := contractInst.MaxSupply(callOpts) + if err != nil { + return nil, err + } + totalSupply, err := contractInst.TotalSupply(callOpts) + if err != nil { + return nil, err + } + res := big.NewInt(0) + res.Sub(maxSupply, totalSupply) + return res, nil +} + +func (s *CommunityBurnProcessor) maxSupplyCollectibles(ctx context.Context, chainID uint64, contractAddress common.Address) (*big.Int, error) { + callOpts := &bind.CallOpts{Context: ctx, Pending: false} + contractInst, err := s.contractMaker.NewCollectiblesInstance(chainID, contractAddress) + if err != nil { + return nil, err + } + return contractInst.MaxSupply(callOpts) +} + +func (s *CommunityBurnProcessor) maxSupplyAssets(ctx context.Context, chainID uint64, contractAddress common.Address) (*big.Int, error) { + callOpts := &bind.CallOpts{Context: ctx, Pending: false} + contractInst, err := s.contractMaker.NewAssetsInstance(chainID, contractAddress) + if err != nil { + return nil, err + } + return contractInst.MaxSupply(callOpts) +} + +func (s *CommunityBurnProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { + ctx := context.Background() + err := s.validateBurnAmount(ctx, params) + if err != nil { + return []byte{}, createCommunityBurnErrorResponse(err) + } + + maxSupply, err := s.maxSupply(ctx, params) + if err != nil { + return []byte{}, createCommunityBurnErrorResponse(err) + } + newMaxSupply := big.NewInt(0) + newMaxSupply.Sub(maxSupply, params.CommunityParams.GetAmount()) + + switch params.CommunityParams.GetTokenType() { + case protobuf.CommunityTokenType_ERC721: + collectiblesABI, err := abi.JSON(strings.NewReader(collectibles.CollectiblesABI)) + if err != nil { + return []byte{}, createCommunityBurnErrorResponse(err) + } + return collectiblesABI.Pack("setMaxSupply", newMaxSupply) + case protobuf.CommunityTokenType_ERC20: + assetsABI, err := abi.JSON(strings.NewReader(assets.AssetsABI)) + if err != nil { + return []byte{}, createCommunityBurnErrorResponse(err) + } + return assetsABI.Pack("setMaxSupply", newMaxSupply) + default: + return nil, ErrCommunityTokenType + } +} + +func (s *CommunityBurnProcessor) EstimateGas(params ProcessorInputParams) (uint64, error) { + if params.TestsMode { + return 0, ErrNoEstimationFound + } + + input, err := s.PackTxInputData(params) + if err != nil { + return 0, createCommunityBurnErrorResponse(err) + } + + ethClient, err := s.contractMaker.RPCClient.EthClient(params.FromChain.ChainID) + if err != nil { + return 0, createCommunityBurnErrorResponse(err) + } + + toAddress := params.CommunityParams.GetTokenContractAddress() + msg := ethereum.CallMsg{ + From: params.FromAddr, + To: &toAddress, + Value: walletCommon.ZeroBigIntValue(), + Data: input, + } + + estimation, err := ethClient.EstimateGas(context.Background(), msg) + if err != nil { + return 0, createCommunityBurnErrorResponse(err) + } + + increasedEstimation := float64(estimation) * pathProcessorCommon.IncreaseEstimatedGasFactor + logutils.ZapLogger().Debug("CommunityBurnProcessor estimation", zap.Uint64("gas", uint64(increasedEstimation))) + + return uint64(increasedEstimation), nil +} + +func (s *CommunityBurnProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) { + return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce, verifiedAccount) +} + +func (s *CommunityBurnProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) +} + +func (s *CommunityBurnProcessor) BuildTransactionV2(sendArgs *wallettypes.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.FromChainID, *sendArgs, lastUsedNonce) +} + +func (s *CommunityBurnProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { + return params.CommunityParams.GetAmount(), nil +} + +func (s *CommunityBurnProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { + return params.CommunityParams.GetTokenContractAddress(), nil +} diff --git a/services/wallet/router/pathprocessor/processor_community_deploy_assets.go b/services/wallet/router/pathprocessor/processor_community_deploy_assets.go new file mode 100644 index 00000000000..28c49f00ac1 --- /dev/null +++ b/services/wallet/router/pathprocessor/processor_community_deploy_assets.go @@ -0,0 +1,126 @@ +package pathprocessor + +import ( + "context" + "math/big" + "strings" + + "go.uber.org/zap" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/status-im/status-go/account" + communitytokens "github.com/status-im/status-go/contracts/community-tokens" + "github.com/status-im/status-go/contracts/community-tokens/assets" + 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/logutils" + "github.com/status-im/status-go/rpc" + walletCommon "github.com/status-im/status-go/services/wallet/common" + pathProcessorCommon "github.com/status-im/status-go/services/wallet/router/pathprocessor/common" + "github.com/status-im/status-go/services/wallet/wallettypes" + "github.com/status-im/status-go/transactions" +) + +type CommunityDeployAssetsProcessor struct { + contractMaker *communitytokens.CommunityTokensContractMaker + transactor transactions.TransactorIface +} + +func NewCommunityDeployAssetsProcessor(rpcClient *rpc.Client, transactor transactions.TransactorIface) *CommunityDeployAssetsProcessor { + return &CommunityDeployAssetsProcessor{ + contractMaker: &communitytokens.CommunityTokensContractMaker{ + RPCClient: rpcClient, + }, + transactor: transactor, + } +} + +func createCommunityDeployAssetsErrorResponse(err error) error { + return createErrorResponse(pathProcessorCommon.ProcessorCommunityDeployAssetsName, err) +} + +func (s *CommunityDeployAssetsProcessor) Name() string { + return pathProcessorCommon.ProcessorCommunityDeployAssetsName +} + +func (s *CommunityDeployAssetsProcessor) AvailableFor(params ProcessorInputParams) (bool, error) { + return true, nil +} + +func (s *CommunityDeployAssetsProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { + return walletCommon.ZeroBigIntValue(), walletCommon.ZeroBigIntValue(), nil +} + +func (s *CommunityDeployAssetsProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { + assetsABI, err := abi.JSON(strings.NewReader(assets.AssetsABI)) + if err != nil { + return []byte{}, err + } + + data, err := assetsABI.Pack("" /*constructor name is empty*/, params.CommunityParams.DeploymentParameters.Name, + params.CommunityParams.DeploymentParameters.Symbol, pathProcessorCommon.CommunityDeploymentTokenDecimals, + params.CommunityParams.DeploymentParameters.GetSupply(), params.CommunityParams.DeploymentParameters.TokenURI, + params.CommunityParams.DeploymentParameters.OwnerTokenAddress, + params.CommunityParams.DeploymentParameters.MasterTokenAddress) + if err != nil { + return []byte{}, err + } + + return append(common.FromHex(assets.AssetsBin), data...), nil +} + +func (s *CommunityDeployAssetsProcessor) EstimateGas(params ProcessorInputParams) (uint64, error) { + if params.TestsMode { + return 0, ErrNoEstimationFound + } + + input, err := s.PackTxInputData(params) + if err != nil { + return 0, createCommunityDeployAssetsErrorResponse(err) + } + + ethClient, err := s.contractMaker.RPCClient.EthClient(params.FromChain.ChainID) + if err != nil { + return 0, createCommunityDeployAssetsErrorResponse(err) + } + + msg := ethereum.CallMsg{ + From: params.FromAddr, + To: nil, + Value: walletCommon.ZeroBigIntValue(), + Data: input, + } + + estimation, err := ethClient.EstimateGas(context.Background(), msg) + if err != nil { + return 0, createCommunityDeployAssetsErrorResponse(err) + } + + increasedEstimation := float64(estimation) * pathProcessorCommon.IncreaseEstimatedGasFactor + logutils.ZapLogger().Debug("CommunityDeployAssetsProcessor estimation", zap.Uint64("gas", uint64(increasedEstimation))) + + return uint64(increasedEstimation), nil +} + +func (s *CommunityDeployAssetsProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) { + return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce, verifiedAccount) +} + +func (s *CommunityDeployAssetsProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) +} + +func (s *CommunityDeployAssetsProcessor) BuildTransactionV2(sendArgs *wallettypes.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.FromChainID, *sendArgs, lastUsedNonce) +} + +func (s *CommunityDeployAssetsProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { + return params.AmountIn, nil +} + +func (s *CommunityDeployAssetsProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { + return communitytokendeployer.ContractAddress(params.FromChain.ChainID) +} diff --git a/services/wallet/router/pathprocessor/processor_community_deploy_collectibles.go b/services/wallet/router/pathprocessor/processor_community_deploy_collectibles.go new file mode 100644 index 00000000000..339eb71532c --- /dev/null +++ b/services/wallet/router/pathprocessor/processor_community_deploy_collectibles.go @@ -0,0 +1,126 @@ +package pathprocessor + +import ( + "context" + "math/big" + "strings" + + "go.uber.org/zap" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/status-im/status-go/account" + communitytokens "github.com/status-im/status-go/contracts/community-tokens" + "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/logutils" + "github.com/status-im/status-go/rpc" + walletCommon "github.com/status-im/status-go/services/wallet/common" + pathProcessorCommon "github.com/status-im/status-go/services/wallet/router/pathprocessor/common" + "github.com/status-im/status-go/services/wallet/wallettypes" + "github.com/status-im/status-go/transactions" +) + +type CommunityDeployCollectiblesProcessor struct { + contractMaker *communitytokens.CommunityTokensContractMaker + transactor transactions.TransactorIface +} + +func NewCommunityDeployCollectiblesProcessor(rpcClient *rpc.Client, transactor transactions.TransactorIface) *CommunityDeployCollectiblesProcessor { + return &CommunityDeployCollectiblesProcessor{ + contractMaker: &communitytokens.CommunityTokensContractMaker{ + RPCClient: rpcClient, + }, + transactor: transactor, + } +} + +func createCommunityDeployCollectiblesErrorResponse(err error) error { + return createErrorResponse(pathProcessorCommon.ProcessorCommunityDeployCollectiblesName, err) +} + +func (s *CommunityDeployCollectiblesProcessor) Name() string { + return pathProcessorCommon.ProcessorCommunityDeployCollectiblesName +} + +func (s *CommunityDeployCollectiblesProcessor) AvailableFor(params ProcessorInputParams) (bool, error) { + return true, nil +} + +func (s *CommunityDeployCollectiblesProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { + return walletCommon.ZeroBigIntValue(), walletCommon.ZeroBigIntValue(), nil +} + +func (s *CommunityDeployCollectiblesProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { + collectiblesABI, err := abi.JSON(strings.NewReader(collectibles.CollectiblesABI)) + if err != nil { + return []byte{}, err + } + + data, err := collectiblesABI.Pack("" /*constructor name is empty*/, params.CommunityParams.DeploymentParameters.Name, + params.CommunityParams.DeploymentParameters.Symbol, params.CommunityParams.DeploymentParameters.GetSupply(), + params.CommunityParams.DeploymentParameters.RemoteSelfDestruct, params.CommunityParams.DeploymentParameters.Transferable, + params.CommunityParams.DeploymentParameters.TokenURI, params.CommunityParams.DeploymentParameters.OwnerTokenAddress, + params.CommunityParams.DeploymentParameters.MasterTokenAddress) + if err != nil { + return []byte{}, err + } + + return append(common.FromHex(collectibles.CollectiblesBin), data...), nil +} + +func (s *CommunityDeployCollectiblesProcessor) EstimateGas(params ProcessorInputParams) (uint64, error) { + if params.TestsMode { + return 0, ErrNoEstimationFound + } + + input, err := s.PackTxInputData(params) + if err != nil { + return 0, createCommunityDeployCollectiblesErrorResponse(err) + } + + ethClient, err := s.contractMaker.RPCClient.EthClient(params.FromChain.ChainID) + if err != nil { + return 0, createCommunityDeployCollectiblesErrorResponse(err) + } + + msg := ethereum.CallMsg{ + From: params.FromAddr, + To: nil, + Value: walletCommon.ZeroBigIntValue(), + Data: input, + } + + estimation, err := ethClient.EstimateGas(context.Background(), msg) + if err != nil { + return 0, createCommunityDeployCollectiblesErrorResponse(err) + } + + increasedEstimation := float64(estimation) * pathProcessorCommon.IncreaseEstimatedGasFactor + logutils.ZapLogger().Debug("CommunityDeployCollectiblesProcessor estimation", zap.Uint64("gas", uint64(increasedEstimation))) + + return uint64(increasedEstimation), nil +} + +func (s *CommunityDeployCollectiblesProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) { + return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce, verifiedAccount) +} + +func (s *CommunityDeployCollectiblesProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) +} + +func (s *CommunityDeployCollectiblesProcessor) BuildTransactionV2(sendArgs *wallettypes.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.FromChainID, *sendArgs, lastUsedNonce) +} + +func (s *CommunityDeployCollectiblesProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { + return params.AmountIn, nil +} + +func (s *CommunityDeployCollectiblesProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { + return communitytokendeployer.ContractAddress(params.FromChain.ChainID) +} diff --git a/services/wallet/router/pathprocessor/processor_community_deploy_owner_token.go b/services/wallet/router/pathprocessor/processor_community_deploy_owner_token.go new file mode 100644 index 00000000000..e9a96feecc4 --- /dev/null +++ b/services/wallet/router/pathprocessor/processor_community_deploy_owner_token.go @@ -0,0 +1,191 @@ +package pathprocessor + +import ( + "context" + "fmt" + "math/big" + "strings" + + "go.uber.org/zap" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/status-im/status-go/account" + communitytokens "github.com/status-im/status-go/contracts/community-tokens" + communitytokendeployer "github.com/status-im/status-go/contracts/community-tokens/deployer" + "github.com/status-im/status-go/errors" + "github.com/status-im/status-go/eth-node/crypto" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/logutils" + "github.com/status-im/status-go/rpc" + walletCommon "github.com/status-im/status-go/services/wallet/common" + pathProcessorCommon "github.com/status-im/status-go/services/wallet/router/pathprocessor/common" + "github.com/status-im/status-go/services/wallet/wallettypes" + "github.com/status-im/status-go/transactions" +) + +type CommunityDeployOwnerTokenProcessor struct { + contractMaker *communitytokens.CommunityTokensContractMaker + transactor transactions.TransactorIface +} + +func NewCommunityDeployOwnerTokenProcessor(rpcClient *rpc.Client, transactor transactions.TransactorIface) *CommunityDeployOwnerTokenProcessor { + return &CommunityDeployOwnerTokenProcessor{ + contractMaker: &communitytokens.CommunityTokensContractMaker{ + RPCClient: rpcClient, + }, + transactor: transactor, + } +} + +func createCommunityDeployOwnerTokenErrorResponse(err error) error { + return createErrorResponse(pathProcessorCommon.ProcessorCommunityDeployOwnerTokenName, err) +} + +func (s *CommunityDeployOwnerTokenProcessor) Name() string { + return pathProcessorCommon.ProcessorCommunityDeployOwnerTokenName +} + +func (s *CommunityDeployOwnerTokenProcessor) AvailableFor(params ProcessorInputParams) (bool, error) { + return true, nil +} + +func (s *CommunityDeployOwnerTokenProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { + return walletCommon.ZeroBigIntValue(), walletCommon.ZeroBigIntValue(), nil +} + +func decodeSignature(sig []byte) (r [32]byte, s [32]byte, v uint8, err error) { + if len(sig) != crypto.SignatureLength { + err = &errors.ErrorResponse{ + Code: ErrIncorrectSignatureFormat.Code, + Details: fmt.Sprintf(ErrIncorrectSignatureFormat.Details, len(sig), crypto.SignatureLength), + } + return [32]byte{}, [32]byte{}, 0, err + } + copy(r[:], sig[:32]) + copy(s[:], sig[32:64]) + v = sig[64] + 27 + return r, s, v, nil +} + +func convert33BytesPubKeyToEthAddress(pubKey string) (common.Address, error) { + decoded, err := types.DecodeHex(pubKey) + if err != nil { + return common.Address{}, err + } + communityPubKey, err := crypto.DecompressPubkey(decoded) + if err != nil { + return common.Address{}, err + } + return common.Address(crypto.PubkeyToAddress(*communityPubKey)), nil +} + +func prepareDeploymentSignatureStruct(signature string, communityID string, addressFrom common.Address) (communitytokendeployer.CommunityTokenDeployerDeploymentSignature, error) { + r, s, v, err := decodeSignature(common.FromHex(signature)) + if err != nil { + return communitytokendeployer.CommunityTokenDeployerDeploymentSignature{}, err + } + communityEthAddress, err := convert33BytesPubKeyToEthAddress(communityID) + if err != nil { + return communitytokendeployer.CommunityTokenDeployerDeploymentSignature{}, err + } + communitySignature := communitytokendeployer.CommunityTokenDeployerDeploymentSignature{ + V: v, + R: r, + S: s, + Deployer: addressFrom, + Signer: communityEthAddress, + } + return communitySignature, nil +} + +func (s *CommunityDeployOwnerTokenProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { + deployerABI, err := abi.JSON(strings.NewReader(communitytokendeployer.CommunityTokenDeployerABI)) + if err != nil { + return []byte{}, err + } + + ownerTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{ + Name: params.CommunityParams.OwnerTokenParameters.Name, + Symbol: params.CommunityParams.OwnerTokenParameters.Symbol, + BaseURI: params.CommunityParams.OwnerTokenParameters.TokenURI, + } + + masterTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{ + Name: params.CommunityParams.MasterTokenParameters.Name, + Symbol: params.CommunityParams.MasterTokenParameters.Symbol, + BaseURI: params.CommunityParams.MasterTokenParameters.TokenURI, + } + + communitySignature, err := prepareDeploymentSignatureStruct(params.CommunityParams.TokenDeploymentSignature, + params.CommunityParams.CommunityID, params.FromAddr) + if err != nil { + return []byte{}, err + } + + return deployerABI.Pack("deploy", ownerTokenConfig, masterTokenConfig, communitySignature, common.FromHex(params.CommunityParams.SignerPubKey)) +} + +func (s *CommunityDeployOwnerTokenProcessor) EstimateGas(params ProcessorInputParams) (uint64, error) { + if params.TestsMode { + return 0, ErrNoEstimationFound + } + + contractAddress, err := s.GetContractAddress(params) + if err != nil { + return 0, createCommunityDeployOwnerTokenErrorResponse(err) + } + + input, err := s.PackTxInputData(params) + if err != nil { + return 0, createCommunityDeployOwnerTokenErrorResponse(err) + } + + ethClient, err := s.contractMaker.RPCClient.EthClient(params.FromChain.ChainID) + if err != nil { + return 0, createCommunityDeployOwnerTokenErrorResponse(err) + } + + msg := ethereum.CallMsg{ + From: params.FromAddr, + To: &contractAddress, + Value: walletCommon.ZeroBigIntValue(), + Data: input, + } + + estimation, err := ethClient.EstimateGas(context.Background(), msg) + if err != nil { + return 0, createCommunityDeployOwnerTokenErrorResponse(err) + } + + increasedEstimation := float64(estimation) * pathProcessorCommon.IncreaseEstimatedGasFactor + logutils.ZapLogger().Debug("CommunityDeployOwnerTokenProcessor estimation", zap.Uint64("gas", uint64(increasedEstimation))) + + return uint64(increasedEstimation), nil +} + +func (s *CommunityDeployOwnerTokenProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) { + return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce, verifiedAccount) +} + +func (s *CommunityDeployOwnerTokenProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) +} + +func (s *CommunityDeployOwnerTokenProcessor) BuildTransactionV2(sendArgs *wallettypes.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + tx, n, e := s.transactor.ValidateAndBuildTransaction(sendArgs.FromChainID, *sendArgs, lastUsedNonce) + if e != nil { + return nil, 0, e + } + return tx, n, nil +} + +func (s *CommunityDeployOwnerTokenProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { + return params.AmountIn, nil +} + +func (s *CommunityDeployOwnerTokenProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { + return communitytokendeployer.ContractAddress(params.FromChain.ChainID) +} diff --git a/services/wallet/router/pathprocessor/processor_community_mint_tokens.go b/services/wallet/router/pathprocessor/processor_community_mint_tokens.go new file mode 100644 index 00000000000..093456552c2 --- /dev/null +++ b/services/wallet/router/pathprocessor/processor_community_mint_tokens.go @@ -0,0 +1,155 @@ +package pathprocessor + +import ( + "context" + "math/big" + "strings" + + "go.uber.org/zap" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/status-im/status-go/account" + communitytokens "github.com/status-im/status-go/contracts/community-tokens" + "github.com/status-im/status-go/contracts/community-tokens/assets" + "github.com/status-im/status-go/contracts/community-tokens/collectibles" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/logutils" + "github.com/status-im/status-go/protocol/protobuf" + "github.com/status-im/status-go/rpc" + walletCommon "github.com/status-im/status-go/services/wallet/common" + pathProcessorCommon "github.com/status-im/status-go/services/wallet/router/pathprocessor/common" + "github.com/status-im/status-go/services/wallet/wallettypes" + "github.com/status-im/status-go/transactions" +) + +type CommunityMintTokensProcessor struct { + contractMaker *communitytokens.CommunityTokensContractMaker + transactor transactions.TransactorIface +} + +func NewCommunityMintTokensProcessor(rpcClient *rpc.Client, transactor transactions.TransactorIface) *CommunityMintTokensProcessor { + return &CommunityMintTokensProcessor{ + contractMaker: &communitytokens.CommunityTokensContractMaker{ + RPCClient: rpcClient, + }, + transactor: transactor, + } +} + +func createCommunityMintTokensErrorResponse(err error) error { + return createErrorResponse(pathProcessorCommon.ProcessorCommunityMintTokensName, err) +} + +func (s *CommunityMintTokensProcessor) Name() string { + return pathProcessorCommon.ProcessorCommunityMintTokensName +} + +func (s *CommunityMintTokensProcessor) AvailableFor(params ProcessorInputParams) (bool, error) { + return true, nil +} + +func (s *CommunityMintTokensProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { + return walletCommon.ZeroBigIntValue(), walletCommon.ZeroBigIntValue(), 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 *big.Int, walletAddresses []common.Address) (result []common.Address) { + n := amount.Int64() + for _, address := range walletAddresses { + for i := int64(0); i < n; i++ { + result = append(result, address) + } + } + return +} + +func prepareMintAssetsData(amount *big.Int, walletAddresses []common.Address) (result []*big.Int) { + length := len(walletAddresses) + for i := 0; i < length; i++ { + result = append(result, new(big.Int).Set(amount)) + } + return result +} + +func (s *CommunityMintTokensProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { + switch params.CommunityParams.GetTokenType() { + case protobuf.CommunityTokenType_ERC721: + destinationAddresses := multiplyWalletAddresses(params.CommunityParams.GetAmount(), params.CommunityParams.WalletAddresses) + collectiblesABI, err := abi.JSON(strings.NewReader(collectibles.CollectiblesABI)) + if err != nil { + return []byte{}, err + } + return collectiblesABI.Pack("mintTo", destinationAddresses) + case protobuf.CommunityTokenType_ERC20: + destinationAmounts := prepareMintAssetsData(params.CommunityParams.GetAmount(), params.CommunityParams.WalletAddresses) + assetsABI, err := abi.JSON(strings.NewReader(assets.AssetsABI)) + if err != nil { + return []byte{}, err + } + return assetsABI.Pack("mintTo", params.CommunityParams.WalletAddresses, destinationAmounts) + default: + return nil, ErrCommunityTokenType + } +} + +func (s *CommunityMintTokensProcessor) EstimateGas(params ProcessorInputParams) (uint64, error) { + if params.TestsMode { + return 0, ErrNoEstimationFound + } + + contractAddress, err := s.GetContractAddress(params) + if err != nil { + return 0, createENSReleaseErrorResponse(err) + } + + input, err := s.PackTxInputData(params) + if err != nil { + return 0, createCommunityMintTokensErrorResponse(err) + } + + ethClient, err := s.contractMaker.RPCClient.EthClient(params.FromChain.ChainID) + if err != nil { + return 0, createCommunityMintTokensErrorResponse(err) + } + + msg := ethereum.CallMsg{ + From: params.FromAddr, + To: &contractAddress, + Value: walletCommon.ZeroBigIntValue(), + Data: input, + } + + estimation, err := ethClient.EstimateGas(context.Background(), msg) + if err != nil { + return 0, createCommunityMintTokensErrorResponse(err) + } + + increasedEstimation := float64(estimation) * pathProcessorCommon.IncreaseEstimatedGasFactor + logutils.ZapLogger().Debug("CommunityMintTokensProcessor estimation", zap.Uint64("gas", uint64(increasedEstimation))) + + return uint64(increasedEstimation), nil +} + +func (s *CommunityMintTokensProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) { + return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce, verifiedAccount) +} + +func (s *CommunityMintTokensProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) +} + +func (s *CommunityMintTokensProcessor) BuildTransactionV2(sendArgs *wallettypes.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.FromChainID, *sendArgs, lastUsedNonce) +} + +func (s *CommunityMintTokensProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { + return params.CommunityParams.GetAmount(), nil +} + +func (s *CommunityMintTokensProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { + return params.CommunityParams.GetTokenContractAddress(), nil +} diff --git a/services/wallet/router/pathprocessor/processor_community_remote_burn.go b/services/wallet/router/pathprocessor/processor_community_remote_burn.go new file mode 100644 index 00000000000..84e7370d219 --- /dev/null +++ b/services/wallet/router/pathprocessor/processor_community_remote_burn.go @@ -0,0 +1,120 @@ +package pathprocessor + +import ( + "context" + "math/big" + "strings" + + "go.uber.org/zap" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/status-im/status-go/account" + communitytokens "github.com/status-im/status-go/contracts/community-tokens" + "github.com/status-im/status-go/contracts/community-tokens/collectibles" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/logutils" + "github.com/status-im/status-go/rpc" + walletCommon "github.com/status-im/status-go/services/wallet/common" + pathProcessorCommon "github.com/status-im/status-go/services/wallet/router/pathprocessor/common" + "github.com/status-im/status-go/services/wallet/wallettypes" + "github.com/status-im/status-go/transactions" +) + +type CommunityRemoteBurnProcessor struct { + contractMaker *communitytokens.CommunityTokensContractMaker + transactor transactions.TransactorIface +} + +func NewCommunityRemoteBurnProcessor(rpcClient *rpc.Client, transactor transactions.TransactorIface) *CommunityRemoteBurnProcessor { + return &CommunityRemoteBurnProcessor{ + contractMaker: &communitytokens.CommunityTokensContractMaker{ + RPCClient: rpcClient, + }, + transactor: transactor, + } +} + +func createCommunityRemoteBurnErrorResponse(err error) error { + return createErrorResponse(pathProcessorCommon.ProcessorCommunityRemoteBurnName, err) +} + +func (s *CommunityRemoteBurnProcessor) Name() string { + return pathProcessorCommon.ProcessorCommunityRemoteBurnName +} + +func (s *CommunityRemoteBurnProcessor) AvailableFor(params ProcessorInputParams) (bool, error) { + return true, nil +} + +func (s *CommunityRemoteBurnProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { + return walletCommon.ZeroBigIntValue(), walletCommon.ZeroBigIntValue(), nil +} + +func (s *CommunityRemoteBurnProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { + collectiblesABI, err := abi.JSON(strings.NewReader(collectibles.CollectiblesABI)) + if err != nil { + return []byte{}, createCommunityRemoteBurnErrorResponse(err) + } + var tokenIds []*big.Int + for _, tokenId := range params.CommunityParams.TokenIds { + tokenIds = append(tokenIds, tokenId.ToInt()) + } + return collectiblesABI.Pack("remoteBurn", tokenIds) +} + +func (s *CommunityRemoteBurnProcessor) EstimateGas(params ProcessorInputParams) (uint64, error) { + if params.TestsMode { + return 0, ErrNoEstimationFound + } + + input, err := s.PackTxInputData(params) + if err != nil { + return 0, createCommunityRemoteBurnErrorResponse(err) + } + + ethClient, err := s.contractMaker.RPCClient.EthClient(params.FromChain.ChainID) + if err != nil { + return 0, createCommunityRemoteBurnErrorResponse(err) + } + + toAddress := params.CommunityParams.GetTokenContractAddress() + msg := ethereum.CallMsg{ + From: params.FromAddr, + To: &toAddress, + Value: walletCommon.ZeroBigIntValue(), + Data: input, + } + + estimation, err := ethClient.EstimateGas(context.Background(), msg) + if err != nil { + return 0, createCommunityRemoteBurnErrorResponse(err) + } + + increasedEstimation := float64(estimation) * pathProcessorCommon.IncreaseEstimatedGasFactor + logutils.ZapLogger().Debug("CommunityRemoteBurnProcessor estimation", zap.Uint64("gas", uint64(increasedEstimation))) + + return uint64(increasedEstimation), nil +} + +func (s *CommunityRemoteBurnProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) { + return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce, verifiedAccount) +} + +func (s *CommunityRemoteBurnProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) +} + +func (s *CommunityRemoteBurnProcessor) BuildTransactionV2(sendArgs *wallettypes.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.FromChainID, *sendArgs, lastUsedNonce) +} + +func (s *CommunityRemoteBurnProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { + return params.CommunityParams.GetAmount(), nil +} + +func (s *CommunityRemoteBurnProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { + return params.CommunityParams.GetTokenContractAddress(), nil +} diff --git a/services/wallet/router/pathprocessor/processor_community_set_signer_pub_key.go b/services/wallet/router/pathprocessor/processor_community_set_signer_pub_key.go new file mode 100644 index 00000000000..605131a48db --- /dev/null +++ b/services/wallet/router/pathprocessor/processor_community_set_signer_pub_key.go @@ -0,0 +1,117 @@ +package pathprocessor + +import ( + "context" + "math/big" + "strings" + + "go.uber.org/zap" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/status-im/status-go/account" + communitytokens "github.com/status-im/status-go/contracts/community-tokens" + "github.com/status-im/status-go/contracts/community-tokens/ownertoken" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/logutils" + "github.com/status-im/status-go/rpc" + walletCommon "github.com/status-im/status-go/services/wallet/common" + pathProcessorCommon "github.com/status-im/status-go/services/wallet/router/pathprocessor/common" + "github.com/status-im/status-go/services/wallet/wallettypes" + "github.com/status-im/status-go/transactions" +) + +type CommunitySetSignerPubKeyProcessor struct { + contractMaker *communitytokens.CommunityTokensContractMaker + transactor transactions.TransactorIface +} + +func NewCommunitySetSignerPubKeyProcessor(rpcClient *rpc.Client, transactor transactions.TransactorIface) *CommunitySetSignerPubKeyProcessor { + return &CommunitySetSignerPubKeyProcessor{ + contractMaker: &communitytokens.CommunityTokensContractMaker{ + RPCClient: rpcClient, + }, + transactor: transactor, + } +} + +func createCommunitySetSignerPubKeyErrorResponse(err error) error { + return createErrorResponse(pathProcessorCommon.ProcessorCommunitySetSignerPubKeyName, err) +} + +func (s *CommunitySetSignerPubKeyProcessor) Name() string { + return pathProcessorCommon.ProcessorCommunitySetSignerPubKeyName +} + +func (s *CommunitySetSignerPubKeyProcessor) AvailableFor(params ProcessorInputParams) (bool, error) { + return true, nil +} + +func (s *CommunitySetSignerPubKeyProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { + return walletCommon.ZeroBigIntValue(), walletCommon.ZeroBigIntValue(), nil +} + +func (s *CommunitySetSignerPubKeyProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { + ownerTokenABI, err := abi.JSON(strings.NewReader(ownertoken.OwnerTokenABI)) + if err != nil { + return []byte{}, err + } + + return ownerTokenABI.Pack("setSignerPublicKey", common.FromHex(params.CommunityParams.SignerPubKey)) +} + +func (s *CommunitySetSignerPubKeyProcessor) EstimateGas(params ProcessorInputParams) (uint64, error) { + if params.TestsMode { + return 0, ErrNoEstimationFound + } + + input, err := s.PackTxInputData(params) + if err != nil { + return 0, createCommunitySetSignerPubKeyErrorResponse(err) + } + + ethClient, err := s.contractMaker.RPCClient.EthClient(params.FromChain.ChainID) + if err != nil { + return 0, createCommunitySetSignerPubKeyErrorResponse(err) + } + + toAddress := params.CommunityParams.GetTokenContractAddress() + msg := ethereum.CallMsg{ + From: params.FromAddr, + To: &toAddress, + Value: walletCommon.ZeroBigIntValue(), + Data: input, + } + + estimation, err := ethClient.EstimateGas(context.Background(), msg) + if err != nil { + return 0, createCommunitySetSignerPubKeyErrorResponse(err) + } + + increasedEstimation := float64(estimation) * pathProcessorCommon.IncreaseEstimatedGasFactor + logutils.ZapLogger().Debug("CommunitySetSignerPubKeyProcessor estimation", zap.Uint64("gas", uint64(increasedEstimation))) + + return uint64(increasedEstimation), nil +} + +func (s *CommunitySetSignerPubKeyProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, usedNonce uint64, err error) { + return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce, verifiedAccount) +} + +func (s *CommunitySetSignerPubKeyProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, *sendArgs.TransferTx, lastUsedNonce) +} + +func (s *CommunitySetSignerPubKeyProcessor) BuildTransactionV2(sendArgs *wallettypes.SendTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.FromChainID, *sendArgs, lastUsedNonce) +} + +func (s *CommunitySetSignerPubKeyProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { + return params.AmountIn, nil +} + +func (s *CommunitySetSignerPubKeyProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { + return params.CommunityParams.GetTokenContractAddress(), nil +} diff --git a/services/wallet/router/router.go b/services/wallet/router/router.go index 843f0a6f5af..e69d8b75be0 100644 --- a/services/wallet/router/router.go +++ b/services/wallet/router/router.go @@ -207,6 +207,12 @@ func newSuggestedRoutes( return suggestedRoutes, nil } + if input.SendType.IsCommunityRelatedTransfer() { + res := make([]routes.Route, 0) + res = append(res, candidates) + return suggestedRoutes, res + } + node := &routes.Node{ Path: nil, Children: routes.BuildGraph(input.AmountIn.ToInt(), candidates, 0, []uint64{}), @@ -633,6 +639,85 @@ func (r *Router) getSelectedChains(input *requests.RouteInputParams) (selectedFr return selectedFromChains, selectedToChains, nil } +func (r *Router) CreateProcessorInputParams(input *requests.RouteInputParams, fromNetwork *params.Network, toNetwork *params.Network, + fromToken *token.Token, toToken *token.Token, amountIn *big.Int, slippagePercentage float32, + useCommunityTokenTransferDetailsAtIndex int) (pathprocessor.ProcessorInputParams, error) { + var err error + processorInputParams := pathprocessor.ProcessorInputParams{ + FromChain: fromNetwork, + ToChain: toNetwork, + FromToken: fromToken, + ToToken: toToken, + ToAddr: input.AddrTo, + FromAddr: input.AddrFrom, + AmountIn: amountIn, + SlippagePercentage: slippagePercentage, + + Username: input.Username, + PublicKey: input.PublicKey, + PackID: input.PackID.ToInt(), + } + + if input.AmountOut != nil { + processorInputParams.AmountOut = input.AmountOut.ToInt() + } + + if input.PackID != nil { + processorInputParams.PackID = input.PackID.ToInt() + } + + if input.SendType.IsCommunityRelatedTransfer() { + processorInputParams.CommunityParams = input.CommunityRouteInputParams + + if input.CommunityRouteInputParams.UseTransferDetails() && fromNetwork != nil { + tokenContractAddress := input.CommunityRouteInputParams.TransferDetails[useCommunityTokenTransferDetailsAtIndex].TokenContractAddress + tokenType, err := r.tokenManager.GetCommunityTokenType(fromNetwork.ChainID, tokenContractAddress.String()) + if err != nil { + return processorInputParams, err + } + + privilegeLevel, err := r.tokenManager.GetCommunityTokenPrivilegesLevel(fromNetwork.ChainID, tokenContractAddress.String()) + if err != nil { + return processorInputParams, err + } + + input.CommunityRouteInputParams.TransferDetails[useCommunityTokenTransferDetailsAtIndex].TokenType = tokenType + input.CommunityRouteInputParams.TransferDetails[useCommunityTokenTransferDetailsAtIndex].PrivilegeLevel = privilegeLevel + + err = input.CommunityRouteInputParams.SetInternalParams(useCommunityTokenTransferDetailsAtIndex) + if err != nil { + return processorInputParams, err + } + } + } + + if input.TestsMode { + processorInputParams.TestsMode = input.TestsMode + processorInputParams.TestEstimationMap = input.TestParams.EstimationMap + processorInputParams.TestBonderFeeMap = input.TestParams.BonderFeeMap + processorInputParams.TestApprovalGasEstimation = input.TestParams.ApprovalGasEstimation + processorInputParams.TestApprovalL1Fee = input.TestParams.ApprovalL1Fee + } + + return processorInputParams, err +} + +func (r *Router) findFromAndToTokens(testsMode bool, input *requests.RouteInputParams, network *params.Network) (fromToken *walletToken.Token, toToken *walletToken.Token) { + if testsMode { + fromToken = input.TestParams.TokenFrom + } else { + fromToken = findToken(input.SendType, r.tokenManager, r.collectiblesService, input.AddrFrom, network, input.TokenID) + } + if fromToken == nil { + return + } + + if input.SendType == sendtype.Swap { + toToken = findToken(input.SendType, r.tokenManager, r.collectiblesService, common.Address{}, network, input.ToTokenID) + } + return +} + func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInputParams, selectedFromChains []*params.Network, selectedToChains []*params.Network) (candidates routes.Route, processorErrors []*ProcessorError, err error) { var ( @@ -679,24 +764,11 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp continue } - var ( - token *walletToken.Token - toToken *walletToken.Token - ) - - if testsMode { - token = input.TestParams.TokenFrom - } else { - token = findToken(input.SendType, r.tokenManager, r.collectiblesService, input.AddrFrom, network, input.TokenID) - } + token, toToken := r.findFromAndToTokens(testsMode, input, network) if token == nil { continue } - if input.SendType == sendtype.Swap { - toToken = findToken(input.SendType, r.tokenManager, r.collectiblesService, common.Address{}, network, input.ToTokenID) - } - var fetchedFees *fees.SuggestedFees if testsMode { fetchedFees = input.TestParams.SuggestedFees @@ -741,120 +813,29 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp for _, dest := range selectedToChains { - if !input.SendType.IsAvailableFor(network) { - continue - } - - if !input.SendType.IsAvailableBetween(network, dest) { - continue - } - - processorInputParams := pathprocessor.ProcessorInputParams{ - FromChain: network, - ToChain: dest, - FromToken: token, - ToToken: toToken, - ToAddr: input.AddrTo, - FromAddr: input.AddrFrom, - AmountIn: amountOption.amount, - AmountOut: input.AmountOut.ToInt(), - - Username: input.Username, - PublicKey: input.PublicKey, - PackID: input.PackID.ToInt(), - } - if input.TestsMode { - processorInputParams.TestsMode = input.TestsMode - processorInputParams.TestEstimationMap = input.TestParams.EstimationMap - processorInputParams.TestBonderFeeMap = input.TestParams.BonderFeeMap - processorInputParams.TestApprovalGasEstimation = input.TestParams.ApprovalGasEstimation - processorInputParams.TestApprovalL1Fee = input.TestParams.ApprovalL1Fee - } - - can, err := pProcessor.AvailableFor(processorInputParams) - if err != nil { - appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) - continue - } - if !can { - continue - } - - bonderFees, tokenFees, err := pProcessor.CalculateFees(processorInputParams) - if err != nil { - appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) - continue - } - - gasLimit, err := pProcessor.EstimateGas(processorInputParams) - if err != nil { - appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) - continue - } - - approvalContractAddress, err := pProcessor.GetContractAddress(processorInputParams) - if err != nil { - appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) - continue - } - approvalRequired, approvalAmountRequired, err := r.requireApproval(ctx, input.SendType, &approvalContractAddress, processorInputParams) - if err != nil { - appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) - continue - } - - var approvalGasLimit uint64 - if approvalRequired { - if processorInputParams.TestsMode { - approvalGasLimit = processorInputParams.TestApprovalGasEstimation - } else { - approvalGasLimit, err = r.estimateGasForApproval(processorInputParams, &approvalContractAddress) + if input.UseCommunityTransferDetails() { + for i := 0; i < len(input.CommunityRouteInputParams.TransferDetails); i++ { + usedNoncesMu.Lock() + path, err := r.buildPath(ctx, input, network, dest, token, toToken, amountOption, pProcessor, fetchedFees, usedNonces, i) + usedNoncesMu.Unlock() if err != nil { - appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) + appendProcessorErrorFn(pProcessor.Name(), input.SendType, network.ChainID, dest.ChainID, amountOption.amount, err) continue } - } - } - - amountOut, err := pProcessor.CalculateAmountOut(processorInputParams) - if err != nil { - appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) - continue - } - path := &routes.Path{ - RouterInputParamsUuid: input.Uuid, - ProcessorName: pProcessor.Name(), - FromChain: network, - ToChain: dest, - FromToken: token, - ToToken: toToken, - AmountIn: (*hexutil.Big)(amountOption.amount), - AmountInLocked: amountOption.locked, - AmountOut: (*hexutil.Big)(amountOut), - - // set params that we don't want to be recalculated with every new block creation - TxGasAmount: gasLimit, - TxBonderFees: (*hexutil.Big)(bonderFees), - TxTokenFees: (*hexutil.Big)(tokenFees), - - ApprovalRequired: approvalRequired, - ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired), - ApprovalContractAddress: &approvalContractAddress, - ApprovalGasAmount: approvalGasLimit, - - SubtractFees: amountOption.subtractFees, - } + appendPathFn(path) + } + } else { + usedNoncesMu.Lock() + path, err := r.buildPath(ctx, input, network, dest, token, toToken, amountOption, pProcessor, fetchedFees, usedNonces, 0) + usedNoncesMu.Unlock() + if err != nil { + appendProcessorErrorFn(pProcessor.Name(), input.SendType, network.ChainID, dest.ChainID, amountOption.amount, err) + continue + } - usedNoncesMu.Lock() - err = r.evaluateAndUpdatePathDetails(ctx, path, fetchedFees, usedNonces, processorInputParams.TestsMode, processorInputParams.TestApprovalL1Fee) - usedNoncesMu.Unlock() - if err != nil { - appendProcessorErrorFn(pProcessor.Name(), input.SendType, processorInputParams.FromChain.ChainID, processorInputParams.ToChain.ChainID, processorInputParams.AmountIn, err) - continue + appendPathFn(path) } - - appendPathFn(path) } } } @@ -872,6 +853,112 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp return candidates, processorErrors, nil } +func (r *Router) buildPath(ctx context.Context, input *requests.RouteInputParams, fromNetwork *params.Network, + toNetwork *params.Network, fromToken *token.Token, toToken *token.Token, amountOption amountOption, + pathProcessor pathprocessor.PathProcessor, fetchedFees *fees.SuggestedFees, usedNonces map[uint64]uint64, + useCommunityTokenTransferDetailsAtIndex int) (*routes.Path, error) { + if !input.SendType.IsAvailableFor(fromNetwork) { + return nil, ErrPathNotSupportedForProvidedChain + } + + if !input.SendType.IsAvailableBetween(fromNetwork, toNetwork) { + return nil, ErrPathNotSupportedBetweenProvidedChains + } + + processorInputParams, err := r.CreateProcessorInputParams(input, fromNetwork, toNetwork, fromToken, toToken, amountOption.amount, 0, useCommunityTokenTransferDetailsAtIndex) + if err != nil { + return nil, err + } + + can, err := pathProcessor.AvailableFor(processorInputParams) + if err != nil { + return nil, err + } + if !can { + return nil, ErrPathNotAvaliableForProvidedParameters + } + + bonderFees, tokenFees, err := pathProcessor.CalculateFees(processorInputParams) + if err != nil { + return nil, err + } + + gasLimit, err := pathProcessor.EstimateGas(processorInputParams) + if err != nil { + return nil, err + } + + contractAddress, err := pathProcessor.GetContractAddress(processorInputParams) + if err != nil { + return nil, err + } + approvalRequired, approvalAmountRequired, err := r.requireApproval(ctx, input.SendType, &contractAddress, processorInputParams) + if err != nil { + return nil, err + } + + var approvalGasLimit uint64 + if approvalRequired { + if processorInputParams.TestsMode { + approvalGasLimit = processorInputParams.TestApprovalGasEstimation + } else { + approvalGasLimit, err = r.estimateGasForApproval(processorInputParams, &contractAddress) + if err != nil { + return nil, err + } + } + } + + amountOut, err := pathProcessor.CalculateAmountOut(processorInputParams) + if err != nil { + return nil, err + } + + path := &routes.Path{ + ProcessorName: pathProcessor.Name(), + FromChain: fromNetwork, + ToChain: toNetwork, + FromToken: fromToken, + ToToken: toToken, + AmountIn: (*hexutil.Big)(amountOption.amount), + AmountInLocked: amountOption.locked, + AmountOut: (*hexutil.Big)(amountOut), + + // set params that we don't want to be recalculated with every new block creation + UsedContractAddress: &contractAddress, + TxGasAmount: gasLimit, + TxBonderFees: (*hexutil.Big)(bonderFees), + TxTokenFees: (*hexutil.Big)(tokenFees), + + ApprovalRequired: approvalRequired, + ApprovalAmountRequired: (*hexutil.Big)(approvalAmountRequired), + ApprovalContractAddress: &contractAddress, + ApprovalGasAmount: approvalGasLimit, + + SubtractFees: amountOption.subtractFees, + } + + if input.SendType.IsCommunityRelatedTransfer() { + // set community params copy as community params for the path instance + communityParams := processorInputParams.CommunityParams.Copy() + if input.UseCommunityTransferDetails() { + // in case of multi token community transfer we need to set the internal params to refer to the correct token + err = communityParams.SetInternalParams(useCommunityTokenTransferDetailsAtIndex) + if err != nil { + return nil, err + } + } + path.SetCommunityParams(communityParams) + } + + err = r.evaluateAndUpdatePathDetails(ctx, path, fetchedFees, usedNonces, processorInputParams.TestsMode, processorInputParams.TestApprovalL1Fee) + if err != nil { + return nil, err + } + + return path, nil +} + func (r *Router) checkBalancesForTheBestRoute(ctx context.Context, bestRoute routes.Route) (hasPositiveBalance bool, err error) { // make a copy of the active balance map balanceMapCopy := make(map[string]*big.Int) diff --git a/services/wallet/router/router_helper.go b/services/wallet/router/router_helper.go index bc168922ccf..a825c0d13d7 100644 --- a/services/wallet/router/router_helper.go +++ b/services/wallet/router/router_helper.go @@ -381,6 +381,11 @@ func findToken(sendType sendtype.SendType, tokenManager *token.Manager, collecti return tokenManager.FindToken(network, tokenID) } + if sendType.IsCommunityRelatedTransfer() { + // TODO: optimize tokens to handle community tokens + return nil + } + contractAddress, collectibleTokenID, success := ParseCollectibleID(tokenID) if !success { return nil diff --git a/services/wallet/router/routes/router_path.go b/services/wallet/router/routes/router_path.go index 2ac001ee21e..de77e8eff8e 100644 --- a/services/wallet/router/routes/router_path.go +++ b/services/wallet/router/routes/router_path.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/status-im/status-go/params" + "github.com/status-im/status-go/services/wallet/requests" "github.com/status-im/status-go/services/wallet/router/fees" walletToken "github.com/status-im/status-go/services/wallet/token" ) @@ -26,6 +27,7 @@ type Path struct { SuggestedMinPriorityFee *hexutil.Big // Suggested min priority fee by the network (in ETH WEI) SuggestedMaxPriorityFee *hexutil.Big // Suggested max priority fee by the network (in ETH WEI) CurrentBaseFee *hexutil.Big // Current network base fee (in ETH WEI) + UsedContractAddress *common.Address // Address of the contract that will be used for the transaction TxNonce *hexutil.Uint64 // Nonce for the transaction TxMaxFeesPerGas *hexutil.Big // Max fees per gas (determined by client via GasFeeMode, in ETH WEI) @@ -41,7 +43,7 @@ type Path struct { ApprovalRequired bool // Is approval required for the transaction ApprovalAmountRequired *hexutil.Big // Amount required for the approval transaction - ApprovalContractAddress *common.Address // Address of the contract that needs to be approved + ApprovalContractAddress *common.Address // Address of the contract that will be used for the approval transaction, the same as UsedContractAddress. We can remove this field and use UsedContractAddress instead. ApprovalTxNonce *hexutil.Uint64 // Nonce for the transaction ApprovalMaxFeesPerGas *hexutil.Big // Max fees per gas (determined by client via GasFeeMode, in ETH WEI) ApprovalBaseFee *hexutil.Big // Base fee for the approval transaction (in ETH WEI) @@ -57,10 +59,17 @@ type Path struct { RequiredTokenBalance *big.Int // (in selected token) RequiredNativeBalance *big.Int // (in ETH WEI) SubtractFees bool + + // used internally + communityParams *requests.CommunityRouteInputParams } func (p *Path) PathIdentity() string { - return fmt.Sprintf("%s-%s-%d", p.RouterInputParamsUuid, p.ProcessorName, p.FromChain.ChainID) + var communityID string + if p.communityParams != nil { + communityID = p.communityParams.ID() + } + return fmt.Sprintf("%s-%s-%d-%s", p.RouterInputParamsUuid, p.ProcessorName, p.FromChain.ChainID, communityID) } func (p *Path) TxIdentityKey(approval bool) string { @@ -71,6 +80,14 @@ func (p *Path) Equal(o *Path) bool { return p.FromChain.ChainID == o.FromChain.ChainID && p.ToChain.ChainID == o.ToChain.ChainID } +func (p *Path) SetCommunityParams(params *requests.CommunityRouteInputParams) { + p.communityParams = params +} + +func (p *Path) GetCommunityParams() *requests.CommunityRouteInputParams { + return p.communityParams +} + func (p *Path) Copy() *Path { newPath := &Path{ RouterInputParamsUuid: p.RouterInputParamsUuid, @@ -141,6 +158,11 @@ func (p *Path) Copy() *Path { newPath.TxMaxFeesPerGas = (*hexutil.Big)(big.NewInt(0).Set(p.TxMaxFeesPerGas.ToInt())) } + if p.UsedContractAddress != nil { + addr := common.HexToAddress(p.UsedContractAddress.Hex()) + newPath.UsedContractAddress = &addr + } + if p.TxBaseFee != nil { newPath.TxBaseFee = (*hexutil.Big)(big.NewInt(0).Set(p.TxBaseFee.ToInt())) } @@ -211,5 +233,9 @@ func (p *Path) Copy() *Path { newPath.RequiredNativeBalance = big.NewInt(0).Set(p.RequiredNativeBalance) } + if p.communityParams != nil { + newPath.communityParams = p.communityParams.Copy() + } + return newPath } diff --git a/services/wallet/router/sendtype/send_type.go b/services/wallet/router/sendtype/send_type.go index cf17007b8f1..1a979506449 100644 --- a/services/wallet/router/sendtype/send_type.go +++ b/services/wallet/router/sendtype/send_type.go @@ -20,6 +20,13 @@ const ( ERC721Transfer ERC1155Transfer Swap + CommunityBurn + CommunityDeployAssets + CommunityDeployCollectibles + CommunityDeployOwnerToken + CommunityMintTokens + CommunityRemoteBurn + CommunitySetSignerPubKey ) func (s SendType) IsCollectiblesTransfer() bool { @@ -34,6 +41,11 @@ func (s SendType) IsStickersTransfer() bool { return s == StickersBuy } +func (s SendType) IsCommunityRelatedTransfer() bool { + return s == CommunityDeployOwnerToken || s == CommunityDeployCollectibles || s == CommunityDeployAssets || + s == CommunityMintTokens || s == CommunityRemoteBurn || s == CommunityBurn || s == CommunitySetSignerPubKey +} + // canUseProcessor is used to check if certain SendType can be used with a given path processor func (s SendType) CanUseProcessor(pathProcessorName string) bool { switch s { @@ -56,6 +68,20 @@ func (s SendType) CanUseProcessor(pathProcessorName string) bool { return pathProcessorName == pathProcessorCommon.ProcessorENSPublicKeyName case StickersBuy: return pathProcessorName == pathProcessorCommon.ProcessorStickersBuyName + case CommunityBurn: + return pathProcessorName == pathProcessorCommon.ProcessorCommunityBurnName + case CommunityDeployAssets: + return pathProcessorName == pathProcessorCommon.ProcessorCommunityDeployAssetsName + case CommunityDeployCollectibles: + return pathProcessorName == pathProcessorCommon.ProcessorCommunityDeployCollectiblesName + case CommunityDeployOwnerToken: + return pathProcessorName == pathProcessorCommon.ProcessorCommunityDeployOwnerTokenName + case CommunityMintTokens: + return pathProcessorName == pathProcessorCommon.ProcessorCommunityMintTokensName + case CommunityRemoteBurn: + return pathProcessorName == pathProcessorCommon.ProcessorCommunityRemoteBurnName + case CommunitySetSignerPubKey: + return pathProcessorName == pathProcessorCommon.ProcessorCommunitySetSignerPubKeyName default: return true } @@ -71,6 +97,8 @@ func (s SendType) ProcessZeroAmountInProcessor(amountIn *big.Int, amountOut *big if amountOut.Cmp(walletCommon.ZeroBigIntValue()) == 0 { return false } + } else if s.IsCommunityRelatedTransfer() { + return true } else if s != ENSRelease { return false } @@ -83,6 +111,7 @@ func (s SendType) IsAvailableBetween(from, to *params.Network) bool { if s.IsCollectiblesTransfer() || s.IsEnsTransfer() || s.IsStickersTransfer() || + s.IsCommunityRelatedTransfer() || s == Swap { return from.ChainID == to.ChainID } @@ -95,32 +124,20 @@ func (s SendType) IsAvailableBetween(from, to *params.Network) bool { } func (s SendType) IsAvailableFor(network *params.Network) bool { - // Set of network ChainIDs allowed for any type of transaction - allAllowedNetworks := map[uint64]bool{ - walletCommon.EthereumMainnet: true, - walletCommon.EthereumSepolia: true, - } - - // Additional specific networks for the Swap SendType - swapAllowedNetworks := map[uint64]bool{ - walletCommon.EthereumMainnet: true, - walletCommon.OptimismMainnet: true, - walletCommon.ArbitrumMainnet: true, - } - // Check for Swap specific networks if s == Swap { - return swapAllowedNetworks[network.ChainID] + swapAllowedNetworks := map[uint64]bool{ + walletCommon.EthereumMainnet: true, + walletCommon.OptimismMainnet: true, + walletCommon.ArbitrumMainnet: true, + } + _, ok := swapAllowedNetworks[network.ChainID] + return ok } if s.IsEnsTransfer() || s.IsStickersTransfer() { return network.ChainID == walletCommon.EthereumMainnet || network.ChainID == walletCommon.EthereumSepolia } - // Check for any SendType available for all networks - if s == Transfer || s == Bridge || s.IsCollectiblesTransfer() || allAllowedNetworks[network.ChainID] { - return true - } - - return false + return true } diff --git a/services/wallet/service.go b/services/wallet/service.go index 3b5abebd404..0f464f92c05 100644 --- a/services/wallet/service.go +++ b/services/wallet/service.go @@ -280,6 +280,27 @@ func buildPathProcessors( buyStickers := pathprocessor.NewStickersBuyProcessor(rpcClient, transactor) ret = append(ret, buyStickers) + communityBurn := pathprocessor.NewCommunityBurnProcessor(rpcClient, transactor) + ret = append(ret, communityBurn) + + communityDeployAssets := pathprocessor.NewCommunityDeployAssetsProcessor(rpcClient, transactor) + ret = append(ret, communityDeployAssets) + + communityDeployCollectibles := pathprocessor.NewCommunityDeployCollectiblesProcessor(rpcClient, transactor) + ret = append(ret, communityDeployCollectibles) + + communityDeployOwnerToken := pathprocessor.NewCommunityDeployOwnerTokenProcessor(rpcClient, transactor) + ret = append(ret, communityDeployOwnerToken) + + communityMintTokens := pathprocessor.NewCommunityMintTokensProcessor(rpcClient, transactor) + ret = append(ret, communityMintTokens) + + communityRemoteBurn := pathprocessor.NewCommunityRemoteBurnProcessor(rpcClient, transactor) + ret = append(ret, communityRemoteBurn) + + communitySetSignerPubKey := pathprocessor.NewCommunitySetSignerPubKeyProcessor(rpcClient, transactor) + ret = append(ret, communitySetSignerPubKey) + return ret } diff --git a/services/wallet/token/token.go b/services/wallet/token/token.go index fa47b7b565f..9e3f77a7fa1 100644 --- a/services/wallet/token/token.go +++ b/services/wallet/token/token.go @@ -28,6 +28,7 @@ import ( "github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/params" "github.com/status-im/status-go/protocol/communities/token" + "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/rpc" "github.com/status-im/status-go/rpc/network" "github.com/status-im/status-go/server" @@ -623,6 +624,20 @@ func (tm *Manager) DiscoverToken(ctx context.Context, chainID uint64, address co }, nil } +func (tm *Manager) GetCommunityTokenType(chainID uint64, tokenContractAddress string) (protobuf.CommunityTokenType, error) { + if tm.communityTokensDB != nil { + return tm.communityTokensDB.GetTokenType(chainID, tokenContractAddress) + } + return protobuf.CommunityTokenType_UNKNOWN_TOKEN_TYPE, nil +} + +func (tm *Manager) GetCommunityTokenPrivilegesLevel(chainID uint64, tokenContractAddress string) (token.PrivilegesLevel, error) { + if tm.communityTokensDB != nil { + return tm.communityTokensDB.GetTokenPrivilegesLevel(chainID, tokenContractAddress) + } + return token.CommunityLevel, nil +} + func (tm *Manager) getTokensFromDB(query string, args ...any) ([]*Token, error) { communityTokens := []*token.CommunityToken{} if tm.communityTokensDB != nil { diff --git a/services/wallet/transfer/transaction_manager_route.go b/services/wallet/transfer/transaction_manager_route.go index 190aaa65bf4..4eed14f6500 100644 --- a/services/wallet/transfer/transaction_manager_route.go +++ b/services/wallet/transfer/transaction_manager_route.go @@ -22,15 +22,6 @@ import ( "github.com/status-im/status-go/transactions" ) -type BuildRouteExtraParams struct { - AddressFrom common.Address - AddressTo common.Address - Username string - PublicKey string - PackID *big.Int - SlippagePercentage float32 -} - func (tm *TransactionManager) ClearLocalRouterTransactionsData() { tm.routerTransactions = nil } @@ -127,39 +118,32 @@ func buildApprovalTxForPath(transactor transactions.TransactorIface, path *route } func buildTxForPath(path *routes.Path, pathProcessors map[string]pathprocessor.PathProcessor, - usedNonces map[uint64]int64, signer ethTypes.Signer, params BuildRouteExtraParams) (*wallettypes.TransactionData, error) { + usedNonces map[uint64]int64, signer ethTypes.Signer, processorInputParams *pathprocessor.ProcessorInputParams) (*wallettypes.TransactionData, error) { lastUsedNonce := int64(-1) if nonce, ok := usedNonces[path.FromChain.ChainID]; ok { lastUsedNonce = nonce } - processorInputParams := pathprocessor.ProcessorInputParams{ - FromAddr: params.AddressFrom, - ToAddr: params.AddressTo, - FromChain: path.FromChain, - ToChain: path.ToChain, - FromToken: path.FromToken, - ToToken: path.ToToken, - AmountIn: path.AmountIn.ToInt(), - AmountOut: path.AmountOut.ToInt(), - - Username: params.Username, - PublicKey: params.PublicKey, - PackID: params.PackID, - } - - data, err := pathProcessors[path.ProcessorName].PackTxInputData(processorInputParams) + // update processor input params for the current path + processorInputParams.FromChain = path.FromChain + processorInputParams.ToChain = path.ToChain + processorInputParams.FromToken = path.FromToken + processorInputParams.ToToken = path.ToToken + processorInputParams.AmountIn = path.AmountIn.ToInt() + processorInputParams.AmountOut = path.AmountOut.ToInt() + // update porcessor input community related params + processorInputParams.CommunityParams = path.GetCommunityParams() + + data, err := pathProcessors[path.ProcessorName].PackTxInputData(*processorInputParams) if err != nil { return nil, err } - addrTo := types.Address(params.AddressTo) sendArgs := &wallettypes.SendTxArgs{ Version: wallettypes.SendTxArgsVersion1, // tx fields - From: types.Address(params.AddressFrom), - To: &addrTo, + From: types.Address(processorInputParams.FromAddr), Value: path.AmountIn, Data: data, Nonce: path.TxNonce, @@ -172,8 +156,16 @@ func buildTxForPath(path *routes.Path, pathProcessors map[string]pathprocessor.P ValueOut: path.AmountOut, FromChainID: path.FromChain.ChainID, ToChainID: path.ToChain.ChainID, - SlippagePercentage: params.SlippagePercentage, + SlippagePercentage: processorInputParams.SlippagePercentage, } + + isContractDeployment := path.ProcessorName == pathProcessorCommon.ProcessorCommunityDeployCollectiblesName || + path.ProcessorName == pathProcessorCommon.ProcessorCommunityDeployAssetsName + if !isContractDeployment { + addrTo := types.Address(processorInputParams.ToAddr) + sendArgs.To = &addrTo + } + if path.FromToken != nil { sendArgs.FromTokenID = path.FromToken.Symbol sendArgs.ToContractAddress = types.Address(path.FromToken.Address) @@ -195,6 +187,14 @@ func buildTxForPath(path *routes.Path, pathProcessors map[string]pathprocessor.P toContractAddr := types.Address(path.FromToken.Address) sendArgs.To = &toContractAddr } + } else if path.ProcessorName == pathProcessorCommon.ProcessorCommunityDeployOwnerTokenName || // special handling for community related txs, tokenID for those txs is ETH + path.ProcessorName == pathProcessorCommon.ProcessorCommunityMintTokensName || + path.ProcessorName == pathProcessorCommon.ProcessorCommunityRemoteBurnName || + path.ProcessorName == pathProcessorCommon.ProcessorCommunityBurnName || + path.ProcessorName == pathProcessorCommon.ProcessorCommunitySetSignerPubKeyName { + toContractAddr := types.Address(*path.UsedContractAddress) + sendArgs.To = &toContractAddr + sendArgs.ToContractAddress = toContractAddr } } if path.ToToken != nil { @@ -216,12 +216,12 @@ func buildTxForPath(path *routes.Path, pathProcessors map[string]pathprocessor.P } func (tm *TransactionManager) BuildTransactionsFromRoute(route routes.Route, pathProcessors map[string]pathprocessor.PathProcessor, - params BuildRouteExtraParams) (*responses.SigningDetails, uint64, uint64, error) { + processorInputParams *pathprocessor.ProcessorInputParams) (*responses.SigningDetails, uint64, uint64, error) { if len(route) == 0 { return nil, 0, 0, ErrNoRoute } - accFrom, err := tm.accountsDB.GetAccountByAddress(types.Address(params.AddressFrom)) + accFrom, err := tm.accountsDB.GetAccountByAddress(types.Address(processorInputParams.FromAddr)) if err != nil { return nil, 0, 0, err } @@ -246,7 +246,7 @@ func (tm *TransactionManager) BuildTransactionsFromRoute(route routes.Route, pat // always check for approval tx first for the path and build it if needed if path.ApprovalRequired && !tm.ApprovalPlacedForPath(path.ProcessorName) { - txDetails.ApprovalTxData, err = buildApprovalTxForPath(tm.transactor, path, params.AddressFrom, usedNonces, signer) + txDetails.ApprovalTxData, err = buildApprovalTxForPath(tm.transactor, path, processorInputParams.FromAddr, usedNonces, signer) if err != nil { return nil, path.FromChain.ChainID, path.ToChain.ChainID, err } @@ -259,7 +259,7 @@ func (tm *TransactionManager) BuildTransactionsFromRoute(route routes.Route, pat } // build tx for the path - txDetails.TxData, err = buildTxForPath(path, pathProcessors, usedNonces, signer, params) + txDetails.TxData, err = buildTxForPath(path, pathProcessors, usedNonces, signer, processorInputParams) if err != nil { return nil, path.FromChain.ChainID, path.ToChain.ChainID, err } @@ -354,6 +354,11 @@ func addSignatureAndSendTransaction( txData.TxArgs.MultiTransactionID = multiTransactionID + if txWithSignature.To() == nil { + toAddr := crypto.CreateAddress(txData.TxArgs.From, txData.Tx.Nonce()) + txData.TxArgs.To = &toAddr + } + return responses.NewRouterSentTransaction(txData.TxArgs, txData.SentHash, isApproval), nil } diff --git a/signal/events_community_tokens.go b/signal/events_community_tokens.go index f89215f82b4..88631ee0c03 100644 --- a/signal/events_community_tokens.go +++ b/signal/events_community_tokens.go @@ -18,25 +18,25 @@ const ( ) type CommunityTokenTransactionSignal struct { - TransactionType string `json:"transactionType"` - Success bool `json:"success"` // transaction's status - Hash common.Hash `json:"hash"` // transaction hash - CommunityToken *token.CommunityToken `json:"communityToken,omitempty"` // community token changed by transaction - OwnerToken *token.CommunityToken `json:"ownerToken,omitempty"` // owner token emitted by deployment transaction - MasterToken *token.CommunityToken `json:"masterToken,omitempty"` // master token emitted by deployment transaction - ErrorString string `json:"errorString"` // information about failed operation + SendType int `json:"sendType"` + Success bool `json:"success"` // transaction's status + Hash common.Hash `json:"hash"` // transaction hash + CommunityToken *token.CommunityToken `json:"communityToken,omitempty"` // community token changed by transaction + OwnerToken *token.CommunityToken `json:"ownerToken,omitempty"` // owner token emitted by deployment transaction + MasterToken *token.CommunityToken `json:"masterToken,omitempty"` // master token emitted by deployment transaction + ErrorString string `json:"errorString"` // information about failed operation } -func SendCommunityTokenTransactionStatusSignal(transactionType string, success bool, hash common.Hash, +func SendCommunityTokenTransactionStatusSignal(sendType int, success bool, hash common.Hash, communityToken *token.CommunityToken, ownerToken *token.CommunityToken, masterToken *token.CommunityToken, errorString string) { send(EventCommunityTokenTransactionStatusChanged, CommunityTokenTransactionSignal{ - TransactionType: transactionType, - Success: success, - Hash: hash, - CommunityToken: communityToken, - OwnerToken: ownerToken, - MasterToken: masterToken, - ErrorString: errorString, + SendType: sendType, + Success: success, + Hash: hash, + CommunityToken: communityToken, + OwnerToken: ownerToken, + MasterToken: masterToken, + ErrorString: errorString, }) } diff --git a/transactions/transactor.go b/transactions/transactor.go index bd690d41e97..4f1abd81966 100644 --- a/transactions/transactor.go +++ b/transactions/transactor.go @@ -190,13 +190,20 @@ func (t *Transactor) SendRawTransaction(chainID uint64, rawTx string) error { } func createPendingTransaction(from common.Address, symbol string, chainID uint64, multiTransactionID wallet_common.MultiTransactionIDType, tx *gethtypes.Transaction) (pTx *PendingTransaction) { + var toAddress common.Address + if tx.To() != nil { + toAddress = *tx.To() + } else { + toAddr := crypto.CreateAddress(types.Address(from), tx.Nonce()) + toAddress = common.Address(toAddr) + } pTx = &PendingTransaction{ Hash: tx.Hash(), Timestamp: uint64(time.Now().Unix()), Value: bigint.BigInt{Int: tx.Value()}, From: from, - To: *tx.To(), + To: toAddress, Nonce: tx.Nonce(), Data: string(tx.Data()), Type: WalletTransfer, @@ -406,8 +413,6 @@ func (t *Transactor) validateAndBuildTransaction(rpcWrapper *rpcWrapper, args wa if args.Gas != nil { gas = uint64(*args.Gas) } else { - ctx, cancel = context.WithTimeout(context.Background(), t.rpcCallTimeout) - defer cancel() var ( gethTo common.Address