Kind Realm (renderable)
Name v0
Namespace gov / dao / impl
Exported functions 17
Module gno.land/r/gov/dao/impl/v0
gno 0.9 executor_invocation_authority_filetest.gno gno
⧉
1 // PKGPATH: gno.land/r/test/execauthority 2 package execauthority 3 4 // Regression pin for who may invoke a proposal executor. 5 // 6 // dao.Executor.Execute is a public method on a public type and 7 // dao.NewSimpleExecutor is a public constructor, so any realm can build an 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.
8
// executor around any func(realm) error it can obtain. Execute is a CROSSING
9 // method declared in r/gov/dao, so invoking it mints a gno.land/r/gov/dao
10 // frame for the callback — and that value is a capability: realms gate on it
11 // (authz.NewContractAuthority("gno.land/r/gov/dao") + DoByPrevious, ownable,
12 // anything reading Previous().Address()).
13 //
14 // Until SimpleExecutor.Execute gated on its invoker, the forged frame was
15 // byte-identical to an approved one: same PkgPath, same Previous().PkgPath(),
16 // same Previous().Address(). A callback could not tell a supermajority-backed
17 // execution from one no member ever voted on, and the forgery minted no
18 // proposal id, so there was nothing to render, audit or deny afterwards.
19 //
20 // The documented containment was never the one holding the line. types.gno
21 // said invocation was "contained by the executor being unexported inside
22 // ProposalRequest/Proposal with no accessor" — that protects the executor
23 // OBJECT, but an attacker never needs someone else's executor: a reachable
24 // CALLBACK is enough, and an ordinary exported entrypoint of the victim whose
25 // signature is assignable to func(realm) error is such a callback.
26 // r/gnops/valopers had exactly that shape.
27 //
28 // Sections 2, 3 and 6 are the inversion: the forgery is now refused, the two
29 // frames are no longer comparable, and the proxy identity is not mintable.
30 // Section 5 keeps SafeExecutor characterized — it remains dead code that
31 // cannot be wired as written, since its allowlist holds the impl path while
32 // an executor's caller is the proxy.
33
34 import (
35 "chain"
36 "strings"
37 "testing"
38
39 "gno.land/r/gov/dao"
40 "gno.land/r/gov/dao/impl/v0"
41 "gno.land/r/gov/dao/memberstore/v0"
42 )
43
44 const (
45 member address = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"
46 outside address = "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn"
47 )
48
49 // observed records the frame the callback was handed, so the approved and the
50 // forged invocation can be compared.
51 type frame struct {
52 cur string
53 prev string
54 prevAddr address
55 ran bool
56 }
57
58 var seen frame
59
60 func probe(cur realm) error {
61 seen = frame{
62 cur: cur.PkgPath(),
63 prev: cur.Previous().PkgPath(),
64 prevAddr: cur.Previous().Address(),
65 ran: true ,
66 }
67 return nil
68 }
69
70 func init(cur realm) {
71 memberstore.Get( 0 , cur).DeleteAll()
72 memberstore.Get( 0 , cur).SetTier(memberstore.T1)
73 memberstore.Get( 0 , cur).SetMember(memberstore.T1, member, memberstore.NewMember( 3 ))
74 dao.UpdateImpl(cross(cur), dao.NewUpdateRequest(
75 impl.NewGovDAO(), [] string { "gno.land/r/gov/dao/impl/v0" }))
76 }
77
78 func main(cur realm) {
79 // 1. The approved path still works: a member proposes, votes it to
80 // supermajority, and executes. The gate must not break governance.
81 testing.SetOriginCaller(member)
82 testing.SetRealm(testing.NewUserRealm(member))
83
84 pid := dao.MustCreateProposal(cross(cur), dao.NewProposalRequest(
85 "approved" , "carried by a supermajority vote" , dao.NewSimpleExecutor( 0 , cur, probe, "" )))
86 dao.MustVoteOnProposal(cross(cur), dao.NewVoteRequest(dao.YesVote, pid))
87 println( "approved path executes:" , dao.ExecuteProposal(cross(cur), pid))
88 approved := seen
89 println( "callback ran:" , approved.ran)
90 println( "approved frame cur :" , approved.cur)
91 println( "approved frame prev:" , approved.prev)
92
93 // 2. The forged path: no proposal, no vote, and a caller who is not a
94 // member of any tier. Execute is called directly on a locally built
95 // executor, and is refused.
96 seen = frame{}
97 testing.SetOriginCaller(outside)
98 testing.SetRealm(testing.NewUserRealm(outside))
99
100 // The same caller cannot open a proposal -- PreCreateProposal rejects
101 // non-members. That is the authority level the DAO grants them, and
102 // Execute must not grant more.
103 _, err := dao.CreateProposal(cross(cur), dao.NewProposalRequest(
104 "rejected" , "a non-member cannot propose" , dao.NewSimpleExecutor( 0 , cur, probe, "" )))
105 println( "forger cannot even open a proposal:" , err != nil )
106 println( "callback ran:" , seen.ran)
107
108 forged := dao.NewSimpleExecutor( 0 , cur, probe, "no proposal backs this" )
109 println( "forged Execute is refused:" , forged.Execute(cross(cur)))
110 println( "callback ran:" , seen.ran)
111
112 // 3. And because it never ran, there is no forged frame to compare: the
113 // governance identity was not minted for an unauthorized invoker.
114 println( "no frame was handed out:" , seen.cur == "" && seen.prev == "" )
115 println( "approved prev address was the gov/dao package address:" ,
116 approved.prevAddr == chain.PackageAddress( "gno.land/r/gov/dao" ))
117
118 // 4. The forgery leaves no trace on the DAO either: no proposal id was
119 // minted, so there is nothing to render, audit, or deny after the fact.
120 _, missing := dao.GetProposal(pid + 1 )
121 println( "no proposal was created for the forged execution:" , missing != nil )
122
123 // 5. SafeExecutor still cannot be wired as written: allowedDAOs holds the
124 // impl path, while an executor's caller is the proxy path, so its check
125 // rejects the APPROVED path. It remains dead code; the gate that works
126 // lives in SimpleExecutor.Execute and keys on the proxy, not on
127 // InAllowedDAOs.
128 println( "InAllowedDAOs(proxy path):" , dao.InAllowedDAOs( "gno.land/r/gov/dao" ))
129 println( "allowedDAOs holds instead:" , dao.AllowedDAOs()[ 0 ])
130
131 seen = frame{}
132 testing.SetOriginCaller(member)
133 testing.SetRealm(testing.NewUserRealm(member))
134 spid := dao.MustCreateProposal(cross(cur), dao.NewProposalRequest(
135 "safe" , "same approved path, wrapped in SafeExecutor" ,
136 dao.NewSafeExecutor(dao.NewSimpleExecutor( 0 , cur, probe, "" ))))
137 dao.MustVoteOnProposal(cross(cur), dao.NewVoteRequest(dao.YesVote, spid))
138 println( "SafeExecutor rejects the approved path:" , !dao.ExecuteOrRejectProposal(cross(cur), spid))
139 println( "and its callback never ran:" , !seen.ran)
140 println( "denial reason names the allowlist:" ,
141 strings.Contains(dao.Render(cross(cur), spid.String()), "execution only allowed by validated govDAOs" ))
142
143 // 6. The reachability precondition is now closed at the primitive. A
144 // realm-authored callback is stuck with its own current realm (the one
145 // value a forger never controlled), and it can no longer borrow the
146 // proxy as Previous() either -- so gates keying on Previous(), which
147 // were the forgeable ones, hold.
148 testing.SetOriginCaller(outside)
149 testing.SetRealm(testing.NewUserRealm(outside))
150 seen = frame{}
151 println( "forged Execute refused again:" ,
152 dao.NewSimpleExecutor( 0 , cur, probe, "" ).Execute(cross(cur)) != nil )
153 println( "no proxy identity was borrowed:" , seen.prev != "gno.land/r/gov/dao" )
154
155 // The member store was never reachable this way -- it gates on PkgPath(),
156 // which the forgery does not control -- but the attempt is now refused one
157 // layer earlier, at Execute, so the abort below never happens.
158 e := dao.NewSimpleExecutor( 0 , cur, func (cur realm) error {
159 memberstore.Get( 0 , cur).SetMember(memberstore.T1, outside, memberstore.NewMember( 3 ))
160 return nil
161 }, "" )
162 println( "member-store escalation refused at Execute:" , e.Execute(cross(cur)) != nil )
163 }
164
165 // Output:
166 // approved path executes: true
167 // callback ran: true
168 // approved frame cur : gno.land/r/test/execauthority
169 // approved frame prev: gno.land/r/gov/dao
170 // forger cannot even open a proposal: true
171 // callback ran: false
172 // forged Execute is refused: execution denied: executors are only invocable by gno.land/r/gov/dao
173 // callback ran: false
174 // no frame was handed out: true
175 // approved prev address was the gov/dao package address: true
176 // no proposal was created for the forged execution: true
177 // InAllowedDAOs(proxy path): false
178 // allowedDAOs holds instead: gno.land/r/gov/dao/impl/v0
179 // SafeExecutor rejects the approved path: true
180 // and its callback never ran: true
181 // denial reason names the allowlist: true
182 // forged Execute refused again: true
183 // no proxy identity was borrowed: true
184 // member-store escalation refused at Execute: true
185