Skip to content
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

feat: r/gov/dao v2 #2581

Open
wants to merge 87 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
4dd7c58
Add standard govdao types
zivkovicmilos Jul 13, 2024
aad859b
Initial simpledao implementation
zivkovicmilos Jul 13, 2024
81396bf
Add membstore tests
zivkovicmilos Jul 13, 2024
dd5d64b
Add propstore tests
zivkovicmilos Jul 13, 2024
c342741
Add testing stubs
zivkovicmilos Jul 13, 2024
3d16ba6
Update examples/gno.land/p/gov/dao/types.gno
zivkovicmilos Jul 14, 2024
8f2e3ac
Move /nt/simpledao -> /demo/simpledao
zivkovicmilos Jul 14, 2024
ae0639a
Move DAO API to dao.gno
zivkovicmilos Jul 14, 2024
3a7ba00
Move vote API to vote.gno
zivkovicmilos Jul 14, 2024
00056a5
Add GetVoteByMember to proposal API
zivkovicmilos Jul 14, 2024
209ee06
Drop name from dao.Member
zivkovicmilos Jul 14, 2024
54c555d
Add limit to number of proposals returned
zivkovicmilos Jul 14, 2024
84aeb30
Add Size() to MembStore API
zivkovicmilos Jul 14, 2024
322bc8c
Apply Yoda wisdom to MemberStore API
zivkovicmilos Jul 14, 2024
794a353
Add execution failed ProposalStatus
zivkovicmilos Jul 14, 2024
8c45039
Drop unused PropStore method
zivkovicmilos Jul 14, 2024
268021d
Drop RemoveMember, add removal in UpdateMember
zivkovicmilos Jul 14, 2024
43e5c66
Add Size() to PropStore API
zivkovicmilos Jul 14, 2024
e1cd617
Add pagination to MembStore
zivkovicmilos Jul 14, 2024
67e4535
Rename proposal.gno -> proposals.gno
zivkovicmilos Jul 14, 2024
8fbbd5e
Start cleaning up r/gov/dao
zivkovicmilos Jul 14, 2024
fd13d02
Clean house for the govdao executor
zivkovicmilos Jul 14, 2024
ebfd3d2
Add remaining simpledao tests
zivkovicmilos Jul 15, 2024
b3856c7
Remove unsafe ctx executor usage
zivkovicmilos Jul 15, 2024
4d3d94e
Fixup prop filetests
zivkovicmilos Jul 15, 2024
1f1d5d7
Fixup prop filetests
zivkovicmilos Jul 15, 2024
7fa7530
Remove faulty test
zivkovicmilos Jul 15, 2024
ef299bd
Add pagination for vote fetching
zivkovicmilos Jul 15, 2024
3d0c056
Tidy mod
zivkovicmilos Jul 15, 2024
4396c48
Update update method
zivkovicmilos Jul 15, 2024
24e0b08
Add r/sys/vars
zivkovicmilos Jul 15, 2024
4827d0b
Version packages, add unit tests
zivkovicmilos Jul 15, 2024
26f21d8
Start integrating r/sys/vars into EndBlocker
zivkovicmilos Jul 15, 2024
71e8644
Add moniker to r/gnoland/valopers
zivkovicmilos Jul 16, 2024
93d2b70
Resolve freaky import
zivkovicmilos Jul 16, 2024
efe35cc
Restore Endblocker
zivkovicmilos Jul 16, 2024
63cf23f
Make tidy
zivkovicmilos Jul 16, 2024
4520593
Make tidy
zivkovicmilos Jul 16, 2024
af65764
Fixup vars tests
zivkovicmilos Jul 16, 2024
f567394
Merge branch 'master' into dev/zivkovicmilos/govdao
zivkovicmilos Jul 16, 2024
4ec9f3b
Export errors
zivkovicmilos Jul 16, 2024
30ac5ff
Update comment
zivkovicmilos Jul 16, 2024
c05289f
Drop useless voters methods
zivkovicmilos Jul 16, 2024
d8e5b7d
Drop excess field
zivkovicmilos Jul 16, 2024
d320493
Update voting power
zivkovicmilos Jul 16, 2024
d33b0df
Please the standards police
zivkovicmilos Jul 16, 2024
79be838
Slim down the DAO API
zivkovicmilos Jul 16, 2024
8c52893
Revert "Slim down the DAO API"
zivkovicmilos Jul 16, 2024
74948fc
Add Executor to proposal
zivkovicmilos Jul 16, 2024
c9b4cc4
Fix up old API
zivkovicmilos Jul 17, 2024
93ca6a8
Mod tidy
zivkovicmilos Jul 17, 2024
ee77102
Make the PropStore part of the DAO API
zivkovicmilos Jul 17, 2024
dbf0966
Fixup prop test
zivkovicmilos Jul 17, 2024
2f337a6
Move out memberstore
zivkovicmilos Jul 17, 2024
31d265f
Add Render to proposal
zivkovicmilos Jul 18, 2024
908b949
Add govdao executor wrapper
zivkovicmilos Jul 18, 2024
6b3b81c
Remove unused code
zivkovicmilos Jul 18, 2024
480a287
Add new prop test
zivkovicmilos Jul 18, 2024
06fe4fe
Move out memberstore
zivkovicmilos Jul 20, 2024
dba0379
Move p/gov/dao -> p/demo/dao
zivkovicmilos Jul 20, 2024
873ba9b
Drop voting fetchers from the propstore
zivkovicmilos Jul 21, 2024
6ceb72a
Rename VotingStats -> Stats
zivkovicmilos Jul 21, 2024
8198c7f
Add key name to r/sys/vars emit event
zivkovicmilos Jul 21, 2024
8a51452
Merge branch 'master' into dev/zivkovicmilos/govdao
zivkovicmilos Jul 22, 2024
b0fa6b7
Merge branch 'master' into dev/zivkovicmilos/govdao
zivkovicmilos Aug 15, 2024
676b202
Make the executor not panic due to wrong caller
zivkovicmilos Aug 15, 2024
852291c
isCallerGOVDAO -> isCallerDAORealm
zivkovicmilos Aug 15, 2024
ae650d2
Resolve nitpick
zivkovicmilos Aug 15, 2024
aa4f9ef
Fix faulty test assert
zivkovicmilos Aug 15, 2024
33343d7
Merge branch 'master' into dev/zivkovicmilos/govdao
zivkovicmilos Sep 18, 2024
c3c33a2
Merge branch 'master' into dev/zivkovicmilos/govdao
zivkovicmilos Oct 7, 2024
30b80f7
Move r/sys/vars outside the PR
zivkovicmilos Oct 7, 2024
7addbd5
Move event emissions to the dao package
zivkovicmilos Oct 7, 2024
2f2632b
Add p/demo/dao doc and new event
zivkovicmilos Oct 7, 2024
671872a
Add duplicate check for WithInitialMembers
zivkovicmilos Oct 7, 2024
3f196b2
Change valoper proposal comment
zivkovicmilos Oct 7, 2024
c4b3970
Drop unused constant from propstore
zivkovicmilos Oct 7, 2024
38e2eae
Remove unused Vote struct from vote.gno
zivkovicmilos Oct 7, 2024
82745d9
Tidy mods
zivkovicmilos Oct 7, 2024
15404f5
Move combinederr to a specific package
zivkovicmilos Oct 7, 2024
2f19233
Remove IsExpired from the executor
zivkovicmilos Oct 7, 2024
9cbc56a
Remove unused check
zivkovicmilos Oct 7, 2024
3858a3c
Update gno.land/pkg/gnoland/vals.go
zivkovicmilos Oct 7, 2024
ee989ff
Add note about votes in the DAO
zivkovicmilos Oct 7, 2024
af75547
Merge branch 'dev/zivkovicmilos/govdao' of https://github.com/gnolang…
zivkovicmilos Oct 7, 2024
d98d780
Remove /r/gov/dao/v2 dependency
zivkovicmilos Oct 9, 2024
7171651
Merge branch 'master' into dev/zivkovicmilos/govdao
zivkovicmilos Oct 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions examples/gno.land/p/combinederr/combinederr.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package combinederr

