package dao
import (
"chain"
"chain/runtime"
"errors"
"strconv"
"gno.land/p/nt/ufmt/v0"
)
// dao is the actual govDAO implementation, having all the needed business logic
var dao DAO
// allowedDAOs contains realms that can be used to update the actual govDAO implementation,
// and validate Proposals.
// This is like that to be able to rollback using a previous govDAO implementation in case
// the latest implementation has a breaking bug. After a test period, a proposal can be
// executed to remove all previous govDAOs implementations and leave the last one.
var allowedDAOs []string
// proposals contains all the proposals in history.
var proposals *Proposals = NewProposals()
// Remember this realm for rendering.
var gRealm = runtime.CurrentRealm()
// Render calls directly to Render's DAO implementation.
// This allows to have this realm as the main entry point for everything.
func Render(p string) string {
if dao == nil {
return "DAO not initialized"
}
return dao.Render(gRealm.PkgPath(), p)
}
// MustCreateProposal is an utility method that does the same as CreateProposal,
// but instead of erroing if something happens, it panics.
func MustCreateProposal(cur realm, r ProposalRequest) ProposalID {
pid, err := CreateProposal(cur, r)
if err != nil {
panic(err.Error())
}
return pid
}
// ExecuteProposal will try to execute the proposal with the provided ProposalID.
// If the proposal was denied, it will return false. If the proposal is correctly
// executed, it will return true. If something happens this function will panic.
func ExecuteProposal(cur realm, pid ProposalID) bool {
if dao == nil {
return false
}
execute, err := dao.PreExecuteProposal(pid)
if err != nil {
panic(err.Error())
}
if !execute {
return false
}
prop, err := GetProposal(cur, pid)
if err != nil {
panic(err.Error())
}
if err := prop.executor.Execute(cross); err != nil {
panic(err.Error())
}
return true
}
// 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(cur realm, r ProposalRequest) (ProposalID, error) {
if dao == nil {
return -1, errors.New("DAO not initialized")
}
author, err := dao.PreCreateProposal(r)
if err != nil {
return -1, err
}
p := &Proposal{
author: author,
title: r.title,
description: r.description,
executor: r.executor,
allowedDAOs: allowedDAOs[:],
}
pid := proposals.SetProposal(p)
dao.PostCreateProposal(r, pid)
chain.Emit("ProposalCreated",
"id", strconv.FormatInt(int64(pid), 10),
)
return pid, nil
}
func MustVoteOnProposal(cur realm, r VoteRequest) {
if err := VoteOnProposal(cur, r); err != nil {
panic(err.Error())
}
}
// VoteOnProposal sends a vote to the actual govDAO implementation.
// If the voter cannot vote the specified proposal, this method will return an error
// with the explanation of why.
func VoteOnProposal(cur realm, r VoteRequest) error {
if dao == nil {
return errors.New("DAO not initialized")
}
return dao.VoteOnProposal(r)
}
// MustVoteOnProposalSimple is like MustVoteOnProposal but intended to be used through gnokey with basic types.
func MustVoteOnProposalSimple(cur realm, pid int64, option string) {
MustVoteOnProposal(cur, VoteRequest{
Option: VoteOption(option),
ProposalID: ProposalID(pid),
})
}
func MustGetProposal(cur realm, pid ProposalID) *Proposal {
p, err := GetProposal(cur, pid)
if err != nil {
panic(err.Error())
}
return p
}
// GetProposal gets created proposal by its ID
func GetProposal(cur realm, pid ProposalID) (*Proposal, error) {
if dao == nil {
return nil, errors.New("DAO not initialized")
}
if err := dao.PreGetProposal(pid); err != nil {
return nil, err
}
prop := proposals.GetProposal(pid)
if prop == nil {
return nil, errors.New(ufmt.Sprintf("Proposal %v does not exist.", int64(pid)))
}
if err := dao.PostGetProposal(pid, prop); err != nil {
return nil, err
}
return prop, nil
}
// UpdateImpl is a method intended to be used on a proposal.
// This method will update the current govDAO implementation
// to a new one. AllowedDAOs are a list of realms that can
// call this method, in case the new DAO implementation had
// a breaking bug. Any value set as nil will be ignored.
// If AllowedDAOs field is not set correctly, the actual DAO
// implementation wont be able to execute new Proposals!
func UpdateImpl(cur realm, r UpdateRequest) {
gRealm := runtime.PreviousRealm().PkgPath()
if !InAllowedDAOs(gRealm) {
panic("permission denied for prev realm: " + gRealm)
}
if r.AllowedDAOs != nil {
allowedDAOs = r.AllowedDAOs
}
if r.DAO != nil {
dao = r.DAO
}
}
func AllowedDAOs() []string {
dup := make([]string, len(allowedDAOs))
copy(dup, allowedDAOs)
return dup
}
func InAllowedDAOs(pkg string) bool {
if len(allowedDAOs) == 0 {
return true // corner case for initialization
}
for _, d := range allowedDAOs {
if pkg == d {
return true
}
}
return false
}