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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

admin.gno

3.59 Kb · 95 lines
 1package memba_quest_attestation_v1
 2
 3// Owner authority and its rotation path.
 4//
 5// No owner is compiled in: `owner` is seeded at package load from the
 6// publishing transaction's signer (on gnoland-1 the samcrew namespace multisig,
 7// the stamped creator at enable time), and SetSigner reads the mutable `owner`,
 8// so the address holding ownership can be changed after deployment.
 9//
10// WHY THIS MATTERS ON MAINNET, specifically here. SetSigner is the ONLY
11// owner-gated entrypoint, and it installs/rotates the backend's offline signer
12// public key — the key every voucher is verified against. Realms are immutable,
13// there is no faucet, and mainnet genesis locks ugnot transfers under
14// Constitution §126, so a compile-time owner cannot be corrected later, and a
15// compile-time testnet owner would freeze the signer at whatever value it last
16// held: a compromised backend signer could never be rotated out, and quest
17// attestation would have to be abandoned rather than repaired.
18//
19// TWO-STEP BY DESIGN: the successor must claim the role with its own
20// transaction, so ownership can never be handed to an address that cannot
21// transact (a typo, or an unfunded address under §126) with no way back.
22//
23// All owner mutations require a current realm frame and authenticate the
24// immediate caller through cur.Previous(), including signer rotation.
25
26import "chain"
27
28var (
29	// owner is the LIVE authority, seeded with the publisher at package load.
30	// Initialized at declaration so there is no init-ordering question about
31	// gates that read it.
32	owner address = publisherAtLoad()
33
34	// pendingOwner is the staged successor; "" when none is in flight.
35	pendingOwner address
36)
37
38// TransferOwnership stages a handoff. Owner only; it takes effect only once
39// newOwner calls AcceptOwnership. Calling it again before acceptance replaces
40// the staged address, which is how a mistyped proposal is corrected.
41func TransferOwnership(cur realm, newOwner address) {
42	assertOwner(cur)
43	if newOwner == "" {
44		panic("newOwner must be non-empty")
45	}
46	pendingOwner = newOwner
47	chain.Emit("OwnershipTransferStarted", "pending", newOwner.String())
48}
49
50// AcceptOwnership completes the handoff. Only the staged pendingOwner may call
51// it — including against the outgoing owner, so the two steps cannot be
52// collapsed into one by the party giving up the role.
53func AcceptOwnership(cur realm) {
54	if !cur.IsCurrent() {
55		panic("spoofed realm")
56	}
57	if pendingOwner == "" {
58		panic("no pending ownership transfer")
59	}
60	if cur.Previous().Address() != pendingOwner {
61		panic("unauthorized: only the pending owner may accept")
62	}
63	owner = pendingOwner
64	pendingOwner = ""
65	chain.Emit("OwnershipTransferAccepted", "owner", owner.String())
66}
67
68// CancelOwnershipTransfer clears a staged handoff. Owner only, so a proposal
69// can be aborted without the proposed address cooperating.
70func CancelOwnershipTransfer(cur realm) {
71	assertOwner(cur)
72	if pendingOwner == "" {
73		panic("no pending ownership transfer")
74	}
75	cancelled := pendingOwner
76	pendingOwner = ""
77	chain.Emit("OwnershipTransferCancelled", "cancelled", cancelled.String())
78}
79
80// GetOwner returns the address that currently holds ownership.
81func GetOwner() string { return owner.String() }
82
83// GetPendingOwner returns the staged successor ("" when none).
84func GetPendingOwner() string { return pendingOwner.String() }
85
86// assertOwner is the gate for this file's entrypoints. It rejects a
87// stale/sibling `cur` first, then takes the caller from cur.Previous().
88func assertOwner(cur realm) {
89	if !cur.IsCurrent() {
90		panic("spoofed realm")
91	}
92	if cur.Previous().Address() != owner {
93		panic("unauthorized: owner multisig only")
94	}
95}