channels_actions.gno
5.23 Kb · 111 lines
1package memba_weighted_host
2
3import (
4 "chain"
5 "strconv"
6 "strings"
7 "gno.land/p/samcrew/memba_weighted_policy"
8)
9
10const ChannelsPath = "gno.land/r/samcrew/memba_dao_channels_v2"
11
12type ChannelsActionKind string
13
14const (
15 ChannelsAcceptOwner ChannelsActionKind = "accept-owner"
16 ChannelsReturnOwner ChannelsActionKind = "return-owner"
17 ChannelsAbortReturn ChannelsActionKind = "abort-return"
18 ChannelsAddMember ChannelsActionKind = "add-member"
19 ChannelsRemoveMember ChannelsActionKind = "remove-member"
20 ChannelsSetRoles ChannelsActionKind = "set-roles"
21 ChannelsCreateText ChannelsActionKind = "create-text-channel"
22 ChannelsUnpause ChannelsActionKind = "unpause"
23)
24
25type ChannelsPolicy struct{ dao, successor address }
26
27func NewChannelsPolicy(dao, successor address) ChannelsPolicy {
28 if !dao.IsValid() || dao != chain.PackageAddress("gno.land/r/samcrew/memba_dao") {
29 panic("channels policy requires the founding DAO address")
30 }
31 if !successor.IsValid() || successor == dao || successor == chain.PackageAddress(ChannelsPath) {
32 panic("channels policy requires a valid distinct successor")
33 }
34 return ChannelsPolicy{dao: dao, successor: successor}
35}
36func (p ChannelsPolicy) DAO() address { return p.dao }
37func (p ChannelsPolicy) Successor() address { return p.successor }
38
39type ChannelsAction struct {
40 kind ChannelsActionKind
41 recipient, subject address
42 roles string
43 name, description string
44}
45func (p ChannelsPolicy) Accept() ChannelsAction { return ChannelsAction{kind: ChannelsAcceptOwner} }
46func (p ChannelsPolicy) Return() ChannelsAction { return ChannelsAction{kind: ChannelsReturnOwner, recipient: p.successor} }
47func (p ChannelsPolicy) AbortReturn() ChannelsAction { return ChannelsAction{kind: ChannelsAbortReturn, recipient: p.successor} }
48func (p ChannelsPolicy) AddMember(who address, roles string) ChannelsAction { a:=ChannelsAction{kind: ChannelsAddMember, subject:who, roles:roles}; a.Bytes(); return a }
49func (p ChannelsPolicy) RemoveMember(who address) ChannelsAction { a:=ChannelsAction{kind: ChannelsRemoveMember, subject:who}; a.Bytes(); return a }
50func (p ChannelsPolicy) SetRoles(who address, roles string) ChannelsAction { a:=ChannelsAction{kind: ChannelsSetRoles, subject:who, roles:roles}; a.Bytes(); return a }
51func (p ChannelsPolicy) CreateText(name, description string) ChannelsAction { a:=ChannelsAction{kind: ChannelsCreateText, name:name, description:description}; a.Bytes(); return a }
52func ChannelsResume() ChannelsAction { return ChannelsAction{kind: ChannelsUnpause} }
53func (a ChannelsAction) Kind() ChannelsActionKind { return a.kind }
54func (a ChannelsAction) Recipient() address { return a.recipient }
55func (a ChannelsAction) Subject() address { return a.subject }
56func (a ChannelsAction) Roles() string { return a.roles }
57func (a ChannelsAction) Name() string { return a.name }
58func (a ChannelsAction) Description() string { return a.description }
59
60func validChannelsName(name string) bool {
61 if len(name)<1 || len(name)>50 { return false }
62 for _,c:=range name { if !((c>='a'&&c<='z')||(c>='0'&&c<='9')||c=='-') { return false } }
63 return true
64}
65func validChannelsDescription(description string) bool {
66 if len(description)<1 || len(description)>200 { return false }
67 for _,c:=range description { if c<32 || c==127 { return false } }
68 return true
69}
70
71// Only the four target ACL roles are representable; every role list has a
72// canonical order, so a voted commitment cannot alias another role grant.
73func canonicalChannelsRoles(roles string) bool {
74 if roles == "" { return false }
75 parts:=strings.Split(roles, ",")
76 if len(parts)>4 { return false }
77 previous:=-1
78 for _,part:=range parts {
79 index:=-1
80 switch part { case "admin": index=0; case "dev": index=1; case "ops": index=2; case "member": index=3 }
81 if index<=previous { return false }
82 previous=index
83 }
84 return true
85}
86func (a ChannelsAction) Bytes() string {
87 base:="channels/v1|"+ChannelsPath+"|"+string(a.kind)
88 switch a.kind {
89 case ChannelsAcceptOwner, ChannelsUnpause:
90 if a.recipient!="" || a.subject!="" || a.roles!="" || a.name!="" || a.description!="" { panic("malformed channels action") }
91 return base
92 case ChannelsReturnOwner, ChannelsAbortReturn:
93 if !a.recipient.IsValid() || a.subject!="" || a.roles!="" || a.name!="" || a.description!="" { panic("malformed channels return action") }
94 return base+"|"+string(a.recipient)
95 case ChannelsAddMember, ChannelsSetRoles:
96 if !a.subject.IsValid() || a.recipient!="" || a.name!="" || a.description!="" || !canonicalChannelsRoles(a.roles) { panic("invalid channels member action") }
97 return base+"|"+string(a.subject)+"|"+strconv.Itoa(len(a.roles))+":"+a.roles
98 case ChannelsRemoveMember:
99 if !a.subject.IsValid() || a.recipient!="" || a.roles!="" || a.name!="" || a.description!="" { panic("invalid channels member action") }
100 return base+"|"+string(a.subject)
101 case ChannelsCreateText:
102 if a.recipient!="" || a.subject!="" || a.roles!="" || !validChannelsName(a.name) || !validChannelsDescription(a.description) { panic("invalid channels text creation") }
103 return base+"|"+a.name+"|"+strconv.Itoa(len(a.description))+":"+a.description
104 default: panic("unsupported channels action")
105 }
106}
107func (a ChannelsAction) Category() memba_weighted_policy.Category {
108 a.Bytes()
109 if a.kind==ChannelsUnpause { return memba_weighted_policy.Financial }
110 return memba_weighted_policy.Critical
111}