From a53334bc7be48568500d158224528c9616660760 Mon Sep 17 00:00:00 2001 From: Miguel Victoria Date: Tue, 18 Feb 2025 18:37:54 +0100 Subject: [PATCH 1/3] feat: add remove resource --- gno/p/basedao/basedao.gno | 2 ++ gno/p/daokit/resources.gno | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/gno/p/basedao/basedao.gno b/gno/p/basedao/basedao.gno index 1e578694a5..ef7d9a16bb 100644 --- a/gno/p/basedao/basedao.gno +++ b/gno/p/basedao/basedao.gno @@ -80,6 +80,8 @@ func New(conf *Config) *DAO { NewRemoveMemberHandler(dao), NewAssignRoleHandler(dao), NewUnassignRoleHandler(dao), + daokit.NewAddResourceHandler(dao.Core.Resources), + daokit.NewRemoveResourceHandler(dao.Core.Resources), } { dao.Core.SetResource(&daokit.Resource{Handler: m, Condition: conf.InitialCondition}) } diff --git a/gno/p/daokit/resources.gno b/gno/p/daokit/resources.gno index d517b40e8b..6923b7d72d 100644 --- a/gno/p/daokit/resources.gno +++ b/gno/p/daokit/resources.gno @@ -1,6 +1,8 @@ package daokit import ( + "errors" + "gno.land/p/demo/avl" "gno.land/p/teritori/daocond" ) @@ -24,6 +26,10 @@ func (r *resourcesStore) setResource(resource *Resource) { r.Resources.Set(resource.Handler.Type(), resource) } +func (r *resourcesStore) removeResource(resource *Resource) { + r.Resources.Remove(resource.Handler.Type()) +} + func (r *resourcesStore) getResource(name string) *Resource { value, ok := r.Resources.Get(name) if !ok { @@ -32,3 +38,34 @@ func (r *resourcesStore) getResource(name string) *Resource { res := value.(*Resource) return res } + +const MsgAddResourceKind = "gno.land/p/teritori/daokit.AddResource" + +func NewAddResourceHandler(r *resourcesStore) MessageHandler { + return NewMessageHandler(MsgAddResourceKind, func(ipayload interface{}) { + payload, ok := ipayload.(Resource) + if !ok { + panic(errors.New("invalid payload type")) + } + + if getResource(payload.Handler.Type()) != nil { + panic("ressource " + payload.Handler.Type() + " already exists") + } + r.setResource(payload) + }) +} + +const MsgRemoveResourceKind = "gno.land/p/teritori/daokit.RemoveResource" + +func NewRemoveResourceHandler(r *resourcesStore) MessageHandler { + return NewMessageHandler(MsgRemoveResourceKind, func(ipayload interface{}) { + payload, ok := ipayload.(Resource) + if !ok { + panic(errors.New("invalid payload type")) + } + if getResource(payload.Handler.Type()) == nil { + panic("ressource " + payload.Handler.Type() + " does not exists") + } + r.removeResource(payload) + }) +} From e31f9e352b1d2a1c8c962ab4d9f34fcce429dde3 Mon Sep 17 00:00:00 2001 From: Miguel Victoria Date: Tue, 18 Feb 2025 22:38:48 +0100 Subject: [PATCH 2/3] add tests and invalidate --- gno/p/basedao/basedao.gno | 4 +- gno/p/basedao/basedao_test.gno | 122 ++++++++++++++++++++++++++++++++- gno/p/daokit/daokit.gno | 39 +++++++++++ gno/p/daokit/proposals.gno | 20 ++++++ gno/p/daokit/resources.gno | 33 --------- 5 files changed, 182 insertions(+), 36 deletions(-) diff --git a/gno/p/basedao/basedao.gno b/gno/p/basedao/basedao.gno index ef7d9a16bb..46a108bc8a 100644 --- a/gno/p/basedao/basedao.gno +++ b/gno/p/basedao/basedao.gno @@ -80,8 +80,8 @@ func New(conf *Config) *DAO { NewRemoveMemberHandler(dao), NewAssignRoleHandler(dao), NewUnassignRoleHandler(dao), - daokit.NewAddResourceHandler(dao.Core.Resources), - daokit.NewRemoveResourceHandler(dao.Core.Resources), + daokit.NewAddResourceHandler(dao.Core), + daokit.NewRemoveResourceHandler(dao.Core), } { dao.Core.SetResource(&daokit.Resource{Handler: m, Condition: conf.InitialCondition}) } diff --git a/gno/p/basedao/basedao_test.gno b/gno/p/basedao/basedao_test.gno index 98d3dcc544..a137a41c41 100644 --- a/gno/p/basedao/basedao_test.gno +++ b/gno/p/basedao/basedao_test.gno @@ -57,7 +57,7 @@ func TestNewDAO(t *testing.T) { } } - urequire.Equal(t, 5, dao.Core.ResourcesCount(), "expected 5 resources") + urequire.Equal(t, 7, dao.Core.ResourcesCount(), "expected 7 resources") urequire.Equal(t, dao.Realm.PkgPath(), daoRealm.PkgPath()) // XXX: check realm and profile @@ -725,3 +725,123 @@ func newTestingDAO(t *testing.T, threshold float64, members []Member) *testingDA unknownProposalRequest: unknownProposalRequest, } } + +func TestAddRemoveResource(t *testing.T) { + resourceType := "newResource" + members := []Member{ + { + alice.String(), + []string{"admin"}, + }, + { + bob.String(), + []string{"admin"}, + }, + } + tdao := newTestingDAO(t, 0.2, members) + + if !tdao.dao.Members.HasRole(bob.String(), "admin") { + t.Errorf("Expected member %s to have role 'admin'", bob.String()) + } + + mockHandler := daokit.NewMessageHandler(resourceType, func(_ interface{}) {}) + + addResourceProposal := daokit.ProposalRequest{ + Title: "My Proposal", + Description: "My Proposal Description", + Message: daokit.NewAddResourceMsg(&daokit.Resource{ + Handler: mockHandler, + Condition: daocond.MembersThreshold(0.2, tdao.dao.Members.IsMember, tdao.dao.Members.MembersCount), + }), + } + urequire.Equal(t, 8, tdao.dao.Core.ResourcesCount(), "expected 8 resources") + std.TestSetOrigCaller(alice) + tdao.dao.InstantExecute(addResourceProposal) + urequire.Equal(t, 9, tdao.dao.Core.ResourcesCount(), "expected 9 resources") + + removeResourceProposal := daokit.ProposalRequest{ + Title: "My Proposal", + Description: "My Proposal Description", + Message: daokit.NewRemoveResourceMsg(&daokit.Resource{ + Handler: mockHandler, + Condition: daocond.MembersThreshold(0.2, tdao.dao.Members.IsMember, tdao.dao.Members.MembersCount), + }), + } + + tdao.dao.InstantExecute(removeResourceProposal) + + urequire.Equal(t, 8, tdao.dao.Core.ResourcesCount(), "expected 8 resources") +} + +func TestAddRemoveResourceInvalidateProposals(t *testing.T) { + resourceType := "newResource" + members := []Member{ + { + alice.String(), + []string{"admin"}, + }, + { + bob.String(), + []string{"admin"}, + }, + } + tdao := newTestingDAO(t, 0.2, members) + + if !tdao.dao.Members.HasRole(bob.String(), "admin") { + t.Errorf("Expected member %s to have role 'admin'", bob.String()) + } + + mockHandler := daokit.NewMessageHandler(resourceType, func(_ interface{}) {}) + + addResourceProposal := daokit.ProposalRequest{ + Title: "My Proposal", + Description: "My Proposal Description", + Message: daokit.NewAddResourceMsg(&daokit.Resource{ + Handler: mockHandler, + Condition: daocond.MembersThreshold(0.2, tdao.dao.Members.IsMember, tdao.dao.Members.MembersCount), + }), + } + std.TestSetOrigCaller(alice) + tdao.dao.InstantExecute(addResourceProposal) + + proposalAddMember := daokit.ProposalRequest{ + Message: daokit.NewMessage(MsgAddMemberKind, nil), + } + + newResourceProposal := daokit.ProposalRequest{ + Message: daokit.NewMessage(resourceType, nil), + } + + tdao.dao.Propose(newResourceProposal) + tdao.dao.Propose(proposalAddMember) + + // Check proposal is open + tdao.dao.Core.ProposalModule.Proposals.Iterate("", "", func(k string, v interface{}) bool { + p := v.(*daokit.Proposal) + if p.Message.Type() == resourceType { + urequire.Equal(t, int(daokit.ProposalStatusOpen), int(p.Status), "expected to be open") + } else if p.Message.Type() == MsgAddMemberKind { + urequire.Equal(t, int(daokit.ProposalStatusOpen), int(p.Status), "expected to be open") + } + }) + + removeResourceProposal := daokit.ProposalRequest{ + Title: "removeResource", + Description: "My Proposal Description", + Message: daokit.NewRemoveResourceMsg(&daokit.Resource{ + Handler: mockHandler, + Condition: daocond.MembersThreshold(0.2, tdao.dao.Members.IsMember, tdao.dao.Members.MembersCount), + }), + } + tdao.dao.InstantExecute(removeResourceProposal) + + // proposal should be invalidated + tdao.dao.Core.ProposalModule.Proposals.Iterate("", "", func(k string, v interface{}) bool { + p := v.(*daokit.Proposal) + if p.Message.Type() == resourceType { + urequire.Equal(t, int(daokit.ProposalStatusInvalidated), int(p.Status), "expected to be invalidated") + } else if p.Message.Type() == MsgAddMemberKind { + urequire.Equal(t, int(daokit.ProposalStatusOpen), int(p.Status), "expected to stay open") + } + }) +} diff --git a/gno/p/daokit/daokit.gno b/gno/p/daokit/daokit.gno index a0ae52748b..8f4bdb1fe4 100644 --- a/gno/p/daokit/daokit.gno +++ b/gno/p/daokit/daokit.gno @@ -1,6 +1,7 @@ package daokit import ( + "errors" "time" "gno.land/p/teritori/daocond" @@ -93,3 +94,41 @@ func (d *Core) propose(proposer string, req ProposalRequest) *Proposal { return d.ProposalModule.newProposal(proposer, req, resource.Condition.NewState()) } + +const MsgAddResourceKind = "gno.land/p/teritori/daokit.AddResource" + +func NewAddResourceHandler(d *Core) MessageHandler { + return NewMessageHandler(MsgAddResourceKind, func(ipayload interface{}) { + payload, ok := ipayload.(*Resource) + if !ok { + panic(errors.New("invalid payload type")) + } + + d.SetResource(payload) + d.ProposalModule.InvalidateWithResource(payload.Handler.Type()) + }) +} + +func NewAddResourceMsg(payload *Resource) ExecutableMessage { + return NewMessage(MsgAddResourceKind, payload) +} + +const MsgRemoveResourceKind = "gno.land/p/teritori/daokit.RemoveResource" + +func NewRemoveResourceHandler(d *Core) MessageHandler { + return NewMessageHandler(MsgRemoveResourceKind, func(ipayload interface{}) { + payload, ok := ipayload.(*Resource) + if !ok { + panic(errors.New("invalid payload type")) + } + if d.Resources.getResource(payload.Handler.Type()) == nil { + panic("ressource " + payload.Handler.Type() + " does not exists") + } + d.Resources.removeResource(payload) + d.ProposalModule.InvalidateWithResource(payload.Handler.Type()) + }) +} + +func NewRemoveResourceMsg(payload *Resource) ExecutableMessage { + return NewMessage(MsgRemoveResourceKind, payload) +} diff --git a/gno/p/daokit/proposals.gno b/gno/p/daokit/proposals.gno index 7e84b450de..669b9bbf66 100644 --- a/gno/p/daokit/proposals.gno +++ b/gno/p/daokit/proposals.gno @@ -14,6 +14,7 @@ const ( ProposalStatusOpen ProposalStatus = iota ProposalStatusPassed ProposalStatusExecuted + ProposalStatusInvalidated ) func (s ProposalStatus) String() string { @@ -24,6 +25,8 @@ func (s ProposalStatus) String() string { return "Passed" case ProposalStatusExecuted: return "Executed" + case ProposalStatusInvalidated: + return "Invalidated" default: return "Unknown" } @@ -86,6 +89,23 @@ func (p *ProposalModule) GetProposal(id uint64) *Proposal { return proposal } +func (p *ProposalModule) InvalidateWithResource(resourceType string) { + toInvalidate := map[string]*Proposal{} + + p.Proposals.Iterate("", "", func(k string, v interface{}) bool { + proposal := v.(*Proposal) + if proposal.Status == ProposalStatusOpen && proposal.Message.Type() == resourceType { + proposal.Status = ProposalStatusInvalidated + toInvalidate[k] = proposal + } + return false + }) + + for k, v := range toInvalidate { + p.Proposals.Set(k, v) + } +} + func (p *Proposal) UpdateStatus() { conditionsAreMet := p.ConditionState.Eval(p.Votes) if p.Status == ProposalStatusOpen && conditionsAreMet { diff --git a/gno/p/daokit/resources.gno b/gno/p/daokit/resources.gno index 6923b7d72d..1d5c470968 100644 --- a/gno/p/daokit/resources.gno +++ b/gno/p/daokit/resources.gno @@ -1,8 +1,6 @@ package daokit import ( - "errors" - "gno.land/p/demo/avl" "gno.land/p/teritori/daocond" ) @@ -38,34 +36,3 @@ func (r *resourcesStore) getResource(name string) *Resource { res := value.(*Resource) return res } - -const MsgAddResourceKind = "gno.land/p/teritori/daokit.AddResource" - -func NewAddResourceHandler(r *resourcesStore) MessageHandler { - return NewMessageHandler(MsgAddResourceKind, func(ipayload interface{}) { - payload, ok := ipayload.(Resource) - if !ok { - panic(errors.New("invalid payload type")) - } - - if getResource(payload.Handler.Type()) != nil { - panic("ressource " + payload.Handler.Type() + " already exists") - } - r.setResource(payload) - }) -} - -const MsgRemoveResourceKind = "gno.land/p/teritori/daokit.RemoveResource" - -func NewRemoveResourceHandler(r *resourcesStore) MessageHandler { - return NewMessageHandler(MsgRemoveResourceKind, func(ipayload interface{}) { - payload, ok := ipayload.(Resource) - if !ok { - panic(errors.New("invalid payload type")) - } - if getResource(payload.Handler.Type()) == nil { - panic("ressource " + payload.Handler.Type() + " does not exists") - } - r.removeResource(payload) - }) -} From 1aec067906dfdfd38d6e4b04c28dddaf90027c57 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 19 Feb 2025 06:11:10 +0100 Subject: [PATCH 3/3] chore: rename addresource -> setresource Signed-off-by: Norman --- gno/p/basedao/basedao.gno | 2 +- gno/p/basedao/basedao_test.gno | 6 ++++-- gno/p/daokit/daokit.gno | 39 ---------------------------------- gno/p/daokit/messages.gno | 38 +++++++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 42 deletions(-) diff --git a/gno/p/basedao/basedao.gno b/gno/p/basedao/basedao.gno index be88aab519..631dfab3d5 100644 --- a/gno/p/basedao/basedao.gno +++ b/gno/p/basedao/basedao.gno @@ -85,7 +85,7 @@ func New(conf *Config) *DAO { NewRemoveMemberHandler(dao), NewAssignRoleHandler(dao), NewUnassignRoleHandler(dao), - daokit.NewAddResourceHandler(dao.Core), + daokit.NewSetResourceHandler(dao.Core), daokit.NewRemoveResourceHandler(dao.Core), } { dao.Core.SetResource(&daokit.Resource{Handler: m, Condition: conf.InitialCondition}) diff --git a/gno/p/basedao/basedao_test.gno b/gno/p/basedao/basedao_test.gno index a137a41c41..7de13d3154 100644 --- a/gno/p/basedao/basedao_test.gno +++ b/gno/p/basedao/basedao_test.gno @@ -749,7 +749,7 @@ func TestAddRemoveResource(t *testing.T) { addResourceProposal := daokit.ProposalRequest{ Title: "My Proposal", Description: "My Proposal Description", - Message: daokit.NewAddResourceMsg(&daokit.Resource{ + Message: daokit.NewSetResourceMsg(&daokit.Resource{ Handler: mockHandler, Condition: daocond.MembersThreshold(0.2, tdao.dao.Members.IsMember, tdao.dao.Members.MembersCount), }), @@ -796,7 +796,7 @@ func TestAddRemoveResourceInvalidateProposals(t *testing.T) { addResourceProposal := daokit.ProposalRequest{ Title: "My Proposal", Description: "My Proposal Description", - Message: daokit.NewAddResourceMsg(&daokit.Resource{ + Message: daokit.NewSetResourceMsg(&daokit.Resource{ Handler: mockHandler, Condition: daocond.MembersThreshold(0.2, tdao.dao.Members.IsMember, tdao.dao.Members.MembersCount), }), @@ -823,6 +823,7 @@ func TestAddRemoveResourceInvalidateProposals(t *testing.T) { } else if p.Message.Type() == MsgAddMemberKind { urequire.Equal(t, int(daokit.ProposalStatusOpen), int(p.Status), "expected to be open") } + return false }) removeResourceProposal := daokit.ProposalRequest{ @@ -843,5 +844,6 @@ func TestAddRemoveResourceInvalidateProposals(t *testing.T) { } else if p.Message.Type() == MsgAddMemberKind { urequire.Equal(t, int(daokit.ProposalStatusOpen), int(p.Status), "expected to stay open") } + return false }) } diff --git a/gno/p/daokit/daokit.gno b/gno/p/daokit/daokit.gno index 8f4bdb1fe4..a0ae52748b 100644 --- a/gno/p/daokit/daokit.gno +++ b/gno/p/daokit/daokit.gno @@ -1,7 +1,6 @@ package daokit import ( - "errors" "time" "gno.land/p/teritori/daocond" @@ -94,41 +93,3 @@ func (d *Core) propose(proposer string, req ProposalRequest) *Proposal { return d.ProposalModule.newProposal(proposer, req, resource.Condition.NewState()) } - -const MsgAddResourceKind = "gno.land/p/teritori/daokit.AddResource" - -func NewAddResourceHandler(d *Core) MessageHandler { - return NewMessageHandler(MsgAddResourceKind, func(ipayload interface{}) { - payload, ok := ipayload.(*Resource) - if !ok { - panic(errors.New("invalid payload type")) - } - - d.SetResource(payload) - d.ProposalModule.InvalidateWithResource(payload.Handler.Type()) - }) -} - -func NewAddResourceMsg(payload *Resource) ExecutableMessage { - return NewMessage(MsgAddResourceKind, payload) -} - -const MsgRemoveResourceKind = "gno.land/p/teritori/daokit.RemoveResource" - -func NewRemoveResourceHandler(d *Core) MessageHandler { - return NewMessageHandler(MsgRemoveResourceKind, func(ipayload interface{}) { - payload, ok := ipayload.(*Resource) - if !ok { - panic(errors.New("invalid payload type")) - } - if d.Resources.getResource(payload.Handler.Type()) == nil { - panic("ressource " + payload.Handler.Type() + " does not exists") - } - d.Resources.removeResource(payload) - d.ProposalModule.InvalidateWithResource(payload.Handler.Type()) - }) -} - -func NewRemoveResourceMsg(payload *Resource) ExecutableMessage { - return NewMessage(MsgRemoveResourceKind, payload) -} diff --git a/gno/p/daokit/messages.gno b/gno/p/daokit/messages.gno index 6b5bc670b9..ecfdd49225 100644 --- a/gno/p/daokit/messages.gno +++ b/gno/p/daokit/messages.gno @@ -64,3 +64,41 @@ func (g *genericMessageHandler) Instantiate() ExecutableMessage { func (g *genericMessageHandler) Type() string { return g.kind } + +const MsgSetResourceKind = "gno.land/p/teritori/daokit.SetResource" + +func NewSetResourceHandler(d *Core) MessageHandler { + return NewMessageHandler(MsgSetResourceKind, func(ipayload interface{}) { + payload, ok := ipayload.(*Resource) + if !ok { + panic(errors.New("invalid payload type")) + } + + d.SetResource(payload) + d.ProposalModule.InvalidateWithResource(payload.Handler.Type()) + }) +} + +func NewSetResourceMsg(payload *Resource) ExecutableMessage { + return NewMessage(MsgSetResourceKind, payload) +} + +const MsgRemoveResourceKind = "gno.land/p/teritori/daokit.RemoveResource" + +func NewRemoveResourceHandler(d *Core) MessageHandler { + return NewMessageHandler(MsgRemoveResourceKind, func(ipayload interface{}) { + payload, ok := ipayload.(*Resource) + if !ok { + panic(errors.New("invalid payload type")) + } + if d.Resources.getResource(payload.Handler.Type()) == nil { + panic("ressource " + payload.Handler.Type() + " does not exists") + } + d.Resources.removeResource(payload) + d.ProposalModule.InvalidateWithResource(payload.Handler.Type()) + }) +} + +func NewRemoveResourceMsg(payload *Resource) ExecutableMessage { + return NewMessage(MsgRemoveResourceKind, payload) +}