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.

lastwords_test.gno

8.66 Kb · 251 lines
  1package lastwords
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"chain"
  8	"chain/banker"
  9	"chain/runtime"
 10
 11	"gno.land/p/nt/testutils/v0"
 12	"gno.land/p/nt/uassert/v0"
 13)
 14
 15var realmAddr = chain.PackageAddress("gno.land/r/nym-moultest123/x/games/lastwords/v0")
 16
 17var (
 18	alice = testutils.TestAddress("alice")
 19	bob   = testutils.TestAddress("bob")
 20	carol = testutils.TestAddress("carol")
 21)
 22
 23// reset reopens the slot at the current height. Heights are relative in tests
 24// (SkipHeights moves forward from wherever the last test left off and there is
 25// no absolute setter), so nothing here asserts an absolute height.
 26func reset() {
 27	open(runtime.ChainHeight())
 28}
 29
 30// write sends amount ugnot alongside the call, the way a real transaction
 31// would: the runtime deposits it at the realm's address, then OriginSend
 32// reports it to the crossing function.
 33func write(cur realm, who address, amount int64, msg string) {
 34	testing.IssueCoins(realmAddr, chain.Coins{{denom, amount}})
 35	testing.SetOriginSend(chain.Coins{{denom, amount}})
 36	testing.SetRealm(testing.NewUserRealm(who))
 37	Write(cross(cur), msg)
 38}
 39
 40func TestWriteTakesTheSlot(cur realm, t *testing.T) {
 41	reset()
 42	uassert.Equal(t, "", Message())
 43
 44	write(cur, alice, Price("hello"), "hello")
 45	uassert.Equal(t, "hello", Message())
 46	uassert.Equal(t, alice.String(), Author().String())
 47	uassert.Equal(t, int64(0), ledger.TotalOwed(), "nobody was displaced yet")
 48	uassert.Equal(t, MinWrite+5*PerByte, Pot(), "the whole first payment is the pot")
 49}
 50
 51// TestDisplacedWriterIsPaid is rule 3: overwriting somebody pays them, so a
 52// second player has a reason to exist.
 53func TestDisplacedWriterIsPaid(cur realm, t *testing.T) {
 54	reset()
 55	write(cur, alice, Price("alice was here"), "alice was here")
 56	potAfterFirst := Pot()
 57
 58	paidByBob := Price("no, bob was")
 59	write(cur, bob, paidByBob, "no, bob was")
 60
 61	dividend := paidByBob * DividendPct / 100
 62	uassert.Equal(t, dividend, Owed(alice), "half of bob's payment, credited")
 63	uassert.Equal(t, potAfterFirst+paidByBob-dividend, Pot())
 64	uassert.Equal(t, int64(0), Owed(bob), "the holder is owed nothing yet")
 65}
 66
 67func TestPriceRisesWithTheHolderAndTheLength(cur realm, t *testing.T) {
 68	reset()
 69	uassert.Equal(t, MinWrite, Price(""), "the base price with an empty slot")
 70	uassert.Equal(t, MinWrite+10*PerByte, Price("0123456789"), "ten bytes cost ten byte-prices")
 71
 72	first := Price("x")
 73	write(cur, alice, first, "x")
 74	uassert.Equal(t, first+1, Price(""), "outbidding the holder is the floor")
 75}
 76
 77func TestWriteRejectsBadInput(cur realm, t *testing.T) {
 78	reset()
 79
 80	testing.SetOriginSend(chain.Coins{{denom, MinWrite}})
 81	testing.SetRealm(testing.NewUserRealm(alice))
 82	uassert.AbortsContains(t, cur, "must not be empty", func() { Write(cross(cur), "") })
 83
 84	long := strings.Repeat("x", MaxLen+1)
 85	testing.SetOriginSend(chain.Coins{{denom, Price(long)}})
 86	testing.SetRealm(testing.NewUserRealm(alice))
 87	uassert.AbortsContains(t, cur, "at most", func() { Write(cross(cur), long) })
 88
 89	testing.SetOriginSend(chain.Coins{{denom, Price("a\nb")}})
 90	testing.SetRealm(testing.NewUserRealm(alice))
 91	uassert.AbortsContains(t, cur, "single line", func() { Write(cross(cur), "a\nb") })
 92
 93	testing.SetOriginSend(chain.Coins{{denom, MinWrite - 1}})
 94	testing.SetRealm(testing.NewUserRealm(alice))
 95	uassert.AbortsContains(t, cur, "price is", func() { Write(cross(cur), "cheapskate") })
 96
 97	uassert.Equal(t, "", Message(), "none of that took the slot")
 98}
 99
