-
Notifications
You must be signed in to change notification settings - Fork 373
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## Description This PR introduces an upgrade to the `r/gov/dao` system: - it makes it configurable through custom implementations - added a `p/demo/simpledao` implementation - the implementations are changeable through a govdao proposal - adds weighted voting to a govdao example implementation <details><summary>Contributors' checklist...</summary> - [x] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [x] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md). </details> --------- Co-authored-by: Manfred Touron <[email protected]>
- Loading branch information
1 parent
386f8bd
commit 71bc19d
Showing
62 changed files
with
3,542 additions
and
1,063 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package combinederr | ||
|
||
import "strings" | ||
|
||
// CombinedError is a combined execution error | ||
type CombinedError struct { | ||
errors []error | ||
} | ||
|
||
// Error returns the combined execution error | ||
func (e *CombinedError) Error() string { | ||
if len(e.errors) == 0 { | ||
return "" | ||
} | ||
|
||
var sb strings.Builder | ||
|
||
for _, err := range e.errors { | ||
sb.WriteString(err.Error() + "; ") | ||
} | ||
|
||
// Remove the last semicolon and space | ||
result := sb.String() | ||
|
||
return result[:len(result)-2] | ||
} | ||
|
||
// Add adds a new error to the execution error | ||
func (e *CombinedError) Add(err error) { | ||
if err == nil { | ||
return | ||
} | ||
|
||
e.errors = append(e.errors, err) | ||
} | ||
|
||
// Size returns a | ||
func (e *CombinedError) Size() int { | ||
return len(e.errors) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module gno.land/p/demo/combinederr |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package dao | ||
|
||
const ( | ||
ProposalAddedEvent = "ProposalAdded" // emitted when a new proposal has been added | ||
ProposalAcceptedEvent = "ProposalAccepted" // emitted when a proposal has been accepted | ||
ProposalNotAcceptedEvent = "ProposalNotAccepted" // emitted when a proposal has not been accepted | ||
ProposalExecutedEvent = "ProposalExecuted" // emitted when a proposal has been executed | ||
|
||
ProposalEventIDKey = "proposal-id" | ||
ProposalEventAuthorKey = "proposal-author" | ||
ProposalEventExecutionKey = "exec-status" | ||
) | ||
|
||
// ProposalRequest is a single govdao proposal request | ||
// that contains the necessary information to | ||
// log and generate a valid proposal | ||
type ProposalRequest struct { | ||
Description string // the description associated with the proposal | ||
Executor Executor // the proposal executor | ||
} | ||
|
||
// DAO defines the DAO abstraction | ||
type DAO interface { | ||
// PropStore is the DAO proposal storage | ||
PropStore | ||
|
||
// Propose adds a new proposal to the executor-based GOVDAO. | ||
// Returns the generated proposal ID | ||
Propose(request ProposalRequest) (uint64, error) | ||
|
||
// ExecuteProposal executes the proposal with the given ID | ||
ExecuteProposal(id uint64) error | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// Package dao houses common DAO building blocks (framework), which can be used or adopted by any | ||
// specific DAO implementation. By design, the DAO should house the proposals it receives, but not the actual | ||
// DAO members or proposal votes. These abstractions should be implemented by a separate entity, to keep the DAO | ||
// agnostic of implementation details such as these (member / vote management). | ||
package dao |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package dao | ||
|
||
import ( | ||
"std" | ||
|
||
"gno.land/p/demo/ufmt" | ||
) | ||
|
||
// EmitProposalAdded emits an event signaling that | ||
// a given proposal was added | ||
func EmitProposalAdded(id uint64, proposer std.Address) { | ||
std.Emit( | ||
ProposalAddedEvent, | ||
ProposalEventIDKey, ufmt.Sprintf("%d", id), | ||
ProposalEventAuthorKey, proposer.String(), | ||
) | ||
} | ||
|
||
// EmitProposalAccepted emits an event signaling that | ||
// a given proposal was accepted | ||
func EmitProposalAccepted(id uint64) { | ||
std.Emit( | ||
ProposalAcceptedEvent, | ||
ProposalEventIDKey, ufmt.Sprintf("%d", id), | ||
) | ||
} | ||
|
||
// EmitProposalNotAccepted emits an event signaling that | ||
// a given proposal was not accepted | ||
func EmitProposalNotAccepted(id uint64) { | ||
std.Emit( | ||
ProposalNotAcceptedEvent, | ||
ProposalEventIDKey, ufmt.Sprintf("%d", id), | ||
) | ||
} | ||
|
||
// EmitProposalExecuted emits an event signaling that | ||
// a given proposal was executed, with the given status | ||
func EmitProposalExecuted(id uint64, status ProposalStatus) { | ||
std.Emit( | ||
ProposalExecutedEvent, | ||
ProposalEventIDKey, ufmt.Sprintf("%d", id), | ||
ProposalEventExecutionKey, status.String(), | ||
) | ||
} | ||
|
||
// EmitVoteAdded emits an event signaling that | ||
// a vote was cast for a given proposal | ||
func EmitVoteAdded(id uint64, voter std.Address, option VoteOption) { | ||
std.Emit( | ||
VoteAddedEvent, | ||
VoteAddedIDKey, ufmt.Sprintf("%d", id), | ||
VoteAddedAuthorKey, voter.String(), | ||
VoteAddedOptionKey, option.String(), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package dao | ||
|
||
// Executor represents a minimal closure-oriented proposal design. | ||
// It is intended to be used by a govdao governance proposal (v1, v2, etc) | ||
type Executor interface { | ||
// Execute executes the given proposal, and returns any error encountered | ||
// during the execution | ||
Execute() error | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module gno.land/p/demo/dao | ||
|
||
require gno.land/p/demo/ufmt v0.0.0-latest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package dao | ||
|
||
import "std" | ||
|
||
// ProposalStatus is the currently active proposal status, | ||
// changed based on DAO functionality. | ||
// Status transitions: | ||
// | ||
// ACTIVE -> ACCEPTED -> EXECUTION(SUCCEEDED/FAILED) | ||
// | ||
// ACTIVE -> NOT ACCEPTED | ||
type ProposalStatus string | ||
|
||
var ( | ||
Active ProposalStatus = "active" // proposal is still active | ||
Accepted ProposalStatus = "accepted" // proposal gathered quorum | ||
NotAccepted ProposalStatus = "not accepted" // proposal failed to gather quorum | ||
ExecutionSuccessful ProposalStatus = "execution successful" // proposal is executed successfully | ||
ExecutionFailed ProposalStatus = "execution failed" // proposal is failed during execution | ||
) | ||
|
||
func (s ProposalStatus) String() string { | ||
return string(s) | ||
} | ||
|
||
// PropStore defines the proposal storage abstraction | ||
type PropStore interface { | ||
// Proposals returns the given paginated proposals | ||
Proposals(offset, count uint64) []Proposal | ||
|
||
// ProposalByID returns the proposal associated with | ||
// the given ID, if any | ||
ProposalByID(id uint64) (Proposal, error) | ||
|
||
// Size returns the number of proposals in | ||
// the proposal store | ||
Size() int | ||
} | ||
|
||
// Proposal is the single proposal abstraction | ||
type Proposal interface { | ||
// Author returns the author of the proposal | ||
Author() std.Address | ||
|
||
// Description returns the description of the proposal | ||
Description() string | ||
|
||
// Status returns the status of the proposal | ||
Status() ProposalStatus | ||
|
||
// Executor returns the proposal executor | ||
Executor() Executor | ||
|
||
// Stats returns the voting stats of the proposal | ||
Stats() Stats | ||
|
||
// IsExpired returns a flag indicating if the proposal expired | ||
IsExpired() bool | ||
|
||
// Render renders the proposal in a readable format | ||
Render() string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package dao | ||
|
||
// NOTE: | ||
// This voting pods will be removed in a future version of the | ||
// p/demo/dao package. A DAO shouldn't have to comply with or define how the voting mechanism works internally; | ||
// it should be viewed as an entity that makes decisions | ||
// | ||
// The extent of "votes being enforced" in this implementation is just in the context | ||
// of types a DAO can use (import), and in the context of "Stats", where | ||
// there is a notion of "Yay", "Nay" and "Abstain" votes. | ||
const ( | ||
VoteAddedEvent = "VoteAdded" // emitted when a vote was cast for a proposal | ||
|
||
VoteAddedIDKey = "proposal-id" | ||
VoteAddedAuthorKey = "author" | ||
VoteAddedOptionKey = "option" | ||
) | ||
|
||
// VoteOption is the limited voting option for a DAO proposal | ||
type VoteOption string | ||
|
||
const ( | ||
YesVote VoteOption = "YES" // Proposal should be accepted | ||
NoVote VoteOption = "NO" // Proposal should be rejected | ||
AbstainVote VoteOption = "ABSTAIN" // Side is not chosen | ||
) | ||
|
||
func (v VoteOption) String() string { | ||
return string(v) | ||
} | ||
|
||
// Stats encompasses the proposal voting stats | ||
type Stats struct { | ||
YayVotes uint64 | ||
NayVotes uint64 | ||
AbstainVotes uint64 | ||
|
||
TotalVotingPower uint64 | ||
} | ||
|
||
// YayPercent returns the percentage (0-100) of the yay votes | ||
// in relation to the total voting power | ||
func (v Stats) YayPercent() uint64 { | ||
return v.YayVotes * 100 / v.TotalVotingPower | ||
} | ||
|
||
// NayPercent returns the percentage (0-100) of the nay votes | ||
// in relation to the total voting power | ||
func (v Stats) NayPercent() uint64 { | ||
return v.NayVotes * 100 / v.TotalVotingPower | ||
} | ||
|
||
// AbstainPercent returns the percentage (0-100) of the abstain votes | ||
// in relation to the total voting power | ||
func (v Stats) AbstainPercent() uint64 { | ||
return v.AbstainVotes * 100 / v.TotalVotingPower | ||
} | ||
|
||
// MissingVotes returns the summed voting power that has not | ||
// participated in proposal voting yet | ||
func (v Stats) MissingVotes() uint64 { | ||
return v.TotalVotingPower - (v.YayVotes + v.NayVotes + v.AbstainVotes) | ||
} | ||
|
||
// MissingVotesPercent returns the percentage (0-100) of the missing votes | ||
// in relation to the total voting power | ||
func (v Stats) MissingVotesPercent() uint64 { | ||
return v.MissingVotes() * 100 / v.TotalVotingPower | ||
} |
5 changes: 3 additions & 2 deletions
5
examples/gno.land/r/gov/dao/gno.mod → examples/gno.land/p/demo/membstore/gno.mod
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
module gno.land/r/gov/dao | ||
module gno.land/p/demo/membstore | ||
|
||
require ( | ||
gno.land/p/demo/avl v0.0.0-latest | ||
gno.land/p/demo/testutils v0.0.0-latest | ||
gno.land/p/demo/uassert v0.0.0-latest | ||
gno.land/p/demo/ufmt v0.0.0-latest | ||
gno.land/p/demo/urequire v0.0.0-latest | ||
gno.land/p/gov/proposal v0.0.0-latest | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package membstore | ||
|
||
import ( | ||
"std" | ||
) | ||
|
||
// MemberStore defines the member storage abstraction | ||
type MemberStore interface { | ||
// Members returns all members in the store | ||
Members(offset, count uint64) []Member | ||
|
||
// Size returns the current size of the store | ||
Size() int | ||
|
||
// IsMember returns a flag indicating if the given address | ||
// belongs to a member | ||
IsMember(address std.Address) bool | ||
|
||
// TotalPower returns the total voting power of the member store | ||
TotalPower() uint64 | ||
|
||
// Member returns the requested member | ||
Member(address std.Address) (Member, error) | ||
|
||
// AddMember adds a member to the store | ||
AddMember(member Member) error | ||
|
||
// UpdateMember updates the member in the store. | ||
// If updating a member's voting power to 0, | ||
// the member will be removed | ||
UpdateMember(address std.Address, member Member) error | ||
} | ||
|
||
// Member holds the relevant member information | ||
type Member struct { | ||
Address std.Address // bech32 gno address of the member (unique) | ||
VotingPower uint64 // the voting power of the member | ||
} |
Oops, something went wrong.