From a798fe181a4bf026d2dd3fb15111dcb99bea3e4c Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Thu, 9 Jan 2025 18:07:19 +0100 Subject: [PATCH] feat(gno/dao): handle vote --- gno/p/dao_roles_based/dao_roles_based.gno | 39 ++++++++++++++++++- gno/p/dao_roles_based/proposals.gno | 47 +++++++++++++---------- gno/p/dao_roles_based/render.gno | 10 ++--- gno/p/dao_roles_based/resources.gno | 12 +++++- gno/p/dao_roles_based/voting.gno | 15 -------- gno/r/dao_realm2/dao_realm2.gno | 6 ++- 6 files changed, 84 insertions(+), 45 deletions(-) delete mode 100644 gno/p/dao_roles_based/voting.gno diff --git a/gno/p/dao_roles_based/dao_roles_based.gno b/gno/p/dao_roles_based/dao_roles_based.gno index 75c67dbe7..fb0671e8e 100644 --- a/gno/p/dao_roles_based/dao_roles_based.gno +++ b/gno/p/dao_roles_based/dao_roles_based.gno @@ -34,13 +34,48 @@ func NewDaoRolesBasedJSON(rolesJSON, membersJSON, resourcesJSON string) *DaoRole return dao } -func (d *DaoRolesBased) NewProposal(title, description string) { +func (d *DaoRolesBased) Vote(proposalID uint64, vote string) { + // 3th caller is the voter, 2nd is the dao realm, 1st is the dao_roles_based module + voter := std.GetCallerAt(3) + if !d.MemberModule.IsMember(voter.String()) { + panic("voter is not a member") + } + + proposal := d.ProposalModule.getProposal(proposalID) + if proposal == nil { + panic("proposal not found") + } + + if proposal.status != ProposalStatusOpen { + panic("proposal is not open") + } + + if daocond.Vote(vote) != daocond.VoteYes && daocond.Vote(vote) != daocond.VoteNo && daocond.Vote(vote) != daocond.VoteAbstain { + panic("invalid vote") + } + + e := &daocond.EventVote{ + VoterID: voter.String(), + Vote: daocond.Vote(vote), + } + + proposal.state.HandleEvent(e, proposal.votes) + proposal.votes[voter.String()] = daocond.Vote(vote) + +} + +func (d *DaoRolesBased) NewProposal(title, description, resource string) { // 3th caller is the proposer, 2nd is the dao realm, 1st is the dao_roles_based module proposer := std.GetCallerAt(3) if !d.MemberModule.IsMember(proposer.String()) { panic("proposer is not a member") } + condition := d.ResourcesModule.getResource(resource) + if condition == nil { + panic("resource not found") + } + if len(title) < 9 { panic("title should be at least 9 characters long") } @@ -49,7 +84,7 @@ func (d *DaoRolesBased) NewProposal(title, description string) { panic("description should be at least 15 characters long") } - d.ProposalModule.newProposal(title, description, proposer) + d.ProposalModule.newProposal(title, description, proposer, condition.NewState()) } func (d *DaoRolesBased) HasRole(memberId string, role string) bool { diff --git a/gno/p/dao_roles_based/proposals.gno b/gno/p/dao_roles_based/proposals.gno index df8741391..00d09ba02 100644 --- a/gno/p/dao_roles_based/proposals.gno +++ b/gno/p/dao_roles_based/proposals.gno @@ -5,6 +5,7 @@ import ( "gno.land/p/demo/avl" "gno.land/p/demo/seqid" + "gno.land/p/teritori/daocond" ) type ProposalStatus int @@ -28,26 +29,21 @@ func (s ProposalStatus) String() string { } } -type Votes struct { - Yes uint64 - No uint64 - Abstain uint64 -} - type Proposal struct { - ID seqid.ID - Title string - Description string - Proposer std.Address + id seqid.ID + title string + description string + proposer std.Address + resource string + state daocond.State // StartHeight int64 //MinVotingPeriod dao_utils.Expiration // copy here the dao_utils.Expiration type from gno/p/dao_utils/expiration.gno //Expiration dao_utils.Expiration // copy here the dao_utils.Expiration type from gno/p/dao_utils/expiration.gno // Threshold Threshold: Treshold should be handle in the conditions //Message dao_interfaces.ExecutableMessage // copy here the dao_interfaces.ExecutableMessage type from gno/p/dao_interfaces/messages.gno - Status ProposalStatus - Votes Votes + status ProposalStatus // AllowRevoting bool not handled in this proposal - Ballots *avl.Tree // std.Address -> Ballot + votes map[string]daocond.Vote } type ProposalModule struct { @@ -61,16 +57,25 @@ func newProposalModule() *ProposalModule { } } -func (p *ProposalModule) newProposal(title, description string, proposer std.Address) { +func (p *ProposalModule) newProposal(title, description string, proposer std.Address, state daocond.State) { id := p.proposalIDCounter.Next() proposal := &Proposal{ - ID: id, - Title: title, - Description: description, - Proposer: proposer, - Status: ProposalStatusOpen, - Votes: Votes{}, - Ballots: avl.NewTree(), + id: id, + title: title, + description: description, + proposer: proposer, + status: ProposalStatusOpen, + state: state, + votes: map[string]daocond.Vote{}, } p.proposals.Set(id.String(), proposal) } + +func (p *ProposalModule) getProposal(id uint64) *Proposal { + value, ok := p.proposals.Get(seqid.ID(id).String()) + if !ok { + return nil + } + proposal := value.(*Proposal) + return proposal +} diff --git a/gno/p/dao_roles_based/render.gno b/gno/p/dao_roles_based/render.gno index f9d3c003e..de5c2aa8b 100644 --- a/gno/p/dao_roles_based/render.gno +++ b/gno/p/dao_roles_based/render.gno @@ -58,11 +58,11 @@ func (p *ProposalModule) Render() string { i := 1 p.proposals.Iterate("", "", func(key string, value interface{}) bool { proposal := value.(*Proposal) - sb.WriteString((ufmt.Sprintf("**Proposal %d: %s**\n\n", i, proposal.Title))) - sb.WriteString((ufmt.Sprintf("Description: %s\n\n", proposal.Description))) - sb.WriteString((ufmt.Sprintf("Proposer: %s\n\n", proposal.Proposer))) - sb.WriteString((ufmt.Sprintf("Status: %s\n\n", proposal.Status.String()))) - sb.WriteString((ufmt.Sprintf("Votes: Yes: %d, No: %d, Abstain: %d\n\n", proposal.Votes.Yes, proposal.Votes.No, proposal.Votes.Abstain))) + sb.WriteString((ufmt.Sprintf("**Proposal %d: %s**\n\n", i, proposal.title))) + sb.WriteString((ufmt.Sprintf("Description: %s\n\n", proposal.description))) + sb.WriteString((ufmt.Sprintf("Proposer: %s\n\n", proposal.proposer))) + sb.WriteString((ufmt.Sprintf("Status: %s\n\n", proposal.status.String()))) + sb.WriteString((ufmt.Sprintf("State: %s\n\n", proposal.state.RenderJSON(proposal.votes).String()))) sb.WriteString((ufmt.Sprintf("\n--------------------------------\n"))) i += 1 return false diff --git a/gno/p/dao_roles_based/resources.gno b/gno/p/dao_roles_based/resources.gno index 15e881f76..dd414d330 100644 --- a/gno/p/dao_roles_based/resources.gno +++ b/gno/p/dao_roles_based/resources.gno @@ -2,10 +2,11 @@ package dao_roles_based import ( "gno.land/p/demo/avl" + "gno.land/p/teritori/daocond" ) type ResourcesModule struct { - resources *avl.Tree // string -> daocond.Condition + resources *avl.Tree // string -> *daocond.Condition } func newResourcesModule() *ResourcesModule { @@ -17,3 +18,12 @@ func newResourcesModule() *ResourcesModule { func (r *ResourcesModule) setResources(resources *avl.Tree) { r.resources = resources } + +func (r *ResourcesModule) getResource(name string) daocond.Condition { + value, ok := r.resources.Get(name) + if !ok { + return nil + } + condition := value.(daocond.Condition) + return condition +} diff --git a/gno/p/dao_roles_based/voting.gno b/gno/p/dao_roles_based/voting.gno deleted file mode 100644 index 5ff2cd7ca..000000000 --- a/gno/p/dao_roles_based/voting.gno +++ /dev/null @@ -1,15 +0,0 @@ -package dao_roles_based - -type Vote int - -const ( - VoteYes Vote = iota - VoteNo - VoteAbstain -) - -type Ballot struct { - proposalID uint64 - vote Vote - rationale string -} diff --git a/gno/r/dao_realm2/dao_realm2.gno b/gno/r/dao_realm2/dao_realm2.gno index 9708b6062..e4d9f8d89 100644 --- a/gno/r/dao_realm2/dao_realm2.gno +++ b/gno/r/dao_realm2/dao_realm2.gno @@ -16,7 +16,11 @@ func init() { } func Proposal(title, description string) { - dao.NewProposal(title, description) + dao.NewProposal(title, description, "social.publish_post") +} + +func Vote(proposalID uint64, vote string) { + dao.Vote(proposalID, vote) } func Render(path string) string {