-
Notifications
You must be signed in to change notification settings - Fork 414
feat: Regional Community DAO #4333
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -97,6 +97,12 @@ type ( | |||||||||||||||||||
Execute() error | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
// RenderableProposal makes sure that the proposal is renderable | ||||||||||||||||||||
RenderableProposal interface { | ||||||||||||||||||||
ProposalDefinition | ||||||||||||||||||||
Render() string | ||||||||||||||||||||
} | ||||||||||||||||||||
Comment on lines
+100
to
+104
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could be simpler:
Suggested change
This interface might be a good candidate to be defined in a |
||||||||||||||||||||
|
||||||||||||||||||||
// CustomizableVoteChoices defines an interface for proposal definitions that want | ||||||||||||||||||||
// to customize the list of allowed voting choices. | ||||||||||||||||||||
CustomizableVoteChoices interface { | ||||||||||||||||||||
|
@@ -219,6 +225,8 @@ func (p Proposal) IsVoteChoiceValid(c VoteChoice) bool { | |||||||||||||||||||
return p.voteChoices.Has(string(c)) | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
// add default prop render | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Definitely a good feature to have, default render support for proposals, or DAOs so users can opt in. At some point I tried to have render defaults so they could also be used in |
||||||||||||||||||||
|
||||||||||||||||||||
// IsQuorumReached checks if a participation quorum is reach. | ||||||||||||||||||||
func IsQuorumReached(quorum float64, r ReadonlyVotingRecord, members MemberSet) bool { | ||||||||||||||||||||
if members.Size() <= 0 || quorum <= 0 { | ||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -139,7 +139,7 @@ func FindMostVotedChoice(r ReadonlyVotingRecord) VoteChoice { | |
|
||
// SelectChoiceByAbsoluteMajority select the vote choice by absolute majority. | ||
// Vote choice is a majority when chosen by more than half of the votes. | ||
// Absolute majority considers abstentions when counting votes. | ||
// Absolute majority considers abstentions when counting votes. // what does the bool mean? quorum reached? prop passed? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case and the other majority check functions the second boolean result means that a majority was reached and voted for the same voting choice when result is There might be cases where devs would have to define their own majority check functions if the ones pre defined here won't work for their use case. It might be good to add more majority check functions to cover more cases. A nice to have :) Quorum can be checked using the |
||
func SelectChoiceByAbsoluteMajority(r ReadonlyVotingRecord, membersCount int) (VoteChoice, bool) { | ||
choice := FindMostVotedChoice(r) | ||
if choice != ChoiceNone && r.VoteCount(choice) > int(membersCount/2) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module gno.land/r/devrels/rcdao |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package rcdao | ||
|
||
import ( | ||
"std" | ||
"time" | ||
|
||
"gno.land/p/nt/commondao" | ||
"gno.land/r/sys/users" | ||
) | ||
|
||
type EventProposal struct { | ||
title string | ||
body string | ||
location string // ie Berlin, Germany | ||
requestedBudget std.Coins | ||
beneficiary std.Address // after the prop passes, coins are sent here | ||
} | ||
|
||
func (ep EventProposal) Title() string { return ep.title } | ||
func (ep EventProposal) Body() string { return ep.body } | ||
func (ep EventProposal) Location() string { return ep.location } | ||
func (EventProposal) VotingPeriod() time.Duration { return time.Hour * 24 * 7 } | ||
|
||
func (ep EventProposal) Render() string { | ||
out := ep.body + "\n\n" | ||
out += ep.location + "\n\n" | ||
out += "Asking amount: " + ep.requestedBudget.String() + "\n\n" | ||
|
||
name := "Author: " + ep.beneficiary.String() | ||
user := users.ResolveAddress(ep.beneficiary) | ||
if user != nil { | ||
name = "Author: " + user.RenderLink("") | ||
} | ||
|
||
out += name | ||
return out | ||
} | ||
|
||
func (EventProposal) Tally(r commondao.ReadonlyVotingRecord, set commondao.MemberSet) (bool, error) { | ||
_, passed := commondao.SelectChoiceBySuperMajority(r, set.Size()) | ||
return passed, nil | ||
} | ||
|
||
func (ep EventProposal) Execute() error { | ||
crossing() | ||
|
||
bank := std.NewBanker(std.BankerTypeRealmSend) | ||
treasuryCoins := bank.GetCoins(std.CurrentRealm().Address()) // DAO treasury coins | ||
|
||
for i := 0; i < len(ep.requestedBudget); i++ { | ||
coin := ep.requestedBudget[i] | ||
if treasuryCoins.AmountOf(coin.Denom) >= coin.Amount { | ||
panic("cannot pay out requested budget, too little in the treasury") | ||
} | ||
} | ||
Comment on lines
+50
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could also move this into a Just in case, have in mind that right now Right now, and before returning an error within the Otherwise returning an error in either of those will potentially lead to save any state changes done before returning the error and the proposal state will be updated to I will give a though on how to improve this behavior because right now doesn't seem right. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
PR for those is #4352 |
||
|
||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package rcdao | ||
|
||
import ( | ||
"std" | ||
"strconv" | ||
|
||
"gno.land/p/demo/ufmt" | ||
"gno.land/p/moul/md" | ||
"gno.land/p/nt/commondao" | ||
) | ||
|
||
var dao *commondao.CommonDAO | ||
|
||
func init() { | ||
dao = commondao.New( | ||
commondao.WithName("Regional Community DAO"), | ||
commondao.WithMember("g125em6arxsnj49vx35f0n0z34putv5ty3376fg5")) | ||
} | ||
|
||
// I want to have a quick view of members | ||
// I want to have an easy render, name it DefaultRender | ||
// Exposing RO trees for members & props would make it easy to integrate with existing p/ | ||
|
||
func ProposeMeetup(title, body, location string, reqBudget std.Coins, beneficiary std.Address) { | ||
crossing() | ||
|
||
proposer := std.PreviousRealm().Address() | ||
if !dao.Members().Has(proposer) { | ||
panic("only members can propose") | ||
} | ||
|
||
prop := EventProposal{ | ||
title: title, | ||
body: body, | ||
location: location, | ||
requestedBudget: reqBudget, | ||
beneficiary: beneficiary, | ||
} | ||
|
||
p := dao.MustPropose(proposer, prop) | ||
std.Emit("NewProp", "id", strconv.Itoa(int(p.ID())), "title", p.Definition().Title()) | ||
} | ||
|
||
func Render(_ string) string { | ||
out := md.H1(dao.Name()) | ||
|
||
out += "---\n\n" | ||
out += "## Active Props\n\n" | ||
out += ufmt.Sprintf("Active prop num: %d\n\n", dao.ActiveProposals().Size()) | ||
|
||
dao.ActiveProposals().Iterate(0, dao.ActiveProposals().Size(), true, func(prop *commondao.Proposal) bool { | ||
out += ufmt.Sprintf("### Prop #%d - %s\n\n", +prop.ID(), prop.Definition().Title()) | ||
|
||
ep := prop.Definition().(commondao.RenderableProposal) | ||
out += ep.Render() | ||
|
||
return false | ||
}) | ||
|
||
return out | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the idea is that devs should explicitly check it.
Following that idea it would be good that the fact is mentioned in the
Propose()
method docs.Documentation is something that should be improved 👍
Not checking that the creator address is a DAO member makes the proposing open to other use cases where devs might want to create proposals without enforcing membership.