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

grant_test.gno

9.64 Kb · 244 lines
  1package grant
  2
  3import (
  4	"chain"
  5	"chain/banker"
  6	"testing"
  7
  8	"gno.land/p/moul/grants/v0"
  9	"gno.land/p/nt/uassert/v0"
 10	"gno.land/p/nt/urequire/v0"
 11)
 12
 13const (
 14	zoe = address("g12cs4cehujpffpjpywmkqj43m6u5ya53nj69sjz") // an applicant
 15	kim = address("g1jvh5ukk07dvd57fxefcp5aa29xaydxmxs7myyp") // a second one
 16)
 17
 18func balanceOf(a address) int64 {
 19	return banker.NewReadonlyBanker().GetCoins(a).AmountOf(Denom)
 20}
 21
 22func issue(n int64) { testing.IssueCoins(Address(), chain.NewCoins(chain.NewCoin(Denom, n))) }
 23
 24func TestFreshBoardIsOwnedAndEmpty(t *testing.T) {
 25	reset()
 26	uassert.True(t, Owner.IsValid(), "the owner is a real address")
 27	uassert.True(t, zoe.IsValid())
 28	uassert.True(t, kim.IsValid())
 29	uassert.True(t, Address().IsValid(), "the treasury address")
 30
 31	urequire.Equal(t, 1, len(Members()))
 32	uassert.Equal(t, Owner.String(), Members()[0].String())
 33	uassert.Equal(t, 0, Requests(), "nothing seeded, this board is real")
 34	uassert.Equal(t, int64(0), Committed())
 35	uassert.Equal(t, int64(0), Raised())
 36	uassert.Equal(t, int64(0), Disbursed())
 37}
 38
 39// The whole path with real coins, on a one-member board: the owner is the
 40// majority, so one ballot carries and one review pays.
 41func TestApplyApproveProveGetPaid(cur realm, t *testing.T) {
 42	reset()
 43	urequire.Equal(t, int64(0), Balance(), "start from an empty treasury")
 44
 45	// Someone funds the board, by name.
 46	issue(500)
 47	testing.SetOriginSend(chain.NewCoins(chain.NewCoin(Denom, 500)))
 48	testing.SetRealm(testing.NewUserRealm(kim))
 49	Fund(cross(cur))
 50	uassert.Equal(t, int64(500), Balance())
 51	uassert.Equal(t, int64(500), Raised())
 52	uassert.Equal(t, kim.String(), program.Donations[0].From.String())
 53
 54	// An outsider applies. Anyone may.
 55	testing.SetRealm(testing.NewUserRealm(zoe))
 56	id := Apply(cross(cur), "Ship the indexer", "two tranches", "alpha:100,beta:400")
 57	uassert.Equal(t, 1, id)
 58	uassert.Equal(t, int64(0), Committed(), "a pending request promises nothing")
 59
 60	// A non-member cannot vote, not even the applicant.
 61	testing.SetRealm(testing.NewUserRealm(zoe))
 62	uassert.AbortsContains(t, cur, grants.ErrNotMember.Error(), func() {
 63		Vote(cross(cur), id, true, "me again")
 64	})
 65
 66	testing.SetRealm(testing.NewUserRealm(Owner))
 67	Vote(cross(cur), id, true, "worth it")
 68	uassert.Equal(t, int64(500), Committed(), "approval promises the full ask")
 69	uassert.Equal(t, int64(0), Available())
 70
 71	// Milestone 1: proof, then the one review that carries also pays.
 72	testing.SetRealm(testing.NewUserRealm(kim))
 73	uassert.AbortsContains(t, cur, grants.ErrNotApplicant.Error(), func() {
 74		SubmitProof(cross(cur), id, 0, "url", "https://example.com/pr/1", "not mine to prove")
 75	})
 76	testing.SetRealm(testing.NewUserRealm(zoe))
 77	SubmitProof(cross(cur), id, 0, "url", "https://example.com/pr/1", "alpha is live")
 78	testing.SetRealm(testing.NewUserRealm(Owner))
 79	Review(cross(cur), id, 0, true, "confirmed")
 80
 81	uassert.Equal(t, int64(100), balanceOf(zoe), "the tranche landed")
 82	uassert.Equal(t, int64(400), Balance())
 83	uassert.Equal(t, int64(100), Disbursed())
 84	urequire.Equal(t, 1, len(program.Payments))
 85	uassert.Equal(t, int64(100), program.Payments[0].Amount)
 86
 87	// Out of order is refused.
 88	testing.SetRealm(testing.NewUserRealm(zoe))
 89	uassert.AbortsContains(t, cur, grants.ErrOutOfOrder.Error(), func() {
 90		SubmitProof(cross(cur), id, 0, "url", "https://example.com/pr/1", "again")
 91	})
 92
 93	// Milestone 2: a proof the owner refuses, then one they accept.
 94	testing.SetRealm(testing.NewUserRealm(zoe))
 95	SubmitProof(cross(cur), id, 1, "text", "trust me", "")
 96	testing.SetRealm(testing.NewUserRealm(Owner))
 97	Review(cross(cur), id, 1, false, "no evidence")
 98	uassert.Equal(t, int64(100), balanceOf(zoe), "a refused proof pays nothing")
 99
100	testing.SetRealm(testing.NewUserRealm(zoe))
101	SubmitProof(cross(cur), id, 1, "hash", "sha256:deadbeef", "beta, signed release")
102	testing.SetRealm(testing.NewUserRealm(Owner))
103	Review(cross(cur), id, 1, true, "verified")
104
105	uassert.Equal(t, int64(500), balanceOf(zoe), "paid in full")
106	uassert.Equal(t, int64(0), Balance(), "the treasury is back to empty")
107	uassert.Equal(t, int64(0), Committed())
108	uassert.Equal(t, int64(500), Disbursed())
109	uassert.Equal(t, 2, len(program.Payments))
110
111	// Both attempts on milestone 2 are on the record, refusal included.
112	m2 := program.Board.Get(id).Milestones[1]
113	urequire.Equal(t, 2, len(m2.Attempts))
114	uassert.Equal(t, "refused", m2.Attempts[0].Outcome.String())
115	uassert.Equal(t, "no evidence", m2.Attempts[0].Reviews[0].Reason)
116	uassert.Equal(t, "accepted", m2.Attempts[1].Outcome.String())
117}
118
119// The verdict that would release a tranche the treasury cannot pay is refused
120// outright and leaves no trace.
121func TestReviewRefusesToReleaseWhatItCannotPay(cur realm, t *testing.T) {
122	reset()
123	urequire.Equal(t, int64(0), Balance(), "no coins anywhere")
124
125	testing.SetRealm(testing.NewUserRealm(zoe))
126	id := Apply(cross(cur), "Ship the indexer", "", "alpha:100")
127	testing.SetRealm(testing.NewUserRealm(Owner))
128	Vote(cross(cur), id, true, "worth it")
129	testing.SetRealm(testing.NewUserRealm(zoe))
130	SubmitProof(cross(cur), id, 0, "url", "https://example.com/pr/1", "")
131
132	testing.SetRealm(testing.NewUserRealm(Owner))
133	uassert.AbortsContains(t, cur, grants.ErrUnderfunded.Error(), func() {
134		Review(cross(cur), id, 0, true, "confirmed")
135	})
136	m := program.Board.Get(id).Milestones[0]
137	uassert.Equal(t, 0, len(m.Current().Reviews), "the ballot was not recorded")
138	uassert.False(t, m.Released)
139	uassert.Equal(t, 0, len(program.Payments))
140	uassert.Equal(t, int64(-100), Available(), "and the shortfall is visible")
141}
142
143func TestRetractAndBadInput(cur realm, t *testing.T) {
144	reset()
145	testing.SetRealm(testing.NewUserRealm(zoe))
146	uassert.AbortsContains(t, cur, grants.ErrBadSpec.Error(), func() {
147		Apply(cross(cur), "Bad", "", "no amount here")
148	})
149	uassert.Equal(t, 0, Requests(), "nothing was filed")
150
151	testing.SetRealm(testing.NewUserRealm(zoe))
152	id := Apply(cross(cur), "Never mind", "", "thing:1")
153	testing.SetRealm(testing.NewUserRealm(kim))
154	uassert.AbortsContains(t, cur, grants.ErrNotApplicant.Error(), func() {
155		Retract(cross(cur), id)
156	})
157	testing.SetRealm(testing.NewUserRealm(zoe))
158	Retract(cross(cur), id)
159	uassert.Equal(t, "withdrawn", program.Board.Get(id).Status.String())
160
161	testing.SetOriginSend(chain.NewCoins())
162	testing.SetRealm(testing.NewUserRealm(kim))
163	uassert.AbortsContains(t, cur, grants.ErrNothingSent.Error(), func() { Fund(cross(cur)) })
164	uassert.Equal(t, 0, len(program.Donations))
165}
166
167// The owner can hand out a seat, and once there are two members the bar moves
168// to two. That is the path from this personal board to a real one.
169func TestProposeMemberGrowsTheBoardAndRaisesTheBar(cur realm, t *testing.T) {
170	reset()
171	testing.SetRealm(testing.NewUserRealm(zoe))
172	uassert.AbortsContains(t, cur, grants.ErrNotMember.Error(), func() {
173		ProposeMember(cross(cur), zoe.String(), true, "let me in")
174	})
175
176	testing.SetRealm(testing.NewUserRealm(Owner))
177	id := ProposeMember(cross(cur), kim.String(), true, "reviews everything anyway")
178	testing.SetRealm(testing.NewUserRealm(Owner))
179	Vote(cross(cur), id, true, "proposed it")
180	uassert.Equal(t, "completed", program.Board.Get(id).Status.String())
181	urequire.Equal(t, 2, len(Members()))
182
183	testing.SetRealm(testing.NewUserRealm(zoe))
184	next := Apply(cross(cur), "Ship the indexer", "", "alpha:100")
185	r := program.Board.Get(next)
186	uassert.Equal(t, 2, program.Board.Eligible(r))
187	uassert.Equal(t, 2, program.Board.Majority(r), "two members, two signatures")
188
189	testing.SetRealm(testing.NewUserRealm(Owner))
190	Vote(cross(cur), next, true, "worth it")
191	uassert.Equal(t, "pending", program.Board.Get(next).Status.String(), "the owner is no longer a majority")
192	testing.SetRealm(testing.NewUserRealm(kim))
193	Vote(cross(cur), next, true, "agreed")
194	uassert.Equal(t, "approved", program.Board.Get(next).Status.String())
195}
196
197func TestRenderUnknownPaths(t *testing.T) {
198	reset()
199	uassert.True(t, len(Render("")) > 0)
200	uassert.Equal(t, "# moul's grant board\n\nThere is no request 99.\n", Render("request/99"))
201	uassert.True(t, len(Render("ledger")) > 0)
202	uassert.True(t, len(Render("nope")) > 0)
203}
204
205// The case this board exists for: an account with nothing in it cannot pay the
206// gas to ask for its first coins, so someone else asks for it. The treasury
207// ends where it started, at zero, which is what the pinned pages render.
208func TestApplyForPaysTheBeneficiaryNotTheFiler(cur realm, t *testing.T) {
209	reset()
210	urequire.Equal(t, int64(0), Balance())
211	before := balanceOf(kim)
212
213	issue(300)
214	testing.SetOriginSend(chain.NewCoins(chain.NewCoin(Denom, 300)))
215	testing.SetRealm(testing.NewUserRealm(address(Owner)))
216	Fund(cross(cur))
217
218	// zoe files for kim, whose account is empty.
219	testing.SetRealm(testing.NewUserRealm(zoe))
220	id := ApplyFor(cross(cur), kim.String(), "kim's account is empty, she cannot pay the gas",
221		"Fund a first key", "so she can transact at all", "first transfer:300")
222	r := program.Board.Get(id)
223	urequire.True(t, r.OnBehalf())
224	uassert.Equal(t, kim.String(), r.Payee().String())
225
226	// The owner carries it alone, and either party may prove it. Here the
227	// filer does, because the payee still cannot send a transaction.
228	testing.SetRealm(testing.NewUserRealm(address(Owner)))
229	Vote(cross(cur), id, true, "I know kim.")
230	testing.SetRealm(testing.NewUserRealm(zoe))
231	SubmitProof(cross(cur), id, 0, "text", "she has a key and no coins", "")
232	testing.SetRealm(testing.NewUserRealm(address(Owner)))
233	Review(cross(cur), id, 0, true, "Sent.")
234
235	uassert.Equal(t, before+300, balanceOf(kim), "the payee got the coins")
236	uassert.Equal(t, int64(0), Balance(), "and the treasury is back to empty")
237	uassert.Equal(t, kim.String(), program.Payments[0].To.String())
238
239	// A reason is not optional when the payee is someone else.
240	testing.SetRealm(testing.NewUserRealm(zoe))
241	uassert.AbortsContains(t, cur, "needs a reason", func() {
242		ApplyFor(cross(cur), kim.String(), "", "no reason given", "", "x:1")
243	})
244}