All notable changes to the GRUDGE contract and the lib/chain adapter it backs.
Dates are when the work landed; the contract is versioned by its on-chain
storage layout (SCHEMA_VERSION).
Tightens Anchored Proof from opt-in to required: create_challenge now rejects
an empty proof_anchor, so every challenge registers a proof source and
must verify_anchor before it can accept evidence. The "casual/unanchored"
text-only proof path is gone — all evidence must link to the verified host.
⚠️ Requires a fresh deploy. No storage-layout change (SCHEMA_VERSIONstays 4), but the create-time validation changed, so a redeploy makes the new rule explicit. The frontend create wizard marks the proof source required andProofAnchoradds a copy-to-clipboard for the ownership code.
- GenLayer Studio:
0x6A21dc70A225dD2179F0E0EE96E0eFF7210E27f7(schema 4, mandatory anchor).apps/web/.env.local,contracts/deployments.json, and the README deployment table all point here. - The prior optional-anchor v6 (
0x503Cd4…9817) drops to previous indeployments.json, the README table, and the docs "previous deployments" list.
Moves the proof boundary from "validators judge submitted text" to "validators verify an owned, timestamped source" (the gap called out in the submission review): identity, account ownership, and the proof period are now enforced before/around the LLM judgment instead of being left to it.
⚠️ Requires a fresh deploy.Challengegained two fields (proof_anchor: str,anchor_verified: bool) andcreate_challengegained a parameter.SCHEMA_VERSIONbumped 3 → 4. After deploying, updateNEXT_PUBLIC_GRUDGE_CONTRACT_ADDRESS.
- GenLayer Studio:
0x503Cd4D2f88520c1f8a6455cC958199508789817(schema 4).apps/web/.env.local,contracts/deployments.json, and the README deployment table all point here. - Previous deployments stay referenced (README +
deployments.json+ commented.env.localblock) so the team can re-test earlier transactions: Studio v50xb9b501…379c, Bradbury0x652789…80C6/0xaba1Db…819C. - New
apps/web/scripts/seed-studionet.mjsseeds the fresh contract with demo transactions (funded throwaway accounts →create_challenge→stakes) so the ledger isn't empty on first load.
GrudgeClientgainedgetAnchorInfo/verifyAnchor(genlayer + mock adapters), withAnchorInfoSchemaintypes.tsanduseAnchorInfo/useVerifyAnchorhooks.- New
ProofAnchorcomponent: anchor badge + the creator's verify flow (shows thegrudge-<id>-<addr>code to paste, firesverify_anchor, which is on the slow AI-writes poll path since it runs a web-fetch consensus round). - Create wizard: optional "Proof source URL" field wired through
create_challenge's newproof_anchorarg;EvidenceTribunalblocks evidence submission until an anchored grudge is verified. judgePrompt.tsmirrors the contract's new TIME WINDOW / ANCHORED PROOF prompt blocks so the mock judge matches on-chain behavior; mock client tests extended to cover the anchor origin gate.
| Method | Kind | Signature → returns |
|---|---|---|
verify_anchor |
write (web-fetch consensus) | (challenge_id: int) -> str → {"anchor","code","verified"} |
get_anchor_code |
view | (challenge_id: int) -> str → {"anchor","code","verified"} |
Contract method count: 17 → 19 (views 10 → 11, writes 7 → 8).
create_challenge(…, proof_anchor: str)— new trailing parameter.""= unanchored (previous behavior); a non-empty value must be one http(s) URL ≤ 200 chars and registers the challenge's proof source.verify_anchor— ownership proof: the validator set fetches the anchor page and checks a challenge-bound code (grudge-<id>-<creator-addr-prefix>) appears on it, viagl.eq_principle.strict_eq(deterministic containment on non-deterministically fetched content — no LLM round).submit_evidence— deterministic pre-consensus gates on anchored challenges: anchor must be verified, evidence must contain ≥1 link, and every link's host must equal the anchor host. The judge prompt gains a TIME WINDOW block (this proof period's pinned start/end) and, when anchored, an ANCHORED PROOF block requiring the verdict to rest on the fetched page — an unfetchable anchored link is deterministically REJECTED.get_challenge—+ proof_anchor,+ anchor_verified;get_challenges_pagesummaries —+ anchor_verified.
GrudgeClient.getAnchorInfo/GrudgeClient.verifyAnchor(genlayer + mock; the mock mirrors the origin gate).ProofAnchorcomponent: anchor badge on the challenge page + the creator's verification flow (shows the code to paste, firesverify_anchor).- Create wizard: optional "Proof source URL" field; the tribunal blocks evidence submission until an anchored grudge is verified.
Deliberate scope note: the anchor proves account ownership, not human truth — the defensible claim is "evidence must come from an owned, platform-timestamped source," not "cheating is impossible."
Four high-impact features on top of the hardened contract, built without
weakening any existing security property: every new payable path updates
total_locked and calls _assert_solvent(); every new LLM call lives in a
zero-arg fn inside gl.eq_principle.prompt_comparative with sort_keys=True;
every new view is bounded.
⚠️ Requires a fresh deploy. Storage layout changed (Evidencegained two fields; a newreputationmap).SCHEMA_VERSIONbumped 2 → 3. studionet history resets, so this is a clean redeploy — there is no in-place migration. After deploying, updateNEXT_PUBLIC_GRUDGE_CONTRACT_ADDRESS.
| Method | Kind | Signature → returns |
|---|---|---|
appeal_verdict |
write · payable | (challenge_id: int, evidence_index: int) -> str → {"verdict","reason","confidence"} |
get_reputation |
view | (address: str) -> str → reputation object (below) |
explain_verdict |
view (runs consensus) | (challenge_id: int, evidence_index: int) -> str → {"verdict","explanation"} (explanation ≤ 600 chars) |
suggest_evidence_policy |
view (runs consensus) | (statement: str) -> str → {"policy","rationale"} (each ≤ 280 chars) |
Contract method count: 13 → 17 (views 7 → 10, writes 6 → 7).
create_challenge(statement, evidence_policy, category, duration_days, required_proofs)— same signature, new behavior: an emptyevidence_policyis now allowed and triggers AI policy design (the contract fills it viasuggest_evidence_policy). A non-empty policy still enforces the 4–280 char bound.get_challenge/get_challenges_page's nestedevidence— each entry gains two fields:appealed: boolandappeal_bond: str(wei).get_evidence_page— same two new fields per entry.
Evidencedataclass +2 fields:appealed: bool,appeal_bond: u256(appended; existing fields unmoved).- New
reputation: TreeMap[Address, Reputation]map. - New
Reputationdataclass:challenges_created, challenges_won, proofs_verified, proofs_rejected, doubts_made, doubts_correct(allu32).
SCHEMA_VERSION = 3MIN_APPEAL_BOND = 10**17(0.1 GEN)CONVICTION_DAMPENER = 3
When a proof is REJECTED, the creator can appeal with a bond. A fresh consensus round re-judges the evidence with an appeal notice prepended.
- Flip → VERIFIED: the proof counts (
verified_count += 1), reputation moves rejected→verified, and the bond is returned to the appellant (credited toclaimable). - Upheld: the bond is forfeited to the doubter pool.
- One appeal per entry; bond
>= MIN_APPEAL_BOND; creator-only. - Solvency: the bond is held like any stake (
total_locked += bondon deposit, then only moves between buckets), so conservation holds — proven bytest_appeal_conservation.
The contract's LLM proposes a concrete, anti-gaming evidence policy from the
statement. Used two ways: the suggest_evidence_policy view (preview, creates
nothing) and the blank-policy autofill branch in create_challenge.
explain_verdict returns the referee's full reasoning for an existing verdict
via consensus, without re-judging or mutating state (verified by
test_explain_does_not_mutate). Output bounded to 600 chars.
On-chain reputation derived from kept/broken/doubt history. Counters update in
settle (creator created/won; each distinct doubter made/correct),
submit_evidence, and dispute_evidence. get_reputation returns the raw
counters plus two deterministic 0–100 scores (no LLM):
{
"address": "0x…",
"challenges_created": 0, "challenges_won": 0,
"proofs_verified": 0, "proofs_rejected": 0,
"doubts_made": 0, "doubts_correct": 0,
"conviction_score": 0, // 100·won/(created+3) — dampened so 1/1 ≠ 100
"doubter_accuracy": 0 // 100·correct/(made+3)
}New GrudgeClient methods (implemented in both genlayer.ts and mock.ts):
getReputation, explainVerdict, suggestPolicy, appealVerdict.
New Zod schemas: ReputationSchema; EvidenceEntrySchema extended with
appealed + appealBond.
New hooks: useReputation, useExplainVerdict, useSuggestPolicy,
useAppealVerdict.
New / updated UI:
ConvictionBadge— on profile, challenge creator, and each doubter.ExplainVerdict— "Explain this verdict" disclosure with a typewriter reveal.AppealAction— bond modal on rejected entries (creator only).- Create wizard — "✨ Suggest a fair policy" button + rationale preview; the policy field may be left blank to let the AI design one.
- Contract: +18 Direct Mode cases (41 total) — F4 (5), F3 (3), F2 (4), F1 (6), all run the real contract in-memory with the LLM mocked.
- Web: 35 vitest cases (mock client extended for all four features).
- Gates green:
ruff --select ALL,mypy --strict,genvm_lint,genvm-lint check/typecheck, the wallet-lib guard.
The contract before v5: paginated bounded views (no unbounded reads),
total_locked solvency bookkeeping + _assert_solvent(), the claiming
reentrancy guard on claim(), and deterministic time via
gl.message_raw["datetime"]. Public surface: create_challenge, stake,
submit_evidence, dispute_evidence, settle, claim, and the
get_challenge* / get_*_page / get_claimable / get_solvency views.