Cosmic Bull

Rendered from pearl/r/bounty_panel/README.md at commit 6a510c665a53 in the project repository. The committed file is the source of truth; this page is a rendering of it.

Bounty panel — gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/bounty_panel

A public bounty board on pearl-1 whose resolution authority is separate from its funding authority. A funder escrows GNOT and, at creation, names a panel of resolvers and an M-of-N threshold. Contributors submit work during a submission window; after that window closes the panel votes on the competing submissions, and the first submission to reach M votes wins the escrow. Neither the panel nor the threshold can change after creation.

Built as the application-factory benchmark: an objective rather than a repository was supplied — "create public bounties with a reward, contributors submit work, a designated resolution process determines the winner, the reward is paid on-chain to the winner, preventing unauthorized claims, double payouts, and stuck funds" — and the lifecycle was objective → discovery → architecture → reuse → implementation → testing → adversarial audit → remediation → GitHub → pearl-1.

DISCOVERY / REUSE ANALYSIS (summary; full record in DISCOVERY.md)

Five sources searched: the Cosmic Bull catalog, all existing Cosmic Bull /p/ and /r/ sources, a fresh enumeration of pearl-1 (631 packages — 190 /p/, 441 /r/), the on-chain sources/imports of the closest candidates, and the public Gno ecosystem. No relevant existing implementation was found in the searched sources for a bounty whose resolvers are designated at creation and are not the funder.

The gate's headline finding is that this objective was already 6/7 implemented by the sibling realm grants (live since h584731): escrowed reward, on-chain submissions, restricted award, pull payment, fee model, permissionless refund valve. The delta is exactly one thing, and the build was confined to it:

resolution authority separate from funding authority, designated at creation, immutable thereafter, decided by an M-of-N threshold over competing submissions.

In grants the resolver is the funder by construction. For an adjudicated public bounty that is the wrong trust model: the deciding party has a financial interest in the outcome and sees every submission before deciding.

Classification of the significant hits:

ComponentClassAction
r/.../grants (own, live)RELATED IMPLEMENTATIONPattern source, not modified or redeployed
p/.../coinio (own, live)COSMIC BULL EXISTING PRIMITIVEReused as deployed
p/.../feeledger (own, live)COSMIC BULL EXISTING PRIMITIVEReused as deployed
p/nt/markdown/sanitize/v0REUSABLE EXISTING PRIMITIVEReused (ecosystem beats ours here)
p/nt/avl/v0REUSABLE EXISTING PRIMITIVEReused
p/.../daocond, p/.../commondaoRELATED IMPLEMENTATIONRejected, with reasons (below)
r/samcrew/escrow_v3, r/…/bountyboardRELATED IMPLEMENTATIONUnsuitable

daocond/commondao were the one genuine reuse call that went against reuse, and the reasoning is recorded so a reviewer can disagree on evidence: float-valued thresholds (non-exact for a value-critical tally), caller-supplied closures crossing a realm boundary, and a tally shape that approves one proposal rather than selecting among competing submissions.

No new /p/ package was created. Composition was sufficient.

Architecture & state ownership

ConcernOwner
Bounty records, panels, submissions, ballots, openTotal, deadlines, rolesthis realm (new)
Claimable balances (U), fee pot (F), fee arithmetic, overflow checksfeeledger (deployed instance)
Receipt guard, payouts, reserve-protected sweepscoinio (deployed instance)
Free-text markdown neutralization in Renderp/nt/markdown/sanitize/v0
Ordered iterationp/nt/avl/v0

Lifecycle — terminal states are frozen, exactly one transition per bounty:

Open ──Vote reaches M──> Awarded     (winner credited, fee charged)
     ──funder Cancel───> Cancelled   (only while NO submission exists)
     ──anyone Expire───> Expired     (after resolveDeadline + 100)

Non-overlapping windows. Submissions close at submitDeadline and voting opens at that same height. A resolver therefore votes only on content that can no longer change — no bait-and-switch where a submission collects votes and is then edited — and no submission can be added in response to the votes already cast.

