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: GovDAO implementation proposal. #3389

Open
wants to merge 20 commits into
base: master
Choose a base branch
from

Conversation

ajnavarro
Copy link
Contributor

@ajnavarro ajnavarro commented Dec 20, 2024

GovDAO following Jae's specs:

https://gist.github.com/jaekwon/918ad325c4c8f7fb5d6e022e33cb7eb3

Notes:

Ignore all deleted files. I had to remove all previous govDAO implementations because it was too difficult (and worthless) to make it backward compatible, so I thought the best thing to do was remove it.

Issues needed to be fixed:

Main concepts

gno.land/r/gov/dao/proxy

This is the cornerstone of the architecture and cannot be changed. It uses the proxy pattern to interact with the actual govDAO implementation. GovDAO implementations will be upgraded through Proposals.

gno.land/r/gov/dao/v3/memberstore

This realm manages member data. Anyone can read from it, but only the current govDAO (which the proxy package points to) can write to it. Rather than offering generic “GET” and “SET” methods, it provides specific methods tailored to the v3 use cases (for example, storing members by tier).

gno.land/r/gov/dao/v3/impl

This realm contains the actual govDAO v3 implementation and focuses solely on business logic. By storing members in separate packages, we enable future govDAO implementations to reuse and migrate data as needed.

Imports for Each Package

mermaid-diagram-2025-03-03-115520

Example flow diagram for creating a new Proposal

mermaid-diagram-2025-03-03-121255

Screenshots

page-1

page-2

votes-list-page

Code used to create the screenshots

func CreateTestProposals() {
	nm1 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj")

	portfolio := "### This is my portfolio:\n\n- THINGS"

	proxy.MustCreateProposal(NewAddMemberRequest(nm1, memberstore.T2, portfolio))

	proxy.MustCreateProposal(NewChangeLawRequest(law))

	removeAddr := std.Address("g1dfr24yhk5ztwtqn2a36m8f6ud8cx5hww4dkjfl")
	proxy.MustCreateProposal(NewWithdrawMemberRequest(removeAddr, "This is the removal reason text."))

	proxy.MustCreateProposal(blog.NewPostProposalRequest("slug", "Post Title", "Post Body", "01/03/2025", "AUTHOR ONE", "TAG1, TAG2"))

	proxy.MustCreateProposal(params.NewStringPropRequest("KEY", "VALUE"))

	proxy.MustCreateProposal(NewAddMemberRequest(nm1, memberstore.T2, portfolio))

	proxy.MustVoteOnProposal(proxy.VoteRequest{
		Option:     proxy.YesVote,
	ProposalID: proxy.ProposalID(1),
	})

	proxy.MustVoteOnProposal(proxy.VoteRequest{
		Option:     proxy.NoVote,
	ProposalID: proxy.ProposalID(2),
	})

	ExecuteProposal(proxy.ProposalID(2))
}

@github-actions github-actions bot added the 🧾 package/realm Tag used for new Realms or Packages. label Dec 20, 2024
@Gno2D2
Copy link
Collaborator

Gno2D2 commented Dec 20, 2024

🛠 PR Checks Summary

All Automated Checks passed. ✅

Manual Checks (for Reviewers):
  • IGNORE the bot requirements for this PR (force green CI check)
Read More

🤖 This bot helps streamline PR reviews by verifying automated checks and providing guidance for contributors and reviewers.

✅ Automated Checks (for Contributors):

🟢 Maintainers must be able to edit this pull request (more info)

☑️ Contributor Actions:
  1. Fix any issues flagged by automated checks.
  2. Follow the Contributor Checklist to ensure your PR is ready for review.
    • Add new tests, or document why they are unnecessary.
    • Provide clear examples/screenshots, if necessary.
    • Update documentation, if required.
    • Ensure no breaking changes, or include BREAKING CHANGE notes.
    • Link related issues/PRs, where applicable.
☑️ Reviewer Actions:
  1. Complete manual checks for the PR, including the guidelines and additional checks if applicable.
