PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/bubblerumble3

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
bubblerumble3
Namespace
g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr
Files
3 (gnomod.toml)
Exported functions
13
Module
gno.land/r/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/bubblerumble3
gno
0.9

Files (3)

  • gnomod.tomltoml
  • bubblerumble3.gnogno
  • bubblerumble3_test.gnogno
bubblerumble3_test.gnogno
1package bubblerumble323import (4	"chain"5	"chain/banker"6	"chain/runtime"7	"strings"8	"testing"9)1011const realmPath = "gno.land/r/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/bubblerumble3"1213var (14	alice = address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")15	bob   = address("g1dmt3sa5ucvecxuhf3j6ne5r0e3z4x7h6c03xc0")16	carol = address("g1akeqsvhucjt8gf5yupyzjxsjd29wv8fayng37c")17	dave  = address("g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr") // also the admin18)1920func aborts(t *testing.T, want string, f func()) {21	t.Helper()22	r := revive(f)23	if r == nil {24		t.Fatalf("expected an abort containing %q, got none", want)25	}26	msg := ""27	switch e := r.(type) {28	case error:29		msg = e.Error()30	case string:31		msg = e32	}33	if !strings.Contains(msg, want) {34		t.Fatalf("abort %q does not contain %q", msg, want)35	}36}3738func held(who address) int64 {39	return banker.NewReadonlyBanker().GetCoins(who).AmountOf("ugnot")40}4142// fund attaches ugnot to the next call the way a transaction would. The caller43// is set with testing.SetRealm in the test body: it binds to the calling frame.44func fund(ugnot int64) {45	if ugnot > 0 {46		testing.IssueCoins(chain.PackageAddress(realmPath), chain.Coins{{"ugnot", ugnot}})47		testing.SetOriginSend(chain.Coins{{"ugnot", ugnot}})48	} else {49		testing.SetOriginSend(nil)50	}51}5253// heldAtReset is what the realm held when the scenario began: coins from54// earlier scenarios stay in the test realm after reset() forgets their pools.55var heldAtReset int645657func reset() {58	pools = nil59	players = map[address]*Player{}60	teamPaid = map[string]int64{}61	teamWon = map[string]int64{}62	teamSize = map[string]int64{}63	shotsOn = true64	heldAtReset = held(chain.PackageAddress(realmPath))65}6667// The conservation check: every ugnot the realm took in during the scenario is68// either in an open pot or credited to somebody.69func realmHeld() int64 { return held(chain.PackageAddress(realmPath)) - heldAtReset }7071func owedAll() int64 {72	var sum int6473	for _, p := range pools {74		if !p.Paid {75			sum += p.Pot76		}77	}78	for _, pl := range players {79		sum += pl.Owed80	}81	return sum82}8384func conserved(t *testing.T) {85	t.Helper()86	if realmHeld() != owedAll() {87		t.Fatalf("realm holds %d, pots and credits account for %d", realmHeld(), owedAll())88	}89}9091func owed(a address) int64 { return player(a).Owed }9293func TestSplitSelfRebidFlagLockAndWithdraw(cur realm, t *testing.T) {94	reset()95	fund(0)96	testing.SetRealm(testing.NewUserRealm(dave))97	id := CreatePool(cross(cur), "split", 3600, 1_000_000, 0)98	p := pool(id)99	if p.Life != 10*3600 {100		t.Fatalf("default life is ten windows: %d", p.Life)101	}102103	// first bid: dividends and the inviter's cut fall back into the pot, the creator is paid 5% at once104	d0 := held(dave)105	fund(1_000_000)106	testing.SetRealm(testing.NewUserRealm(alice))107	Bid(cross(cur), id, "", "kr", 0)108	if p.Pot != 950_000 || held(dave)-d0 != 50_000 || p.DivPaid != 0 || p.FirstBidAt.IsZero() {109		t.Fatalf("first bid: pot %d creator +%d div %d", p.Pot, held(dave)-d0, p.DivPaid)110	}111112	// bob, invited by alice, bids 2 GNOT: alice is credited all the dividends and paid 10% as inviter at once113	a0 := held(alice)114	fund(2_000_000)115	testing.SetRealm(testing.NewUserRealm(bob))116	Bid(cross(cur), id, alice.String(), "us", 0)117	if owed(alice) != 600_000 || held(alice)-a0 != 200_000 || p.Pot != 950_000+2_000_000-600_000-200_000-100_000 {118		t.Fatalf("second bid: alice owed %d, +%d in hand, pot %d", owed(alice), held(alice)-a0, p.Pot)119	}120	if player(bob).Referrer != alice || player(alice).Recruits != 1 || p.find(alice).Earned != 600_000 {121		t.Fatalf("inviter: %+v", *player(bob))122	}123124	// bob rebids on himself, switching his flag to KR: allowed; his dividends go125	// to alice, not himself; his flag in THIS pool stays US (locked at his first bid)126	b1 := held(bob)127	fund(2_000_000)128	testing.SetRealm(testing.NewUserRealm(bob))129	Bid(cross(cur), id, "", "kr", 0)130	if p.LastBidder != bob || p.Bids != 3 || owed(alice) != 1_200_000 || owed(bob) != 0 || b1-held(bob) != 0 {131		t.Fatalf("self rebid: last %s bids %d alice owed %d bob owed %d", p.LastBidder, p.Bids, owed(alice), owed(bob))132	}133	if player(bob).Flag != "KR" || p.find(bob).Flag != "US" || p.History[2].Flag != "US" {134		t.Fatalf("flag lock: player %s member %s history %s", player(bob).Flag, p.find(bob).Flag, p.History[2].Flag)135	}136	conserved(t)137138	// alice withdraws what she is owed; a second withdraw has nothing139	a1 := held(alice)140	fund(0)141	testing.SetRealm(testing.NewUserRealm(alice))142	if got := Withdraw(cross(cur)); got != 1_200_000 || held(alice)-a1 != 1_200_000 || owed(alice) != 0 || player(alice).Withdrawn != 1_200_000 {143		t.Fatalf("withdraw: %d, +%d, owed %d", got, held(alice)-a1, owed(alice))144	}145	aborts(t, "nothing to withdraw", func() { Withdraw(cross(cur)) })146	conserved(t)147148	// in a new pool, bob's locked flag is his current one149	fund(0)150	testing.SetRealm(testing.NewUserRealm(dave))151	id2 := CreatePool(cross(cur), "next", 3600, 1_000_000, 0)152	fund(1_000_000)153	testing.SetRealm(testing.NewUserRealm(bob))154	Bid(cross(cur), id2, "", "", 0)155	if pool(id2).find(bob).Flag != "KR" {156		t.Fatalf("new pool flag %s", pool(id2).find(bob).Flag)157	}158}159160func TestBoostFloor(t *testing.T) {161	if c := ClockFor(3600, 100, 1000); c != 360 {162		t.Fatalf("ten times the price on an hour: %d", c)163	}164	if c := ClockFor(600, 100, 1000); c != MinClock {165		t.Fatalf("ten times the price on ten minutes floors at five: %d", c)166	}167	if c := ClockFor(60, 100, 100); c != 60 {168		t.Fatalf("a plain bid on a one-minute window keeps the window: %d", c)169	}170	if c := ClockFor(60, 100, 1000); c != 60 {171		t.Fatalf("no boost can shorten a window under the floor: %d", c)172	}173	if mulDiv(80_000_000_000, 3_000_000_000, 7_000_000_000) != 34_285_714_285 {174		t.Fatal("mulDiv on numbers whose product passes int64")175	}176}177178func TestLastDaySlidesToProRata(cur realm, t *testing.T) {179	reset()180	fund(0)181	testing.SetRealm(testing.NewUserRealm(dave))182	// a one-hour window and a 90-minute life: the life ends before the second clock would183	id := CreatePool(cross(cur), "slide", 3600, 1_000_000, 5400)184	p := pool(id)185	fund(1_000_000)186	testing.SetRealm(testing.NewUserRealm(alice))187	Bid(cross(cur), id, "", "FR", 0)188	if p.LiveBps() != 10000 {189		t.Fatalf("fresh pool live %d", p.LiveBps())190	}191	// 50 minutes pass (5s a block), bob bids exactly the price (more would shorten192	// his clock): 2400 of 5400 seconds of life are left193	testing.SkipHeights(600)194	fund(1_414_214)195	testing.SetRealm(testing.NewUserRealm(bob))196	Bid(cross(cur), id, "", "US", 0)197	if p.LiveBps() != 10000*2400/5400 {198		t.Fatalf("live %d", p.LiveBps())199	}200	if !p.Deadline().Equal(p.LastDay()) {201		t.Fatalf("deadline %v should be the last day %v, not the clock", p.Deadline(), p.LastDay())202	}203	// the last day comes with time still on bob's clock: the pool is over anyway204	testing.SkipHeights(600)205	if !p.Over() || p.LiveBps() != 0 {206		t.Fatalf("over %v live %d", p.Over(), p.LiveBps())207	}208	pot := p.Pot209	a0, b0 := owed(alice), owed(bob) // alice already holds dividends from bob's bid210	fund(0)211	testing.SetRealm(testing.NewUserRealm(carol))212	Claim(cross(cur), id)213	// bob (US) won alone under his flag, so the team share goes to the other bidder,214	// alice; nothing is live, so the rest is split by paid-in215	cut := pot * TeamShare / 100216	rest := pot - cut217	total := p.find(alice).Paid + p.find(bob).Paid218	wantA := cut + rest*p.find(alice).Paid/total219	if owed(alice)-a0 != wantA || owed(bob)-b0 != pot-wantA {220		t.Fatalf("alice +%d (want %d) bob +%d (want %d)", owed(alice)-a0, wantA, owed(bob)-b0, pot-wantA)221	}222	if p.TeamPaid != cut || p.SharedPaid != rest*p.find(alice).Paid/total || p.WinnerPaid != pot-wantA {223		t.Fatalf("bookkeeping: team %d shared %d winner %d", p.TeamPaid, p.SharedPaid, p.WinnerPaid)224	}225	conserved(t)226227	// a pool that closes early with a single bidder credits them everything228	reset()229	fund(0)230	testing.SetRealm(testing.NewUserRealm(dave))231	id = CreatePool(cross(cur), "quick", 60, 1_000_000, 7200)232	p = pool(id)233	fund(1_000_000)234	testing.SetRealm(testing.NewUserRealm(alice))235	Bid(cross(cur), id, "", "FR", 0)236	testing.SkipHeights(13) // 65s: the clock ran out, the life barely started237	fund(0)238	testing.SetRealm(testing.NewUserRealm(bob))239	Claim(cross(cur), id)240	if owed(alice) != p.Pot || p.SharedPaid != 0 || p.TeamPaid != 0 {241		t.Fatalf("solo: alice owed %d of %d, shared %d team %d", owed(alice), p.Pot, p.SharedPaid, p.TeamPaid)242	}243	conserved(t)244}245246func TestFlagWar(cur realm, t *testing.T) {247	reset()248	fund(0)249	testing.SetRealm(testing.NewUserRealm(dave))250	id := CreatePool(cross(cur), "team", 3600, 1_000_000, 0)251	p := pool(id)252	fund(20_000_000)253	testing.SetRealm(testing.NewUserRealm(dave))254	Bid(cross(cur), id, "", "FR", 0)255	fund(1_414_214)256	testing.SetRealm(testing.NewUserRealm(alice))257	Bid(cross(cur), id, "", "FR", 0)258	fund(1_732_051)259	testing.SetRealm(testing.NewUserRealm(carol))260	Bid(cross(cur), id, "", "US", 0)261	fund(2_000_000)262	testing.SetRealm(testing.NewUserRealm(alice))263	Bid(cross(cur), id, "", "", 0) // alice on top, FR264	testing.SkipHeights(721)265	pot := p.Pot266	live := p.LiveBps()267	if live == 0 || live == 10000 {268		t.Fatalf("live %d should be partway", live)269	}270	a0, d0, c0 := owed(alice), owed(dave), owed(carol)271	fund(0)272	testing.SetRealm(testing.NewUserRealm(bob))273	Claim(cross(cur), id)274	// the team share goes to dave, the only OTHER FR bidder; the winner takes none of it275	cut := pot * TeamShare / 100276	rest := pot - cut277	shared := rest - rest*live/10000278	total := p.find(alice).Paid + p.find(dave).Paid + p.find(carol).Paid279	daveShare, carolShare := shared*p.find(dave).Paid/total, shared*p.find(carol).Paid/total280	if owed(dave)-d0 != cut+daveShare {281		t.Fatalf("dave: +%d want team %d + share %d", owed(dave)-d0, cut, daveShare)282	}283	if owed(carol)-c0 != carolShare {284		t.Fatalf("carol (US) gets only her pro-rata share: +%d want %d", owed(carol)-c0, carolShare)285	}286	if owed(alice)-a0 != pot-cut-daveShare-carolShare {287		t.Fatalf("alice: +%d want %d", owed(alice)-a0, pot-cut-daveShare-carolShare)288	}289	if p.WinnerFlag != "FR" || teamWon["FR"] != pot || p.TeamPaid != cut || p.SharedPaid != daveShare+carolShare {290		t.Fatalf("bookkeeping: %+v", *p)291	}292	conserved(t)293	aborts(t, "already paid out", func() { Claim(cross(cur), id) })294295	// a private flag buys nothing: a winner nobody else flew with hands the team296	// share to all the other bidders, pro-rata297	reset()298	fund(0)299	testing.SetRealm(testing.NewUserRealm(dave))300	id = CreatePool(cross(cur), "lone", 60, 1_000_000, 0)301	p = pool(id)302	fund(1_000_000)303	testing.SetRealm(testing.NewUserRealm(alice))304	Bid(cross(cur), id, "", "FR", 0)305	fund(1_414_214)306	testing.SetRealm(testing.NewUserRealm(carol))307	Bid(cross(cur), id, "", "US", 0)308	fund(1_732_051)309	testing.SetRealm(testing.NewUserRealm(bob))310	Bid(cross(cur), id, "", "AQ", 0) // bob, alone under AQ, on top311	testing.SkipHeights(13)312	pot = p.Pot313	a0, c0, b0 := owed(alice), owed(carol), owed(bob)314	fund(0)315	testing.SetRealm(testing.NewUserRealm(dave))316	Claim(cross(cur), id)317	cut = pot * TeamShare / 100318	others := p.find(alice).Paid + p.find(carol).Paid319	// the life (ten minutes) had 65 seconds on it: a sliver of the rest is shared too320	rest = pot - cut321	shared = rest - rest*p.LiveBps()/10000322	total = others + p.find(bob).Paid323	wantA := cut*p.find(alice).Paid/others + shared*p.find(alice).Paid/total324	wantC := cut*p.find(carol).Paid/others + shared*p.find(carol).Paid/total325	if owed(alice)-a0 != wantA || owed(carol)-c0 != wantC || owed(bob)-b0 != pot-wantA-wantC {326		t.Fatalf("lone flag: alice +%d (want %d) carol +%d (want %d) bob +%d", owed(alice)-a0, wantA, owed(carol)-c0, wantC, owed(bob)-b0)327	}328	conserved(t)329}330331func TestShots(cur realm, t *testing.T) {332	reset()333	fund(0)334	testing.SetRealm(testing.NewUserRealm(dave))335	id := CreatePool(cross(cur), "shots", 3600, 1_000_000, 0)336	p := pool(id)337	fund(5_000_000)338	testing.SetRealm(testing.NewUserRealm(dave))339	Bid(cross(cur), id, "", "", 0)340341	// the shot must leave the price covered, and must buy some chance342	fund(1_000_000)343	testing.SetRealm(testing.NewUserRealm(alice))344	aborts(t, "for the bid", func() { Bid(cross(cur), id, "", "", 500_000) })345	fund(1_414_214 + 1)346	testing.SetRealm(testing.NewUserRealm(alice))347	aborts(t, "too small for any chance", func() { Bid(cross(cur), id, "", "", 1) })348	heldAtReset += 1_000_000 + 1_414_215 // coins attached to the two aborted calls never reach a real realm349350	// a 0.1 GNOT shot at a ~6 GNOT pot: 0.8 × 0.1/6.7 ≈ 1.2%, capped at 1%; the shot goes into the pot whole351	pot0 := p.Pot352	fund(1_414_214 + 100_000)353	testing.SetRealm(testing.NewUserRealm(alice))354	Bid(cross(cur), id, "", "", 100_000)355	if len(p.Shots) != 1 || p.Shots[0].Bps != ShotMaxBps || p.Shots[0].Resolved {356		t.Fatalf("shot: %+v", *p.Shots[0])357	}358	// dividends (30%) credited to dave, the inviter's 10% back into the pot, creator 5% out, the shot in whole359	if toPot := int64(1_414_214 - 424_264 - 141_421 - 70_710 + 141_421); p.Pot != pot0+toPot+100_000 || owed(dave) != 424_264 {360		t.Fatalf("pot %d, dave owed %d", p.Pot, owed(dave))361	}362	sh := p.Shots[0]363	if sh.Prize != p.Pot || sh.Prize > ShotCap || p.find(alice).Paid != 1_414_214+100_000 {364		t.Fatalf("prize %d pot %d paid %d", sh.Prize, p.Pot, p.find(alice).Paid)365	}366	// the odds of a small shot: 0.02 GNOT at 5 GNOT is 0.8 × 0.02/5 = 0.32%367	if bps := ShotBps(20_000, 5_000_000); bps != 32 {368		t.Fatalf("bps %d", bps)369	}370	// not drawn before the delay371	Draw(cross(cur), id)372	if sh.Resolved {373		t.Fatal("drawn too early")374	}375	testing.SkipHeights(ShotDelay)376	h := runtime.ChainHeight()377	fund(0)378	testing.SetRealm(testing.NewUserRealm(bob))379	want := shotHits(seedFor(id, sh, h), sh.Bps) // the same inputs the realm hashes380	Draw(cross(cur), id)381	if !sh.Resolved || sh.DrawH != h || sh.Won != want {382		t.Fatalf("drawn: %+v want hit=%v", *sh, want)383	}384	if want {385		if !p.Popped || p.LastBidder != alice || !p.Over() || p.Misses != 0 {386			t.Fatalf("a hit pops: %+v", *p)387		}388	} else if p.Popped || p.LastBidder != alice || p.Misses != 1 {389		t.Fatalf("a miss is counted and changes nothing else: %+v", *p)390	}391	Draw(cross(cur), id) // a second draw is a no-op392	if p.Misses > 1 {393		t.Fatal("drawn twice")394	}395	conserved(t)396397	// the draw itself: over every seed in a cycle, the chance is exactly bps in 10000398	hits := 0399	for s := uint64(0); s < 10000; s++ {400		if shotHits(s, 32) {401			hits++402		}403	}404	if hits != 32 {405		t.Fatalf("32 bps should hit 32 of 10000 seeds, got %d", hits)406	}407408	// a shot nobody draws in its window is void: counted as a miss, never a hit409	reset()410	fund(0)411	testing.SetRealm(testing.NewUserRealm(dave))412	id = CreatePool(cross(cur), "late", 3600, 1_000_000, 0)413	p = pool(id)414	fund(5_000_000)415	testing.SetRealm(testing.NewUserRealm(dave))416	Bid(cross(cur), id, "", "", 0)417	fund(1_414_214 + 1_000_000)418	testing.SetRealm(testing.NewUserRealm(alice))419	Bid(cross(cur), id, "", "", 1_000_000) // 1% anyway420	testing.SkipHeights(ShotDelay + ShotWindow + 1)421	Draw(cross(cur), id)422	if !p.Shots[0].Resolved || p.Shots[0].Won || p.Misses != 1 || p.Popped {423		t.Fatalf("void: %+v misses %d", *p.Shots[0], p.Misses)424	}425	// the last block of the window still draws426	fund(1_732_051 + 1_000_000)427	testing.SetRealm(testing.NewUserRealm(alice))428	Bid(cross(cur), id, "", "", 1_000_000)429	testing.SkipHeights(ShotDelay + ShotWindow)430	Draw(cross(cur), id)431	if !p.Shots[1].Resolved || p.Shots[1].DrawH != runtime.ChainHeight() {432		t.Fatalf("window end: %+v", *p.Shots[1])433	}434	conserved(t)435436	// the switch: only the admin, and a shot is refused while it is off437	fund(0)438	testing.SetRealm(testing.NewUserRealm(alice))439	aborts(t, "only the deployer", func() { SetShots(cross(cur), false) })440	fund(0)441	testing.SetRealm(testing.NewUserRealm(dave))442	SetShots(cross(cur), false)443	fund(2_000_000 + 1_000_000)444	testing.SetRealm(testing.NewUserRealm(alice))445	aborts(t, "shots are off", func() { Bid(cross(cur), id, "", "", 1_000_000) })446	if !strings.Contains(Render("json"), "\"shotsOn\":false") {447		t.Error("json should say shots are off")448	}449}450451func TestSliceOnABigPot(cur realm, t *testing.T) {452	reset()453	fund(0)454	testing.SetRealm(testing.NewUserRealm(dave))455	id := CreatePool(cross(cur), "big", 3600, 1_000_000, 0)456	p := pool(id)457	fund(50 * ShotCap) // 500 GNOT458	testing.SetRealm(testing.NewUserRealm(dave))459	Bid(cross(cur), id, "", "", 0)460	fund(1_414_214 + 1_000_000) // a 1 GNOT shot: 0.8 × 1/10 = 8%, capped at 1% of a 10 GNOT slice461	testing.SetRealm(testing.NewUserRealm(alice))462	Bid(cross(cur), id, "", "", 1_000_000)463	sh := p.Shots[0]464	if sh.Prize != ShotCap || sh.Bps != ShotMaxBps {465		t.Fatalf("shot at a big pot: %+v", *sh)466	}467	testing.SkipHeights(ShotDelay)468	h := runtime.ChainHeight()469	fund(0)470	testing.SetRealm(testing.NewUserRealm(bob))471	pot0, a0 := p.Pot, owed(alice)472	hit := shotHits(seedFor(id, sh, h), sh.Bps)473	Draw(cross(cur), id)474	if hit {475		if p.Popped || p.Pot != pot0-ShotCap || owed(alice)-a0 != ShotCap || p.ShotPaid != ShotCap {476			t.Fatalf("slice: %+v alice +%d", *p, owed(alice)-a0)477		}478	} else if p.Pot != pot0 || owed(alice) != a0 || p.Misses != 1 {479		t.Fatalf("a miss at a big pot changes nothing but the tally: misses %d", p.Misses)480	}481	conserved(t)482}483484func TestBidAfterPopRefunds(cur realm, t *testing.T) {485	reset()486	fund(0)487	testing.SetRealm(testing.NewUserRealm(dave))488	id := CreatePool(cross(cur), "pop", 3600, 1_000_000, 0)489	p := pool(id)490	fund(1_000_000)491	testing.SetRealm(testing.NewUserRealm(dave))492	Bid(cross(cur), id, "", "", 0)493	// a shot that cannot miss is due: the next bid draws it, finds the pool popped, and refunds494	p.Shots = append(p.Shots, &Shot{N: 1, Bidder: alice, Paid: 1, Prize: p.Pot, Bps: 10000, CommitH: runtime.ChainHeight() - ShotDelay})495	b0 := held(bob)496	fund(2_000_000)497	testing.SetRealm(testing.NewUserRealm(bob))498	Bid(cross(cur), id, "", "", 0)499	if !p.Popped || p.LastBidder != alice || p.Bids != 1 || held(bob)-b0 != 2_000_000 { // refunded in full500		t.Fatalf("after pop: %+v bob %d→%d", *p, b0, held(bob))501	}502	conserved(t)503	// and the pop credits alice as the winner; dave, the only other bidder, is the team504	fund(0)505	testing.SetRealm(testing.NewUserRealm(carol))506	Claim(cross(cur), id)507	cut := p.Pot * TeamShare / 100508	if owed(alice) != p.Pot-cut || p.TeamPaid != cut || owed(dave) != cut {509		t.Fatalf("popped payout: alice owed %d of %d, team %d, dave owed %d", owed(alice), p.Pot, p.TeamPaid, owed(dave))510	}511	conserved(t)512513	// a closed, unpopped pool refuses a bid outright514	reset()515	fund(0)516	testing.SetRealm(testing.NewUserRealm(dave))517	id = CreatePool(cross(cur), "closed", 60, 1_000_000, 0)518	fund(1_000_000)519	testing.SetRealm(testing.NewUserRealm(dave))520	Bid(cross(cur), id, "", "", 0)521	testing.SkipHeights(13)522	fund(2_000_000)523	testing.SetRealm(testing.NewUserRealm(bob))524	aborts(t, "pool is closed", func() { Bid(cross(cur), id, "", "", 0) })525}526527func TestRosterCapAndCreateBounds(cur realm, t *testing.T) {528	reset()529	fund(0)530	testing.SetRealm(testing.NewUserRealm(dave))531	id := CreatePool(cross(cur), "cap", 3600, 1_000, 0)532	p := pool(id)533	p.Roster = nil534	for i := 0; i < RosterMax; i++ {535		p.Roster = append(p.Roster, &Member{Addr: address("g1x" + itoa(int64(i))), Paid: 1})536	}537	fund(1_000)538	testing.SetRealm(testing.NewUserRealm(alice))539	Bid(cross(cur), id, "", "", 0)540	if len(p.Roster) != RosterMax || p.Roster[len(p.Roster)-1].Addr != alice || p.Roster[0].Addr != address("g1x1") {541		t.Fatalf("roster cap: len %d first %s last %s", len(p.Roster), p.Roster[0].Addr, p.Roster[len(p.Roster)-1].Addr)542	}543	fund(0)544	testing.SetRealm(testing.NewUserRealm(dave))545	aborts(t, "life must be at least the window", func() { CreatePool(cross(cur), "x", 7200, 1_000_000, 3600) })546	aborts(t, "life must be between", func() { CreatePool(cross(cur), "x", 3600, 1_000_000, 31*24*3600) })547	id = CreatePool(cross(cur), "x", 20*24*3600, 1_000_000, 0)548	if pool(id).Life != MaxLife {549		t.Fatalf("ten windows past the maximum life is the maximum: %d", pool(id).Life)550	}551	id = CreatePool(cross(cur), "y", 10, 1_000_000, 0)552	if pool(id).Life != MinLife {553		t.Fatalf("ten windows under the minimum life is the minimum: %d", pool(id).Life)554	}555}556557func TestRender(cur realm, t *testing.T) {558	reset()559	fund(0)560	testing.SetRealm(testing.NewUserRealm(dave))561	id := CreatePool(cross(cur), "r", 3600, 1_000_000, 0)562	fund(1_000_000)563	testing.SetRealm(testing.NewUserRealm(alice))564	Bid(cross(cur), id, "", "FR", 0)565	fund(1_414_214 + 200_000)566	testing.SetRealm(testing.NewUserRealm(bob))567	Bid(cross(cur), id, alice.String(), "US", 200_000)568	all := Render("json")569	for _, want := range []string{"\"split\":{\"pot\":55,\"div\":30,\"ref\":10,\"creator\":5,\"team\":35,\"roster\":100,\"shotCap\":10000000,\"shotMaxBps\":100,\"shotFair\":8000,\"shotDelay\":3,\"shotWindow\":2,\"minClock\":300}", "\"shotsOn\":true", "\"bidders\":2", "\"flag\":\"US\"", "\"liveBps\":", "\"lastDay\":", "\"shots\":1,\"misses\":0,\"pending\":1", "\"height\":"} {570		if !strings.Contains(all, want) {571			t.Errorf("json missing %s in %s", want, all)572		}573	}574	one := Render("json/1")575	if !strings.Contains(one, "\"shot\":200000") || !strings.Contains(one, "\"shotlog\":[{\"n\":1") || !strings.Contains(one, "\"hit\":false") {576		t.Errorf("detail: %s", one)577	}578	me := Render("me/" + alice.String())579	if !strings.Contains(me, "\"owed\":424264") || !strings.Contains(me, "\"withdrawn\":0") {580		t.Errorf("me: %s", me)581	}582	if !strings.Contains(Render(""), "last day") || !strings.Contains(Render("1"), "a 0.2 GNOT shot") {583		t.Errorf("markdown: %s", Render("1"))584	}585}586

Functions

  • Bid(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id int64, ref string, flag string, shot int64)

  • Cancel(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id int64)

  • Claim(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id int64)

  • ClockFor(window int64, price int64, sent int64) int64

  • CreatePool(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, name string, window int64, basePrice int64, life int64) int64

  • Draw(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id int64)

  • GNOT(ugnot int64) string

  • Owed(a string) int64

  • Price(base int64, n int64) int64

  • Render(path string) string

  • SetShots(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, on bool)

  • ShotBps(shot int64, prize int64) int64

  • Withdraw(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}) int64

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

Rendered

RenderedRawgnoweb ↗

vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.