Cancel is restricted (the deliberate divergence from grants): once a single contributor has submitted, the funder can no longer unilaterally reclaim the escrow. Only the panel, by awarding, or the expiry valve can end the bounty. That is the anti-harvest guarantee a public bounty needs and a grant programme does not.

Re-voting is allowed until the threshold is reached, and a resolver's previous vote is withdrawn before the new one is recorded, so per-submission counts always sum to the number of live ballots (asserted by voteSum after every vote in the suite).

The funder may be a panel member. Barring them would be unenforceable theatre — a funder can always name an address they control — and the panel is public on-chain from creation, so a self-resolved bounty is visible to contributors before they spend effort. Disclosure beats a prohibition that cannot be enforced.

Invariant specification

Monetary (conservation): H == B + U + F + S, S >= 0 — coins held at the realm address equal open escrow + claimable balances + fee pot + out-of-band surplus. Asserted in delta form at the end of every test (the test banker resets per test while realm globals persist).

Application: openTotal == Σ amount(status == Open); winner != zero iff Awarded; Σ submission.votes == ballots.Size() per bounty; panel, threshold, deadlines and fee bps immutable after creation; 1 <= threshold <= panelSize.

Fee model: fee = floor(amount × bps / 10000), rounding favors the winner; snapshotted at creation (closes the award-time admin race) AND capped by the funder's own maxFeeBps argument (closes the creation-time race); compile-time cap 10%; refunds always fee-free.

The three objective-named hazards:

Security / audit

Two independent fresh-context audits were run: one over the realm and its tests, one over the composition seams (coinio/feeledger/ sanitizer boundaries). 0 RED. Auditor 2 returned zero findings and ten GREEN. Auditor 1 returned three YELLOW; the two auditors disagreed on one of them and the disagreement was adjudicated on the evidence:

Both auditors independently flagged that the local coinio and feeledger sources were not yet byte-verified against their live pearl-1 deployments. That belongs to the PEARL VALIDATION stage and is recorded in the deployment record, not claimed here.

Testing

27 tests, all passing (gno test .ok . 0.65s); gno lint . and gno fmt -diff -q . clean. The suite covers the state machine, every authorization boundary, both fee races, rounding, window edges, re-voting and threshold arithmetic, terminal-state finality, the stray-send guard on all eleven non-payable entrypoints, sweep isolation, render sanitization of untrusted text, int64 overflow at creation, ledger saturation, and cross-realm callers as first-class participants.

Three suite-scope tests do work that per-test assertions cannot:

Harness note that cost real time and is recorded so it is not relearned: testing.SetRealm is keyed by frame index (ctx.RealmFrames[frameIdx] in gnovm/tests/stdlibs/testing/context_testing.go). A helper that sets the realm and returns leaves the test's own frame untouched and the previous staging silently wins. Every caller in this suite is therefore staged inline in the test function's own frame; the one helper that stages (createAs) is safe only because it makes the call from inside its own frame.

Second harness artifact, called out at each use: a panic caught by uassert does not roll back realm globals in the test VM, whereas on-chain it aborts and reverts the whole transaction. Tests that deliberately abort mid-mutation assert only on fields written before the abort point.

Gas / storage characteristics

Bounded by construction: MaxPanelSize 16, MaxSubmissions 500, MaxTitleLen 80, MaxDescLen 2000, MaxURILen 500, MaxRenderRows 20, windows in [1, 10_000_000] blocks. Submissions carry a reference (URL or content hash), never the work itself — the chain cannot judge quality, and storing bulk content pushes unbounded cost onto every future reader. Per-bounty state growth is paid for by the party creating it (each submission costs its submitter a storage deposit); Render is the one surface whose cost lands on third parties, which is why it is the one that paginates.

Deploy payload: 32,743 bytes of realm source + an 88-byte gnomod.toml. Live gas and storage-deposit figures are in the deployment record, not estimated here.

Limitations

Dependency graph

bounty_panel ──┬─> coinio ────> (stdlib)
               ├─> feeledger ─> p/nt/avl/v0
               ├─> p/nt/avl/v0
               └─> p/nt/markdown/sanitize/v0

Deployment record: pearl/DEPLOYMENT.md.