📚 Resources:
Debug
Automated Checks
Maintainers must be able to edit this pull request (more info)

If

🟢 Condition met
└── 🟢 And
    ├── 🟢 The base branch matches this pattern: ^master$
    └── 🟢 The pull request was created from a fork (head branch repo: ajnavarro/gno)

Then

🟢 Requirement satisfied
└── 🟢 Maintainer can modify this pull request

Manual Checks
**IGNORE** the bot requirements for this PR (force green CI check)

If

🟢 Condition met
└── 🟢 On every pull request

Can be checked by

  • Any user with comment edit permission

@ajnavarro ajnavarro changed the title feature: GovDAO implementation proposal. feat: GovDAO implementation proposal. Dec 20, 2024
@ajnavarro ajnavarro marked this pull request as draft December 20, 2024 15:31
@Kouteki Kouteki added this to the 🚀 Mainnet beta launch milestone Jan 7, 2025
@ajnavarro ajnavarro force-pushed the feature/govdao-v3 branch 2 times, most recently from 708314e to c3bce0c Compare February 14, 2025 17:36
Signed-off-by: Antonio Navarro Perez <[email protected]>
Signed-off-by: Antonio Navarro Perez <[email protected]>
Signed-off-by: Antonio Navarro Perez <[email protected]>
Signed-off-by: Antonio Navarro Perez <[email protected]>
Signed-off-by: Antonio Navarro Perez <[email protected]>
Signed-off-by: Antonio Navarro Perez <[email protected]>
Signed-off-by: Antonio Navarro Perez <[email protected]>
Signed-off-by: Antonio Navarro Perez <[email protected]>
@github-actions github-actions bot added the 📦 ⛰️ gno.land Issues or PRs gno.land package related label Feb 28, 2025
Copy link

codecov bot commented Feb 28, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

📢 Thoughts on this report? Let us know!

Signed-off-by: Antonio Navarro Perez <[email protected]>
@ajnavarro ajnavarro marked this pull request as ready for review March 3, 2025 12:23
@ajnavarro ajnavarro requested a review from leohhhn March 3, 2025 13:33
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this file should be called render.gno?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that file is the govdao implementation, the render method is at impl.gno

@Kouteki Kouteki linked an issue Mar 5, 2025 that may be closed by this pull request
@MikaelVallenet
Copy link
Member

MikaelVallenet commented Mar 5, 2025

Hello 👋

I worked on the subject and wrote down some things, here is some:

I think there is a mistake unless I misunderstand but a user needs 2 invitation points from different sources (or rather each source can only delegate a point to one person) to become a T3 and here I have the impression that only 1 is enough in your implementation from AddMember func in dao/v3/impl/impl.gno

"2 invitation points from 2 members must be delegated for T3 membership. Delegation/invitation can be withdrawn at any time." jae specs

I also find the implemtation of invitation points a bit confusing, like the AddMember function, which in reality only works for adding T3s, otherwise you have to go through a proposal, here's the implementation I made: https://github.com/TERITORI/teritori-dapp/blob/main/gno/r/govdao/invitations.gno

I also find it odd to have render logic on a file other than render.gno, as Léon indicates: #3389 (comment)

The proxy pattern is ingenious and I think it's a good solution.

I also see the user author type, which in the end isn't used? but the caller addr could be passed in the proxy request instead of what is done rn, which would eventually, depending on future choices, potentially allow realms (could be sub-daos) to be accepted as members, whereas we're using OriginCaller() and therefore necessarily an EOA.

Here's a link to our implementation using daokit, a framework we've developed to build dao based on complex conditions: https://github.com/TERITORI/teritori-dapp/tree/main/gno/r/govdao

Feel freel to answer and create a thread/conversation about my feedbacks ❤️

@Kouteki Kouteki requested a review from jeronimoalbi March 6, 2025 14:21
Comment on lines 13 to 19
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
)
Copy link
Member

Choose a reason for hiding this comment

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

