Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions 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}