PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/sys/names

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
names
Namespace
sys
Files
4 (gnomod.toml)
Exported functions
6
Module
gno.land/r/sys/names
gno
0.9

Files (4)

  • gnomod.tomltoml
  • render.gnogno
  • verifier.gnogno
  • verifier_test.gnogno
verifier.gnogno
1// Package names enforces namespace permissions for package deployment.2//3// Two namespace shapes grant deploy authority when enforcement is enabled:4//5//  1. PA (personal-address) namespaces — gno.land/{r,p}/<addr>/* — the6//     deployer's address string equals the namespace literal. Anyone can7//     deploy under their own address.8//9//  2. Registered-name namespaces — gno.land/{r,p}/<name>/* — r/sys/users10//     has a (name → addr) mapping where the resolved address equals the11//     deployer AND the name is the user's CURRENT name (not a historical12//     alias from a rename chain). This is the bridge that lets13//     r/sys/namereg/v0 (or any other DAO-whitelisted controller) grant14//     deploy authority via name registration.15//16// Authority is unscoped: a registered name owns BOTH r/<name>/* and17// p/<name>/* paths. There is no sub-prefix isolation (e.g. r/u/<name>/*).18//19// The realm exposes an emergency-halt switch via SetPaused. When paused,20// the verifier rejects EVERY namespace check — PA included — until21// unpaused. This is the "true emergency" semantic; the narrow alternative22// (pause registered-name only, preserve PA) was considered and rejected23// because the threats most likely to justify pausing this realm24// (verifier bug, compromised controller, signature-layer incident) do25// not reliably exempt PA from the same blast radius.26package names2728import (29	"chain"3031	"gno.land/r/gov/dao"32	govimpl "gno.land/r/gov/dao/impl/v0"33	memberstore "gno.land/r/gov/dao/memberstore/v0"34	susers "gno.land/r/sys/users"35)3637var (38	// admin is the GovDAO T1 multisig address, hardcoded at realm-source39	// commit time. Its only capability is gating Enable() — a one-way,40	// one-shot genesis activation of the namespace verifier. The address41	// has no other authority on this realm; pause/unpause is gated on a42	// separate GovDAO T1 proposal (see ProposeSetPaused), and there is43	// no SetEnabled(false) or SetAdmin path.44	//45	// Hardcoding is acceptable because:46	//   - Enable() is called once, at chain genesis. After that the47	//     address is dead weight — no further capability flows through it.48	//   - The narrow blast radius of "stale admin" is "Enable() can never49	//     be called", which leaves the verifier in pre-Enable bypass mode50	//     (returns true for all checks) — degraded but not exploitable.51	//   - A rotation path would only matter if Enable() needed to be52	//     re-issued. It doesn't; the flag is sticky.53	//54	// If the genesis activation pattern ever needs to change (e.g. a55	// SetEnabled(false) emergency disable is added later), this admin56	// model should be replaced with a GovDAO T1 proposal flow that57	// mirrors ProposeSetPaused. Until then, the hardcoded address is58	// the smallest viable governance surface for the one-shot use case.59	admin   = address("g1skl80cuz8zq3lul9pgz5pc35l2pfzgxgfpsqkx")60	enabled = false61	paused  = false62)6364// nameLookupFn returns (addr, ok) for a given registered name. Allows65// the verifier function to be unit-tested without wiring up r/sys/users66// state — production binds resolveCurrentName as the lookup, tests pass67// nil (PA-only) or a fake.68type nameLookupFn func(name string) (addr address, ok bool)6970// IsAuthorizedAddressForNamespace checks if the given address can deploy71// to the given namespace. See package doc for the two authorization paths72// and the pause semantic.73//74// Pre-Enable, all checks pass (testing/dev convenience).75func IsAuthorizedAddressForNamespace(address_XXX address, namespace string) bool {76	return verifier(enabled, paused, address_XXX, namespace, resolveCurrentName)77}7879// resolveCurrentName is the production nameLookupFn, backed by r/sys/users.80// Returns ok=true only if the name resolves to a non-deleted user AND the81// queried name is that user's CURRENT name (the most recent UpdateName).82//83// Restricting to the current name has two consequences worth knowing:84//85//  1. After a UpdateName from "alice" to "alice2", the user keeps deploy86//     authority over r/alice2/* but LOSES it for r/alice/*. Already-87//     deployed packages at r/alice/* keep working — deploy-time88//     authorization doesn't unwind the past — but no NEW deploys can89//     land there.90//91//  2. The old name "alice" is also unregisterable by anyone else: when92//     r/sys/users.UpdateName runs, it inserts the new name into nameStore93//     but does not remove the old one. r/sys/users.RegisterUser then94//     rejects re-registration of "alice" with ErrNameTaken. Net effect:95//     a rename permanently removes the old name from circulation.96//97// The alternative (allow historical aliases to retain authority) was98// rejected because it lets a single user register one cheap name, then99// rename N times to claim authority over N distinct namespaces — a100// stealth namespace acquisition vector worse than the current101// burn-on-rename behavior.102func resolveCurrentName(name string) (address, bool) {103	data, isCurrent := susers.ResolveName(name)104	if data == nil || !isCurrent {105		return "", false106	}107	return data.Addr(), true108}109110// Enable enables the namespace check for this realm.111// The namespace check is disabled initially to ease txtar and other testing contexts,112// but this function is meant to be called in the genesis of a chain.113func Enable(cur realm) {114	if !cur.IsCurrent() {115		panic("unauthorized: cur is not the caller's live realm")116	}117	if cur.Previous().Address() != admin {118		panic("caller is not admin")119	}120	enabled = true121}122123func IsEnabled() bool {124	return enabled125}126127// ProposeSetPaused returns a GovDAO proposal request that, when voted128// through and executed, toggles the chain-wide deploy gate. When the129// realm is paused, the verifier rejects EVERY namespace check — PA130// (personal-address) included — until a subsequent ProposeSetPaused(false)131// proposal executes.132//133// This is an emergency halt. A paused state means NO new MsgAddPackage134// transactions land at any path on the chain. Existing realms continue135// to receive MsgCall traffic normally — pause is scoped to addpkg, not136// to all VM operations. Use cases:137//   - Bug discovered in this realm or r/sys/users that requires a138//     hotfix before further deploys can be trusted.139//   - Wallet/signature-layer incident under investigation.140//141// (A "compromised controller" use case was considered and removed: the142// controller's RegisterUser path is direct into r/sys/users and does143// NOT go through this verifier, so pause does not freeze new144// registrations. To contain a compromised controller, the appropriate145// flow is ProposeControllerRemoval in r/sys/users, not pause here.)146//147// The narrow alternative (pause registered-name path only, preserve148// PA) was considered and rejected. See package doc for rationale.149//150// Gated on GovDAO proposal at T1 tier — not the hardcoded admin used151// by Enable. Pause is consequential enough to warrant a tier-restricted152// governance vote rather than a single-multisig click. T1 filter153// prevents lower-tier members from spamming pause proposals to dilute154// attention. Trade-off is response time: a T1 vote takes hours-to-days;155// if a faster emergency-halt mechanism is needed, that belongs at a156// different layer (e.g. an ante-handler-level chain pause), not here.157//158// Pause is orthogonal to the pre-Enable bypass: before Enable, the159// verifier returns true regardless of paused state. So executing a160// pause proposal before Enable has no effect on deploys, but the161// value persists and applies the moment Enable runs. To avoid this162// staging trap, operators should call Enable BEFORE any pause163// proposals are voted in.164//165// Idempotency: calling ProposeSetPaused(v) when the realm's current166// paused state already equals v panics at proposal-creation time so167// voters never see a proposal whose execution would no-op.168func ProposeSetPaused(cur realm, v bool) dao.ProposalRequest {169	if paused == v {170		panic("paused state already matches requested value; no-op proposal rejected")171	}172	cb := func(cur realm) error {173		setPaused(0, cur, v)174		return nil175	}176	title := "Unpause Namespace Verifier"177	desc := "This proposal unpauses `r/sys/names`. After execution, the namespace verifier will resume normal authorization checks (PA + registered-name paths). MsgCall traffic to existing realms is unaffected (pause was scoped to MsgAddPackage); only NEW package deploys gate on the unpaused state."178	if v {179		title = "Pause Namespace Verifier"180		desc = "This proposal pauses `r/sys/names`. After execution, the namespace verifier will reject EVERY new MsgAddPackage on the chain — PA (personal-address) and registered-name namespaces alike — until a subsequent unpause proposal executes. This is an emergency halt scoped to addpkg; MsgCall traffic to existing realms is unaffected."181	}182	return dao.NewProposalRequestWithFilter(183		title,184		desc,185		dao.NewSimpleExecutor(0, cur, cb, ""),186		govimpl.NewFilterByTier(memberstore.T1),187	)188}189190// setPaused is the private actuator behind ProposeSetPaused's executor191// callback. It is unexported to ensure no path outside the proposal192// flow can flip the flag — every state change goes through GovDAO.193//194// Emits NamespaceEnforcement{Paused,Unpaused} with the executor's195// realm path for off-chain audit trails.196func setPaused(_ int, rlm realm, v bool) {197	paused = v198	if v {199		chain.Emit("NamespaceEnforcementPaused", "by", rlm.Previous().PkgPath())200	} else {201		chain.Emit("NamespaceEnforcementUnpaused", "by", rlm.Previous().PkgPath())202	}203}204205// IsPaused reports the current value of the pause flag. Note: when206// the realm is pre-Enable, IsPaused may return true but the verifier207// will still pass-through (pre-Enable bypass takes priority).208func IsPaused() bool {209	return paused210}211212// verifier checks namespace deployment permissions.213// lookup is the registered-name resolver — pass nil to disable that path214// (used by tests that want to exercise PA-only behavior).215//216// Order of checks (top to bottom, first matching wins):217//  1. !isEnabled → return true   (pre-Enable: testing/dev bypass)218//  2. isPaused   → return false  (emergency halt — INCLUDES PA)219//  3. invalid input → return false220//  4. PA match (addr.String() == namespace) → return true221//  5. Registered-name lookup match → return true222//  6. otherwise → return false223//224// The pause check is intentionally above the PA check. A SetPaused(true)225// halts every deploy regardless of namespace shape.226func verifier(isEnabled, isPaused bool, address_XXX address, namespace string, lookup nameLookupFn) bool {227	if !isEnabled {228		return true // pre-genesis / dev convenience: bypass everything229	}230231	if isPaused {232		return false // emergency halt: reject every deploy including PA233	}234235	if namespace == "" || !address_XXX.IsValid() {236		return false237	}238239	// Path 1: PA (personal-address) namespace.240	// gno.land/{p,r}/{ADDRESS}/**241	if address_XXX.String() == namespace {242		return true243	}244245	// Path 2: registered-name namespace via r/sys/users.246	if lookup != nil {247		if owner, ok := lookup(namespace); ok && owner == address_XXX {248			return true249		}250	}251252	return false253}254

Functions

  • Enable(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})

  • IsAuthorizedAddressForNamespace(address_XXX string, namespace string) bool

  • IsEnabled() bool

  • IsPaused() bool

  • ProposeSetPaused(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, v bool) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}

  • Render(string) string

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

Rendered

RenderedRawgnoweb ↗

vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.