From cd843be27cf9fbb5160ff57535a20f685d44496f Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Tue, 28 Jan 2025 16:45:48 +0100 Subject: [PATCH] feat: sync daokit w/ zenao --- .../dao_roles_based/dao_roles_based_test.gno | 419 ---------- gno/p/dao_roles_based/gno.mod | 1 - gno/p/dao_roles_based/members.gno | 57 -- gno/p/dao_roles_based/messages.gno | 151 ---- .../dao_roles_based.gno | 63 +- gno/p/daokit/daokit_test.gno | 721 ++++++++++++++++++ gno/p/daokit/gno.mod | 1 + gno/p/daokit/members.gno | 121 +++ gno/p/daokit/messages.gno | 353 +++++++++ .../{dao_roles_based => daokit}/proposals.gno | 5 +- gno/p/{dao_roles_based => daokit}/render.gno | 18 +- .../{dao_roles_based => daokit}/resources.gno | 2 +- gno/p/role_manager/role_manager.gno | 38 + gno/p/role_manager/role_manager_test.gno | 34 + gno/r/dao_realm/dao_realm.gno | 24 +- gno/r/dao_realm/dao_realm_test.gno | 4 +- gno/r/social_feeds/messages.gno | 12 +- 17 files changed, 1330 insertions(+), 694 deletions(-) delete mode 100644 gno/p/dao_roles_based/dao_roles_based_test.gno delete mode 100644 gno/p/dao_roles_based/gno.mod delete mode 100644 gno/p/dao_roles_based/members.gno delete mode 100644 gno/p/dao_roles_based/messages.gno rename gno/p/{dao_roles_based => daokit}/dao_roles_based.gno (67%) create mode 100644 gno/p/daokit/daokit_test.gno create mode 100644 gno/p/daokit/gno.mod create mode 100644 gno/p/daokit/members.gno create mode 100644 gno/p/daokit/messages.gno rename gno/p/{dao_roles_based => daokit}/proposals.gno (97%) rename gno/p/{dao_roles_based => daokit}/render.gno (89%) rename gno/p/{dao_roles_based => daokit}/resources.gno (95%) diff --git a/gno/p/dao_roles_based/dao_roles_based_test.gno b/gno/p/dao_roles_based/dao_roles_based_test.gno deleted file mode 100644 index bbda1a99e..000000000 --- a/gno/p/dao_roles_based/dao_roles_based_test.gno +++ /dev/null @@ -1,419 +0,0 @@ -package dao_roles_based - -// Test Dao Creation (done) -// Test Dao Has Role (done) -// Test Dao IsMember (done) -// Test Dao Proposal (done) -// Test Dao Vote -// Test Dao Execute - -import ( - "std" - "testing" - - "gno.land/p/demo/json" - "gno.land/p/demo/testutils" -) - -var ( - alice = testutils.TestAddress("alice") - bob = testutils.TestAddress("bob") - carol = testutils.TestAddress("carol") - dave = testutils.TestAddress("dave") -) - -type MockExecutableMessage struct{} - -func (msg MockExecutableMessage) Type() string { - return "MockExecutableMessage" -} - -func (msg MockExecutableMessage) ToJSON() *json.Node { - return json.ObjectNode("", map[string]*json.Node{}) -} - -func (msg MockExecutableMessage) FromJSON(ast *json.Node) { -} - -func (msg MockExecutableMessage) String() string { - return "MockExecutableMessage" -} - -type MockMessageHandler struct{} - -func (h MockMessageHandler) Execute(iMsg ExecutableMessage) { -} - -func (h MockMessageHandler) Instantiate() ExecutableMessage { - return MockExecutableMessage{} -} - -func (h MockMessageHandler) Type() string { - return MockExecutableMessage{}.Type() -} - -func TestNewDaoRolesBasedJSON(t *testing.T) { - name := "My DAO" - description := "My DAO Description" - roles := []string{"admin"} - members := [][]string{{alice.String(), "admin"}, {bob.String()}, {carol.String()}} - resourcesJSON := `[]` - handlers := []MessageHandler{} - - dao := NewDaoRolesBasedJSON(name, description, roles, members, resourcesJSON, handlers) - roles = dao.MemberModule.GetRoles() - if len(roles) != 1 { - t.Errorf("Expected 1 role, got %d", len(roles)) - } - if roles[0] != "admin" { - t.Errorf("Expected role 'admin', got %s", roles[0]) - } - - for _, member := range members { - address := member[0] - if !dao.IsMember(address) { - t.Errorf("Expected member %s to be a member", address) - } - if len(member) == 2 && !dao.HasRole(address, member[1]) { - t.Errorf("Expected member %s to have role %s", address, member[1]) - } - } - - resourcesLen := dao.ResourcesModule.resources.Size() - if resourcesLen != 0 { - t.Errorf("Expected 0 resources, got %d", resourcesLen) - } -} - -func TestProposeJSON(t *testing.T) { - name := "My DAO" - description := "My DAO Description" - roles := []string{"admin"} - members := [][]string{{alice.String(), "admin"}, {bob.String()}, {carol.String()}} - - messagesHandlers := []MessageHandler{ - &MockMessageHandler{}, - } - - resourcesJSON := `[{"resource":"MockExecutableMessage","condition":{"type":"members-threshold","threshold":"60"}}]` - std.TestSetOrigCaller(alice) - dao := NewDaoRolesBasedJSON(name, description, roles, members, resourcesJSON, messagesHandlers) - - validProposalJSON := `{"title":"My Proposal","description":"My Proposal Description","message":{"type":"MockExecutableMessage","payload":{}}}` - - type testNewProposalInput struct { - proposalJSON string - proposer std.Address - } - - type tesNewProposalExpected struct { - title string - description string - proposer std.Address - messsageType string - panic bool - } - - type testNewProposal struct { - input testNewProposalInput - expected tesNewProposalExpected - } - - type testNewProposalTable map[string]testNewProposal - - tests := testNewProposalTable{ - "Success": { - input: testNewProposalInput{ - proposalJSON: validProposalJSON, - proposer: alice, - }, - expected: tesNewProposalExpected{ - title: "My Proposal", - description: "My Proposal Description", - proposer: alice, - messsageType: "MockExecutableMessage", - panic: false, - }, - }, - "Bad JSON format": { - input: testNewProposalInput{ - proposalJSON: `badjson`, - proposer: alice, - }, - expected: tesNewProposalExpected{ - panic: true, - }, - }, - "Non-member": { - input: testNewProposalInput{ - proposalJSON: validProposalJSON, - proposer: dave, - }, - expected: tesNewProposalExpected{ - panic: true, - }, - }, - "Unknown message type": { - input: testNewProposalInput{ - proposalJSON: `{"title":"My Proposal","description":"My Proposal Description","message":{"type":"UnknownMessage","payload":{""}}}`, - proposer: alice, - }, - expected: tesNewProposalExpected{ - panic: true, - }, - }, - } - - for testName, test := range tests { - t.Run(testName, func(t *testing.T) { - if test.expected.panic { - defer func() { - if r := recover(); r == nil { - t.Errorf("Expected panic, got none") - } - }() - } - - std.TestSetOrigCaller(test.input.proposer) - dao.ProposeJSON(test.input.proposalJSON) - - proposal := dao.ProposalModule.getProposal(1) - if proposal.title != test.expected.title { - t.Errorf("Expected title %s, got %s", test.expected.title, proposal.title) - } - if proposal.description != test.expected.description { - t.Errorf("Expected description %s, got %s", test.expected.description, proposal.description) - } - if proposal.proposer.String() != test.expected.proposer.String() { - t.Errorf("Expected proposer %s, got %s", test.expected.proposer, proposal.proposer) - } - if proposal.message.Type() != test.expected.messsageType { - t.Errorf("Expected message type %s, got %s", test.expected.messsageType, proposal.message.Type()) - } - }) - } -} - -func TestVote(t *testing.T) { - name := "My DAO" - description := "My DAO Description" - roles := []string{"admin"} - members := [][]string{{alice.String(), "admin"}, {bob.String()}, {carol.String()}} - - messagesHandlers := []MessageHandler{ - &MockMessageHandler{}, - } - - resourcesJSON := `[{"resource":"MockExecutableMessage","condition":{"type":"members-threshold","threshold":"10"}}]` - std.TestSetOrigCaller(alice) - dao := NewDaoRolesBasedJSON(name, description, roles, members, resourcesJSON, messagesHandlers) - proposalJSON := `{"title":"My Proposal","description":"My Proposal Description","message":{"type":"MockExecutableMessage","payload":{}}}` - std.TestSetOrigCaller(alice) - dao.ProposeJSON(proposalJSON) - - type testVoteInput struct { - proposalID uint64 - vote string - voter std.Address - } - - type testVoteExpected struct { - eval bool - panic bool - } - - type testVote struct { - input testVoteInput - expected testVoteExpected - } - - type testVoteTable map[string]testVote - - tests := testVoteTable{ - "Success no": { - input: testVoteInput{ - proposalID: 1, - vote: "no", - voter: alice, - }, - expected: testVoteExpected{ - eval: false, - panic: false, - }, - }, - "Success yes": { - input: testVoteInput{ - proposalID: 1, - vote: "yes", - voter: alice, - }, - expected: testVoteExpected{ - eval: true, - panic: false, - }, - }, - "Unknown proposal": { - input: testVoteInput{ - proposalID: 2, - vote: "yes", - voter: alice, - }, - expected: testVoteExpected{ - eval: false, - panic: true, - }, - }, - "Non-member": { - input: testVoteInput{ - proposalID: 1, - vote: "yes", - voter: dave, - }, - expected: testVoteExpected{ - eval: false, - panic: true, - }, - }, - "Invalid vote": { - input: testVoteInput{ - proposalID: 1, - vote: "invalid", - voter: alice, - }, - expected: testVoteExpected{ - eval: false, - panic: true, - }, - }, - } - - for testName, test := range tests { - t.Run(testName, func(t *testing.T) { - if test.expected.panic { - defer func() { - if r := recover(); r == nil { - t.Errorf("Expected panic, got none") - } - }() - } - - std.TestSetOrigCaller(test.input.voter) - dao.Vote(test.input.proposalID, test.input.vote) - - proposal := dao.ProposalModule.getProposal(test.input.proposalID) - eval := proposal.state.Eval(proposal.votes) - if eval != test.expected.eval { - t.Errorf("Expected eval %t, got %t", test.expected.eval, eval) - } - }) - } -} - -func TestExecuteProposal(t *testing.T) { - name := "My DAO" - description := "My DAO Description" - roles := []string{"admin"} - members := [][]string{{alice.String(), "admin"}, {bob.String()}, {carol.String()}} - - messagesHandlers := []MessageHandler{ - &MockMessageHandler{}, - } - - resourcesJSON := `[{"resource":"MockExecutableMessage","condition":{"type":"members-threshold","threshold":"10"}}]` - std.TestSetOrigCaller(alice) - dao := NewDaoRolesBasedJSON(name, description, roles, members, resourcesJSON, messagesHandlers) - proposalJSON := `{"title":"My Proposal","description":"My Proposal Description","message":{"type":"MockExecutableMessage","payload":{}}}` - std.TestSetOrigCaller(alice) - dao.ProposeJSON(proposalJSON) - - type testExecuteInput struct { - proposalID uint64 - executor std.Address - haveVote bool - voter std.Address - } - - type testExecuteExpected struct { - panic bool - } - - type testExecute struct { - input testExecuteInput - expected testExecuteExpected - } - - type testExecuteTable map[string]testExecute - - tests := testExecuteTable{ - "Conditions not met": { - input: testExecuteInput{ - proposalID: 1, - executor: alice, - haveVote: false, - voter: alice, - }, - expected: testExecuteExpected{ - panic: true, - }, - }, - "Success": { - input: testExecuteInput{ - proposalID: 1, - executor: alice, - haveVote: true, - voter: alice, - }, - expected: testExecuteExpected{ - panic: false, - }, - }, - "Unknown proposal": { - input: testExecuteInput{ - proposalID: 2, - executor: alice, - haveVote: false, - voter: alice, - }, - expected: testExecuteExpected{ - panic: true, - }, - }, - "Non-member": { - input: testExecuteInput{ - proposalID: 1, - executor: dave, - haveVote: false, - voter: alice, - }, - expected: testExecuteExpected{ - panic: true, - }, - }, - } - - for testName, test := range tests { - t.Run(testName, func(t *testing.T) { - if test.expected.panic { - defer func() { - if r := recover(); r == nil { - t.Errorf("Expected panic, got none") - } - }() - } - - if test.input.haveVote { - std.TestSetOrigCaller(test.input.voter) - dao.Vote(test.input.proposalID, "yes") - } - - std.TestSetOrigCaller(test.input.executor) - dao.Execute(test.input.proposalID) - - proposal := dao.ProposalModule.getProposal(test.input.proposalID) - - if proposal.status != ProposalStatusExecuted { - t.Errorf("Expected status %s, got %s", ProposalStatusExecuted, proposal.status) - } - }) - } -} diff --git a/gno/p/dao_roles_based/gno.mod b/gno/p/dao_roles_based/gno.mod deleted file mode 100644 index 9e2990236..000000000 --- a/gno/p/dao_roles_based/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/teritori/dao_roles_based diff --git a/gno/p/dao_roles_based/members.gno b/gno/p/dao_roles_based/members.gno deleted file mode 100644 index 1d508635c..000000000 --- a/gno/p/dao_roles_based/members.gno +++ /dev/null @@ -1,57 +0,0 @@ -package dao_roles_based - -import ( - "std" - - "gno.land/p/demo/avl" - "gno.land/p/teritori/role_manager" -) - -type MemberModule struct { - roleManager *role_manager.RoleManager - members *avl.Tree // address -> struct{} -} - -func newMemberModule() *MemberModule { - return &MemberModule{ - roleManager: role_manager.NewWithAddress(std.CurrentRealm().Addr()), - members: avl.NewTree(), - } -} - -func (m *MemberModule) HasRole(memberId string, role string) bool { - return m.roleManager.HasRole(std.Address(memberId), role) -} - -func (m *MemberModule) IsMember(memberId string) bool { - return m.members.Has(memberId) -} - -func (m *MemberModule) MembersCount() uint64 { - return uint64(m.members.Size()) -} - -func (m *MemberModule) GetRoles() []string { - return m.roleManager.GetRoles() -} - -func (m *MemberModule) GetUserRoles(memberId string) []string { - return m.roleManager.GetUserRoles(std.Address(memberId)) -} - -func (m *MemberModule) setRoles(roles []string) { - caller := std.CurrentRealm().Addr() - for _, role := range roles { - m.roleManager.CreateNewRole(role, []string{}, caller) - } -} - -func (m *MemberModule) setMembers(members [][]string) { - caller := std.CurrentRealm().Addr() - for _, member := range members { - m.members.Set(member[0], struct{}{}) - for _, role := range member[1:] { - m.roleManager.AddRoleToUser(std.Address(member[0]), role, caller) - } - } -} diff --git a/gno/p/dao_roles_based/messages.gno b/gno/p/dao_roles_based/messages.gno deleted file mode 100644 index dccb15606..000000000 --- a/gno/p/dao_roles_based/messages.gno +++ /dev/null @@ -1,151 +0,0 @@ -package dao_roles_based - -import ( - "gno.land/p/demo/avl" - "gno.land/p/demo/json" -) - -type ExecutableMessage interface { - ToJSON() *json.Node - FromJSON(ast *json.Node) - - String() string - Type() string -} - -type MessageHandler interface { - Execute(message ExecutableMessage) - Instantiate() ExecutableMessage - Type() string -} - -type MessagesRegistry struct { - handlers *avl.Tree -} - -func newMessagesRegistry() *MessagesRegistry { - registry := &MessagesRegistry{ - handlers: avl.NewTree(), - } - registry.register(NewRegisterHandlerExecutableMessageHandler(registry)) - registry.register(NewRemoveHandlerExecutableMessageHandler(registry)) - return registry -} - -func (r *MessagesRegistry) register(handler MessageHandler) { - r.handlers.Set(handler.Type(), handler) -} - -func (r *MessagesRegistry) remove(t string) { - r.handlers.Remove(t) -} - -func (r *MessagesRegistry) messageFromJSON(message *json.Node) ExecutableMessage { - messageType := json.Must(message.GetKey("type")).MustString() - payload := json.Must(message.GetKey("payload")) - h, ok := r.handlers.Get(messageType) - if !ok { - panic("invalid ExecutableMessage: invalid message type") - } - - instance := h.(MessageHandler).Instantiate() - instance.FromJSON(payload) - return instance -} - -func (r *MessagesRegistry) execute(msg ExecutableMessage) { - h, ok := r.handlers.Get(msg.Type()) - if !ok { - panic("invalid ExecutableMessage: invalid message type") - } - - h.(MessageHandler).Execute(msg) -} - -type RegisterHandlerExecutableMessage struct { - Handler MessageHandler -} - -var _ ExecutableMessage = &RegisterHandlerExecutableMessage{} - -func (m RegisterHandlerExecutableMessage) Type() string { - return "gno.land/p/teritori/dao_interfaces.RegisterHandler" -} - -func (m *RegisterHandlerExecutableMessage) FromJSON(ast *json.Node) { - panic("not implemented") -} - -func (m *RegisterHandlerExecutableMessage) ToJSON() *json.Node { - panic("not implemented") -} - -func (m *RegisterHandlerExecutableMessage) String() string { - return m.Handler.Type() -} - -type RegisterHandlerExecutableMessageHandler struct { - registry *MessagesRegistry -} - -var _ MessageHandler = &RegisterHandlerExecutableMessageHandler{} - -func NewRegisterHandlerExecutableMessageHandler(registry *MessagesRegistry) *RegisterHandlerExecutableMessageHandler { - return &RegisterHandlerExecutableMessageHandler{registry: registry} -} - -func (h RegisterHandlerExecutableMessageHandler) Type() string { - return RegisterHandlerExecutableMessage{}.Type() -} - -func (h *RegisterHandlerExecutableMessageHandler) Instantiate() ExecutableMessage { - return &RegisterHandlerExecutableMessage{} -} - -func (h *RegisterHandlerExecutableMessageHandler) Execute(msg ExecutableMessage) { - h.registry.register(msg.(*RegisterHandlerExecutableMessage).Handler) -} - -type RemoveHandlerExecutableMessage struct { - HandlerType string -} - -var _ ExecutableMessage = &RemoveHandlerExecutableMessage{} - -func (m RemoveHandlerExecutableMessage) Type() string { - return "gno.land/p/teritori/dao_interfaces.RemoveHandler" -} - -func (m *RemoveHandlerExecutableMessage) FromJSON(ast *json.Node) { - m.HandlerType = ast.MustString() -} - -func (m *RemoveHandlerExecutableMessage) ToJSON() *json.Node { - return json.StringNode("", m.HandlerType) -} - -func (m *RemoveHandlerExecutableMessage) String() string { - return m.HandlerType -} - -type RemoveHandlerExecutableMessageHandler struct { - registry *MessagesRegistry -} - -var _ MessageHandler = &RemoveHandlerExecutableMessageHandler{} - -func NewRemoveHandlerExecutableMessageHandler(registry *MessagesRegistry) *RemoveHandlerExecutableMessageHandler { - return &RemoveHandlerExecutableMessageHandler{registry: registry} -} - -func (h RemoveHandlerExecutableMessageHandler) Type() string { - return RemoveHandlerExecutableMessage{}.Type() -} - -func (h *RemoveHandlerExecutableMessageHandler) Instantiate() ExecutableMessage { - return &RemoveHandlerExecutableMessage{} -} - -func (h *RemoveHandlerExecutableMessageHandler) Execute(msg ExecutableMessage) { - h.registry.remove(msg.(*RemoveHandlerExecutableMessage).HandlerType) -} diff --git a/gno/p/dao_roles_based/dao_roles_based.gno b/gno/p/daokit/dao_roles_based.gno similarity index 67% rename from gno/p/dao_roles_based/dao_roles_based.gno rename to gno/p/daokit/dao_roles_based.gno index e06f8d167..91890a0b7 100644 --- a/gno/p/dao_roles_based/dao_roles_based.gno +++ b/gno/p/daokit/dao_roles_based.gno @@ -1,4 +1,4 @@ -package dao_roles_based +package daokit import ( "path" @@ -10,7 +10,7 @@ import ( "gno.land/p/teritori/daocond" ) -type DaoRolesBased struct { +type Dao struct { Name string Description string @@ -23,16 +23,16 @@ type DaoRolesBased struct { realmName string } -func NewDaoRolesBasedJSON(name, description string, roles []string, members [][]string, resourcesJSON string, handlers []MessageHandler) *DaoRolesBased { - if len(name) < 5 { - panic("dao name should be at least 5 characters long") +func NewDaoJSON(name, description string, roles []string, members [][]string, resourcesJSON string, handlers []MessageHandler) *Dao { + if len(name) < 1 { + panic("dao name should be at least 1 characters long") } - if len(description) < 10 { - panic("dao description should be at least 10 characters long") + if len(description) < 1 { + panic("dao description should be at least 1 characters long") } - dao := &DaoRolesBased{ + dao := &Dao{ Name: name, Description: description, MemberModule: newMemberModule(), @@ -42,12 +42,24 @@ func NewDaoRolesBasedJSON(name, description string, roles []string, members [][] renderingRouter: mux.NewRouter(), realmName: path.Base(std.CurrentRealm().PkgPath()), } + dao.initMessagesRegistry() dao.initRenderingRouter() dao.MemberModule.setRoles(roles) dao.MemberModule.setMembers(members) resources := dao.parseResourcesJSON(resourcesJSON) + conditionRaw, ok := resources.Get("init") + if !ok { + panic("resources should have an 'init' condition") + } + condition := conditionRaw.(daocond.Condition) + dao.MessagesRegistry.handlers.Iterate("", "", func(key string, value interface{}) bool { + resources.Set(key, condition) + return false + }) + resources.Remove("init") + dao.ResourcesModule.setResources(resources) for _, handler := range handlers { @@ -57,7 +69,7 @@ func NewDaoRolesBasedJSON(name, description string, roles []string, members [][] return dao } -func (d *DaoRolesBased) Vote(proposalID uint64, vote string) { +func (d *Dao) Vote(proposalID uint64, vote string) { voter := std.PrevRealm().Addr() if !d.MemberModule.IsMember(voter.String()) { panic("voter is not a member") @@ -86,7 +98,7 @@ func (d *DaoRolesBased) Vote(proposalID uint64, vote string) { } -func (d *DaoRolesBased) Execute(proposalID uint64) { +func (d *Dao) Execute(proposalID uint64) { executor := std.PrevRealm().Addr() if !d.MemberModule.IsMember(executor.String()) { panic("executor is not a member") @@ -114,26 +126,23 @@ func (d *DaoRolesBased) Execute(proposalID uint64) { proposal.status = ProposalStatusExecuted } -func (d *DaoRolesBased) ProposeJSON(proposalJSON string) { +func (d *Dao) ProposeJSON(proposalJSON string) { var req ProposalRequest req.fromJSON(json.Must(json.Unmarshal([]byte(proposalJSON)))) msg := d.MessagesRegistry.messageFromJSON(req.message) d.propose(req.title, req.description, msg.Type(), msg) } -func (d *DaoRolesBased) HasRole(memberId string, role string) bool { - return d.MemberModule.HasRole(memberId, role) -} - -func (d *DaoRolesBased) IsMember(memberId string) bool { - return d.MemberModule.IsMember(memberId) -} - -func (d *DaoRolesBased) MembersCount() uint64 { - return d.MemberModule.MembersCount() +func (d *Dao) InstantExecute(proposalJSON string) { + var req ProposalRequest + req.fromJSON(json.Must(json.Unmarshal([]byte(proposalJSON)))) + msg := d.MessagesRegistry.messageFromJSON(req.message) + proposal := d.propose(req.title, req.description, msg.Type(), msg) + d.Vote(uint64(proposal.id), "yes") + d.Execute(uint64(proposal.id)) } -func (d *DaoRolesBased) propose(title, description, resource string, message ExecutableMessage) { +func (d *Dao) propose(title, description, resource string, message ExecutableMessage) *Proposal { proposer := std.PrevRealm().Addr() if !d.MemberModule.IsMember(proposer.String()) { panic(proposer + " proposer is not a member" + proposer) @@ -144,18 +153,18 @@ func (d *DaoRolesBased) propose(title, description, resource string, message Exe panic("resource not found") } - if len(title) < 9 { + if len(title) < 3 { panic("title should be at least 9 characters long") } - if len(description) < 15 { + if len(description) < 3 { panic("description should be at least 15 characters long") } - d.ProposalModule.newProposal(title, description, proposer, message, condition.NewState()) + return d.ProposalModule.newProposal(title, description, proposer, message, condition.NewState()) } -func (d *DaoRolesBased) parseResourcesJSON(resourcesJSON string) *avl.Tree { +func (d *Dao) parseResourcesJSON(resourcesJSON string) *avl.Tree { nodes, err := json.Unmarshal([]byte(resourcesJSON)) if err != nil { panic("invalid resources json format") @@ -164,7 +173,7 @@ func (d *DaoRolesBased) parseResourcesJSON(resourcesJSON string) *avl.Tree { resources := avl.NewTree() for _, resourceNode := range resourcesNodesArray { resourceObj := resourceNode.MustObject() - condition := daocond.ConditionFromJSON(resourceObj["condition"], d.HasRole, d.IsMember, d.MembersCount) + condition := daocond.ConditionFromJSON(resourceObj["condition"], d.MemberModule.HasRole, d.MemberModule.IsMember, d.MemberModule.MembersCount) resources.Set(resourceObj["resource"].MustString(), condition) } return resources diff --git a/gno/p/daokit/daokit_test.gno b/gno/p/daokit/daokit_test.gno new file mode 100644 index 000000000..4b541f93a --- /dev/null +++ b/gno/p/daokit/daokit_test.gno @@ -0,0 +1,721 @@ +package daokit + +import ( + "std" + "testing" + + "gno.land/p/demo/json" + "gno.land/p/demo/testutils" +) + +var ( + alice = testutils.TestAddress("alice") + bob = testutils.TestAddress("bob") + carol = testutils.TestAddress("carol") + dave = testutils.TestAddress("dave") +) + +type MockExecutableMessage struct{} + +func (msg MockExecutableMessage) Type() string { + return "MockExecutableMessage" +} + +func (msg MockExecutableMessage) ToJSON() *json.Node { + return json.ObjectNode("", map[string]*json.Node{}) +} + +func (msg MockExecutableMessage) FromJSON(ast *json.Node) { +} + +func (msg MockExecutableMessage) String() string { + return "MockExecutableMessage" +} + +type MockMessageHandler struct{} + +func (h MockMessageHandler) Execute(iMsg ExecutableMessage) { +} + +func (h MockMessageHandler) Instantiate() ExecutableMessage { + return MockExecutableMessage{} +} + +func (h MockMessageHandler) Type() string { + return MockExecutableMessage{}.Type() +} + +func TestNewDaoJSON(t *testing.T) { + name := "My DAO" + description := "My DAO Description" + roles := []string{"admin"} + members := [][]string{{alice.String(), "admin"}, {bob.String()}, {carol.String()}} + resourcesJSON := `[{"resource":"init","condition":{"type":"members-threshold","threshold":"60"}}]` + handlers := []MessageHandler{} + + dao := NewDaoJSON(name, description, roles, members, resourcesJSON, handlers) + roles = dao.MemberModule.GetRoles() + if len(roles) != 1 { + t.Errorf("Expected 1 role, got %d", len(roles)) + } + if roles[0] != "admin" { + t.Errorf("Expected role 'admin', got %s", roles[0]) + } + + for _, member := range members { + address := member[0] + if !dao.MemberModule.IsMember(address) { + t.Errorf("Expected member %s to be a member", address) + } + if len(member) == 2 && !dao.MemberModule.HasRole(address, member[1]) { + t.Errorf("Expected member %s to have role %s", address, member[1]) + } + } + + resourcesLen := dao.ResourcesModule.resources.Size() + if resourcesLen != 6 { // There is 6 default resources + t.Errorf("Expected 6 resources, got %d", resourcesLen) + } +} + +func TestProposeJSON(t *testing.T) { + name := "My DAO" + description := "My DAO Description" + roles := []string{"admin"} + members := [][]string{{alice.String(), "admin"}, {bob.String()}, {carol.String()}} + + messagesHandlers := []MessageHandler{ + &MockMessageHandler{}, + } + + resourcesJSON := `[{"resource":"MockExecutableMessage","condition":{"type":"members-threshold","threshold":"60"}},{"resource":"init","condition":{"type":"members-threshold","threshold":"60"}}]` + std.TestSetOrigCaller(alice) + dao := NewDaoJSON(name, description, roles, members, resourcesJSON, messagesHandlers) + + validProposalJSON := `{"title":"My Proposal","description":"My Proposal Description","message":{"type":"MockExecutableMessage","payload":{}}}` + + type testNewProposalInput struct { + proposalJSON string + proposer std.Address + } + + type tesNewProposalExpected struct { + title string + description string + proposer std.Address + messsageType string + panic bool + } + + type testNewProposal struct { + input testNewProposalInput + expected tesNewProposalExpected + } + + type testNewProposalTable map[string]testNewProposal + + tests := testNewProposalTable{ + "Success": { + input: testNewProposalInput{ + proposalJSON: validProposalJSON, + proposer: alice, + }, + expected: tesNewProposalExpected{ + title: "My Proposal", + description: "My Proposal Description", + proposer: alice, + messsageType: "MockExecutableMessage", + panic: false, + }, + }, + "Bad JSON format": { + input: testNewProposalInput{ + proposalJSON: `badjson`, + proposer: alice, + }, + expected: tesNewProposalExpected{ + panic: true, + }, + }, + "Non-member": { + input: testNewProposalInput{ + proposalJSON: validProposalJSON, + proposer: dave, + }, + expected: tesNewProposalExpected{ + panic: true, + }, + }, + "Unknown message type": { + input: testNewProposalInput{ + proposalJSON: `{"title":"My Proposal","description":"My Proposal Description","message":{"type":"UnknownMessage","payload":{""}}}`, + proposer: alice, + }, + expected: tesNewProposalExpected{ + panic: true, + }, + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + if test.expected.panic { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic, got none") + } + }() + } + + std.TestSetOrigCaller(test.input.proposer) + dao.ProposeJSON(test.input.proposalJSON) + + proposal := dao.ProposalModule.getProposal(1) + if proposal.title != test.expected.title { + t.Errorf("Expected title %s, got %s", test.expected.title, proposal.title) + } + if proposal.description != test.expected.description { + t.Errorf("Expected description %s, got %s", test.expected.description, proposal.description) + } + if proposal.proposer.String() != test.expected.proposer.String() { + t.Errorf("Expected proposer %s, got %s", test.expected.proposer, proposal.proposer) + } + if proposal.message.Type() != test.expected.messsageType { + t.Errorf("Expected message type %s, got %s", test.expected.messsageType, proposal.message.Type()) + } + }) + } +} + +func TestVote(t *testing.T) { + name := "My DAO" + description := "My DAO Description" + roles := []string{"admin"} + members := [][]string{{alice.String(), "admin"}, {bob.String()}, {carol.String()}} + + messagesHandlers := []MessageHandler{ + &MockMessageHandler{}, + } + + resourcesJSON := `[{"resource":"MockExecutableMessage","condition":{"type":"members-threshold","threshold":"20"}},{"resource":"init","condition":{"type":"members-threshold","threshold":"60"}}]` + std.TestSetOrigCaller(alice) + dao := NewDaoJSON(name, description, roles, members, resourcesJSON, messagesHandlers) + proposalJSON := `{"title":"My Proposal","description":"My Proposal Description","message":{"type":"MockExecutableMessage","payload":{}}}` + std.TestSetOrigCaller(alice) + dao.ProposeJSON(proposalJSON) + + type testVoteInput struct { + proposalID uint64 + vote string + voter std.Address + } + + type testVoteExpected struct { + eval bool + panic bool + } + + type testVote struct { + input testVoteInput + expected testVoteExpected + } + + type testVoteTable map[string]testVote + + tests := testVoteTable{ + "Success no": { + input: testVoteInput{ + proposalID: 1, + vote: "no", + voter: alice, + }, + expected: testVoteExpected{ + eval: false, + panic: false, + }, + }, + "Success yes": { + input: testVoteInput{ + proposalID: 1, + vote: "yes", + voter: alice, + }, + expected: testVoteExpected{ + eval: true, + panic: false, + }, + }, + "Unknown proposal": { + input: testVoteInput{ + proposalID: 2, + vote: "yes", + voter: alice, + }, + expected: testVoteExpected{ + eval: false, + panic: true, + }, + }, + "Non-member": { + input: testVoteInput{ + proposalID: 1, + vote: "yes", + voter: dave, + }, + expected: testVoteExpected{ + eval: false, + panic: true, + }, + }, + "Invalid vote": { + input: testVoteInput{ + proposalID: 1, + vote: "invalid", + voter: alice, + }, + expected: testVoteExpected{ + eval: false, + panic: true, + }, + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + if test.expected.panic { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic, got none") + } + }() + } + + std.TestSetOrigCaller(test.input.voter) + dao.Vote(test.input.proposalID, test.input.vote) + + proposal := dao.ProposalModule.getProposal(test.input.proposalID) + eval := proposal.state.Eval(proposal.votes) + if eval != test.expected.eval { + t.Errorf("Expected eval %t, got %t", test.expected.eval, eval) + } + }) + } +} + +func TestExecuteProposal(t *testing.T) { + name := "My DAO" + description := "My DAO Description" + roles := []string{"admin"} + members := [][]string{{alice.String(), "admin"}, {bob.String()}, {carol.String()}} + + messagesHandlers := []MessageHandler{ + &MockMessageHandler{}, + } + + resourcesJSON := `[{"resource":"MockExecutableMessage","condition":{"type":"members-threshold","threshold":"20"}},{"resource":"init","condition":{"type":"members-threshold","threshold":"60"}}]` + std.TestSetOrigCaller(alice) + dao := NewDaoJSON(name, description, roles, members, resourcesJSON, messagesHandlers) + proposalJSON := `{"title":"My Proposal","description":"My Proposal Description","message":{"type":"MockExecutableMessage","payload":{}}}` + std.TestSetOrigCaller(alice) + dao.ProposeJSON(proposalJSON) + + type testExecuteInput struct { + proposalID uint64 + executor std.Address + haveVote bool + voter std.Address + } + + type testExecuteExpected struct { + panic bool + } + + type testExecute struct { + input testExecuteInput + expected testExecuteExpected + } + + type testExecuteTable map[string]testExecute + + tests := testExecuteTable{ + "Conditions not met": { + input: testExecuteInput{ + proposalID: 1, + executor: alice, + haveVote: false, + voter: alice, + }, + expected: testExecuteExpected{ + panic: true, + }, + }, + "Success": { + input: testExecuteInput{ + proposalID: 1, + executor: alice, + haveVote: true, + voter: alice, + }, + expected: testExecuteExpected{ + panic: false, + }, + }, + "Unknown proposal": { + input: testExecuteInput{ + proposalID: 2, + executor: alice, + haveVote: false, + voter: alice, + }, + expected: testExecuteExpected{ + panic: true, + }, + }, + "Non-member": { + input: testExecuteInput{ + proposalID: 1, + executor: dave, + haveVote: false, + voter: alice, + }, + expected: testExecuteExpected{ + panic: true, + }, + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + if test.expected.panic { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic, got none") + } + }() + } + + if test.input.haveVote { + std.TestSetOrigCaller(test.input.voter) + dao.Vote(test.input.proposalID, "yes") + } + + std.TestSetOrigCaller(test.input.executor) + dao.Execute(test.input.proposalID) + + proposal := dao.ProposalModule.getProposal(test.input.proposalID) + + if proposal.status != ProposalStatusExecuted { + t.Errorf("Expected status %s, got %s", ProposalStatusExecuted, proposal.status) + } + }) + } +} + +func TestInstantExecute(t *testing.T) { + name := "My DAO" + description := "My DAO Description" + roles := []string{"admin"} + members := [][]string{{alice.String(), "admin"}, {bob.String()}, {carol.String()}} + + messagesHandlers := []MessageHandler{ + &MockMessageHandler{}, + } + + resourcesJSON := `[{"resource":"MockExecutableMessage","condition":{"type":"members-threshold","threshold":"20"}},{"resource":"init","condition":{"type":"members-threshold","threshold":"60"}}]` + std.TestSetOrigCaller(alice) + dao := NewDaoJSON(name, description, roles, members, resourcesJSON, messagesHandlers) + + proposalJSON := `{"title":"My Proposal","description":"My Proposal Description","message":{"type":"MockExecutableMessage","payload":{}}}` + std.TestSetOrigCaller(alice) + dao.ProposeJSON(proposalJSON) + + type testInstantExecuteInput struct { + proposalJSON string + executor std.Address + } + + type testInstantExecuteExpected struct { + panic bool + } + + type testInstantExecute struct { + input testInstantExecuteInput + expected testInstantExecuteExpected + } + + type testInstantExecuteTable map[string]testInstantExecute + + tests := testInstantExecuteTable{ + "Success": { + input: testInstantExecuteInput{ + proposalJSON: proposalJSON, + executor: alice, + }, + expected: testInstantExecuteExpected{ + panic: false, + }, + }, + "Unknown message type": { + input: testInstantExecuteInput{ + proposalJSON: `{"title":"My Proposal","description":"My Proposal Description","message":{"type":"UnknownMessage","payload":{""}}}`, + executor: alice, + }, + expected: testInstantExecuteExpected{ + panic: true, + }, + }, + "Non-member": { + input: testInstantExecuteInput{ + proposalJSON: proposalJSON, + executor: dave, + }, + expected: testInstantExecuteExpected{ + panic: true, + }, + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + if test.expected.panic { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic, got none") + } + }() + } + + std.TestSetOrigCaller(test.input.executor) + dao.InstantExecute(test.input.proposalJSON) + }) + } +} + +func TestGetMembers(t *testing.T) { + name := "My DAO" + description := "My DAO Description" + roles := []string{"admin"} + members := [][]string{{alice.String(), "admin"}, {bob.String()}, {carol.String()}} + + messagesHandlers := []MessageHandler{ + &MockMessageHandler{}, + } + + resourcesJSON := `[{"resource":"MockExecutableMessage","condition":{"type":"members-threshold","threshold":"60"}},{"resource":"init","condition":{"type":"members-threshold","threshold":"60"}}]` + std.TestSetOrigCaller(alice) + dao := NewDaoJSON(name, description, roles, members, resourcesJSON, messagesHandlers) + + expectedMembers := []string{alice.String(), bob.String(), carol.String()} + m := dao.MemberModule.GetMembers() + if len(m) != len(expectedMembers) { + t.Errorf("Expected %d members, got %d", len(expectedMembers), len(m)) + } + + for _, eMember := range expectedMembers { + if !dao.MemberModule.IsMember(eMember) { + t.Errorf("Expected member %s to be a member", eMember) + } + } +} + +func TestAddMemberProposal(t *testing.T) { + name := "My DAO" + description := "My DAO Description" + roles := []string{"admin"} + members := [][]string{{alice.String(), "admin"}} + + messagesHandlers := []MessageHandler{ + &MockMessageHandler{}, + } + + resourcesJSON := `[{"resource":"MockExecutableMessage","condition":{"type":"members-threshold","threshold":"60"}},{"resource":"init","condition":{"type":"members-threshold","threshold":"60"}}]` + + std.TestSetOrigCaller(alice) + dao := NewDaoJSON(name, description, roles, members, resourcesJSON, messagesHandlers) + + proposalJSON := `{"title":"Add Member Proposal","description":"Add Member Proposal Description","message":{"type":"gno.land/p/zenao/dao_roles_based.AddNewMember","payload":{"address":"` + bob.String() + `","roles":["admin"]}}}` + + std.TestSetOrigCaller(alice) + + if dao.MemberModule.IsMember(bob.String()) { + t.Errorf("Expected member %s to not be a member", bob.String()) + } + + dao.InstantExecute(proposalJSON) + + if !dao.MemberModule.IsMember(bob.String()) { + t.Errorf("Expected member %s to be a member", bob.String()) + } + + if !dao.MemberModule.HasRole(bob.String(), "admin") { + t.Errorf("Expected member %s to have role 'admin'", bob.String()) + } + + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic, got none") + } + }() + + proposalWithUnknowkRole := `{"title":"Add Member Proposal","description":"Add Member Proposal Description","message":{"type":"gno.land/p/zenao/dao_roles_based.AddNewMember","payload":{"address":"` + carol.String() + `","roles":["unknown"]}}}` + std.TestSetOrigCaller(alice) + dao.InstantExecute(proposalWithUnknowkRole) +} + +func TestRemoveMemberProposal(t *testing.T) { + name := "My DAO" + description := "My DAO Description" + roles := []string{"admin"} + members := [][]string{{alice.String(), "admin"}, {bob.String(), "admin"}} + + messagesHandlers := []MessageHandler{ + &MockMessageHandler{}, + } + + resourcesJSON := `[{"resource":"MockExecutableMessage","condition":{"type":"members-threshold","threshold":"60"}},{"resource":"init","condition":{"type":"members-threshold","threshold":"20"}}]` + + std.TestSetOrigCaller(alice) + dao := NewDaoJSON(name, description, roles, members, resourcesJSON, messagesHandlers) + + proposalJSON := `{"title":"Remove Member Proposal","description":"Remove Member Proposal Description","message":{"type":"gno.land/p/zenao/dao_roles_based.RemoveMember","payload": "` + bob.String() + `"}}` + + std.TestSetOrigCaller(alice) + + if !dao.MemberModule.IsMember(bob.String()) { + t.Errorf("Expected member %s to be a member", bob.String()) + } + + if !dao.MemberModule.HasRole(bob.String(), "admin") { + t.Errorf("Expected member %s to have role 'admin'", bob.String()) + } + + dao.InstantExecute(proposalJSON) + + if dao.MemberModule.IsMember(bob.String()) { + t.Errorf("Expected user %s to not be a member", bob.String()) + } + + if dao.MemberModule.HasRole(bob.String(), "admin") { + t.Errorf("Expected user %s to not have role 'admin'", bob.String()) + } +} + +func TestAddRoleToUserProposal(t *testing.T) { + name := "My DAO" + description := "My DAO Description" + roles := []string{"admin"} + members := [][]string{{alice.String(), "admin"}, {bob.String()}} + + messagesHandlers := []MessageHandler{ + &MockMessageHandler{}, + } + + resourcesJSON := `[{"resource":"MockExecutableMessage","condition":{"type":"members-threshold","threshold":"60"}},{"resource":"init","condition":{"type":"members-threshold","threshold":"20"}}]` + + std.TestSetOrigCaller(alice) + dao := NewDaoJSON(name, description, roles, members, resourcesJSON, messagesHandlers) + + proposalJSON := `{"title":"Add Role To User Proposal","description":"Add Role To User Proposal Description","message":{"type":"gno.land/p/zenao/dao_roles_based.AddRoleToUser","payload":{"address":"` + bob.String() + `","role":"admin"}}}` + + std.TestSetOrigCaller(alice) + + if dao.MemberModule.HasRole(bob.String(), "admin") { + t.Errorf("Expected member %s to not have role 'admin'", bob.String()) + } + + dao.InstantExecute(proposalJSON) + + if !dao.MemberModule.HasRole(bob.String(), "admin") { + t.Errorf("Expected member %s to have role 'admin'", bob.String()) + } + + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic, got none") + } + }() + + proposalWithUnknowkRole := `{"title":"Add Role To User Proposal","description":"Add Role To User Proposal Description","message":{"type":"gno.land/p/zenao/dao_roles_based.AddRoleToUser","payload":{"address":"` + alice.String() + `","role":"unknown"}}}` + std.TestSetOrigCaller(alice) + dao.InstantExecute(proposalWithUnknowkRole) + + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic, got none") + } + }() + + proposalWithNonMember := `{"title":"Add Role To User Proposal","description":"Add Role To User Proposal Description","message":{"type":"gno.land/p/zenao/dao_roles_based.AddRoleToUser","payload":{"address":"` + carol.String() + `","role":"admin"}}}` + std.TestSetOrigCaller(alice) + dao.InstantExecute(proposalWithNonMember) + + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic, got none") + } + }() + + proposalWithAlreadyRole := `{"title":"Add Role To User Proposal","description":"Add Role To User Proposal Description","message":{"type":"gno.land/p/zenao/dao_roles_based.AddRoleToUser","payload":{"address":"` + bob.String() + `","role":"admin"}}}` + std.TestSetOrigCaller(alice) + dao.InstantExecute(proposalWithAlreadyRole) +} + +func TestRemoveRoleFromUserProposal(t *testing.T) { + name := "My DAO" + description := "My DAO Description" + roles := []string{"admin"} + members := [][]string{{alice.String(), "admin"}, {bob.String(), "admin"}} + + messagesHandlers := []MessageHandler{ + &MockMessageHandler{}, + } + + resourcesJSON := `[{"resource":"MockExecutableMessage","condition":{"type":"members-threshold","threshold":"60"}},{"resource":"init","condition":{"type":"members-threshold","threshold":"20"}}]` + + std.TestSetOrigCaller(alice) + dao := NewDaoJSON(name, description, roles, members, resourcesJSON, messagesHandlers) + + proposalJSON := `{"title":"Remove Role From User Proposal","description":"Remove Role From User Proposal Description","message":{"type":"gno.land/p/zenao/dao_roles_based.RemoveRoleFromUser","payload":{"address":"` + bob.String() + `","role":"admin"}}}` + + std.TestSetOrigCaller(alice) + + if !dao.MemberModule.HasRole(bob.String(), "admin") { + t.Errorf("Expected member %s to have role 'admin'", bob.String()) + } + + dao.InstantExecute(proposalJSON) + + if dao.MemberModule.HasRole(bob.String(), "admin") { + t.Errorf("Expected member %s to not have role 'admin'", bob.String()) + } + + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic, got none") + } + }() + + proposalWithUnknowkRole := `{"title":"Remove Role From User Proposal","description":"Remove Role From User Proposal Description","message":{"type":"gno.land/p/zenao/dao_roles_based.RemoveRoleFromUser","payload":{"address":"` + alice.String() + `","role":"unknown"}}}` + std.TestSetOrigCaller(alice) + dao.InstantExecute(proposalWithUnknowkRole) + + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic, got none") + } + }() + + proposalWithNonMember := `{"title":"Remove Role From User Proposal","description":"Remove Role From User Proposal Description","message":{"type":"gno.land/p/zenao/dao_roles_based.RemoveRoleFromUser","payload":{"address":"` + carol.String() + `","role":"admin"}}}` + std.TestSetOrigCaller(alice) + dao.InstantExecute(proposalWithNonMember) + + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic, got none") + } + }() + + proposalWithNonRole := `{"title":"Remove Role From User Proposal","description":"Remove Role From User Proposal Description","message":{"type":"gno.land/p/zenao/dao_roles_based.RemoveRoleFromUser","payload":{"address":"` + bob.String() + `","role":"admin"}}}` + std.TestSetOrigCaller(alice) + dao.InstantExecute(proposalWithNonRole) +} diff --git a/gno/p/daokit/gno.mod b/gno/p/daokit/gno.mod new file mode 100644 index 000000000..d65b5c117 --- /dev/null +++ b/gno/p/daokit/gno.mod @@ -0,0 +1 @@ +module gno.land/p/teritori/daokit diff --git a/gno/p/daokit/members.gno b/gno/p/daokit/members.gno new file mode 100644 index 000000000..1ccde486b --- /dev/null +++ b/gno/p/daokit/members.gno @@ -0,0 +1,121 @@ +package daokit + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/teritori/role_manager" +) + +type MemberModule struct { + roleManager *role_manager.RoleManager + members *avl.Tree // address -> struct{} +} + +// TODO: FIX the owner of the role_manager is the deployer of the contract not the realm +func newMemberModule() *MemberModule { + return &MemberModule{ + roleManager: role_manager.NewWithAddress(std.CurrentRealm().Addr()), + members: avl.NewTree(), + } +} + +func (m *MemberModule) HasRole(memberId string, role string) bool { + return m.roleManager.HasRole(std.Address(memberId), role) +} + +func (m *MemberModule) IsMember(memberId string) bool { + return m.members.Has(memberId) +} + +func (m *MemberModule) MembersCount() uint64 { + return uint64(m.members.Size()) +} + +func (m *MemberModule) GetMembers() []string { + members := make([]string, 0, m.members.Size()) + m.members.Iterate("", "", func(key string, value interface{}) bool { + members = append(members, key) + return false + }) + return members +} + +func (m *MemberModule) GetRoles() []string { + return m.roleManager.GetRoles() +} + +func (m *MemberModule) GetMemberRoles(memberId string) []string { + return m.roleManager.GetUserRoles(std.Address(memberId)) +} + +func (m *MemberModule) CountMemberRoles(memberId string) int { + return m.roleManager.CountUserRoles(std.Address(memberId)) +} + +func (m *MemberModule) GetMembersWithRole(role string) []string { + return m.roleManager.GetRoleUsers(role) +} + +func (m *MemberModule) setRoles(roles []string) { + caller := std.CurrentRealm().Addr() + for _, role := range roles { + m.roleManager.CreateNewRole(role, []string{}, caller) + } +} + +func (m *MemberModule) setMembers(members [][]string) { + caller := std.CurrentRealm().Addr() + for _, member := range members { + m.members.Set(member[0], struct{}{}) + for _, role := range member[1:] { + m.roleManager.AddRoleToUser(std.Address(member[0]), role, caller) + } + } +} + +// TODO: add test for this kind of proposals +func (m *MemberModule) addMember(member string, roles []string) { + if m.IsMember(member) { + panic("member already exists") + } + caller := std.CurrentRealm().Addr() + m.members.Set(member, struct{}{}) + for _, role := range roles { + m.roleManager.AddRoleToUser(std.Address(member), role, caller) + } +} + +func (m *MemberModule) removeMember(member string) { + if !m.IsMember(member) { + panic("member does not exist") + } + m.members.Remove(member) + m.roleManager.RemoveAllRolesFromUser(std.Address(member), std.CurrentRealm().Addr()) +} + +func (m *MemberModule) addRoleToMember(member string, role string) { + if !m.IsMember(member) { + panic("member does not exist") + } + if !m.roleManager.RoleExists(role) { + panic("role does not exist") + } + if m.HasRole(member, role) { + panic("member already has the role") + } + m.roleManager.AddRoleToUser(std.Address(member), role, std.CurrentRealm().Addr()) +} + +func (m *MemberModule) removeRoleFromMember(member string, role string) { + if !m.IsMember(member) { + panic("member does not exist") + } + if !m.roleManager.RoleExists(role) { + panic("role does not exist") + } + if !m.HasRole(member, role) { + panic("member does not have the role") + } + m.roleManager.RemoveRoleFromUser(std.Address(member), role, std.CurrentRealm().Addr()) +} diff --git a/gno/p/daokit/messages.gno b/gno/p/daokit/messages.gno new file mode 100644 index 000000000..5b5c1ab9a --- /dev/null +++ b/gno/p/daokit/messages.gno @@ -0,0 +1,353 @@ +package daokit + +import ( + "gno.land/p/demo/avl" + "gno.land/p/demo/json" + "gno.land/p/demo/ufmt" +) + +type ExecutableMessage interface { + ToJSON() *json.Node + FromJSON(ast *json.Node) + + String() string + Type() string +} + +type MessageHandler interface { + Execute(message ExecutableMessage) + Instantiate() ExecutableMessage + Type() string +} + +type MessagesRegistry struct { + handlers *avl.Tree +} + +func newMessagesRegistry() *MessagesRegistry { + return &MessagesRegistry{handlers: avl.NewTree()} +} + +func (dao *Dao) initMessagesRegistry() { + dao.MessagesRegistry.register(NewRegisterHandlerExecutableMessageHandler(dao.MessagesRegistry)) + dao.MessagesRegistry.register(NewRemoveHandlerExecutableMessageHandler(dao.MessagesRegistry)) + dao.MessagesRegistry.register(NewAddNewMemberMessageHandler(dao)) + dao.MessagesRegistry.register(NewRemoveMemberMessageHandler(dao)) + dao.MessagesRegistry.register(NewAddRoleToUserMessageHandler(dao)) + dao.MessagesRegistry.register(NewRemoveRoleFromUserMessageHandler(dao)) +} + +func (r *MessagesRegistry) register(handler MessageHandler) { + r.handlers.Set(handler.Type(), handler) +} + +func (r *MessagesRegistry) remove(t string) { + r.handlers.Remove(t) +} + +func (r *MessagesRegistry) messageFromJSON(message *json.Node) ExecutableMessage { + messageType := json.Must(message.GetKey("type")).MustString() + payload := json.Must(message.GetKey("payload")) + h, ok := r.handlers.Get(messageType) + if !ok { + panic("invalid ExecutableMessage: invalid message type") + } + + instance := h.(MessageHandler).Instantiate() + instance.FromJSON(payload) + return instance +} + +func (r *MessagesRegistry) execute(msg ExecutableMessage) { + h, ok := r.handlers.Get(msg.Type()) + if !ok { + panic("invalid ExecutableMessage: invalid message type") + } + h.(MessageHandler).Execute(msg) +} + +type RegisterHandlerExecutableMessage struct { + Handler MessageHandler +} + +var _ ExecutableMessage = &RegisterHandlerExecutableMessage{} + +func (m RegisterHandlerExecutableMessage) Type() string { + return "gno.land/p/zenao/dao_roles_based.RegisterHandler" +} + +func (m *RegisterHandlerExecutableMessage) FromJSON(ast *json.Node) { + panic("not implemented") +} + +func (m *RegisterHandlerExecutableMessage) ToJSON() *json.Node { + panic("not implemented") +} + +func (m *RegisterHandlerExecutableMessage) String() string { + return m.Handler.Type() +} + +type RegisterHandlerExecutableMessageHandler struct { + registry *MessagesRegistry +} + +var _ MessageHandler = &RegisterHandlerExecutableMessageHandler{} + +func NewRegisterHandlerExecutableMessageHandler(registry *MessagesRegistry) *RegisterHandlerExecutableMessageHandler { + return &RegisterHandlerExecutableMessageHandler{registry: registry} +} + +func (h RegisterHandlerExecutableMessageHandler) Type() string { + return RegisterHandlerExecutableMessage{}.Type() +} + +func (h *RegisterHandlerExecutableMessageHandler) Instantiate() ExecutableMessage { + return &RegisterHandlerExecutableMessage{} +} + +func (h *RegisterHandlerExecutableMessageHandler) Execute(msg ExecutableMessage) { + h.registry.register(msg.(*RegisterHandlerExecutableMessage).Handler) +} + +type RemoveHandlerExecutableMessage struct { + HandlerType string +} + +var _ ExecutableMessage = &RemoveHandlerExecutableMessage{} + +func (m RemoveHandlerExecutableMessage) Type() string { + return "gno.land/p/zenao/dao_roles_based.RemoveHandler" +} + +func (m *RemoveHandlerExecutableMessage) FromJSON(ast *json.Node) { + m.HandlerType = ast.MustString() +} + +func (m *RemoveHandlerExecutableMessage) ToJSON() *json.Node { + return json.StringNode("", m.HandlerType) +} + +func (m *RemoveHandlerExecutableMessage) String() string { + return m.HandlerType +} + +type RemoveHandlerExecutableMessageHandler struct { + registry *MessagesRegistry +} + +var _ MessageHandler = &RemoveHandlerExecutableMessageHandler{} + +func NewRemoveHandlerExecutableMessageHandler(registry *MessagesRegistry) *RemoveHandlerExecutableMessageHandler { + return &RemoveHandlerExecutableMessageHandler{registry: registry} +} + +func (h RemoveHandlerExecutableMessageHandler) Type() string { + return RemoveHandlerExecutableMessage{}.Type() +} + +func (h *RemoveHandlerExecutableMessageHandler) Instantiate() ExecutableMessage { + return &RemoveHandlerExecutableMessage{} +} + +func (h *RemoveHandlerExecutableMessageHandler) Execute(msg ExecutableMessage) { + h.registry.remove(msg.(*RemoveHandlerExecutableMessage).HandlerType) +} + +type AddNewMemberMessage struct { + Address string + Roles []string +} + +var _ ExecutableMessage = &AddNewMemberMessage{} + +func (m AddNewMemberMessage) Type() string { + return "gno.land/p/zenao/dao_roles_based.AddNewMember" +} + +func (m *AddNewMemberMessage) FromJSON(ast *json.Node) { + obj := ast.MustObject() + m.Address = obj["address"].MustString() + rolesArray := obj["roles"].MustArray() + for _, role := range rolesArray { + m.Roles = append(m.Roles, role.MustString()) + } +} + +func (m *AddNewMemberMessage) ToJSON() *json.Node { + roles := []*json.Node{} + for _, role := range m.Roles { + roles = append(roles, json.StringNode("", role)) + } + rolesNode := json.ArrayNode("", roles) + return json.ObjectNode("", map[string]*json.Node{ + "address": json.StringNode("", m.Address), + "roles": rolesNode, + }) +} + +func (m *AddNewMemberMessage) String() string { + return ufmt.Sprintf("Add new member: %s with roles: %v", m.Address, m.Roles) +} + +type AddNewMemberMessageHandler struct { + dao *Dao +} + +func NewAddNewMemberMessageHandler(dao *Dao) *AddNewMemberMessageHandler { + return &AddNewMemberMessageHandler{dao: dao} +} + +func (h AddNewMemberMessageHandler) Execute(msg ExecutableMessage) { + message := msg.(*AddNewMemberMessage) + h.dao.MemberModule.addMember(message.Address, message.Roles) +} + +func (h AddNewMemberMessageHandler) Type() string { + return AddNewMemberMessage{}.Type() +} + +func (h *AddNewMemberMessageHandler) Instantiate() ExecutableMessage { + return &AddNewMemberMessage{} +} + +type RemoveMemberMessage struct { + Address string +} + +var _ ExecutableMessage = &RemoveMemberMessage{} + +func (m RemoveMemberMessage) Type() string { + return "gno.land/p/zenao/dao_roles_based.RemoveMember" +} + +func (m *RemoveMemberMessage) FromJSON(ast *json.Node) { + m.Address = ast.MustString() +} + +func (m *RemoveMemberMessage) ToJSON() *json.Node { + return json.StringNode("", m.Address) +} + +func (m *RemoveMemberMessage) String() string { + return ufmt.Sprintf("Remove member: %s", m.Address) +} + +type RemoveMemberMessageHandler struct { + dao *Dao +} + +func NewRemoveMemberMessageHandler(dao *Dao) *RemoveMemberMessageHandler { + return &RemoveMemberMessageHandler{dao: dao} +} + +func (h RemoveMemberMessageHandler) Execute(msg ExecutableMessage) { + message := msg.(*RemoveMemberMessage) + h.dao.MemberModule.removeMember(message.Address) +} + +func (h RemoveMemberMessageHandler) Type() string { + return RemoveMemberMessage{}.Type() +} + +func (h *RemoveMemberMessageHandler) Instantiate() ExecutableMessage { + return &RemoveMemberMessage{} +} + +type AddRoleToUserMessage struct { + Address string + Role string +} + +var _ ExecutableMessage = &AddRoleToUserMessage{} + +func (m AddRoleToUserMessage) Type() string { + return "gno.land/p/zenao/dao_roles_based.AddRoleToUser" +} + +func (m *AddRoleToUserMessage) FromJSON(ast *json.Node) { + obj := ast.MustObject() + m.Address = obj["address"].MustString() + m.Role = obj["role"].MustString() +} + +func (m *AddRoleToUserMessage) ToJSON() *json.Node { + return json.ObjectNode("", map[string]*json.Node{ + "address": json.StringNode("", m.Address), + "role": json.StringNode("", m.Role), + }) +} + +func (m *AddRoleToUserMessage) String() string { + return ufmt.Sprintf("Add role: %s to user: %s", m.Role, m.Address) +} + +type AddRoleToUserMessageHandler struct { + dao *Dao +} + +func NewAddRoleToUserMessageHandler(dao *Dao) *AddRoleToUserMessageHandler { + return &AddRoleToUserMessageHandler{dao: dao} +} + +func (h AddRoleToUserMessageHandler) Execute(msg ExecutableMessage) { + message := msg.(*AddRoleToUserMessage) + h.dao.MemberModule.addRoleToMember(message.Address, message.Role) +} + +func (h AddRoleToUserMessageHandler) Type() string { + return AddRoleToUserMessage{}.Type() +} + +func (h *AddRoleToUserMessageHandler) Instantiate() ExecutableMessage { + return &AddRoleToUserMessage{} +} + +type RemoveRoleFromUserMessage struct { + Address string + Role string +} + +var _ ExecutableMessage = &RemoveRoleFromUserMessage{} + +func (m RemoveRoleFromUserMessage) Type() string { + return "gno.land/p/zenao/dao_roles_based.RemoveRoleFromUser" +} + +func (m *RemoveRoleFromUserMessage) FromJSON(ast *json.Node) { + obj := ast.MustObject() + m.Address = obj["address"].MustString() + m.Role = obj["role"].MustString() +} + +func (m *RemoveRoleFromUserMessage) ToJSON() *json.Node { + return json.ObjectNode("", map[string]*json.Node{ + "address": json.StringNode("", m.Address), + "role": json.StringNode("", m.Role), + }) +} + +func (m *RemoveRoleFromUserMessage) String() string { + return ufmt.Sprintf("Remove role: %s from user: %s", m.Role, m.Address) +} + +type RemoveRoleFromUserMessageHandler struct { + dao *Dao +} + +func NewRemoveRoleFromUserMessageHandler(dao *Dao) *RemoveRoleFromUserMessageHandler { + return &RemoveRoleFromUserMessageHandler{dao: dao} +} + +func (h RemoveRoleFromUserMessageHandler) Execute(msg ExecutableMessage) { + message := msg.(*RemoveRoleFromUserMessage) + h.dao.MemberModule.removeRoleFromMember(message.Address, message.Role) +} + +func (h RemoveRoleFromUserMessageHandler) Type() string { + return RemoveRoleFromUserMessage{}.Type() +} + +func (h *RemoveRoleFromUserMessageHandler) Instantiate() ExecutableMessage { + return &RemoveRoleFromUserMessage{} +} diff --git a/gno/p/dao_roles_based/proposals.gno b/gno/p/daokit/proposals.gno similarity index 97% rename from gno/p/dao_roles_based/proposals.gno rename to gno/p/daokit/proposals.gno index 345c78d39..53035a920 100644 --- a/gno/p/dao_roles_based/proposals.gno +++ b/gno/p/daokit/proposals.gno @@ -1,4 +1,4 @@ -package dao_roles_based +package daokit import ( "std" @@ -61,7 +61,7 @@ func newProposalModule() *ProposalModule { } } -func (p *ProposalModule) newProposal(title, description string, proposer std.Address, message ExecutableMessage, state daocond.State) { +func (p *ProposalModule) newProposal(title, description string, proposer std.Address, message ExecutableMessage, state daocond.State) *Proposal { id := p.proposalIDCounter.Next() proposal := &Proposal{ id: id, @@ -74,6 +74,7 @@ func (p *ProposalModule) newProposal(title, description string, proposer std.Add votes: map[string]daocond.Vote{}, } p.proposals.Set(id.String(), proposal) + return proposal } func (p *ProposalModule) getProposal(id uint64) *Proposal { diff --git a/gno/p/dao_roles_based/render.gno b/gno/p/daokit/render.gno similarity index 89% rename from gno/p/dao_roles_based/render.gno rename to gno/p/daokit/render.gno index b13d089de..2abbfd847 100644 --- a/gno/p/dao_roles_based/render.gno +++ b/gno/p/daokit/render.gno @@ -1,4 +1,4 @@ -package dao_roles_based +package daokit import ( "std" @@ -17,7 +17,7 @@ const ( PROPOSAL_DETAIL_PATH = "proposal/{id}" ) -func (d *DaoRolesBased) initRenderingRouter() { +func (d *Dao) initRenderingRouter() { d.renderingRouter.HandleFunc(HOME_PATH, d.renderHomePage) d.renderingRouter.HandleFunc(CONFIG_PATH, d.renderConfigPage) d.renderingRouter.HandleFunc(PROPOSAL_HISTORY_PATH, d.renderProposalHistoryPage) @@ -25,11 +25,11 @@ func (d *DaoRolesBased) initRenderingRouter() { d.renderingRouter.HandleFunc(PROPOSAL_DETAIL_PATH, d.renderProposalDetailPage) } -func (d *DaoRolesBased) Render(path string) string { +func (d *Dao) Render(path string) string { return d.renderingRouter.Render(path) } -func (d *DaoRolesBased) renderHomePage(res *mux.ResponseWriter, req *mux.Request) { +func (d *Dao) renderHomePage(res *mux.ResponseWriter, req *mux.Request) { res.Write(ufmt.Sprintf("# %s - %s\n\n", d.Name, std.CurrentRealm().Addr().String())) res.Write(ufmt.Sprintf("> Description: %s\n\n", d.Description)) res.Write(ufmt.Sprintf("Discover more about this DAO on the [configuration page ⚙️](%s:%s)\n\n", d.realmName, CONFIG_PATH)) @@ -69,7 +69,7 @@ func (d *DaoRolesBased) renderHomePage(res *mux.ResponseWriter, req *mux.Request res.Write(ufmt.Sprintf("[Add a new proposal 🗳️](%s$help)\n\n", d.realmName)) } -func (d *DaoRolesBased) renderConfigPage(res *mux.ResponseWriter, req *mux.Request) { +func (d *Dao) renderConfigPage(res *mux.ResponseWriter, req *mux.Request) { res.Write(ufmt.Sprintf("# %s - Config ⚙️\n\n", d.Name)) roles := d.MemberModule.GetRoles() res.Write(ufmt.Sprintf("## Roles 🏷️\n\n")) @@ -88,7 +88,7 @@ func (d *DaoRolesBased) renderConfigPage(res *mux.ResponseWriter, req *mux.Reque }) } -func (d *DaoRolesBased) renderProposalHistoryPage(res *mux.ResponseWriter, req *mux.Request) { +func (d *Dao) renderProposalHistoryPage(res *mux.ResponseWriter, req *mux.Request) { res.Write(ufmt.Sprintf("# %s - Proposal History\n\n", d.Name)) res.Write(ufmt.Sprintf("## Proposals 🗳️\n\n")) i := 1 @@ -106,16 +106,16 @@ func (d *DaoRolesBased) renderProposalHistoryPage(res *mux.ResponseWriter, req * res.Write(ufmt.Sprintf("[Add a new proposal 🗳️](%s$help)\n\n", d.realmName)) } -func (d *DaoRolesBased) renderMemberDetailPage(res *mux.ResponseWriter, req *mux.Request) { +func (d *Dao) renderMemberDetailPage(res *mux.ResponseWriter, req *mux.Request) { res.Write(ufmt.Sprintf("# %s - Member Detail - %s\n\n", d.Name, req.GetVar("address"))) - roles := d.MemberModule.GetUserRoles(req.GetVar("address")) + roles := d.MemberModule.GetMemberRoles(req.GetVar("address")) res.Write(ufmt.Sprintf("## Roles 🏷️\n\n")) for _, role := range roles { res.Write(ufmt.Sprintf("- %s\n\n", role)) } } -func (d *DaoRolesBased) renderProposalDetailPage(res *mux.ResponseWriter, req *mux.Request) { +func (d *Dao) renderProposalDetailPage(res *mux.ResponseWriter, req *mux.Request) { id, err := seqid.FromString(req.GetVar("id")) if err != nil { panic(err) diff --git a/gno/p/dao_roles_based/resources.gno b/gno/p/daokit/resources.gno similarity index 95% rename from gno/p/dao_roles_based/resources.gno rename to gno/p/daokit/resources.gno index dd414d330..9b1516c4c 100644 --- a/gno/p/dao_roles_based/resources.gno +++ b/gno/p/daokit/resources.gno @@ -1,4 +1,4 @@ -package dao_roles_based +package daokit import ( "gno.land/p/demo/avl" diff --git a/gno/p/role_manager/role_manager.gno b/gno/p/role_manager/role_manager.gno index 02235a302..d617bf096 100644 --- a/gno/p/role_manager/role_manager.gno +++ b/gno/p/role_manager/role_manager.gno @@ -165,6 +165,14 @@ func (rm *RoleManager) CountRoles() int { return rm.roles.Size() } +func (rm *RoleManager) CountUserRoles(user std.Address) int { + userRoles := rm.getUser(user) + if userRoles == nil { + return 0 + } + return userRoles.Size() +} + func (rm *RoleManager) GetRoles() []string { i := 0 res := make([]string, rm.roles.Size()) @@ -176,6 +184,19 @@ func (rm *RoleManager) GetRoles() []string { return res } +func (rm *RoleManager) GetRoleUsers(roleName string) []string { + role := rm.mustGetRole(roleName) + i := 0 + users := role.users + res := make([]string, users.Size()) + users.Iterate("", "", func(key string, value interface{}) bool { + res[i] = key + i++ + return false + }) + return res +} + func (rm *RoleManager) GetUserRoles(user std.Address) []string { userRoles, ok := rm.users.Get(user.String()) if !ok { @@ -192,6 +213,23 @@ func (rm *RoleManager) GetUserRoles(user std.Address) []string { return res } +func (rm *RoleManager) RemoveAllRolesFromUser(user std.Address, caller std.Address) { + if rm.owner.Owner() != caller && !rm.HasPermission(caller, PermissionWriteRole) { + panic("caller does not have permission") + } + + userRoles := rm.getUser(user) + if userRoles == nil { + return + } + userRoles.Iterate("", "", func(key string, value interface{}) bool { + role := value.(*Role) + role.users.Remove(user.String()) + return false + }) + rm.users.Remove(user.String()) +} + func (rm *RoleManager) mustGetRole(roleName string) *Role { role, ok := rm.roles.Get(roleName) if !ok { diff --git a/gno/p/role_manager/role_manager_test.gno b/gno/p/role_manager/role_manager_test.gno index 5e9608de1..1a822a5cd 100644 --- a/gno/p/role_manager/role_manager_test.gno +++ b/gno/p/role_manager/role_manager_test.gno @@ -247,3 +247,37 @@ func TestHasRole(t *testing.T) { t.Fatalf("expected user to not have role4") } } + +func TestRemoveAllRolesFromUser(t *testing.T) { + std.TestSetOrigCaller(alice) + rm := NewWithAddress(alice) + rm.CreateNewRole("role1", []string{"perm1", "perm2"}, alice) + rm.CreateNewRole("role2", []string{"perm3", "perm4"}, alice) + rm.CreateNewRole("role3", []string{"perm5", "perm6"}, alice) + + rm.AddRoleToUser(alice, "role1", alice) + rm.AddRoleToUser(alice, "role2", alice) + rm.AddRoleToUser(alice, "role3", alice) + + rm.RemoveAllRolesFromUser(alice, alice) + + aliceRoles := rm.getUser(alice) + if aliceRoles != nil { + t.Fatalf("expected user to have no roles") + } + + rm.AddRoleToUser(alice, "role1", alice) + + aliceRoles = rm.getUser(alice) + if !aliceRoles.Has("role1") { + t.Fatalf("expected user to have role1") + } + + if aliceRoles.Has("role2") { + t.Fatalf("expected user to not have role2") + } + + if aliceRoles.Has("role3") { + t.Fatalf("expected user to not have role3") + } +} diff --git a/gno/r/dao_realm/dao_realm.gno b/gno/r/dao_realm/dao_realm.gno index 9934b3272..7eb0ff9fa 100644 --- a/gno/r/dao_realm/dao_realm.gno +++ b/gno/r/dao_realm/dao_realm.gno @@ -1,12 +1,11 @@ package dao_realm import ( - "gno.land/p/teritori/dao_roles_based" + "gno.land/p/teritori/daokit" "gno.land/r/demo/profile" - "gno.land/r/teritori/social_feeds" ) -var dao *dao_roles_based.DaoRolesBased +var dao *daokit.Dao func init() { name := "Demo DAO" @@ -20,23 +19,10 @@ func init() { {"g16jv3rpz7mkt0gqulxas56se2js7v5vmc6n6e0r"}, } - messagesHandlers := []dao_roles_based.MessageHandler{ - social_feeds.NewCreatePostHandler(), - } - - conditions := make(map[string]string) - conditions[messagesHandlers[0].Type()] = `"condition":{"type":"AND","conditions":[{"type":"members-threshold","threshold":"60"},{"type":"role-count","role":"public-relationships","count":"1"}]}` - - var resourcesJSON = `[` - for i, mh := range messagesHandlers { - resourcesJSON += `{"resource":"` + mh.Type() + `",` + conditions[mh.Type()] + `}` - if i < len(messagesHandlers)-1 { - resourcesJSON += `,` - } - } - resourcesJSON += `]` + messagesHandlers := []daokit.MessageHandler{} + resourcesJSON := `[{"resource":"init","condition":{"type":"members-threshold","threshold":"60"}}]` - dao = dao_roles_based.NewDaoRolesBasedJSON(name, description, roles, members, resourcesJSON, messagesHandlers) + dao = daokit.NewDaoJSON(name, description, roles, members, resourcesJSON, messagesHandlers) profile.SetStringField(profile.DisplayName, name) profile.SetStringField(profile.Bio, description) diff --git a/gno/r/dao_realm/dao_realm_test.gno b/gno/r/dao_realm/dao_realm_test.gno index 39cbc29c5..69a3bac82 100644 --- a/gno/r/dao_realm/dao_realm_test.gno +++ b/gno/r/dao_realm/dao_realm_test.gno @@ -3,7 +3,7 @@ package dao_realm import "testing" func TestInit(t *testing.T) { - membersCount := dao.MembersCount() + membersCount := dao.MemberModule.MembersCount() if membersCount != 4 { t.Fatalf("Expected 4 members, got %d", membersCount) } @@ -15,7 +15,7 @@ func TestInit(t *testing.T) { } for i, role := range roles { err := true - for j, expectedRole := range expectedRoles { + for _, expectedRole := range expectedRoles { if role == expectedRole { err = false break diff --git a/gno/r/social_feeds/messages.gno b/gno/r/social_feeds/messages.gno index c4c30ed19..3807f92d3 100644 --- a/gno/r/social_feeds/messages.gno +++ b/gno/r/social_feeds/messages.gno @@ -6,7 +6,7 @@ import ( "gno.land/p/demo/json" "gno.land/p/teritori/dao_interfaces" - "gno.land/p/teritori/dao_roles_based" + "gno.land/p/teritori/daokit" "gno.land/p/teritori/jsonutil" ) @@ -80,7 +80,7 @@ func (h *BanPostHandler) Execute(iMsg dao_interfaces.ExecutableMessage) { func (h BanPostHandler) Type() string { return ExecutableMessageBanPost{}.Type() - +} func (h BanPostHandler) Instantiate() dao_interfaces.ExecutableMessage { return &ExecutableMessageBanPost{} @@ -95,7 +95,7 @@ type ExecutableMessageCreatePost struct { Metadata string } -var _ dao_roles_based.ExecutableMessage = (*ExecutableMessageCreatePost)(nil) +var _ daokit.ExecutableMessage = (*ExecutableMessageCreatePost)(nil) func (msg ExecutableMessageCreatePost) Type() string { return "gno.land/r/teritori/social_feeds.CreatePost" @@ -141,13 +141,13 @@ func (msg *ExecutableMessageCreatePost) String() string { type CreatePostHandler struct{} -var _ dao_roles_based.MessageHandler = (*CreatePostHandler)(nil) +var _ daokit.MessageHandler = (*CreatePostHandler)(nil) func NewCreatePostHandler() *CreatePostHandler { return &CreatePostHandler{} } -func (h *CreatePostHandler) Execute(iMsg dao_roles_based.ExecutableMessage) { +func (h *CreatePostHandler) Execute(iMsg daokit.ExecutableMessage) { msg := iMsg.(*ExecutableMessageCreatePost) CreatePost(msg.FeedID, msg.ParentID, msg.Category, msg.Metadata) } @@ -156,6 +156,6 @@ func (h CreatePostHandler) Type() string { return ExecutableMessageCreatePost{}.Type() } -func (h CreatePostHandler) Instantiate() dao_roles_based.ExecutableMessage { +func (h CreatePostHandler) Instantiate() daokit.ExecutableMessage { return &ExecutableMessageCreatePost{} }