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

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions examples/gno.land/r/gov/dao/propstore/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/r/gov/dao/propstore
82 changes: 82 additions & 0 deletions examples/gno.land/r/gov/dao/propstore/propstore.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package propstore

import (
"std"

"gno.land/r/gov/dao/ruler"
)
var proposals []*Proposal

var callback DAOCallback

func init(){
// TODO: set current DAOCallback impl
}

// MustNewProposal is like NewProposal but it panics if the proposal cannot be added.
func MustNewProposal(title string, desc string, callback func(Metadata) error, meta Metadata) int {
id ,err := NewProposal(title, desc, callback, meta)
if err != nil {
panic(err)
}

return id
}

// NewProposal creates a new proposal with the specified information.
// The provided callback will be executed when the proposal is validated by GovDAO.
// The actual govDAO implementation will be in charge of validating that the proposer
// is able to create the proposal or not. Metadata object is an arbitrary object that
// will be sent to GovDAO for validation purposes and also it will be sent to the callback function.
// That metadata object can be used to give extra information to the callback function if needed, or even give
// some metadata information to the actual GovDAO in charge for validation purposes.
func NewProposal(title string, desc string, callback func(Metadata) error, meta Metadata) (int, error) {
caller := std.GetOrigCaller()

if err := callback.BeforeNewProposal(caller,title, meta); err != nil {
return -1, err
}

p := &Proposal{
Author: caller,
Title: title,
Description: desc,
Executor: newExecutor(callback, meta),
RulerOnCreation: ruler.CurrentDAO,
}

pid = len(proposals)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

pid should be a specific type instead of an int

proposals = append(proposals, p)
callback.AfterNewProposal(caller,title, meta, pid)

return pid, nil
}

// GetProposal gets a proposal by ID
func GetProposal(id int) *Proposal {
if id > len(proposals)-1 {
return nil
}

return proposals[id]
}

// SetCallback gives to the actual GovDAO in charge the permissions
// to set a DAOCallback implementation, giving information about when
// a Proposal is created, giving the opportunity to GovDAO to validate
// Proposal creations.
func SetCallback(cb DAOCallback) {
if !checkCurrent(){
panic("this method cann only be called by current GovDAO")
}

callback = cb
}

func caller() string {
return std.PrevRealm().PkgPath()
}

func checkCurrent() bool {
return caller() == ruler.CurrentDAO
}
88 changes: 88 additions & 0 deletions examples/gno.land/r/gov/dao/propstore/types.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package propstore

import (
"errors"
"std"

"gno.land/r/gov/dao/ruler"
)

type DAOCallback interface {
// BeforeNewProposal is called before a Proposal is created, allowing to the actual
// GovDAO implementation the opportunity of validate it.
BeforeNewProposal(caller std.Address,title string, meta Metadata) error

// AfterNewProposal is called after the creation of a new Proposal.
// It gives information about the proposal ID, so it can be paired with
// the GovDAO proposal voting status, also allowing future queries to get
// the Proposal object.
AfterNewProposal(caller std.Address,title string, meta Metadata, pid int)
}

type Proposal struct {
Author std.Address

Title string
Description string

Executor *executor

RulerOnCreation string
}

// TODO it should depends on the GovDAO
// func (p *Proposal) String() string {
// return ufmt.Sprintf(`
// Title: %s

// Proposed by: %s

// Proposed by GovDAO: %s

// %s

// This proposal contains the following metadata:

// %s`, p.Title, p.Author, p.RulerOnCreation, p.Description, p.Executor.String())
// }

// Executor is in charge of execute a proposal when it is approved by the members.
type executor struct {
metadata Metadata
callbackFn func(m Metadata) error
}

func (e *executor) String() string {
if e.metadata == nil {
return "No metadata available."
}

return e.metadata.String()
}

func newExecutor(fn func(m Metadata) error, metadata Metadata) *executor {
return &Executor{
metadata: metadata,
callbackFn: securityWrapper(fn),
}
}

func securityWrapper(fn func(m Metadata) error) func(m Metadata) error {
return func (m Metadata) error {
if std.PrevRealm().PkgPath() != ruler.CurrentDAO {
return ErrInvalidExecutorCaller
}

return fn(m)
}
}

var ErrInvalidExecutorCaller error = errors.New("the realm calling the proposal executor was not actually in charge")

// Metadata contains any kind of information that will be stored on the executor
// and will be sent to the callback function when the proposal is validated.
// It can contains any kind of data.
type Metadata interface {
IsMetadata()
String() string
}
1 change: 1 addition & 0 deletions examples/gno.land/r/gov/dao/ruler/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/r/gov/dao/ruler
101 changes: 101 additions & 0 deletions examples/gno.land/r/gov/dao/ruler/ruler.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package ruler

import "std"

var PrevDAOs []string
var DeletedDAOs []string
var CurrentDAO string

func init() {
CurrentDAO = "gno.land/r/gov/dao/v3"
}

