Search Apps Documentation Source Content File Folder Download Copy Actions Download

govdao_test.gno

10.04 Kb · 311 lines
  1package impl
  2
  3import (
  4	"fmt"
  5	"strings"
  6	"testing"
  7
  8	"gno.land/p/nt/testutils/v0"
  9	"gno.land/p/nt/urequire/v0"
 10	"gno.land/r/gov/dao"
 11	"gno.land/r/gov/dao/v3/memberstore"
 12)
 13
 14func init() {
 15	loadMembers()
 16
 17	dao.UpdateImpl(cross, dao.UpdateRequest{
 18		DAO:         govDAO,
 19		AllowedDAOs: []string{"gno.land/r/gov/dao/v3/impl"},
 20	})
 21}
 22
 23var (
 24	m1    = testutils.TestAddress("m1")
 25	m11   = testutils.TestAddress("m1.1")
 26	m111  = testutils.TestAddress("m1.1.1")
 27	m1111 = testutils.TestAddress("m1.1.1.1")
 28	m2    = testutils.TestAddress("m2")
 29	m3    = testutils.TestAddress("m3")
 30	m4    = testutils.TestAddress("m4")
 31	m5    = testutils.TestAddress("m5")
 32	m6    = testutils.TestAddress("m6")
 33
 34	noMember = testutils.TestAddress("nm1")
 35)
 36
 37func loadMembers() {
 38	// This is needed because state is saved between unit tests,
 39	// and we want to avoid having real members used on tests
 40	mstore := memberstore.Get()
 41	mstore.DeleteAll()
 42
 43	mstore.SetTier(memberstore.T1)
 44	mstore.SetTier(memberstore.T2)
 45	mstore.SetTier(memberstore.T3)
 46
 47	mstore.SetMember(memberstore.T1, m1, memberByTier(memberstore.T1))
 48	mstore.SetMember(memberstore.T1, m11, memberByTier(memberstore.T1))
 49	mstore.SetMember(memberstore.T1, m111, memberByTier(memberstore.T1))
 50	mstore.SetMember(memberstore.T1, m1111, memberByTier(memberstore.T1))
 51
 52	mstore.SetMember(memberstore.T2, m2, memberByTier(memberstore.T2))
 53	mstore.SetMember(memberstore.T2, m3, memberByTier(memberstore.T2))
 54	mstore.SetMember(memberstore.T3, m4, memberByTier(memberstore.T3))
 55	mstore.SetMember(memberstore.T3, m5, memberByTier(memberstore.T3))
 56	mstore.SetMember(memberstore.T3, m6, memberByTier(memberstore.T3))
 57}
 58
 59func TestCreateProposalAndVote(cur realm, t *testing.T) {
 60	loadMembers()
 61
 62	portfolio := "# This is my portfolio:\n\n- THINGS"
 63
 64	testing.SetOriginCaller(noMember)
 65	testing.SetRealm(testing.NewCodeRealm("gno.land/r/gov/dao/v3/impl"))
 66
 67	nm1 := testutils.TestAddress("nm1")
 68
 69	urequire.AbortsWithMessage(t, "Only T1 and T2 members can be added by proposal. To add a T3 member use AddMember function directly.", func(cur realm) {
 70		dao.MustCreateProposal(cross, NewAddMemberRequest(cur, nm1, memberstore.T3, portfolio))
 71	})
 72
 73	urequire.AbortsWithMessage(t, "proposer is not a member", func(cur realm) {
 74		dao.MustCreateProposal(cross, NewAddMemberRequest(cur, nm1, memberstore.T2, portfolio))
 75	})
 76
 77	testing.SetOriginCaller(m1)
 78	testing.SetRealm(testing.NewCodeRealm("gno.land/r/gov/dao/v3/impl"))
 79
 80	proposalRequest := NewAddMemberRequest(cur, nm1, memberstore.T2, portfolio)
 81
 82	testing.SetOriginCaller(m1)
 83	testing.SetRealm(testing.NewUserRealm(m1))
 84	pid := dao.MustCreateProposal(cross, proposalRequest)
 85	urequire.Equal(t, int(pid), 0)
 86
 87	// m1 votes yes because that member is interested on it
 88	dao.MustVoteOnProposal(cross, dao.VoteRequest{
 89		Option:     dao.YesVote,
 90		ProposalID: dao.ProposalID(0),
 91	})
 92
 93	testing.SetOriginCaller(m11)
 94
 95	dao.MustVoteOnProposal(cross, dao.VoteRequest{
 96		Option:     dao.NoVote,
 97		ProposalID: dao.ProposalID(0),
 98	})
 99
100	testing.SetOriginCaller(m2)
101
102	dao.MustVoteOnProposal(cross, dao.VoteRequest{
103		Option:     dao.NoVote,
104		ProposalID: dao.ProposalID(0),
105	})
106
107	testing.SetOriginCaller(m3)
108
109	dao.MustVoteOnProposal(cross, dao.VoteRequest{
110		Option:     dao.NoVote,
111		ProposalID: dao.ProposalID(0),
112	})
113
114	testing.SetOriginCaller(m4)
115
116	urequire.AbortsWithMessage(t, "member on specified tier is not allowed to vote on this proposal", func() {
117		dao.MustVoteOnProposal(cross, dao.VoteRequest{
118			Option:     dao.NoVote,
119			ProposalID: dao.ProposalID(0),
120		})
121	})
122
123	testing.SetOriginCaller(m111)
124
125	// Same effect as:
126	// dao.MustVoteOnProposal(dao.VoteRequest{
127	// 	Option:     dao.NoVote,
128	// 	ProposalID: dao.ProposalID(0),
129	// })
130	dao.MustVoteOnProposalSimple(cross, 0, "NO")
131
132	urequire.Equal(t, true, strings.Contains(dao.Render(""), "Prop #0 - New T2 Member Proposal"))
133	// urequire.Equal(t, true, strings.Contains(dao.Render(""), "Author: "+m1.String()))
134
135	urequire.AbortsWithMessage(t, "proposal didn't reach supermajority yet: 66.66", func() {
136		dao.ExecuteProposal(cross, dao.ProposalID(0))
137	})
138
139	testing.SetOriginCaller(m1111)
140	dao.MustVoteOnProposal(cross, dao.VoteRequest{
141		Option:     dao.NoVote,
142		ProposalID: dao.ProposalID(0),
143	})
144
145	accepted := dao.ExecuteProposal(cross, dao.ProposalID(0))
146	urequire.Equal(t, false, accepted)
147
148	urequire.Equal(t, true, contains(dao.Render("0"), "**PROPOSAL HAS BEEN DENIED**"))
149	urequire.Equal(t, true, contains(dao.Render("0"), "NO PERCENT: 68.42105263157895%"))
150}
151
152func TestExecutorCreationRealm(cur realm, t *testing.T) {
153	loadMembers()
154
155	// Test that executor creation realm is captured correctly
156	testing.SetOriginCaller(m1)
157	testing.SetRealm(testing.NewCodeRealm("gno.land/r/template/contract"))
158
159	// Create executor in the template contract realm
160	executor := dao.NewSimpleExecutor(func(realm) error { return nil }, "Test executor from template")
161
162	proposalRequest := dao.NewProposalRequest(
163		"Test Proposal",
164		"This proposal tests executor creation realm tracking",
165		executor,
166	)
167
168	// Create proposal from user realm (user can call DAO directly)
169	testing.SetRealm(testing.NewUserRealm(m1))
170	pid := dao.MustCreateProposal(cross, proposalRequest)
171
172	// Get the proposal
173	prop := dao.MustGetProposal(cross, pid)
174
175	// Verify the author is m1
176	urequire.Equal(t, m1, prop.Author())
177
178	// Verify the executor creation realm is captured correctly
179	urequire.Equal(t, "gno.land/r/template/contract", prop.ExecutorCreationRealm())
180
181	// Check that it's displayed in the individual proposal render output
182	individualRendered := dao.Render(pid.String())
183	urequire.Equal(t, true, contains(individualRendered, "Executor created in: gno.land/r/template/contract"))
184	urequire.Equal(t, true, contains(individualRendered, "Test executor from template"))
185
186	// Also verify the main content is there
187	urequire.Equal(t, true, contains(individualRendered, "Test Proposal"))
188	urequire.Equal(t, true, contains(individualRendered, "This proposal tests executor creation realm tracking"))
189}
190
191func TestProposalPagination(cur realm, t *testing.T) {
192	loadMembers()
193	portfolio := "### This is my portfolio:\n\n- THINGS"
194
195	testing.SetOriginCaller(m1)
196	testing.SetRealm(testing.NewCodeRealm("gno.land/r/gov/dao/v3/impl"))
197
198	nm1 := testutils.TestAddress("nm1")
199
200	var pid dao.ProposalID
201
202	proposalRequest := NewAddMemberRequest(cur, nm1, memberstore.T2, portfolio)
203
204	testing.SetOriginCaller(m1)
205	testing.SetRealm(testing.NewUserRealm(m1))
206	pid = dao.MustCreateProposal(cross, proposalRequest)
207
208	// TODO: tests keep the same vm state: https://github.com/gnolang/gno/issues/1982
209	urequire.Equal(t, 2, int(pid))
210
211	testing.SetRealm(testing.NewUserRealm(m1))
212	pid = dao.MustCreateProposal(cross, proposalRequest)
213	urequire.Equal(t, 3, int(pid))
214
215	testing.SetRealm(testing.NewUserRealm(m1))
216	pid = dao.MustCreateProposal(cross, proposalRequest)
217	urequire.Equal(t, 4, int(pid))
218
219	testing.SetRealm(testing.NewUserRealm(m1))
220	pid = dao.MustCreateProposal(cross, proposalRequest)
221	urequire.Equal(t, 5, int(pid))
222
223	testing.SetRealm(testing.NewUserRealm(m1))
224	pid = dao.MustCreateProposal(cross, proposalRequest)
225	urequire.Equal(t, 6, int(pid))
226
227	testing.SetRealm(testing.NewUserRealm(m1))
228	pid = dao.MustCreateProposal(cross, proposalRequest)
229	urequire.Equal(t, 7, int(pid))
230
231	fmt.Println(dao.Render(""))
232	urequire.Equal(t, true, contains(dao.Render(""), "### [Prop #7 - New T2 Member Proposal](/r/gov/dao:7)"))
233	urequire.Equal(t, true, contains(dao.Render(""), "### [Prop #6 - New T2 Member Proposal](/r/gov/dao:6)"))
234	urequire.Equal(t, true, contains(dao.Render(""), "### [Prop #5 - New T2 Member Proposal](/r/gov/dao:5)"))
235	urequire.Equal(t, true, contains(dao.Render(""), "### [Prop #4 - New T2 Member Proposal](/r/gov/dao:4)"))
236	urequire.Equal(t, true, contains(dao.Render(""), "### [Prop #3 - New T2 Member Proposal](/r/gov/dao:3)"))
237
238	urequire.Equal(t, true, contains(dao.Render("?page=2"), "### [Prop #2 - New T2 Member Proposal](/r/gov/dao:2)"))
239	urequire.Equal(t, true, contains(dao.Render("?page=2"), "### [Prop #1 - Test Proposal](/r/gov/dao:1)"))
240	urequire.Equal(t, true, contains(dao.Render("?page=2"), "### [Prop #0 - New T2 Member Proposal](/r/gov/dao:0)"))
241}
242
243func TestUpgradeDaoImplementation(t *testing.T) {
244	loadMembers()
245
246	testing.SetOriginCaller(noMember)
247	testing.SetRealm(testing.NewCodeRealm("gno.land/r/gov/dao/v3/impl"))
248
249	urequire.PanicsWithMessage(t, "proposer is not a member", func() {
250		NewUpgradeDaoImplRequest(govDAO, "gno.land/r/gov/dao/v4/impl", "Something happened and we have to fix it.")
251	})
252
253	testing.SetOriginCaller(m1)
254	testing.SetRealm(testing.NewCodeRealm("gno.land/r/gov/dao/v3/impl"))
255
256	preq := NewUpgradeDaoImplRequest(govDAO, "gno.land/r/gov/dao/v4/impl", "Something happened and we have to fix it.")
257
258	testing.SetOriginCaller(m1)
259	testing.SetRealm(testing.NewUserRealm(m1))
260	pid := dao.MustCreateProposal(cross, preq)
261	urequire.Equal(t, int(pid), 8)
262
263	// m1 votes yes because that member is interested on it
264	dao.MustVoteOnProposal(cross, dao.VoteRequest{
265		Option:     dao.YesVote,
266		ProposalID: dao.ProposalID(pid),
267	})
268
269	testing.SetOriginCaller(m11)
270
271	dao.MustVoteOnProposal(cross, dao.VoteRequest{
272		Option:     dao.YesVote,
273		ProposalID: dao.ProposalID(pid),
274	})
275
276	testing.SetOriginCaller(m2)
277
278	dao.MustVoteOnProposal(cross, dao.VoteRequest{
279		Option:     dao.YesVote,
280		ProposalID: dao.ProposalID(pid),
281	})
282
283	testing.SetOriginCaller(m3)
284
285	dao.MustVoteOnProposal(cross, dao.VoteRequest{
286		Option:     dao.YesVote,
287		ProposalID: dao.ProposalID(pid),
288	})
289
290	testing.SetOriginCaller(m111)
291
292	// Same effect as:
293	// dao.MustVoteOnProposal(dao.VoteRequest{
294	// 	Option:     dao.YesVote,
295	// 	ProposalID: dao.ProposalID(pid),
296	// })
297	dao.MustVoteOnProposalSimple(cross, int64(pid), "YES")
298
299	urequire.Equal(t, true, contains(dao.Render("8"), "**Proposal is open for votes**"))
300	urequire.Equal(t, true, contains(dao.Render("8"), "68.42105263157895%"))
301	urequire.Equal(t, true, contains(dao.Render("8"), "0%"))
302
303	accepted := dao.ExecuteProposal(cross, dao.ProposalID(pid))
304	urequire.Equal(t, true, accepted)
305	urequire.Equal(t, true, contains(dao.Render("8"), "**PROPOSAL HAS BEEN ACCEPTED**"))
306	urequire.Equal(t, true, contains(dao.Render("8"), "YES PERCENT: 68.42105263157895%"))
307}
308
309func contains(s, substr string) bool {
310	return strings.Index(s, substr) >= 0
311}