github.com/status-im/status-go@v1.1.0/protocol/communities_messenger_helpers_test.go (about)

     1  package protocol
     2  
     3  import (
     4  	"context"
     5  	"crypto/ecdsa"
     6  	"encoding/json"
     7  	"errors"
     8  	"math/big"
     9  	"strconv"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/stretchr/testify/suite"
    14  
    15  	gethcommon "github.com/ethereum/go-ethereum/common"
    16  	hexutil "github.com/ethereum/go-ethereum/common/hexutil"
    17  
    18  	"github.com/status-im/status-go/account"
    19  	"github.com/status-im/status-go/eth-node/crypto"
    20  	"github.com/status-im/status-go/eth-node/types"
    21  	"github.com/status-im/status-go/multiaccounts/accounts"
    22  	"github.com/status-im/status-go/multiaccounts/settings"
    23  	"github.com/status-im/status-go/params"
    24  	"github.com/status-im/status-go/protocol/common"
    25  	"github.com/status-im/status-go/protocol/communities"
    26  	"github.com/status-im/status-go/protocol/communities/token"
    27  	"github.com/status-im/status-go/protocol/protobuf"
    28  	"github.com/status-im/status-go/protocol/requests"
    29  	"github.com/status-im/status-go/services/wallet/bigint"
    30  	walletCommon "github.com/status-im/status-go/services/wallet/common"
    31  	"github.com/status-im/status-go/services/wallet/thirdparty"
    32  	walletToken "github.com/status-im/status-go/services/wallet/token"
    33  	"github.com/status-im/status-go/transactions"
    34  )
    35  
    36  type AccountManagerMock struct {
    37  	AccountsMap map[string]string
    38  }
    39  
    40  func (m *AccountManagerMock) GetVerifiedWalletAccount(db *accounts.Database, address, password string) (*account.SelectedExtKey, error) {
    41  	return &account.SelectedExtKey{
    42  		Address: types.HexToAddress(address),
    43  	}, nil
    44  }
    45  
    46  func (m *AccountManagerMock) CanRecover(rpcParams account.RecoverParams, revealedAddress types.Address) (bool, error) {
    47  	return true, nil
    48  }
    49  
    50  func (m *AccountManagerMock) Sign(rpcParams account.SignParams, verifiedAccount *account.SelectedExtKey) (result types.HexBytes, err error) {
    51  	// mock signature
    52  	bytesArray := []byte(rpcParams.Address)
    53  	bytesArray = append(bytesArray, []byte(rpcParams.Password)...)
    54  	bytesArray = common.Shake256(bytesArray)
    55  	return append([]byte{0}, bytesArray...), nil
    56  }
    57  
    58  func (m *AccountManagerMock) DeleteAccount(address types.Address) error {
    59  	return nil
    60  }
    61  
    62  type TokenManagerMock struct {
    63  	Balances *communities.BalancesByChain
    64  }
    65  
    66  func (m *TokenManagerMock) GetAllChainIDs() ([]uint64, error) {
    67  	chainIDs := make([]uint64, 0, len(*m.Balances))
    68  	for key := range *m.Balances {
    69  		chainIDs = append(chainIDs, key)
    70  	}
    71  	return chainIDs, nil
    72  }
    73  
    74  func (m *TokenManagerMock) getBalanceBasedOnParams(accounts, tokenAddresses []gethcommon.Address, chainIDs []uint64) map[uint64]map[gethcommon.Address]map[gethcommon.Address]*hexutil.Big {
    75  	retBalances := make(map[uint64]map[gethcommon.Address]map[gethcommon.Address]*hexutil.Big)
    76  
    77  	for _, chainId := range chainIDs {
    78  		if _, exists := retBalances[chainId]; !exists {
    79  			retBalances[chainId] = make(map[gethcommon.Address]map[gethcommon.Address]*hexutil.Big)
    80  		}
    81  		if storedAccounts, exists := (*m.Balances)[chainId]; exists {
    82  			for _, account := range accounts {
    83  				if _, exists := retBalances[chainId][account]; !exists {
    84  					retBalances[chainId][account] = make(map[gethcommon.Address]*hexutil.Big)
    85  				}
    86  				if storedTokenAddresses, exists := storedAccounts[account]; exists {
    87  					for _, tokenAddress := range tokenAddresses {
    88  						if _, exists := retBalances[chainId][account][tokenAddress]; !exists {
    89  							retBalances[chainId][account] = make(map[gethcommon.Address]*hexutil.Big)
    90  						}
    91  
    92  						if balance, exists := storedTokenAddresses[tokenAddress]; exists {
    93  							retBalances[chainId][account][tokenAddress] = balance
    94  						}
    95  					}
    96  				}
    97  			}
    98  		}
    99  	}
   100  
   101  	return retBalances
   102  }
   103  
   104  func (m *TokenManagerMock) GetBalancesByChain(ctx context.Context, accounts, tokenAddresses []gethcommon.Address, chainIDs []uint64) (map[uint64]map[gethcommon.Address]map[gethcommon.Address]*hexutil.Big, error) {
   105  	time.Sleep(100 * time.Millisecond) // simulate response time
   106  	return m.getBalanceBasedOnParams(accounts, tokenAddresses, chainIDs), nil
   107  }
   108  
   109  func (m *TokenManagerMock) GetCachedBalancesByChain(ctx context.Context, accounts, tokenAddresses []gethcommon.Address, chainIDs []uint64) (map[uint64]map[gethcommon.Address]map[gethcommon.Address]*hexutil.Big, error) {
   110  	time.Sleep(100 * time.Millisecond) // simulate response time
   111  	return m.getBalanceBasedOnParams(accounts, tokenAddresses, chainIDs), nil
   112  }
   113  
   114  func (m *TokenManagerMock) FindOrCreateTokenByAddress(ctx context.Context, chainID uint64, address gethcommon.Address) *walletToken.Token {
   115  	time.Sleep(100 * time.Millisecond) // simulate response time
   116  	return nil
   117  }
   118  
   119  type CollectiblesManagerMock struct {
   120  	Collectibles                 *communities.CollectiblesByChain
   121  	collectibleOwnershipResponse map[string][]thirdparty.AccountBalance
   122  }
   123  
   124  func (m *CollectiblesManagerMock) FetchCachedBalancesByOwnerAndContractAddress(ctx context.Context, chainID walletCommon.ChainID,
   125  	ownerAddress gethcommon.Address, contractAddresses []gethcommon.Address) (thirdparty.TokenBalancesPerContractAddress, error) {
   126  	return m.FetchBalancesByOwnerAndContractAddress(ctx, chainID, ownerAddress, contractAddresses)
   127  }
   128  
   129  func (m *CollectiblesManagerMock) FetchBalancesByOwnerAndContractAddress(ctx context.Context, chainID walletCommon.ChainID,
   130  	ownerAddress gethcommon.Address, contractAddresses []gethcommon.Address) (thirdparty.TokenBalancesPerContractAddress, error) {
   131  	ret := make(thirdparty.TokenBalancesPerContractAddress)
   132  	accountsBalances, ok := (*m.Collectibles)[uint64(chainID)]
   133  	if !ok {
   134  		return ret, nil
   135  	}
   136  
   137  	balances, ok := accountsBalances[ownerAddress]
   138  	if !ok {
   139  		return ret, nil
   140  	}
   141  
   142  	for _, contractAddress := range contractAddresses {
   143  		balance, ok := balances[contractAddress]
   144  		if ok {
   145  			ret[contractAddress] = balance
   146  		}
   147  	}
   148  
   149  	return ret, nil
   150  }
   151  
   152  func (m *CollectiblesManagerMock) GetCollectibleOwnership(requestedID thirdparty.CollectibleUniqueID) ([]thirdparty.AccountBalance, error) {
   153  	for id, balances := range m.collectibleOwnershipResponse {
   154  		if id == requestedID.HashKey() {
   155  			return balances, nil
   156  		}
   157  	}
   158  	return []thirdparty.AccountBalance{}, nil
   159  }
   160  
   161  func (m *CollectiblesManagerMock) FetchCollectibleOwnersByContractAddress(ctx context.Context, chainID walletCommon.ChainID, contractAddress gethcommon.Address) (*thirdparty.CollectibleContractOwnership, error) {
   162  	ret := &thirdparty.CollectibleContractOwnership{
   163  		ContractAddress: contractAddress,
   164  		Owners:          []thirdparty.CollectibleOwner{},
   165  	}
   166  	accountsBalances, ok := (*m.Collectibles)[uint64(chainID)]
   167  	if !ok {
   168  		return ret, nil
   169  	}
   170  
   171  	for wallet, balances := range accountsBalances {
   172  		balance, ok := balances[contractAddress]
   173  		if ok {
   174  			ret.Owners = append(ret.Owners, thirdparty.CollectibleOwner{
   175  				OwnerAddress:  wallet,
   176  				TokenBalances: balance,
   177  			})
   178  		}
   179  	}
   180  
   181  	return ret, nil
   182  }
   183  
   184  func (m *CollectiblesManagerMock) SetCollectibleOwnershipResponse(id thirdparty.CollectibleUniqueID, balances []thirdparty.AccountBalance) {
   185  	if m.collectibleOwnershipResponse == nil {
   186  		m.collectibleOwnershipResponse = map[string][]thirdparty.AccountBalance{}
   187  	}
   188  	m.collectibleOwnershipResponse[id.HashKey()] = balances
   189  }
   190  
   191  type CollectiblesServiceMock struct {
   192  	Collectibles map[uint64]map[string]*communities.CollectibleContractData
   193  	Assets       map[uint64]map[string]*communities.AssetContractData
   194  	Signers      map[string]string
   195  }
   196  
   197  func (c *CollectiblesServiceMock) SetSignerPubkeyForCommunity(communityID []byte, signerPubKey string) {
   198  	if c.Signers == nil {
   199  		c.Signers = make(map[string]string)
   200  	}
   201  	c.Signers[types.EncodeHex(communityID)] = signerPubKey
   202  }
   203  
   204  func (c *CollectiblesServiceMock) SetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, newSignerPubKey string) (string, error) {
   205  	return "", nil
   206  }
   207  
   208  func (c *CollectiblesServiceMock) GetCollectibleContractData(chainID uint64, contractAddress string) (*communities.CollectibleContractData, error) {
   209  	collectibleContractData, dataExists := c.Collectibles[chainID][contractAddress]
   210  	if dataExists {
   211  		return collectibleContractData, nil
   212  	}
   213  	return nil, nil
   214  }
   215  
   216  func (c *CollectiblesServiceMock) GetAssetContractData(chainID uint64, contractAddress string) (*communities.AssetContractData, error) {
   217  	assetsContractData, dataExists := c.Assets[chainID][contractAddress]
   218  	if dataExists {
   219  		return assetsContractData, nil
   220  	}
   221  	return nil, nil
   222  }
   223  
   224  func (c *CollectiblesServiceMock) SetMockCollectibleContractData(chainID uint64, contractAddress string, collectible *communities.CollectibleContractData) {
   225  	if c.Collectibles == nil {
   226  		c.Collectibles = make(map[uint64]map[string]*communities.CollectibleContractData)
   227  	}
   228  	if _, ok := c.Collectibles[chainID]; !ok {
   229  		c.Collectibles[chainID] = make(map[string]*communities.CollectibleContractData)
   230  	}
   231  	c.Collectibles[chainID][contractAddress] = collectible
   232  }
   233  
   234  func (c *CollectiblesServiceMock) SetMockCommunityTokenData(token *token.CommunityToken) {
   235  	if c.Collectibles == nil {
   236  		c.Collectibles = make(map[uint64]map[string]*communities.CollectibleContractData)
   237  	}
   238  
   239  	data := &communities.CollectibleContractData{
   240  		TotalSupply:    token.Supply,
   241  		Transferable:   token.Transferable,
   242  		RemoteBurnable: token.RemoteSelfDestruct,
   243  		InfiniteSupply: token.InfiniteSupply,
   244  	}
   245  
   246  	c.SetMockCollectibleContractData(uint64(token.ChainID), token.Address, data)
   247  }
   248  
   249  func (c *CollectiblesServiceMock) SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error) {
   250  	if c.Signers == nil {
   251  		c.Signers = make(map[string]string)
   252  	}
   253  	return c.Signers[communityID], nil
   254  }
   255  
   256  func (c *CollectiblesServiceMock) SetMockAssetContractData(chainID uint64, contractAddress string, assetData *communities.AssetContractData) {
   257  	if c.Assets == nil {
   258  		c.Assets = make(map[uint64]map[string]*communities.AssetContractData)
   259  	}
   260  	c.Assets[chainID] = make(map[string]*communities.AssetContractData)
   261  	c.Assets[chainID][contractAddress] = assetData
   262  }
   263  
   264  func (c *CollectiblesServiceMock) DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) {
   265  	return gethcommon.Hex2Bytes("ccbb375343347491706cf4b43796f7b96ccc89c9e191a8b78679daeba1684ec7"), nil
   266  }
   267  
   268  func (s *CollectiblesServiceMock) ProcessCommunityTokenAction(message *protobuf.CommunityTokenAction) error {
   269  	return nil
   270  }
   271  
   272  type testCommunitiesMessengerConfig struct {
   273  	testMessengerConfig
   274  
   275  	nodeConfig  *params.NodeConfig
   276  	appSettings *settings.Settings
   277  
   278  	password            string
   279  	walletAddresses     []string
   280  	mockedBalances      *communities.BalancesByChain
   281  	collectiblesService communities.CommunityTokensServiceInterface
   282  	collectiblesManager communities.CollectiblesManager
   283  }
   284  
   285  func (tcmc *testCommunitiesMessengerConfig) complete() error {
   286  	err := tcmc.testMessengerConfig.complete()
   287  	if err != nil {
   288  		return err
   289  	}
   290  
   291  	if tcmc.nodeConfig == nil {
   292  		tcmc.nodeConfig = defaultTestCommunitiesMessengerNodeConfig()
   293  	}
   294  	if tcmc.appSettings == nil {
   295  		tcmc.appSettings = defaultTestCommunitiesMessengerSettings()
   296  	}
   297  
   298  	return nil
   299  }
   300  
   301  func defaultTestCommunitiesMessengerNodeConfig() *params.NodeConfig {
   302  	return &params.NodeConfig{
   303  		NetworkID: 10,
   304  		DataDir:   "test",
   305  	}
   306  }
   307  func defaultTestCommunitiesMessengerSettings() *settings.Settings {
   308  	networks := json.RawMessage("{}")
   309  	return &settings.Settings{
   310  		Address:                   types.HexToAddress("0x1122334455667788990011223344556677889900"),
   311  		AnonMetricsShouldSend:     false,
   312  		CurrentNetwork:            "mainnet_rpc",
   313  		DappsAddress:              types.HexToAddress("0x1122334455667788990011223344556677889900"),
   314  		InstallationID:            "d3efcff6-cffa-560e-a547-21d3858cbc51",
   315  		KeyUID:                    "0x1122334455667788990011223344556677889900",
   316  		Name:                      "Test",
   317  		Networks:                  &networks,
   318  		LatestDerivedPath:         0,
   319  		PhotoPath:                 "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAIAAACRXR/mAAAAjklEQVR4nOzXwQmFMBAAUZXUYh32ZB32ZB02sxYQQSZGsod55/91WFgSS0RM+SyjA56ZRZhFmEWYRRT6h+M6G16zrxv6fdJpmUWYRbxsYr13dKfanpN0WmYRZhGzXz6AWYRZRIfbaX26fT9Jk07LLMIsosPt9I/dTDotswizCG+nhFmEWYRZhFnEHQAA///z1CFkYamgfQAAAABJRU5ErkJggg==",
   320  		PreviewPrivacy:            false,
   321  		PublicKey:                 "0x04112233445566778899001122334455667788990011223344556677889900112233445566778899001122334455667788990011223344556677889900",
   322  		SigningPhrase:             "yurt joey vibe",
   323  		SendPushNotifications:     true,
   324  		ProfilePicturesVisibility: 1,
   325  		DefaultSyncPeriod:         777600,
   326  		UseMailservers:            true,
   327  		LinkPreviewRequestEnabled: true,
   328  		SendStatusUpdates:         true,
   329  		WalletRootAddress:         types.HexToAddress("0x1122334455667788990011223344556677889900")}
   330  }
   331  
   332  func newTestCommunitiesMessenger(s *suite.Suite, waku types.Waku, config testCommunitiesMessengerConfig) *Messenger {
   333  	err := config.complete()
   334  	s.Require().NoError(err)
   335  
   336  	accountsManagerMock := &AccountManagerMock{}
   337  	accountsManagerMock.AccountsMap = make(map[string]string)
   338  	for _, walletAddress := range config.walletAddresses {
   339  		accountsManagerMock.AccountsMap[walletAddress] = types.EncodeHex(crypto.Keccak256([]byte(config.password)))
   340  	}
   341  
   342  	tokenManagerMock := &TokenManagerMock{
   343  		Balances: config.mockedBalances,
   344  	}
   345  
   346  	options := []Option{
   347  		WithAccountManager(accountsManagerMock),
   348  		WithTokenManager(tokenManagerMock),
   349  		WithCollectiblesManager(config.collectiblesManager),
   350  		WithCommunityTokensService(config.collectiblesService),
   351  		WithAppSettings(*config.appSettings, *config.nodeConfig),
   352  	}
   353  
   354  	config.extraOptions = append(config.extraOptions, options...)
   355  
   356  	messenger, err := newTestMessenger(waku, config.testMessengerConfig)
   357  	s.Require().NoError(err)
   358  
   359  	currentDistributorObj, ok := messenger.communitiesKeyDistributor.(*CommunitiesKeyDistributorImpl)
   360  	s.Require().True(ok)
   361  	messenger.communitiesKeyDistributor = &TestCommunitiesKeyDistributor{
   362  		CommunitiesKeyDistributorImpl: *currentDistributorObj,
   363  		subscriptions:                 map[chan *CommunityAndKeyActions]bool{},
   364  		mutex:                         sync.RWMutex{},
   365  	}
   366  
   367  	// add wallet account with keypair
   368  	for _, walletAddress := range config.walletAddresses {
   369  		kp := accounts.GetProfileKeypairForTest(false, true, false)
   370  		kp.Accounts[0].Address = types.HexToAddress(walletAddress)
   371  		err := messenger.settings.SaveOrUpdateKeypair(kp)
   372  		s.Require().NoError(err)
   373  	}
   374  
   375  	walletAccounts, err := messenger.settings.GetActiveAccounts()
   376  	s.Require().NoError(err)
   377  	s.Require().Len(walletAccounts, len(config.walletAddresses))
   378  	for i := range config.walletAddresses {
   379  		s.Require().Equal(walletAccounts[i].Type, accounts.AccountTypeGenerated)
   380  	}
   381  	return messenger
   382  }
   383  
   384  func createEncryptedCommunity(s *suite.Suite, owner *Messenger) (*communities.Community, *Chat) {
   385  	community, chat := createCommunityConfigurable(s, owner, protobuf.CommunityPermissions_AUTO_ACCEPT)
   386  	// Add community permission
   387  	_, err := owner.CreateCommunityTokenPermission(&requests.CreateCommunityTokenPermission{
   388  		CommunityID: community.ID(),
   389  		Type:        protobuf.CommunityTokenPermission_BECOME_MEMBER,
   390  		TokenCriteria: []*protobuf.TokenCriteria{{
   391  			ContractAddresses: map[uint64]string{3: "0x933"},
   392  			Type:              protobuf.CommunityTokenType_ERC20,
   393  			Symbol:            "STT",
   394  			Name:              "Status Test Token",
   395  			AmountInWei:       "10000000000000000000",
   396  			Decimals:          18,
   397  		}},
   398  	})
   399  	s.Require().NoError(err)
   400  
   401  	// Add channel permission
   402  	response, err := owner.CreateCommunityTokenPermission(&requests.CreateCommunityTokenPermission{
   403  		CommunityID: community.ID(),
   404  		Type:        protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
   405  		TokenCriteria: []*protobuf.TokenCriteria{
   406  			&protobuf.TokenCriteria{
   407  				ContractAddresses: map[uint64]string{3: "0x933"},
   408  				Type:              protobuf.CommunityTokenType_ERC20,
   409  				Symbol:            "STT",
   410  				Name:              "Status Test Token",
   411  				AmountInWei:       "10000000000000000000",
   412  				Decimals:          18,
   413  			},
   414  		},
   415  		ChatIds: []string{chat.ID},
   416  	})
   417  	s.Require().NoError(err)
   418  	s.Require().Len(response.Communities(), 1)
   419  	community = response.Communities()[0]
   420  	s.Require().True(community.Encrypted())
   421  	s.Require().True(community.ChannelEncrypted(chat.CommunityChatID()))
   422  
   423  	return community, chat
   424  
   425  }
   426  
   427  func createCommunity(s *suite.Suite, owner *Messenger) (*communities.Community, *Chat) {
   428  	return createCommunityConfigurable(s, owner, protobuf.CommunityPermissions_AUTO_ACCEPT)
   429  }
   430  
   431  func createOnRequestCommunity(s *suite.Suite, owner *Messenger) (*communities.Community, *Chat) {
   432  	return createCommunityConfigurable(s, owner, protobuf.CommunityPermissions_MANUAL_ACCEPT)
   433  }
   434  
   435  func createCommunityConfigurable(s *suite.Suite, owner *Messenger, permission protobuf.CommunityPermissions_Access) (*communities.Community, *Chat) {
   436  	description := &requests.CreateCommunity{
   437  		Membership:  permission,
   438  		Name:        "status",
   439  		Color:       "#ffffff",
   440  		Description: "status community description",
   441  	}
   442  
   443  	// Create an community chat
   444  	response, err := owner.CreateCommunity(description, true)
   445  	s.Require().NoError(err)
   446  	s.Require().NotNil(response)
   447  
   448  	s.Require().Len(response.Communities(), 1)
   449  	community := response.Communities()[0]
   450  	s.Require().True(community.Joined())
   451  	s.Require().True(community.IsControlNode())
   452  	s.Require().Len(community.Chats(), 1)
   453  
   454  	s.Require().Len(response.CommunitiesSettings(), 1)
   455  	communitySettings := response.CommunitiesSettings()[0]
   456  	s.Require().Equal(communitySettings.CommunityID, community.IDString())
   457  	s.Require().Equal(communitySettings.HistoryArchiveSupportEnabled, false)
   458  
   459  	return community, response.Chats()[0]
   460  }
   461  
   462  func advertiseCommunityTo(s *suite.Suite, community *communities.Community, owner *Messenger, user *Messenger) {
   463  	// Create wrapped (Signed) community data.
   464  	wrappedCommunity, err := community.ToProtocolMessageBytes()
   465  	s.Require().NoError(err)
   466  
   467  	// Unwrap signer (Admin) data at user side.
   468  	signer, description, err := communities.UnwrapCommunityDescriptionMessage(wrappedCommunity)
   469  	s.Require().NoError(err)
   470  
   471  	// Handle community data state at receiver side
   472  	messageState := user.buildMessageState()
   473  	messageState.CurrentMessageState = &CurrentMessageState{}
   474  	messageState.CurrentMessageState.PublicKey = &user.identity.PublicKey
   475  	err = user.handleCommunityDescription(messageState, signer, description, wrappedCommunity, nil, community.Shard().Protobuffer())
   476  	s.Require().NoError(err)
   477  }
   478  
   479  func createRequestToJoinCommunity(s *suite.Suite, communityID types.HexBytes, user *Messenger, password string, addresses []string) *requests.RequestToJoinCommunity {
   480  	airdropAddress := ""
   481  	if len(addresses) > 0 {
   482  		airdropAddress = addresses[0]
   483  	}
   484  
   485  	request := &requests.RequestToJoinCommunity{
   486  		CommunityID:       communityID,
   487  		AddressesToReveal: addresses,
   488  		AirdropAddress:    airdropAddress}
   489  
   490  	if password != "" {
   491  		signingParams, err := user.GenerateJoiningCommunityRequestsForSigning(common.PubkeyToHex(&user.identity.PublicKey), communityID, request.AddressesToReveal)
   492  		s.Require().NoError(err)
   493  
   494  		for i := range signingParams {
   495  			signingParams[i].Password = password
   496  		}
   497  		signatures, err := user.SignData(signingParams)
   498  		s.Require().NoError(err)
   499  
   500  		updateAddresses := len(request.AddressesToReveal) == 0
   501  		if updateAddresses {
   502  			request.AddressesToReveal = make([]string, len(signingParams))
   503  		}
   504  		for i := range signingParams {
   505  			request.AddressesToReveal[i] = signingParams[i].Address
   506  			request.Signatures = append(request.Signatures, types.FromHex(signatures[i]))
   507  		}
   508  		if updateAddresses {
   509  			request.AirdropAddress = request.AddressesToReveal[0]
   510  		}
   511  	}
   512  
   513  	return request
   514  }
   515  
   516  func joinCommunity(s *suite.Suite, communityID types.HexBytes, controlNode *Messenger, user *Messenger, password string, addresses []string) {
   517  	requestToJoin := createRequestToJoinCommunity(s, communityID, user, password, addresses)
   518  	response, err := user.RequestToJoinCommunity(requestToJoin)
   519  	s.Require().NoError(err)
   520  	s.Require().NotNil(response)
   521  	s.Require().Len(response.RequestsToJoinCommunity(), 1)
   522  	s.Require().Len(response.ActivityCenterNotifications(), 1)
   523  
   524  	notification := response.ActivityCenterNotifications()[0]
   525  	s.Require().NotNil(notification)
   526  	s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityRequest)
   527  	s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusPending)
   528  
   529  	// Retrieve and accept join request
   530  	_, err = WaitOnMessengerResponse(controlNode, func(r *MessengerResponse) bool {
   531  		return len(r.Communities()) > 0 && r.Communities()[0].HasMember(&user.identity.PublicKey)
   532  	}, "control node did accept user request to join")
   533  	s.Require().NoError(err)
   534  
   535  	// Retrieve join request response
   536  	_, err = WaitOnMessengerResponse(user, func(r *MessengerResponse) bool {
   537  		return len(r.Communities()) > 0 && r.Communities()[0].HasMember(&user.identity.PublicKey) &&
   538  			// Note: 'handleCommunityRequestToJoinResponse' does not return RequestToJoin with revealed addresses
   539  			checkRequestToJoinInResponse(r, user, communities.RequestToJoinStateAccepted, 0)
   540  	}, "user not accepted")
   541  	s.Require().NoError(err)
   542  }
   543  
   544  func requestToJoinCommunity(s *suite.Suite, controlNode *Messenger, user *Messenger, request *requests.RequestToJoinCommunity) types.HexBytes {
   545  	response, err := user.RequestToJoinCommunity(request)
   546  	s.Require().NoError(err)
   547  	s.Require().NotNil(response)
   548  	s.Require().Len(response.RequestsToJoinCommunity(), 1)
   549  
   550  	requestToJoin := response.RequestsToJoinCommunity()[0]
   551  	s.Require().Equal(requestToJoin.PublicKey, common.PubkeyToHex(&user.identity.PublicKey))
   552  
   553  	_, err = WaitOnMessengerResponse(
   554  		controlNode,
   555  		func(r *MessengerResponse) bool {
   556  			return checkRequestToJoinInResponse(r, user, communities.RequestToJoinStatePending, 1)
   557  		},
   558  		"control node did not receive community request to join",
   559  	)
   560  	s.Require().NoError(err)
   561  
   562  	return requestToJoin.ID
   563  }
   564  
   565  func joinOnRequestCommunity(s *suite.Suite, communityID types.HexBytes, controlNode *Messenger, user *Messenger, password string, addresses []string) {
   566  	s.Require().NotEmpty(password)
   567  	s.Require().NotEmpty(addresses)
   568  	s.Require().NotEmpty(communityID)
   569  	request := createRequestToJoinCommunity(s, communityID, user, password, addresses)
   570  	// Request to join the community
   571  	requestToJoinID := requestToJoinCommunity(s, controlNode, user, request)
   572  
   573  	// accept join request
   574  	acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: requestToJoinID}
   575  	response, err := controlNode.AcceptRequestToJoinCommunity(acceptRequestToJoin)
   576  	s.Require().NoError(err)
   577  	s.Require().NotNil(response)
   578  
   579  	updatedCommunity := response.Communities()[0]
   580  	s.Require().NotNil(updatedCommunity)
   581  	s.Require().True(updatedCommunity.HasMember(&user.identity.PublicKey))
   582  
   583  	// receive request to join response
   584  	_, err = WaitOnMessengerResponse(
   585  		user,
   586  		func(r *MessengerResponse) bool {
   587  			return len(r.Communities()) > 0 && r.Communities()[0].HasMember(&user.identity.PublicKey)
   588  		},
   589  		"user did not receive request to join response",
   590  	)
   591  	s.Require().NoError(err)
   592  
   593  	userCommunity, err := user.GetCommunityByID(communityID)
   594  	s.Require().NoError(err)
   595  	s.Require().True(userCommunity.HasMember(&user.identity.PublicKey))
   596  
   597  	_, err = WaitOnMessengerResponse(
   598  		controlNode,
   599  		func(r *MessengerResponse) bool {
   600  			return len(r.Communities()) > 0 && r.Communities()[0].HasMember(&user.identity.PublicKey)
   601  		},
   602  		"control node did not receive request to join response",
   603  	)
   604  	s.Require().NoError(err)
   605  }
   606  
   607  func sendChatMessage(s *suite.Suite, sender *Messenger, chatID string, text string) *common.Message {
   608  	msg := &common.Message{
   609  		ChatMessage: &protobuf.ChatMessage{
   610  			ChatId:      chatID,
   611  			ContentType: protobuf.ChatMessage_TEXT_PLAIN,
   612  			Text:        text,
   613  		},
   614  	}
   615  
   616  	_, err := sender.SendChatMessage(context.Background(), msg)
   617  	s.Require().NoError(err)
   618  
   619  	return msg
   620  }
   621  
   622  func grantPermission(s *suite.Suite, community *communities.Community, controlNode *Messenger, target *Messenger, role protobuf.CommunityMember_Roles) {
   623  	responseAddRole, err := controlNode.AddRoleToMember(&requests.AddRoleToMember{
   624  		CommunityID: community.ID(),
   625  		User:        common.PubkeyToHexBytes(target.IdentityPublicKey()),
   626  		Role:        role,
   627  	})
   628  	s.Require().NoError(err)
   629  	s.Require().NoError(checkRolePermissionInResponse(responseAddRole, target.IdentityPublicKey(), role))
   630  
   631  	response, err := WaitOnMessengerResponse(target, func(response *MessengerResponse) bool {
   632  		if len(response.Communities()) == 0 {
   633  			return false
   634  		}
   635  
   636  		err := checkRolePermissionInResponse(response, target.IdentityPublicKey(), role)
   637  
   638  		return err == nil
   639  	}, "community description changed message not received")
   640  	s.Require().NoError(err)
   641  	s.Require().NoError(checkRolePermissionInResponse(response, target.IdentityPublicKey(), role))
   642  }
   643  
   644  func checkRolePermissionInResponse(response *MessengerResponse, member *ecdsa.PublicKey, role protobuf.CommunityMember_Roles) error {
   645  	if len(response.Communities()) == 0 {
   646  		return errors.New("Response does not contain communities")
   647  	}
   648  	rCommunities := response.Communities()
   649  	switch role {
   650  	case protobuf.CommunityMember_ROLE_OWNER:
   651  		if !rCommunities[0].IsMemberOwner(member) {
   652  			return errors.New("Member without owner role")
   653  		}
   654  	case protobuf.CommunityMember_ROLE_ADMIN:
   655  		if !rCommunities[0].IsMemberAdmin(member) {
   656  			return errors.New("Member without admin role")
   657  		}
   658  	case protobuf.CommunityMember_ROLE_TOKEN_MASTER:
   659  		if !rCommunities[0].IsMemberTokenMaster(member) {
   660  			return errors.New("Member without token master role")
   661  		}
   662  	default:
   663  		return errors.New("Can't check unknonw member role")
   664  	}
   665  
   666  	return nil
   667  }
   668  
   669  func checkMemberJoinedToTheCommunity(response *MessengerResponse, member *ecdsa.PublicKey) error {
   670  	if len(response.Communities()) == 0 {
   671  		return errors.New("No communities in the response")
   672  	}
   673  
   674  	if !response.Communities()[0].HasMember(member) {
   675  		return errors.New("Member was not added to the community")
   676  	}
   677  
   678  	return nil
   679  }
   680  
   681  func waitOnCommunitiesEvent(user *Messenger, condition func(*communities.Subscription) bool) <-chan error {
   682  	errCh := make(chan error, 1)
   683  
   684  	go func() {
   685  		defer close(errCh)
   686  		subscription := user.communitiesManager.Subscribe()
   687  
   688  		for {
   689  			select {
   690  			case sub, more := <-subscription:
   691  				if !more {
   692  					errCh <- errors.New("channel closed when waiting for communities event")
   693  					return
   694  				}
   695  				if condition(sub) {
   696  					return
   697  				}
   698  
   699  			case <-time.After(5 * time.Second):
   700  				errCh <- errors.New("timed out when waiting for communities event")
   701  				return
   702  			}
   703  		}
   704  	}()
   705  
   706  	return errCh
   707  }
   708  
   709  func makeAddressSatisfyTheCriteria(s *suite.Suite, mockedBalances communities.BalancesByChain, mockedCollectibles communities.CollectiblesByChain,
   710  	chainID uint64, address string, criteria *protobuf.TokenCriteria) {
   711  
   712  	walletAddress := gethcommon.HexToAddress(address)
   713  	contractAddress := gethcommon.HexToAddress(criteria.ContractAddresses[chainID])
   714  
   715  	switch criteria.Type {
   716  	case protobuf.CommunityTokenType_ERC20:
   717  		balance, ok := new(big.Int).SetString(criteria.AmountInWei, 10)
   718  		s.Require().True(ok)
   719  
   720  		if _, exists := mockedBalances[chainID]; !exists {
   721  			mockedBalances[chainID] = make(map[gethcommon.Address]map[gethcommon.Address]*hexutil.Big)
   722  		}
   723  
   724  		if _, exists := mockedBalances[chainID][walletAddress]; !exists {
   725  			mockedBalances[chainID][walletAddress] = make(map[gethcommon.Address]*hexutil.Big)
   726  		}
   727  
   728  		mockedBalances[chainID][walletAddress][contractAddress] = (*hexutil.Big)(balance)
   729  
   730  	case protobuf.CommunityTokenType_ERC721:
   731  		amount, err := strconv.ParseUint(criteria.AmountInWei, 10, 32)
   732  		s.Require().NoError(err)
   733  
   734  		balances := []thirdparty.TokenBalance{}
   735  		for i := uint64(0); i < amount; i++ {
   736  			balances = append(balances, thirdparty.TokenBalance{
   737  				TokenID: &bigint.BigInt{
   738  					Int: new(big.Int).SetUint64(i + 1),
   739  				},
   740  				Balance: &bigint.BigInt{
   741  					Int: new(big.Int).SetUint64(1),
   742  				},
   743  			})
   744  		}
   745  
   746  		if _, exists := mockedCollectibles[chainID]; !exists {
   747  			mockedCollectibles[chainID] = make(map[gethcommon.Address]thirdparty.TokenBalancesPerContractAddress)
   748  		}
   749  
   750  		if _, exists := mockedCollectibles[chainID][walletAddress]; !exists {
   751  			mockedCollectibles[chainID][walletAddress] = make(thirdparty.TokenBalancesPerContractAddress)
   752  		}
   753  
   754  		mockedCollectibles[chainID][walletAddress][contractAddress] = balances
   755  
   756  	case protobuf.CommunityTokenType_ENS:
   757  		// not implemented
   758  	}
   759  }
   760  
   761  func checkRequestToJoinInResponse(r *MessengerResponse, member *Messenger, state communities.RequestToJoinState, accountsCount int) bool {
   762  	for _, request := range r.RequestsToJoinCommunity() {
   763  		if request.PublicKey == member.IdentityPublicKeyString() &&
   764  			request.State == state &&
   765  			accountsCount == len(request.RevealedAccounts) {
   766  			return true
   767  		}
   768  	}
   769  	return false
   770  }