Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}