Skip to content

Commit d5c5b1a

Browse files
Villaquiranmn0izn0izNorman
authored
feat(gno/daokit): "resource management" handlers (#1513)
Signed-off-by: Norman <norman@samourai.coop> Co-authored-by: n0izn0iz <n0izn0iz@users.noreply.github.com> Co-authored-by: Norman <norman@samourai.coop>
1 parent 4b50bfa commit d5c5b1a

File tree

5 files changed

+187
-1
lines changed

5 files changed

+187
-1
lines changed

gno/p/basedao/basedao.gno

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ func New(conf *Config) *DAO {
8585
NewRemoveMemberHandler(dao),
8686
NewAssignRoleHandler(dao),
8787
NewUnassignRoleHandler(dao),
88+
daokit.NewSetResourceHandler(dao.Core),
89+
daokit.NewRemoveResourceHandler(dao.Core),
8890
} {
8991
dao.Core.SetResource(&daokit.Resource{Handler: m, Condition: conf.InitialCondition})
9092
}

gno/p/basedao/basedao_test.gno

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func TestNewDAO(t *testing.T) {
5757
}
5858
}
5959

60-
urequire.Equal(t, 5, dao.Core.ResourcesCount(), "expected 5 resources")
60+
urequire.Equal(t, 7, dao.Core.ResourcesCount(), "expected 7 resources")
6161
urequire.Equal(t, dao.Realm.PkgPath(), daoRealm.PkgPath())
6262

6363
// XXX: check realm and profile
@@ -725,3 +725,125 @@ func newTestingDAO(t *testing.T, threshold float64, members []Member) *testingDA
725725
unknownProposalRequest: unknownProposalRequest,
726726
}
727727
}
728+
729+
func TestAddRemoveResource(t *testing.T) {
730+
resourceType := "newResource"
731+
members := []Member{
732+
{
733+
alice.String(),
734+
[]string{"admin"},
735+
},
736+
{
737+
bob.String(),
738+
[]string{"admin"},
739+
},
740+
}
741+
tdao := newTestingDAO(t, 0.2, members)
742+
743+
if !tdao.dao.Members.HasRole(bob.String(), "admin") {
744+
t.Errorf("Expected member %s to have role 'admin'", bob.String())
745+
}
746+
747+
mockHandler := daokit.NewMessageHandler(resourceType, func(_ interface{}) {})
748+
749+
addResourceProposal := daokit.ProposalRequest{
750+
Title: "My Proposal",
751+
Description: "My Proposal Description",
752+
Message: daokit.NewSetResourceMsg(&daokit.Resource{
753+
Handler: mockHandler,
754+
Condition: daocond.MembersThreshold(0.2, tdao.dao.Members.IsMember, tdao.dao.Members.MembersCount),
755+
}),
756+
}
757+
urequire.Equal(t, 8, tdao.dao.Core.ResourcesCount(), "expected 8 resources")
758+
std.TestSetOrigCaller(alice)
759+
tdao.dao.InstantExecute(addResourceProposal)
760+
urequire.Equal(t, 9, tdao.dao.Core.ResourcesCount(), "expected 9 resources")
761+
762+
removeResourceProposal := daokit.ProposalRequest{
763+
Title: "My Proposal",
764+
Description: "My Proposal Description",
765+
Message: daokit.NewRemoveResourceMsg(&daokit.Resource{
766+
Handler: mockHandler,
767+
Condition: daocond.MembersThreshold(0.2, tdao.dao.Members.IsMember, tdao.dao.Members.MembersCount),
768+
}),
769+
}
770+
771+
tdao.dao.InstantExecute(removeResourceProposal)
772+
773+
urequire.Equal(t, 8, tdao.dao.Core.ResourcesCount(), "expected 8 resources")
774+
}
775+
776+
func TestAddRemoveResourceInvalidateProposals(t *testing.T) {
777+
resourceType := "newResource"
778+
members := []Member{
779+
{
780+
alice.String(),
781+
[]string{"admin"},
782+
},
783+
{
784+
bob.String(),
785+
[]string{"admin"},
786+
},
787+
}
788+
tdao := newTestingDAO(t, 0.2, members)
789+
790+
if !tdao.dao.Members.HasRole(bob.String(), "admin") {
791+
t.Errorf("Expected member %s to have role 'admin'", bob.String())
792+
}
793+
794+
mockHandler := daokit.NewMessageHandler(resourceType, func(_ interface{}) {})
795+
796+
addResourceProposal := daokit.ProposalRequest{
797+
Title: "My Proposal",
798+
Description: "My Proposal Description",
799+
Message: daokit.NewSetResourceMsg(&daokit.Resource{
800+
Handler: mockHandler,
801+
Condition: daocond.MembersThreshold(0.2, tdao.dao.Members.IsMember, tdao.dao.Members.MembersCount),
802+
}),
803+
}
804+
std.TestSetOrigCaller(alice)
805+
tdao.dao.InstantExecute(addResourceProposal)
806+
807+
proposalAddMember := daokit.ProposalRequest{
808+
Message: daokit.NewMessage(MsgAddMemberKind, nil),
809+
}
810+
811+
newResourceProposal := daokit.ProposalRequest{
812+
Message: daokit.NewMessage(resourceType, nil),
813+
}
814+
815+
tdao.dao.Propose(newResourceProposal)
816+
tdao.dao.Propose(proposalAddMember)
817+
818+
// Check proposal is open
819+
tdao.dao.Core.ProposalModule.Proposals.Iterate("", "", func(k string, v interface{}) bool {
820+
p := v.(*daokit.Proposal)
821+
if p.Message.Type() == resourceType {
822+
urequire.Equal(t, int(daokit.ProposalStatusOpen), int(p.Status), "expected to be open")
823+
} else if p.Message.Type() == MsgAddMemberKind {
824+
urequire.Equal(t, int(daokit.ProposalStatusOpen), int(p.Status), "expected to be open")
825+
}
826+
return false
827+
})
828+
829+
removeResourceProposal := daokit.ProposalRequest{
830+
Title: "removeResource",
831+
Description: "My Proposal Description",
832+
Message: daokit.NewRemoveResourceMsg(&daokit.Resource{
833+
Handler: mockHandler,
834+
Condition: daocond.MembersThreshold(0.2, tdao.dao.Members.IsMember, tdao.dao.Members.MembersCount),
835+
}),
836+
}
837+
tdao.dao.InstantExecute(removeResourceProposal)
838+
839+
// proposal should be invalidated
840+
tdao.dao.Core.ProposalModule.Proposals.Iterate("", "", func(k string, v interface{}) bool {
841+
p := v.(*daokit.Proposal)
842+
if p.Message.Type() == resourceType {
843+
urequire.Equal(t, int(daokit.ProposalStatusInvalidated), int(p.Status), "expected to be invalidated")
844+
} else if p.Message.Type() == MsgAddMemberKind {
845+
urequire.Equal(t, int(daokit.ProposalStatusOpen), int(p.Status), "expected to stay open")
846+
}
847+
return false
848+
})
849+
}

