From 574450289c8f3ac66d1ebbbe817abaef2e08be53 Mon Sep 17 00:00:00 2001 From: Michal Iskierko Date: Thu, 14 Mar 2024 09:39:06 +0100 Subject: [PATCH] feat_: Move community tokens transaction listening to status-go Use EventWatcher to catch wallet events. Handling all community tokens wallet events in communitytokens service (database and messenger operations). Adding new signal to nim: CommunityTokenTransactionSignal, which is emitted everytime when the event is received. Issue #4351 --- api/geth_backend.go | 1 + node/status_node_services.go | 2 +- protocol/communities/manager.go | 32 +- protocol/communities/persistence.go | 23 ++ protocol/communities_events_utils_test.go | 3 +- .../communities_messenger_helpers_test.go | 27 +- .../communities_messenger_signers_test.go | 7 +- protocol/messenger.go | 3 +- protocol/messenger_communities.go | 4 + protocol/messenger_config.go | 5 +- protocol/messenger_storenode_request_test.go | 5 +- services/communitytokens/api.go | 213 +++++------ services/communitytokens/api_test.go | 18 +- .../communitytokens/asset_contract_data.go | 8 - .../collectible_contract_data.go | 10 - .../{ => communitytokensdatabase}/database.go | 2 +- .../database_test.go | 2 +- services/communitytokens/manager.go | 9 +- services/communitytokens/service.go | 335 +++++++++++++++++- services/ens/api.go | 6 + services/wallet/token/token.go | 6 +- signal/events_community_tokens.go | 36 ++ transactions/pendingtxtracker.go | 16 +- transactions/pendingtxtracker_test.go | 6 +- 24 files changed, 565 insertions(+), 214 deletions(-) delete mode 100644 services/communitytokens/asset_contract_data.go delete mode 100644 services/communitytokens/collectible_contract_data.go rename services/communitytokens/{ => communitytokensdatabase}/database.go (98%) rename services/communitytokens/{ => communitytokensdatabase}/database_test.go (99%) create mode 100644 signal/events_community_tokens.go diff --git a/api/geth_backend.go b/api/geth_backend.go index f4b744f92..4187e33c4 100644 --- a/api/geth_backend.go +++ b/api/geth_backend.go @@ -2366,6 +2366,7 @@ func (b *GethStatusBackend) injectAccountsIntoWakuService(w types.WakuKeyManager } b.statusNode.ChatService(accDB).Init(messenger) b.statusNode.EnsService().Init(messenger.SyncEnsNamesWithDispatchMessage) + b.statusNode.CommunityTokensService().Init(messenger) } return nil diff --git a/node/status_node_services.go b/node/status_node_services.go index 98ad29264..468b45400 100644 --- a/node/status_node_services.go +++ b/node/status_node_services.go @@ -487,7 +487,7 @@ func (b *StatusNode) pendingTrackerService(walletFeed *event.Feed) *transactions func (b *StatusNode) CommunityTokensService() *communitytokens.Service { if b.communityTokensSrvc == nil { - b.communityTokensSrvc = communitytokens.NewService(b.rpcClient, b.gethAccountManager, b.pendingTracker, b.config, b.appDB) + b.communityTokensSrvc = communitytokens.NewService(b.rpcClient, b.gethAccountManager, b.pendingTracker, b.config, b.appDB, &b.walletFeed) } return b.communityTokensSrvc } diff --git a/protocol/communities/manager.go b/protocol/communities/manager.go index a6bb396be..b9b6dbb26 100644 --- a/protocol/communities/manager.go +++ b/protocol/communities/manager.go @@ -42,12 +42,12 @@ import ( "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/requests" "github.com/status-im/status-go/protocol/transport" - "github.com/status-im/status-go/services/communitytokens" "github.com/status-im/status-go/services/wallet/bigint" walletcommon "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/thirdparty" "github.com/status-im/status-go/services/wallet/token" "github.com/status-im/status-go/signal" + "github.com/status-im/status-go/transactions" ) var defaultAnnounceList = [][]string{ @@ -102,7 +102,7 @@ type Manager struct { torrentConfig *params.TorrentConfig torrentClient *torrent.Client walletConfig *params.WalletConfig - communityTokensService communitytokens.ServiceInterface + communityTokensService CommunityTokensServiceInterface historyArchiveTasksWaitGroup sync.WaitGroup historyArchiveTasks sync.Map // stores `chan struct{}` membersReevaluationTasks sync.Map // stores `membersReevaluationTask` @@ -190,7 +190,7 @@ type managerOptions struct { tokenManager TokenManager collectiblesManager CollectiblesManager walletConfig *params.WalletConfig - communityTokensService communitytokens.ServiceInterface + communityTokensService CommunityTokensServiceInterface permissionChecker PermissionChecker // allowForcingCommunityMembersReevaluation indicates whether we should allow forcing community members reevaluation. @@ -205,6 +205,26 @@ type TokenManager interface { GetAllChainIDs() ([]uint64, error) } +type CollectibleContractData struct { + TotalSupply *bigint.BigInt + Transferable bool + RemoteBurnable bool + InfiniteSupply bool +} + +type AssetContractData struct { + TotalSupply *bigint.BigInt + InfiniteSupply bool +} + +type CommunityTokensServiceInterface interface { + GetCollectibleContractData(chainID uint64, contractAddress string) (*CollectibleContractData, error) + SetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, newSignerPubKey string) (string, error) + GetAssetContractData(chainID uint64, contractAddress string) (*AssetContractData, error) + SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error) + DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) +} + type DefaultTokenManager struct { tokenManager *token.Manager } @@ -286,7 +306,7 @@ func WithWalletConfig(walletConfig *params.WalletConfig) ManagerOption { } } -func WithCommunityTokensService(communityTokensService communitytokens.ServiceInterface) ManagerOption { +func WithCommunityTokensService(communityTokensService CommunityTokensServiceInterface) ManagerOption { return func(opts *managerOptions) { opts.communityTokensService = communityTokensService } @@ -4747,6 +4767,10 @@ func (m *Manager) GetCommunityToken(communityID string, chainID int, address str return m.persistence.GetCommunityToken(communityID, chainID, address) } +func (m *Manager) GetCommunityTokenByChainAndAddress(chainID int, address string) (*community_token.CommunityToken, error) { + return m.persistence.GetCommunityTokenByChainAndAddress(chainID, address) +} + func (m *Manager) GetCommunityTokens(communityID string) ([]*community_token.CommunityToken, error) { return m.persistence.GetCommunityTokens(communityID) } diff --git a/protocol/communities/persistence.go b/protocol/communities/persistence.go index aac58afed..1aed19ef9 100644 --- a/protocol/communities/persistence.go +++ b/protocol/communities/persistence.go @@ -1276,6 +1276,29 @@ func (p *Persistence) GetCommunityToken(communityID string, chainID int, address return &token, nil } +func (p *Persistence) GetCommunityTokenByChainAndAddress(chainID int, address string) (*token.CommunityToken, error) { + token := token.CommunityToken{} + var supplyStr string + err := p.db.QueryRow(`SELECT 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 + FROM community_tokens WHERE chain_id = ? AND address = ?`, chainID, address).Scan(&token.CommunityID, &token.Address, &token.TokenType, &token.Name, + &token.Symbol, &token.Description, &supplyStr, &token.InfiniteSupply, &token.Transferable, + &token.RemoteSelfDestruct, &token.ChainID, &token.DeployState, &token.Base64Image, &token.Decimals, + &token.Deployer, &token.PrivilegesLevel) + if err == sql.ErrNoRows { + return nil, nil + } else if err != nil { + return nil, err + } + supplyBigInt, ok := new(big.Int).SetString(supplyStr, 10) + if ok { + token.Supply = &bigint.BigInt{Int: supplyBigInt} + } else { + token.Supply = &bigint.BigInt{Int: big.NewInt(0)} + } + return &token, nil +} + func (p *Persistence) getCommunityTokensInternal(rows *sql.Rows) ([]*token.CommunityToken, error) { tokens := []*token.CommunityToken{} diff --git a/protocol/communities_events_utils_test.go b/protocol/communities_events_utils_test.go index 42eb63d0f..3b0aee609 100644 --- a/protocol/communities_events_utils_test.go +++ b/protocol/communities_events_utils_test.go @@ -18,7 +18,6 @@ import ( "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/requests" "github.com/status-im/status-go/protocol/tt" - "github.com/status-im/status-go/services/communitytokens" "github.com/status-im/status-go/services/wallet/bigint" ) @@ -2061,7 +2060,7 @@ func testEventSenderAddTokenMasterAndOwnerToken(base CommunityEventsTestsInterfa } func addCommunityTokenToCommunityTokensService(base CommunityEventsTestsInterface, token *token.CommunityToken) { - data := &communitytokens.CollectibleContractData{ + data := &communities.CollectibleContractData{ TotalSupply: token.Supply, Transferable: token.Transferable, RemoteBurnable: token.RemoteSelfDestruct, diff --git a/protocol/communities_messenger_helpers_test.go b/protocol/communities_messenger_helpers_test.go index 3a7d8c5cf..ed4e287df 100644 --- a/protocol/communities_messenger_helpers_test.go +++ b/protocol/communities_messenger_helpers_test.go @@ -24,7 +24,6 @@ import ( "github.com/status-im/status-go/protocol/communities/token" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/requests" - "github.com/status-im/status-go/services/communitytokens" walletToken "github.com/status-im/status-go/services/wallet/token" "github.com/status-im/status-go/transactions" ) @@ -74,8 +73,8 @@ func (m *TokenManagerMock) FindOrCreateTokenByAddress(ctx context.Context, chain } type CollectiblesServiceMock struct { - Collectibles map[uint64]map[string]*communitytokens.CollectibleContractData - Assets map[uint64]map[string]*communitytokens.AssetContractData + Collectibles map[uint64]map[string]*communities.CollectibleContractData + Assets map[uint64]map[string]*communities.AssetContractData Signers map[string]string } @@ -90,7 +89,7 @@ func (c *CollectiblesServiceMock) SetSignerPubKey(ctx context.Context, chainID u return "", nil } -func (c *CollectiblesServiceMock) GetCollectibleContractData(chainID uint64, contractAddress string) (*communitytokens.CollectibleContractData, error) { +func (c *CollectiblesServiceMock) GetCollectibleContractData(chainID uint64, contractAddress string) (*communities.CollectibleContractData, error) { collectibleContractData, dataExists := c.Collectibles[chainID][contractAddress] if dataExists { return collectibleContractData, nil @@ -98,7 +97,7 @@ func (c *CollectiblesServiceMock) GetCollectibleContractData(chainID uint64, con return nil, nil } -func (c *CollectiblesServiceMock) GetAssetContractData(chainID uint64, contractAddress string) (*communitytokens.AssetContractData, error) { +func (c *CollectiblesServiceMock) GetAssetContractData(chainID uint64, contractAddress string) (*communities.AssetContractData, error) { assetsContractData, dataExists := c.Assets[chainID][contractAddress] if dataExists { return assetsContractData, nil @@ -106,22 +105,22 @@ func (c *CollectiblesServiceMock) GetAssetContractData(chainID uint64, contractA return nil, nil } -func (c *CollectiblesServiceMock) SetMockCollectibleContractData(chainID uint64, contractAddress string, collectible *communitytokens.CollectibleContractData) { +func (c *CollectiblesServiceMock) SetMockCollectibleContractData(chainID uint64, contractAddress string, collectible *communities.CollectibleContractData) { if c.Collectibles == nil { - c.Collectibles = make(map[uint64]map[string]*communitytokens.CollectibleContractData) + c.Collectibles = make(map[uint64]map[string]*communities.CollectibleContractData) } if _, ok := c.Collectibles[chainID]; !ok { - c.Collectibles[chainID] = make(map[string]*communitytokens.CollectibleContractData) + c.Collectibles[chainID] = make(map[string]*communities.CollectibleContractData) } c.Collectibles[chainID][contractAddress] = collectible } func (c *CollectiblesServiceMock) SetMockCommunityTokenData(token *token.CommunityToken) { if c.Collectibles == nil { - c.Collectibles = make(map[uint64]map[string]*communitytokens.CollectibleContractData) + c.Collectibles = make(map[uint64]map[string]*communities.CollectibleContractData) } - data := &communitytokens.CollectibleContractData{ + data := &communities.CollectibleContractData{ TotalSupply: token.Supply, Transferable: token.Transferable, RemoteBurnable: token.RemoteSelfDestruct, @@ -138,11 +137,11 @@ func (c *CollectiblesServiceMock) SafeGetSignerPubKey(ctx context.Context, chain return c.Signers[communityID], nil } -func (c *CollectiblesServiceMock) SetMockAssetContractData(chainID uint64, contractAddress string, assetData *communitytokens.AssetContractData) { +func (c *CollectiblesServiceMock) SetMockAssetContractData(chainID uint64, contractAddress string, assetData *communities.AssetContractData) { if c.Assets == nil { - c.Assets = make(map[uint64]map[string]*communitytokens.AssetContractData) + c.Assets = make(map[uint64]map[string]*communities.AssetContractData) } - c.Assets[chainID] = make(map[string]*communitytokens.AssetContractData) + c.Assets[chainID] = make(map[string]*communities.AssetContractData) c.Assets[chainID][contractAddress] = assetData } @@ -159,7 +158,7 @@ type testCommunitiesMessengerConfig struct { password string walletAddresses []string mockedBalances *map[uint64]map[gethcommon.Address]map[gethcommon.Address]*hexutil.Big - collectiblesService communitytokens.ServiceInterface + collectiblesService communities.CommunityTokensServiceInterface } func (tcmc *testCommunitiesMessengerConfig) complete() error { diff --git a/protocol/communities_messenger_signers_test.go b/protocol/communities_messenger_signers_test.go index 95733b049..c20487a8f 100644 --- a/protocol/communities_messenger_signers_test.go +++ b/protocol/communities_messenger_signers_test.go @@ -22,7 +22,6 @@ import ( "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/requests" "github.com/status-im/status-go/protocol/tt" - "github.com/status-im/status-go/services/communitytokens" "github.com/status-im/status-go/services/wallet/bigint" "github.com/status-im/status-go/waku" ) @@ -182,7 +181,7 @@ func (s *MessengerCommunitiesSignersSuite) TestControlNodeUpdateSigner() { // update mock - the signer for the community returned by the contracts should be john s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.john.identity.PublicKey)) s.collectiblesServiceMock.SetMockCollectibleContractData(chainID, tokenAddress, - &communitytokens.CollectibleContractData{TotalSupply: &bigint.BigInt{}}) + &communities.CollectibleContractData{TotalSupply: &bigint.BigInt{}}) // bob accepts community update _, err = WaitOnSignaledMessengerResponse( @@ -377,7 +376,7 @@ func (s *MessengerCommunitiesSignersSuite) TestAutoAcceptOnOwnershipChangeReques // set john as contract owner s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.john.identity.PublicKey)) s.collectiblesServiceMock.SetMockCollectibleContractData(chainID, tokenAddress, - &communitytokens.CollectibleContractData{TotalSupply: &bigint.BigInt{}}) + &communities.CollectibleContractData{TotalSupply: &bigint.BigInt{}}) hasTokenPermission := func(r *MessengerResponse) bool { return len(r.Communities()) > 0 && r.Communities()[0].HasTokenPermissions() @@ -515,7 +514,7 @@ func (s *MessengerCommunitiesSignersSuite) TestNewOwnerAcceptRequestToJoin() { // update mock - the signer for the community returned by the contracts should be john s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.john.identity.PublicKey)) s.collectiblesServiceMock.SetMockCollectibleContractData(chainID, tokenAddress, - &communitytokens.CollectibleContractData{TotalSupply: &bigint.BigInt{}}) + &communities.CollectibleContractData{TotalSupply: &bigint.BigInt{}}) // alice accepts community update _, err = WaitOnSignaledMessengerResponse( diff --git a/protocol/messenger.go b/protocol/messenger.go index 6c29467e4..7d74da0d7 100644 --- a/protocol/messenger.go +++ b/protocol/messenger.go @@ -62,7 +62,6 @@ import ( "github.com/status-im/status-go/protocol/verification" "github.com/status-im/status-go/server" "github.com/status-im/status-go/services/browsers" - "github.com/status-im/status-go/services/communitytokens" ensservice "github.com/status-im/status-go/services/ens" "github.com/status-im/status-go/services/ext/mailservers" localnotifications "github.com/status-im/status-go/services/local-notifications" @@ -174,7 +173,7 @@ type Messenger struct { // flag to enable backedup messages processing, false by default processBackedupMessages bool - communityTokensService communitytokens.ServiceInterface + communityTokensService communities.CommunityTokensServiceInterface // used to track dispatched messages dispatchMessageTestCallback func(common.RawMessage) diff --git a/protocol/messenger_communities.go b/protocol/messenger_communities.go index 316b8cf2f..05032be2f 100644 --- a/protocol/messenger_communities.go +++ b/protocol/messenger_communities.go @@ -4221,6 +4221,10 @@ func (m *Messenger) GetCommunityToken(communityID string, chainID int, address s return m.communitiesManager.GetCommunityToken(communityID, chainID, address) } +func (m *Messenger) GetCommunityTokenByChainAndAddress(chainID int, address string) (*token.CommunityToken, error) { + return m.communitiesManager.GetCommunityTokenByChainAndAddress(chainID, address) +} + func (m *Messenger) GetCommunityTokens(communityID string) ([]*token.CommunityToken, error) { return m.communitiesManager.GetCommunityTokens(communityID) } diff --git a/protocol/messenger_config.go b/protocol/messenger_config.go index 5e2bc52fa..6763daeb6 100644 --- a/protocol/messenger_config.go +++ b/protocol/messenger_config.go @@ -9,7 +9,6 @@ import ( "github.com/status-im/status-go/rpc" "github.com/status-im/status-go/server" "github.com/status-im/status-go/services/browsers" - "github.com/status-im/status-go/services/communitytokens" "github.com/status-im/status-go/wakuv2" "go.uber.org/zap" @@ -90,7 +89,7 @@ type config struct { torrentConfig *params.TorrentConfig walletConfig *params.WalletConfig walletService *wallet.Service - communityTokensService communitytokens.ServiceInterface + communityTokensService communities.CommunityTokensServiceInterface httpServer *server.MediaServer rpcClient *rpc.Client tokenManager communities.TokenManager @@ -379,7 +378,7 @@ func WithWalletService(s *wallet.Service) Option { } } -func WithCommunityTokensService(s communitytokens.ServiceInterface) Option { +func WithCommunityTokensService(s communities.CommunityTokensServiceInterface) Option { return func(c *config) error { c.communityTokensService = s return nil diff --git a/protocol/messenger_storenode_request_test.go b/protocol/messenger_storenode_request_test.go index 3f5efe146..54766c7e6 100644 --- a/protocol/messenger_storenode_request_test.go +++ b/protocol/messenger_storenode_request_test.go @@ -29,7 +29,6 @@ import ( "github.com/status-im/status-go/protocol/requests" "github.com/status-im/status-go/protocol/transport" "github.com/status-im/status-go/protocol/tt" - "github.com/status-im/status-go/services/communitytokens" mailserversDB "github.com/status-im/status-go/services/mailservers" "github.com/status-im/status-go/services/wallet/bigint" "github.com/status-im/status-go/t/helpers" @@ -1042,7 +1041,7 @@ func (s *MessengerStoreNodeRequestSuite) TestFetchRealCommunity() { for _, communityToken := range communityTokens { s.collectiblesServiceMock.SetSignerPubkeyForCommunity(communityIDBytes, ownerPublicKey) s.collectiblesServiceMock.SetMockCollectibleContractData(communityToken.ChainID, communityToken.ContractAddress, - &communitytokens.CollectibleContractData{TotalSupply: &bigint.BigInt{}}) + &communities.CollectibleContractData{TotalSupply: &bigint.BigInt{}}) } results := map[string]singleResult{} @@ -1205,7 +1204,7 @@ func (s *MessengerStoreNodeRequestSuite) TestFetchingCommunityWithOwnerToken() { // update mock - the signer for the community returned by the contracts should be owner s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.owner.identity.PublicKey)) s.collectiblesServiceMock.SetMockCollectibleContractData(chainID, tokenAddress, - &communitytokens.CollectibleContractData{TotalSupply: &bigint.BigInt{}}) + &communities.CollectibleContractData{TotalSupply: &bigint.BigInt{}}) community, err = s.owner.communitiesManager.GetByID(community.ID()) s.Require().NoError(err) diff --git a/services/communitytokens/api.go b/services/communitytokens/api.go index a379c38b8..6c7a3004c 100644 --- a/services/communitytokens/api.go +++ b/services/communitytokens/api.go @@ -2,11 +2,12 @@ package communitytokens import ( "context" - "errors" "fmt" "math/big" "strings" + "github.com/pkg/errors" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -19,6 +20,9 @@ import ( "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/images" + "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" @@ -37,22 +41,30 @@ type API struct { } type DeploymentDetails struct { - ContractAddress string `json:"contractAddress"` - TransactionHash string `json:"transactionHash"` + 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"` + 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 { @@ -92,12 +104,10 @@ func (d *DeploymentParameters) Validate(isAsset bool) error { } func (api *API) DeployCollectibles(ctx context.Context, chainID uint64, deploymentParameters DeploymentParameters, txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) { - err := deploymentParameters.Validate(false) if err != nil { return DeploymentDetails{}, err } - transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password)) ethClient, err := api.s.manager.rpcClient.EthClient(chainID) @@ -105,7 +115,6 @@ func (api *API) DeployCollectibles(ctx context.Context, chainID uint64, deployme log.Error(err.Error()) return DeploymentDetails{}, err } - address, tx, _, err := collectibles.DeployCollectibles(transactOpts, ethClient, deploymentParameters.Name, deploymentParameters.Symbol, deploymentParameters.GetSupply(), deploymentParameters.RemoteSelfDestruct, deploymentParameters.Transferable, @@ -120,15 +129,26 @@ func (api *API) DeployCollectibles(ctx context.Context, chainID uint64, deployme wcommon.ChainID(chainID), tx.Hash(), common.Address(txArgs.From), + address, transactions.DeployCommunityToken, - transactions.AutoDelete, + transactions.Keep, + "", ) if err != nil { log.Error("TrackPendingTransaction error", "error", err) return DeploymentDetails{}, err } - return DeploymentDetails{address.Hex(), tx.Hash().Hex()}, nil + savedCommunityToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), deploymentParameters, txArgs.From.Hex(), address.Hex(), + protobuf.CommunityTokenType_ERC721, token.CommunityLevel) + if err != nil { + return DeploymentDetails{}, err + } + + return 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) { @@ -162,8 +182,7 @@ func prepareDeploymentSignatureStruct(signature string, communityID string, addr func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64, ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters, - signature string, communityID string, signerPubKey string, - txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) { + signerPubKey string, txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) { err := ownerTokenParameters.Validate(false) if err != nil { return DeploymentDetails{}, err @@ -197,7 +216,12 @@ func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64, BaseURI: masterTokenParameters.TokenURI, } - communitySignature, err := prepareDeploymentSignatureStruct(signature, communityID, common.Address(txArgs.From)) + signature, err := api.s.Messenger.CreateCommunityTokenDeploymentSignature(context.Background(), chainID, txArgs.From.Hex(), ownerTokenParameters.CommunityID) + if err != nil { + return DeploymentDetails{}, err + } + + communitySignature, err := prepareDeploymentSignatureStruct(types.HexBytes(signature).String(), ownerTokenParameters.CommunityID, common.Address(txArgs.From)) if err != nil { return DeploymentDetails{}, err } @@ -211,81 +235,38 @@ func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64, return DeploymentDetails{}, err } + log.Debug("Contract deployed hash:", tx.Hash().String()) + err = api.s.pendingTracker.TrackPendingTransaction( wcommon.ChainID(chainID), tx.Hash(), common.Address(txArgs.From), + common.Address{}, transactions.DeployOwnerToken, - transactions.AutoDelete, + transactions.Keep, + "", ) if err != nil { log.Error("TrackPendingTransaction error", "error", err) return DeploymentDetails{}, err } - return DeploymentDetails{"", tx.Hash().Hex()}, nil -} - -func (api *API) GetMasterTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) { - ethClient, err := api.s.manager.rpcClient.EthClient(chainID) + savedOwnerToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), ownerTokenParameters, txArgs.From.Hex(), + api.s.TemporaryOwnerContractAddress(tx.Hash().Hex()), protobuf.CommunityTokenType_ERC721, token.OwnerLevel) if err != nil { - return "", err + return DeploymentDetails{}, err } - - receipt, err := ethClient.TransactionReceipt(ctx, common.HexToHash(txHash)) + savedMasterToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), masterTokenParameters, txArgs.From.Hex(), + api.s.TemporaryMasterContractAddress(tx.Hash().Hex()), protobuf.CommunityTokenType_ERC721, token.MasterLevel) if err != nil { - return "", err + return DeploymentDetails{}, err } - deployerContractInst, err := api.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 (api *API) GetOwnerTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) { - ethClient, err := api.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 := api.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) + return DeploymentDetails{ + ContractAddress: "", + TransactionHash: tx.Hash().Hex(), + OwnerToken: savedOwnerToken, + MasterToken: savedMasterToken}, nil } func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentParameters DeploymentParameters, txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) { @@ -318,15 +299,26 @@ func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentPara wcommon.ChainID(chainID), tx.Hash(), common.Address(txArgs.From), + address, transactions.DeployCommunityToken, - transactions.AutoDelete, + transactions.Keep, + "", ) if err != nil { log.Error("TrackPendingTransaction error", "error", err) return DeploymentDetails{}, err } - return DeploymentDetails{address.Hex(), tx.Hash().Hex()}, nil + savedCommunityToken, err := api.s.CreateCommunityTokenAndSave(int(chainID), deploymentParameters, txArgs.From.Hex(), address.Hex(), + protobuf.CommunityTokenType_ERC20, token.CommunityLevel) + if err != nil { + return DeploymentDetails{}, err + } + + return DeploymentDetails{ + ContractAddress: address.Hex(), + TransactionHash: tx.Hash().Hex(), + CommunityToken: savedCommunityToken}, nil } // Returns gas units + 10% @@ -403,7 +395,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, - signature string, communityID string, signerPubKey string) (uint64, error) { + communityID string, signerPubKey string) (uint64, error) { ethClient, err := api.s.manager.rpcClient.EthClient(chainID) if err != nil { log.Error(err.Error()) @@ -432,7 +424,12 @@ func (api *API) DeployOwnerTokenEstimate(ctx context.Context, chainID uint64, fr BaseURI: masterTokenParameters.TokenURI, } - communitySignature, err := prepareDeploymentSignatureStruct(signature, communityID, common.HexToAddress(fromAddress)) + signature, err := api.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 } @@ -531,8 +528,10 @@ func (api *API) MintTokens(ctx context.Context, chainID uint64, contractAddress wcommon.ChainID(chainID), tx.Hash(), common.Address(txArgs.From), + common.HexToAddress(contractAddress), transactions.AirdropCommunityToken, - transactions.AutoDelete, + transactions.Keep, + "", ) if err != nil { log.Error("TrackPendingTransaction error", "error", err) @@ -614,7 +613,7 @@ func (api *API) RemoteDestructedAmount(ctx context.Context, chainID uint64, cont } // This is only ERC721 function -func (api *API) RemoteBurn(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, tokenIds []*bigint.BigInt) (string, error) { +func (api *API) RemoteBurn(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, tokenIds []*bigint.BigInt, additionalData string) (string, error) { err := api.validateTokens(tokenIds) if err != nil { return "", err @@ -641,8 +640,10 @@ func (api *API) RemoteBurn(ctx context.Context, chainID uint64, contractAddress wcommon.ChainID(chainID), tx.Hash(), common.Address(txArgs.From), + common.HexToAddress(contractAddress), transactions.RemoteDestructCollectible, - transactions.AutoDelete, + transactions.Keep, + additionalData, ) if err != nil { log.Error("TrackPendingTransaction error", "error", err) @@ -730,42 +731,8 @@ func (api *API) remainingAssetsSupply(ctx context.Context, chainID uint64, contr return &bigint.BigInt{Int: res}, nil } -func (api *API) maxSupplyCollectibles(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) { - callOpts := &bind.CallOpts{Context: ctx, Pending: false} - contractInst, err := api.NewCollectiblesInstance(chainID, contractAddress) - if err != nil { - return nil, err - } - return contractInst.MaxSupply(callOpts) -} - -func (api *API) maxSupplyAssets(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) { - callOpts := &bind.CallOpts{Context: ctx, Pending: false} - contractInst, err := api.NewAssetsInstance(chainID, contractAddress) - if err != nil { - return nil, err - } - return contractInst.MaxSupply(callOpts) -} - -func (api *API) maxSupply(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) { - tokenType, err := api.s.db.GetTokenType(chainID, contractAddress) - if err != nil { - return nil, err - } - - switch tokenType { - case protobuf.CommunityTokenType_ERC721: - return api.maxSupplyCollectibles(ctx, chainID, contractAddress) - case protobuf.CommunityTokenType_ERC20: - return api.maxSupplyAssets(ctx, chainID, contractAddress) - default: - return nil, fmt.Errorf("unknown token type: %v", tokenType) - } -} - func (api *API) prepareNewMaxSupply(ctx context.Context, chainID uint64, contractAddress string, burnAmount *bigint.BigInt) (*big.Int, error) { - maxSupply, err := api.maxSupply(ctx, chainID, contractAddress) + maxSupply, err := api.s.maxSupply(ctx, chainID, contractAddress) if err != nil { return nil, err } @@ -801,8 +768,10 @@ func (api *API) Burn(ctx context.Context, chainID uint64, contractAddress string wcommon.ChainID(chainID), tx.Hash(), common.Address(txArgs.From), + common.HexToAddress(contractAddress), transactions.BurnCommunityToken, - transactions.AutoDelete, + transactions.Keep, + "", ) if err != nil { log.Error("TrackPendingTransaction error", "error", err) diff --git a/services/communitytokens/api_test.go b/services/communitytokens/api_test.go index b60a4a4ba..07669d760 100644 --- a/services/communitytokens/api_test.go +++ b/services/communitytokens/api_test.go @@ -19,37 +19,37 @@ func TestDeploymentParameters(t *testing.T) { }{ { name: "emptyName", - parameters: DeploymentParameters{"", "SYMBOL", &bigint.BigInt{Int: big.NewInt(int64(123))}, false, false, false, "", "", ""}, + 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, "", "", ""}, + 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, "", "", ""}, + 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, "", "", ""}, + 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, "", "", ""}, + 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, "", "", ""}, + 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, "", "", ""}, + parameters: DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(maxSupply)}, false, false, false, "", "", "", "", "", nil, "", 0}, isError: false, }, } @@ -65,10 +65,10 @@ func TestDeploymentParameters(t *testing.T) { }) } - notInfiniteSupplyParams := DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(123)}, false, false, false, "", "", ""} + 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, "", "", ""} + infiniteSupplyParams := DeploymentParameters{"NAME", "SYM", &bigint.BigInt{Int: big.NewInt(123)}, true, false, false, "", "", "", "", "", nil, "", 0} requiredSupply = infiniteSupplyParams.GetInfiniteSupply() require.Equal(t, infiniteSupplyParams.GetSupply(), requiredSupply) } diff --git a/services/communitytokens/asset_contract_data.go b/services/communitytokens/asset_contract_data.go deleted file mode 100644 index d972ac8f3..000000000 --- a/services/communitytokens/asset_contract_data.go +++ /dev/null @@ -1,8 +0,0 @@ -package communitytokens - -import "github.com/status-im/status-go/services/wallet/bigint" - -type AssetContractData struct { - TotalSupply *bigint.BigInt - InfiniteSupply bool -} diff --git a/services/communitytokens/collectible_contract_data.go b/services/communitytokens/collectible_contract_data.go deleted file mode 100644 index 1b56d73f3..000000000 --- a/services/communitytokens/collectible_contract_data.go +++ /dev/null @@ -1,10 +0,0 @@ -package communitytokens - -import "github.com/status-im/status-go/services/wallet/bigint" - -type CollectibleContractData struct { - TotalSupply *bigint.BigInt - Transferable bool - RemoteBurnable bool - InfiniteSupply bool -} diff --git a/services/communitytokens/database.go b/services/communitytokens/communitytokensdatabase/database.go similarity index 98% rename from services/communitytokens/database.go rename to services/communitytokens/communitytokensdatabase/database.go index 72771f9e1..8da07e5f0 100644 --- a/services/communitytokens/database.go +++ b/services/communitytokens/communitytokensdatabase/database.go @@ -1,4 +1,4 @@ -package communitytokens +package communitytokensdatabase import ( "database/sql" diff --git a/services/communitytokens/database_test.go b/services/communitytokens/communitytokensdatabase/database_test.go similarity index 99% rename from services/communitytokens/database_test.go rename to services/communitytokens/communitytokensdatabase/database_test.go index e2cc2180f..94c5f7de8 100644 --- a/services/communitytokens/database_test.go +++ b/services/communitytokens/communitytokensdatabase/database_test.go @@ -1,4 +1,4 @@ -package communitytokens +package communitytokensdatabase import ( "database/sql" diff --git a/services/communitytokens/manager.go b/services/communitytokens/manager.go index 4219b55ec..02e75429a 100644 --- a/services/communitytokens/manager.go +++ b/services/communitytokens/manager.go @@ -13,6 +13,7 @@ import ( 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" ) @@ -71,7 +72,7 @@ func (m *Manager) GetAssetContractInstance(chainID uint64, contractAddress strin return contractInst, nil } -func (m *Manager) GetCollectibleContractData(chainID uint64, contractAddress string) (*CollectibleContractData, error) { +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) @@ -91,7 +92,7 @@ func (m *Manager) GetCollectibleContractData(chainID uint64, contractAddress str return nil, err } - return &CollectibleContractData{ + return &communities.CollectibleContractData{ TotalSupply: &bigint.BigInt{Int: totalSupply}, Transferable: transferable, RemoteBurnable: remoteBurnable, @@ -99,7 +100,7 @@ func (m *Manager) GetCollectibleContractData(chainID uint64, contractAddress str }, nil } -func (m *Manager) GetAssetContractData(chainID uint64, contractAddress string) (*AssetContractData, error) { +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 { @@ -110,7 +111,7 @@ func (m *Manager) GetAssetContractData(chainID uint64, contractAddress string) ( return nil, err } - return &AssetContractData{ + return &communities.AssetContractData{ TotalSupply: &bigint.BigInt{Int: totalSupply}, InfiniteSupply: GetInfiniteSupply().Cmp(totalSupply) == 0, }, nil diff --git a/services/communitytokens/service.go b/services/communitytokens/service.go index d22b62b2d..00b8371f7 100644 --- a/services/communitytokens/service.go +++ b/services/communitytokens/service.go @@ -3,49 +3,60 @@ package communitytokens import ( "context" "database/sql" + "encoding/json" "fmt" + "math/big" + + "github.com/pkg/errors" "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/log" "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/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/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/walletevent" + "github.com/status-im/status-go/signal" "github.com/status-im/status-go/transactions" ) -type ServiceInterface interface { - GetCollectibleContractData(chainID uint64, contractAddress string) (*CollectibleContractData, error) - SetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, newSignerPubKey string) (string, error) - GetAssetContractData(chainID uint64, contractAddress string) (*AssetContractData, error) - SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error) - DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) -} - // Collectibles service type Service struct { manager *Manager accountsManager *account.GethManager pendingTracker *transactions.PendingTxTracker config *params.NodeConfig - db *Database + db *communitytokensdatabase.Database + Messenger *protocol.Messenger + walletFeed *event.Feed + walletWatcher *walletevent.Watcher } // Returns a new Collectibles Service. -func NewService(rpcClient *rpc.Client, accountsManager *account.GethManager, pendingTracker *transactions.PendingTxTracker, config *params.NodeConfig, appDb *sql.DB) *Service { +func NewService(rpcClient *rpc.Client, accountsManager *account.GethManager, pendingTracker *transactions.PendingTxTracker, + config *params.NodeConfig, appDb *sql.DB, walletFeed *event.Feed) *Service { return &Service{ manager: &Manager{rpcClient: rpcClient}, accountsManager: accountsManager, pendingTracker: pendingTracker, config: config, - db: NewCommunityTokensDatabase(appDb), + db: communitytokensdatabase.NewCommunityTokensDatabase(appDb), + walletFeed: walletFeed, } } @@ -68,14 +79,181 @@ func (s *Service) APIs() []ethRpc.API { // 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 { + log.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 { + log.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 { + log.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) { + return s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) +} + +func (s *Service) handleRemoteDestructCollectible(status string, pendingTransaction *transactions.PendingTransaction) (*token.CommunityToken, error) { + return s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) +} + +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 + } + } + + return s.Messenger.GetCommunityTokenByChainAndAddress(int(pendingTransaction.ChainID), pendingTransaction.To.String()) +} + +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 { @@ -142,11 +320,11 @@ func (s *Service) SafeGetOwnerTokenAddress(ctx context.Context, chainID uint64, return ownerTokenAddress.Hex(), err } -func (s *Service) GetCollectibleContractData(chainID uint64, contractAddress string) (*CollectibleContractData, error) { +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) (*AssetContractData, error) { +func (s *Service) GetAssetContractData(chainID uint64, contractAddress string) (*communities.AssetContractData, error) { return s.manager.GetAssetContractData(chainID, contractAddress) } @@ -176,8 +354,10 @@ func (s *Service) SetSignerPubKey(ctx context.Context, chainID uint64, contractA wcommon.ChainID(chainID), tx.Hash(), common.Address(txArgs.From), + common.HexToAddress(contractAddress), transactions.SetSignerPublicKey, transactions.AutoDelete, + "", ) if err != nil { log.Error("TrackPendingTransaction error", "error", err) @@ -186,3 +366,132 @@ func (s *Service) SetSignerPubKey(ctx context.Context, chainID uint64, contractA 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 DeploymentParameters, + deployerAddress string, contractAddress string, tokenType protobuf.CommunityTokenType, privilegesLevel token.PrivilegesLevel) (*token.CommunityToken, error) { + + 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, + } + + return s.Messenger.SaveCommunityToken(tokenToSave, deploymentParameters.CroppedImage) +} + +func (s *Service) TemporaryMasterContractAddress(hash string) string { + return hash + "-master" +} + +func (s *Service) TemporaryOwnerContractAddress(hash string) string { + return hash + "-owner" +} + +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) +} diff --git a/services/ens/api.go b/services/ens/api.go index c62b09b40..8beca2b0f 100644 --- a/services/ens/api.go +++ b/services/ens/api.go @@ -357,8 +357,10 @@ func (api *API) Release(ctx context.Context, chainID uint64, txArgs transactions wcommon.ChainID(chainID), tx.Hash(), common.Address(txArgs.From), + registryAddr, transactions.ReleaseENS, transactions.AutoDelete, + "", ) if err != nil { log.Error("TrackPendingTransaction error", "error", err) @@ -484,8 +486,10 @@ func (api *API) Register(ctx context.Context, chainID uint64, txArgs transaction wcommon.ChainID(chainID), tx.Hash(), common.Address(txArgs.From), + registryAddr, transactions.RegisterENS, transactions.AutoDelete, + "", ) if err != nil { log.Error("TrackPendingTransaction error", "error", err) @@ -600,8 +604,10 @@ func (api *API) SetPubKey(ctx context.Context, chainID uint64, txArgs transactio wcommon.ChainID(chainID), tx.Hash(), common.Address(txArgs.From), + *resolverAddress, transactions.SetPubKey, transactions.AutoDelete, + "", ) if err != nil { log.Error("TrackPendingTransaction error", "error", err) diff --git a/services/wallet/token/token.go b/services/wallet/token/token.go index 0019b417e..6d9fed924 100644 --- a/services/wallet/token/token.go +++ b/services/wallet/token/token.go @@ -29,7 +29,7 @@ import ( "github.com/status-im/status-go/rpc/network" "github.com/status-im/status-go/server" "github.com/status-im/status-go/services/accounts/accountsevent" - "github.com/status-im/status-go/services/communitytokens" + "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/async" "github.com/status-im/status-go/services/wallet/bigint" @@ -102,7 +102,7 @@ type Manager struct { ContractMaker *contracts.ContractMaker networkManager *network.Manager stores []store // Set on init, not changed afterwards - communityTokensDB *communitytokens.Database + communityTokensDB *communitytokensdatabase.Database communityManager *community.Manager mediaServer *server.MediaServer walletFeed *event.Feed @@ -178,7 +178,7 @@ func NewTokenManager( networkManager: networkManager, communityManager: communityManager, stores: stores, - communityTokensDB: communitytokens.NewCommunityTokensDatabase(appDB), + communityTokensDB: communitytokensdatabase.NewCommunityTokensDatabase(appDB), tokens: tokens, mediaServer: mediaServer, walletFeed: walletFeed, diff --git a/signal/events_community_tokens.go b/signal/events_community_tokens.go new file mode 100644 index 000000000..c5435d458 --- /dev/null +++ b/signal/events_community_tokens.go @@ -0,0 +1,36 @@ +package signal + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/status-im/status-go/protocol/communities/token" +) + +const ( + + // EventCommunityTokenTransactionStatusChanged is triggered when community token contract + // transaction changed its status + EventCommunityTokenTransactionStatusChanged = "communityToken.communityTokenTransactionStatusChanged" +) + +type CommunityTokenTransactionSignal struct { + TransactionType string `json:"transactionType"` + Success bool `json:"success"` // transaction's status + Hash common.Hash `json:"hash"` // transaction hash + CommunityToken *token.CommunityToken `json:"communityToken,omitempty"` // community token changed by transaction + OwnerToken *token.CommunityToken `json:"ownerToken,omitempty"` // owner token emitted by deployment transaction + MasterToken *token.CommunityToken `json:"masterToken,omitempty"` // master token emitted by deployment transaction + ErrorString string `json:"errorString"` // information about failed operation +} + +func SendCommunityTokenTransactionStatusSignal(transactionType string, success bool, hash common.Hash, + communityToken *token.CommunityToken, ownerToken *token.CommunityToken, masterToken *token.CommunityToken, errorString string) { + send(EventCommunityTokenTransactionStatusChanged, CommunityTokenTransactionSignal{ + TransactionType: transactionType, + Success: success, + Hash: hash, + CommunityToken: communityToken, + OwnerToken: ownerToken, + MasterToken: masterToken, + ErrorString: errorString, + }) +} diff --git a/transactions/pendingtxtracker.go b/transactions/pendingtxtracker.go index 598575b98..11bb7415e 100644 --- a/transactions/pendingtxtracker.go +++ b/transactions/pendingtxtracker.go @@ -343,14 +343,16 @@ func (tm *PendingTxTracker) emitNotifications(chainID common.ChainID, changes [] } // PendingTransaction called with autoDelete = false will keep the transaction in the database until it is confirmed by the caller using Delete -func (tm *PendingTxTracker) TrackPendingTransaction(chainID common.ChainID, hash eth.Hash, from eth.Address, trType PendingTrxType, autoDelete AutoDeleteType) error { +func (tm *PendingTxTracker) TrackPendingTransaction(chainID common.ChainID, hash eth.Hash, from eth.Address, to eth.Address, trType PendingTrxType, autoDelete AutoDeleteType, additionalData string) error { err := tm.addPending(&PendingTransaction{ - ChainID: chainID, - Hash: hash, - From: from, - Timestamp: uint64(time.Now().Unix()), - Type: trType, - AutoDelete: &autoDelete, + ChainID: chainID, + Hash: hash, + From: from, + To: to, + Timestamp: uint64(time.Now().Unix()), + Type: trType, + AutoDelete: &autoDelete, + AdditionalData: additionalData, }) if err != nil { return err diff --git a/transactions/pendingtxtracker_test.go b/transactions/pendingtxtracker_test.go index 9c46cd38e..87cb7a088 100644 --- a/transactions/pendingtxtracker_test.go +++ b/transactions/pendingtxtracker_test.go @@ -290,7 +290,7 @@ func TestPendingTxTracker_MultipleClients(t *testing.T) { sub := eventFeed.Subscribe(eventChan) for i := range txs { - err := m.TrackPendingTransaction(txs[i].ChainID, txs[i].Hash, txs[i].From, txs[i].Type, AutoDelete) + err := m.TrackPendingTransaction(txs[i].ChainID, txs[i].Hash, txs[i].From, txs[i].To, txs[i].Type, AutoDelete, "") require.NoError(t, err) } @@ -365,7 +365,7 @@ func TestPendingTxTracker_Watch(t *testing.T) { sub := eventFeed.Subscribe(eventChan) // Track the first transaction - err := m.TrackPendingTransaction(txs[1].ChainID, txs[1].Hash, txs[1].From, txs[1].Type, Keep) + err := m.TrackPendingTransaction(txs[1].ChainID, txs[1].Hash, txs[1].From, txs[1].To, txs[1].Type, Keep, "") require.NoError(t, err) // Store the confirmed already @@ -464,7 +464,7 @@ func TestPendingTxTracker_Watch_StatusChangeIncrementally(t *testing.T) { for i := range txs { // Track the first transaction - err := m.TrackPendingTransaction(txs[i].ChainID, txs[i].Hash, txs[i].From, txs[i].Type, Keep) + err := m.TrackPendingTransaction(txs[i].ChainID, txs[i].Hash, txs[i].From, txs[i].To, txs[i].Type, Keep, "") require.NoError(t, err) }