// Render outputs a human readable state of the ruler
func Render(string) string {
out := ""
out += "Current GovDAO in charge: " + CurrentDAO + "\n\n"
out += "Previous GovDAO implementations in charge: \n"
for _, d := range PrevDAOs {
out += "- " + d +"\n"
}

out += "\nPrevious deleted GovDAOs due to security issues or similar: \n"
for _, d := range DeletedDAOs {
out += "- " + d +"\n"
}

return out
}

// Rollback allows to rollback to a previous validated package in case the actual one has a breaking bug.
// Any current or actual package is allowed to call Rollback function. If some of previous GovDAOs are insecure,
// we recommend to call Remove method to avoid letting it rollback to itself.
func Rollback(pkg string) {
if !checkCurrent() && !checkPrev() {
panic("package " + pkg + " cannot be rollback by " + caller())
}

idx := has(pkg)
if idx < 0 {
panic("package " + pkg + " does not exists, so it cannot be rollback")
}

CurrentDAO = pkg
remove(idx)
}

// New changes who is the current DAO in charge. it can be called only by the actual DAO in charge only.
func New(pkg string) {
if !checkCurrent() {
panic("package " + pkg + " cannot be added by " + caller())
}

PrevDAOs = append(PrevDAOs, CurrentDAO)
CurrentDAO = pkg
}

// Remove deletes previous DAOs that can contain security issues. That will avoid calls to rollback from them.
func Remove(pkg string) {
if !checkCurrent() {
panic("package " + pkg + " cannot be removed by " + caller())
}

idx := has(pkg)
if idx < 0 {
panic("package " + pkg + " does not exists, so it cannot be rollback")
}

DeletedDAOs = append(DeletedDAOs, pkg)

remove(idx)
}

func caller() string {
return std.PrevRealm().PkgPath()
}

func checkCurrent() bool {
return caller() == CurrentDAO
}

func checkPrev() bool {
return has(caller()) >= 0
}

func has(pkg string) int {
for i, d := range PrevDAOs {
if pkg == d {
return i
}
}

return -1
}

func remove(i int) {
if i < 0 {
return
}

PrevDAOs = append(PrevDAOs[:i], PrevDAOs[i+1:]...)
}
103 changes: 103 additions & 0 deletions examples/gno.land/r/gov/dao/ruler/ruler_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package ruler

import (
"std"
"testing"

"gno.land/p/demo/urequire"
)

const v3 = "gno.land/r/gov/dao/v3"
const v4 = "gno.land/r/gov/dao/v4"
const v5 = "gno.land/r/gov/dao/v5"
const v6 = "gno.land/r/gov/dao/v6"

const invalid = "gno.land/r/invalid/dao"

func TestBridge_Functions(t *testing.T) {

// invalid package cannot add a new dao in charge
std.TestSetRealm(std.NewCodeRealm(invalid))
urequire.PanicsWithMessage(t, "package gno.land/r/gov/dao/v4 cannot be added by gno.land/r/invalid/dao", func() {
New(v4)
})


// dao in charge can add a new dao
std.TestSetRealm(std.NewCodeRealm(v3))
urequire.NotPanics(t, func() {
New(v4)
})


// v4 that is in charge adds v5 in charge
std.TestSetRealm(std.NewCodeRealm(v4))
urequire.NotPanics(t, func() {
New(v5)
})

// v3 is not in charge anymore even if it was
std.TestSetRealm(std.NewCodeRealm(v3))
urequire.PanicsWithMessage(t, "package gno.land/r/gov/dao/v5 cannot be added by gno.land/r/gov/dao/v3", func() {
New(v5)
})

urequire.Equal(
t,
`Current GovDAO in charge: gno.land/r/gov/dao/v5

Previous GovDAO implementations in charge:
- gno.land/r/gov/dao/v3
- gno.land/r/gov/dao/v4

Previous deleted GovDAOs due to security issues or similar:
`,
Render(""),
)

// v3 can rollback because it is a previous govDAO
std.TestSetRealm(std.NewCodeRealm(v3))
urequire.NotPanics(t, func() {
Rollback(v4)
})

urequire.Equal(
t,
`Current GovDAO in charge: gno.land/r/gov/dao/v4

Previous GovDAO implementations in charge:
- gno.land/r/gov/dao/v3

Previous deleted GovDAOs due to security issues or similar:
`,
Render(""),
)

std.TestSetRealm(std.NewCodeRealm(v3))
urequire.PanicsWithMessage(t, "package gno.land/r/gov/dao/v6 cannot be added by gno.land/r/gov/dao/v3", func() {
New(v6)
})

std.TestSetRealm(std.NewCodeRealm(v4))
urequire.NotPanics(t, func() {
New(v6)
})

std.TestSetRealm(std.NewCodeRealm(v6))
urequire.NotPanics(t, func() {
Remove(v3)
})

urequire.Equal(
t,
`Current GovDAO in charge: gno.land/r/gov/dao/v6

Previous GovDAO implementations in charge:
- gno.land/r/gov/dao/v4

Previous deleted GovDAOs due to security issues or similar:
- gno.land/r/gov/dao/v3
`,
Render(""),
)
}
Loading
Loading