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.72 Kb · 115 lines
  1package escrow_v3
  2
  3// Admin authority and its rotation path.
  4//
  5// No admin is compiled in: `admin` below is seeded at package load from the
  6// publishing transaction's signer (on gnoland-1 the samcrew namespace
  7// multisig, the stamped creator at enable time). Every gate in this realm
  8// reads the mutable `admin`, so the address that holds admin can be changed
  9// after deployment.
 10//
 11// WHY THIS IS NOT OPTIONAL ON MAINNET. Realms are immutable once published and
 12// there is no faucet; mainnet genesis additionally locks ugnot transfers under
 13// Constitution §126. A compile-time admin therefore cannot be corrected after
 14// the fact. A compile-time testnet key would permanently disable Pause/Unpause
 15// and ResolveDispute, the only path that settles a disputed milestone,
 16// stranding custodied funds with nobody able to move them. Redeploying at a
 17// new path is not a recovery: it abandons the funds already held at this one.
 18//
 19// TWO-STEP BY DESIGN. The handoff stages a pending address that must claim it
 20// with its own transaction. A one-step setter would let admin be handed to an
 21// address that cannot act — a typo, or an unfunded address under §126 — with no
 22// way back, which is precisely the failure this file exists to prevent.
 23//
 24// CALLER AUTH: `cur.IsCurrent()` then `cur.Previous().Address()`, matching
 25// memba_arcade_leaderboard_v1 / memba_points_v1 and the project's contract-review
 26// checklist #9. This file deliberately imports none of the frame-unverified
 27// caller package that checklist flags — a new path should not inherit it.
 28//
 29// escrow.gno's older gates still read the frame-unverified caller and stay
 30// grandfathered in antipattern-prevrealm-baseline.txt. That is not a dangerous
 31// mix: for the direct multisig calls these entrypoints are for, both forms
 32// resolve to the same address, and `IsCurrent()` rejects the sibling/stale-`cur`
 33// cases where they could differ at all — so this file is strictly the tighter
 34// of the two.
 35
 36import "chain"
 37
 38var (
 39	// admin is the LIVE authority, seeded with the publisher at package load.
 40	// Initialized at declaration rather than in init() so there is no
 41	// init-ordering question about gates that read it.
 42	admin address = publisherAtLoad()
 43
 44	// feeFallback receives the protocol fee only when memba_market_config
 45	// reports no treasury (see resolveFee). It is the publisher too, so a config
 46	// misread routes fees to the namespace multisig rather than to a testnet
 47	// key or to nobody.
 48	feeFallback address = publisherAtLoad()
 49
 50	// pendingAdmin is the staged successor; "" when no handoff is in flight.
 51	pendingAdmin address
 52)
 53
 54// TransferOwnership stages a handoff to newAdmin. Admin only. The transfer does
 55// not take effect until newAdmin calls AcceptOwnership, so admin is never moved
 56// to an address that has not demonstrated it can transact.
 57//
 58// Calling it again before acceptance replaces the staged address, which is how
 59// a mistyped proposal is corrected.
 60func TransferOwnership(cur realm, newAdmin address) {
 61	assertAdmin(cur)
 62	if newAdmin == "" {
 63		panic("newAdmin must be non-empty")
 64	}
 65	pendingAdmin = newAdmin
 66	chain.Emit("OwnershipTransferStarted", "pending", newAdmin.String())
 67}
 68
 69// AcceptOwnership completes the handoff. Only the staged pendingAdmin may call
 70// it — including against the outgoing admin, so the two steps cannot be
 71// collapsed into one by the party giving up the role.
 72func AcceptOwnership(cur realm) {
 73	if !cur.IsCurrent() {
 74		panic("spoofed realm")
 75	}
 76	if pendingAdmin == "" {
 77		panic("no pending ownership transfer")
 78	}
 79	if cur.Previous().Address() != pendingAdmin {
 80		panic("unauthorized: only the pending admin may accept")
 81	}
 82	admin = pendingAdmin
 83	pendingAdmin = ""
 84	chain.Emit("OwnershipTransferAccepted", "admin", admin.String())
 85}
 86
 87// CancelOwnershipTransfer clears a staged handoff. Admin only — the outgoing
 88// admin must be able to abort a proposal it no longer wants, without needing
 89// the proposed address to cooperate.
 90func CancelOwnershipTransfer(cur realm) {
 91	assertAdmin(cur)
 92	if pendingAdmin == "" {
 93		panic("no pending ownership transfer")
 94	}
 95	cancelled := pendingAdmin
 96	pendingAdmin = ""
 97	chain.Emit("OwnershipTransferCancelled", "cancelled", cancelled.String())
 98}
 99
100// GetAdmin returns the address that currently holds admin.
101func GetAdmin() string { return admin.String() }
102
103// GetPendingAdmin returns the staged successor ("" when none is in flight).
104func GetPendingAdmin() string { return pendingAdmin.String() }
105
106// assertAdmin is the authorization gate for this file's entrypoints. It rejects
107// a stale/sibling `cur` first, then takes the caller from cur.Previous().
108func assertAdmin(cur realm) {
109	if !cur.IsCurrent() {
110		panic("spoofed realm")
111	}
112	if cur.Previous().Address() != admin {
113		panic("unauthorized: only admin may perform this action")
114	}
115}