allowlist_test.gno
5.09 Kb · 129 lines
1package dao
2
3import (
4 "testing"
5
6 "gno.land/p/nt/uassert/v0"
7 "gno.land/p/nt/urequire/v0"
8)
9
10// allowedDAOs is the sole authorization for UpdateImpl, memberstore.Get,
11// treasury.Send and treasury.SetTokenKeys, and InAllowedDAOs() fails OPEN when
12// it is empty — the bootstrap window that lets the genesis MsgRun seed the
13// member set before lockdown.
14//
15// These tests pin that the transition empty -> non-empty is one-way: once the
16// DAO is locked down, UpdateImpl cannot put it back into the fail-open state.
17// Both paths below reopened the gate before the guard, because
18// NewUpdateRequest copies nil into a NON-nil empty slice and the old test was
19// `r.AllowedDAOs != nil`.
20//
21// These call the real UpdateImpl through a code realm rather than replaying
22// its logic, so reverting the guard makes them fail.
23func TestUpdateImplIgnoresEmptyAllowedDAOs(cur realm, t *testing.T) {
24 savedDAOs, savedDAO := allowedDAOs, dao
25 defer func() { allowedDAOs, dao = savedDAOs, savedDAO }()
26
27 lock := func() {
28 allowedDAOs = nil // reopen so the next UpdateImpl is permitted
29 testing.SetRealm(testing.NewCodeRealm(v0))
30 UpdateImpl(cross(cur), UpdateRequest{DAO: &dummyDao{}, AllowedDAOs: []string{v0}})
31 uassert.False(t, InAllowedDAOs(invalid), "precondition: locked down")
32 }
33
34 // Path 1: NewUpdateRequest(d, nil) — "swap the implementation, leave
35 // permissions alone", and the form v0/loader uses.
36 lock()
37 testing.SetRealm(testing.NewCodeRealm(v0))
38 UpdateImpl(cross(cur), NewUpdateRequest(&dummyDao{}, nil))
39 uassert.False(t, InAllowedDAOs(invalid),
40 "a nil AllowedDAOs must not reopen the permission gate")
41
42 // Path 2: an explicitly empty slice via the struct literal.
43 lock()
44 testing.SetRealm(testing.NewCodeRealm(v0))
45 UpdateImpl(cross(cur), UpdateRequest{DAO: &dummyDao{}, AllowedDAOs: []string{}})
46 uassert.False(t, InAllowedDAOs(invalid),
47 "an empty AllowedDAOs must not reopen the permission gate")
48
49 // A legitimate extension of the list must still apply.
50 lock()
51 testing.SetRealm(testing.NewCodeRealm(v0))
52 UpdateImpl(cross(cur), NewUpdateRequest(&dummyDao{}, []string{v0, v1}))
53 uassert.True(t, InAllowedDAOs(v1), "a non-empty AllowedDAOs must still be stored")
54 uassert.False(t, InAllowedDAOs(invalid), "and must not admit anyone else")
55}
56
57// The bootstrap window itself must survive the guard: with no allowlist
58// configured yet, any caller is allowed so the genesis MsgRun can seed the
59// member set and then lock down.
60func TestBootstrapWindowStillOpen(t *testing.T) {
61 saved := allowedDAOs
62 defer func() { allowedDAOs = saved }()
63
64 allowedDAOs = nil
65 uassert.True(t, InAllowedDAOs("gno.land/r/gov/dao/loader/v0"),
66 "an unset allowlist must stay open for genesis bootstrap")
67}
68
69// len(AllowedDAOs) != 0 is not sufficient on its own. InAllowedDAOs compares by
70// exact string and a user realm's PkgPath() is "", so a single "" entry admits
71// any caller whose previous frame is a user realm — the same fail-open outcome
72// the guard exists to prevent. NewUpgradeDaoImplRequest passes its realmPkg
73// argument straight into the list, so an empty one reaches here.
74func TestUpdateImplRejectsBlankAllowedDAOEntry(cur realm, t *testing.T) {
75 savedDAOs, savedDAO := allowedDAOs, dao
76 defer func() { allowedDAOs, dao = savedDAOs, savedDAO }()
77
78 for _, blank := range []string{"", " "} {
79 allowedDAOs = nil
80 testing.SetRealm(testing.NewCodeRealm(v0))
81 UpdateImpl(cross(cur), UpdateRequest{DAO: &dummyDao{}, AllowedDAOs: []string{v0}})
82
83 testing.SetRealm(testing.NewCodeRealm(v0))
84 urequire.AbortsWithMessage(t, cur,
85 "AllowedDAOs entries must be realm paths; got an empty one",
86 func() {
87 UpdateImpl(cross(cur), UpdateRequest{
88 DAO: &dummyDao{},
89 AllowedDAOs: []string{v0, blank},
90 })
91 })
92
93 uassert.False(t, InAllowedDAOs(""),
94 "a blank entry must never make it into the allowlist")
95 uassert.True(t, InAllowedDAOs(v0),
96 "the rejected request must leave the previous allowlist intact")
97 }
98}
99
100// A padded entry passes a plain non-blank test but is useless: entries are
101// stored exactly as given and InAllowedDAOs compares whole strings, so
102// " gno.land/r/x " matches no caller. The list is still non-empty, so the
103// bootstrap window is closed. A proposal that padded every entry would lock
104// the DAO out of its own allowlist with no way back.
105func TestUpdateImplRejectsPaddedAllowedDAOEntry(cur realm, t *testing.T) {
106 savedDAOs, savedDAO := allowedDAOs, dao
107 defer func() { allowedDAOs, dao = savedDAOs, savedDAO }()
108
109 for _, padded := range []string{" " + v1, v1 + " ", "\t" + v1} {
110 allowedDAOs = nil
111 testing.SetRealm(testing.NewCodeRealm(v0))
112 UpdateImpl(cross(cur), UpdateRequest{DAO: &dummyDao{}, AllowedDAOs: []string{v0}})
113
114 testing.SetRealm(testing.NewCodeRealm(v0))
115 urequire.AbortsWithMessage(t, cur,
116 "AllowedDAOs entries must not have leading or trailing spaces; entry 1",
117 func() {
118 UpdateImpl(cross(cur), UpdateRequest{
119 DAO: &dummyDao{},
120 AllowedDAOs: []string{v0, padded},
121 })
122 })
123
124 uassert.False(t, InAllowedDAOs(padded),
125 "a padded entry must never make it into the allowlist")
126 uassert.True(t, InAllowedDAOs(v0),
127 "the rejected request must leave the previous allowlist intact")
128 }
129}