Kind Realm (renderable)
Name v0
Namespace gov / dao / impl
Exported functions 17
Module gno.land/r/gov/dao/impl/v0
gno 0.9 prop_requests.gno gno
⧉
1 package impl 2 3 import ( 4 "chain/runtime/unsafe" 5 "strings" 6 7 "gno.land/p/aeddi/panictoerr/v0" 8 AddMember (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, addr string)
GetInstance (int, rlm interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}) *gno.land/r/gov/dao/impl/v0.GovDAO
NewAddMemberRequest (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, addr string, tier string, portfolio string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewChangeLawRequest (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, newLaw struct{Supermajority float64}) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewFilterByTier (tier string) struct{Tier string}
NewGovDAO () *gno.land/r/gov/dao/impl/v0.GovDAO
NewLaw (supermajority float64) struct{Supermajority float64}
NewPromoteMemberRequest (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, addr string, fromTier string, toTier string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewProposalsStatuses () struct{BPTree *gno.land/p/nt/bptree/v0.BPTree}
NewRender (d *gno.land/r/gov/dao/impl/v0.GovDAO) *gno.land/r/gov/dao/impl/v0.render
NewTreasuryGRC20TokensUpdate (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, newTokenKeys []string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewTreasuryPaymentRequest (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, payment interface {BankerID func() string; String func() string}, reason string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewUpgradeDaoImplRequest (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, newDao interface {ExecuteProposal func(int, .uverse.realm, gno.land/r/gov/dao.ProposalID, gno.land/r/gov/dao.Executor) .uverse.error; PostCreateProposal func(int, .uverse.realm, gno.land/r/gov/dao.ProposalRequest, gno.land/r/gov/dao.ProposalID); PreCreateProposal func(int, .uverse.realm, gno.land/r/gov/dao.ProposalRequest) (.uverse.address, .uverse.error); PreExecuteProposal func(int, .uverse.realm, gno.land/r/gov/dao.ProposalID) (bool, .uverse.error); Render func(.uverse.realm, string, string) string; VoteOnProposal func(int, .uverse.realm, gno.land/r/gov/dao.VoteRequest) .uverse.error}, realmPkg string, reason string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
NewWithdrawMemberRequest (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, addr string, reason string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}
Render (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, in string) string
StringifyProposal (p *gno.land/r/gov/dao.Proposal) string
StringifyVotes (int, rlm interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, ps *gno.land/r/gov/dao/impl/v0.proposalStatus) string
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.
"gno.land/p/moul/md/v0"
9 "gno.land/p/nt/markdown/sanitize/v0"
10 trs_pkg "gno.land/p/nt/treasury/v0"
11 "gno.land/p/nt/ufmt/v0"
12
13 "gno.land/r/gov/dao"
14 "gno.land/r/gov/dao/memberstore/v0"
15 "gno.land/r/gov/dao/treasury/v0"
16 )
17
18 func NewChangeLawRequest(cur realm, newLaw Law) dao.ProposalRequest {
19 member, _ := memberstore.Get( 0 , cur).GetMember(unsafe.OriginCaller())
20 if member == nil {
21 panic( "proposer is not a member" )
22 }
23
24 cb := func (cur realm) error {
25 law = &newLaw
26 return nil
27 }
28
29 e := dao.NewSimpleExecutor( 0 , cur, cb, ufmt.Sprintf( "A new Law is proposed:\n %v" , newLaw))
30
31 return dao.NewProposalRequest( "Change Law Proposal" , "This proposal is looking to change the actual govDAO Law" , e)
32 }
33
34 func NewUpgradeDaoImplRequest(cur realm, newDao dao.DAO, realmPkg, reason string ) dao.ProposalRequest {
35 // Rejected here as well as in UpdateImpl so the mistake surfaces when the
36 // proposal is written, not when it executes. An empty realmPkg would be
37 // stored as an allowlist entry matching every user realm's empty
38 // PkgPath(), and would render as a blank name in the grant sentence below.
39 if strings.TrimSpace(realmPkg) == "" {
40 panic( "realmPkg must be the realm path being granted govDAO authority" )
41 }
42 // Padding is rejected for the same reason UpdateImpl rejects it: the entry
43 // is stored as given and matched whole, so a padded path grants nobody
44 // anything. Caught here so the author sees it, not the voters.
45 if realmPkg != strings.TrimSpace(realmPkg) {
46 panic( "realmPkg must not have leading or trailing spaces" )
47 }
48
49 member, _ := memberstore.Get( 0 , cur).GetMember(unsafe.OriginCaller())
50 if member == nil {
51 panic( "proposer is not a member" )
52 }
53
54 cb := func (cur realm) error {
55 // dao.UpdateImpl() must be cross-called from v0/impl but
56 // what calls this cb function is r/gov/dao.
57 // therefore we must cross back into v0/impl and then
58 // cross call dao.UpdateRequest().
59 dao.UpdateImpl(cross(cur), dao.NewUpdateRequest(newDao, [] string { "gno.land/r/gov/dao/impl/v0" , realmPkg}))
60 return nil
61 }
62
63 // State the grant: this executor rewrites AllowedDAOs, and previously
64 // rendered nothing at all. realmPkg is caller-supplied and lands in a code
65 // span, so it is wrapped with sanitize.InlineCode — which emits its own
66 // fence, so do not add backticks around it. (md.EscapeText is wrong here:
67 // CommonMark 6.1 does not process backslash escapes inside code spans.)
68 // Nothing ties realmPkg to newDao, which is why the text says so.
69 e := dao.NewSimpleExecutor( 0 , cur, cb, ufmt.Sprintf(
70 "Replaces the govDAO implementation with the DAO carried by this proposal.\n\n" +
71 "After execution, only `gno.land/r/gov/dao/impl/v0` and %s may replace the " +
72 "implementation, mutate the member store, or move treasury funds.\n\n" +
73 "This grant is stated by the proposer: the incoming implementation is passed " +
74 "as a value and is not verified to belong to the realm named above." ,
75 sanitize.InlineCode(realmPkg)))
76
77 return dao.NewProposalRequest( "Change DAO implementation" , "This proposal is looking to change the actual govDAO implementation. Reason: " +reason, e)
78 }
79
80 func NewAddMemberRequest(cur realm, addr address, tier string , portfolio string ) dao.ProposalRequest {
81 // Reject a non-bech32 address at proposal-build time (see AddMember): keeps
82 // an unauthenticable / injection-bearing key out of the member store.
83 if !addr.IsValid() {
84 panic( "invalid member address: " + addr.String())
85 }
86 _, ok := memberstore.GetTier(tier)
87 if !ok {
88 panic( "provided tier does not exists" )
89 }
90
91 if tier != memberstore.T1 && tier != memberstore.T2 {
92 panic( "Only T1 and T2 members can be added by proposal. To add a T3 member use AddMember function directly." )
93 }
94
95 if portfolio == "" {
96 panic( "A portfolio for the proposed member is required" )
97 }
98
99 member, _ := memberstore.Get( 0 , cur).GetMember(unsafe.OriginCaller())
100 if member == nil {
101 panic( "proposer is not a member" )
102 }
103
104 if member.InvitationPoints <= 0 {
105 panic( "proposer does not have enough invitation points for inviting new people to the board" )
106 }
107
108 cb := func (cur realm) error {
109 // Add the member first, spend the proposer's invitation point second.
110 //
111 // SetMember RETURNS an error when the address is already a member --
112 // easy to reach, since AddMember enrols T3 members directly. A returned
113 // executor error does not revert: ExecuteOrRejectProposal marks the
114 // proposal rejected and the transaction still commits. So with the old
115 // order the point was spent and the member was not added.
116 //
117 // RemoveInvitationPoint can still fail, despite the build-time check
118 // above: it panics at zero, and AddMember spends points from this same
119 // captured member, so a proposer can drain their own between creation
120 // and execution. That panics and reverts, which loses nothing.
121 if err := memberstore.Get( 0 , cur).SetMember(tier, addr, memberByTier(tier)); err != nil {
122 return err
123 }
124 member.RemoveInvitationPoint()
125
126 return nil
127 }
128
129 e := dao.NewSimpleExecutor( 0 , cur, cb, ufmt.Sprintf( "A new member with address %v is proposed to be on tier %v. Provided Portfolio information:\n\n%v" , addr, tier, portfolio))
130
131 name := tryResolveAddr(addr)
132 return dao.NewProposalRequestWithFilter(
133 ufmt.Sprintf( "New %s Member Proposal" , tier),
134 ufmt.Sprintf( "This is a proposal to add `%s` to **%s**.\n#### `%s`'s Portfolio:\n\n%s\n" , name, tier, name, portfolio),
135 e,
136 FilterByTier{Tier: tier},
137 )
138 }
139
140 func NewWithdrawMemberRequest(cur realm, addr address, reason string ) dao.ProposalRequest {
141 member, tier := memberstore.Get( 0 , cur).GetMember(addr)
142 if member == nil {
143 panic( "user we want to remove not found" )
144 }
145
146 reason = strings.TrimSpace(reason)
147 if tier == memberstore.T1 && reason == "" {
148 panic( "T1 user removals must contains a reason." )
149 }
150
151 cb := func (cur realm) error {
152 memberstore.Get( 0 , cur).RemoveMember(addr)
153 return nil
154 }
155
156 e := dao.NewSimpleExecutor( 0 , cur, cb, ufmt.Sprintf( "Member with address %v will be withdrawn.\n\n REASON: %v." , addr, reason))
157
158 return dao.NewProposalRequest(
159 "Member Withdrawal Proposal" ,
160 ufmt.Sprintf( "This is a proposal to remove %s from the GovDAO" , tryResolveAddr(addr)),
161 e,
162 )
163 }
164
165 func NewPromoteMemberRequest(cur realm, addr address, fromTier string , toTier string ) dao.ProposalRequest {
166 cb := func (cur realm) error {
167 // Fail on an unknown destination tier before touching the member.
168 //
169 // Everything that can be checked before a write is checked here, and
170 // RETURNS. A returned executor error rejects the proposal and closes it,
171 // and since nothing has been mutated yet that commits nothing.
172 //
173 // After RemoveMember the policy inverts: a return would commit a member
174 // who was removed and not re-added, so everything below panics instead,
175 // which reverts the whole transaction.
176 // A tier dropped from the global table. NewChangeTiersRequest replaces
177 // that table wholesale, so a proposal listing only T1 and T2 removes T3
178 // for good. memberByTier would not notice: it switches on the constant
179 // and ignores GetTier's ok, so the promotion would succeed and hand the
180 // member zero invitation points. Untested -- reaching it needs a passed
181 // tier-change proposal, which rewrites state every other test shares.
182 if _, ok := memberstore.GetTier(toTier); !ok {
183 return ufmt.Errorf( "unknown destination tier: %s" , toTier)
184 }
185
186 mbt := memberstore.Get( 0 , cur)
187
188 // SetMember consults the store's own index, not the global tier table
189 // above: DeleteAll empties the buckets and leaves the definitions.
190 if !mbt.Has(toTier) {
191 return ufmt.Errorf( "destination tier is missing from the member store: %s" , toTier)
192 }
193
194 prevTier := mbt.RemoveMember(addr)
195 if prevTier == "" {
196 panic( "member not found, so cannot be promoted" )
197 }
198
199 if prevTier != fromTier {
200 panic( "previous tier changed from the one indicated in the proposal" )
201 }
202
203 if err := mbt.SetMember(toTier, addr, memberByTier(toTier)); err != nil {
204 // Unreachable: both of SetMember's failures are ruled out above.
205 // Panicking is what makes it safe to be wrong about that.
206 panic( "promotion failed after removal: " + err.Error())
207 }
208
209 return nil
210 }
211
212 e := dao.NewSimpleExecutor( 0 , cur, cb, ufmt.Sprintf( "A new member with address %v will be promoted from tier %v to tier %v." , addr, fromTier, toTier))
213
214 return dao.NewProposalRequestWithFilter(
215 "Member Promotion Proposal" ,
216 ufmt.Sprintf( "This is a proposal to promote %s from **%s** to **%s**." , tryResolveAddr(addr), fromTier, toTier),
217 e,
218 FilterByTier{Tier: toTier},
219 )
220 }
221
222 func NewTreasuryPaymentRequest(cur realm, payment trs_pkg.Payment, reason string ) dao.ProposalRequest {
223 // A foreign impl renders a Payment line no Banker will process, so it would
224 // fail only at execution, after the vote. Rejected here so the proposer
225 // sees it, not the voters. See IsCanonicalPayment.
226 if !trs_pkg.IsCanonicalPayment(payment) {
227 panic( "payment must be built by treasury.NewCoinsPayment or treasury.NewGRC20Payment" )
228 }
229 if !treasury.HasBanker(payment.BankerID()) {
230 panic( "banker not registered in treasury with ID: " + payment.BankerID())
231 }
232
233 reason = strings.TrimSpace(reason)
234 if reason == "" {
235 panic( "treasury payment request requires a reason" )
236 }
237
238 cb := func (cur realm) error {
239 return panictoerr.PanicToError( func () {
240 treasury.Send(cross(cur), payment)
241 })
242 }
243
244 // Both are caller-supplied and land in the same body as the Payment line a
245 // member votes on, which render.gno emits raw. InlineText folds newlines to
246 // spaces, so neither can start a line of its own and forge that label.
247 // Clamp before escaping, per clamp.gno.
248 safeReason := sanitize.InlineText(clampField(reason, maxRenderedReason))
249 safePayment := sanitize.InlineText(clampField(payment.String(), maxRenderedPayment))
250
251 e := dao.NewSimpleExecutor( 0 , cur,
252 cb,
253 ufmt.Sprintf(
254 "A payment will be sent by the GovDAO treasury.\n\nReason: %s\n\nPayment: %s." ,
255 safeReason,
256 safePayment,
257 ),
258 )
259
260 return dao.NewProposalRequest(
261 "Treasury Payment" ,
262 ufmt.Sprintf(
263 "This proposal is looking to send a payment using the treasury.\n\nReason: %s\n\nPayment: %s" ,
264 safeReason,
265 safePayment,
266 ),
267 e,
268 )
269 }
270
271 // NewTreasuryGRC20TokensUpdate creates a proposal request to update the list of GRC20 tokens registry
272 // keys used by the treasury. The new list, if voted and accepted, will overwrite the current one.
273 func NewTreasuryGRC20TokensUpdate(cur realm, newTokenKeys [] string ) dao.ProposalRequest {
274 if len(newTokenKeys) == 0 {
275 panic( "the list of new tokens is empty" )
276 }
277
278 // Copied for the same reason NewCoinsPayment copies its coins: the bullet
279 // list below is rendered once at creation, but the executor closure
280 // re-reads the slice at execution, so the caller could otherwise install a
281 // token set the board never saw.
282 keys := make([] string , len(newTokenKeys))
283 copy(keys, newTokenKeys)
284
285 cb := func (cur realm) error {
286 return panictoerr.PanicToError( func () {
287 // NOTE:: Consider checking if the keys are already registered
288 // in the grc20reg before updating the treasury tokens keys.
289 treasury.SetTokenKeys(cross(cur), keys)
290 })
291 }
292
293 bulletList := md.BulletList(keys)
294
295 e := dao.NewSimpleExecutor( 0 , cur,
296 cb,
297 ufmt.Sprintf(
298 "The list of GRC20 tokens used by the treasury will be updated.\n\nNew Token Keys:\n%s.\n" ,
299 bulletList,
300 ),
301 )
302
303 return dao.NewProposalRequest(
304 "Treasury GRC20 Tokens Update" ,
305 ufmt.Sprintf(
306 "This proposal is looking to update the list of GRC20 tokens used by the treasury.\n\nNew Token Keys:\n%s" ,
307 bulletList,
308 ),
309 e,
310 )
311 }
312
313 func memberByTier(tier string ) *memberstore.Member {
314 switch tier {
315 case memberstore.T1:
316 t, _ := memberstore.GetTier(memberstore.T1)
317 return memberstore.NewMember(t.InvitationPoints)
318 case memberstore.T2:
319 t, _ := memberstore.GetTier(memberstore.T2)
320 return memberstore.NewMember(t.InvitationPoints)
321 case memberstore.T3:
322 t, _ := memberstore.GetTier(memberstore.T3)
323 return memberstore.NewMember(t.InvitationPoints)
324 default :
325 panic( "member not found by the specified tier" )
326 }
327 }
328