import "strings"

// CombinedError is a combined execution error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// CombinedError is a combined execution error
// CombinedError is a combined execution error.

Nitpick: in comments, sentences end with a period. It can be enforced by a linter. I only reported this line, but there are many other occurences of missing period.

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)
}
1 change: 1 addition & 0 deletions examples/gno.land/p/combinederr/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/combinederr
33 changes: 33 additions & 0 deletions examples/gno.land/p/demo/dao/dao.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dao
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a package-level comment to explain the role of this p/

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added:

2f2632b


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
}
5 changes: 5 additions & 0 deletions examples/gno.land/p/demo/dao/doc.gno
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
56 changes: 56 additions & 0 deletions examples/gno.land/p/demo/dao/events.gno
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(),
)
}
9 changes: 9 additions & 0 deletions examples/gno.land/p/demo/dao/executor.gno
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
}
3 changes: 3 additions & 0 deletions examples/gno.land/p/demo/dao/gno.mod
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
57 changes: 57 additions & 0 deletions examples/gno.land/p/demo/dao/proposals.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package dao

import "std"

type ProposalStatus string

// ACTIVE -> ACCEPTED -> EXECUTION(SUCCEEDED/FAILED)
// ACTIVE -> NOT ACCEPTED
Comment on lines +7 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: these comment lines should be part of a comment block for type ProposalStatus string declaration, with a short explanation sentence.

We should use a linter for missing comments for exports.

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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: Wouldn't it be better and more secure to use a struct here? What if we define a custom struct that implements the interface for an attack?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to leave the implementation details of the proposal up to the package / realm creator:

// 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
}

The Package abstraction is completely read-only anyways. An example of this is the simpledao.proposal, which is an implementation of a Proposal that has tally information

// 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
}
68 changes: 68 additions & 0 deletions examples/gno.land/p/demo/dao/vote.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package dao
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment to explain that the vote will be removed from this 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. Therefore, we should focus on the proposal rather than the votes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to merge this PR soon, so I'm in favor of just adding the comment. However, please note that this is the biggest weakness of this implementation, and it's the only issue I see that will require us to create a v2.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked, it turns out the extent of "votes" being present in the dao package is just in the context of a single type Stats. We don't enforce a voting strategy in the p/demo/dao package, at all.

simpledao, which is a DAO implementation, exposes its own vote method, that is not standard.

I've added a comment to make it clear:

ee989ff


// 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"
)

type VoteOption string

const (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be an option DidNotVote at iota, as it indicates a missing vote, different from voluntary abstention? What impact on decision rules, quorum, etc?

I see that later in the code you keep track of missing votes (it is always useful), but it is not clear to me if, when and why missing vote == abstain, in the context of a generic govDAO where provided specific governance rules could be different.

YesVote VoteOption = "YES"
NoVote VoteOption = "NO"
AbstainVote VoteOption = "ABSTAIN"
)

func (v VoteOption) String() string {
return string(v)
}

// Stats encompasses the proposal voting stats
type Stats struct {
YayVotes uint64
NayVotes uint64
AbstainVotes uint64
moul marked this conversation as resolved.
Show resolved Hide resolved

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
}
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
)
38 changes: 38 additions & 0 deletions examples/gno.land/p/demo/membstore/members.gno
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
}
Loading
Loading