Search Apps Documentation Source Content File Folder Download Copy Actions Download

govdao.gno

5.08 Kb ยท 197 lines
  1package impl
  2
  3import (
  4	"chain"
  5	"chain/runtime"
  6	"errors"
  7
  8	"gno.land/p/nt/ufmt"
  9	"gno.land/r/gov/dao"
 10	"gno.land/r/gov/dao/v3/memberstore"
 11	"gno.land/r/sys/users"
 12)
 13
 14var ErrMemberNotFound = errors.New("member not found")
 15
 16type GovDAO struct {
 17	pss    ProposalsStatuses
 18	render *render
 19}
 20
 21func NewGovDAO() *GovDAO {
 22	pss := NewProposalsStatuses()
 23	d := &GovDAO{
 24		pss: pss,
 25	}
 26
 27	d.render = NewRender(d)
 28
 29	// There was no realm, from main(), so it succeeded, And
 30	// when returning, there was no finalization.  We don't
 31	// finalize anyways because there wasn't a realm boundary.
 32	// XXX make filetest main package a realm.
 33	//
 34	// filetest.init() ->
 35	//   v3/init.Init() ->
 36	//     NewGovDAO() ->
 37	//       returns an unsaved DAO NOTE NO REALM!
 38	//     dao.UpdateImpl =>
 39	//       saves dao under
 40	//
 41	// r/gov/dao.CrossPropposal() ->
 42	//   proposals.SetProposal(),
 43	//     that proposal lives in r/gov/dao.
 44	// r/gov/dao.ExecuteProposal() ->
 45	//   g.PreExecuteProposal() ->
 46	//     XXX g.test = 1 fails, owned by gov/dao.
 47	//
 48	//
 49	func(cur realm) {
 50		// TODO: replace with future attach()
 51		_govdao = d
 52	}(cross)
 53
 54	return d
 55}
 56
 57// Setting this to a global variable forces attaching the GovDAO struct to this
 58// realm. TODO replace with future `attach()`.
 59var _govdao *GovDAO
 60
 61func (g *GovDAO) PreCreateProposal(r dao.ProposalRequest) (address, error) {
 62	if !g.isValidCall() {
 63		return "", errors.New(ufmt.Sprintf("proposal creation must be done directly by a user or through the r/gov/dao proxy. current realm: %v; previous realm: %v",
 64			runtime.CurrentRealm(), runtime.PreviousRealm()))
 65	}
 66
 67	// Verify that the one creating the proposal is a member.
 68	caller := runtime.OriginCaller()
 69	mem, _ := getMembers(cross).GetMember(caller)
 70	if mem == nil {
 71		return caller, errors.New("only members can create new proposals")
 72	}
 73
 74	return caller, nil
 75}
 76
 77func (g *GovDAO) PostCreateProposal(r dao.ProposalRequest, pid dao.ProposalID) {
 78	// Tiers Allowed to Vote
 79	tatv := []string{memberstore.T1, memberstore.T2, memberstore.T3}
 80	switch v := r.Filter().(type) {
 81	case FilterByTier:
 82		// only members from T1 are allowed to vote when adding new members to T1
 83		if v.Tier == memberstore.T1 {
 84			tatv = []string{memberstore.T1}
 85		}
 86		// only members from T1 and T2 are allowed to vote when adding new members to T2
 87		if v.Tier == memberstore.T2 {
 88			tatv = []string{memberstore.T1, memberstore.T2}
 89		}
 90	}
 91	g.pss.Set(pid.String(), newProposalStatus(tatv))
 92}
 93
 94// Members **must** have a namespace to vote to enforce simpler tracking.
 95func (g *GovDAO) VoteOnProposal(r dao.VoteRequest) error {
 96	if !g.isValidCall() {
 97		return errors.New("proposal voting must be done directly by a user")
 98	}
 99
100	caller := runtime.OriginCaller()
101	mem, tie := getMembers(cross).GetMember(caller)
102	if mem == nil {
103		return ErrMemberNotFound
104	}
105
106	if users.ResolveAddress(caller) == nil {
107		return errors.New("voter should have a namespace")
108	}
109
110	status := g.pss.GetStatus(r.ProposalID)
111
112	if status.Denied || status.Accepted {
113		return errors.New(ufmt.Sprintf("proposal closed. Accepted: %v", status.Accepted))
114	}
115
116	if !status.IsAllowed(tie) {
117		return errors.New("member on specified tier is not allowed to vote on this proposal")
118	}
119
120	mVoted, _ := status.AllVotes.GetMember(caller)
121	if mVoted != nil {
122		return errors.New("already voted on proposal")
123	}
124
125	switch r.Option {
126	case dao.YesVote:
127		status.AllVotes.SetMember(tie, caller, mem)
128		status.YesVotes.SetMember(tie, caller, mem)
129	case dao.NoVote:
130		status.AllVotes.SetMember(tie, caller, mem)
131		status.NoVotes.SetMember(tie, caller, mem)
132	default:
133		return errors.New("voting can only be YES or NO")
134	}
135
136	return nil
137}
138
139func (g *GovDAO) PreGetProposal(pid dao.ProposalID) error {
140	return nil
141}
142
143func (g *GovDAO) PostGetProposal(pid dao.ProposalID, p *dao.Proposal) error {
144	return nil
145}
146
147func (g *GovDAO) PreExecuteProposal(pid dao.ProposalID) (bool, error) {
148	if !g.isValidCall() {
149		return false, errors.New("proposal execution must be done directly by a user")
150	}
151	status := g.pss.GetStatus(pid)
152	if status.Denied || status.Accepted {
153		return false, errors.New(ufmt.Sprintf("proposal already executed. Accepted: %v", status.Accepted))
154	}
155
156	if status.YesPercent() >= law.Supermajority {
157		status.Accepted = true
158		return true, nil
159	}
160
161	if status.NoPercent() >= law.Supermajority {
162		status.Denied = true
163		return false, nil
164	}
165
166	return false, errors.New(ufmt.Sprintf("proposal didn't reach supermajority yet: %v", law.Supermajority))
167}
168
169func (g *GovDAO) Render(pkgPath string, path string) string {
170	return g.render.Render(pkgPath, path)
171}
172
173func (g *GovDAO) isValidCall() bool {
174	// We need to verify two cases:
175	// 1: r/gov/dao (proxy) functions called directly by an user
176	// 2: r/gov/dao/v3/impl methods called directly by an user
177
178	// case 1
179	if runtime.CurrentRealm().PkgPath() == "gno.land/r/gov/dao" {
180		// called directly by an user through MsgCall
181		if runtime.PreviousRealm().IsUser() {
182			return true
183		}
184		isMsgRun := chain.PackageAddress(runtime.PreviousRealm().PkgPath()) == runtime.OriginCaller()
185		// called directly by an user through MsgRun
186		if isMsgRun {
187			return true
188		}
189	}
190
191	// case 2
192	if runtime.CurrentRealm().IsUser() {
193		return true
194	}
195
196	return false
197}