Search Apps Documentation Source Content File Folder Download Copy Actions Download

proxy.gno

5.74 Kb · 206 lines
  1package dao
  2
  3import (
  4	"chain"
  5	"errors"
  6	"strconv"
  7
  8	"gno.land/p/nt/ufmt/v0"
  9)
 10
 11// dao is the actual govDAO implementation, having all the needed business logic
 12var dao DAO
 13
 14// allowedDAOs contains realms that can be used to update the actual govDAO implementation,
 15// and validate Proposals.
 16// This is like that to be able to rollback using a previous govDAO implementation in case
 17// the latest implementation has a breaking bug. After a test period, a proposal can be
 18// executed to remove all previous govDAOs implementations and leave the last one.
 19var allowedDAOs []string
 20
 21// proposals contains all the proposals in history.
 22var proposals *Proposals = NewProposals()
 23
 24// Render calls directly to Render's DAO implementation.
 25// This allows to have this realm as the main entry point for everything.
 26func Render(cur realm, p string) string {
 27	if dao == nil {
 28		return "DAO not initialized"
 29	}
 30	return dao.Render(cross(cur), cur.PkgPath(), p)
 31}
 32
 33// MustCreateProposal is an utility method that does the same as CreateProposal,
 34// but instead of erroing if something happens, it panics.
 35func MustCreateProposal(cur realm, r ProposalRequest) ProposalID {
 36	pid, err := CreateProposal(cur, r)
 37	if err != nil {
 38		panic(err.Error())
 39	}
 40
 41	return pid
 42}
 43
 44// ExecuteProposal will try to execute the proposal with the provided ProposalID.
 45// If the proposal was denied, it will return false. If the proposal is correctly
 46// executed, it will return true. If something happens this function will panic.
 47func ExecuteProposal(cur realm, pid ProposalID) bool {
 48	return executeProposal(cur, pid, false)
 49}
 50
 51// ExecuteOrRejectProposal executes the proposal with the provided ProposalID or rejects
 52// it when there is an execution error.
 53// If the proposal was denied, it will return false. If the proposal is correctly
 54// executed, it will return true, unless execution fails with an error, in which case
 55// proposal is rejected with the error as the reason.
 56// This function allows to finish proposals by rejecting them when there is a state
 57// change or an error in the proposal parameters that makes execution fail, potentially
 58// leaving the proposal active forever because it can't be successfully executed.
 59func ExecuteOrRejectProposal(cur realm, pid ProposalID) bool {
 60	return executeProposal(cur, pid, true)
 61}
 62
 63// CreateProposal will try to create a new proposal, that will be validated by the actual
 64// govDAO implementation. If the proposal cannot be created, an error will be returned.
 65func CreateProposal(cur realm, r ProposalRequest) (ProposalID, error) {
 66	if dao == nil {
 67		return -1, errors.New("DAO not initialized")
 68	}
 69	author, err := dao.PreCreateProposal(0, cur, r)
 70	if err != nil {
 71		return -1, err
 72	}
 73
 74	p := &Proposal{
 75		author:      author,
 76		title:       r.title,
 77		description: r.description,
 78		executor:    r.executor,
 79		allowedDAOs: allowedDAOs[:],
 80	}
 81
 82	pid := proposals.SetProposal(p)
 83	dao.PostCreateProposal(0, cur, r, pid)
 84
 85	chain.Emit("ProposalCreated",
 86		"id", strconv.FormatInt(int64(pid), 10),
 87	)
 88
 89	return pid, nil
 90}
 91
 92func MustVoteOnProposal(cur realm, r VoteRequest) {
 93	if err := VoteOnProposal(cur, r); err != nil {
 94		panic(err.Error())
 95	}
 96}
 97
 98// VoteOnProposal sends a vote to the actual govDAO implementation.
 99// If the voter cannot vote the specified proposal, this method will return an error
100// with the explanation of why.
101func VoteOnProposal(cur realm, r VoteRequest) error {
102	if dao == nil {
103		return errors.New("DAO not initialized")
104	}
105	return dao.VoteOnProposal(0, cur, r)
106}
107
108// MustVoteOnProposalSimple is like MustVoteOnProposal but intended to be used through gnokey with basic types.
109func MustVoteOnProposalSimple(cur realm, pid int64, option string) {
110	MustVoteOnProposal(cur, VoteRequest{
111		Option:     VoteOption(option),
112		ProposalID: ProposalID(pid),
113	})
114}
115
116func MustGetProposal(pid ProposalID) *Proposal {
117	p, err := GetProposal(pid)
118	if err != nil {
119		panic(err.Error())
120	}
121
122	return p
123}
124
125// GetProposal gets created proposal by its ID. Non-crossing pure read:
126// looks up the proposal in this realm's package var. Callable directly
127// from any realm without cross-call syntax.
128func GetProposal(pid ProposalID) (*Proposal, error) {
129	if dao == nil {
130		return nil, errors.New("DAO not initialized")
131	}
132	prop := proposals.GetProposal(pid)
133	if prop == nil {
134		return nil, errors.New(ufmt.Sprintf("Proposal %v does not exist.", int64(pid)))
135	}
136	return prop, nil
137}
138
139// UpdateImpl is a method intended to be used on a proposal.
140// This method will update the current govDAO implementation
141// to a new one. AllowedDAOs are a list of realms that can
142// call this method, in case the new DAO implementation had
143// a breaking bug. Any value set as nil will be ignored.
144// If AllowedDAOs field is not set correctly, the actual DAO
145// implementation wont be able to execute new Proposals!
146func UpdateImpl(cur realm, r UpdateRequest) {
147	gRealm := cur.Previous().PkgPath()
148
149	if !InAllowedDAOs(gRealm) {
150		panic("permission denied for prev realm: " + gRealm)
151	}
152
153	if r.AllowedDAOs != nil {
154		allowedDAOs = r.AllowedDAOs
155	}
156
157	if r.DAO != nil {
158		dao = r.DAO
159	}
160}
161
162func AllowedDAOs() []string {
163	dup := make([]string, len(allowedDAOs))
164	copy(dup, allowedDAOs)
165	return dup
166}
167
168func InAllowedDAOs(pkg string) bool {
169	if len(allowedDAOs) == 0 {
170		return true // corner case for initialization
171	}
172	for _, d := range allowedDAOs {
173		if pkg == d {
174			return true
175		}
176	}
177	return false
178}
179
180func executeProposal(cur realm, pid ProposalID, execErrorRejects bool) bool {
181	if dao == nil {
182		return false
183	}
184	execute, err := dao.PreExecuteProposal(0, cur, pid)
185	if err != nil {
186		panic(err.Error())
187	}
188
189	if !execute {
190		return false
191	}
192	prop, err := GetProposal(pid)
193	if err != nil {
194		panic(err.Error())
195	}
196
197	err = dao.ExecuteProposal(0, cur, pid, prop.executor)
198	if err != nil {
199		if execErrorRejects {
200			return false
201		}
202
203		panic(err.Error())
204	}
205	return true
206}