status-go/protocol/communities_messenger_signe...

915 lines
31 KiB
Go
Raw Permalink Normal View History

package protocol
import (
"bytes"
"context"
"testing"
"time"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/suite"
"go.uber.org/zap"
utils "github.com/status-im/status-go/common"
"github.com/status-im/status-go/protocol/common"
"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/protocol/requests"
"github.com/status-im/status-go/services/wallet/bigint"
)
func TestMessengerCommunitiesSignersSuite(t *testing.T) {
suite.Run(t, new(MessengerCommunitiesSignersSuite))
}
type MessengerCommunitiesSignersSuite struct {
CommunitiesMessengerTestSuiteBase
john *Messenger
bob *Messenger
alice *Messenger
}
func (s *MessengerCommunitiesSignersSuite) SetupTest() {
s.CommunitiesMessengerTestSuiteBase.SetupTest()
communities.SetValidateInterval(300 * time.Millisecond)
s.john = s.newMessenger(accountPassword, []string{commonAccountAddress})
s.bob = s.newMessenger(accountPassword, []string{bobAddress})
s.alice = s.newMessenger(accountPassword, []string{aliceAddress1})
_, err := s.john.Start()
s.Require().NoError(err)
_, err = s.bob.Start()
s.Require().NoError(err)
_, err = s.alice.Start()
s.Require().NoError(err)
}
func (s *MessengerCommunitiesSignersSuite) TearDownTest() {
TearDownMessenger(&s.Suite, s.john)
TearDownMessenger(&s.Suite, s.bob)
TearDownMessenger(&s.Suite, s.alice)
s.CommunitiesMessengerTestSuiteBase.TearDownTest()
}
func (s *MessengerCommunitiesSignersSuite) newMessenger(password string, walletAddresses []string) *Messenger {
communityManagerOptions := []communities.ManagerOption{
communities.WithAllowForcingCommunityMembersReevaluation(true),
}
return s.newMessengerWithConfig(testMessengerConfig{
logger: s.logger,
extraOptions: []Option{WithCommunityManagerOptions(communityManagerOptions)},
}, password, walletAddresses)
}
func (s *MessengerCommunitiesSignersSuite) createCommunity(controlNode *Messenger) *communities.Community {
community, _ := createCommunity(&s.Suite, controlNode)
return community
}
func (s *MessengerCommunitiesSignersSuite) advertiseCommunityTo(controlNode *Messenger, community *communities.Community, user *Messenger) {
advertiseCommunityTo(&s.Suite, community, controlNode, user)
}
// John crates a community
// Ownership is transferred to Alice
// Alice kick all members Bob and John
// Bob automatically rejoin
// John receive AC notification to share the address and join to the community
// Bob and John accepts the changes
func (s *MessengerCommunitiesSignersSuite) TestControlNodeUpdateSigner() {
// Create a community
// Transfer ownership
// Process message
community := s.createCommunity(s.john)
s.advertiseCommunityTo(s.john, community, s.bob)
s.advertiseCommunityTo(s.john, community, s.alice)
s.joinCommunity(community, s.john, s.bob)
s.joinCommunity(community, s.john, s.alice)
// john mints owner token
var chainID uint64 = 1
tokenAddress := "token-address"
tokenName := "tokenName"
tokenSymbol := "TSM"
_, err := s.john.SaveCommunityToken(&token.CommunityToken{
TokenType: protobuf.CommunityTokenType_ERC721,
CommunityID: community.IDString(),
Address: tokenAddress,
ChainID: int(chainID),
Name: tokenName,
Supply: &bigint.BigInt{},
Symbol: tokenSymbol,
PrivilegesLevel: token.OwnerLevel,
}, nil)
s.Require().NoError(err)
// john adds minted owner token to community
err = s.john.AddCommunityToken(community.IDString(), int(chainID), tokenAddress)
s.Require().NoError(err)
// 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,
&communities.CollectibleContractData{TotalSupply: &bigint.BigInt{}})
// bob accepts community update
_, err = WaitOnSignaledMessengerResponse(
s.bob,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && len(r.Communities()[0].TokenPermissions()) == 1
},
"no communities",
)
s.Require().NoError(err)
// alice accepts community update
_, err = WaitOnSignaledMessengerResponse(
s.alice,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && len(r.Communities()[0].TokenPermissions()) == 1
},
"no communities",
)
s.Require().NoError(err)
// Ownership token will be transferred to Alice and she will kick all members
// and request kicked members to rejoin
// the signer for the community returned by the contracts should be alice
s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.alice.identity.PublicKey))
response, err := s.alice.PromoteSelfToControlNode(community.ID())
s.Require().NoError(err)
s.Require().NotNil(response)
community, err = s.alice.communitiesManager.GetByID(community.ID())
s.Require().NoError(err)
s.Require().True(community.IsControlNode())
s.Require().True(common.IsPubKeyEqual(community.ControlNode(), &s.alice.identity.PublicKey))
s.Require().True(community.IsOwner())
// check that Bob received kick event, also he will receive
// request to share RevealedAddresses and send request to join to the control node
_, err = WaitOnSignaledMessengerResponse(
s.bob,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && !r.Communities()[0].HasMember(&s.bob.identity.PublicKey) &&
!r.Communities()[0].Joined() && r.Communities()[0].Spectated() &&
len(r.ActivityCenterNotifications()) == 0
},
"Bob was not kicked from the community",
)
s.Require().NoError(err)
// check that John received kick event, and AC notification msg created
// John, as ex-owner must manually join the community
_, err = WaitOnSignaledMessengerResponse(
s.john,
func(r *MessengerResponse) bool {
inSpectateMode := len(r.Communities()) > 0 && !r.Communities()[0].HasMember(&s.john.identity.PublicKey) &&
!r.Communities()[0].Joined() && r.Communities()[0].Spectated()
sharedNotificationExist := false
for _, acNotification := range r.ActivityCenterNotifications() {
if acNotification.Type == ActivityCenterNotificationTypeShareAccounts {
sharedNotificationExist = true
break
}
}
return inSpectateMode && sharedNotificationExist
},
"John was not kicked from the community",
)
s.Require().NoError(err)
// Alice auto-accept requests to join with RevealedAddresses
_, err = WaitOnMessengerResponse(
s.alice,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && len(r.Communities()[0].Members()) == 2
},
"no community update with accepted request",
)
s.Require().NoError(err)
validateResults := func(messenger *Messenger) *communities.Community {
community, err = messenger.communitiesManager.GetByID(community.ID())
s.Require().NoError(err)
s.Require().True(common.IsPubKeyEqual(community.ControlNode(), &s.alice.identity.PublicKey))
s.Require().Len(community.Members(), 2)
s.Require().True(community.HasMember(&messenger.identity.PublicKey))
return community
}
community = validateResults(s.alice)
s.Require().True(community.IsControlNode())
s.Require().True(community.IsOwner())
// Bob is a community member again
_, err = WaitOnMessengerResponse(
s.bob,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && r.Communities()[0].HasMember(&s.bob.identity.PublicKey) &&
r.Communities()[0].Joined() && !r.Communities()[0].Spectated()
},
"Bob was auto-accepted",
)
s.Require().NoError(err)
community = validateResults(s.bob)
s.Require().False(community.IsControlNode())
s.Require().False(community.IsOwner())
// John manually joins the community
s.joinCommunity(community, s.alice, s.john)
// Alice change community name
expectedName := "Alice owns community"
response, err = s.alice.EditCommunity(&requests.EditCommunity{
CommunityID: community.ID(),
CreateCommunity: requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
Name: expectedName,
Color: "#000000",
Description: "edited community description",
},
})
s.Require().NoError(err)
s.Require().NotNil(response)
validateNameInResponse := func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && r.Communities()[0].IDString() == community.IDString() &&
r.Communities()[0].Name() == expectedName
}
s.Require().True(validateNameInResponse(response))
validateNameInDB := func(messenger *Messenger) {
community, err = messenger.communitiesManager.GetByID(community.ID())
s.Require().NoError(err)
s.Require().Equal(expectedName, response.Communities()[0].Name())
}
validateNameInDB(s.alice)
// john accepts community update from alice (new control node)
_, err = WaitOnMessengerResponse(
s.john,
validateNameInResponse,
"john did not receive community name update",
)
s.Require().NoError(err)
validateNameInDB(s.john)
// bob accepts community update from alice (new control node)
_, err = WaitOnMessengerResponse(
s.bob,
validateNameInResponse,
"bob did not receive community name update",
)
s.Require().NoError(err)
validateNameInDB(s.bob)
}
func (s *MessengerCommunitiesSignersSuite) TestAutoAcceptOnOwnershipChangeRequestRequired() {
community, _ := createOnRequestCommunity(&s.Suite, s.john)
s.advertiseCommunityTo(s.john, community, s.bob)
s.advertiseCommunityTo(s.john, community, s.alice)
s.joinOnRequestCommunity(community, s.john, s.bob)
s.joinOnRequestCommunity(community, s.john, s.alice)
// john mints owner token
var chainID uint64 = 1
tokenAddress := "token-address"
tokenName := "tokenName"
tokenSymbol := "TSM"
_, err := s.john.SaveCommunityToken(&token.CommunityToken{
TokenType: protobuf.CommunityTokenType_ERC721,
CommunityID: community.IDString(),
Address: tokenAddress,
ChainID: int(chainID),
Name: tokenName,
Supply: &bigint.BigInt{},
Symbol: tokenSymbol,
PrivilegesLevel: token.OwnerLevel,
}, nil)
s.Require().NoError(err)
err = s.john.AddCommunityToken(community.IDString(), int(chainID), tokenAddress)
s.Require().NoError(err)
// set john as contract owner
s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.john.identity.PublicKey))
s.collectiblesServiceMock.SetMockCollectibleContractData(chainID, tokenAddress,
&communities.CollectibleContractData{TotalSupply: &bigint.BigInt{}})
hasTokenPermission := func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && r.Communities()[0].HasTokenPermissions()
}
// bob received owner permissions
_, err = WaitOnSignaledMessengerResponse(
s.bob,
hasTokenPermission,
"no communities with token permission for Bob",
)
s.Require().NoError(err)
// alice received owner permissions
_, err = WaitOnSignaledMessengerResponse(
s.alice,
hasTokenPermission,
"no communities with token permission for Alice",
)
s.Require().NoError(err)
// simulate Alice received owner token
s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.alice.identity.PublicKey))
// after receiving owner token - set up control node, set up owner role, kick all members
// and request kicked members to rejoin
response, err := s.alice.PromoteSelfToControlNode(community.ID())
s.Require().NoError(err)
s.Require().NotNil(response)
community, err = s.alice.communitiesManager.GetByID(community.ID())
s.Require().NoError(err)
s.Require().True(community.IsControlNode())
s.Require().True(common.IsPubKeyEqual(community.ControlNode(), &s.alice.identity.PublicKey))
s.Require().True(community.IsOwner())
// check that client received kick event
// Bob will receive request to share RevealedAddresses and send request to join to the control node
_, err = WaitOnSignaledMessengerResponse(
s.bob,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && !r.Communities()[0].HasMember(&s.bob.identity.PublicKey)
},
"Bob was not kicked from the community",
)
s.Require().NoError(err)
// check that client received kick event
// John will receive request to share RevealedAddresses and send request to join to the control node
_, err = WaitOnSignaledMessengerResponse(
s.john,
func(r *MessengerResponse) bool {
wasKicked := len(r.Communities()) > 0 && !r.Communities()[0].HasMember(&s.john.identity.PublicKey)
sharedNotificationExist := false
for _, acNotification := range r.ActivityCenterNotifications() {
if acNotification.Type == ActivityCenterNotificationTypeShareAccounts {
sharedNotificationExist = true
break
}
}
return wasKicked && sharedNotificationExist
},
"John was not kicked from the community",
)
s.Require().NoError(err)
// Alice auto-accept requests to join with RevealedAddresses
_, err = WaitOnMessengerResponse(
s.alice,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && len(r.Communities()[0].Members()) == 2
},
"no community update with accepted request",
)
s.Require().NoError(err)
validateResults := func(messenger *Messenger) *communities.Community {
community, err = messenger.communitiesManager.GetByID(community.ID())
s.Require().NoError(err)
s.Require().True(common.IsPubKeyEqual(community.ControlNode(), &s.alice.identity.PublicKey))
s.Require().Len(community.Members(), 2)
s.Require().True(community.HasMember(&messenger.identity.PublicKey))
return community
}
community = validateResults(s.alice)
s.Require().True(community.IsControlNode())
s.Require().True(community.IsOwner())
_, err = WaitOnMessengerResponse(
s.bob,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && r.Communities()[0].HasMember(&s.bob.identity.PublicKey)
},
"Bob was auto-accepted",
)
s.Require().NoError(err)
community = validateResults(s.bob)
s.Require().False(community.IsControlNode())
s.Require().False(community.IsOwner())
}
func (s *MessengerCommunitiesSignersSuite) TestNewOwnerAcceptRequestToJoin() {
// Create a community
// Transfer ownership
// New owner accepts new request to join
community := s.createCommunity(s.john)
s.advertiseCommunityTo(s.john, community, s.alice)
s.joinCommunity(community, s.john, s.alice)
// john mints owner token
var chainID uint64 = 1
tokenAddress := "token-address"
tokenName := "tokenName"
tokenSymbol := "TSM"
_, err := s.john.SaveCommunityToken(&token.CommunityToken{
TokenType: protobuf.CommunityTokenType_ERC721,
CommunityID: community.IDString(),
Address: tokenAddress,
ChainID: int(chainID),
Name: tokenName,
Supply: &bigint.BigInt{},
Symbol: tokenSymbol,
PrivilegesLevel: token.OwnerLevel,
}, nil)
s.Require().NoError(err)
// john adds minted owner token to community
err = s.john.AddCommunityToken(community.IDString(), int(chainID), tokenAddress)
s.Require().NoError(err)
// 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,
&communities.CollectibleContractData{TotalSupply: &bigint.BigInt{}})
// alice accepts community update
_, err = WaitOnSignaledMessengerResponse(
s.alice,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && len(r.Communities()[0].TokenPermissions()) == 1
},
"no communities",
)
s.Require().NoError(err)
// Ownership token will be transferred to Alice and she will kick all members
// and request kicked members to rejoin
// the signer for the community returned by the contracts should be alice
s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.alice.identity.PublicKey))
response, err := s.alice.PromoteSelfToControlNode(community.ID())
s.Require().NoError(err)
s.Require().NotNil(response)
community, err = s.alice.communitiesManager.GetByID(community.ID())
s.Require().NoError(err)
s.Require().True(community.IsControlNode())
s.Require().True(common.IsPubKeyEqual(community.ControlNode(), &s.alice.identity.PublicKey))
s.Require().True(community.IsOwner())
// check that John received kick event, also he will receive
// request to share RevealedAddresses and send request to join to the control node
_, err = WaitOnSignaledMessengerResponse(
s.john,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && !r.Communities()[0].HasMember(&s.john.identity.PublicKey)
},
"John was not kicked from the community",
)
s.Require().NoError(err)
// Alice advertises community to Bob
chat := CreateOneToOneChat(common.PubkeyToHex(&s.bob.identity.PublicKey), &s.bob.identity.PublicKey, s.bob.transport)
inputMessage := common.NewMessage()
inputMessage.ChatId = chat.ID
inputMessage.Text = "some text"
inputMessage.CommunityID = community.IDString()
err = s.alice.SaveChat(chat)
s.Require().NoError(err)
_, err = s.alice.SendChatMessage(context.Background(), inputMessage)
s.Require().NoError(err)
_, err = WaitOnSignaledMessengerResponse(
s.bob,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0
},
"Community was not advertised to Bob",
)
s.Require().NoError(err)
// Bob joins the community
s.joinCommunity(community, s.alice, s.bob)
}
func (s *MessengerCommunitiesSignersSuite) testDescriptionSignature(description []byte) {
var amm protobuf.ApplicationMetadataMessage
err := proto.Unmarshal(description, &amm)
s.Require().NoError(err)
signer, err := utils.RecoverKey(&amm)
s.Require().NoError(err)
s.NotNil(signer)
}
func (s *MessengerCommunitiesSignersSuite) forceCommunityChange(community *communities.Community, owner *Messenger, user *Messenger) {
newDescription := community.DescriptionText() + " new"
_, err := owner.EditCommunity(&requests.EditCommunity{
CommunityID: community.ID(),
CreateCommunity: requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
Name: community.Name(),
Color: community.Color(),
Description: newDescription,
},
})
s.Require().NoError(err)
// alice receives new description
_, err = WaitOnMessengerResponse(user, func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && r.Communities()[0].DescriptionText() == newDescription
}, "new description not received")
s.Require().NoError(err)
}
func (s *MessengerCommunitiesSignersSuite) testSyncCommunity(mintOwnerToken bool) {
community := s.createCommunity(s.john)
s.advertiseCommunityTo(s.john, community, s.alice)
s.joinCommunity(community, s.john, s.alice)
// FIXME: Remove this workaround when fixed:
// https://github.com/status-im/status-go/issues/4413
s.forceCommunityChange(community, s.john, s.alice)
aliceCommunity, err := s.alice.GetCommunityByID(community.ID())
s.Require().NoError(err)
s.testDescriptionSignature(aliceCommunity.DescriptionProtocolMessage())
if mintOwnerToken {
// john mints owner token
var chainID uint64 = 1
tokenAddress := "token-address"
tokenName := "tokenName"
tokenSymbol := "TSM"
communityToken := &token.CommunityToken{
TokenType: protobuf.CommunityTokenType_ERC721,
CommunityID: community.IDString(),
Address: tokenAddress,
ChainID: int(chainID),
Name: tokenName,
Supply: &bigint.BigInt{},
Symbol: tokenSymbol,
PrivilegesLevel: token.OwnerLevel,
}
_, err := s.john.SaveCommunityToken(communityToken, nil)
s.Require().NoError(err)
// john adds minted owner token to community
err = s.john.AddCommunityToken(community.IDString(), int(chainID), tokenAddress)
s.Require().NoError(err)
// 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.SetMockCommunityTokenData(communityToken)
// alice accepts community update
_, err = WaitOnSignaledMessengerResponse(
s.alice,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && len(r.Communities()[0].TokenPermissions()) == 1
},
"no communities",
)
s.Require().NoError(err)
}
// Create alice second instance
alice2, err := newMessengerWithKey(
s.shh,
s.alice.identity,
s.logger.With(zap.String("name", "alice-2")),
[]Option{WithCommunityTokensService(s.collectiblesServiceMock)})
s.Require().NoError(err)
2024-02-29 20:12:39 +00:00
defer TearDownMessenger(&s.Suite, alice2)
// Create communities backup
clock, _ := s.alice.getLastClockWithRelatedChat()
communitiesBackup, err := s.alice.backupCommunities(context.Background(), clock)
s.Require().NoError(err)
// Find wanted communities in the backup
var syncCommunityMessages []*protobuf.SyncInstallationCommunity
for _, b := range communitiesBackup {
for _, c := range b.Communities {
if bytes.Equal(c.Id, community.ID()) {
syncCommunityMessages = append(syncCommunityMessages, c)
}
}
}
s.Require().Len(syncCommunityMessages, 1)
s.testDescriptionSignature(syncCommunityMessages[0].Description)
// Push the backup into second instance
messageState := alice2.buildMessageState()
err = alice2.HandleSyncInstallationCommunity(messageState, syncCommunityMessages[0], nil)
s.Require().NoError(err)
s.Require().Len(messageState.Response.Communities(), 1)
expectedControlNode := community.PublicKey()
if mintOwnerToken {
expectedControlNode = &s.john.identity.PublicKey
}
responseCommunity := messageState.Response.Communities()[0]
s.Require().Equal(community.IDString(), responseCommunity.IDString())
s.Require().True(common.IsPubKeyEqual(expectedControlNode, responseCommunity.ControlNode()))
}
func (s *MessengerCommunitiesSignersSuite) TestSyncTokenGatedCommunity() {
testCases := []struct {
name string
mintOwnerToken bool
}{
{
name: "general community sync",
mintOwnerToken: false,
},
{
name: "community with token ownership",
mintOwnerToken: true,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
s.testSyncCommunity(tc.mintOwnerToken)
})
}
}
func (s *MessengerCommunitiesSignersSuite) TestWithMintedOwnerTokenApplyCommunityEventsUponMakingDeviceControlNode() {
community := s.createCommunity(s.john)
// john mints owner token
var chainID uint64 = 1
tokenAddress := "token-address"
tokenName := "tokenName"
tokenSymbol := "TSM"
_, err := s.john.SaveCommunityToken(&token.CommunityToken{
TokenType: protobuf.CommunityTokenType_ERC721,
CommunityID: community.IDString(),
Address: tokenAddress,
ChainID: int(chainID),
Name: tokenName,
Supply: &bigint.BigInt{},
Symbol: tokenSymbol,
PrivilegesLevel: token.OwnerLevel,
}, nil)
s.Require().NoError(err)
err = s.john.AddCommunityToken(community.IDString(), int(chainID), tokenAddress)
s.Require().NoError(err)
// Make sure there is no control node
s.Require().False(common.IsPubKeyEqual(community.ControlNode(), &s.john.identity.PublicKey))
// Trick. We need to remove the community private key otherwise the events
// will be signed and Events will be approved instead of being in Pending State.
_, err = s.john.RemovePrivateKey(community.ID())
s.Require().NoError(err)
request := requests.CreateCommunityTokenPermission{
CommunityID: community.ID(),
Type: protobuf.CommunityTokenPermission_BECOME_ADMIN,
TokenCriteria: []*protobuf.TokenCriteria{
&protobuf.TokenCriteria{
Type: protobuf.CommunityTokenType_ERC20,
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
Symbol: "TEST",
AmountInWei: "100000000000000000000",
Decimals: uint64(18),
},
},
}
response, err := s.john.CreateCommunityTokenPermission(&request)
s.Require().NoError(err)
s.Require().Len(response.CommunityChanges, 1)
s.Require().Len(response.CommunityChanges[0].TokenPermissionsAdded, 1)
addedPermission := func() *communities.CommunityTokenPermission {
for _, permission := range response.CommunityChanges[0].TokenPermissionsAdded {
return permission
}
return nil
}()
s.Require().NotNil(addedPermission)
s.Require().Equal(communities.TokenPermissionAdditionPending, addedPermission.State)
messengerReponse, err := s.john.PromoteSelfToControlNode(community.ID())
s.Require().NoError(err)
s.Require().Len(messengerReponse.Communities(), 1)
tokenPermissions := messengerReponse.Communities()[0].TokenPermissions()
s.Require().Len(tokenPermissions, 2)
tokenPermissionsMap := make(map[protobuf.CommunityTokenPermission_Type]struct{}, len(tokenPermissions))
for _, t := range tokenPermissions {
tokenPermissionsMap[t.Type] = struct{}{}
}
s.Require().Len(tokenPermissionsMap, 2)
s.Require().Contains(tokenPermissionsMap, protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER)
s.Require().Contains(tokenPermissionsMap, protobuf.CommunityTokenPermission_BECOME_ADMIN)
for _, v := range tokenPermissions {
s.Require().Equal(communities.TokenPermissionApproved, v.State)
}
}
func (s *MessengerCommunitiesSignersSuite) TestWithoutMintedOwnerTokenMakingDeviceControlNodeIsBlocked() {
community := s.createCommunity(s.john)
// Make sure there is no control node
s.Require().False(common.IsPubKeyEqual(community.ControlNode(), &s.john.identity.PublicKey))
response, err := s.john.PromoteSelfToControlNode(community.ID())
s.Require().Nil(response)
s.Require().NotNil(err)
s.Require().Error(err, "Owner token is needed")
}
func (s *MessengerCommunitiesSignersSuite) TestControlNodeDeviceChanged() {
// Note: we don't have any specific check if control node device changed,
// so in this test we will just call twice 'PromoteSelfToControlNode'
community, _ := createOnRequestCommunity(&s.Suite, s.john)
// john mints owner token
ownerTokenAddress := "token-address"
_, err := s.john.SaveCommunityToken(&token.CommunityToken{
TokenType: protobuf.CommunityTokenType_ERC721,
CommunityID: community.IDString(),
Address: ownerTokenAddress,
ChainID: int(testChainID1),
Name: "ownerToken",
Supply: &bigint.BigInt{},
Symbol: "OT",
PrivilegesLevel: token.OwnerLevel,
}, nil)
s.Require().NoError(err)
err = s.john.AddCommunityToken(community.IDString(), int(testChainID1), ownerTokenAddress)
s.Require().NoError(err)
// john mints TM token
tokenMasterTokenAddress := "token-master-address"
_, err = s.john.SaveCommunityToken(&token.CommunityToken{
TokenType: protobuf.CommunityTokenType_ERC721,
CommunityID: community.IDString(),
Address: tokenMasterTokenAddress,
ChainID: int(testChainID1),
Name: "tokenMasterToken",
Supply: &bigint.BigInt{},
Symbol: "TMT",
PrivilegesLevel: token.MasterLevel,
}, nil)
s.Require().NoError(err)
err = s.john.AddCommunityToken(community.IDString(), int(testChainID1), tokenMasterTokenAddress)
s.Require().NoError(err)
// set john as contract owner
s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.john.identity.PublicKey))
s.collectiblesServiceMock.SetMockCollectibleContractData(testChainID1, ownerTokenAddress,
&communities.CollectibleContractData{TotalSupply: &bigint.BigInt{}})
s.collectiblesServiceMock.SetMockCollectibleContractData(testChainID1, tokenMasterTokenAddress,
&communities.CollectibleContractData{TotalSupply: &bigint.BigInt{}})
community, err = s.john.communitiesManager.GetByID(community.ID())
s.Require().NoError(err)
s.Require().True(common.IsPubKeyEqual(community.ControlNode(), &s.john.identity.PublicKey))
var tokenMasterTokenCriteria *protobuf.TokenCriteria
for _, permission := range community.TokenPermissions() {
if permission.Type == protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER {
s.Require().Len(permission.TokenCriteria, 1)
tokenMasterTokenCriteria = permission.TokenCriteria[0]
break
}
}
s.Require().NotNil(tokenMasterTokenCriteria)
s.makeAddressSatisfyTheCriteria(testChainID1, bobAddress, tokenMasterTokenCriteria)
waitOnAliceCommunityValidation := waitOnCommunitiesEvent(s.alice, func(sub *communities.Subscription) bool {
return sub.TokenCommunityValidated != nil
})
s.advertiseCommunityTo(s.john, community, s.alice)
err = <-waitOnAliceCommunityValidation
s.Require().NoError(err)
waitOnBobCommunityValidation := waitOnCommunitiesEvent(s.bob, func(sub *communities.Subscription) bool {
return sub.TokenCommunityValidated != nil
})
s.advertiseCommunityTo(s.john, community, s.bob)
err = <-waitOnBobCommunityValidation
s.Require().NoError(err)
s.joinOnRequestCommunity(community, s.john, s.alice)
s.joinOnRequestCommunity(community, s.john, s.bob)
community, err = s.john.communitiesManager.GetByID(community.ID())
s.Require().NoError(err)
s.Require().True(checkRoleBasedOnThePermissionType(protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER, &s.bob.identity.PublicKey, community))
// Simulate control node device changed and new control node device has all members revealed addresses
response, err := s.john.PromoteSelfToControlNode(community.ID())
s.Require().NoError(err)
s.Require().Len(response.CommunityChanges, 1)
s.Require().Len(response.CommunityChanges[0].MembersRemoved, 0)
// Simulate control node device changed and new control node device does not have bob's revealed addresses
bobRequestID := communities.CalculateRequestID(s.bob.IdentityPublicKeyString(), community.ID())
err = s.john.communitiesManager.RemoveRequestToJoinRevealedAddresses(bobRequestID)
s.Require().NoError(err)
// due to test execution is fast, we update request to join clock
clock := uint64(time.Now().Unix() - 2)
err = s.john.communitiesManager.UpdateClockInRequestToJoin(bobRequestID, clock)
s.Require().NoError(err)
_, err = s.john.PromoteSelfToControlNode(community.ID())
s.Require().NoError(err)
community, err = s.john.communitiesManager.GetByID(community.ID())
s.Require().NoError(err)
s.Require().Len(community.Members(), 2)
for _, chat := range community.Chats() {
s.Require().Len(chat.Members, 2)
}
// Bob will receive request to share RevealedAddresses and send request to join to the control node
_, err = WaitOnMessengerResponse(
s.bob,
func(r *MessengerResponse) bool {
return len(r.Communities()) == 1 && !r.Communities()[0].HasMember(&s.bob.identity.PublicKey) &&
r.Communities()[0].Spectated() && len(r.ActivityCenterNotifications()) == 0
},
"Bob was not soft kicked from the community",
)
s.Require().NoError(err)
// check that alice was not soft kicked
_, err = WaitOnMessengerResponse(
s.alice,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && r.Communities()[0].HasMember(&s.alice.identity.PublicKey) &&
!r.Communities()[0].Spectated() && len(r.ActivityCenterNotifications()) == 0 &&
r.Communities()[0].Joined()
},
"Alice was kicked from the community",
)
s.Require().NoError(err)
// John auto-accept requests to join with RevealedAddresses
_, err = WaitOnMessengerResponse(
s.john,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && len(r.Communities()[0].Members()) == 3
},
"no community update with accepted request",
)
s.Require().NoError(err)
_, err = WaitOnMessengerResponse(
s.bob,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && r.Communities()[0].HasMember(&s.bob.identity.PublicKey) &&
r.Communities()[0].Joined() && !r.Communities()[0].Spectated() && r.Communities()[0].IsTokenMaster()
},
"Bob was auto-accepted",
)
s.Require().NoError(err)
}