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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}