I like this pattern of providing default values while allowing new implementations to extend or change them. However, I dislike using the proxy package and its prefix. One option is to create a dedicated package for types, which would require two imports. Alternatively, we could rename the proxy package to "govdao." This way, when you import govdao, you import "the component to communicate with govdao." The implementation can remain internal, so users don't need to worry about it or import it. In my opinion, it makes sense to consider the proxy as the front-facing package and name it govdao. We can specify in the comments that it follows the proxy pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Now the package is gno.land/r/gov/dao directly.

return proxy.ProposalRequest{
Title: "Member Withdrawn",
Description: "This prpoposal is looking to remove a member from the board.",
Callback: removeMemberCallback,
Copy link
Member

@moul moul Mar 7, 2025

Choose a reason for hiding this comment

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

what prevent me to do this:

proxy.NewWithdrawMemberRequest("g12345", "foo").Callback(proxy.RemoveMemberMetadata{Addr: "g12345"})

We should unexport Callback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Calls to member storage are protected, and can only be done by the actual govDAO implementation, so that specific call will fail. In any case, I'll make callback private to avoid confusion in other implementations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Callback was moved to private and added the ProposalRequestBuilder struct to generate the Proposal requests without being able to access the internal fields.

//TODO: get prev realm here and remove the author coming from the govDAO impl
author, err := dao.PreCreateProposal(r)
if err != nil {
return -1, err
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason for the first proposal to be ID 0, or is just for convenience?

Copy link
Contributor Author

@ajnavarro ajnavarro Mar 10, 2025

Choose a reason for hiding this comment

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

Convenience and just a programming standard.

}

// GetProposal gets created proposal by its ID
func GetProposal(pid ProposalID) (*Proposal, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't a call to this function or MustGetProposal() allow overwriting Proposal.Executor? For example:

MustGetProposal(2).Executor = nil

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. I'll address this problem making it private like Callback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved the executor to a private field. Because of that, the method to execute proposals has to be moved to the proxy realm.

Comment on lines 93 to 96
ipid := int(pid)
if len(proposals) <= ipid {
return nil, nil
}
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this one an error case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Instead of erroring, I wanted to return nil if there was no proposal.

Comment on lines 120 to 126
if r.AllowedDAOs != nil {
AllowedDAOs = r.AllowedDAOs
}

if r.DAO != nil {
dao = r.DAO
}
Copy link
Member

Choose a reason for hiding this comment

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

Maybe if it's the new realm implementation version the one making the update request, and after it's allowed, it could prepend itself to the list of AllowedDAOs to make sure that the last realm version is always allowed to update the implementation 🤔. It might help avoiding a deadlock, if I understand correctly.

Maybe it might even be worth considering having a RollbackImpl() feature that is independent of the allowed DAO list and UpdateImpl(UpdateRequest) mechanic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmm, interesting, I'll have a look.

Comment on lines 92 to 95
type Metadata interface {
IsMetadata()
String() string
}
Copy link
Member

@jeronimoalbi jeronimoalbi Mar 10, 2025

Choose a reason for hiding this comment

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

Also, an alternative could be something in a "similar" fashion as the error interface:

type Metadata interface {
  Metadata() string
}

If the smaller interface would make sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

String() string is implementing the Stringer interface: https://pkg.go.dev/fmt#Stringer

IsMetadata() is a flag intended to avoid sending any random object that is not specifically intended to be used as metadata.

So we need both sadly.

Comment on lines +17 to +27
type Member struct {
InvitationPoints int
}

func (m *Member) RemoveInvitationPoint() {
if m.InvitationPoints <= 0 {
panic("not enough invitation points")
}

m.InvitationPoints = m.InvitationPoints - 1
}
Copy link
Member

Choose a reason for hiding this comment

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

It seems that InvitationPoints property could be private to make sure only RemoveInvitationPoint() is used to change its value

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Might be an option, but doing that won't allow us to easily create Members from outside the memberstorage package. Also, I should add extra methods to get how many invitation points are available. Do you see any corner case where we must make that field private?

InvitationPoints: 2,
Payment: 90000,
MaxSize: func(membersByTier MembersByTier, tiersByName TiersByName) int {
return membersByTier.GetTierSize(T1) * 4
Copy link
Member

Choose a reason for hiding this comment

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

Isn't the max size size(T1) * 2, or the ratio changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed.

@moul
Copy link
Member

moul commented Mar 10, 2025

I'm initiating a new review round. There are conflicts with the master branch. Could you please merge the master branch? I expect this to be the last time.

@ajnavarro
Copy link
Contributor Author

ajnavarro commented Mar 10, 2025

Answering to @MikaelVallenet:

I think there is a mistake unless I misunderstand but a user needs 2 invitation points from different sources (or rather each source can only delegate a point to one person) to become a T3 and here I have the impression that only 1 is enough in your implementation from AddMember func in dao/v3/impl/impl.gno

"2 invitation points from 2 members must be delegated for T3 membership. Delegation/invitation can be withdrawn at any time." jae specs

Yes, you are right. I have to change that on the actual implementation. Still a TODO.

I also find the implemtation of invitation points a bit confusing, like the AddMember function, which in reality only works for adding T3s, otherwise you have to go through a proposal, here's the implementation I made: TERITORI/teritori-dapp@main/gno/r/govdao/invitations.gno

It is done that way to be able to migrate data to future govDAOs implementations as easy as possible. Implementing invitation points the way you did makes it impossible to get data from future DAOs and use it from there.

I also find it odd to have render logic on a file other than render.gno, as Léon indicates: #3389 (comment)

That struct implements all the business logic. Part of the business logic is the Render function, that will be used on the proxy realm directly. How do you suggest to implement it, taking into account that Render is just a small part of all the business logic implemented to fulfill all the needed methods from the govDAO interface?

I also see the user author type, which in the end isn't used? but the caller addr could be passed in the proxy request instead of what is done rn, which would eventually, depending on future choices, potentially allow realms (could be sub-daos) to be accepted as members, whereas we're using OriginCaller() and therefore necessarily an EOA.

Author interface is intended to make possible to implement any kind of Author in the future, like other DAOs. Right now it is just returning a Member Author. The information about who is the caller is gathered on the implementation itself, because it can change. If we send the address as part of the function contract, we are limiting the future functionalities that are possible to implement eventually, without giving any new information that couldn't be gathered by other methods like the ones living in std.

Comment on lines +90 to +92
if member.InvitationPoints <= 0 {
panic("proposer does not have enough invitation points for inviting new people to the board")
}
Copy link
Member

Choose a reason for hiding this comment

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

Should invitation points also be considered when adding a T1 or T2 member?

Copy link
Contributor

Choose a reason for hiding this comment

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

Same question, but I don't get it either when I read the specs directly.

Copy link
Contributor Author

@ajnavarro ajnavarro Mar 12, 2025

Choose a reason for hiding this comment

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

They are being used when the Proposal is executed.

Comment on lines 39 to 46
// v3 that is in charge adds v5 in charge
std.TestSetRealm(std.NewCodeRealm(v3))
urequire.NotPanics(t, func() {
UpdateImpl(UpdateRequest{
DAO: &dummyDao{},
AllowedDAOs: []string{v3},
})
})
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like you're adding v3, not v5.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Comment on lines 48 to 54
// v3 can still do updates
std.TestSetRealm(std.NewCodeRealm(v3))
urequire.NotPanics(t, func() {
UpdateImpl(UpdateRequest{
AllowedDAOs: []string{v4},
})
})
Copy link
Contributor

@aeddi aeddi Mar 10, 2025

Choose a reason for hiding this comment

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

Noob question: does std.TestSetRealm(std.NewCodeRealm(v3)) reset the state of the Realm?
If yes: shouldn't this test try to update AllowedDAOs two times?
If no: why all these occurence of std.TestSetRealm(std.NewCodeRealm(v3)) if they're not resetting the state or something else?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One of the annoying bugs existing right now is that the state does not change between tests. I'm changing the caller just to be sure that everything will work on different use cases.


// UpdateImpl is a method intended to be used on a proposal.
// This method will update the current govDAO implementation
// to a new one. newDAOUpdaters are a list of realms that can
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the comment outdated? I don't see any trace of newDAOUpdaters

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed.

Comment on lines 120 to 126
if r.AllowedDAOs != nil {
AllowedDAOs = r.AllowedDAOs
}

if r.DAO != nil {
dao = r.DAO
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you comment / provide more details on why these two attributes are optional? Naively, I would have said that they cannot be nil at the risk of breaking the system. It could be useful to clarify.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is done that way to allow updating the list of allowed DAOs or the DAO implementation separately if needed.

So after a DAO update, you can eventually remove from AllowedDAOs through a proposal the previous DAO implementation (when everything seems to be working properly).

Comment on lines 80 to 82
if v.Tier == memberstore.T1 {

}
Copy link
Contributor

Choose a reason for hiding this comment

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

Still WIP or leftover?

Copy link
Member

Choose a reason for hiding this comment

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

Same question :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Removed. I thought that for removing a T1 member there was a special case, but it is not.

Comment on lines 164 to 194
func promoteMemberCallback(m proxy.Metadata) error {
pmm, ok := m.(*PromoteMemberMetadata)
if !ok {
panic("metadata type for promoting a member is wrong")
}

prevTier := memberstore.Get().RemoveMember(pmm.Addr)
if prevTier == "" {
panic("member not found, so cannot be promoted")
}

if prevTier != pmm.FromTier {
panic("previous tier changed from the one indicated in the proposal")
}

return memberstore.Get().SetMember(pmm.ToTier, pmm.Addr, pmm.Member)
}

func NewPromoteMemberRequest(addr std.Address, fromTier string, toTier string) proxy.ProposalRequest {
return proxy.ProposalRequest{
Title: "Member Promotion",
Description: "This prpoposal is looking to promote a member to an upper tier.",
Callback: promoteMemberCallback,
Metadata: &PromoteMemberMetadata{
Addr: addr,
FromTier: fromTier,
ToTier: toTier,
Member: memberByTier(toTier),
},
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we not check if toTier > fromTier?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

there is no correlation between tiers, they are just keys. So technically you can demote too.

### 2.1 Tier Definitions
- [X] **T1 (Core Tier)**
- [X] Highest tier; self-selecting membership with a *supermajority* vote from T1.
- [X] Membership can only be withdrawn by supermajority vote *with cause*.
Copy link
Contributor

Choose a reason for hiding this comment

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

withdrawn by supermajority vote from T1?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

AFAIK for all of members, right? Ping me if I misunderstood something.

- [X] Membership can only be withdrawn by supermajority vote *with cause*.
- [X] **T2**
- [X] Selected by GovDAO with T3 abstaining, requiring a *simple majority* vote.
- [X] Membership can be withdrawn for any reason.
Copy link
Contributor

Choose a reason for hiding this comment

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

Membership can be withdrawn with T3 abstaining?

- [X] Selected by GovDAO with T3 abstaining, requiring a *simple majority* vote.
- [X] Membership can be withdrawn for any reason.
- [X] **T3 (General Tier)**
- [X] Lowest tier; *permissionless invitation* from T1 and T2 members.
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the meaning of permissionless in this context? T1, T2 and T3 all use their invitation points so what's the difference between T1/T2 and T3?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

AFAIK, adding them without asking other members, a.k.a outside a proposal. That's why there is a specific method to add T3 members using your invitation points on govdao/v3 implementation.

InvitationPoints: 3,
Payment: 90000,
MinSize: func(membersByTier MembersByTier, tiersByName TiersByName) int {
return 70
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it planned to implement the "time based requirements"? I mean at least 70 after 7 years, 2 new T1 members per quarter, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think so, but I didn't find a good way of implementing that programmatically.

Copy link
Member

@zivkovicmilos zivkovicmilos left a comment

Choose a reason for hiding this comment

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

It's finally happening, isn't it? :)

Amazing work 👏

var AllowedDAOs []string

// proposals contains all the proposals in history.
var proposals []*Proposal
Copy link
Member

Choose a reason for hiding this comment

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

Just curious, why a slice and not an AVL?

Copy link
Member

Choose a reason for hiding this comment

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

shouldn't be a slice; a p/moul/ulist or avl.Tree + seqid seems better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changing that to an avl tree

// CreateProposal will try to create a new proposal, that will be validated by the actual
// govDAO implementation. If the proposal cannot be created, an error will be returned.
func CreateProposal(r ProposalRequest) (ProposalID, error) {
//TODO: get prev realm here and remove the author coming from the govDAO impl
Copy link
Member

Choose a reason for hiding this comment

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

Leftover?


pid := ProposalID(len(proposals))

proposals = append(proposals, p)
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to delete a proposal?

Copy link
Member

Choose a reason for hiding this comment

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

no, we can eventually mark them as cancelled, but shouldn't delete them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

proposals will be there forever, no deletion. The dao implementation will decide witch ones to show, and the state of them.


// GetProposal gets created proposal by its ID
func GetProposal(pid ProposalID) (*Proposal, error) {
if err := dao.PreGetProposal(pid); err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

What's the idea behind PreGetProposal?

Copy link
Member

Choose a reason for hiding this comment

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

And of course PostGetProposal?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Give to the implementation the ability of do any business logic before returning the proposal to the user, for example, only allow x requests per member, or limit who is able to read the proposals, or charge a fee for getting old proposals are a few of the possible implementations here.

PostGetProposal is more or less the same, but now you have some information about the Proposal itself, so you can filter or do some logic depending on that information. For example, have an historic about who queried the proposals.

return index(pkg)
}

func index(pkg string) int {
Copy link
Member

Choose a reason for hiding this comment

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

The most revolutionary search method :)


// MembersByTier contains all `Member`s indexed by their Address.
type MembersByTier struct {
*avl.Tree // *avl.Tree[string]*avl.Tree[std.Address]*Member
Copy link
Member

Choose a reason for hiding this comment

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

I would probably change this comment to:
tier name -> address -> member

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -0,0 +1,172 @@
package memberstore
Copy link
Member

Choose a reason for hiding this comment

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

Do we test this functionality out somewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, at memberstore_test.gno

BasePower float64

// Payment defines what will be the amount being paid to Members on different tiers. The payment itself will be good, but not exceed 90th percentile of senior software architect roles in the highest paid city globally.
Payment int // TODO: implement payment.
Copy link
Member

Choose a reason for hiding this comment

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

Leftover?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes and no, we need to implement a TreasuryDAO or similar to be able to pay Members

return govDAO.Render(in)
}

// AddMember allows T1 and T2 members to freely add T3 members using their invitation points.
Copy link
Member

Choose a reason for hiding this comment

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

Just a note, it's a bit confusing to have proposals for T1 / T2, but have a public func for T3 additions. Maybe they should all go through a proposal, but T1 / T2 can only initiate it through invitation points (that get deducted after the proposal is resolved)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From the specs, T3 member invitations must be permisionless. If we do them through a proposal, then there are not permisionless, right? maybe I misunderstood the specs.

@@ -0,0 +1,71 @@
package impl
Copy link
Member

Choose a reason for hiding this comment

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

I would like to see more tests for this functionality. I figured you were holding off on them until the implementation settled

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Totally, more tests are needed in general. Waiting to write them until all the nits, name changes, and the base implementation is settled as you said.

Signed-off-by: Antonio Navarro Perez <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
📦 ⛰️ gno.land Issues or PRs gno.land package related 🧾 package/realm Tag used for new Realms or Packages.
Projects
Status: 🎯 Current Topics
Status: In Review
Development

Successfully merging this pull request may close these issues.

GovDAO V3
9 participants