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

million_test.gno

11.83 Kb · 345 lines
  1package million
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"chain"
  8	"chain/banker"
  9
 10	"gno.land/p/nt/testutils/v0"
 11	"gno.land/p/nt/uassert/v0"
 12)
 13
 14var (
 15	deployer = testutils.TestAddress("deployer")
 16	alice    = testutils.TestAddress("alice")
 17	bob      = testutils.TestAddress("bob")
 18)
 19
 20var realmAddr = chain.PackageAddress("gno.land/r/g19t6f4f4ptt3m949jznalfsx2h8aj696llul0nj/million/v0")
 21
 22// Tests own the admin role through a test address rather than DefaultAdmin.
 23func init() {
 24	owner = deployer
 25}
 26
 27// fund credits the realm with what the next paint call claims to send, as
 28// the chain would before running the message, and resets the per-message
 29// origin-send spending limit that the previous call used up.
 30func fund(n int64) {
 31	testing.SetOriginSpend(nil)
 32	if n > 0 {
 33		testing.IssueCoins(realmAddr, ugnot(n))
 34	}
 35}
 36
 37func balanceOf(a address) int64 {
 38	return banker.NewReadonlyBanker().GetCoin(a, Denom)
 39}
 40
 41func ugnot(n int64) chain.Coins {
 42	return chain.NewCoins(chain.NewCoin(Denom, n))
 43}
 44
 45// testing.SetRealm only affects the frame that calls it, so it cannot be
 46// wrapped in a helper: every test sets the caller inline.
 47
 48func TestInitialState(t *testing.T) {
 49	uassert.Equal(t, Width, GetWidth())
 50	uassert.Equal(t, Height, GetHeight())
 51	uassert.Equal(t, DefaultPrice, Price())
 52	uassert.Equal(t, DefaultMaxBatch, MaxBatchSize())
 53	uassert.Equal(t, 0, Pixel(0, 0))
 54	uassert.Equal(t, deployer, Owner())
 55	uassert.Equal(t, 16, len(strings.Split(Palette(), ",")))
 56}
 57
 58func TestPaintOnePixel(cur realm, t *testing.T) {
 59	adminBefore := balanceOf(owner)
 60
 61	testing.SetRealm(testing.NewUserRealm(alice))
 62	testing.SetOriginSend(ugnot(price))
 63	fund(price)
 64	Paint(cross(cur), 10, 20, 6)
 65
 66	uassert.Equal(t, 6, Pixel(10, 20))
 67	uassert.Equal(t, 1, Painted())
 68	uassert.Equal(t, price, Revenue())
 69	uassert.Equal(t, int64(1), Paints())
 70	// The payment went straight to the admin, nothing stayed in the realm.
 71	uassert.Equal(t, adminBefore+price, balanceOf(owner))
 72	uassert.Equal(t, int64(0), balanceOf(realmAddr))
 73
 74	// Anyone can repaint for the same price; the count does not grow.
 75	testing.SetRealm(testing.NewUserRealm(bob))
 76	testing.SetOriginSend(ugnot(price))
 77	fund(price)
 78	Paint(cross(cur), 10, 20, 15)
 79	uassert.Equal(t, 15, Pixel(10, 20))
 80	uassert.Equal(t, 1, Painted())
 81	uassert.Equal(t, 2*price, Revenue())
 82	uassert.Equal(t, adminBefore+2*price, balanceOf(owner))
 83}
 84
 85func TestAdminPaintsForFree(cur realm, t *testing.T) {
 86	adminBefore := balanceOf(owner)
 87	revenueBefore := Revenue()
 88
 89	testing.SetRealm(testing.NewUserRealm(owner))
 90	testing.SetOriginSend(nil)
 91	Paint(cross(cur), 400, 400, 8)
 92	uassert.Equal(t, 8, Pixel(400, 400))
 93
 94	testing.SetRealm(testing.NewUserRealm(owner))
 95	testing.SetOriginSend(nil)
 96	PaintBatch(cross(cur), "401,400,9;402,400,10")
 97	uassert.Equal(t, 10, Pixel(402, 400))
 98
 99	// Free means exactly free: the admin must not attach coins either.
100	testing.SetRealm(testing.NewUserRealm(owner))
101	testing.SetOriginSend(ugnot(price))
102	uassert.AbortsContains(t, cur, "must send exactly 0ugnot", func() { Paint(cross(cur), 403, 400, 1) })
103
104	uassert.Equal(t, revenueBefore, Revenue())
105	uassert.Equal(t, adminBefore, balanceOf(owner))
106}
107
108func TestPaintRejectsWrongPayment(cur realm, t *testing.T) {
109	before := Painted()
110
111	testing.SetRealm(testing.NewUserRealm(alice))
112	testing.SetOriginSend(ugnot(price-1))
113	uassert.AbortsContains(t, cur, "must send exactly", func() { Paint(cross(cur), 0, 0, 1) })
114
115	testing.SetRealm(testing.NewUserRealm(alice))
116	testing.SetOriginSend(ugnot(price+1))
117	uassert.AbortsContains(t, cur, "must send exactly", func() { Paint(cross(cur), 0, 0, 1) })
118
119	testing.SetRealm(testing.NewUserRealm(alice))
120	testing.SetOriginSend(nil)
121	uassert.AbortsContains(t, cur, "must send exactly", func() { Paint(cross(cur), 0, 0, 1) })
122
123	testing.SetRealm(testing.NewUserRealm(alice))
124	testing.SetOriginSend(chain.NewCoins(chain.NewCoin("uatom", price)))
125	uassert.AbortsContains(t, cur, "only ugnot is accepted", func() { Paint(cross(cur), 0, 0, 1) })
126
127	uassert.Equal(t, before, Painted())
128	uassert.Equal(t, 0, Pixel(0, 0))
129}
130
131func TestPaintRejectsInvalidPixels(cur realm, t *testing.T) {
132	testing.SetRealm(testing.NewUserRealm(alice))
133	testing.SetOriginSend(ugnot(price))
134	uassert.AbortsContains(t, cur, "out of range", func() { Paint(cross(cur), Width, 0, 1) })
135	uassert.AbortsContains(t, cur, "out of range", func() { Paint(cross(cur), 0, Height, 1) })
136	uassert.AbortsContains(t, cur, "out of range", func() { Paint(cross(cur), -1, 0, 1) })
137	uassert.AbortsContains(t, cur, "color must be", func() { Paint(cross(cur), 0, 0, 0) })
138	uassert.AbortsContains(t, cur, "color must be", func() { Paint(cross(cur), 0, 0, 16) })
139	uassert.AbortsContains(t, cur, "color must be", func() { Paint(cross(cur), 0, 0, -1) })
140}
141
142func TestPaintRejectsCodeRealm(cur realm, t *testing.T) {
143	// Regression for the OriginSend bypass: an intermediate realm forwards
144	// the call while the envelope claims the coins were sent.
145	testing.SetRealm(testing.NewCodeRealm("gno.land/r/attacker/wrapper"))
146	testing.SetOriginSend(ugnot(price))
147	uassert.AbortsContains(t, cur, "directly by a user", func() { Paint(cross(cur), 1, 1, 1) })
148	uassert.Equal(t, 0, Pixel(1, 1))
149}
150
151func TestPaintBatch(cur realm, t *testing.T) {
152	before := Painted()
153	testing.SetRealm(testing.NewUserRealm(alice))
154	testing.SetOriginSend(ugnot(3 * price))
155	fund(3 * price)
156	PaintBatch(cross(cur), "100,100,1;101,100,2;102,100,3;")
157
158	uassert.Equal(t, 1, Pixel(100, 100))
159	uassert.Equal(t, 2, Pixel(101, 100))
160	uassert.Equal(t, 3, Pixel(102, 100))
161	uassert.Equal(t, before+3, Painted())
162
163	row := Rows(100, 101)
164	uassert.Equal(t, Width, len(row))
165	uassert.Equal(t, "123", row[100:103])
166}
167
168func TestPaintBatchIsAtomic(cur realm, t *testing.T) {
169	// One bad pixel rejects the whole batch before anything is written.
170	testing.SetRealm(testing.NewUserRealm(alice))
171	testing.SetOriginSend(ugnot(2*price))
172	uassert.AbortsContains(t, cur, "out of range", func() { PaintBatch(cross(cur), "200,200,1;9999,0,1") })
173	uassert.Equal(t, 0, Pixel(200, 200))
174
175	testing.SetRealm(testing.NewUserRealm(alice))
176	testing.SetOriginSend(ugnot(price))
177	uassert.AbortsContains(t, cur, "must send exactly", func() { PaintBatch(cross(cur), "200,200,1;201,200,1") })
178	uassert.Equal(t, 0, Pixel(200, 200))
179
180	testing.SetRealm(testing.NewUserRealm(alice))
181	testing.SetOriginSend(ugnot(price))
182	uassert.AbortsContains(t, cur, "x,y,color", func() { PaintBatch(cross(cur), "garbage") })
183	uassert.AbortsContains(t, cur, "x,y,color", func() { PaintBatch(cross(cur), "") })
184}
185
186func TestPaintBatchTooLarge(cur realm, t *testing.T) {
187	var sb strings.Builder
188	for i := 0; i <= maxBatch; i++ {
189		if i > 0 {
190			sb.WriteByte(';')
191		}
192		sb.WriteString("0,")
193		sb.WriteString(itoa(i))
194		sb.WriteString(",1")
195	}
196	testing.SetRealm(testing.NewUserRealm(alice))
197	testing.SetOriginSend(ugnot(int64(maxBatch+1)*price))
198	uassert.AbortsContains(t, cur, "too many pixels", func() { PaintBatch(cross(cur), sb.String()) })
199}
200
201func TestSetPrice(cur realm, t *testing.T) {
202	testing.SetRealm(testing.NewUserRealm(alice))
203	testing.SetOriginSend(nil)
204	uassert.AbortsWithMessage(t, cur, "owner only", func() { SetPrice(cross(cur), 1) })
205
206	testing.SetRealm(testing.NewUserRealm(owner))
207	testing.SetOriginSend(nil)
208	uassert.AbortsWithMessage(t, cur, "price must not be negative", func() { SetPrice(cross(cur), -1) })
209	SetPrice(cross(cur), 0)
210	uassert.Equal(t, int64(0), Price())
211
212	// Free painting requires sending nothing.
213	testing.SetRealm(testing.NewUserRealm(alice))
214	testing.SetOriginSend(nil)
215	Paint(cross(cur), 300, 300, 4)
216	uassert.Equal(t, 4, Pixel(300, 300))
217	testing.SetRealm(testing.NewUserRealm(alice))
218	testing.SetOriginSend(ugnot(1))
219	uassert.AbortsContains(t, cur, "must send exactly", func() { Paint(cross(cur), 300, 301, 4) })
220
221	testing.SetRealm(testing.NewUserRealm(owner))
222	testing.SetOriginSend(nil)
223	SetPrice(cross(cur), DefaultPrice)
224}
225
226func TestSetMaxBatch(cur realm, t *testing.T) {
227	orig := MaxBatchSize()
228
229	testing.SetRealm(testing.NewUserRealm(alice))
230	uassert.AbortsWithMessage(t, cur, "owner only", func() { SetMaxBatch(cross(cur), 100) })
231
232	testing.SetRealm(testing.NewUserRealm(owner))
233	uassert.AbortsContains(t, cur, "max batch must be between", func() { SetMaxBatch(cross(cur), 0) })
234	uassert.AbortsContains(t, cur, "max batch must be between", func() { SetMaxBatch(cross(cur), HardMaxBatch+1) })
235
236	SetMaxBatch(cross(cur), 3)
237	uassert.Equal(t, 3, MaxBatchSize())
238
239	// The new limit is enforced by PaintBatch.
240	testing.SetRealm(testing.NewUserRealm(alice))
241	testing.SetOriginSend(ugnot(4 * price))
242	fund(4 * price)
243	uassert.AbortsContains(t, cur, "too many pixels", func() {
244		PaintBatch(cross(cur), "0,500,1;1,500,1;2,500,1;3,500,1")
245	})
246
247	testing.SetRealm(testing.NewUserRealm(owner))
248	SetMaxBatch(cross(cur), orig)
249	uassert.Equal(t, orig, MaxBatchSize())
250}
251
252func TestWithdraw(cur realm, t *testing.T) {
253	testing.IssueCoins(realmAddr, ugnot(1_000))
254	ro := banker.NewReadonlyBanker()
255	bobBefore := ro.GetCoin(bob, Denom)
256
257	testing.SetRealm(testing.NewUserRealm(alice))
258	testing.SetOriginSend(nil)
259	uassert.AbortsWithMessage(t, cur, "owner only", func() { Withdraw(cross(cur), alice, 1) })
260
261	testing.SetRealm(testing.NewUserRealm(owner))
262	testing.SetOriginSend(nil)
263	uassert.AbortsWithMessage(t, cur, "amount must be positive", func() { Withdraw(cross(cur), bob, 0) })
264	uassert.AbortsWithMessage(t, cur, "invalid address", func() { Withdraw(cross(cur), address("nope"), 1) })
265	Withdraw(cross(cur), bob, 600)
266
267	uassert.Equal(t, bobBefore+600, ro.GetCoin(bob, Denom))
268	uassert.Equal(t, int64(400), ro.GetCoin(realmAddr, Denom))
269	// Leave the realm empty for the tests that assert on it.
270	Withdraw(cross(cur), bob, 400)
271}
272
273func TestTransferOwnership(cur realm, t *testing.T) {
274	original := owner
275
276	testing.SetRealm(testing.NewUserRealm(alice))
277	testing.SetOriginSend(nil)
278	uassert.AbortsWithMessage(t, cur, "owner only", func() { TransferOwnership(cross(cur), alice) })
279
280	testing.SetRealm(testing.NewUserRealm(original))
281	testing.SetOriginSend(nil)
282	uassert.AbortsWithMessage(t, cur, "invalid address", func() { TransferOwnership(cross(cur), address("nope")) })
283	TransferOwnership(cross(cur), alice)
284	uassert.Equal(t, alice, Owner())
285
286	testing.SetRealm(testing.NewUserRealm(alice))
287	testing.SetOriginSend(nil)
288	TransferOwnership(cross(cur), original)
289	uassert.Equal(t, original, Owner())
290}
291
292func TestVersionAndDirtyRows(cur realm, t *testing.T) {
293	v := Version()
294	uassert.Equal(t, "", DirtyRows(v))
295
296	testing.SetRealm(testing.NewUserRealm(alice))
297	testing.SetOriginSend(ugnot(3 * price))
298	fund(3 * price)
299	PaintBatch(cross(cur), "0,700,1;1,700,2;0,701,3")
300
301	uassert.Equal(t, v+1, Version())
302	uassert.Equal(t, "700,701", DirtyRows(v))
303	uassert.Equal(t, "", DirtyRows(Version()))
304
305	// A second call on one of those rows moves only that row forward.
306	testing.SetRealm(testing.NewUserRealm(bob))
307	testing.SetOriginSend(ugnot(price))
308	fund(price)
309	Paint(cross(cur), 5, 701, 4)
310	uassert.Equal(t, v+2, Version())
311	uassert.Equal(t, "701", DirtyRows(v+1))
312	uassert.Equal(t, "700,701", DirtyRows(v))
313
314	// A rejected paint changes nothing.
315	testing.SetRealm(testing.NewUserRealm(bob))
316	testing.SetOriginSend(ugnot(1))
317	uassert.AbortsContains(t, cur, "must send exactly", func() { Paint(cross(cur), 5, 702, 4) })
318	uassert.Equal(t, v+2, Version())
319	uassert.Equal(t, "", DirtyRows(v+2))
320
321	// Negative "since" behaves like zero: painted rows only, never blank ones.
322	uassert.Equal(t, DirtyRows(0), DirtyRows(-1))
323	uassert.True(t, !strings.Contains(","+DirtyRows(0)+",", ",799,"), "row 799 was never painted")
324}
325
326func TestRender(t *testing.T) {
327	out := Render("")
328	uassert.True(t, strings.Contains(out, "# Million Gno"), "title")
329	uassert.True(t, strings.Contains(out, "| Size | 1250 x 800 |"), "size row")
330	uassert.True(t, strings.Contains(out, "| Price per pixel | 50000ugnot |"), "price row")
331	uassert.True(t, strings.Contains(out, "| Admin | "+owner.String()+" |"), "admin row")
332	uassert.True(t, strings.Contains(out, "$help&func=PaintBatch"), "help link")
333}
334
335func itoa(n int) string {
336	if n == 0 {
337		return "0"
338	}
339	var b []byte
340	for n > 0 {
341		b = append([]byte{byte('0' + n%10)}, b...)
342		n /= 10
343	}
344	return string(b)
345}