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/namereg/v0

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v0
Namespace
sys / namereg
Files
17 (gnomod.toml)
Exported functions
11
Module
gno.land/r/sys/namereg/v0
gno
0.9

Files (17)

  • gnomod.tomltoml
  • admin.gnogno
  • api.gnogno
  • blacklist.gnogno
  • canonical.gnogno
  • errors.gnogno
  • init.gnogno
  • preregister.gnogno
  • render.gnogno
  • users.gnogno
  • blacklist_test.gnogno
  • canonical_test.gnogno
  • users_test.gnogno
  • z_0_prop1_filetest.gnogno
  • z_1_prop2_filetest.gnogno
  • z_2_prop3_filetest.gnogno
  • z_3_prop4_filetest.gnogno
  • users_test.gnogno
    1package namereg23import (4	"chain"5	"testing"67	"gno.land/p/nt/testutils/v0"8

    Functions

    • Canonicalize(s string) string

    • IsPaused() bool

    • IsReserved(stem string) bool

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

    • ProposeNewName(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}

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

    • Register(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}, username string)

    • Render(path string) string

    • RenderLatestUsersWidget(num int) string

    • ValidateNymFormat(username string) interface {Error func() string}

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

    Rendered

    RenderedRawgnoweb ↗
    "gno.land/p/nt/uassert/v0"
    9 "gno.land/p/nt/urequire/v0"
    10
    11 susers "gno.land/r/sys/users"
    12)
    13
    14// cur is a zero-value realm used as a placeholder when forwarding to
    15// uassert/urequire dispatch helpers that gained an `rlm realm` param.
    16// These tests pass `func()` callbacks (no crossing inside the callback),
    17// so rlm is ignored — a nil realm here is safe.
    18var cur realm
    19
    20func init(cur realm) {
    21 // Unit tests run with DefaultHeight=123, so the production init() in init.gno
    22 // (guarded on height==0) is a no-op in tests. Temporarily reset height so we
    23 // can whitelist this realm as a controller for testing.
    24 testing.SetHeight(0)
    25 susers.AddControllerAtGenesis(cross(cur), chain.PackageAddress("gno.land/r/sys/namereg/v0"))
    26 testing.SetHeight(123)
    27}
    28
    29func TestRegister_Valid(cur realm, t *testing.T) {
    30 // Stems chosen with no `l` characters to keep canonical forms identical
    31 // to the originals — avoids accidental cross-subtest collisions with
    32 // other tests that may register l-bearing names.
    33 validUsernames := []string{
    34 "nym-bravo123", // 5-char stem (minimum)
    35 "nym-tango456", // 5-char stem
    36 "nym-victor789", // 6-char stem
    37 "nym-romeoecho012", // 10-char stem
    38 "nym-mikefoxhote345", // 11-char stem
    39 "nym-mikefoxhotenp678", // 13-char stem (maximum)
    40 }
    41
    42 for _, username := range validUsernames {
    43 addr := testutils.TestAddress(username)
    44
    45 testing.SetRealm(testing.NewUserRealm(addr))
    46 testing.SetOriginCaller(addr)
    47 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", registerPrice)))
    48
    49 urequire.NotPanics(t, cur, func() {
    50 Register(cross(cur), username)
    51 })
    52 }
    53}
    54
    55func TestRegister_Free(cur realm, t *testing.T) {
    56 oldPrice := registerPrice
    57 defer func() { registerPrice = oldPrice }()
    58 registerPrice = 0
    59
    60 addr := testutils.TestAddress("free-payer")
    61
    62 testing.SetRealm(testing.NewUserRealm(addr))
    63 testing.SetOriginCaller(addr)
    64
    65 urequire.NotPanics(t, cur, func() {
    66 Register(cross(cur), "nym-foxtrot456")
    67 })
    68}
    69
    70func TestRegister_InvalidFormat(cur realm, t *testing.T) {
    71 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", registerPrice)))
    72 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("fmt-payer")))
    73
    74 cases := []string{
    75 "", // empty
    76 " ", // whitespace
    77 "alice123", // missing nym- prefix
    78 "usr-alice123", // wrong prefix
    79 "nym-", // prefix only
    80 "nym-abc123", // stem too short (3 chars, need ≥5)
    81 "nym-abcd123", // stem too short (4 chars)
    82 "nym-abcdefghijklmn123", // stem too long (14 chars, need ≤13)
    83 "nym-Alice123", // uppercase in stem
    84 "nym-al1ce123", // digit in stem
    85 "nym-alice12", // only 2 trailing digits
    86 "nym-alice1234", // 4 trailing digits (extra digit in stem too long)
    87 "nym-alice&#($)123", // special chars in stem
    88 }
    89
    90 for _, username := range cases {
    91 uassert.AbortsWithMessage(t, cur, ErrInvalidFormat.Error(), func() {
    92 Register(cross(cur), username)
    93 })
    94 }
    95}
    96
    97func TestRegister_ReservedPrefix(cur realm, t *testing.T) {
    98 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", registerPrice)))
    99 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("prefix-payer")))
    100
    101 cases := []string{
    102 "nym-gnoblah123", // gno prefix
    103 "nym-gnomeland456", // gno prefix
    104 "nym-glasgow012", // gl prefix (most-confusable with bech32 g1...)
    105 "nym-atomic123", // atom prefix
    106 "nym-atonex123", // atone prefix
    107 "nym-photons456", // photon prefix
    108 "nym-cosmoswide789", // cosmos prefix
    109 }
    110
    111 for _, username := range cases {
    112 uassert.AbortsWithMessage(t, cur, ErrReservedPrefix.Error(), func() {
    113 Register(cross(cur), username)
    114 })
    115 }
    116}
    117
    118// gi is intentionally NOT in reservedPrefixes (i is visually distinct
    119// enough from 1/l that legitimate gi* names should be registerable).
    120// Phishing protection for the gi/gl visual class is enforced by
    121// canonical-collision detection in r/sys/users — see
    122// TestRegister_CanonicalCollision.
    123func TestRegister_GiPrefix_Allowed(cur realm, t *testing.T) {
    124 addr := testutils.TestAddress("gi-prefix-allowed")
    125 testing.SetRealm(testing.NewUserRealm(addr))
    126 testing.SetOriginCaller(addr)
    127 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", registerPrice)))
    128
    129 urequire.NotPanics(t, cur, func() {
    130 Register(cross(cur), "nym-gillette789")
    131 })
    132}
    133
    134func TestRegister_Blacklisted(cur realm, t *testing.T) {
    135 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", registerPrice)))
    136 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("blk-payer")))
    137
    138 cases := []string{
    139 // `admin` is in reservedNames; with stem 5 chars it now matches the regex.
    140 "nym-admin123",
    141 // `support` is reserved (7 chars).
    142 "nym-support456",
    143 // `bitcoin` is reserved (7 chars).
    144 "nym-bitcoin789",
    145 // Implicit s-suffix expansion: `blog` is reserved, so `blogs` (stem
    146 // = blog+s) must also be rejected.
    147 "nym-blogs012",
    148 // Canonical match: `setting` is reserved → canonical "setting" (no l).
    149 // Try `setting` directly via 7-char stem.
    150 "nym-setting345",
    151 // Canonical match: `news` is reserved via `new+s` rule; canonical
    152 // of `news` is `news`. With a 5-char stem this won't reach here
    153 // (stem must be ≥5). Use a longer reserved name with l→i interaction:
    154 // `tendermint` (10 chars, no l) → canonical = tendermint → blacklisted.
    155 "nym-tendermint678",
    156 }
    157
    158 for _, username := range cases {
    159 uassert.AbortsWithMessage(t, cur, ErrBlacklisted.Error(), func() {
    160 Register(cross(cur), username)
    161 })
    162 }
    163}
    164
    165func TestRegister_CanonicalCollision(cur realm, t *testing.T) {
    166 // First register a name. Same-digits confusable variant must collide
    167 // in the unified susers canonical store.
    168 addr1 := testutils.TestAddress("collision-1")
    169 testing.SetRealm(testing.NewUserRealm(addr1))
    170 testing.SetOriginCaller(addr1)
    171 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", registerPrice)))
    172 urequire.NotPanics(t, cur, func() {
    173 Register(cross(cur), "nym-balloon123")
    174 })
    175
    176 // Then attempt to register a canonical-equivalent variant — same
    177 // canonical full name (after l→i, etc.). Must reject from susers.
    178 addr2 := testutils.TestAddress("collision-2")
    179 testing.SetRealm(testing.NewUserRealm(addr2))
    180 testing.SetOriginCaller(addr2)
    181 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", registerPrice)))
    182 uassert.AbortsWithMessage(t, cur, susers.ErrCanonicalCollision.Error(), func() {
    183 Register(cross(cur), "nym-baiioon123")
    184 })
    185
    186 // Reverse direction also blocked.
    187 addr3 := testutils.TestAddress("collision-3")
    188 testing.SetRealm(testing.NewUserRealm(addr3))
    189 testing.SetOriginCaller(addr3)
    190 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", registerPrice)))
    191 urequire.NotPanics(t, cur, func() {
    192 Register(cross(cur), "nym-papaiio789")
    193 })
    194
    195 addr4 := testutils.TestAddress("collision-4")
    196 testing.SetRealm(testing.NewUserRealm(addr4))
    197 testing.SetOriginCaller(addr4)
    198 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", registerPrice)))
    199 uassert.AbortsWithMessage(t, cur, susers.ErrCanonicalCollision.Error(), func() {
    200 Register(cross(cur), "nym-papallo789")
    201 })
    202}
    203
    204// Decision #2 in Option B: store key is full canonical username (not
    205// stem). Same alpha stem with different digit suffixes coexists — does
    206// not collide.
    207func TestRegister_SameStemDifferentDigits_Coexist(cur realm, t *testing.T) {
    208 addrA := testutils.TestAddress("stem-coexist-A")
    209 testing.SetRealm(testing.NewUserRealm(addrA))
    210 testing.SetOriginCaller(addrA)
    211 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", registerPrice)))
    212 urequire.NotPanics(t, cur, func() {
    213 Register(cross(cur), "nym-foolbar000")
    214 })
    215
    216 addrB := testutils.TestAddress("stem-coexist-B")
    217 testing.SetRealm(testing.NewUserRealm(addrB))
    218 testing.SetOriginCaller(addrB)
    219 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", registerPrice)))
    220 urequire.NotPanics(t, cur, func() {
    221 Register(cross(cur), "nym-foolbar999")
    222 })
    223}
    224
    225func TestRegister_TakenUsername(cur realm, t *testing.T) {
    226 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", registerPrice)))
    227 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("taken-payer")))
    228
    229 username := "nym-zulutwo567"
    230
    231 urequire.NotPanics(t, cur, func() {
    232 Register(cross(cur), username)
    233 })
    234
    235 // Re-registration of the same exact name is rejected by susers via
    236 // ErrNameTaken (the nameStore.Has check precedes the canonical check).
    237 uassert.AbortsWithMessage(t, cur, susers.ErrNameTaken.Error(), func() {
    238 Register(cross(cur), username)
    239 })
    240}
    241
    242func TestRegister_InvalidPayment(cur realm, t *testing.T) {
    243 addr := testutils.TestAddress("payment-payer")
    244
    245 testing.SetRealm(testing.NewUserRealm(addr))
    246 testing.SetOriginCaller(addr)
    247 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", 12))) // invalid
    248
    249 uassert.AbortsContains(t, cur, ErrInvalidPayment.Error(), func() {
    250 Register(cross(cur), "nym-yankee345")
    251 })
    252}
    253
    254// Audit finding #11: the OriginSend() payment check is only trustworthy
    255// when the direct caller is a pure EOA (IsUserCall). Any intermediate
    256// code realm can attach -send to the tx, keep the coins, and call
    257// Register; OriginSend() would still describe the envelope but namereg
    258// would receive nothing. The EOA-only guard must reject all such callers.
    259func TestRegister_IntermediateCodeRealmRejected(cur realm, t *testing.T) {
    260 testing.SetRealm(testing.NewCodeRealm("gno.land/r/evil/wrapper"))
    261 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", registerPrice)))
    262
    263 uassert.AbortsWithMessage(t, cur, ErrNonUserCall.Error(), func() {
    264 Register(cross(cur), "nym-whisky345")
    265 })
    266}
    267
    268// Audit finding #13: the OriginSend mismatch error must report the
    269// CURRENT registerPrice, not the value frozen at package init time.
    270func TestRegister_PaymentErrorReflectsCurrentPrice(cur realm, t *testing.T) {
    271 oldPrice := registerPrice
    272 defer func() { registerPrice = oldPrice }()
    273
    274 registerPrice = 5_000_000
    275
    276 addr := testutils.TestAddress("price-payer")
    277 testing.SetRealm(testing.NewUserRealm(addr))
    278 testing.SetOriginCaller(addr)
    279 testing.SetOriginSend(chain.NewCoins(chain.NewCoin("ugnot", 1_000_000))) // wrong
    280
    281 uassert.AbortsContains(t, cur, "5000000", func() {
    282 Register(cross(cur), "nym-xrayfox678")
    283 })
    284
    285 uassert.AbortsContains(t, cur, ErrInvalidPayment.Error(), func() {
    286 Register(cross(cur), "nym-xrayfox678")
    287 })
    288}
    289
    290// Audit finding #14: ProposeNewRegisterPrice originally rejected only
    291// negative prices. A passed proposal to set a negative price would
    292// have been arithmetically nonsense; reject below MinRegisterPrice
    293// (currently 0) at proposal-creation time so governance can never
    294// underflow the floor.
    295func TestProposeNewRegisterPrice_floor(cur realm, t *testing.T) {
    296 t.Run("zero is accepted (free registration is the default)", func(t *testing.T) {
    297 urequire.NotPanics(t, cur, func() { ProposeNewRegisterPrice(cur, 0) })
    298 })
    299
    300 t.Run("negative is rejected", func(t *testing.T) {
    301 urequire.PanicsWithMessage(t, cur,
    302 "price below floor: -1 ugnot < 0 ugnot (MinRegisterPrice)",
    303 func() { ProposeNewRegisterPrice(cur, -1) })
    304 })
    305
    306 t.Run("at floor is accepted", func(t *testing.T) {
    307 urequire.NotPanics(t, cur, func() { ProposeNewRegisterPrice(cur, MinRegisterPrice) })
    308 })
    309
    310 t.Run("above floor is accepted", func(t *testing.T) {
    311 urequire.NotPanics(t, cur, func() { ProposeNewRegisterPrice(cur, 1_000_000_000) })
    312 })
    313}
    314

    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.