2023-07-18 15:06:12 +00:00
|
|
|
package communities
|
|
|
|
|
|
|
|
import (
|
2023-07-21 09:38:34 +00:00
|
|
|
"crypto/ecdsa"
|
2024-02-19 09:52:22 +00:00
|
|
|
"encoding/json"
|
2023-07-18 15:06:12 +00:00
|
|
|
"errors"
|
2024-02-19 09:52:22 +00:00
|
|
|
"fmt"
|
2023-07-18 15:06:12 +00:00
|
|
|
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
2023-07-18 15:06:12 +00:00
|
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
|
|
|
)
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
type CommunityEvent struct {
|
|
|
|
CommunityEventClock uint64 `json:"communityEventClock"`
|
|
|
|
Type protobuf.CommunityEvent_EventType `json:"type"`
|
|
|
|
CommunityConfig *protobuf.CommunityConfig `json:"communityConfig,omitempty"`
|
|
|
|
TokenPermission *protobuf.CommunityTokenPermission `json:"tokenPermissions,omitempty"`
|
|
|
|
CategoryData *protobuf.CategoryData `json:"categoryData,omitempty"`
|
|
|
|
ChannelData *protobuf.ChannelData `json:"channelData,omitempty"`
|
|
|
|
MemberToAction string `json:"memberToAction,omitempty"`
|
|
|
|
RequestToJoin *protobuf.CommunityRequestToJoin `json:"requestToJoin,omitempty"`
|
|
|
|
TokenMetadata *protobuf.CommunityTokenMetadata `json:"tokenMetadata,omitempty"`
|
|
|
|
Payload []byte `json:"payload"`
|
|
|
|
Signature []byte `json:"signature"`
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
func (e *CommunityEvent) ToProtobuf() *protobuf.CommunityEvent {
|
|
|
|
var acceptedRequestsToJoin map[string]*protobuf.CommunityRequestToJoin
|
|
|
|
var rejectedRequestsToJoin map[string]*protobuf.CommunityRequestToJoin
|
|
|
|
|
|
|
|
switch e.Type {
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT:
|
|
|
|
acceptedRequestsToJoin = make(map[string]*protobuf.CommunityRequestToJoin)
|
|
|
|
acceptedRequestsToJoin[e.MemberToAction] = e.RequestToJoin
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT:
|
|
|
|
rejectedRequestsToJoin = make(map[string]*protobuf.CommunityRequestToJoin)
|
|
|
|
rejectedRequestsToJoin[e.MemberToAction] = e.RequestToJoin
|
|
|
|
}
|
|
|
|
|
|
|
|
return &protobuf.CommunityEvent{
|
|
|
|
CommunityEventClock: e.CommunityEventClock,
|
|
|
|
Type: e.Type,
|
|
|
|
CommunityConfig: e.CommunityConfig,
|
|
|
|
TokenPermission: e.TokenPermission,
|
|
|
|
CategoryData: e.CategoryData,
|
|
|
|
ChannelData: e.ChannelData,
|
|
|
|
MemberToAction: e.MemberToAction,
|
|
|
|
RejectedRequestsToJoin: rejectedRequestsToJoin,
|
|
|
|
AcceptedRequestsToJoin: acceptedRequestsToJoin,
|
|
|
|
TokenMetadata: e.TokenMetadata,
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
func communityEventFromProtobuf(msg *protobuf.SignedCommunityEvent) (*CommunityEvent, error) {
|
|
|
|
decodedEvent := protobuf.CommunityEvent{}
|
|
|
|
err := proto.Unmarshal(msg.Payload, &decodedEvent)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
memberToAction := decodedEvent.MemberToAction
|
|
|
|
var requestToJoin *protobuf.CommunityRequestToJoin
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
switch decodedEvent.Type {
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT:
|
|
|
|
for member, request := range decodedEvent.AcceptedRequestsToJoin {
|
|
|
|
memberToAction = member
|
|
|
|
requestToJoin = request
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT:
|
|
|
|
for member, request := range decodedEvent.RejectedRequestsToJoin {
|
|
|
|
memberToAction = member
|
|
|
|
requestToJoin = request
|
|
|
|
break
|
|
|
|
}
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &CommunityEvent{
|
2024-02-19 09:52:22 +00:00
|
|
|
CommunityEventClock: decodedEvent.CommunityEventClock,
|
|
|
|
Type: decodedEvent.Type,
|
|
|
|
CommunityConfig: decodedEvent.CommunityConfig,
|
|
|
|
TokenPermission: decodedEvent.TokenPermission,
|
|
|
|
CategoryData: decodedEvent.CategoryData,
|
|
|
|
ChannelData: decodedEvent.ChannelData,
|
|
|
|
MemberToAction: memberToAction,
|
|
|
|
RequestToJoin: requestToJoin,
|
|
|
|
TokenMetadata: decodedEvent.TokenMetadata,
|
|
|
|
Payload: msg.Payload,
|
|
|
|
Signature: msg.Signature,
|
|
|
|
}, nil
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
func (e *CommunityEvent) RecoverSigner() (*ecdsa.PublicKey, error) {
|
|
|
|
if e.Signature == nil || len(e.Signature) == 0 {
|
|
|
|
return nil, errors.New("missing signature")
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
signer, err := crypto.SigToPub(
|
|
|
|
crypto.Keccak256(e.Payload),
|
|
|
|
e.Signature,
|
|
|
|
)
|
2023-07-18 15:06:12 +00:00
|
|
|
if err != nil {
|
2024-02-19 09:52:22 +00:00
|
|
|
return nil, errors.New("failed to recover signer")
|
2023-07-21 09:38:34 +00:00
|
|
|
}
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
return signer, nil
|
|
|
|
}
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
func (e *CommunityEvent) Sign(pk *ecdsa.PrivateKey) error {
|
|
|
|
sig, err := crypto.Sign(crypto.Keccak256(e.Payload), pk)
|
2023-07-18 15:06:12 +00:00
|
|
|
if err != nil {
|
2023-08-17 17:14:23 +00:00
|
|
|
return err
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
e.Signature = sig
|
2023-08-17 17:14:23 +00:00
|
|
|
return nil
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
func (e *CommunityEvent) Validate() error {
|
|
|
|
switch e.Type {
|
2023-07-18 15:06:12 +00:00
|
|
|
case protobuf.CommunityEvent_COMMUNITY_EDIT:
|
2024-02-19 09:52:22 +00:00
|
|
|
if e.CommunityConfig == nil || e.CommunityConfig.Identity == nil ||
|
|
|
|
e.CommunityConfig.Permissions == nil || e.CommunityConfig.AdminSettings == nil {
|
|
|
|
return errors.New("invalid config change admin event")
|
|
|
|
}
|
2023-07-18 15:06:12 +00:00
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_CHANGE:
|
2024-02-19 09:52:22 +00:00
|
|
|
if e.TokenPermission == nil || len(e.TokenPermission.Id) == 0 {
|
|
|
|
return errors.New("invalid token permission change event")
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_DELETE:
|
2024-02-19 09:52:22 +00:00
|
|
|
if e.TokenPermission == nil || len(e.TokenPermission.Id) == 0 {
|
|
|
|
return errors.New("invalid token permission delete event")
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_CATEGORY_CREATE:
|
2024-02-19 09:52:22 +00:00
|
|
|
if e.CategoryData == nil || len(e.CategoryData.CategoryId) == 0 {
|
|
|
|
return errors.New("invalid community category create event")
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_CATEGORY_DELETE:
|
2024-02-19 09:52:22 +00:00
|
|
|
if e.CategoryData == nil || len(e.CategoryData.CategoryId) == 0 {
|
|
|
|
return errors.New("invalid community category delete event")
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_CATEGORY_EDIT:
|
2024-02-19 09:52:22 +00:00
|
|
|
if e.CategoryData == nil || len(e.CategoryData.CategoryId) == 0 {
|
|
|
|
return errors.New("invalid community category edit event")
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_CHANNEL_CREATE:
|
2024-02-19 09:52:22 +00:00
|
|
|
if e.ChannelData == nil || len(e.ChannelData.ChannelId) == 0 ||
|
|
|
|
e.ChannelData.Channel == nil {
|
|
|
|
return errors.New("invalid community channel create event")
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_CHANNEL_DELETE:
|
2024-02-19 09:52:22 +00:00
|
|
|
if e.ChannelData == nil || len(e.ChannelData.ChannelId) == 0 {
|
|
|
|
return errors.New("invalid community channel delete event")
|
|
|
|
}
|
2023-07-18 15:06:12 +00:00
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_CHANNEL_EDIT:
|
2024-02-19 09:52:22 +00:00
|
|
|
if e.ChannelData == nil || len(e.ChannelData.ChannelId) == 0 ||
|
|
|
|
e.ChannelData.Channel == nil {
|
|
|
|
return errors.New("invalid community channel edit event")
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_CHANNEL_REORDER:
|
2024-02-19 09:52:22 +00:00
|
|
|
if e.ChannelData == nil || len(e.ChannelData.ChannelId) == 0 {
|
|
|
|
return errors.New("invalid community channel reorder event")
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_CATEGORY_REORDER:
|
2024-02-19 09:52:22 +00:00
|
|
|
if e.CategoryData == nil || len(e.CategoryData.CategoryId) == 0 {
|
|
|
|
return errors.New("invalid community category reorder event")
|
|
|
|
}
|
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT, protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT:
|
|
|
|
if len(e.MemberToAction) == 0 || e.RequestToJoin == nil {
|
|
|
|
return errors.New("invalid community request to join event")
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_MEMBER_KICK:
|
2024-02-19 09:52:22 +00:00
|
|
|
if len(e.MemberToAction) == 0 {
|
|
|
|
return errors.New("invalid community member kick event")
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
2024-02-19 09:52:22 +00:00
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
case protobuf.CommunityEvent_COMMUNITY_MEMBER_BAN:
|
2024-02-19 09:52:22 +00:00
|
|
|
if len(e.MemberToAction) == 0 {
|
|
|
|
return errors.New("invalid community member ban event")
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
2024-02-19 09:52:22 +00:00
|
|
|
|
2023-07-18 15:06:12 +00:00
|
|
|
case protobuf.CommunityEvent_COMMUNITY_MEMBER_UNBAN:
|
2024-02-19 09:52:22 +00:00
|
|
|
if len(e.MemberToAction) == 0 {
|
|
|
|
return errors.New("invalid community member unban event")
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
case protobuf.CommunityEvent_COMMUNITY_TOKEN_ADD:
|
|
|
|
if e.TokenMetadata == nil || len(e.TokenMetadata.ContractAddresses) == 0 {
|
|
|
|
return errors.New("invalid add community token event")
|
2024-02-16 10:06:36 +00:00
|
|
|
}
|
|
|
|
}
|
2024-02-19 09:52:22 +00:00
|
|
|
return nil
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
// EventTypeID constructs a unique identifier for an event and its associated target.
|
|
|
|
func (e *CommunityEvent) EventTypeID() string {
|
|
|
|
switch e.Type {
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_EDIT:
|
|
|
|
return fmt.Sprintf("%d", e.Type)
|
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_CHANGE,
|
|
|
|
protobuf.CommunityEvent_COMMUNITY_MEMBER_TOKEN_PERMISSION_DELETE:
|
|
|
|
return fmt.Sprintf("%d-%s", e.Type, e.TokenPermission.Id)
|
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_CATEGORY_CREATE,
|
|
|
|
protobuf.CommunityEvent_COMMUNITY_CATEGORY_DELETE,
|
|
|
|
protobuf.CommunityEvent_COMMUNITY_CATEGORY_EDIT,
|
|
|
|
protobuf.CommunityEvent_COMMUNITY_CATEGORY_REORDER:
|
|
|
|
return fmt.Sprintf("%d-%s", e.Type, e.CategoryData.CategoryId)
|
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_CHANNEL_CREATE,
|
|
|
|
protobuf.CommunityEvent_COMMUNITY_CHANNEL_DELETE,
|
|
|
|
protobuf.CommunityEvent_COMMUNITY_CHANNEL_EDIT,
|
|
|
|
protobuf.CommunityEvent_COMMUNITY_CHANNEL_REORDER:
|
|
|
|
return fmt.Sprintf("%d-%s", e.Type, e.ChannelData.ChannelId)
|
|
|
|
|
|
|
|
case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT,
|
|
|
|
protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT,
|
|
|
|
protobuf.CommunityEvent_COMMUNITY_MEMBER_KICK,
|
|
|
|
protobuf.CommunityEvent_COMMUNITY_MEMBER_BAN,
|
|
|
|
protobuf.CommunityEvent_COMMUNITY_MEMBER_UNBAN:
|
|
|
|
return fmt.Sprintf("%d-%s", e.Type, e.MemberToAction)
|
2023-07-18 15:06:12 +00:00
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
case protobuf.CommunityEvent_COMMUNITY_TOKEN_ADD:
|
|
|
|
return fmt.Sprintf("%d-%s", e.Type, e.TokenMetadata.Name)
|
2023-08-08 18:33:29 +00:00
|
|
|
}
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
return ""
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
func communityEventsToJSONEncodedBytes(communityEvents []CommunityEvent) ([]byte, error) {
|
|
|
|
return json.Marshal(communityEvents)
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
func communityEventsFromJSONEncodedBytes(jsonEncodedRawEvents []byte) ([]CommunityEvent, error) {
|
|
|
|
var events []CommunityEvent
|
|
|
|
err := json.Unmarshal(jsonEncodedRawEvents, &events)
|
2023-07-18 15:06:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-02-19 09:52:22 +00:00
|
|
|
return events, nil
|
2023-07-18 15:06:12 +00:00
|
|
|
}
|