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

10.91 Kb · 319 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/g1sw5xklxjjuv0yvuxy5f5s3l3mnj0nqq626a9wr/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, MaxBatch, 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 TestWithdraw(cur realm, t *testing.T) {
227	testing.IssueCoins(realmAddr, ugnot(1_000))
228	ro := banker.NewReadonlyBanker()
229	bobBefore := ro.GetCoin(bob, Denom)
230
231	testing.SetRealm(testing.NewUserRealm(alice))
232	testing.SetOriginSend(nil)
233	uassert.AbortsWithMessage(t, cur, "owner only", func() { Withdraw(cross(cur), alice, 1) })
234
235	testing.SetRealm(testing.NewUserRealm(owner))
236	testing.SetOriginSend(nil)
237	uassert.AbortsWithMessage(t, cur, "amount must be positive", func() { Withdraw(cross(cur), bob, 0) })
238	uassert.AbortsWithMessage(t, cur, "invalid address", func() { Withdraw(cross(cur), address("nope"), 1) })
239	Withdraw(cross(cur), bob, 600)
240
241	uassert.Equal(t, bobBefore+600, ro.GetCoin(bob, Denom))
242	uassert.Equal(t, int64(400), ro.GetCoin(realmAddr, Denom))
243	// Leave the realm empty for the tests that assert on it.
244	Withdraw(cross(cur), bob, 400)
245}
246
247func TestTransferOwnership(cur realm, t *testing.T) {
248	original := owner
249
250	testing.SetRealm(testing.NewUserRealm(alice))
251	testing.SetOriginSend(nil)
252	uassert.AbortsWithMessage(t, cur, "owner only", func() { TransferOwnership(cross(cur), alice) })
253
254	testing.SetRealm(testing.NewUserRealm(original))
255	testing.SetOriginSend(nil)
256	uassert.AbortsWithMessage(t, cur, "invalid address", func() { TransferOwnership(cross(cur), address("nope")) })
257	TransferOwnership(cross(cur), alice)
258	uassert.Equal(t, alice, Owner())
259
260	testing.SetRealm(testing.NewUserRealm(alice))
261	testing.SetOriginSend(nil)
262	TransferOwnership(cross(cur), original)
263	uassert.Equal(t, original, Owner())
264}
265
266func TestVersionAndDirtyRows(cur realm, t *testing.T) {
267	v := Version()
268	uassert.Equal(t, "", DirtyRows(v))
269
270	testing.SetRealm(testing.NewUserRealm(alice))
271	testing.SetOriginSend(ugnot(3 * price))
272	fund(3 * price)
273	PaintBatch(cross(cur), "0,700,1;1,700,2;0,701,3")
274
275	uassert.Equal(t, v+1, Version())
276	uassert.Equal(t, "700,701", DirtyRows(v))
277	uassert.Equal(t, "", DirtyRows(Version()))
278
279	// A second call on one of those rows moves only that row forward.
280	testing.SetRealm(testing.NewUserRealm(bob))
281	testing.SetOriginSend(ugnot(price))
282	fund(price)
283	Paint(cross(cur), 5, 701, 4)
284	uassert.Equal(t, v+2, Version())
285	uassert.Equal(t, "701", DirtyRows(v+1))
286	uassert.Equal(t, "700,701", DirtyRows(v))
287
288	// A rejected paint changes nothing.
289	testing.SetRealm(testing.NewUserRealm(bob))
290	testing.SetOriginSend(ugnot(1))
291	uassert.AbortsContains(t, cur, "must send exactly", func() { Paint(cross(cur), 5, 702, 4) })
292	uassert.Equal(t, v+2, Version())
293	uassert.Equal(t, "", DirtyRows(v+2))
294
295	// Negative "since" behaves like zero: painted rows only, never blank ones.
296	uassert.Equal(t, DirtyRows(0), DirtyRows(-1))
297	uassert.True(t, !strings.Contains(","+DirtyRows(0)+",", ",799,"), "row 799 was never painted")
298}
299
300func TestRender(t *testing.T) {
301	out := Render("")
302	uassert.True(t, strings.Contains(out, "# Million Gno"), "title")
303	uassert.True(t, strings.Contains(out, "| Size | 1250 x 800 |"), "size row")
304	uassert.True(t, strings.Contains(out, "| Price per pixel | 50000ugnot |"), "price row")
305	uassert.True(t, strings.Contains(out, "| Admin | "+owner.String()+" |"), "admin row")
306	uassert.True(t, strings.Contains(out, "$help&func=PaintBatch"), "help link")
307}
308
309func itoa(n int) string {
310	if n == 0 {
311		return "0"
312	}
313	var b []byte
314	for n > 0 {
315		b = append([]byte{byte('0' + n%10)}, b...)
316		n /= 10
317	}
318	return string(b)
319}