gno/p/daokit/messages.gno

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,41 @@ func (g *genericMessageHandler) Instantiate() ExecutableMessage {
6464
func (g *genericMessageHandler) Type() string {
6565
return g.kind
6666
}
67+
68+
const MsgSetResourceKind = "gno.land/p/teritori/daokit.SetResource"
69+
70+
func NewSetResourceHandler(d *Core) MessageHandler {
71+
return NewMessageHandler(MsgSetResourceKind, func(ipayload interface{}) {
72+
payload, ok := ipayload.(*Resource)
73+
if !ok {
74+
panic(errors.New("invalid payload type"))
75+
}
76+
77+
d.SetResource(payload)
78+
d.ProposalModule.InvalidateWithResource(payload.Handler.Type())
79+
})
80+
}
81+
82+
func NewSetResourceMsg(payload *Resource) ExecutableMessage {
83+
return NewMessage(MsgSetResourceKind, payload)
84+
}
85+
86+
const MsgRemoveResourceKind = "gno.land/p/teritori/daokit.RemoveResource"
87+
88+
func NewRemoveResourceHandler(d *Core) MessageHandler {
89+
return NewMessageHandler(MsgRemoveResourceKind, func(ipayload interface{}) {
90+
payload, ok := ipayload.(*Resource)
91+
if !ok {
92+
panic(errors.New("invalid payload type"))
93+
}
94+
if d.Resources.getResource(payload.Handler.Type()) == nil {
95+
panic("ressource " + payload.Handler.Type() + " does not exists")
96+
}
97+
d.Resources.removeResource(payload)
98+
d.ProposalModule.InvalidateWithResource(payload.Handler.Type())
99+
})
100+
}
101+
102+
func NewRemoveResourceMsg(payload *Resource) ExecutableMessage {
103+
return NewMessage(MsgRemoveResourceKind, payload)
104+
}

gno/p/daokit/proposals.gno

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const (
1717
ProposalStatusOpen ProposalStatus = iota
1818
ProposalStatusPassed
1919
ProposalStatusExecuted
20+
ProposalStatusInvalidated
2021
)
2122

2223
func (s ProposalStatus) String() string {
@@ -27,6 +28,8 @@ func (s ProposalStatus) String() string {
2728
return "Passed"
2829
case ProposalStatusExecuted:
2930
return "Executed"
31+
case ProposalStatusInvalidated:
32+
return "Invalidated"
3033
default:
3134
return "Unknown"
3235
}
@@ -91,6 +94,23 @@ func (p *ProposalModule) GetProposal(id uint64) *Proposal {
9194
return proposal
9295
}
9396

97+
func (p *ProposalModule) InvalidateWithResource(resourceType string) {
98+
toInvalidate := map[string]*Proposal{}
99+
100+
p.Proposals.Iterate("", "", func(k string, v interface{}) bool {
101+
proposal := v.(*Proposal)
102+
if proposal.Status == ProposalStatusOpen && proposal.Message.Type() == resourceType {
103+
proposal.Status = ProposalStatusInvalidated
104+
toInvalidate[k] = proposal
105+
}
106+
return false
107+
})
108+
109+
for k, v := range toInvalidate {
110+
p.Proposals.Set(k, v)
111+
}
112+
}
113+
94114
func (p *Proposal) UpdateStatus() {
95115
conditionsAreMet := p.ConditionState.Eval(p.Votes)
96116
if p.Status == ProposalStatusOpen && conditionsAreMet {

gno/p/daokit/resources.gno

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ func (r *resourcesStore) setResource(resource *Resource) {
2424
r.Resources.Set(resource.Handler.Type(), resource)
2525
}
2626

27+
func (r *resourcesStore) removeResource(resource *Resource) {
28+
r.Resources.Remove(resource.Handler.Type())
29+
}
30+
2731
func (r *resourcesStore) getResource(name string) *Resource {
2832
value, ok := r.Resources.Get(name)
2933
if !ok {

0 commit comments

Comments
 (0)