Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions gno/p/basedao/basedao.gno
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ func New(conf *Config) *DAO {
NewRemoveMemberHandler(dao),
NewAssignRoleHandler(dao),
NewUnassignRoleHandler(dao),
daokit.NewSetResourceHandler(dao.Core),
daokit.NewRemoveResourceHandler(dao.Core),
} {
dao.Core.SetResource(&daokit.Resource{Handler: m, Condition: conf.InitialCondition})
}
Expand Down
124 changes: 123 additions & 1 deletion gno/p/basedao/basedao_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -725,3 +725,125 @@ 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.NewSetResourceMsg(&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.NewSetResourceMsg(&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")
}
return false
})

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")
}
return false
})
}
38 changes: 38 additions & 0 deletions gno/p/daokit/messages.gno
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
20 changes: 20 additions & 0 deletions gno/p/daokit/proposals.gno
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
ProposalStatusOpen ProposalStatus = iota
ProposalStatusPassed
ProposalStatusExecuted
ProposalStatusInvalidated
)

func (s ProposalStatus) String() string {
Expand All @@ -27,6 +28,8 @@ func (s ProposalStatus) String() string {
return "Passed"
case ProposalStatusExecuted:
return "Executed"
case ProposalStatusInvalidated:
return "Invalidated"
default:
return "Unknown"
}
Expand Down Expand Up @@ -91,6 +94,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 {
Expand Down
4 changes: 4 additions & 0 deletions gno/p/daokit/resources.gno
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,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 {
Expand Down
Loading