Skip to content

Commit

Permalink
feat: govdao mvp improvement (#2344)
Browse files Browse the repository at this point in the history
Continues #1945

---------

Signed-off-by: moul <[email protected]>
  • Loading branch information
moul authored Jun 17, 2024
1 parent e7e47d2 commit c5b999f
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 37 deletions.
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?

// 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() Status {
switch {
case exec.success:
return Success
case exec.done:
return Failed
default:
return NotExecuted
}
}

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

// ErrAlreadyDone is the error returned when trying to execute an already
// executed proposal.
var ErrAlreadyDone = errors.New("already executed")

// Status enum.
type Status string

var (
NotExecuted Status = "not_executed"
Success Status = "success"
Failed Status = "failed"
)
124 changes: 98 additions & 26 deletions examples/gno.land/r/gov/dao/dao.gno
Original file line number Diff line number Diff line change
Expand Up @@ -2,64 +2,136 @@ package govdao

import (
"std"
"strconv"

"gno.land/p/gov/proposal"
"gno.land/p/demo/ufmt"
pproposal "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.
type Proposal struct {
author std.Address
type proposal struct {
idx int
Comment string
Executor proposal.Executor
author std.Address
comment string
executor pproposal.Executor
// XXX: make "accepted" and "finished" managed by an interface that can have various voting implementations
accepted bool
finished bool
}

func (p proposal) Status() Status {
if p.executor.Done() {
return Status(p.executor.Status())
}
if p.accepted {
return Accepted
}
// 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(comment string, executor pproposal.Executor) int {
// XXX: require payment?
// XXX: sanitize proposal
if executor == nil {
panic("missing proposal executor")
}
caller := std.PrevRealm().Addr()
AssertIsMember(caller)
proposal.author = caller
proposal.idx = len(proposals)
proposals = append(proposals, proposal)
return proposal.idx

prop := &proposal{
comment: comment,
executor: executor,
author: caller,
idx: len(proposals),
}

proposals = append(proposals, prop)
return prop.idx
}

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

if !proposalExists(idx) {
return "404"
}
prop := getProposal(idx)
output := ""
output += ufmt.Sprintf("# Prop#%d", prop.idx) + "\n"
output += "\n"
output += prop.comment
output += "\n"
output += ufmt.Sprintf("Status: %s", string(prop.Status()))
output += "\n"
output += ufmt.Sprintf("Author: %s", string(prop.author))
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
)
10 changes: 10 additions & 0 deletions examples/gno.land/r/gov/dao/types.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package govdao

// Status enum.
type Status string

var (
Accepted Status = "accepted"
Active Status = "active"
// Timeout, NotAccepted
)
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 tests the govdao ecosystem from an external perspective.
// It aims to confirm that the system can remain static while supporting
// additional DAO use cases 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)
// --
// # Prop#0
//
// manual valset changes proposal example
// Status: active
// Author: g1tk0fscr3p5g8hnhxq6v93jxcpm5g3cstlxfxa3
// --
// --
// # Prop#0
//
// manual valset changes proposal example
// Status: accepted
// Author: g1tk0fscr3p5g8hnhxq6v93jxcpm5g3cstlxfxa3
// --
// --
// # Prop#0
//
// manual valset changes proposal example
// Status: success
// Author: g1tk0fscr3p5g8hnhxq6v93jxcpm5g3cstlxfxa3
9 changes: 3 additions & 6 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,10 @@ 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)
comment := "manual valset changes proposal example"
govdao.Propose(comment, executor)
}
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

0 comments on commit c5b999f

Please sign in to comment.