Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

admin.gno

4.07 Kb · 105 lines
  1package memba_reviews_v2
  2
  3// Moderator authority and its rotation path.
  4//
  5// No moderator is compiled in: `moderator` below is seeded at package load from
  6// the publishing transaction's signer (on gnoland-1 the samcrew namespace
  7// multisig, the stamped creator at enable time), and assertModerator reads the
  8// mutable `moderator`, so the address holding moderation can be changed after
  9// deployment.
 10//
 11// WHY THIS MATTERS ON MAINNET. Realms are immutable once published, there is no
 12// faucet, and mainnet genesis locks ugnot transfers under Constitution §126 — so
 13// a compile-time moderator cannot be corrected after the fact: a testnet key
 14// compiled in would permanently disable HideTarget/UnhideTarget/ClearFlags,
 15// leaving this realm with no way to act on a flagged review ever again.
 16//
 17// TWO-STEP BY DESIGN: the successor must claim the role with its own transaction,
 18// so moderation can never be handed to an address that cannot transact (a typo,
 19// or an unfunded address under §126) with no way back.
 20//
 21// CALLER AUTH here uses `cur.IsCurrent()` + `cur.Previous().Address()`, per
 22// contract-review checklist #9, matching memba_arcade_leaderboard_v1 /
 23// memba_points_v1. The realm's existing assertModerator keeps its older
 24// frame-unverified caller read (grandfathered in
 25// antipattern-prevrealm-baseline.txt); only the value it compares against
 26// changed, which is the smallest edit that removes the immutability.
 27
 28import "chain"
 29
 30var (
 31	// moderator is the LIVE authority, seeded with the publisher at package load.
 32	// Initialized at declaration so there is no init-ordering question about
 33	// gates that read it.
 34	moderator address = publisherAtLoad()
 35
 36	// pendingModerator is the staged successor; "" when none is in flight.
 37	pendingModerator address
 38)
 39
 40// TransferOwnership stages a handoff of the moderator role. Moderator only; it
 41// takes effect only once newModerator calls AcceptOwnership. Calling it again
 42// before acceptance replaces the staged address, which is how a mistyped
 43// proposal is corrected.
 44func TransferOwnership(cur realm, newModerator address) {
 45	assertModeratorCur(cur)
 46	if newModerator == "" {
 47		panic("newModerator must be non-empty")
 48	}
 49	if !newModerator.IsValid() {
 50		panic("invalid address: " + newModerator.String())
 51	}
 52	if newModerator == moderator {
 53		panic("newModerator is the current moderator")
 54	}
 55	pendingModerator = newModerator
 56	chain.Emit("OwnershipTransferStarted", "pending", newModerator.String())
 57}
 58
 59// AcceptOwnership completes the handoff. Only the staged pendingModerator may
 60// call it — including against the outgoing moderator, so the two steps cannot be
 61// collapsed into one by the party giving up the role.
 62func AcceptOwnership(cur realm) {
 63	if !cur.IsCurrent() {
 64		panic("spoofed realm")
 65	}
 66	if pendingModerator == "" {
 67		panic("no pending ownership transfer")
 68	}
 69	if cur.Previous().Address() != pendingModerator {
 70		panic("unauthorized: only the pending moderator may accept")
 71	}
 72	moderator = pendingModerator
 73	pendingModerator = ""
 74	chain.Emit("OwnershipTransferAccepted", "moderator", moderator.String())
 75}
 76
 77// CancelOwnershipTransfer clears a staged handoff. Moderator only, so a
 78// proposal can be aborted without the proposed address cooperating.
 79func CancelOwnershipTransfer(cur realm) {
 80	assertModeratorCur(cur)
 81	if pendingModerator == "" {
 82		panic("no pending ownership transfer")
 83	}
 84	cancelled := pendingModerator
 85	pendingModerator = ""
 86	chain.Emit("OwnershipTransferCancelled", "cancelled", cancelled.String())
 87}
 88
 89// GetModerator returns the address that currently holds moderation.
 90func GetModerator() string { return moderator.String() }
 91
 92// GetPendingModerator returns the staged successor ("" when none).
 93func GetPendingModerator() string { return pendingModerator.String() }
 94
 95// assertModeratorCur is the gate for this file's entrypoints. Named apart from
 96// the realm's existing assertModerator so the older call sites keep their
 97// signature and semantics untouched.
 98func assertModeratorCur(cur realm) {
 99	if !cur.IsCurrent() {
100		panic("spoofed realm")
101	}
102	if cur.Previous().Address() != moderator {
103		panic("unauthorized: moderator multisig only")
104	}
105}