-
Notifications
You must be signed in to change notification settings - Fork 389
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
ajnavarro
wants to merge
4
commits into
gnolang:master
Choose a base branch
from
ajnavarro:feature/govdao-v3
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,388
−0
Draft
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module gno.land/r/gov/dao/propstore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module gno.land/r/gov/dao/ruler |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:]...) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(""), | ||
) | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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