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/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/crowdfund

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
crowdfund
Namespace
g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39
Files
3 (gnomod.toml)
Exported functions
28
Module
gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/crowdfund
gno
0.9

Files (3)

  • gnomod.tomltoml
  • crowdfund.gnogno
  • crowdfund_test.gnogno
crowdfund_test.gnogno
1package crowdfund23import (4	"strings"5	"testing"67	"chain"89	"gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/coinio"10	"gno.land/p/nt/testutils/v0"11	"gno.land/p/nt/uassert/v0"12)1314var (15	creator1 = testutils.TestAddress("cfcreator1")16	creator2 = testutils.TestAddress("cfcreator2")17	backerA  = testutils.TestAddress("cfbackera")18	backerB  = testutils.TestAddress("cfbackerb")19	mallory  = testutils.TestAddress("cfmallory")20)2122// HARNESS NOTES, carried from the treasury_board, vesting and grants23// suites (measured there, relied on here):24//25//   - testing.SetRealm records the override per FRAME INDEX: stage the26//     caller INLINE in the test function's frame before a uassert call,27//     or inside a helper that stages and calls in one frame of its own.28//     Never inside a uassert closure — that staging does not take.29//30//   - testing.SkipHeights REPLACES the whole context (OriginCaller,31//     CurrentRealm, OriginSend included). Re-stage after every skip.32//33//   - Realm globals PERSIST across tests while banker state resets per34//     test. begin() disarms any leftover envelope and re-seeds the35//     realm's bank to its carried liabilities, so conservation is36//     asserted in DELTA form.37//38//   - A panic caught by uassert does NOT roll back realm globals here,39//     whereas on-chain the whole transaction reverts. Every abort40//     asserted below happens strictly BEFORE this realm writes state,41//     with one measured exception documented inline42//     (TestClaimTimingIsTheDuebooksVerdict).43//44//   - The SEND envelope is process-global: a staged envelope must be45//     cleared (SetOriginSend(nil)) before the next non-payable call, or46//     rejectStraySend fires and the test asserts the harness leak, not47//     the guard.48func init() {49	self = chain.PackageAddress("gno.land/r/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/crowdfund")50}5152type baseline struct {53	held   int6454	backed int6455	fees   int6456}5758func begin() baseline {59	testing.SetOriginSend(nil)60	if l := Liabilities(); l > 0 {61		testing.IssueCoins(Address(), ugnot(l))62	}63	return baseline{held: Held(), backed: TotalBacked(), fees: FeesAccrued()}64}6566func ugnot(n int64) chain.Coins {67	return chain.NewCoins(chain.NewCoin(Denom, n))68}6970// launchAs stages `from` and launches a MinGoal x MinDuration campaign.71func launchAs(cur realm, from address, title string) int64 {72	testing.SetRealm(testing.NewUserRealm(from))73	return Launch(cross(cur), title, "", MinGoal, MinDurationBlocks)74}7576// pledgeAs stages a direct EOA call with -send and mirrors the77// envelope into the realm's bank, as the chain would.78func pledgeAs(cur realm, from address, id, amount int64) {79	testing.SetRealm(testing.NewUserRealm(from))80	testing.SetOriginSend(ugnot(amount))81	testing.IssueCoins(Address(), ugnot(amount))82	Pledge(cross(cur), id)83	testing.SetOriginSend(nil)84}8586func balanceOf(addr address) int64 {87	return coinio.HeldAt(addr, Denom)88}8990// --- launch ---9192func TestLaunchBasics(cur realm, t *testing.T) {93	begin()94	before := NumCampaigns()95	openBefore := OpenCampaigns()9697	id := launchAs(cur, creator1, "Solar panels for the hackerspace")98	uassert.Equal(t, before+1, id)99	uassert.Equal(t, before+1, NumCampaigns())100	uassert.Equal(t, openBefore+1, OpenCampaigns())101	uassert.Equal(t, int64(1), OpenOf(creator1))102103	cr, goal, raised, feeBps, createdAt, deadline, claimEnd, backers, state := CampaignInfo(id)104	uassert.Equal(t, creator1, cr)105	uassert.Equal(t, MinGoal, goal)106	uassert.Equal(t, int64(0), raised)107	uassert.Equal(t, SuccessFeeBps, feeBps)108	uassert.Equal(t, createdAt+MinDurationBlocks, deadline)109	uassert.Equal(t, deadline+ClaimWindowBlocks, claimEnd)110	uassert.Equal(t, 0, backers)111	uassert.Equal(t, stateFunding, state)112	uassert.Equal(t, stateFunding, Status(id))113114	// Clean up the open slot so later per-creator-cap tests see a known115	// count.116	testing.SetRealm(testing.NewUserRealm(creator1))117	CreatorCancel(cross(cur), id)118	uassert.Equal(t, int64(0), OpenOf(creator1))119}120121func TestLaunchValidation(cur realm, t *testing.T) {122	begin()123	testing.SetRealm(testing.NewUserRealm(creator1))124	uassert.AbortsWithMessage(t, cur, "title must not be empty", func() {125		Launch(cross(cur), "", "", MinGoal, MinDurationBlocks)126	})127	testing.SetRealm(testing.NewUserRealm(creator1))128	uassert.AbortsWithMessage(t, cur, "title exceeds 100 bytes", func() {129		Launch(cross(cur), strings.Repeat("x", MaxTitleLen+1), "", MinGoal, MinDurationBlocks)130	})131	testing.SetRealm(testing.NewUserRealm(creator1))132	uassert.AbortsWithMessage(t, cur, "memo exceeds 256 bytes", func() {133		Launch(cross(cur), "t", strings.Repeat("x", MaxMemoLen+1), MinGoal, MinDurationBlocks)134	})135	testing.SetRealm(testing.NewUserRealm(creator1))136	uassert.AbortsWithMessage(t, cur, "goal must be at least 1000000ugnot", func() {137		Launch(cross(cur), "t", "", MinGoal-1, MinDurationBlocks)138	})139	// Duration bounds are the duebook's own validation, not a check in140	// this realm — asserted against the primitive's error text.141	testing.SetRealm(testing.NewUserRealm(creator1))142	uassert.AbortsWithMessage(t, cur, "duebook: delay outside [minDelay, maxDelay]", func() {143		Launch(cross(cur), "t", "", MinGoal, MinDurationBlocks-1)144	})145	testing.SetRealm(testing.NewUserRealm(creator1))146	uassert.AbortsWithMessage(t, cur, "duebook: delay outside [minDelay, maxDelay]", func() {147		Launch(cross(cur), "t", "", MinGoal, MaxDurationBlocks+1)148	})149	uassert.Equal(t, int64(0), OpenOf(creator1))150}151152func TestLaunchPerCreatorCap(cur realm, t *testing.T) {153	begin()154	uassert.Equal(t, int64(0), OpenOf(creator2))155	ids := []int64{}156	for i := int64(0); i < MaxOpenPerCreator; i++ {157		ids = append(ids, launchAs(cur, creator2, "cap filler"))158	}159	uassert.Equal(t, MaxOpenPerCreator, OpenOf(creator2))160161	testing.SetRealm(testing.NewUserRealm(creator2))162	uassert.AbortsWithMessage(t, cur, "creator already has 8 unsettled campaigns", func() {163		Launch(cross(cur), "one too many", "", MinGoal, MinDurationBlocks)164	})165166	// A settled campaign releases its slot.167	testing.SetRealm(testing.NewUserRealm(creator2))168	CreatorCancel(cross(cur), ids[0])169	uassert.Equal(t, MaxOpenPerCreator-1, OpenOf(creator2))170	idNew := launchAs(cur, creator2, "slot reopened")171	uassert.Equal(t, MaxOpenPerCreator, OpenOf(creator2))172173	// Drain the slots so later tests start from a clean count.174	for _, id := range append(ids[1:], idNew) {175		testing.SetRealm(testing.NewUserRealm(creator2))176		CreatorCancel(cross(cur), id)177	}178	uassert.Equal(t, int64(0), OpenOf(creator2))179}180181// --- pledging ---182183func TestPledgeAndConservation(cur realm, t *testing.T) {184	b := begin()185	id := launchAs(cur, creator1, "conservation case")186187	pledgeAs(cur, backerA, id, 400_000)188	pledgeAs(cur, backerB, id, 250_000)189	pledgeAs(cur, backerA, id, 100_000) // same backer accumulates190191	uassert.Equal(t, int64(500_000), PledgeOf(id, backerA))192	uassert.Equal(t, int64(250_000), PledgeOf(id, backerB))193	_, _, raised, _, _, _, _, backers, _ := CampaignInfo(id)194	uassert.Equal(t, int64(750_000), raised)195	uassert.Equal(t, 2, backers)196197	// Conservation, delta form: every pledged coin is in B and in H.198	uassert.Equal(t, b.backed+750_000, TotalBacked())199	uassert.Equal(t, b.held+750_000, Held())200	uassert.Equal(t, b.fees, FeesAccrued())201	uassert.True(t, Held() >= Liabilities())202203	// Clean up: back out both pledges, cancel.204	testing.SetRealm(testing.NewUserRealm(backerA))205	Unpledge(cross(cur), id)206	testing.SetRealm(testing.NewUserRealm(backerB))207	Unpledge(cross(cur), id)208	uassert.Equal(t, b.backed, TotalBacked())209	testing.SetRealm(testing.NewUserRealm(creator1))210	CreatorCancel(cross(cur), id)211}212213func TestPledgeGuards(cur realm, t *testing.T) {214	begin()215	id := launchAs(cur, creator1, "guard case")216217	// The payment-guard regression: a realm-routed pledge is refused by218	// coinio's receipt shape, not by anything this file could forget.219	testing.SetRealm(testing.NewCodeRealm("gno.land/r/demo/attacker"))220	testing.SetOriginSend(ugnot(1000))221	uassert.AbortsWithMessage(t, cur,222		"coinio: payment must be a direct EOA call with -send (realms and maketx-run are rejected)",223		func() { Pledge(cross(cur), id) })224	testing.SetOriginSend(nil)225226	// Wrong denomination.227	testing.SetRealm(testing.NewUserRealm(backerA))228	testing.SetOriginSend(chain.NewCoins(chain.NewCoin("foocoin", 1000)))229	uassert.AbortsWithMessage(t, cur, "coinio: send exactly one coin type: ugnot", func() {230		Pledge(cross(cur), id)231	})232	testing.SetOriginSend(nil)233234	// No envelope at all.235	testing.SetRealm(testing.NewUserRealm(backerA))236	uassert.AbortsWithMessage(t, cur, "coinio: send exactly one coin type: ugnot", func() {237		Pledge(cross(cur), id)238	})239240	// Unknown campaign.241	testing.SetRealm(testing.NewUserRealm(backerA))242	testing.SetOriginSend(ugnot(1000))243	uassert.AbortsWithMessage(t, cur, "unknown campaign id", func() {244		Pledge(cross(cur), 999_999)245	})246	testing.SetOriginSend(nil)247248	// Below the pledge minimum (audit open question 1: dust-entry249	// bloat); exactly the minimum is accepted.250	testing.SetRealm(testing.NewUserRealm(backerA))251	testing.SetOriginSend(ugnot(MinPledge - 1))252	testing.IssueCoins(Address(), ugnot(MinPledge-1))253	uassert.AbortsWithMessage(t, cur, "pledge at least 10000ugnot", func() {254		Pledge(cross(cur), id)255	})256	testing.SetOriginSend(nil)257	pledgeAs(cur, backerA, id, MinPledge)258	uassert.Equal(t, MinPledge, PledgeOf(id, backerA))259	testing.SetRealm(testing.NewUserRealm(backerA))260	Unpledge(cross(cur), id)261262	// Settled campaign.263	testing.SetRealm(testing.NewUserRealm(creator1))264	CreatorCancel(cross(cur), id)265	testing.SetRealm(testing.NewUserRealm(backerA))266	testing.SetOriginSend(ugnot(1000))267	uassert.AbortsWithMessage(t, cur, "campaign is settled", func() {268		Pledge(cross(cur), id)269	})270	testing.SetOriginSend(nil)271}272273func TestPledgeClosesAtDeadline(cur realm, t *testing.T) {274	begin()275	id := launchAs(cur, creator1, "deadline case")276	testing.SkipHeights(MinDurationBlocks) // height == deadline exactly277278	testing.SetRealm(testing.NewUserRealm(backerA))279	testing.SetOriginSend(ugnot(1000))280	testing.IssueCoins(Address(), ugnot(1000))281	uassert.AbortsWithMessage(t, cur, "funding is closed", func() {282		Pledge(cross(cur), id)283	})284	testing.SetOriginSend(nil)285286	// Unpledge is likewise closed at the deadline (the failed path is287	// Refund's).288	testing.SetRealm(testing.NewUserRealm(backerA))289	uassert.AbortsWithMessage(t, cur,290		"funding is closed; use Refund if the campaign failed",291		func() { Unpledge(cross(cur), id) })292293	// Failed (0 < goal) and past deadline: refunds open, nothing to pay.294	uassert.True(t, IsRefundable(id))295	uassert.Equal(t, "failed (refunds open)", Status(id))296297	testing.SetRealm(testing.NewUserRealm(creator1))298	CreatorCancel(cross(cur), id)299}300301// --- unpledge ---302303func TestUnpledgeReturnsWholePledge(cur realm, t *testing.T) {304	b := begin()305	id := launchAs(cur, creator1, "unpledge case")306	pledgeAs(cur, backerA, id, 300_000)307308	walletBefore := balanceOf(backerA)309	testing.SetRealm(testing.NewUserRealm(backerA))310	Unpledge(cross(cur), id)311312	uassert.Equal(t, walletBefore+300_000, balanceOf(backerA))313	uassert.Equal(t, int64(0), PledgeOf(id, backerA))314	uassert.Equal(t, b.backed, TotalBacked())315	uassert.Equal(t, b.held, Held())316317	// Nothing left to unpledge.318	testing.SetRealm(testing.NewUserRealm(backerA))319	uassert.AbortsWithMessage(t, cur, "no pledge to return", func() {320		Unpledge(cross(cur), id)321	})322	testing.SetRealm(testing.NewUserRealm(creator1))323	CreatorCancel(cross(cur), id)324}325326// --- claim ---327328func TestClaimPaysCreatorMinusFee(cur realm, t *testing.T) {329	b := begin()330	id := launchAs(cur, creator1, "funded project")331	pledgeAs(cur, backerA, id, 900_000)332	pledgeAs(cur, backerB, id, 350_000) // raised 1_250_000 >= goal 1_000_000333334	testing.SkipHeights(MinDurationBlocks)335	uassert.Equal(t, "succeeded (awaiting creator claim)", Status(id))336	uassert.False(t, IsRefundable(id)) // the pot is the creator's to collect337338	// A backer cannot refund out of a succeeded campaign in-window.339	testing.SetRealm(testing.NewUserRealm(backerA))340	uassert.AbortsWithMessage(t, cur, "campaign is not refundable", func() {341		Refund(cross(cur), id)342	})343344	// Only the creator may claim.345	testing.SetRealm(testing.NewUserRealm(mallory))346	uassert.AbortsWithMessage(t, cur, "creator only", func() {347		CreatorClaim(cross(cur), id)348	})349350	walletBefore := balanceOf(creator1)351	openBefore := OpenCampaigns()352	testing.SetRealm(testing.NewUserRealm(creator1))353	CreatorClaim(cross(cur), id)354355	// fee = floor(1_250_000 * 100 / 10_000) = 12_500; credited the rest.356	uassert.Equal(t, walletBefore+1_237_500, balanceOf(creator1))357	uassert.Equal(t, b.fees+12_500, FeesAccrued())358	uassert.Equal(t, b.backed, TotalBacked())359	uassert.Equal(t, statePaid, Status(id))360	uassert.Equal(t, openBefore-1, OpenCampaigns())361	uassert.Equal(t, int64(0), OpenOf(creator1))362363	// The pledge table is dropped; the final backer count is frozen.364	uassert.Equal(t, int64(0), PledgeOf(id, backerA))365	_, _, raised, _, _, _, _, backers, state := CampaignInfo(id)366	uassert.Equal(t, int64(0), raised)367	uassert.Equal(t, 2, backers)368	uassert.Equal(t, statePaid, state)369370	// Settlement is exactly-once: the deferral is consumed.371	testing.SetRealm(testing.NewUserRealm(creator1))372	uassert.AbortsWithMessage(t, cur, "campaign is settled", func() {373		CreatorClaim(cross(cur), id)374	})375	// Conservation: H gained the fee, B is flat, the payout left.376	uassert.Equal(t, b.held+12_500, Held())377	uassert.True(t, Held() >= Liabilities())378}379380func TestClaimFeeRoundingFavorsCreator(cur realm, t *testing.T) {381	b := begin()382	testing.SetRealm(testing.NewUserRealm(creator1))383	id := Launch(cross(cur), "rounding case", "", MinGoal, MinDurationBlocks)384	// 1_000_050 at 100 bps: exact fee 10_000.5 -> floor 10_000.385	pledgeAs(cur, backerA, id, 1_000_050)386	testing.SkipHeights(MinDurationBlocks)387388	walletBefore := balanceOf(creator1)389	testing.SetRealm(testing.NewUserRealm(creator1))390	CreatorClaim(cross(cur), id)391	uassert.Equal(t, walletBefore+990_050, balanceOf(creator1))392	uassert.Equal(t, b.fees+10_000, FeesAccrued())393}394395func TestClaimGoalNotReached(cur realm, t *testing.T) {396	begin()397	id := launchAs(cur, creator1, "underfunded")398	pledgeAs(cur, backerA, id, MinGoal-1)399	testing.SkipHeights(MinDurationBlocks)400401	testing.SetRealm(testing.NewUserRealm(creator1))402	uassert.AbortsWithMessage(t, cur, "goal not reached", func() {403		CreatorClaim(cross(cur), id)404	})405406	// The failed pot is refundable immediately — no settlement needed.407	uassert.True(t, IsRefundable(id))408	testing.SetRealm(testing.NewUserRealm(backerA))409	Refund(cross(cur), id)410	testing.SetRealm(testing.NewUserRealm(mallory))411	SettleFailed(cross(cur), id) // anyone closes it out; slot freed412}413414func TestClaimTimingIsTheDuebooksVerdict(cur realm, t *testing.T) {415	begin()416	id := launchAs(cur, creator1, "timing case")417	pledgeAs(cur, backerA, id, MinGoal)418419	// Before the deadline: the duebook refuses, and on-chain the whole420	// transaction would revert. In the test VM the abort does not roll421	// back realm globals, but MustClaim aborts BEFORE this realm writes422	// any state, so the campaign is observably untouched either way —423	// asserted below.424	testing.SetRealm(testing.NewUserRealm(creator1))425	uassert.AbortsWithMessage(t, cur, "duebook: not due yet", func() {426		CreatorClaim(cross(cur), id)427	})428	uassert.Equal(t, stateFunding, Status(id)) // still funding, untouched429	uassert.Equal(t, MinGoal, PledgeOf(id, backerA))430431	// Past the claim window: expired, the creator's authorization died.432	testing.SkipHeights(MinDurationBlocks + ClaimWindowBlocks)433	testing.SetRealm(testing.NewUserRealm(creator1))434	uassert.AbortsWithMessage(t, cur, "duebook: deferral has expired", func() {435		CreatorClaim(cross(cur), id)436	})437438	// Which is exactly when Lapse starts working — by anyone.439	testing.SetRealm(testing.NewUserRealm(mallory))440	Lapse(cross(cur), id)441	uassert.Equal(t, stateLapsed, Status(id))442	uassert.True(t, IsRefundable(id))443444	testing.SetRealm(testing.NewUserRealm(backerA))445	Refund(cross(cur), id)446}447448// --- settle-failed (audit Y1 remediation) ---449450func TestSettleFailedFreesTheSlotAtTheDeadline(cur realm, t *testing.T) {451	begin()452	id := launchAs(cur, creator1, "doomed project")453	pledgeAs(cur, backerA, id, MinGoal-10_000)454455	// Not before the deadline — the timing verdict is the duebook's.456	testing.SetRealm(testing.NewUserRealm(mallory))457	uassert.AbortsWithMessage(t, cur, "duebook: not due yet", func() {458		SettleFailed(cross(cur), id)459	})460461	testing.SkipHeights(MinDurationBlocks)462	openBefore := OpenCampaigns()463	uassert.Equal(t, int64(1), OpenOf(creator1))464465	// Anyone may settle a failed campaign the moment it is due.466	testing.SetRealm(testing.NewUserRealm(mallory))467	SettleFailed(cross(cur), id)468	uassert.Equal(t, stateFailed, Status(id))469	uassert.Equal(t, openBefore-1, OpenCampaigns()) // duebook slot freed470	uassert.Equal(t, int64(0), OpenOf(creator1))    // creator slot freed471	uassert.True(t, IsRefundable(id))472473	// Settlement is exactly-once here too.474	testing.SetRealm(testing.NewUserRealm(mallory))475	uassert.AbortsWithMessage(t, cur, "campaign is settled", func() {476		SettleFailed(cross(cur), id)477	})478	// And the creator cannot claim a settled campaign.479	testing.SetRealm(testing.NewUserRealm(creator1))480	uassert.AbortsWithMessage(t, cur, "campaign is settled", func() {481		CreatorClaim(cross(cur), id)482	})483484	// Refunds work exactly as on the un-settled failed path, and the485	// drained record is prunable.486	walletBefore := balanceOf(backerA)487	testing.SetRealm(testing.NewUserRealm(backerA))488	Refund(cross(cur), id)489	uassert.Equal(t, walletBefore+MinGoal-10_000, balanceOf(backerA))490	testing.SetRealm(testing.NewUserRealm(mallory))491	Prune(cross(cur), id)492}493494func TestSettleFailedExpiresIntoLapse(cur realm, t *testing.T) {495	begin()496	id := launchAs(cur, creator1, "never settled")497	pledgeAs(cur, backerA, id, MinPledge)498	testing.SkipHeights(MinDurationBlocks + ClaimWindowBlocks)499500	// Past the claim window the deferral is expired: SettleFailed's501	// path is gone (the primitive's verdict) and Lapse is the valve —502	// which is exactly what the campaign page now advises (audit G1).503	page := Render(itoa(id))504	uassert.True(t, strings.Contains(page, "Lapse("+itoa(id)+")"))505	uassert.False(t, strings.Contains(page, "SettleFailed("))506507	testing.SetRealm(testing.NewUserRealm(mallory))508	uassert.AbortsWithMessage(t, cur, "duebook: deferral has expired", func() {509		SettleFailed(cross(cur), id)510	})511	testing.SetRealm(testing.NewUserRealm(mallory))512	Lapse(cross(cur), id)513	uassert.Equal(t, stateLapsed, Status(id))514	testing.SetRealm(testing.NewUserRealm(backerA))515	Refund(cross(cur), id)516}517518func TestSettleFailedRefusesAMetGoal(cur realm, t *testing.T) {519	begin()520	id := launchAs(cur, creator1, "funded, not failed")521	pledgeAs(cur, backerA, id, MinGoal)522	testing.SkipHeights(MinDurationBlocks)523524	testing.SetRealm(testing.NewUserRealm(mallory))525	uassert.AbortsWithMessage(t, cur,526		"goal reached; the claim window is the creator's",527		func() { SettleFailed(cross(cur), id) })528529	testing.SetRealm(testing.NewUserRealm(creator1))530	CreatorClaim(cross(cur), id)531}532533// --- cancel ---534535func TestCancelOpensRefunds(cur realm, t *testing.T) {536	b := begin()537	id := launchAs(cur, creator1, "cancelled project")538	pledgeAs(cur, backerA, id, 600_000)539540	// Mallory cannot cancel someone else's campaign.541	testing.SetRealm(testing.NewUserRealm(mallory))542	uassert.AbortsWithMessage(t, cur, "creator only", func() {543		CreatorCancel(cross(cur), id)544	})545546	testing.SetRealm(testing.NewUserRealm(creator1))547	CreatorCancel(cross(cur), id)548	uassert.Equal(t, stateCancelled, Status(id))549	uassert.True(t, IsRefundable(id))550551	// Cancelling twice: the deferral is gone, the state says so first.552	testing.SetRealm(testing.NewUserRealm(creator1))553	uassert.AbortsWithMessage(t, cur, "campaign is settled", func() {554		CreatorCancel(cross(cur), id)555	})556557	walletBefore := balanceOf(backerA)558	testing.SetRealm(testing.NewUserRealm(backerA))559	Refund(cross(cur), id)560	uassert.Equal(t, walletBefore+600_000, balanceOf(backerA))561	uassert.Equal(t, b.backed, TotalBacked())562	uassert.Equal(t, b.fees, FeesAccrued()) // refunds are fee-free563}564565func TestCancelAfterSuccessRenouncesThePot(cur realm, t *testing.T) {566	begin()567	id := launchAs(cur, creator1, "renounced project")568	pledgeAs(cur, backerA, id, MinGoal)569	testing.SkipHeights(MinDurationBlocks)570	uassert.Equal(t, "succeeded (awaiting creator claim)", Status(id))571572	testing.SetRealm(testing.NewUserRealm(creator1))573	CreatorCancel(cross(cur), id)574	uassert.True(t, IsRefundable(id))575	testing.SetRealm(testing.NewUserRealm(backerA))576	Refund(cross(cur), id)577	uassert.Equal(t, int64(0), PledgeOf(id, backerA))578}579580// --- lapse ---581582func TestLapseRefusesWhileClaimWindowIsOpen(cur realm, t *testing.T) {583	begin()584	id := launchAs(cur, creator1, "lapse timing")585	pledgeAs(cur, backerA, id, MinGoal)586	testing.SkipHeights(MinDurationBlocks) // due, but window open587588	testing.SetRealm(testing.NewUserRealm(mallory))589	uassert.AbortsWithMessage(t, cur, "duebook: deferral has not expired", func() {590		Lapse(cross(cur), id)591	})592593	testing.SkipHeights(ClaimWindowBlocks)594	testing.SetRealm(testing.NewUserRealm(mallory))595	Lapse(cross(cur), id)596	uassert.Equal(t, stateLapsed, Status(id))597598	testing.SetRealm(testing.NewUserRealm(mallory))599	uassert.AbortsWithMessage(t, cur, "campaign is settled", func() {600		Lapse(cross(cur), id)601	})602	testing.SetRealm(testing.NewUserRealm(backerA))603	Refund(cross(cur), id)604}605606// --- refund guards ---607608func TestRefundGuards(cur realm, t *testing.T) {609	begin()610	id := launchAs(cur, creator1, "refund guards")611	pledgeAs(cur, backerA, id, 100_000)612613	// Not refundable while funding is open.614	testing.SetRealm(testing.NewUserRealm(backerA))615	uassert.AbortsWithMessage(t, cur, "campaign is not refundable", func() {616		Refund(cross(cur), id)617	})618619	testing.SetRealm(testing.NewUserRealm(creator1))620	CreatorCancel(cross(cur), id)621622	// A stranger with no pledge gets nothing.623	testing.SetRealm(testing.NewUserRealm(mallory))624	uassert.AbortsWithMessage(t, cur, "no pledge to return", func() {625		Refund(cross(cur), id)626	})627628	// The backer refunds once, then has nothing left.629	testing.SetRealm(testing.NewUserRealm(backerA))630	Refund(cross(cur), id)631	testing.SetRealm(testing.NewUserRealm(backerA))632	uassert.AbortsWithMessage(t, cur, "no pledge to return", func() {633		Refund(cross(cur), id)634	})635}636637// --- prune ---638639func TestPrune(cur realm, t *testing.T) {640	begin()641	id := launchAs(cur, creator1, "prunable")642	pledgeAs(cur, backerA, id, 50_000)643644	// Not while unsettled.645	testing.SetRealm(testing.NewUserRealm(mallory))646	uassert.AbortsWithMessage(t, cur, "campaign is not settled", func() {647		Prune(cross(cur), id)648	})649650	testing.SetRealm(testing.NewUserRealm(creator1))651	CreatorCancel(cross(cur), id)652653	// Not while backer funds remain.654	testing.SetRealm(testing.NewUserRealm(mallory))655	uassert.AbortsWithMessage(t, cur, "campaign still holds backer funds", func() {656		Prune(cross(cur), id)657	})658659	testing.SetRealm(testing.NewUserRealm(backerA))660	Refund(cross(cur), id)661662	nBefore := NumCampaigns()663	testing.SetRealm(testing.NewUserRealm(mallory))664	Prune(cross(cur), id)665	uassert.Equal(t, nBefore, NumCampaigns()) // ids are never reused666	testing.SetRealm(testing.NewUserRealm(mallory))667	uassert.AbortsWithMessage(t, cur, "unknown campaign id", func() {668		Prune(cross(cur), id)669	})670	uassert.PanicsWithMessage(t, cur, "unknown campaign id", func() {671		CampaignInfo(id)672	})673}674675// --- fees and surplus ---676677func TestWithdrawFees(cur realm, t *testing.T) {678	b := begin()679	if b.fees == 0 {680		// Accrue a fee: fund and claim a campaign.681		id := launchAs(cur, creator1, "fee source")682		pledgeAs(cur, backerA, id, MinGoal)683		testing.SkipHeights(MinDurationBlocks)684		testing.SetRealm(testing.NewUserRealm(creator1))685		CreatorClaim(cross(cur), id)686	}687	fees := FeesAccrued()688	uassert.True(t, fees > 0)689690	collectorBefore := balanceOf(FeeCollector)691	testing.SetRealm(testing.NewUserRealm(mallory)) // anyone may trigger692	got := WithdrawFees(cross(cur))693	uassert.Equal(t, fees, got)694	uassert.Equal(t, collectorBefore+fees, balanceOf(FeeCollector))695	uassert.Equal(t, int64(0), FeesAccrued())696697	testing.SetRealm(testing.NewUserRealm(mallory))698	uassert.AbortsWithMessage(t, cur, "no fees accrued", func() {699		WithdrawFees(cross(cur))700	})701}702703func TestSweepSurplus(cur realm, t *testing.T) {704	begin()705	// No surplus: coinio refuses.706	testing.SetRealm(testing.NewUserRealm(mallory))707	uassert.AbortsWithMessage(t, cur, "coinio: no surplus to sweep for ugnot", func() {708		SweepSurplus(cross(cur), Denom)709	})710711	// Force-send 33_000 ugnot out-of-band, then sweep it.712	testing.IssueCoins(Address(), ugnot(33_000))713	liabBefore := Liabilities()714	collectorBefore := balanceOf(FeeCollector)715	testing.SetRealm(testing.NewUserRealm(mallory))716	swept := SweepSurplus(cross(cur), Denom)717	uassert.Equal(t, int64(33_000), swept)718	uassert.Equal(t, collectorBefore+33_000, balanceOf(FeeCollector))719	uassert.Equal(t, liabBefore, Liabilities()) // tracked money untouched720721	// A foreign denomination sweeps in full.722	testing.IssueCoins(Address(), chain.NewCoins(chain.NewCoin("foocoin", 4_200)))723	testing.SetRealm(testing.NewUserRealm(mallory))724	uassert.Equal(t, int64(4_200), SweepSurplus(cross(cur), "foocoin"))725}726727// --- stray sends ---728729func TestNonPayableEntrypointsRejectCoins(cur realm, t *testing.T) {730	begin()731	id := launchAs(cur, creator1, "stray send case")732733	cases := []func(){734		func() { Launch(cross(cur), "t", "", MinGoal, MinDurationBlocks) },735		func() { Unpledge(cross(cur), id) },736		func() { CreatorClaim(cross(cur), id) },737		func() { CreatorCancel(cross(cur), id) },738		func() { SettleFailed(cross(cur), id) },739		func() { Lapse(cross(cur), id) },740		func() { Refund(cross(cur), id) },741		func() { Prune(cross(cur), id) },742		func() { WithdrawFees(cross(cur)) },743		func() { SweepSurplus(cross(cur), Denom) },744	}745	for _, call := range cases {746		testing.SetRealm(testing.NewUserRealm(mallory))747		testing.SetOriginSend(ugnot(1))748		uassert.AbortsWithMessage(t, cur, "this entrypoint does not accept coins", call)749	}750	testing.SetOriginSend(nil)751	testing.SetRealm(testing.NewUserRealm(creator1))752	CreatorCancel(cross(cur), id)753}754755// --- render ---756757func TestRenderIndexAndCampaign(cur realm, t *testing.T) {758	begin()759	// A hostile title must come out of the sanitizer defanged.760	hostile := "[evil](https://x) <script>alert(1)</script>"761	testing.SetRealm(testing.NewUserRealm(creator1))762	id := Launch(cross(cur), hostile, "memo with [link](x) inside", MinGoal, MinDurationBlocks)763764	index := Render("")765	uassert.True(t, strings.Contains(index, "# Crowdfund"))766	uassert.True(t, strings.Contains(index, "conservation: OK"))767	uassert.False(t, strings.Contains(index, "<script>"))768769	page := Render(itoa(id))770	uassert.True(t, strings.Contains(page, "status: **funding**"))771	uassert.True(t, strings.Contains(page, "0%"))772	uassert.False(t, strings.Contains(page, "<script>"))773	uassert.False(t, strings.Contains(page, "[evil](https://x)"))774	uassert.False(t, strings.Contains(page, "[link](x)"))775776	uassert.True(t, strings.Contains(Render("notanumber"), "invalid campaign id"))777	uassert.True(t, strings.Contains(Render("999999"), "unknown campaign id"))778779	testing.SetRealm(testing.NewUserRealm(creator1))780	CreatorCancel(cross(cur), id)781}782783func TestPercentAndBps(t *testing.T) {784	uassert.Equal(t, "0%", percent(0, MinGoal))785	uassert.Equal(t, "50%", percent(500_000, MinGoal))786	uassert.Equal(t, "99%", percent(999_999, MinGoal))787	uassert.Equal(t, "100%", percent(MinGoal, MinGoal))788	uassert.Equal(t, "250%", percent(2_500_000, MinGoal))789	// The overflow-guard branch: r*100 would not fit in int64. Three790	// quarters of 2^62 is exactly 75%, and the scaled form lands on it.791	huge := int64(1) << 62792	uassert.Equal(t, "75%", percent(3*(huge/4), huge))793794	uassert.Equal(t, "1%", bps(100))795	uassert.Equal(t, "5%", bps(500))796	uassert.Equal(t, "0.5%", bps(50))797	uassert.Equal(t, "0.01%", bps(1))798	uassert.Equal(t, "2.25%", bps(225))799}800

Functions

  • Address() string

  • CampaignInfo(id int64) (creator string, goal int64, raised int64, feeBps int64, createdAt int64, deadline int64, claimEnd int64, backers int, state string)

  • CampaignMemo(id int64) string

  • CampaignTitle(id int64) string

  • CreatorCancel(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)

  • CreatorClaim(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)

  • FeesAccrued() int64

  • Height() int64

  • Held() int64

  • IsRefundable(id int64) bool

  • Lapse(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)

  • Launch(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}, title string, memo string, goal int64, durationBlocks int64) int64

  • Liabilities() int64

  • NumCampaigns() int64

  • OpenCampaigns() int64

  • OpenOf(addr string) int64

  • Pledge(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)

  • PledgeOf(id int64, addr string) int64

  • Prune(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)

  • Refund(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)

  • Render(path string) string

  • SettleFailed(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)

  • Status(id int64) string

  • Surplus() int64

  • SweepSurplus(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}, denom string) int64

  • TotalBacked() int64

  • Unpledge(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)

  • WithdrawFees(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.