100// TestEveryWriteExtendsButNeverPastTheHardEnd is rule 1, the terminator. A
101// constant extension per write is how a pot stays open forever.
102func TestEveryWriteExtendsButNeverPastTheHardEnd(cur realm, t *testing.T) {
103	reset()
104	start := runtime.ChainHeight()
105	hardEnd := start + Life
106	uassert.Equal(t, start+Window, Deadline(), "it opens with the window")
107
108	prev := Deadline()
109	for i := 0; i < 30; i++ {
110		testing.SkipHeights(100)
111		if runtime.ChainHeight() >= Deadline() {
112			break
113		}
114		write(cur, alice, Price("again"), "again")
115		uassert.True(t, Deadline() >= prev, "a write never shortens the clock")
116		uassert.True(t, Deadline() <= hardEnd, "and never passes the hard end")
117		prev = Deadline()
118	}
119	uassert.True(t, Deadline() <= hardEnd)
120	uassert.True(t, Deadline() > start+Window, "the writes did buy time")
121}
122
123func TestClosedSlotRefusesAWrite(cur realm, t *testing.T) {
124	reset()
125	write(cur, alice, Price("famous last words"), "famous last words")
126	testing.SkipHeights(Life + 1)
127
128	testing.SetOriginSend(chain.Coins{{denom, Price("too late")}})
129	testing.SetRealm(testing.NewUserRealm(bob))
130	uassert.AbortsContains(t, cur, "slot is closed", func() { Write(cross(cur), "too late") })
131	uassert.Equal(t, "famous last words", Message(), "the last words stay last")
132}
133
134// TestSettleSplitsAmongEveryoneButTheHolder is rule 2, and it is the whole
135// reason this is not a winner-takes-all pot.
136func TestSettleSplitsAmongEveryoneButTheHolder(cur realm, t *testing.T) {
137	reset()
138	write(cur, alice, Price("a"), "a")
139	testing.SkipHeights(10)
140	write(cur, bob, Price("b"), "b")
141	testing.SkipHeights(10)
142	write(cur, carol, Price("c"), "c")
143
144	beforeAlice, beforeBob := Owed(alice), Owed(bob)
145	pot := Pot()
146	uassert.True(t, pot > 0)
147
148	testing.SkipHeights(Life + 1)
149	testing.SetRealm(testing.NewUserRealm(bob)) // anyone may settle
150	Settle(cross(cur))
151
152	uassert.Equal(t, int64(0), Pot(), "the pot is fully distributed")
153	uassert.Equal(t, int64(0), Owed(carol), "the holder takes the slot, not the pot")
154	uassert.True(t, Owed(alice) > beforeAlice, "alice got a share")
155	uassert.True(t, Owed(bob) > beforeBob, "bob got a share")
156	split := (Owed(alice) - beforeAlice) + (Owed(bob) - beforeBob)
157	uassert.Equal(t, pot, split, "and the shares add up to exactly the pot")
158}
159
160func TestSettleWithOneWriterReturnsThePot(cur realm, t *testing.T) {
161	reset()
162	write(cur, alice, Price("alone"), "alone")
163	pot := Pot()
164	testing.SkipHeights(Life + 1)
165
166	testing.SetRealm(testing.NewUserRealm(alice))
167	Settle(cross(cur))
168	uassert.Equal(t, pot, Owed(alice), "nobody else to share with")
169}
170
171func TestSettleRefusesEarlyAndTwice(cur realm, t *testing.T) {
172	reset()
173	write(cur, alice, Price("tick"), "tick")
174
175	testing.SetRealm(testing.NewUserRealm(bob))
176	uassert.AbortsContains(t, cur, "still open", func() { Settle(cross(cur)) })
177
178	testing.SkipHeights(Life + 1)
179	testing.SetRealm(testing.NewUserRealm(bob))
180	Settle(cross(cur))
181	testing.SetRealm(testing.NewUserRealm(bob))
182	uassert.AbortsContains(t, cur, "already settled", func() { Settle(cross(cur)) })
183}
184
185// TestWithdrawPaysRealCoins checks the half nothing else can: the ledger and
186// the realm's balance agree.
187func TestWithdrawPaysRealCoins(cur realm, t *testing.T) {
188	reset()
189	write(cur, alice, Price("first"), "first")
190	testing.SkipHeights(10)
191	write(cur, bob, Price("second"), "second")
192
193	owed := Owed(alice)
194	uassert.True(t, owed > 0)
195
196	ro := banker.NewReadonlyBanker()
197	before := ro.GetCoins(alice).AmountOf(denom)
198
199	testing.SetRealm(testing.NewUserRealm(alice))
200	got := Withdraw(cross(cur))
201	uassert.Equal(t, owed, got)
202	uassert.Equal(t, int64(0), Owed(alice), "the credit is zeroed before the coins move")
203
204	after := banker.NewReadonlyBanker().GetCoins(alice).AmountOf(denom)
205	uassert.Equal(t, before+owed, after, "the coins actually arrived")
206
207	testing.SetRealm(testing.NewUserRealm(carol))
208	uassert.AbortsContains(t, cur, "nothing to withdraw", func() { Withdraw(cross(cur)) })
209}
210
211// TestRenderEscapesTheMessage is the one bug a live deploy makes permanent: the
212// realm's entire product is a string somebody else typed.
213func TestRenderEscapesTheMessage(cur realm, t *testing.T) {
214	reset()
215	write(cur, alice, Price("[click](https://evil.example)"), "[click](https://evil.example)")
216
217	out := Render("")
218	uassert.False(t, strings.Contains(out, "](https://evil.example)"), "no live link")
219	uassert.False(t, strings.Contains(out, "[click]"), "the link syntax is dead")
220	uassert.True(t, strings.Contains(out, "click"), "the text is still readable")
221	uassert.True(t, strings.Contains(out, "Last Words"))
222}
223
224func TestRenderCoversEveryPhase(cur realm, t *testing.T) {
225	reset()
226	uassert.True(t, strings.Contains(Render(""), "The slot is empty"))
227
228	write(cur, alice, Price("open for business"), "open for business")
229	out := Render("")
230	uassert.True(t, strings.Contains(out, "open for business"))
231	uassert.True(t, strings.Contains(out, "Open for another"))
232
233	testing.SkipHeights(Life + 1)
234	out = Render("")
235	uassert.True(t, strings.Contains(out, "**Closed** at height"))
236	uassert.True(t, strings.Contains(out, "may call `Settle`"))
237
238	testing.SetRealm(testing.NewUserRealm(bob))
239	Settle(cross(cur))
240	out = Render("")
241	uassert.True(t, strings.Contains(out, "Settled"))
242	uassert.True(t, strings.Contains(out, "Owed"))
243}
244
245func TestGnotFormatting(t *testing.T) {
246	uassert.Equal(t, "0", gnot(0))
247	uassert.Equal(t, "1", gnot(1000000))
248	uassert.Equal(t, "1.5", gnot(1500000))
249	uassert.Equal(t, "0.000001", gnot(1))
250	uassert.Equal(t, "12.345678", gnot(12345678))
251}