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/users

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
users
Namespace
sys
Files
12 (README)(gnomod.toml)
Exported functions
21
Module
gno.land/r/sys/users
gno
0.9

Files (12)

  • README.mdmarkdown
  • gnomod.tomltoml
  • admin.gnogno
  • api.gnogno
  • canonical.gnogno
  • errors.gnogno
  • render.gnogno
  • store.gnogno
  • users.gnogno
  • crossrealm_test.gnogno
  • store_test.gnogno
  • users_test.gnogno
  • admin.gnogno
    1package users23import (4	"chain"5	"chain/runtime"67	"gno.land/p/moul/addrset/v0"8

    Functions

    • AddControllerAtGenesis(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}, addr string)

    • Canonicalize(name string) string

    • Controllers() [].uverse.address

    • GetReadonlyAddrStore() *gno.land/p/nt/bptree/rotree/v0.ReadOnlyTree

    • GetReadOnlyNameStore() *gno.land/p/nt/bptree/rotree/v0.ReadOnlyTree

    • IsCanonicalTaken(name string) (existing string, taken bool)

    • IsController(addr string) bool

    • IsNameTaken(name string) bool

    • NewErrNotWhitelisted(int, caller 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}) struct{Caller string}

    • ProposeControllerAdditionAndRemoval(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}, toAdd string, toRemove string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}

    • ProposeControllerRemoval(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}, addr string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}

    • ProposeDeleteUser(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}, addr string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}

    • ProposeNewController(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}, addr string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}

    • ProposeRegisterUser(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}, name string, addr string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}

    • ProposeUpdateName(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}, addr string, newName string) struct{title string; description string; executor gno.land/r/gov/dao.Executor; filter gno.land/r/gov/dao.Filter}

    • RegisterUser(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}, name string, address_XXX string) interface {Error func() string}

    • RegisterUserIgnoreCanonical(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}, name string, address_XXX string) interface {Error func() string}

    • Render(string) string

    • ResolveAddress(addr string) *gno.land/r/sys/users.UserData

    • ResolveAny(input string) (*gno.land/r/sys/users.UserData, bool)

    • ResolveName(name string) (data *gno.land/r/sys/users.UserData, isCurrent bool)

    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.

    "gno.land/p/nt/ufmt/v0"
    9
    10 "gno.land/r/gov/dao"
    11)
    12
    13const initControllerPath = "gno.land/r/sys/users/init"
    14
    15var controllers = addrset.Set{} // caller whitelist
    16
    17func init() {
    18 // auto-whitelist the init controller for bootstrapping for testing chain.
    19 if chainID := runtime.ChainID(); chainID == "dev" {
    20 controllers.Add(chain.PackageAddress(initControllerPath))
    21 }
    22}
    23
    24// AddControllerAtGenesis allows adding a controller during chain genesis (height 0).
    25// This is mostly useful for testing.
    26func AddControllerAtGenesis(_ realm, addr address) {
    27 height := runtime.ChainHeight()
    28 if height > 0 {
    29 panic("AddControllerAtGenesis can only be called at genesis (height 0)")
    30 }
    31
    32 if !addr.IsValid() {
    33 panic(ErrInvalidAddress)
    34 }
    35
    36 controllers.Add(addr)
    37}
    38
    39// ProposeNewController allows GovDAO to add a whitelisted caller
    40func ProposeNewController(cur realm, addr address) dao.ProposalRequest {
    41 if !addr.IsValid() {
    42 panic(ErrInvalidAddress)
    43 }
    44
    45 cb := func(cur realm) error {
    46 return addToWhitelist(addr)
    47 }
    48
    49 desc := "This proposal adds " + addr.String() + " to `sys/users` realm's callers whitelist."
    50 return dao.NewProposalRequest("Add Whitelisted Caller to \"sys/users\" Realm", desc, dao.NewSimpleExecutor(0, cur, cb, ""))
    51}
    52
    53// ProposeControllerRemoval allows GovDAO to add a whitelisted caller
    54func ProposeControllerRemoval(cur realm, addr address) dao.ProposalRequest {
    55 if !addr.IsValid() {
    56 panic(ErrInvalidAddress)
    57 }
    58
    59 cb := func(cur realm) error {
    60 return deleteFromWhitelist(addr)
    61 }
    62
    63 desc := "This proposal removes " + addr.String() + " from `sys/users` realm's callers whitelist."
    64 return dao.NewProposalRequest("Remove Whitelisted Caller From \"sys/users\" Realm", desc, dao.NewSimpleExecutor(0, cur, cb, ""))
    65}
    66
    67// ProposeControllerAdditionAndRemoval allows GovDAO to add a new caller and remove an old caller in the same proposal.
    68func ProposeControllerAdditionAndRemoval(cur realm, toAdd, toRemove address) dao.ProposalRequest {
    69 if !toAdd.IsValid() || !toRemove.IsValid() {
    70 panic(ErrInvalidAddress)
    71 }
    72
    73 cb := func(cur realm) error {
    74 return applyControllerSwap(toAdd, toRemove)
    75 }
    76
    77 desc := ufmt.Sprintf(
    78 "This proposal adds %s and removes %s from `sys/users` realm's callers whitelist.",
    79 toAdd,
    80 toRemove,
    81 )
    82 return dao.NewProposalRequest("Add and Remove Whitelisted Callers From \"sys/users\" Realm", desc, dao.NewSimpleExecutor(0, cur, cb, ""))
    83}
    84
    85// applyControllerSwap is the callback body of ProposeControllerAdditionAndRemoval,
    86// extracted so it can be unit-tested without driving the full GovDAO flow.
    87//
    88// The desired end state is "toAdd is in the whitelist AND toRemove is out".
    89// Both operations are made idempotent so the swap doesn't silently no-op when
    90// the chain state has drifted between proposal creation and execution:
    91//
    92// - If toAdd is already whitelisted, treat addToWhitelist's
    93// ErrAlreadyWhitelisted as benign and continue to the remove step.
    94// - If toRemove is already absent, treat deleteFromWhitelist's
    95// ErrNotWhitelisted as benign and return success.
    96//
    97// Without this idempotency, the original code returned early on an "already
    98// whitelisted" toAdd and skipped the remove entirely — a swap proposal could
    99// pass governance and silently leave the old controller active. (audit
    100// finding #6)
    101func applyControllerSwap(toAdd, toRemove address) error {
    102 if err := addToWhitelist(toAdd); err != nil && err != ErrAlreadyWhitelisted {
    103 return err
    104 }
    105 if err := deleteFromWhitelist(toRemove); err != nil {
    106 if _, alreadyOut := err.(ErrNotWhitelisted); !alreadyOut {
    107 return err
    108 }
    109 }
    110 return nil
    111}
    112
    113// ProposeRegisterUser allows GovDAO to register a name without checking
    114// controllers. The executor closure runs with ignoreCanonical=true (decision
    115// #3): DAO grants always bypass canonical-collision detection. Voters see
    116// any collision in the proposal description and can vote NO if unintended.
    117func ProposeRegisterUser(cur realm, name string, addr address) dao.ProposalRequest {
    118 // Validate the name and address now, even though registerUser will validate again
    119 if err := validateName(name); err != nil {
    120 panic(err.Error())
    121 }
    122 if !addr.IsValid() {
    123 panic(ErrInvalidAddress)
    124 }
    125
    126 desc := "This proposal registers " + name + " with address " + addr.String() + " in `sys/users`."
    127 if existing, taken := IsCanonicalTaken(name); taken && existing != name {
    128 desc += "\n\nCANONICAL COLLISION: this name's canonical form matches the existing registration of `" +
    129 existing + "`. DAO grants bypass canonical-collision detection — the proposal will succeed if voted in. " +
    130 "If the collision is unintended, vote NO."
    131 }
    132
    133 cb := func(cur realm) error {
    134 return registerUser(cur, name, addr, true) // bypass canonical (decision #3)
    135 }
    136
    137 return dao.NewProposalRequest("Register User to \"sys/users\" Realm", desc, dao.NewSimpleExecutor(0, cur, cb, ""))
    138}
    139
    140// ProposeUpdateName allows GovDAO to update a name with an alias without
    141// checking controllers. Like ProposeRegisterUser, the executor runs with
    142// ignoreCanonical=true (decision #3).
    143func ProposeUpdateName(cur realm, addr address, newName string) dao.ProposalRequest {
    144 if !addr.IsValid() {
    145 panic(ErrInvalidAddress)
    146 }
    147 if err := validateName(newName); err != nil {
    148 panic(err.Error())
    149 }
    150
    151 desc := "This proposal updates address " + addr.String() + " with alias " + newName + " in `sys/users`."
    152 if existing, taken := IsCanonicalTaken(newName); taken && existing != newName {
    153 desc += "\n\nCANONICAL COLLISION: the new alias's canonical form matches the existing registration of `" +
    154 existing + "`. DAO grants bypass canonical-collision detection — the proposal will succeed if voted in. " +
    155 "If the collision is unintended, vote NO."
    156 }
    157
    158 cb := func(cur realm) error {
    159 data := ResolveAddress(addr)
    160 if data == nil {
    161 return ErrUserNotExistOrDeleted
    162 }
    163 return data.updateName(newName, true) // bypass canonical (decision #3)
    164 }
    165
    166 return dao.NewProposalRequest("Update Name Alias in \"sys/users\" Realm", desc, dao.NewSimpleExecutor(0, cur, cb, ""))
    167}
    168
    169// ProposeDeleteUser allows GovDAO to delete a user without checking controllers
    170func ProposeDeleteUser(cur realm, addr address) dao.ProposalRequest {
    171 if !addr.IsValid() {
    172 panic(ErrInvalidAddress)
    173 }
    174
    175 cb := func(cur realm) error {
    176 data := ResolveAddress(addr)
    177 if data == nil {
    178 return ErrUserNotExistOrDeleted
    179 }
    180 return data.delete()
    181 }
    182
    183 desc := "This proposal deletes the user with address " + addr.String() + " in `sys/users`."
    184 return dao.NewProposalRequest("Delete User in \"sys/users\" Realm", desc, dao.NewSimpleExecutor(0, cur, cb, ""))
    185}
    186
    187// IsController reports whether the given address is currently in the
    188// controller whitelist. Returns the same boolean that gating checks
    189// (RegisterUser, UpdateName, Delete) use internally — useful for
    190// off-chain monitoring and for governance proposals to inspect state
    191// before voting.
    192func IsController(addr address) bool {
    193 return controllers.Has(addr)
    194}
    195
    196// Controllers returns a snapshot of the current controller whitelist.
    197// The returned slice is a fresh copy; mutating it does not affect realm
    198// state. Order is the iteration order of the underlying address set.
    199//
    200// Audit finding #20: without this getter, the controller whitelist was
    201// opaque from outside the package — operators had to read source or
    202// replay every governance proposal to know who could write to the
    203// registry. This is the read-only API that closes that gap.
    204func Controllers() []address {
    205 out := make([]address, 0, controllers.Size())
    206 controllers.IterateByOffset(0, controllers.Size(), func(a address) bool {
    207 out = append(out, a)
    208 return false
    209 })
    210 return out
    211}
    212
    213// Helpers
    214
    215func deleteFromWhitelist(addr address) error {
    216 if !controllers.Has(addr) {
    217 return ErrNotWhitelisted{Caller: "UserRealm{ " + addr.String() + " }"}
    218 }
    219
    220 if ok := controllers.Remove(addr); !ok {
    221 return ErrWhitelistRemoveFailed
    222 }
    223
    224 return nil
    225}
    226
    227func addToWhitelist(newCaller address) error {
    228 if !controllers.Add(newCaller) {
    229 return ErrAlreadyWhitelisted
    230 }
    231
    232 return nil
    233}
    234