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 mvp improvement #2344

Merged
merged 8 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
24 changes: 23 additions & 1 deletion examples/gno.land/p/gov/proposal/proposal.gno
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Package proposal provides a structure for executing proposals.
package proposal

import "std"

const daoPkgPath = "gno.land/r/gov/dao" // XXX: make it configurable with r/sys/vars?
moul marked this conversation as resolved.
Show resolved Hide resolved

// NewExecutor creates a new executor with the provided callback function.
func NewExecutor(callback func() error) Executor {
return &executorImpl{
Expand All @@ -21,7 +25,7 @@ func (exec *executorImpl) Execute() error {
if exec.done {
return ErrAlreadyDone
}
// XXX: assertCalledByGovdao
assertCalledByGovdao()
err := exec.callback()
exec.done = true
exec.success = err == nil
Expand All @@ -37,3 +41,21 @@ func (exec *executorImpl) Done() bool {
func (exec *executorImpl) Success() bool {
return exec.success
}

func (exec executorImpl) Status() string {
switch {
case exec.success:
return "success"
case exec.done:
return "failed"
default:
return "not_executed"
}
}

func assertCalledByGovdao() {
caller := std.CurrentRealm().PkgPath()
if caller != daoPkgPath {
panic("only gov/dao can execute proposals")
}
}
3 changes: 2 additions & 1 deletion examples/gno.land/p/gov/proposal/types.gno
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import "errors"
type Executor interface {
Execute() error
Done() bool
Success() bool // Done() && !err
Success() bool // Done() && !err
Status() string // human-readable execution status (not_executed, success, failed)
}

// ErrAlreadyDone is the error returned when trying to execute an already
Expand Down
96 changes: 80 additions & 16 deletions examples/gno.land/r/gov/dao/dao.gno
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,44 @@ package govdao

import (
"std"
"strconv"

"gno.land/p/demo/ufmt"
"gno.land/p/gov/proposal"
)

var proposals = make([]Proposal, 0)
var proposals = make([]*Proposal, 0)

// XXX var members ...

// Proposal represents a proposal in the governance system.
// XXX: make it an interface with various proposal implementations
type Proposal struct {
author std.Address
idx int
Comment string
Executor proposal.Executor
accepted bool
finished bool
}

func (p Proposal) Status() string {
if p.Executor.Done() {
return p.Executor.Status()
}
if p.accepted {
return "accepted"
moul marked this conversation as resolved.
Show resolved Hide resolved
}
// XXX: timeout
// XXX: not_accepted
return "active"
}

// Propose is designed to be called by another contract or with
// `maketx run`, not by a `maketx call`.
func Propose(proposal Proposal) int {
func Propose(proposal *Proposal) int {
// XXX: require payment?
// XXX: sanitize proposal
// XXX: sanitize proposal or make dedicated constructor which is not the proposal itself
caller := std.PrevRealm().Addr()
AssertIsMember(caller)
proposal.author = caller
Expand All @@ -32,34 +49,81 @@ func Propose(proposal Proposal) int {
}

func VoteOnProposal(idx int, option string) {
assertProposalExists(idx)
caller := std.PrevRealm().Addr()
AssertIsMember(caller)
panic("not implemented")
// XXX: implement the voting (woudl be cool to have a generic p/)

prop := getProposal(idx)
if prop.finished {
panic("prop is not active anymore, cannot vote.")
}
// XXX: implement the real voting (would be cool to have a generic p/)
prop.accepted = option == "YES"
prop.finished = true
}

func ExecuteProposal(idx int) {
assertProposalExists(idx)
// XXX: assert voting is finished
// XXX: assert voting result is YES
// XXX: proposal was not already executed
proposal := proposals[idx]
proposal.Executor.Execute()
}

func assertProposalExists(idx int) {
if idx < 0 || idx >= len(proposals) {
panic("invalid proposal id")
prop := getProposal(idx)
if !prop.finished {
panic("prop is still active, cannot execute.")
}
if !prop.accepted {
panic("prop is not accepted, cannot execute.")
}
prop.Executor.Execute()
}

func IsMember(addr std.Address) bool {
// XXX: implement
return true
return true // in the meantime, everyone is a DAO member
}

func AssertIsMember(addr std.Address) {
if !IsMember(addr) {
panic("caller is not member of govdao")
}
}

func Render(path string) string {
if path == "" {
output := ""
for idx, prop := range proposals {
output += ufmt.Sprintf("- [/r/gov/dao:%d](%d) - %s (by %s)", idx, idx, prop.Comment, prop.author)
}
return output
}

// else display the proposal
idx, err := strconv.Atoi(path)
if err != nil {
return "404"
}

println("AAA", idx)
moul marked this conversation as resolved.
Show resolved Hide resolved
if !proposalExists(idx) {
return "404"
}
prop := getProposal(idx)
output := ""
output += ufmt.Sprintf("# Prop#%d", idx) + "\n"
output += "\n"
output += prop.Comment
output += "\n"
output += ufmt.Sprintf("Status: %s", prop.Status())
return output
}

func getProposal(idx int) *Proposal {
return proposals[idx-1]
}

func proposalExists(idx int) bool {
return idx > 0 && idx <= len(proposals)
}

func assertProposalExists(idx int) {
if !proposalExists(idx) {
panic("invalid proposal id")
}
}
5 changes: 4 additions & 1 deletion examples/gno.land/r/gov/dao/gno.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
module gno.land/r/gov/dao

require gno.land/p/gov/proposal v0.0.0-latest
require (
gno.land/p/demo/ufmt v0.0.0-latest
gno.land/p/gov/proposal v0.0.0-latest
)
3 changes: 3 additions & 0 deletions examples/gno.land/r/gov/integration/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Draft

module integration
4 changes: 4 additions & 0 deletions examples/gno.land/r/gov/integration/integration.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Package integration is used to test the govdao ecosystem from an external point
// of view to confirm that it can be made static while allowing to support more
// DAO usages over time.
package integration
45 changes: 45 additions & 0 deletions examples/gno.land/r/gov/integration/z_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package main

import (
govdao "gno.land/r/gov/dao"
_ "gno.land/r/gov/proposals/prop1"
)

func main() {
println("--")
println(govdao.Render(""))
println("--")
println(govdao.Render("1"))
println("--")
govdao.VoteOnProposal(1, "YES")
println("--")
println(govdao.Render("1"))
println("--")
govdao.ExecuteProposal(1)
println("--")
println(govdao.Render("1"))
}

// Output:
// --
// - [/r/gov/dao:0](0) - manual valset changes proposal example (by g1tk0fscr3p5g8hnhxq6v93jxcpm5g3cstlxfxa3)
// --
// AAA 1
// # Prop#1
//
// manual valset changes proposal example
// Status: active
// --
// --
// AAA 1
// # Prop#1
//
// manual valset changes proposal example
// Status: accepted
// --
// --
// AAA 1
// # Prop#1
//
// manual valset changes proposal example
// Status: success
4 changes: 2 additions & 2 deletions examples/gno.land/r/gov/proposals/prop1/prop1.gno
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ func init() {

// Wraps changesFn to emit a certified event only if executed from a
// complete governance proposal process.
executor := validators.NewProposalExecutor(changesFn)
executor := validators.NewPropExecutor(changesFn)

// Create a proposal.
// XXX: payment
proposal := govdao.Proposal{
Comment: "manual valset changes proposal example",
Executor: executor,
}
govdao.Propose(proposal)
govdao.Propose(&proposal)
}
4 changes: 2 additions & 2 deletions examples/gno.land/r/sys/validators/validators.gno
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ type Change struct {
Power int
}

// NewProposalExecutor creates a new executor that wraps a changes closure
// NewPropExecutor creates a new executor that wraps a changes closure
// proposal. It emits a typed object (subscribed by tm2) only if it passes
// through a complete p/gov/proposal process.
func NewProposalExecutor(changesFn func() []Change) proposal.Executor {
func NewPropExecutor(changesFn func() []Change) proposal.Executor {
if changesFn == nil {
panic("changesFn should not be nil")
}
Expand Down
Loading