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

feat: r/gov/dao v2 #2581

wants to merge 87 commits into from

Conversation

zivkovicmilos
Copy link
Member

@zivkovicmilos zivkovicmilos commented Jul 13, 2024

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
Contributors' checklist...
  • Added new tests, or not needed, or not feasible
  • Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory
  • Updated the official documentation or not needed
  • No breaking changes were made, or a BREAKING CHANGE: xxx message was included in the description
  • Added references to related issues and PRs
  • Provided any useful hints for running manual tests
  • Added new benchmarks to generated graphs, if any. More info here.

@zivkovicmilos zivkovicmilos added the 📦 ⛰️ gno.land Issues or PRs gno.land package related label Jul 13, 2024
@zivkovicmilos zivkovicmilos requested a review from moul July 13, 2024 16:21
@zivkovicmilos zivkovicmilos self-assigned this Jul 13, 2024
@github-actions github-actions bot added the 🧾 package/realm Tag used for new Realms or Packages. label Jul 13, 2024
@moul
Copy link
Member

moul commented Jul 13, 2024

The high-level feedback is:

  • Avoid using GetAllXXX []XXX for members, votes, and proposals. Instead, use Size(), GetByID, and eventually GetByOffset.
  • Simplify the p/gov/dao structure, reducing complexity (flatten the interfaces or eventually embed), remove what's unnecessary.
  • Move the implementation (r/) to the demo/ directory. This allows you to keep the Opt pattern, the 3 Add, Remove, and Update operations instead of just Set, and enables faster PR review/merging. The /nt component should be more optimized and minimal, and you can start something in demo/ and eventually move it after.

import "strings"

// CombinedError is a combined execution error
type CombinedError struct {
Copy link
Member

Choose a reason for hiding this comment

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

p/combinederr? :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Moved:

15404f5

"gno.land/p/demo/context"
"gno.land/p/gov/proposal"
"gno.land/p/demo/dao"
govdao "gno.land/r/gov/dao/v2"
Copy link
Member

Choose a reason for hiding this comment

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

should not depend on a specific govdao version.

Copy link
Member Author

Choose a reason for hiding this comment

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

Resolved in:

d98d780

"gno.land/p/demo/ufmt"
pVals "gno.land/p/sys/validators"
govdao "gno.land/r/gov/dao"
"gno.land/r/sys/validators"
govdao "gno.land/r/gov/dao/v2"
Copy link
Member

@moul moul Sep 23, 2024

Choose a reason for hiding this comment

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

We should find a solution to avoid depending on a specific GovDAO version.

You can either use another mechanism or create a r/valopers/v2/govdaov2 helper package. This will allow us to build the necessary bridge if we switch to GovDAO v3.

Let me know if you can't find an elegant solution, and I can help you brainstorm one.

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've tried to apply the proxy pattern you've mentioned, and I think it fits in nice. Realms that require an expanded GovDAO API will just define their own "bridge", or have some kind of update mechanism.

Let me know what you think:

d98d780

// Craft the proposal comment
comment := ufmt.Sprintf(
// Craft the proposal description
description := ufmt.Sprintf(
"Proposal to add valoper %s (Address: %s; PubKey: %s) to the valset",
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"Proposal to add valoper %s (Address: %s; PubKey: %s) to the valset",
"Add valoper %s (Address: %s; PubKey: %s) to the valset",

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed:

3f196b2


// maxRequestVotes is the maximum number of
// paginated votes that can be requested
maxRequestVotes = 50
Copy link
Member

Choose a reason for hiding this comment

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

unused

Copy link
Member Author

Choose a reason for hiding this comment

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

Dropped:

c4b3970

gno.land/p/nt/poa v0.0.0-latest
gno.land/p/sys/validators v0.0.0-latest
gno.land/r/gov/dao/v2 v0.0.0-latest
Copy link
Member

Choose a reason for hiding this comment

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

r/sys/validators shouldn't depend on r/gov/dao/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.

Resolved in:

d98d780

}

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

@@ -0,0 +1,70 @@
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

func WithInitialMembers(members []Member) Option {
return func(store *MembStore) {
for _, m := range members {
store.members.Set(m.Address.String(), m)
Copy link
Member

Choose a reason for hiding this comment

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

check for ErrAlreadyMember

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 check, good catch:

671872a

status dao.ProposalStatus // status of the proposal

tally *tally // voting tally
getTotalVotingPowerFn func() uint64 // callback for the total voting power
Copy link
Member

Choose a reason for hiding this comment

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

i don't get the reason for having this callback.

Copy link
Member Author

@zivkovicmilos zivkovicmilos Oct 7, 2024

Choose a reason for hiding this comment

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

Voting power information is not contained in the proposal, but in the membstore that's associated with the DAO. We need this callback to construct the proposal stats:

func (p *proposal) Stats() dao.Stats {
	// Get the total voting power of the body
	totalPower := p.getTotalVotingPowerFn()

	return dao.Stats{
		YayVotes:         p.tally.yays,
		NayVotes:         p.tally.nays,
		AbstainVotes:     p.tally.abstains,
		TotalVotingPower: totalPower,
	}
}

We set it when initializing the proposal:

	// Create the wrapped proposal
	prop := &proposal{
		author:                caller,
		description:           request.Description,
		executor:              request.Executor,
		status:                dao.Active,
		tally:                 newTally(),
		getTotalVotingPowerFn: s.membStore.TotalPower,
	}

@zivkovicmilos zivkovicmilos changed the title feat: r/gov/dao v2 + r/sys/vars feat: r/gov/dao v2 Oct 7, 2024
Copy link
Contributor

@mvertes mvertes left a comment

Choose a reason for hiding this comment

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

Mostly doc nitpicking comments, one clarification needed about missing vs abstain votes, and a suggestion on members() method.


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.

Comment on lines +7 to +8
// ACTIVE -> ACCEPTED -> EXECUTION(SUCCEEDED/FAILED)
// ACTIVE -> NOT ACCEPTED
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.


return m
}

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
// AddMember adds member to the member store `m`.
// It fails if `m` is not a GovDAO or if member is already in store.


return nil
}

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
// UpdateMember updates the MembStore in case of change of member address or voting power.
// It fails if the MembStore is not a a GovDAO.


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.


return nil
}

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
// IsMember returns true if the member associated with address is in m store.


return exists
}

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
// Member returns the member entry in m corresponding to address, or an error if not present.


return member.(Member), nil
}

Copy link
Contributor

Choose a reason for hiding this comment

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

I had difficuties to understand Members() API. May be it would help it you keep the same idiom as Go slices: if m was a Go array, members(start, end uint64) would return m[start:end].

func (m *MembStore) Size() int {
return m.members.Size()
}

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
// Size() returns the number of members of the member store.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
don't merge Please don't merge this functionality temporarily 📦 ⛰️ gno.land Issues or PRs gno.land package related 🧾 package/realm Tag used for new Realms or Packages.
Projects
Status: In Progress
Status: 🔥 Important / Blocked
Status: In Review
Development

Successfully merging this pull request may close these issues.

4 participants