From 0d0514dcc217e113d2440839d5d9b975098145b0 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Tue, 3 Dec 2024 21:42:18 +0100 Subject: [PATCH 1/4] chore(wallet)_: community deployment related types moved to wallet requests package `DeploymentParameters` and `DeploymentDetails` types moved from `communitytokens` package to `requests` of the wallet service. --- services/communitytokens/api.go | 122 +++++------------- services/communitytokens/api_test.go | 65 ---------- services/communitytokens/estimations.go | 5 +- services/communitytokens/manager.go | 5 +- services/communitytokens/service.go | 3 +- .../requests/router_input_community_params.go | 83 ++++++++++++ .../router_input_community_params_test.go | 72 +++++++++++ 7 files changed, 192 insertions(+), 163 deletions(-) create mode 100644 services/wallet/requests/router_input_community_params.go create mode 100644 services/wallet/requests/router_input_community_params_test.go diff --git a/services/communitytokens/api.go b/services/communitytokens/api.go index 43e6165aa95..9cdb0a986e6 100644 --- a/services/communitytokens/api.go +++ b/services/communitytokens/api.go @@ -5,7 +5,6 @@ import ( "fmt" "math/big" - "github.com/pkg/errors" "go.uber.org/zap" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -18,13 +17,13 @@ import ( 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/images" "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" ) @@ -39,80 +38,17 @@ type API struct { s *Service } -type DeploymentDetails struct { - ContractAddress string `json:"contractAddress"` - TransactionHash string `json:"transactionHash"` - CommunityToken *token.CommunityToken `json:"communityToken"` - OwnerToken *token.CommunityToken `json:"ownerToken"` - MasterToken *token.CommunityToken `json:"masterToken"` -} - -const maxSupply = 999999999 - -type DeploymentParameters struct { - Name string `json:"name"` - Symbol string `json:"symbol"` - Supply *bigint.BigInt `json:"supply"` - InfiniteSupply bool `json:"infiniteSupply"` - Transferable bool `json:"transferable"` - RemoteSelfDestruct bool `json:"remoteSelfDestruct"` - TokenURI string `json:"tokenUri"` - OwnerTokenAddress string `json:"ownerTokenAddress"` - MasterTokenAddress string `json:"masterTokenAddress"` - CommunityID string `json:"communityId"` - Description string `json:"description"` - CroppedImage *images.CroppedImage `json:"croppedImage,omitempty"` // for community tokens - Base64Image string `json:"base64image"` // for owner & master tokens - Decimals int `json:"decimals"` -} - -func (d *DeploymentParameters) GetSupply() *big.Int { - if d.InfiniteSupply { - return d.GetInfiniteSupply() - } - return d.Supply.Int -} - -// infinite supply for ERC721 is 2^256-1 -func (d *DeploymentParameters) GetInfiniteSupply() *big.Int { - return GetInfiniteSupply() -} - -func GetInfiniteSupply() *big.Int { - max := new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil) - max.Sub(max, big.NewInt(1)) - return max -} - -func (d *DeploymentParameters) Validate(isAsset bool) error { - if len(d.Name) <= 0 { - return errors.New("empty collectible name") - } - if len(d.Symbol) <= 0 { - return errors.New("empty collectible symbol") - } - var maxForType = big.NewInt(maxSupply) - if isAsset { - assetMultiplier, _ := big.NewInt(0).SetString("1000000000000000000", 10) - maxForType = maxForType.Mul(maxForType, assetMultiplier) - } - if !d.InfiniteSupply && (d.Supply.Cmp(big.NewInt(0)) < 0 || d.Supply.Cmp(maxForType) > 0) { - return fmt.Errorf("wrong supply value: %v", d.Supply) - } - return nil -} - -func (api *API) DeployCollectibles(ctx context.Context, chainID uint64, deploymentParameters DeploymentParameters, txArgs wallettypes.SendTxArgs, password string) (DeploymentDetails, error) { +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 DeploymentDetails{}, err + 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 DeploymentDetails{}, err + return requests.DeploymentDetails{}, err } address, tx, _, err := collectibles.DeployCollectibles(transactOpts, ethClient, deploymentParameters.Name, deploymentParameters.Symbol, deploymentParameters.GetSupply(), @@ -121,7 +57,7 @@ func (api *API) DeployCollectibles(ctx context.Context, chainID uint64, deployme common.HexToAddress(deploymentParameters.MasterTokenAddress)) if err != nil { logutils.ZapLogger().Error(err.Error()) - return DeploymentDetails{}, err + return requests.DeploymentDetails{}, err } err = api.s.pendingTracker.TrackPendingTransaction( @@ -135,16 +71,16 @@ func (api *API) DeployCollectibles(ctx context.Context, chainID uint64, deployme ) if err != nil { logutils.ZapLogger().Error("TrackPendingTransaction error", zap.Error(err)) - return DeploymentDetails{}, err + return requests.DeploymentDetails{}, err } savedCommunityToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), deploymentParameters, txArgs.From.Hex(), address.Hex(), protobuf.CommunityTokenType_ERC721, token.CommunityLevel, tx.Hash().Hex()) if err != nil { - return DeploymentDetails{}, err + return requests.DeploymentDetails{}, err } - return DeploymentDetails{ + return requests.DeploymentDetails{ ContractAddress: address.Hex(), TransactionHash: tx.Hash().Hex(), CommunityToken: savedCommunityToken}, nil @@ -180,27 +116,27 @@ func prepareDeploymentSignatureStruct(signature string, communityID string, addr } func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64, - ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters, - signerPubKey string, txArgs wallettypes.SendTxArgs, password string) (DeploymentDetails, error) { + ownerTokenParameters requests.DeploymentParameters, masterTokenParameters requests.DeploymentParameters, + signerPubKey string, txArgs wallettypes.SendTxArgs, password string) (requests.DeploymentDetails, error) { err := ownerTokenParameters.Validate(false) if err != nil { - return DeploymentDetails{}, err + return requests.DeploymentDetails{}, err } if len(signerPubKey) <= 0 { - return DeploymentDetails{}, fmt.Errorf("signerPubKey is empty") + return requests.DeploymentDetails{}, fmt.Errorf("signerPubKey is empty") } err = masterTokenParameters.Validate(false) if err != nil { - return DeploymentDetails{}, err + 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 DeploymentDetails{}, err + return requests.DeploymentDetails{}, err } ownerTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{ @@ -217,12 +153,12 @@ func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64, signature, err := api.s.Messenger.CreateCommunityTokenDeploymentSignature(context.Background(), chainID, txArgs.From.Hex(), ownerTokenParameters.CommunityID) if err != nil { - return DeploymentDetails{}, err + return requests.DeploymentDetails{}, err } communitySignature, err := prepareDeploymentSignatureStruct(types.HexBytes(signature).String(), ownerTokenParameters.CommunityID, common.Address(txArgs.From)) if err != nil { - return DeploymentDetails{}, err + return requests.DeploymentDetails{}, err } logutils.ZapLogger().Debug("Prepare deployment", zap.Any("signature", communitySignature)) @@ -231,7 +167,7 @@ func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64, if err != nil { logutils.ZapLogger().Error(err.Error()) - return DeploymentDetails{}, err + return requests.DeploymentDetails{}, err } logutils.ZapLogger().Debug("Contract deployed", zap.Stringer("hash", tx.Hash())) @@ -247,21 +183,21 @@ func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64, ) if err != nil { logutils.ZapLogger().Error("TrackPendingTransaction error", zap.Error(err)) - return DeploymentDetails{}, 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 DeploymentDetails{}, err + 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 DeploymentDetails{}, err + return requests.DeploymentDetails{}, err } - return DeploymentDetails{ + return requests.DeploymentDetails{ ContractAddress: "", TransactionHash: tx.Hash().Hex(), OwnerToken: savedOwnerToken, @@ -273,11 +209,11 @@ func (api *API) ReTrackOwnerTokenDeploymentTransaction(ctx context.Context, chai return api.s.ReTrackOwnerTokenDeploymentTransaction(ctx, chainID, contractAddress) } -func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentParameters DeploymentParameters, txArgs wallettypes.SendTxArgs, password string) (DeploymentDetails, error) { +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 DeploymentDetails{}, err + return requests.DeploymentDetails{}, err } transactOpts := txArgs.ToTransactOpts(utils.VerifyPasswordAndGetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password)) @@ -285,7 +221,7 @@ func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentPara ethClient, err := api.s.manager.rpcClient.EthClient(chainID) if err != nil { logutils.ZapLogger().Error(err.Error()) - return DeploymentDetails{}, err + return requests.DeploymentDetails{}, err } const decimals = 18 @@ -296,7 +232,7 @@ func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentPara common.HexToAddress(deploymentParameters.MasterTokenAddress)) if err != nil { logutils.ZapLogger().Error(err.Error()) - return DeploymentDetails{}, err + return requests.DeploymentDetails{}, err } err = api.s.pendingTracker.TrackPendingTransaction( @@ -310,16 +246,16 @@ func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentPara ) if err != nil { logutils.ZapLogger().Error("TrackPendingTransaction error", zap.Error(err)) - return DeploymentDetails{}, err + return requests.DeploymentDetails{}, err } savedCommunityToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), deploymentParameters, txArgs.From.Hex(), address.Hex(), protobuf.CommunityTokenType_ERC20, token.CommunityLevel, tx.Hash().Hex()) if err != nil { - return DeploymentDetails{}, err + return requests.DeploymentDetails{}, err } - return DeploymentDetails{ + return requests.DeploymentDetails{ ContractAddress: address.Hex(), TransactionHash: tx.Hash().Hex(), CommunityToken: savedCommunityToken}, nil @@ -334,7 +270,7 @@ func (api *API) DeployAssetsEstimate(ctx context.Context, chainID uint64, fromAd } func (api *API) DeployOwnerTokenEstimate(ctx context.Context, chainID uint64, fromAddress string, - ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters, + ownerTokenParameters requests.DeploymentParameters, masterTokenParameters requests.DeploymentParameters, communityID string, signerPubKey string) (*CommunityTokenFees, error) { return api.s.deployOwnerTokenEstimate(ctx, chainID, fromAddress, ownerTokenParameters, masterTokenParameters, communityID, signerPubKey) } diff --git a/services/communitytokens/api_test.go b/services/communitytokens/api_test.go index 07669d76092..0d4705095aa 100644 --- a/services/communitytokens/api_test.go +++ b/services/communitytokens/api_test.go @@ -1,78 +1,13 @@ package communitytokens import ( - "math/big" "testing" "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/common" - - "github.com/status-im/status-go/services/wallet/bigint" ) -func TestDeploymentParameters(t *testing.T) { - var testCases = []struct { - name string - parameters DeploymentParameters - isError bool - }{ - { - name: "emptyName", - parameters: DeploymentParameters{"", "SYMBOL", &bigint.BigInt{Int: big.NewInt(int64(123))}, false, false, false, "", "", "", "", "", nil, "", 0}, - isError: true, - }, - { - name: "emptySymbol", - parameters: DeploymentParameters{"NAME", "", &bigint.BigInt{Int: big.NewInt(123)}, false, false, false, "", "", "", "", "", nil, "", 0}, - isError: true, - }, - { - name: "negativeSupply", - parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(-123)}, false, false, false, "", "", "", "", "", nil, "", 0}, - isError: true, - }, - { - name: "zeroSupply", - parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(0)}, false, false, false, "", "", "", "", "", nil, "", 0}, - isError: false, - }, - { - name: "negativeSupplyAndInfinite", - parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(-123)}, true, false, false, "", "", "", "", "", nil, "", 0}, - isError: false, - }, - { - name: "supplyGreaterThanMax", - parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(maxSupply + 1)}, false, false, false, "", "", "", "", "", nil, "", 0}, - isError: true, - }, - { - name: "supplyIsMax", - parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(maxSupply)}, false, false, false, "", "", "", "", "", nil, "", 0}, - isError: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - err := tc.parameters.Validate(false) - if tc.isError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } - - notInfiniteSupplyParams := DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(123)}, false, false, false, "", "", "", "", "", 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} - requiredSupply = infiniteSupplyParams.GetInfiniteSupply() - require.Equal(t, infiniteSupplyParams.GetSupply(), requiredSupply) -} - func TestTypedDataHash(t *testing.T) { sigHash := common.Hex2Bytes("dd91c30357aafeb2792b5f0facbd83995943c1ea113a906ebbeb58bfeb27dfc2") domainSep := common.Hex2Bytes("4a672b5a08e88d37f7239165a0c9e03a01196587d52c638c0c99cbee5ba527c8") diff --git a/services/communitytokens/estimations.go b/services/communitytokens/estimations.go index 808d9acbd6a..8e257f6416a 100644 --- a/services/communitytokens/estimations.go +++ b/services/communitytokens/estimations.go @@ -20,6 +20,7 @@ import ( "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" ) @@ -45,7 +46,7 @@ func gweiToWei(val *big.Float) *big.Int { } func (s *Service) deployOwnerTokenEstimate(ctx context.Context, chainID uint64, fromAddress string, - ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters, + ownerTokenParameters requests.DeploymentParameters, masterTokenParameters requests.DeploymentParameters, communityID string, signerPubKey string) (*CommunityTokenFees, error) { gasUnits, err := s.deployOwnerTokenGasUnits(ctx, chainID, fromAddress, ownerTokenParameters, masterTokenParameters, @@ -157,7 +158,7 @@ func (s *Service) remoteBurnGasUnits(ctx context.Context, chainID uint64, contra } func (s *Service) deployOwnerTokenGasUnits(ctx context.Context, chainID uint64, fromAddress string, - ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters, + ownerTokenParameters requests.DeploymentParameters, masterTokenParameters requests.DeploymentParameters, communityID string, signerPubKey string) (uint64, error) { ethClient, err := s.manager.rpcClient.EthClient(chainID) if err != nil { diff --git a/services/communitytokens/manager.go b/services/communitytokens/manager.go index 02e75429a68..70826ab5a36 100644 --- a/services/communitytokens/manager.go +++ b/services/communitytokens/manager.go @@ -16,6 +16,7 @@ import ( "github.com/status-im/status-go/protocol/communities" "github.com/status-im/status-go/rpc" "github.com/status-im/status-go/services/wallet/bigint" + "github.com/status-im/status-go/services/wallet/requests" ) type Manager struct { @@ -96,7 +97,7 @@ func (m *Manager) GetCollectibleContractData(chainID uint64, contractAddress str TotalSupply: &bigint.BigInt{Int: totalSupply}, Transferable: transferable, RemoteBurnable: remoteBurnable, - InfiniteSupply: GetInfiniteSupply().Cmp(totalSupply) == 0, + InfiniteSupply: requests.GetInfiniteSupply().Cmp(totalSupply) == 0, }, nil } @@ -113,7 +114,7 @@ func (m *Manager) GetAssetContractData(chainID uint64, contractAddress string) ( return &communities.AssetContractData{ TotalSupply: &bigint.BigInt{Int: totalSupply}, - InfiniteSupply: GetInfiniteSupply().Cmp(totalSupply) == 0, + InfiniteSupply: requests.GetInfiniteSupply().Cmp(totalSupply) == 0, }, nil } diff --git a/services/communitytokens/service.go b/services/communitytokens/service.go index bacc4313744..e7863d08c2a 100644 --- a/services/communitytokens/service.go +++ b/services/communitytokens/service.go @@ -33,6 +33,7 @@ import ( "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/router/fees" "github.com/status-im/status-go/services/wallet/walletevent" "github.com/status-im/status-go/services/wallet/wallettypes" @@ -571,7 +572,7 @@ func (s *Service) maxSupply(ctx context.Context, chainID uint64, contractAddress } } -func (s *Service) CreateCommunityTokenAndSave(chainID int, deploymentParameters DeploymentParameters, +func (s *Service) CreateCommunityTokenAndSave(chainID int, deploymentParameters requests.DeploymentParameters, deployerAddress string, contractAddress string, tokenType protobuf.CommunityTokenType, privilegesLevel token.PrivilegesLevel, transactionHash string) (*token.CommunityToken, error) { contractVersion := "" diff --git a/services/wallet/requests/router_input_community_params.go b/services/wallet/requests/router_input_community_params.go new file mode 100644 index 00000000000..8aa9b21ef9c --- /dev/null +++ b/services/wallet/requests/router_input_community_params.go @@ -0,0 +1,83 @@ +package requests + +import ( + "fmt" + "math/big" + + "github.com/status-im/status-go/errors" + "github.com/status-im/status-go/images" + "github.com/status-im/status-go/protocol/communities/token" + "github.com/status-im/status-go/services/wallet/bigint" +) + +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"} +) + +type DeploymentDetails struct { + ContractAddress string `json:"contractAddress"` + TransactionHash string `json:"transactionHash"` + CommunityToken *token.CommunityToken `json:"communityToken"` + OwnerToken *token.CommunityToken `json:"ownerToken"` + MasterToken *token.CommunityToken `json:"masterToken"` +} + +type DeploymentParameters struct { + Name string `json:"name"` + Symbol string `json:"symbol"` + Supply *bigint.BigInt `json:"supply"` + InfiniteSupply bool `json:"infiniteSupply"` + Transferable bool `json:"transferable"` + RemoteSelfDestruct bool `json:"remoteSelfDestruct"` + TokenURI string `json:"tokenUri"` + OwnerTokenAddress string `json:"ownerTokenAddress"` + MasterTokenAddress string `json:"masterTokenAddress"` + CommunityID string `json:"communityId"` + Description string `json:"description"` + CroppedImage *images.CroppedImage `json:"croppedImage,omitempty"` // for community tokens + Base64Image string `json:"base64image"` // for owner & master tokens + Decimals int `json:"decimals"` +} + +func (d *DeploymentParameters) GetSupply() *big.Int { + if d.InfiniteSupply { + return d.GetInfiniteSupply() + } + return d.Supply.Int +} + +// infinite supply for ERC721 is 2^256-1 +func (d *DeploymentParameters) GetInfiniteSupply() *big.Int { + return GetInfiniteSupply() +} + +func GetInfiniteSupply() *big.Int { + max := new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil) + max.Sub(max, big.NewInt(1)) + return max +} + +func (d *DeploymentParameters) Validate(isAsset bool) error { + if len(d.Name) <= 0 { + return ErrEmptyCollectibleName + } + if len(d.Symbol) <= 0 { + return ErrEmptyCollectibleSymbol + } + var maxForType = big.NewInt(maxSupply) + if isAsset { + assetMultiplier, _ := big.NewInt(0).SetString("1000000000000000000", 10) + maxForType = maxForType.Mul(maxForType, assetMultiplier) + } + if !d.InfiniteSupply && (d.Supply.Cmp(big.NewInt(0)) < 0 || d.Supply.Cmp(maxForType) > 0) { + return &errors.ErrorResponse{ + Code: ErrWrongSupplyValue.Code, + Details: fmt.Sprintf(ErrWrongSupplyValue.Details, d.Supply), + } + } + return nil +} diff --git a/services/wallet/requests/router_input_community_params_test.go b/services/wallet/requests/router_input_community_params_test.go new file mode 100644 index 00000000000..d847a9b0d51 --- /dev/null +++ b/services/wallet/requests/router_input_community_params_test.go @@ -0,0 +1,72 @@ +package requests + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/status-im/status-go/services/wallet/bigint" +) + +func TestDeploymentParameters(t *testing.T) { + var testCases = []struct { + name string + parameters DeploymentParameters + isError bool + }{ + { + name: "emptyName", + parameters: DeploymentParameters{"", "SYMBOL", &bigint.BigInt{Int: big.NewInt(int64(123))}, false, false, false, "", "", "", "", "", nil, "", 0}, + isError: true, + }, + { + name: "emptySymbol", + parameters: DeploymentParameters{"NAME", "", &bigint.BigInt{Int: big.NewInt(123)}, false, false, false, "", "", "", "", "", nil, "", 0}, + isError: true, + }, + { + name: "negativeSupply", + parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(-123)}, false, false, false, "", "", "", "", "", nil, "", 0}, + isError: true, + }, + { + name: "zeroSupply", + parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(0)}, false, false, false, "", "", "", "", "", nil, "", 0}, + isError: false, + }, + { + name: "negativeSupplyAndInfinite", + parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(-123)}, true, false, false, "", "", "", "", "", nil, "", 0}, + isError: false, + }, + { + name: "supplyGreaterThanMax", + parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(maxSupply + 1)}, false, false, false, "", "", "", "", "", nil, "", 0}, + isError: true, + }, + { + name: "supplyIsMax", + parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(maxSupply)}, false, false, false, "", "", "", "", "", nil, "", 0}, + isError: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.parameters.Validate(false) + if tc.isError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } + + notInfiniteSupplyParams := DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(123)}, false, false, false, "", "", "", "", "", 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} + requiredSupply = infiniteSupplyParams.GetInfiniteSupply() + require.Equal(t, infiniteSupplyParams.GetSupply(), requiredSupply) +} From d2b958592bfe7c677e15f5bd83b6636cf60a4550 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Tue, 10 Dec 2024 11:56:24 +0100 Subject: [PATCH 2/4] chore(community)_: duplicated community tokens package --- services/communitytokensv2/api.go | 501 ++++++++++++ services/communitytokensv2/api_test.go | 31 + .../communitytokensdatabase/database.go | 66 ++ .../communitytokensdatabase/database_test.go | 121 +++ services/communitytokensv2/estimations.go | 443 +++++++++++ services/communitytokensv2/manager.go | 210 +++++ services/communitytokensv2/service.go | 742 ++++++++++++++++++ services/communitytokensv2/token_instances.go | 176 +++++ services/communitytokensv2/version.go | 7 + 9 files changed, 2297 insertions(+) create mode 100644 services/communitytokensv2/api.go create mode 100644 services/communitytokensv2/api_test.go create mode 100644 services/communitytokensv2/communitytokensdatabase/database.go create mode 100644 services/communitytokensv2/communitytokensdatabase/database_test.go create mode 100644 services/communitytokensv2/estimations.go create mode 100644 services/communitytokensv2/manager.go create mode 100644 services/communitytokensv2/service.go create mode 100644 services/communitytokensv2/token_instances.go create mode 100644 services/communitytokensv2/version.go diff --git a/services/communitytokensv2/api.go b/services/communitytokensv2/api.go new file mode 100644 index 00000000000..9cdb0a986e6 --- /dev/null +++ b/services/communitytokensv2/api.go @@ -0,0 +1,501 @@ +package communitytokens + +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" +) + +func NewAPI(s *Service) *API { + return &API{ + s: s, + } +} + +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 + } + + savedCommunityToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), deploymentParameters, txArgs.From.Hex(), address.Hex(), + protobuf.CommunityTokenType_ERC721, token.CommunityLevel, tx.Hash().Hex()) + if err != nil { + return requests.DeploymentDetails{}, err + } + + return requests.DeploymentDetails{ + ContractAddress: address.Hex(), + TransactionHash: tx.Hash().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 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 (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) + if err != nil { + return requests.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{ + ContractAddress: "", + TransactionHash: tx.Hash().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 + } + + savedCommunityToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), deploymentParameters, txArgs.From.Hex(), address.Hex(), + protobuf.CommunityTokenType_ERC20, token.CommunityLevel, tx.Hash().Hex()) + if err != nil { + return requests.DeploymentDetails{}, err + } + + return requests.DeploymentDetails{ + ContractAddress: address.Hex(), + TransactionHash: tx.Hash().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) + if err != nil { + return nil, err + } + + // total supply = airdropped only (w/o burnt) + totalSupply, err := contractInst.TotalSupply(callOpts) + if err != nil { + return nil, err + } + + // minted = all created tokens (airdropped and remotely destructed) + mintedCount, err := contractInst.MintedCount(callOpts) + if err != nil { + return nil, err + } + + var res = new(big.Int) + res.Sub(mintedCount, totalSupply) + + 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) +} + +// Gets signer public key directly from deployer contract +func (api *API) SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error) { + return api.s.SafeGetSignerPubKey(ctx, chainID, communityID) +} + +// Gets owner token contract address from deployer contract +func (api *API) SafeGetOwnerTokenAddress(ctx context.Context, chainID uint64, communityID string) (string, error) { + 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) + if err != nil { + return "", err + } + ownerAddress, err := contractInst.OwnerOf(callOpts, big.NewInt(0)) + if err != nil { + return "", err + } + return ownerAddress.Hex(), nil +} diff --git a/services/communitytokensv2/api_test.go b/services/communitytokensv2/api_test.go new file mode 100644 index 00000000000..0d4705095aa --- /dev/null +++ b/services/communitytokensv2/api_test.go @@ -0,0 +1,31 @@ +package communitytokens + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" +) + +func TestTypedDataHash(t *testing.T) { + sigHash := common.Hex2Bytes("dd91c30357aafeb2792b5f0facbd83995943c1ea113a906ebbeb58bfeb27dfc2") + domainSep := common.Hex2Bytes("4a672b5a08e88d37f7239165a0c9e03a01196587d52c638c0c99cbee5ba527c8") + contractAddr := "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + signer := "0x54e3922e97e334905fb489be7c5df1f83cb1ce58" + deployer := "0x7c8999dC9a822c1f0Df42023113EDB4FDd543266" + goodHashResult := "0xccbb375343347491706cf4b43796f7b96ccc89c9e191a8b78679daeba1684ec7" + + typedHash, err := typedStructuredDataHash(domainSep, signer, deployer, contractAddr, 420) + require.NoError(t, err, "creating typed structured data hash") + require.Equal(t, goodHashResult, typedHash.String()) + + customTypedHash := customTypedStructuredDataHash(domainSep, sigHash, signer, deployer) + require.Equal(t, goodHashResult, customTypedHash.String()) +} + +func TestCompressedKeyToEthAddress(t *testing.T) { + ethAddr, err := convert33BytesPubKeyToEthAddress("0x02bcbe39785b55a22383f82ac631ea7500e204627369c4ea01d9296af0ea573f57") + require.NoError(t, err, "converting pub key to address") + require.Equal(t, "0x0A1ec0002dDB927B03049F1aD8D589aBEA4Ba4b3", ethAddr.Hex()) +} diff --git a/services/communitytokensv2/communitytokensdatabase/database.go b/services/communitytokensv2/communitytokensdatabase/database.go new file mode 100644 index 00000000000..8da07e5f0d6 --- /dev/null +++ b/services/communitytokensv2/communitytokensdatabase/database.go @@ -0,0 +1,66 @@ +package communitytokensdatabase + +import ( + "database/sql" + "fmt" + + "github.com/status-im/status-go/protocol/communities/token" + "github.com/status-im/status-go/protocol/protobuf" +) + +type Database struct { + db *sql.DB +} + +func NewCommunityTokensDatabase(db *sql.DB) *Database { + return &Database{db: db} +} + +func (db *Database) GetTokenType(chainID uint64, contractAddress string) (protobuf.CommunityTokenType, error) { + var result = protobuf.CommunityTokenType_UNKNOWN_TOKEN_TYPE + rows, err := db.db.Query(`SELECT type FROM community_tokens WHERE chain_id=? AND address=? LIMIT 1`, chainID, contractAddress) + if err != nil { + return result, err + } + defer rows.Close() + + if rows.Next() { + err := rows.Scan(&result) + return result, err + } + return result, fmt.Errorf("can't find token: chainId %v, contractAddress %v", chainID, contractAddress) +} + +func (db *Database) GetTokenPrivilegesLevel(chainID uint64, contractAddress string) (token.PrivilegesLevel, error) { + var result = token.CommunityLevel + rows, err := db.db.Query(`SELECT privileges_level FROM community_tokens WHERE chain_id=? AND address=? LIMIT 1`, chainID, contractAddress) + if err != nil { + return result, err + } + defer rows.Close() + + if rows.Next() { + err := rows.Scan(&result) + return result, err + } + return result, fmt.Errorf("can't find privileges level: chainId %v, contractAddress %v", chainID, contractAddress) +} + +func (db *Database) GetCommunityERC20Metadata() ([]*token.CommunityToken, error) { + rows, err := db.db.Query(`SELECT community_id, address, name, symbol, chain_id FROM community_tokens WHERE type = ?`, protobuf.CommunityTokenType_ERC20) + if err != nil { + return nil, err + } + defer rows.Close() + + var result []*token.CommunityToken + for rows.Next() { + token := token.CommunityToken{} + err := rows.Scan(&token.CommunityID, &token.Address, &token.Name, &token.Symbol, &token.ChainID) + if err != nil { + return nil, err + } + result = append(result, &token) + } + return result, rows.Err() +} diff --git a/services/communitytokensv2/communitytokensdatabase/database_test.go b/services/communitytokensv2/communitytokensdatabase/database_test.go new file mode 100644 index 00000000000..94c5f7de8f5 --- /dev/null +++ b/services/communitytokensv2/communitytokensdatabase/database_test.go @@ -0,0 +1,121 @@ +package communitytokensdatabase + +import ( + "database/sql" + "math/big" + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/status-im/status-go/appdatabase" + "github.com/status-im/status-go/protocol/communities/token" + "github.com/status-im/status-go/protocol/protobuf" + "github.com/status-im/status-go/protocol/sqlite" + "github.com/status-im/status-go/services/wallet/bigint" + "github.com/status-im/status-go/t/helpers" +) + +func TestDatabaseSuite(t *testing.T) { + suite.Run(t, new(DatabaseSuite)) +} + +type DatabaseSuite struct { + suite.Suite + + db *Database +} + +func (s *DatabaseSuite) addCommunityToken(db *sql.DB, token *token.CommunityToken) error { + _, err := db.Exec(`INSERT INTO community_tokens (community_id, address, type, name, symbol, description, supply_str, + infinite_supply, transferable, remote_self_destruct, chain_id, deploy_state, image_base64, decimals, deployer, privileges_level) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, token.CommunityID, token.Address, token.TokenType, token.Name, + token.Symbol, token.Description, token.Supply.String(), token.InfiniteSupply, token.Transferable, token.RemoteSelfDestruct, + token.ChainID, token.DeployState, token.Base64Image, token.Decimals, token.Deployer, token.PrivilegesLevel) + return err +} + +func (s *DatabaseSuite) setupDatabase(db *sql.DB) error { + token721 := &token.CommunityToken{ + CommunityID: "123", + TokenType: protobuf.CommunityTokenType_ERC721, + Address: "0x123", + Name: "StatusToken", + Symbol: "STT", + Description: "desc", + Supply: &bigint.BigInt{Int: big.NewInt(123)}, + InfiniteSupply: false, + Transferable: true, + RemoteSelfDestruct: true, + ChainID: 1, + DeployState: token.InProgress, + Base64Image: "ABCD", + Deployer: "0xDEP1", + PrivilegesLevel: token.OwnerLevel, + } + + token20 := &token.CommunityToken{ + CommunityID: "345", + TokenType: protobuf.CommunityTokenType_ERC20, + Address: "0x345", + Name: "StatusToken", + Symbol: "STT", + Description: "desc", + Supply: &bigint.BigInt{Int: big.NewInt(345)}, + InfiniteSupply: false, + Transferable: true, + RemoteSelfDestruct: true, + ChainID: 2, + DeployState: token.Failed, + Base64Image: "QWERTY", + Decimals: 21, + Deployer: "0xDEP2", + PrivilegesLevel: token.CommunityLevel, + } + + err := s.addCommunityToken(db, token721) + if err != nil { + return err + } + return s.addCommunityToken(db, token20) +} + +func (s *DatabaseSuite) SetupTest() { + s.db = nil + + db, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{}) + s.NoError(err, "creating sqlite db instance") + + err = sqlite.Migrate(db) + s.NoError(err, "protocol migrate") + + s.db = &Database{db: db} + + err = s.setupDatabase(db) + s.NoError(err, "setting up database") +} + +func (s *DatabaseSuite) TestGetTokenType() { + contractType, err := s.db.GetTokenType(1, "0x123") + s.Require().NoError(err) + s.Equal(contractType, protobuf.CommunityTokenType_ERC721) + + contractType, err = s.db.GetTokenType(2, "0x345") + s.Require().NoError(err) + s.Equal(contractType, protobuf.CommunityTokenType_ERC20) + + _, err = s.db.GetTokenType(10, "0x777") + s.Require().Error(err) +} + +func (s *DatabaseSuite) TestGetPrivilegesLevel() { + privLevel, err := s.db.GetTokenPrivilegesLevel(1, "0x123") + s.Require().NoError(err) + s.Equal(privLevel, token.OwnerLevel) + + privLevel, err = s.db.GetTokenPrivilegesLevel(2, "0x345") + s.Require().NoError(err) + s.Equal(privLevel, token.CommunityLevel) + + _, err = s.db.GetTokenType(10, "0x777") + s.Require().Error(err) +} diff --git a/services/communitytokensv2/estimations.go b/services/communitytokensv2/estimations.go new file mode 100644 index 00000000000..8e257f6416a --- /dev/null +++ b/services/communitytokensv2/estimations.go @@ -0,0 +1,443 @@ +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 new file mode 100644 index 00000000000..70826ab5a36 --- /dev/null +++ b/services/communitytokensv2/manager.go @@ -0,0 +1,210 @@ +package communitytokens + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/signer/core/apitypes" + "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/crypto" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/protocol/communities" + "github.com/status-im/status-go/rpc" + "github.com/status-im/status-go/services/wallet/bigint" + "github.com/status-im/status-go/services/wallet/requests" +) + +type Manager struct { + rpcClient *rpc.Client +} + +func NewManager(rpcClient *rpc.Client) *Manager { + return &Manager{ + 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) 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) +} + +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 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) 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) GetCollectibleContractData(chainID uint64, contractAddress string) (*communities.CollectibleContractData, error) { + callOpts := &bind.CallOpts{Context: context.Background(), Pending: false} + + contract, err := m.GetCollectiblesContractInstance(chainID, contractAddress) + if err != nil { + return nil, err + } + totalSupply, err := contract.MaxSupply(callOpts) + if err != nil { + return nil, err + } + transferable, err := contract.Transferable(callOpts) + if err != nil { + return nil, err + } + remoteBurnable, err := contract.RemoteBurnable(callOpts) + if err != nil { + return nil, err + } + + return &communities.CollectibleContractData{ + TotalSupply: &bigint.BigInt{Int: totalSupply}, + Transferable: transferable, + RemoteBurnable: remoteBurnable, + InfiniteSupply: requests.GetInfiniteSupply().Cmp(totalSupply) == 0, + }, nil +} + +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) + if err != nil { + return nil, err + } + totalSupply, err := contract.MaxSupply(callOpts) + if err != nil { + return nil, err + } + + return &communities.AssetContractData{ + TotalSupply: &bigint.BigInt{Int: totalSupply}, + InfiniteSupply: requests.GetInfiniteSupply().Cmp(totalSupply) == 0, + }, 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 +} + +// Simpler version of hashing typed structured data alternative to typedStructuredDataHash. Keeping this for reference. +func customTypedStructuredDataHash(domainSeparator []byte, signatureTypedHash []byte, signer string, deployer string) types.Hash { + // every field should be 32 bytes, eth address is 20 bytes so padding should be added + emptyOffset := [12]byte{} + hashedEncoded := crypto.Keccak256Hash(signatureTypedHash, emptyOffset[:], common.HexToAddress(signer).Bytes(), + emptyOffset[:], common.HexToAddress(deployer).Bytes()) + rawData := []byte(fmt.Sprintf("\x19\x01%s%s", domainSeparator, hashedEncoded.Bytes())) + return crypto.Keccak256Hash(rawData) +} + +// Returns a typed structured hash according to https://eips.ethereum.org/EIPS/eip-712 +// Domain separator from smart contract is used. +func typedStructuredDataHash(domainSeparator []byte, signer string, addressFrom string, deployerContractAddress string, chainID uint64) (types.Hash, error) { + myTypedData := apitypes.TypedData{ + Types: apitypes.Types{ + "Deploy": []apitypes.Type{ + {Name: "signer", Type: "address"}, + {Name: "deployer", Type: "address"}, + }, + "EIP712Domain": []apitypes.Type{ + {Name: "name", Type: "string"}, + {Name: "version", Type: "string"}, + {Name: "chainId", Type: "uint256"}, + {Name: "verifyingContract", Type: "address"}, + }, + }, + PrimaryType: "Deploy", + // Domain field should be here to keep correct structure but + // domainSeparator from smart contract is used. + Domain: apitypes.TypedDataDomain{ + Name: "CommunityTokenDeployer", // name from Deployer smart contract + Version: "1", // version from Deployer smart contract + ChainId: math.NewHexOrDecimal256(int64(chainID)), + VerifyingContract: deployerContractAddress, + }, + Message: apitypes.TypedDataMessage{ + "signer": signer, + "deployer": addressFrom, + }, + } + + typedDataHash, err := myTypedData.HashStruct(myTypedData.PrimaryType, myTypedData.Message) + if err != nil { + return types.Hash{}, err + } + rawData := []byte(fmt.Sprintf("\x19\x01%s%s", domainSeparator, string(typedDataHash))) + return crypto.Keccak256Hash(rawData), nil +} + +// Creates +func (m *Manager) DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) { + callOpts := &bind.CallOpts{Pending: false} + communityEthAddr, err := convert33BytesPubKeyToEthAddress(communityID) + if err != nil { + return nil, err + } + + deployerAddr, err := communitytokendeployer.ContractAddress(chainID) + if err != nil { + return nil, err + } + deployerContractInst, err := m.NewCommunityTokenDeployerInstance(chainID) + if err != nil { + return nil, err + } + + domainSeparator, err := deployerContractInst.DOMAINSEPARATOR(callOpts) + if err != nil { + return nil, err + } + + structedHash, err := typedStructuredDataHash(domainSeparator[:], communityEthAddr.Hex(), addressFrom, deployerAddr.Hex(), chainID) + if err != nil { + return nil, err + } + + return structedHash.Bytes(), nil +} diff --git a/services/communitytokensv2/service.go b/services/communitytokensv2/service.go new file mode 100644 index 00000000000..e7863d08c2a --- /dev/null +++ b/services/communitytokensv2/service.go @@ -0,0 +1,742 @@ +package communitytokens + +import ( + "context" + "database/sql" + "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" + "github.com/ethereum/go-ethereum/event" + "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" + "github.com/status-im/status-go/params" + "github.com/status-im/status-go/protocol" + "github.com/status-im/status-go/protocol/communities" + "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/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" + "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/walletevent" + "github.com/status-im/status-go/services/wallet/wallettypes" + "github.com/status-im/status-go/signal" + "github.com/status-im/status-go/transactions" +) + +// Collectibles service +type Service struct { + manager *Manager + accountsManager *account.GethManager + pendingTracker *transactions.PendingTxTracker + config *params.NodeConfig + db *communitytokensdatabase.Database + Messenger *protocol.Messenger + walletFeed *event.Feed + walletWatcher *walletevent.Watcher + transactor *transactions.Transactor + feeManager *fees.FeeManager +} + +// 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 { + return &Service{ + manager: &Manager{rpcClient: rpcClient}, + accountsManager: accountsManager, + pendingTracker: pendingTracker, + config: config, + db: communitytokensdatabase.NewCommunityTokensDatabase(appDb), + walletFeed: walletFeed, + transactor: transactor, + feeManager: &fees.FeeManager{RPCClient: rpcClient}, + } +} + +// Protocols returns a new protocols list. In this case, there are none. +func (s *Service) Protocols() []p2p.Protocol { + return []p2p.Protocol{} +} + +// APIs returns a list of new APIs. +func (s *Service) APIs() []ethRpc.API { + return []ethRpc.API{ + { + Namespace: "communitytokens", + Version: "0.1.0", + Service: NewAPI(s), + Public: true, + }, + } +} + +// Start is run when a service is started. +func (s *Service) Start() error { + + s.walletWatcher = walletevent.NewWatcher(s.walletFeed, s.handleWalletEvent) + s.walletWatcher.Start() + + return nil +} + +func (s *Service) handleWalletEvent(event walletevent.Event) { + if event.Type == transactions.EventPendingTransactionStatusChanged { + var p transactions.StatusChangedPayload + err := json.Unmarshal([]byte(event.Message), &p) + if err != nil { + logutils.ZapLogger().Error(errors.Wrap(err, fmt.Sprintf("can't parse transaction message %v\n", event.Message)).Error()) + return + } + 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()) + return + } + + 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) + 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, + 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()) + if communityToken == nil { + return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String()) + } else { + publishErr := s.publishTokenActionToPrivilegedMembers(communityToken.CommunityID, uint64(communityToken.ChainID), + communityToken.Address, protobuf.CommunityTokenAction_AIRDROP) + if publishErr != nil { + logutils.ZapLogger().Warn("can't publish airdrop action") + } + } + 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()) + if communityToken == nil { + return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String()) + } else { + publishErr := s.publishTokenActionToPrivilegedMembers(communityToken.CommunityID, uint64(communityToken.ChainID), + communityToken.Address, protobuf.CommunityTokenAction_REMOTE_DESTRUCT) + if publishErr != nil { + logutils.ZapLogger().Warn("can't publish remote destruct action") + } + } + return communityToken, err +} + +func (s *Service) handleBurnCommunityToken(status string, pendingTransaction *transactions.PendingTransaction) (*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()) + if err != nil { + return nil, err + } + err = s.Messenger.UpdateCommunityTokenSupply(int(pendingTransaction.ChainID), pendingTransaction.To.String(), &bigint.BigInt{Int: newMaxSupply}) + if err != nil { + return nil, err + } + } + + communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) + + if communityToken == nil { + return nil, fmt.Errorf("token does not exist in database: chainId=%v, address=%v", pendingTransaction.ChainID, pendingTransaction.To.String()) + } else { + publishErr := s.publishTokenActionToPrivilegedMembers(communityToken.CommunityID, uint64(communityToken.ChainID), + communityToken.Address, protobuf.CommunityTokenAction_BURN) + if publishErr != nil { + logutils.ZapLogger().Warn("can't publish burn action") + } + } + 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()) + if err != nil { + return nil, nil, err + } + newOwnerAddress, err := s.GetOwnerTokenContractAddressFromHash(context.Background(), uint64(pendingTransaction.ChainID), pendingTransaction.Hash.Hex()) + if err != nil { + return nil, nil, err + } + + err = s.Messenger.UpdateCommunityTokenAddress(int(pendingTransaction.ChainID), s.TemporaryOwnerContractAddress(pendingTransaction.Hash.Hex()), newOwnerAddress) + if err != nil { + return nil, nil, err + } + err = s.Messenger.UpdateCommunityTokenAddress(int(pendingTransaction.ChainID), s.TemporaryMasterContractAddress(pendingTransaction.Hash.Hex()), newMasterAddress) + if err != nil { + return nil, nil, err + } + + ownerToken, err := s.updateStateAndAddTokenToCommunityDescription(status, int(pendingTransaction.ChainID), newOwnerAddress) + if err != nil { + return nil, nil, err + } + + masterToken, err := s.updateStateAndAddTokenToCommunityDescription(status, int(pendingTransaction.ChainID), newMasterAddress) + if err != nil { + return nil, nil, err + } + + return ownerToken, masterToken, nil +} + +func (s *Service) updateStateAndAddTokenToCommunityDescription(status string, chainID int, address string) (*token.CommunityToken, error) { + tokenToUpdate, err := s.Messenger.GetCommunityTokenByChainAndAddress(chainID, address) + if err != nil { + return nil, err + } + if tokenToUpdate == nil { + return nil, fmt.Errorf("token does not exist in database: chainID=%v, address=%v", chainID, address) + } + + if status == transactions.Success { + err := s.Messenger.UpdateCommunityTokenState(chainID, address, token.Deployed) + if err != nil { + return nil, err + } + err = s.Messenger.AddCommunityToken(tokenToUpdate.CommunityID, chainID, address) + if err != nil { + return nil, err + } + } else { + err := s.Messenger.UpdateCommunityTokenState(chainID, address, token.Failed) + if err != nil { + return nil, err + } + } + 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) handleSetSignerPubKey(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { + + communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.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()) + } + + if status == transactions.Success { + _, err := s.Messenger.PromoteSelfToControlNode(types.FromHex(communityToken.CommunityID)) + if err != nil { + return nil, err + } + } + return communityToken, err +} + +// Stop is run when a service is stopped. +func (s *Service) Stop() error { + s.walletWatcher.Stop() + return nil +} + +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 { + return nil, err + } + switch tokenType { + case protobuf.CommunityTokenType_ERC721: + return s.remainingCollectiblesSupply(ctx, chainID, contractAddress) + case protobuf.CommunityTokenType_ERC20: + return s.remainingAssetsSupply(ctx, chainID, contractAddress) + default: + return nil, fmt.Errorf("unknown token type: %v", tokenType) + } +} + +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) + 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 + } + var res = new(big.Int) + res.Sub(maxSupply, mintedCount) + return &bigint.BigInt{Int: res}, nil +} + +// 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) + 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 + } + var res = new(big.Int) + res.Sub(maxSupply, totalSupply) + return &bigint.BigInt{Int: res}, nil +} + +func (s *Service) ValidateWalletsAndAmounts(walletAddresses []string, amount *bigint.BigInt) error { + if len(walletAddresses) == 0 { + return errors.New("wallet addresses list is empty") + } + if amount.Cmp(big.NewInt(0)) <= 0 { + return errors.New("amount is <= 0") + } + return nil +} + +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) + if err != nil { + return "", err + } + signerPubKey, err := contractInst.SignerPublicKey(callOpts) + if err != nil { + return "", err + } + + return types.ToHex(signerPubKey), nil +} + +func (s *Service) SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error) { + // 1. Get Owner Token contract address from deployer contract - SafeGetOwnerTokenAddress() + ownerTokenAddr, err := s.SafeGetOwnerTokenAddress(ctx, chainID, communityID) + if err != nil { + return "", err + } + // 2. Get Signer from owner token contract - GetSignerPubKey() + return s.GetSignerPubKey(ctx, chainID, ownerTokenAddr) +} + +func (s *Service) SafeGetOwnerTokenAddress(ctx context.Context, chainID uint64, communityID string) (string, error) { + callOpts := &bind.CallOpts{Context: ctx, Pending: false} + deployerContractInst, err := s.manager.NewCommunityTokenDeployerInstance(chainID) + if err != nil { + return "", err + } + registryAddr, err := deployerContractInst.DeploymentRegistry(callOpts) + if err != nil { + return "", err + } + registryContractInst, err := s.NewCommunityOwnerTokenRegistryInstance(chainID, registryAddr.Hex()) + if err != nil { + return "", err + } + communityEthAddress, err := convert33BytesPubKeyToEthAddress(communityID) + if err != nil { + return "", err + } + ownerTokenAddress, err := registryContractInst.GetEntry(callOpts, communityEthAddress) + + return ownerTokenAddress.Hex(), err +} + +func (s *Service) GetCollectibleContractData(chainID uint64, contractAddress string) (*communities.CollectibleContractData, error) { + return s.manager.GetCollectibleContractData(chainID, contractAddress) +} + +func (s *Service) GetAssetContractData(chainID uint64, contractAddress string) (*communities.AssetContractData, error) { + return s.manager.GetAssetContractData(chainID, contractAddress) +} + +func (s *Service) DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) { + return s.manager.DeploymentSignatureDigest(chainID, addressFrom, communityID) +} + +func (s *Service) ProcessCommunityTokenAction(message *protobuf.CommunityTokenAction) error { + communityToken, err := s.Messenger.GetCommunityTokenByChainAndAddress(int(message.ChainId), message.ContractAddress) + if err != nil { + return err + } + if communityToken == nil { + return fmt.Errorf("can't find community token in database: chain %v, address %v", message.ChainId, message.ContractAddress) + } + + if message.ActionType == protobuf.CommunityTokenAction_BURN { + // get new max supply and update database + newMaxSupply, err := s.maxSupply(context.Background(), uint64(communityToken.ChainID), communityToken.Address) + if err != nil { + return nil + } + err = s.Messenger.UpdateCommunityTokenSupply(communityToken.ChainID, communityToken.Address, &bigint.BigInt{Int: newMaxSupply}) + if err != nil { + return err + } + communityToken, _ = s.Messenger.GetCommunityTokenByChainAndAddress(int(message.ChainId), message.ContractAddress) + } + + signal.SendCommunityTokenActionSignal(communityToken, message.ActionType) + + 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) + if err != nil { + return nil, err + } + return contractInst.MaxSupply(callOpts) +} + +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) + if err != nil { + return nil, err + } + return contractInst.MaxSupply(callOpts) +} + +func (s *Service) maxSupply(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) { + tokenType, err := s.db.GetTokenType(chainID, contractAddress) + if err != nil { + return nil, err + } + + switch tokenType { + case protobuf.CommunityTokenType_ERC721: + return s.maxSupplyCollectibles(ctx, chainID, contractAddress) + case protobuf.CommunityTokenType_ERC20: + return s.maxSupplyAssets(ctx, chainID, contractAddress) + default: + return nil, fmt.Errorf("unknown token type: %v", tokenType) + } +} + +func (s *Service) CreateCommunityTokenAndSave(chainID int, deploymentParameters requests.DeploymentParameters, + deployerAddress string, contractAddress string, tokenType protobuf.CommunityTokenType, privilegesLevel token.PrivilegesLevel, transactionHash string) (*token.CommunityToken, error) { + + contractVersion := "" + if privilegesLevel == token.CommunityLevel { + contractVersion = s.currentVersion() + } + + tokenToSave := &token.CommunityToken{ + TokenType: tokenType, + CommunityID: deploymentParameters.CommunityID, + Address: contractAddress, + Name: deploymentParameters.Name, + Symbol: deploymentParameters.Symbol, + Description: deploymentParameters.Description, + Supply: &bigint.BigInt{Int: deploymentParameters.GetSupply()}, + InfiniteSupply: deploymentParameters.InfiniteSupply, + Transferable: deploymentParameters.Transferable, + RemoteSelfDestruct: deploymentParameters.RemoteSelfDestruct, + ChainID: chainID, + DeployState: token.InProgress, + Decimals: deploymentParameters.Decimals, + Deployer: deployerAddress, + PrivilegesLevel: privilegesLevel, + Base64Image: deploymentParameters.Base64Image, + TransactionHash: transactionHash, + Version: contractVersion, + } + + return s.Messenger.SaveCommunityToken(tokenToSave, deploymentParameters.CroppedImage) +} + +const ( + MasterSuffix = "-master" + OwnerSuffix = "-owner" +) + +func (s *Service) TemporaryMasterContractAddress(hash string) string { + return hash + MasterSuffix +} + +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) + if err != nil { + return "", err + } + + receipt, err := ethClient.TransactionReceipt(ctx, common.HexToHash(txHash)) + if err != nil { + return "", err + } + + deployerContractInst, err := s.manager.NewCommunityTokenDeployerInstance(chainID) + if err != nil { + return "", err + } + + logMasterTokenCreatedSig := []byte("DeployMasterToken(address)") + logMasterTokenCreatedSigHash := crypto.Keccak256Hash(logMasterTokenCreatedSig) + + for _, vLog := range receipt.Logs { + if vLog.Topics[0].Hex() == logMasterTokenCreatedSigHash.Hex() { + event, err := deployerContractInst.ParseDeployMasterToken(*vLog) + if err != nil { + return "", err + } + return event.Arg0.Hex(), nil + } + } + return "", fmt.Errorf("can't find master token address in transaction: %v", txHash) +} + +func (s *Service) GetOwnerTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) { + ethClient, err := s.manager.rpcClient.EthClient(chainID) + if err != nil { + return "", err + } + + receipt, err := ethClient.TransactionReceipt(ctx, common.HexToHash(txHash)) + if err != nil { + return "", err + } + + deployerContractInst, err := s.manager.NewCommunityTokenDeployerInstance(chainID) + if err != nil { + return "", err + } + + logOwnerTokenCreatedSig := []byte("DeployOwnerToken(address)") + logOwnerTokenCreatedSigHash := crypto.Keccak256Hash(logOwnerTokenCreatedSig) + + for _, vLog := range receipt.Logs { + if vLog.Topics[0].Hex() == logOwnerTokenCreatedSigHash.Hex() { + event, err := deployerContractInst.ParseDeployOwnerToken(*vLog) + if err != nil { + return "", err + } + return event.Arg0.Hex(), nil + } + } + 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 { + return err + } + return s.Messenger.PublishTokenActionToPrivilegedMembers(decodedCommunityID, chainID, contractAddress, actionType) +} diff --git a/services/communitytokensv2/token_instances.go b/services/communitytokensv2/token_instances.go new file mode 100644 index 00000000000..247a49557be --- /dev/null +++ b/services/communitytokensv2/token_instances.go @@ -0,0 +1,176 @@ +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/communitytokensv2/version.go b/services/communitytokensv2/version.go new file mode 100644 index 00000000000..532e32ad7e7 --- /dev/null +++ b/services/communitytokensv2/version.go @@ -0,0 +1,7 @@ +package communitytokens + +const currentVersion = "1.0.0" + +func (s *Service) currentVersion() string { + return currentVersion +} From 2ab05bf68b6388b269be2e41086ec852cd0eebb8 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Wed, 11 Dec 2024 15:53:02 +0100 Subject: [PATCH 3/4] chore(wallet-community)_: move community transactions to the wallet router - new file `contracts/community-tokens/contracts.go` added to unify contracts creation - the following community related path processors added: - `CommunityBurnProcessor` - `CommunityDeployAssetsProcessor` - `CommunityDeployCollectiblesProcessor` - `CommunityDeployOwnerTokenProcessor` - `CommunityMintTokensProcessor` - `CommunityRemoteBurnProcessor` - `CommunitySetSignerPubKeyProcessor` - `SendType` extended with appropriate options - added endpoints to duplicated `communitytokens` api: - `StoreDeployedCollectibles` - `StoreDeployedOwnerToken` - `StoreDeployedAssets` - removed endpoints from duplicated `communitytokens` api: - `DeployCollectibles` - `DeployOwnerToken` - `ReTrackOwnerTokenDeploymentTransaction` - `DeployAssets` - `DeployCollectiblesEstimate` - `DeployAssetsEstimate` - `DeployOwnerTokenEstimate` - `EstimateMintTokens` - `EstimateRemoteBurn` - `EstimateBurn` - `EstimateSetSignerPubKey` - `NewOwnerTokenInstance` - `NewCommunityTokenDeployerInstance` - `NewCommunityOwnerTokenRegistryInstance` - `NewCollectiblesInstance` - `NewAssetsInstance` - `MintTokens` - `RemoteBurn` - `GetCollectiblesContractInstance` - `GetAssetContractInstance` - `Burn` - `SetSignerPubKey` --- contracts/community-tokens/contracts.go | 78 +++ services/communitytokensv2/api.go | 409 +--------------- services/communitytokensv2/estimations.go | 443 ------------------ services/communitytokensv2/manager.go | 51 +- services/communitytokensv2/service.go | 172 +------ services/communitytokensv2/token_instances.go | 176 ------- .../requests/router_input_community_params.go | 101 +++- .../wallet/requests/router_input_params.go | 17 + .../router/pathprocessor/common/constants.go | 29 +- .../wallet/router/pathprocessor/errors.go | 2 + .../wallet/router/pathprocessor/processor.go | 27 ++ .../pathprocessor/processor_community_burn.go | 234 +++++++++ .../processor_community_deploy_assets.go | 121 +++++ ...processor_community_deploy_collectibles.go | 121 +++++ .../processor_community_deploy_owner_token.go | 181 +++++++ .../processor_community_mint_tokens.go | 151 ++++++ .../processor_community_remote_burn.go | 116 +++++ .../processor_community_set_signer_pub_key.go | 117 +++++ services/wallet/router/router.go | 126 ++++- services/wallet/router/router_helper.go | 5 + services/wallet/router/sendtype/send_type.go | 55 ++- services/wallet/service.go | 21 + services/wallet/token/token.go | 15 + 23 files changed, 1513 insertions(+), 1255 deletions(-) create mode 100644 contracts/community-tokens/contracts.go delete mode 100644 services/communitytokensv2/estimations.go delete mode 100644 services/communitytokensv2/token_instances.go create mode 100644 services/wallet/router/pathprocessor/processor_community_burn.go create mode 100644 services/wallet/router/pathprocessor/processor_community_deploy_assets.go create mode 100644 services/wallet/router/pathprocessor/processor_community_deploy_collectibles.go create mode 100644 services/wallet/router/pathprocessor/processor_community_deploy_owner_token.go create mode 100644 services/wallet/router/pathprocessor/processor_community_mint_tokens.go create mode 100644 services/wallet/router/pathprocessor/processor_community_remote_burn.go create mode 100644 services/wallet/router/pathprocessor/processor_community_set_signer_pub_key.go 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/services/communitytokensv2/api.go b/services/communitytokensv2/api.go index 9cdb0a986e6..90899fab6f1 100644 --- a/services/communitytokensv2/api.go +++ b/services/communitytokensv2/api.go @@ -2,30 +2,16 @@ package communitytokens 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" ) func NewAPI(s *Service) *API { @@ -38,321 +24,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 - } +func (api *API) StoreDeployedCollectibles(ctx context.Context, addressFrom types.Address, addressTo types.Address, chainID uint64, + txHash common.Hash, deploymentParameters requests.DeploymentParameters) (requests.DeploymentDetails, error) { - 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 - } - - 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 requests.DeploymentDetails{ - ContractAddress: address.Hex(), - TransactionHash: tx.Hash().Hex(), + 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) (requests.DeploymentDetails, error) { -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 (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) - if err != nil { - return requests.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) + 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 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()) + 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 requests.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) (requests.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 requests.DeploymentDetails{ - ContractAddress: address.Hex(), - TransactionHash: tx.Hash().Hex(), + 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 +101,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 +120,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 70826ab5a36..9e0ca0fbffd 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 e7863d08c2a..94486375c11 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,10 @@ 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" "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/walletevent" - "github.com/status-im/status-go/services/wallet/wallettypes" "github.com/status-im/status-go/signal" "github.com/status-im/status-go/transactions" ) @@ -59,7 +51,7 @@ type Service struct { func NewService(rpcClient *rpc.Client, accountsManager *account.GethManager, pendingTracker *transactions.PendingTxTracker, 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, @@ -294,53 +286,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 +301,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 +324,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 +354,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 +386,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 +438,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 +449,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 +517,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 +549,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 +579,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/requests/router_input_community_params.go b/services/wallet/requests/router_input_community_params.go index 8aa9b21ef9c..a96148ad62b 100644 --- a/services/wallet/requests/router_input_community_params.go +++ b/services/wallet/requests/router_input_community_params.go @@ -4,20 +4,39 @@ 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" "github.com/status-im/status-go/protocol/communities/token" "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"} + ErrTokenAmountMustBePositive = &errors.ErrorResponse{Code: errors.ErrorCode("WRRC-005"), Details: "token amount must be positive"} ) +type CommunityRouteInputParams struct { + CommunityID string `json:"communityID"` + TokenContractAddress string `json:"tokenContractAddress"` + SignerPubKey string `json:"signerPubKey"` + BurnAmount *hexutil.Big `json:"burnAmount"` + TokenIds []*hexutil.Big `json:"tokenIds"` + WalletAddresses []common.Address `json:"walletAddresses"` + TokenAmount *hexutil.Big `json:"collectibleAmount"` + TokenDeploymentSignature string `json:"tokenDeploymentSignature"` + OwnerTokenParameters *DeploymentParameters `json:"ownerTokenParameters"` + MasterTokenParameters *DeploymentParameters `json:"masterTokenParameters"` + DeploymentParameters *DeploymentParameters `json:"deploymentParameters"` +} + type DeploymentDetails struct { ContractAddress string `json:"contractAddress"` TransactionHash string `json:"transactionHash"` @@ -81,3 +100,79 @@ 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 c.TokenContractAddress == "" { + return ErrNoCommunityContractAddress + } + if c.BurnAmount == nil { + return ErrNoCommunityBurnAmount + } + if c.BurnAmount.ToInt().Cmp(big.NewInt(0)) <= 0 { + return ErrCommunityBurnAmountMustBePositive + } + } + + 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 c.TokenAmount.ToInt().Cmp(big.NewInt(0)) <= 0 { + return ErrTokenAmountMustBePositive + } + } + + if sendType == sendtype.CommunityRemoteBurn { + if len(c.TokenIds) == 0 { + return ErrCommunityTokenIdsListEmpty + } + } + + if sendType == sendtype.CommunitySetSignerPubKey { + if c.SignerPubKey == "" { + return ErrNoCommunitySignerPubKey + } + } + + return nil +} diff --git a/services/wallet/requests/router_input_params.go b/services/wallet/requests/router_input_params.go index f0ab9adf79d..a2fdd07e9ae 100644 --- a/services/wallet/requests/router_input_params.go +++ b/services/wallet/requests/router_input_params.go @@ -32,6 +32,16 @@ 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"} + ErrNoCommunityIdProvided = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-020"), Details: "community id is required for community related transfers"} + ErrNoCommunitySignerPubKey = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-021"), Details: "signer pub key is required"} + ErrNoCommunityTokenDeploymentSignature = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-022"), Details: "signature is required"} + ErrNoCommunityOwnerTokenParameters = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-023"), Details: "owner token parameters are required"} + ErrNoCommunityMasterTokenParameters = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-024"), Details: "master token parameters are required"} + ErrNoCommunityDeploymentParameters = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-025"), Details: "deployment parameters are required"} + ErrNoCommunityContractAddress = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-026"), Details: "contract address is required"} + ErrNoCommunityBurnAmount = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-027"), Details: "burn amount is required"} + ErrCommunityBurnAmountMustBePositive = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-028"), Details: "burn amount must be positive"} + ErrCommunityTokenIdsListEmpty = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-029"), Details: "token list is empty"} ) type RouteInputParams struct { @@ -55,6 +65,9 @@ type RouteInputParams struct { PublicKey string `json:"publicKey"` PackID *hexutil.Big `json:"packID"` + // 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 @@ -141,6 +154,10 @@ func (i *RouteInputParams) Validate() error { } } + if i.SendType.IsCommunityRelatedTransfer() { + return i.CommunityRouteInputParams.validateCommunityRelatedInputs(i.SendType) + } + return i.validateFromLockedAmount() } diff --git a/services/wallet/router/pathprocessor/common/constants.go b/services/wallet/router/pathprocessor/common/constants.go index 522fd32c2c2..29cc5b602f7 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 = 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..bc365a7e546 100644 --- a/services/wallet/router/pathprocessor/errors.go +++ b/services/wallet/router/pathprocessor/errors.go @@ -49,6 +49,8 @@ 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"} ) 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..fb0cac1e974 100644 --- a/services/wallet/router/pathprocessor/processor.go +++ b/services/wallet/router/pathprocessor/processor.go @@ -11,6 +11,8 @@ import ( "github.com/status-im/status-go/account" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/params" + 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/requests" "github.com/status-im/status-go/services/wallet/token" "github.com/status-im/status-go/services/wallet/wallettypes" @@ -44,6 +46,16 @@ 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 @@ -60,6 +72,21 @@ type ProcessorInputParams struct { PublicKey string PackID *big.Int + // community related params + CommunityID string + CommunityTokenContractAddress common.Address + CommunitySignerPubKey string + CommunityBurnAmount *big.Int + CommunityTokenIds []*big.Int + CommunityWalletAddresses []common.Address + CommunityTokenAmount *big.Int + CommunityTokenDeploymentSignature string + CommunityTokenType protobuf.CommunityTokenType + CommunityPrivilegeLevel communitiestoken.PrivilegesLevel + CommunityOwnerTokenParameters ProcessorCommunityTokenParams + CommunityMasterTokenParameters ProcessorCommunityTokenParams + CommunityDeploymentParameters ProcessorCommunityTokenParams + // for testing purposes TestsMode bool TestEstimationMap map[string]requests.Estimation // [bridge-name, estimation] 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..dbe5275989a --- /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" + 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/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.CommunityBurnAmount.Cmp(remainingSupply) > 1 { + return ErrBurnAmountTooHigh + } + return nil +} + +func (s *CommunityBurnProcessor) remainingSupply(ctx context.Context, params ProcessorInputParams) (*big.Int, error) { + switch params.CommunityTokenType { + case protobuf.CommunityTokenType_ERC721: + return s.remainingCollectiblesSupply(ctx, params.FromChain.ChainID, params.CommunityTokenContractAddress) + case protobuf.CommunityTokenType_ERC20: + return s.remainingAssetsSupply(ctx, params.FromChain.ChainID, params.CommunityTokenContractAddress) + default: + return nil, ErrCommunityTokenType + } +} + +func (s *CommunityBurnProcessor) maxSupply(ctx context.Context, params ProcessorInputParams) (*big.Int, error) { + switch params.CommunityTokenType { + case protobuf.CommunityTokenType_ERC721: + return s.maxSupplyCollectibles(ctx, params.FromChain.ChainID, params.CommunityTokenContractAddress) + case protobuf.CommunityTokenType_ERC20: + return s.maxSupplyAssets(ctx, params.FromChain.ChainID, params.CommunityTokenContractAddress) + 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.CommunityBurnAmount) + + switch params.CommunityTokenType { + 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) + } + + msg := ethereum.CallMsg{ + From: params.FromAddr, + To: ¶ms.CommunityTokenContractAddress, + 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.AmountIn, nil +} + +func (s *CommunityBurnProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { + return communitytokendeployer.ContractAddress(params.FromChain.ChainID) +} 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..820ef066042 --- /dev/null +++ b/services/wallet/router/pathprocessor/processor_community_deploy_assets.go @@ -0,0 +1,121 @@ +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 + } + + return assetsABI.Pack("" /*constructor name is empty*/, params.CommunityDeploymentParameters.Name, + params.CommunityDeploymentParameters.Symbol, pathProcessorCommon.CommunityDeploymentTokenDecimals, + params.CommunityDeploymentParameters.Supply, params.CommunityDeploymentParameters.TokenURI, + common.HexToAddress(params.CommunityDeploymentParameters.OwnerTokenAddress), + common.HexToAddress(params.CommunityDeploymentParameters.MasterTokenAddress)) +} + +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: append(common.FromHex(assets.AssetsBin), 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..d56e44cbb96 --- /dev/null +++ b/services/wallet/router/pathprocessor/processor_community_deploy_collectibles.go @@ -0,0 +1,121 @@ +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 + } + + return collectiblesABI.Pack("" /*constructor name is empty*/, params.CommunityDeploymentParameters.Name, + params.CommunityDeploymentParameters.Symbol, params.CommunityDeploymentParameters.Supply, + params.CommunityDeploymentParameters.RemoteSelfDestruct, params.CommunityDeploymentParameters.Transferable, + params.CommunityDeploymentParameters.TokenURI, common.HexToAddress(params.CommunityDeploymentParameters.OwnerTokenAddress), + common.HexToAddress(params.CommunityDeploymentParameters.MasterTokenAddress)) +} + +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: append(common.FromHex(collectibles.CollectiblesBin), 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..2a8c4203b5b --- /dev/null +++ b/services/wallet/router/pathprocessor/processor_community_deploy_owner_token.go @@ -0,0 +1,181 @@ +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/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 { + 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 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.CommunityOwnerTokenParameters.Name, + Symbol: params.CommunityOwnerTokenParameters.Symbol, + BaseURI: params.CommunityOwnerTokenParameters.TokenURI, + } + + masterTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{ + Name: params.CommunityMasterTokenParameters.Name, + Symbol: params.CommunityMasterTokenParameters.Symbol, + BaseURI: params.CommunityMasterTokenParameters.TokenURI, + } + + communitySignature, err := prepareDeploymentSignatureStruct(params.CommunityTokenDeploymentSignature, params.CommunityID, params.FromAddr) + if err != nil { + return []byte{}, err + } + + return deployerABI.Pack("deploy", ownerTokenConfig, masterTokenConfig, communitySignature, common.FromHex(params.CommunitySignerPubKey)) +} + +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) { + return s.transactor.ValidateAndBuildTransaction(sendArgs.FromChainID, *sendArgs, lastUsedNonce) +} + +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..061fc397675 --- /dev/null +++ b/services/wallet/router/pathprocessor/processor_community_mint_tokens.go @@ -0,0 +1,151 @@ +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" + 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/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.CommunityTokenType { + case protobuf.CommunityTokenType_ERC721: + destinationAddresses := multiplyWalletAddresses(params.CommunityTokenAmount, params.CommunityWalletAddresses) + 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.CommunityTokenAmount, params.CommunityWalletAddresses) + assetsABI, err := abi.JSON(strings.NewReader(assets.AssetsABI)) + if err != nil { + return []byte{}, err + } + return assetsABI.Pack("mintTo", params.CommunityWalletAddresses, destinationAmounts) + default: + return nil, ErrCommunityTokenType + } +} + +func (s *CommunityMintTokensProcessor) EstimateGas(params ProcessorInputParams) (uint64, error) { + if params.TestsMode { + return 0, ErrNoEstimationFound + } + + 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: nil, + Value: walletCommon.ZeroBigIntValue(), + Data: append(common.FromHex(collectibles.CollectiblesBin), 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.AmountIn, nil +} + +func (s *CommunityMintTokensProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { + return communitytokendeployer.ContractAddress(params.FromChain.ChainID) +} 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..363180c1de5 --- /dev/null +++ b/services/wallet/router/pathprocessor/processor_community_remote_burn.go @@ -0,0 +1,116 @@ +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 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) + } + return collectiblesABI.Pack("remoteBurn", params.CommunityTokenIds) +} + +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) + } + + msg := ethereum.CallMsg{ + From: params.FromAddr, + To: ¶ms.CommunityTokenContractAddress, + 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.AmountIn, nil +} + +func (s *CommunityRemoteBurnProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { + return communitytokendeployer.ContractAddress(params.FromChain.ChainID) +} 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..fe35355572a --- /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" + communitytokendeployer "github.com/status-im/status-go/contracts/community-tokens/deployer" + "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.CommunitySignerPubKey)) +} + +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) + } + + 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, 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 communitytokendeployer.ContractAddress(params.FromChain.ChainID) +} diff --git a/services/wallet/router/router.go b/services/wallet/router/router.go index 3cff0162eb5..2683139204f 100644 --- a/services/wallet/router/router.go +++ b/services/wallet/router/router.go @@ -580,6 +580,108 @@ func (r *Router) getSelectedChains(input *requests.RouteInputParams) (selectedFr return selectedFromChains, selectedToChains, nil } +func (r *Router) mapToProcessorInputParams(input *requests.RouteInputParams, fromNetwork *params.Network, toNetwork *params.Network, fromToken *token.Token, toToken *token.Token, amountIn *big.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, + + 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.CommunityID = input.CommunityRouteInputParams.CommunityID + processorInputParams.CommunityTokenContractAddress = common.HexToAddress(input.CommunityRouteInputParams.TokenContractAddress) + processorInputParams.CommunitySignerPubKey = input.CommunityRouteInputParams.SignerPubKey + processorInputParams.CommunityBurnAmount = input.CommunityRouteInputParams.BurnAmount.ToInt() + processorInputParams.CommunityTokenDeploymentSignature = input.CommunityRouteInputParams.TokenDeploymentSignature + + if input.SendType == sendtype.CommunityBurn { + if input.CommunityRouteInputParams.BurnAmount != nil { + processorInputParams.CommunityBurnAmount = input.CommunityRouteInputParams.BurnAmount.ToInt() + } + + processorInputParams.CommunityTokenType, err = r.tokenManager.GetCommunityTokenType(fromNetwork.ChainID, input.CommunityRouteInputParams.TokenContractAddress) + if err != nil { + return processorInputParams, err + } + + processorInputParams.CommunityPrivilegeLevel, err = r.tokenManager.GetCommunityTokenPrivilegesLevel(fromNetwork.ChainID, input.CommunityRouteInputParams.TokenContractAddress) + if err != nil { + return processorInputParams, err + } + } + + if input.SendType == sendtype.CommunityRemoteBurn { + for _, v := range input.CommunityRouteInputParams.TokenIds { + processorInputParams.CommunityTokenIds = append(processorInputParams.CommunityTokenIds, v.ToInt()) + } + } + + if input.SendType == sendtype.CommunityMintTokens { + if input.CommunityRouteInputParams.TokenAmount != nil { + processorInputParams.CommunityTokenAmount = input.CommunityRouteInputParams.TokenAmount.ToInt() + } + + processorInputParams.CommunityWalletAddresses = input.CommunityRouteInputParams.WalletAddresses + } + + if input.CommunityRouteInputParams.OwnerTokenParameters != nil { + processorInputParams.CommunityOwnerTokenParameters = pathprocessor.ProcessorCommunityTokenParams{ + Name: input.CommunityRouteInputParams.OwnerTokenParameters.Name, + Symbol: input.CommunityRouteInputParams.OwnerTokenParameters.Symbol, + TokenURI: input.CommunityRouteInputParams.OwnerTokenParameters.TokenURI, + } + } + + if input.CommunityRouteInputParams.MasterTokenParameters != nil { + processorInputParams.CommunityMasterTokenParameters = pathprocessor.ProcessorCommunityTokenParams{ + Name: input.CommunityRouteInputParams.MasterTokenParameters.Name, + Symbol: input.CommunityRouteInputParams.MasterTokenParameters.Symbol, + TokenURI: input.CommunityRouteInputParams.MasterTokenParameters.TokenURI, + } + } + + if input.CommunityRouteInputParams.DeploymentParameters != nil { + processorInputParams.CommunityDeploymentParameters = pathprocessor.ProcessorCommunityTokenParams{ + Name: input.CommunityRouteInputParams.DeploymentParameters.Name, + Symbol: input.CommunityRouteInputParams.DeploymentParameters.Symbol, + TokenURI: input.CommunityRouteInputParams.DeploymentParameters.TokenURI, + Transferable: input.CommunityRouteInputParams.DeploymentParameters.Transferable, + RemoteSelfDestruct: input.CommunityRouteInputParams.DeploymentParameters.RemoteSelfDestruct, + Supply: input.CommunityRouteInputParams.DeploymentParameters.GetSupply(), + OwnerTokenAddress: input.CommunityRouteInputParams.DeploymentParameters.OwnerTokenAddress, + MasterTokenAddress: input.CommunityRouteInputParams.DeploymentParameters.MasterTokenAddress, + } + } + } + + 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) resolveCandidates(ctx context.Context, input *requests.RouteInputParams, selectedFromChains []*params.Network, selectedToChains []*params.Network) (candidates routes.Route, processorErrors []*ProcessorError, err error) { var ( @@ -692,26 +794,10 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp 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 + processorInputParams, err := r.mapToProcessorInputParams(input, network, dest, token, toToken, amountOption.amount) + if err != nil { + appendProcessorErrorFn(pProcessor.Name(), input.SendType, network.ChainID, dest.ChainID, amountOption.amount, err) + continue } can, err := pProcessor.AvailableFor(processorInputParams) diff --git a/services/wallet/router/router_helper.go b/services/wallet/router/router_helper.go index 404a4dcdc08..732d419e03d 100644 --- a/services/wallet/router/router_helper.go +++ b/services/wallet/router/router_helper.go @@ -254,6 +254,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 + } + parts := strings.Split(tokenID, ":") contractAddress := common.HexToAddress(parts[0]) collectibleTokenID, success := new(big.Int).SetString(parts[1], 10) diff --git a/services/wallet/router/sendtype/send_type.go b/services/wallet/router/sendtype/send_type.go index cf17007b8f1..6ed921516a5 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 } 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 { From 7bc48bf4bc3f7681bb8ead66c8f998df31d36d58 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Thu, 12 Dec 2024 15:03:48 +0100 Subject: [PATCH 4/4] u1 --- .../requests/router_input_community_params.go | 9 ++-- .../wallet/requests/router_input_params.go | 4 ++ services/wallet/routeexecution/manager.go | 17 +++---- .../wallet/router/pathprocessor/processor.go | 13 ++--- .../pathprocessor/processor_community_burn.go | 4 +- .../processor_community_mint_tokens.go | 4 +- services/wallet/router/router.go | 29 +++++------ services/wallet/router/sendtype/send_type.go | 4 ++ services/wallet/token/token.go | 1 + .../transfer/transaction_manager_route.go | 48 +++++++------------ 10 files changed, 64 insertions(+), 69 deletions(-) diff --git a/services/wallet/requests/router_input_community_params.go b/services/wallet/requests/router_input_community_params.go index a96148ad62b..33eb100a9ac 100644 --- a/services/wallet/requests/router_input_community_params.go +++ b/services/wallet/requests/router_input_community_params.go @@ -27,10 +27,9 @@ type CommunityRouteInputParams struct { CommunityID string `json:"communityID"` TokenContractAddress string `json:"tokenContractAddress"` SignerPubKey string `json:"signerPubKey"` - BurnAmount *hexutil.Big `json:"burnAmount"` + Amount *hexutil.Big `json:"amount"` TokenIds []*hexutil.Big `json:"tokenIds"` WalletAddresses []common.Address `json:"walletAddresses"` - TokenAmount *hexutil.Big `json:"collectibleAmount"` TokenDeploymentSignature string `json:"tokenDeploymentSignature"` OwnerTokenParameters *DeploymentParameters `json:"ownerTokenParameters"` MasterTokenParameters *DeploymentParameters `json:"masterTokenParameters"` @@ -110,10 +109,10 @@ func (c *CommunityRouteInputParams) validateCommunityRelatedInputs(sendType send if c.TokenContractAddress == "" { return ErrNoCommunityContractAddress } - if c.BurnAmount == nil { + if c.Amount == nil { return ErrNoCommunityBurnAmount } - if c.BurnAmount.ToInt().Cmp(big.NewInt(0)) <= 0 { + if c.Amount.ToInt().Cmp(big.NewInt(0)) <= 0 { return ErrCommunityBurnAmountMustBePositive } } @@ -157,7 +156,7 @@ func (c *CommunityRouteInputParams) validateCommunityRelatedInputs(sendType send if len(c.WalletAddresses) == 0 { return ErrWalletAddressesEmpty } - if c.TokenAmount.ToInt().Cmp(big.NewInt(0)) <= 0 { + if c.Amount.ToInt().Cmp(big.NewInt(0)) <= 0 { return ErrTokenAmountMustBePositive } } diff --git a/services/wallet/requests/router_input_params.go b/services/wallet/requests/router_input_params.go index a2fdd07e9ae..8f29a988017 100644 --- a/services/wallet/requests/router_input_params.go +++ b/services/wallet/requests/router_input_params.go @@ -42,6 +42,7 @@ var ( ErrNoCommunityBurnAmount = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-027"), Details: "burn amount is required"} ErrCommunityBurnAmountMustBePositive = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-028"), Details: "burn amount must be positive"} ErrCommunityTokenIdsListEmpty = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-029"), Details: "token list is empty"} + ErrNoCommunityParametersProvided = &errors.ErrorResponse{Code: errors.ErrorCode("WRR-030"), Details: "no community parameters provided"} ) type RouteInputParams struct { @@ -155,6 +156,9 @@ func (i *RouteInputParams) Validate() error { } if i.SendType.IsCommunityRelatedTransfer() { + if i.CommunityRouteInputParams == nil { + return ErrNoCommunityParametersProvided + } return i.CommunityRouteInputParams.validateCommunityRelatedInputs(i.SendType) } diff --git a/services/wallet/routeexecution/manager.go b/services/wallet/routeexecution/manager.go index fab5684c1b5..f57feaa870c 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/sendtype" "github.com/status-im/status-go/services/wallet/transfer" @@ -86,6 +87,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) + if err != nil { + return + } + m.buildInputParams = buildInputParams response.SendDetails.UpdateFields(routeInputParams) @@ -96,14 +104,7 @@ func (m *Manager) BuildTransactionsFromRoute(ctx context.Context, buildInputPara response.SigningDetails, 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, ) }() } diff --git a/services/wallet/router/pathprocessor/processor.go b/services/wallet/router/pathprocessor/processor.go index fb0cac1e974..61017371100 100644 --- a/services/wallet/router/pathprocessor/processor.go +++ b/services/wallet/router/pathprocessor/processor.go @@ -56,6 +56,7 @@ type ProcessorCommunityTokenParams struct { OwnerTokenAddress string MasterTokenAddress string } + type ProcessorInputParams struct { FromChain *params.Network ToChain *params.Network @@ -67,19 +68,19 @@ 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 CommunityID string CommunityTokenContractAddress common.Address CommunitySignerPubKey string - CommunityBurnAmount *big.Int + CommunityAmount *big.Int CommunityTokenIds []*big.Int CommunityWalletAddresses []common.Address - CommunityTokenAmount *big.Int CommunityTokenDeploymentSignature string CommunityTokenType protobuf.CommunityTokenType CommunityPrivilegeLevel communitiestoken.PrivilegesLevel diff --git a/services/wallet/router/pathprocessor/processor_community_burn.go b/services/wallet/router/pathprocessor/processor_community_burn.go index dbe5275989a..17b9049dffe 100644 --- a/services/wallet/router/pathprocessor/processor_community_burn.go +++ b/services/wallet/router/pathprocessor/processor_community_burn.go @@ -62,7 +62,7 @@ func (s *CommunityBurnProcessor) validateBurnAmount(ctx context.Context, params if err != nil { return err } - if params.CommunityBurnAmount.Cmp(remainingSupply) > 1 { + if params.CommunityAmount.Cmp(remainingSupply) > 1 { return ErrBurnAmountTooHigh } return nil @@ -160,7 +160,7 @@ func (s *CommunityBurnProcessor) PackTxInputData(params ProcessorInputParams) ([ return []byte{}, createCommunityBurnErrorResponse(err) } newMaxSupply := big.NewInt(0) - newMaxSupply.Sub(maxSupply, params.CommunityBurnAmount) + newMaxSupply.Sub(maxSupply, params.CommunityAmount) switch params.CommunityTokenType { case protobuf.CommunityTokenType_ERC721: diff --git a/services/wallet/router/pathprocessor/processor_community_mint_tokens.go b/services/wallet/router/pathprocessor/processor_community_mint_tokens.go index 061fc397675..274d2ce3a7d 100644 --- a/services/wallet/router/pathprocessor/processor_community_mint_tokens.go +++ b/services/wallet/router/pathprocessor/processor_community_mint_tokens.go @@ -79,14 +79,14 @@ func prepareMintAssetsData(amount *big.Int, walletAddresses []common.Address) (r func (s *CommunityMintTokensProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { switch params.CommunityTokenType { case protobuf.CommunityTokenType_ERC721: - destinationAddresses := multiplyWalletAddresses(params.CommunityTokenAmount, params.CommunityWalletAddresses) + destinationAddresses := multiplyWalletAddresses(params.CommunityAmount, params.CommunityWalletAddresses) 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.CommunityTokenAmount, params.CommunityWalletAddresses) + destinationAmounts := prepareMintAssetsData(params.CommunityAmount, params.CommunityWalletAddresses) assetsABI, err := abi.JSON(strings.NewReader(assets.AssetsABI)) if err != nil { return []byte{}, err diff --git a/services/wallet/router/router.go b/services/wallet/router/router.go index 2683139204f..b41d24c889d 100644 --- a/services/wallet/router/router.go +++ b/services/wallet/router/router.go @@ -580,16 +580,18 @@ func (r *Router) getSelectedChains(input *requests.RouteInputParams) (selectedFr return selectedFromChains, selectedToChains, nil } -func (r *Router) mapToProcessorInputParams(input *requests.RouteInputParams, fromNetwork *params.Network, toNetwork *params.Network, fromToken *token.Token, toToken *token.Token, amountIn *big.Int) (pathprocessor.ProcessorInputParams, error) { +func (r *Router) CreateProcessorInputParams(input *requests.RouteInputParams, fromNetwork *params.Network, toNetwork *params.Network, + fromToken *token.Token, toToken *token.Token, amountIn *big.Int, slippagePercentage float32) (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, + FromChain: fromNetwork, + ToChain: toNetwork, + FromToken: fromToken, + ToToken: toToken, + ToAddr: input.AddrTo, + FromAddr: input.AddrFrom, + AmountIn: amountIn, + SlippagePercentage: slippagePercentage, Username: input.Username, PublicKey: input.PublicKey, @@ -608,12 +610,11 @@ func (r *Router) mapToProcessorInputParams(input *requests.RouteInputParams, fro processorInputParams.CommunityID = input.CommunityRouteInputParams.CommunityID processorInputParams.CommunityTokenContractAddress = common.HexToAddress(input.CommunityRouteInputParams.TokenContractAddress) processorInputParams.CommunitySignerPubKey = input.CommunityRouteInputParams.SignerPubKey - processorInputParams.CommunityBurnAmount = input.CommunityRouteInputParams.BurnAmount.ToInt() processorInputParams.CommunityTokenDeploymentSignature = input.CommunityRouteInputParams.TokenDeploymentSignature if input.SendType == sendtype.CommunityBurn { - if input.CommunityRouteInputParams.BurnAmount != nil { - processorInputParams.CommunityBurnAmount = input.CommunityRouteInputParams.BurnAmount.ToInt() + if input.CommunityRouteInputParams.Amount != nil { + processorInputParams.CommunityAmount = input.CommunityRouteInputParams.Amount.ToInt() } processorInputParams.CommunityTokenType, err = r.tokenManager.GetCommunityTokenType(fromNetwork.ChainID, input.CommunityRouteInputParams.TokenContractAddress) @@ -634,8 +635,8 @@ func (r *Router) mapToProcessorInputParams(input *requests.RouteInputParams, fro } if input.SendType == sendtype.CommunityMintTokens { - if input.CommunityRouteInputParams.TokenAmount != nil { - processorInputParams.CommunityTokenAmount = input.CommunityRouteInputParams.TokenAmount.ToInt() + if input.CommunityRouteInputParams.Amount != nil { + processorInputParams.CommunityAmount = input.CommunityRouteInputParams.Amount.ToInt() } processorInputParams.CommunityWalletAddresses = input.CommunityRouteInputParams.WalletAddresses @@ -794,7 +795,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *requests.RouteInp continue } - processorInputParams, err := r.mapToProcessorInputParams(input, network, dest, token, toToken, amountOption.amount) + processorInputParams, err := r.CreateProcessorInputParams(input, network, dest, token, toToken, amountOption.amount, 0) if err != nil { appendProcessorErrorFn(pProcessor.Name(), input.SendType, network.ChainID, dest.ChainID, amountOption.amount, err) continue diff --git a/services/wallet/router/sendtype/send_type.go b/services/wallet/router/sendtype/send_type.go index 6ed921516a5..8eab27ad75c 100644 --- a/services/wallet/router/sendtype/send_type.go +++ b/services/wallet/router/sendtype/send_type.go @@ -139,5 +139,9 @@ func (s SendType) IsAvailableFor(network *params.Network) bool { return network.ChainID == walletCommon.EthereumMainnet || network.ChainID == walletCommon.EthereumSepolia } + if s.IsCommunityRelatedTransfer() { + return true + } + return false } diff --git a/services/wallet/token/token.go b/services/wallet/token/token.go index 9e3f77a7fa1..725edadb7c1 100644 --- a/services/wallet/token/token.go +++ b/services/wallet/token/token.go @@ -715,6 +715,7 @@ func (tm *Manager) DeleteCustom(chainID uint64, address common.Address) error { return err } +// TODO: remove this method after migration to new token store func (tm *Manager) SignalCommunityTokenReceived(address common.Address, txHash common.Hash, value *big.Int, t *Token, isFirst bool) { defer gocommon.LogOnPanic() if tm.walletFeed == nil || t == nil || t.CommunityData == nil { diff --git a/services/wallet/transfer/transaction_manager_route.go b/services/wallet/transfer/transaction_manager_route.go index 4fa38b6b385..a14375066e9 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 } @@ -126,38 +117,31 @@ func buildApprovalTxForPath(transactor transactions.TransactorIface, path *route } func buildTxForPath(transactor transactions.TransactorIface, 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, - } + // 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() - data, err := pathProcessors[path.ProcessorName].PackTxInputData(processorInputParams) + data, err := pathProcessors[path.ProcessorName].PackTxInputData(*processorInputParams) if err != nil { return nil, err } - addrTo := types.Address(params.AddressTo) + addrTo := types.Address(processorInputParams.ToAddr) sendArgs := &wallettypes.SendTxArgs{ Version: wallettypes.SendTxArgsVersion1, // tx fields - From: types.Address(params.AddressFrom), + From: types.Address(processorInputParams.FromAddr), To: &addrTo, Value: path.AmountIn, Data: data, @@ -170,7 +154,7 @@ func buildTxForPath(transactor transactions.TransactorIface, path *routes.Path, ValueOut: path.AmountOut, FromChainID: path.FromChain.ChainID, ToChainID: path.ToChain.ChainID, - SlippagePercentage: params.SlippagePercentage, + SlippagePercentage: processorInputParams.SlippagePercentage, } if path.FromToken != nil { sendArgs.FromTokenID = path.FromToken.Symbol @@ -214,12 +198,12 @@ func buildTxForPath(transactor transactions.TransactorIface, path *routes.Path, } func (tm *TransactionManager) BuildTransactionsFromRoute(route routes.Route, pathProcessors map[string]pathprocessor.PathProcessor, - params BuildRouteExtraParams) (*responses.SigningDetails, error) { + processorInputParams *pathprocessor.ProcessorInputParams) (*responses.SigningDetails, error) { if len(route) == 0 { return nil, ErrNoRoute } - accFrom, err := tm.accountsDB.GetAccountByAddress(types.Address(params.AddressFrom)) + accFrom, err := tm.accountsDB.GetAccountByAddress(types.Address(processorInputParams.FromAddr)) if err != nil { return nil, err } @@ -244,7 +228,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, err } @@ -257,7 +241,7 @@ func (tm *TransactionManager) BuildTransactionsFromRoute(route routes.Route, pat } // build tx for the path - txDetails.TxData, err = buildTxForPath(tm.transactor, path, pathProcessors, usedNonces, signer, params) + txDetails.TxData, err = buildTxForPath(tm.transactor, path, pathProcessors, usedNonces, signer, processorInputParams) if err != nil { return nil, err }