Skip to content

feat(pop): keep the separator in lite names and record issuance provenance - #275

Merged
re-gius merged 15 commits into
masterfrom
re-gius/dotted-lite-labels
Sep 4, 2026
Merged

feat(pop): keep the separator in lite names and record issuance provenance#275
re-gius merged 15 commits into
masterfrom
re-gius/dotted-lite-labels

Conversation

@re-gius

@re-gius re-gius commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Description

The gateway sends michael.42. DotnsPopController._reserveLite stripped the separator and stored alice42, making the contracts the only layer that disagreed with People Chain, the gateway pallet and the host apps. This keeps it: a lite name is stored, minted and shown as michael.42, hashed as one whole label.

A dotted string is ambiguous on its own. To the personhood system michael.42 is one person; to the hierarchical system it is michael beneath 42, the same shape as shop.google. The characters cannot say which. So provenance becomes explicit rather than inferred: isPopIssued(label) is set at mint, never cleared, and keyed by the bare label rather than the node — a reader holding only michael.42 cannot derive the node without first deciding how to hash the separator, which is the question it is asking.

The rule

A name a person chose is lowercase ASCII letters.

Shape Example
Lite letters, one separator, exactly two digits joseph.42
Full-person letters joseph

It mirrors People Chain, where validate_username requires the lite stem and the full-person username to be is_ascii_lowercase, and BaseLabel::is_valid_person is documented as "lowercase ASCII letters only, no digits or hyphens". A label outside the shape cannot have been issued.

Ordinary public names are untouched. They stay isSingleLabel: letters, digits and hyphens, any digit count. So web3, andrew-x and longnamebob01 remain well-formed public labels, and only the gateway paths are letters-only. Being well-formed is not being for sale: classification still decides that, and of the three only longnamebob01 is open to anyone. andrew-x requires full personhood and web3 is governance-reserved.

Length is policy, not format. Neither predicate has a floor. How short a name may be is the governance-reserved band in PopRules, so alice.42 is a well-formed lite label that classification rejects. Mirroring People Chain's MinUsernameLength would duplicate that and drift when the runtime changes it.

Base length is the label as written, except for a lite label, whose separator and two allocated digits come off first. The gateway allocates those digits to distinguish people who chose the same stem, so removing them recovers what the candidate picked; no such allocation stands behind the digits in web3.

Label Base length Tier
joseph.42 6 (stem) PopLite
elizabeth.42 9 (stem) NoStatus
alice.42 5 (stem) Reserved
joseph42 8 (whole) PopFull
web3 4 (whole) Reserved
longnamebob01 13 (whole) NoStatus

The label-taking views now answer for a dotted label. classifyName, price, priceWithCheck and the rest admit a lite label through _requireLabel, so michael.42 returns a classification where it previously reverted. Minting is unchanged: register still requires isSingleLabel, so the public path cannot submit one. Any off-chain caller that treated a non-revert from these views as proof of a public flat label needs to stop reading it that way.

Type

  • Bug fix
  • Feature
  • Breaking change
  • Documentation
  • Chore
  • Refactor
  • Security

Scope

  • Registration
  • Resolver
  • Store
  • Proof of Personhood
  • Deployment scripts
  • Tests

Related Issues

Follows #270. Supersedes #273, which reached the same goal by narrowing the public grammar. That route changed what public names mean to satisfy a representation need originating in the personhood subsystem.

Fixes

Closes #274. The host and gateway acceptance criteria belong to #273 instead.

Checklist

Code

  • Follows project style
  • forge build passes
  • forge test passes
  • No new compiler warnings

Testing

  • New tests added for changed behavior
  • Fuzz tests added where applicable
  • Invariant tests verified

Security

  • No new selfdestruct or delegatecall
  • Access control reviewed
  • No storage layout conflicts (for upgradeable contracts)

Documentation

  • NatSpec updated on changed interfaces
  • README updated if needed

Breaking Changes

  • No breaking changes
  • Breaking changes documented below

Breaking changes:

  • ERC-165 interface id moved. Adding isPopIssued changes type(IDotnsPopController).interfaceId. supportsInterface answers both the new and the pre-isPopIssued id, derived as interfaceId ^ isPopIssued.selector rather than hardcoded so it cannot drift from a copied constant. Regression test included.
  • Storage layout. _popIssued appended before __gap, which shrank from 50 to 49.
  • The full-person path narrows to letters only. alice-bob and micha3l are valid DNS labels and were issuable as identities; they no longer are. They remain registrable as ordinary public names. The previous check caught only a trailing digit, so it admitted both.
  • PopLite is reachable only through the gateway. No flat label classifies PopLite, because digits in an ordinary label say nothing about personhood.
  • Flat digit-suffixed names reclassify. Measured whole rather than digit-stripped, so alice42 moves from Reserved to PopFull. Conversely web3 and mp3, previously rejected outright for carrying a one-digit suffix, are now ordinary labels.
  • The lens listings are no longer a partition. liteNamesOf and fullNamesOf both require provenance, so together they cover what the gateway issued rather than everything an account holds. A public registration appears in neither, where previously any single label landed in fullNamesOf. This is the change most likely to surface as a missing row rather than an error, so dotli and UI owners should see it.
  • Identities minted before this change are not listed. previewnet is upgraded in place and provenance cannot be recovered for names already on chain, so they are re-issued through the gateway rather than migrated.

How to test

forge test --mt test_reserveLiteName_stores_the_label_with_its_separator
forge test --mt test_isPopIssued
forge test --mt test_classify_differs_with_and_without_the_separator
forge test --mt test_isPersonLabel_accepts_letters_only
forge test --mt test_registerBaseName_rejects_a_label_that_is_not_letters_only
forge test --mt test_subname_under_a_two_digit_name_does_not_collide_with_a_person
forge test --mt test_lens_lists_a_full_person_name_as_full
forge test --mt test_lens_omits_a_public_registration_from_both_listings
forge test --mt test_supportsInterface_answers_the_pre_isPopIssued_id

Notes

One subtlety worth reviewing closely. _classifyValidatedName derived the digit count as bytes(name).length - baseLength. For a separated label that yields 3, never 2, so a subtraction-based check silently classifies every lite name as PopFull — no revert, no event, just the wrong tier. It now asks the shape predicate. Mutation-tested: reverting to the subtraction fails two tests with 2 != 1.

Why the legacy set cannot be recovered. isPopIssued is written at mint and postdates those names; the registrar's soulbound flag is absent from contracts/ at v0.5.8-rc1 and is also mint-time only, so no upgrade backfills it.

Both lens signals are load-bearing. isPopIssued is written in _completeGatewayRegistration, so it answers true for lite and full names alike and cannot say which kind a name is. Provenance decides whether a name is an identity; the separator decides which kind. Collapsing either into the other breaks a listing.

A separator remains impossible on a subname, which is what reserves the dotted space to the gateway.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

CI Summary

Check Result
4naly3er Analysis Found 38 issues: 5 medium, 8 low, 14 gas, 11 informational - View Report
Slither Analysis Found 181 issues: 4 high, 40 medium, 80 low, 57 informational - View Report
Contract Tests (Unit + Fuzz) All tests passed (620 total) - View Report
Contract Tests (Invariant) All tests passed (59 total) - View Report
Gas Report 9 contracts analyzed - View Report
Coverage Failed
Documentation Passed - 64 pages generated - View Docs
Format & Lint Passed - Code formatted correctly
Deploy Contracts Reproduces the committed manifest; resume verified
PR Title PR Title Valid
Labels Unknown
Secret Scan Passed - No secrets detected

4naly3er Analysis

Medium (5)

ID Finding Instances
M-1 block.number means different things on different L2s 5
M-2 Centralization Risk for trusted owners 27
M-3 _safeMint() should be used rather than _mint() wherever possible 1
M-4 Using transferFrom on ERC721 tokens 1
M-5 Direct supportsInterface() calls may cause caller to revert 9

Low (8)

ID Finding Instances
L-1 Use a 2-step ownership transfer pattern 4
L-2 External call recipient may consume all transaction gas 8
L-3 Initializers could be front-run 42
L-4 Signature use at deadlines should be allowed 5
L-5 Use Ownable2Step.transferOwnership instead of `Ownable.transferOwner 2
L-6 Unsafe ERC20 operation(s) 1
L-7 Upgradeable contract is missing a __gap[50] storage variable to allo 116
L-8 Upgradeable contract not initialized 174

Gas (14)

ID Finding Instances
GAS-1 Use ERC721A instead ERC721 1
GAS-2 a = a + b is more gas effective than a += b for state variables (e 13
GAS-3 Using bools for storage incurs overhead 5
GAS-4 Cache array length outside of loop 5
GAS-5 For Operations that will not overflow, you could use unchecked 339
GAS-6 Use Custom Errors instead of Revert Strings to save Gas 12
GAS-7 Functions guaranteed to revert when called by normal users can be mark 54
GAS-8 ++i costs less gas compared to i++ or i += 1 (same for --i vs 12
GAS-9 Using private rather than public for constants, saves gas 11
GAS-10 Use shift right/left instead of division/multiplication if possible 1
GAS-11 Splitting require() statements that use && saves gas 4
GAS-12 uint256 to bool mapping: Utilizing Bitmaps to dramatically save 1
GAS-13 Increments/decrements can be unchecked in for-loops 32
GAS-14 Use != 0 instead of > 0 for unsigned integer comparison 22

Informational (11)

ID Finding Instances
NC-1 constants should be defined rather than using magic numbers 18
NC-2 Control structures do not follow the Solidity Style Guide 107
NC-3 Critical Changes Should Use Two-step Procedure 3
NC-4 Dangerous while(true) loop 1
NC-5 Consider disabling renounceOwnership() 3
NC-6 Functions should not be longer than 50 lines 417
NC-7 Use a modifier instead of a require/if statement for a special `ms 18
NC-8 addresss shouldn't be hard-coded 1
NC-9 Take advantage of Custom Error's return value property 1
NC-10 Avoid the use of sensitive terms 28
NC-11 Variables need not be initialized to zero 9

View full report | View logs

Slither Analysis

High (4)

Check Description Location
arbitrary-send-eth DotnsRegistrarController._settleEscrow(address,uint256,address,bool,uint256) (co contracts/registrars/DotnsRegistrarController.sol:263
arbitrary-send-eth Multicall3.aggregate3Value(Multicall3.Call3Value[]) (contracts/utils/Multicall3. contracts/utils/Multicall3.sol:159
reentrancy-eth Reentrancy in DotnsRegistrar.register(uint256,address,string) (contracts/registr contracts/registrars/DotnsRegistrar.sol:125
uninitialized-state DotnsNameEscrow._entriesByRecipient (contracts/escrow/DotnsNameEscrow.sol#101) i contracts/escrow/DotnsNameEscrow.sol:101

Medium (40)

Check Description Location
incorrect-equality DotnsRegistrarController.commit(bytes32) (contracts/registrars/DotnsRegistrarCon contracts/registrars/DotnsRegistrarController.sol:142
reentrancy-no-eth Reentrancy in DotnsPopController.registerBaseName(IDotnsPopController.FullRegist contracts/registrars/DotnsPopController.sol:250
reentrancy-no-eth Reentrancy in DotnsPopController.reserveBaseNameOnly(IDotnsPopController.BaseNam contracts/registrars/DotnsPopController.sol:212
reentrancy-no-eth Reentrancy in DotnsPopController.registerBaseName(IDotnsPopController.FullRegist contracts/registrars/DotnsPopController.sol:250
reentrancy-no-eth Reentrancy in DotnsPopController.reserveBaseName(IDotnsPopController.BaseReserva contracts/registrars/DotnsPopController.sol:194
reentrancy-no-eth Reentrancy in DotnsPopController.reserveBaseName(IDotnsPopController.BaseReserva contracts/registrars/DotnsPopController.sol:194
reentrancy-no-eth Reentrancy in DotnsPopController._releasePopRulesSlot(bytes32) (contracts/regist contracts/registrars/DotnsPopController.sol:865
reentrancy-no-eth Reentrancy in DotnsPopController.reserveBaseNameOnly(IDotnsPopController.BaseNam contracts/registrars/DotnsPopController.sol:212
uninitialized-local DotnsPopController.reserveBaseName(IDotnsPopController.BaseReservation).reserved contracts/registrars/DotnsPopController.sol:196
uninitialized-local DotnsPopLens._pageNames(address,uint256,uint256,bool).seen (contracts/registrars contracts/registrars/DotnsPopLens.sol:183
+30 more

Low (80)

Check Description Location
shadowing-local IDotnsPopResolver.setChatKey(bytes32,bytes).chatKey (contracts/resolvers/IDotnsP contracts/resolvers/IDotnsPopResolver.sol:53
shadowing-local IDotnsPopResolver.chatKey(bytes32).chatKey (contracts/resolvers/IDotnsPopResolve contracts/resolvers/IDotnsPopResolver.sol:71
events-maths DotnsRegistrarController.initialize(IDotnsProtocolRegistry,uint256,uint256) (con contracts/registrars/DotnsRegistrarController.sol:93
calls-loop DotnsPopLens._controller() (contracts/registrars/DotnsPopLens.sol#277-279) has e contracts/registrars/DotnsPopLens.sol:277
calls-loop DotnsPopController._settlePendingLabel(IStoreFactory,address,address,string) (co contracts/registrars/DotnsPopController.sol:395
calls-loop DotnsPopController._settlePendingLabel(IStoreFactory,address,address,string) (co contracts/registrars/DotnsPopController.sol:395
calls-loop DotnsPopLens._controller() (contracts/registrars/DotnsPopLens.sol#277-279) has e contracts/registrars/DotnsPopLens.sol:277
calls-loop DotnsPopLens._pageNames(address,uint256,uint256,bool) (contracts/registrars/Dotn contracts/registrars/DotnsPopLens.sol:164
calls-loop Multicall3.aggregate3(Multicall3.Call3[]) (contracts/utils/Multicall3.sol#128-15 contracts/utils/Multicall3.sol:128
calls-loop DotnsPopLens._controller() (contracts/registrars/DotnsPopLens.sol#277-279) has e contracts/registrars/DotnsPopLens.sol:277
+70 more

Informational (57)

Check Description Location
assembly LabelUtils.namehashUnder(bytes32,bytes32) (contracts/utils/LabelUtils.sol#52-63) contracts/utils/LabelUtils.sol:52
assembly LabelUtils.labelhashMemory(string) (contracts/utils/LabelUtils.sol#40-44) uses a contracts/utils/LabelUtils.sol:40
assembly Multicall3.aggregate3Value(Multicall3.Call3Value[]) (contracts/utils/Multicall3. contracts/utils/Multicall3.sol:159
assembly LabelUtils.labelhash(string) (contracts/utils/LabelUtils.sol#25-32) uses assembl contracts/utils/LabelUtils.sol:25
assembly Multicall3.aggregate3(Multicall3.Call3[]) (contracts/utils/Multicall3.sol#128-15 contracts/utils/Multicall3.sol:128
assembly DotnsRegistry._parentNamehash(string) (contracts/registry/DotnsRegistry.sol#217- contracts/registry/DotnsRegistry.sol:217
costly-loop DotnsNameEscrow._removeRefundEntry(uint256,address) (contracts/escrow/DotnsNameE contracts/escrow/DotnsNameEscrow.sol:641
costly-loop DotnsNameEscrow._removeRefundEntry(uint256,address) (contracts/escrow/DotnsNameE contracts/escrow/DotnsNameEscrow.sol:641
cyclomatic-complexity DotnsPopLens._pageNames(address,uint256,uint256,bool) (contracts/registrars/Dotn contracts/registrars/DotnsPopLens.sol:164
dead-code DotnsRegistrar._popRules() (contracts/registrars/DotnsRegistrar.sol#388-390) is contracts/registrars/DotnsRegistrar.sol:388
+47 more

View full report | View logs

Contract Tests (Unit + Fuzz)

BasicDotnsIntegrationReverts (test/intergration/BasicDotns.reverts.t.sol)

Test Result Error
test_parent_can_reassign_existing_subdomain PASS
test_revert_non_owner_cannot_create_subdomain_under_someone_elses_name PASS
test_revert_poplite_cannot_register_popfull_required PASS
test_revert_unapproved_cannot_set_contenthash PASS

BasicDotnsIntegration (test/intergration/BasicDotns.t.sol)

Test Result Error
test_nostatus_end_to_end PASS
test_popfull_end_to_end PASS
test_poplite_end_to_end PASS
test_third_party_reserved_registration_preserves_existing_reverse PASS

DeployCreate3FactoryTest (test/unit/deploy/DeployCreate3Factory.t.sol)

Test Result Error
test_revertsWhenDeployerNonceNotZero PASS

DeterministicDeploymentTest (test/unit/deploy/DeterministicDeployment.t.sol)

Test Result Error
test_addressesIdenticalAcrossDeployers PASS
test_addressesStableAcrossSequentialRuns PASS
test_adoptRevertsWhenFactoryHasNoCode PASS
test_coreDeploymentAddressesStayTheSameAcrossChainIds PASS
test_create3FactoryResolvesFromProtocolRegistry PASS
test_ensureReusesConfiguredFactory PASS
test_predictionsMatchCreate3Deployments PASS
test_predictionsMatchForNonProxyDeploys PASS
test_reDeployAdoptsAnExistingContract PASS
test_reDeployAdoptsProxyWithoutReinitialising PASS
test_reusedFactoryMakesAddressesDeployerIndependent PASS

DotnsContentResolverTests (test/unit/resolver/DotnsContentResolver.t.sol)

Test Result Error
test_operator_can_modify_records PASS
test_set_contenthash PASS
test_set_text PASS

DotnsCostModelRegistryTests (test/unit/pop/DotnsCostModelRegistry.t.sol)

Test Result Error
test_register_emits_cost_model_registered PASS
test_register_only_owner PASS
test_register_reverts_for_zero_version PASS
test_register_reverts_on_duplicate_version PASS
test_register_sets_current_version_and_model PASS
test_second_model_moves_current_but_keeps_old_priceable PASS
test_setCurrentVersion_emits_current_model_set PASS
test_setCurrentVersion_only_owner PASS
test_setCurrentVersion_reverts_for_unknown_version PASS
test_setCurrentVersion_reverts_to_older_version PASS
test_unknown_version_reverts PASS

DotnsFlatPricingTests (test/unit/pop/DotnsFlatPricing.t.sol)

Test Result Error
testFuzz_price_is_constant PASS
test_constructor_reverts_for_zero_deposit PASS
test_prices_every_base_length_at_the_deposit PASS
test_version_changes_with_the_deposit PASS
test_version_differs_from_another_model_form PASS
test_version_is_stable_for_the_same_deposit PASS

DotnsNameEscrowTest (test/unit/escrow/DotnsNameEscrow.t.sol)

Test Result Error
test_cross_payer_downgrade_charges_only_owner_price PASS
test_cross_payer_pays_the_curve_into_fees PASS
test_cross_payer_verified_sponsors_nostatus_pays_only_D PASS
test_deposit_records_position PASS
test_downgrade_transfer_pays_name_price PASS
test_funded_position_follows_NFT_through_multiple_transfers PASS
test_funded_position_rebinds_to_new_holder_on_transfer PASS
test_reclaim_transfers_custody_to_new_owner PASS
test_release_and_withdraw_subject_to_cooldown_after_transfer PASS
test_release_transfers_token_to_escrow PASS
test_released_tokens_pagination PASS
test_revert_deposit_already_funded PASS
test_revert_deposit_not_controller PASS
test_revert_double_release PASS
test_revert_ghost_nft_transfer_into_escrow PASS
test_revert_reclaim_before_withdraw PASS
test_revert_reclaim_not_controller PASS
test_revert_release_escrow_not_approved PASS
test_revert_release_not_holder PASS
test_revert_withdraw_before_cooldown PASS
test_revert_withdraw_not_recipient PASS
test_same_tier_NoStatus_transfer_rebinds_position_to_new_holder PASS
test_self_registration_seeds_funded_position PASS
test_solvency_after_force_sent_funds PASS
test_transfer_charges_friction_and_rebinds_position PASS
test_transfer_no_rebind_when_to_equals_position_recipient PASS
test_update_cooldown PASS
test_withdraw_sends_refund_after_cooldown PASS

DotnsNameEscrowFuzzTest (test/fuzz/escrow/DotnsNameEscrowFuzz.t.sol)

Test Result Error
testFuzz_deposit_amount PASS
testFuzz_withdraw_timing PASS

DotnsNameEscrowRedeemTest (test/unit/escrow/DotnsNameEscrowRedeem.t.sol)

Test Result Error
test_reclaim_after_withdrawal_credits_nothing_twice PASS
test_reclaim_settles_an_unwithdrawn_deposit_to_the_previous_holder PASS
test_redeem_removes_the_token_from_released_enumeration PASS
test_redeem_returns_the_name_and_moves_no_value PASS
test_redeem_then_release_again_starts_fresh_clocks PASS
test_release_emits_both_clocks_on_NameReleased PASS
test_release_reverts_when_redeem_window_is_unseeded PASS
test_release_stamps_independent_withdraw_and_redeem_clocks PASS
test_revert_reclaim_while_inside_the_redeem_window PASS
test_revert_redeem_after_the_window_closes PASS
test_revert_redeem_after_withdrawing_the_deposit PASS
test_revert_redeem_by_someone_other_than_the_recipient PASS
test_revert_redeem_on_an_unreleased_position PASS
test_revert_updateRedeemWindow_above_the_ceiling PASS
test_revert_updateRedeemWindow_below_the_floor PASS
test_revert_updateRedeemWindow_from_a_non_owner PASS
test_revert_updateRedeemWindow_on_zero PASS
test_settled_deposit_stays_claimable_long_after_reclaim PASS
test_updateRedeemWindow_accepts_both_bounds PASS
test_updateRedeemWindow_does_not_move_an_in_flight_position PASS
test_updateRedeemWindow_sets_the_value_and_emits PASS
test_zero_amount_release_becomes_reclaimable_without_any_withdrawal PASS
test_zero_amount_withdrawal_does_not_forfeit_the_redeem_right PASS

DotnsNameEscrowRefundsTest (test/unit/escrow/DotnsNameEscrowRefunds.t.sol)

Test Result Error
test_claimRefund_emitsRefundClaimed PASS
test_claimRefund_revertsBeforeCooldown PASS
test_claimRefund_revertsOnTransferFailure PASS
test_claimRefund_revertsWhenCallerIsNotRecipient PASS
test_claimRefund_transfersAndDeletes PASS
test_claimRefundsBatch_aggregatesAndTransfers PASS
test_claimRefundsBatch_atomicOnLockedEntry PASS
test_claimRefundsBatch_revertsOnEmpty PASS
test_creditRefund_allocatesMonotonicEntryIds PASS
test_creditRefund_emitsRefundCredited PASS
test_creditRefund_independentCooldowns PASS
test_creditRefund_revertsOnZeroAmount PASS
test_creditRefund_revertsOnZeroRecipient PASS
test_pendingRefundIds_paginates PASS
test_removeRefundEntry_swapPopMiddlePreservesIndices PASS

DotnsNameWhitelistTests (test/unit/whitelist/DotnsNameWhitelist.t.sol)

Test Result Error
test_accept_picks_winner_and_rejects_losers PASS
test_accept_reverts_for_a_signed_caller PASS
test_accept_reverts_when_not_requested PASS
test_claims_pagination PASS
test_consume_by_pop_controller PASS
test_consume_by_public_controller PASS
test_consume_reverts_for_non_controller PASS
test_consume_reverts_for_wrong_registrant PASS
test_every_admin_entry_point_requires_root PASS
test_full_lifecycle_request_accept_consume PASS
test_governance_root_grants_from_any_caller PASS
test_governance_root_reserves_from_any_caller PASS
test_governance_root_sets_a_cap_from_any_caller PASS
test_grantName_clears_pending_claims PASS
test_grantName_direct PASS
test_grantName_reverts_for_zero_user PASS
test_grantName_reverts_when_not_open PASS
test_grantNames_batch PASS
test_grantNames_reverts_above_batch_limit PASS
test_initial_caps_are_the_defaults PASS
test_initialize_reverts_on_second_call PASS
test_isWindowOpen_tracks_the_window PASS
test_names_pagination_lists_active PASS
test_reject_clears_single_claim PASS
test_reject_on_behalf_does_not_bind PASS
test_reject_self_filed_is_sticky PASS
test_requestName_allows_competing_claims PASS
test_requestName_records_and_emits PASS
test_requestName_reverts_for_non_canonical_label PASS
test_requestName_reverts_for_reason_too_long PASS
test_requestName_reverts_for_same_user_twice PASS
test_requestName_reverts_for_zero_user PASS
test_requestName_reverts_outside_window PASS
test_requestName_reverts_when_reserved PASS
test_requestName_submitter_may_differ_from_user PASS
test_revokeName_clears_open_name_with_claims PASS
test_revokeName_rejects_the_owner PASS
test_revokeName_resets_claimed PASS
test_revokeName_reverts_on_reserved_name PASS
test_revokeName_reverts_when_nothing_to_revoke PASS
test_setMaxClaimants_enforced PASS
test_setMaxClaimants_reverts_for_a_signed_caller PASS
test_setMaxClaimants_reverts_out_of_range PASS
test_setMaxGrantBatch_enforced PASS
test_setMaxGrantBatch_reverts_out_of_range PASS
test_setMaxReasonBytes_enforced PASS
test_setMaxReasonBytes_reverts_out_of_range PASS
test_setReserved_clears_pending_claims PASS
test_setReserved_release_reverts_when_not_reserved PASS
test_setReserved_reserve_and_release PASS
test_setReserved_reverts_for_a_signed_caller PASS
test_setWindow_reverts_for_zero_duration PASS
test_setWindow_sets_and_emits PASS

DotnsNameWhitelistFuzz (test/fuzz/whitelist/DotnsNameWhitelistFuzz.t.sol)

Test Result Error
testFuzz_accept_yields_single_winner PASS
testFuzz_competing_claims_do_not_collide PASS
testFuzz_reason_length_bound PASS
testFuzz_reject_never_reserves PASS
testFuzz_requestName_records PASS
testFuzz_requestName_reverts_after_window_closes PASS

DotnsPopControllerTests (test/unit/registrar/DotnsPopController.t.sol)

Test Result Error
test_advanceExpiredHead_last_expire_releases_popRules_slot PASS
test_advanceExpiredHead_promotes_waiter_and_resyncs_popRules PASS
test_both_controllers_can_mint_on_shared_registrar PASS
test_claimLabelStore_settles_callers_own_pending_claim PASS
test_claim_clears_the_reservation_slot PASS
test_claim_releases_popRules_slot PASS
test_claim_then_reEnqueue_on_same_stem_resets_cleanly PASS
test_enqueueReservation_same_user_second_call_replaces_first PASS
test_enqueue_becomesHead_writes_popRules_reservation PASS
test_entry_point_format_rejections PASS
test_expireReservation_is_permissionless PASS
test_expireReservation_on_empty_queue_is_noop PASS
test_gatewayReserve_stashes_pending_claim_when_user_has_no_label_store PASS
test_gatewayReserve_warm_user_after_settle_writes_directly_without_stashing PASS
test_gateway_reservation_does_not_cover_a_digit_suffixed_name PASS
test_gateway_reserved_name_allows_holder_to_register_via_public PASS
test_gateway_reserved_name_rejects_public_register_by_other_user PASS
test_head_expires_clears_slot_for_next_reserver PASS
test_isPopIssued_covers_full_person_names PASS
test_isPopIssued_holds_across_cold_path_settlement PASS
test_isPopIssued_is_false_for_a_name_the_gateway_did_not_issue PASS
test_isPopIssued_is_set_at_mint PASS
test_lens_lists_a_full_person_name_as_full PASS
test_lens_omits_a_public_registration_from_both_listings PASS
test_liteNamesOf_and_fullNamesOf_split_issued_names_by_shape PASS
test_liteNamesOf_pagination_slices_and_clamps PASS
test_lite_name_cannot_host_a_subname PASS
test_multiWaiter_standaloneGuard_rejects_non_head_user PASS
test_nameDetail_and_nameDetailByNode_report_record PASS
test_name_listings_exclude_names_owned_by_others PASS
test_non_owner_cannot_create_subname_under_pop_minted_name PASS
test_owner_of_pop_minted_name_can_create_subname PASS
test_pendingClaimUsers_enumeration_mirrors_stash_and_settle PASS
test_pendingClaimUsers_pagination_boundary_cases PASS
test_pendingClaimUsers_returns_empty_when_offset_past_count PASS
test_pendingClaims_returns_empty_array_for_fresh_user PASS
test_pop_full_mint_after_public_register_reverts_and_writes_no_provenance PASS
test_pop_reservation_of_already_public_minted_name_reverts_at_reserve_time PASS
test_profileOf_reports_store_pending_and_reservation PASS
test_public_register_after_pop_full_mint_reverts_at_registrar PASS
test_public_stranger_can_mint_after_reservation_expires PASS
test_reEnqueue_after_own_expiry_promotes_same_user_to_head PASS
test_registerBaseName_claim_by_store_less_full_person_piles_then_settles PASS
test_registerBaseName_claim_emits_claim_event_and_not_standalone PASS
test_registerBaseName_claim_inherits_chat_key_from_lite_node PASS
test_registerBaseName_claim_path_bypasses_standalone_holder_guard PASS
test_registerBaseName_claim_wipes_entire_queue PASS
test_registerBaseName_guard_blocks_stranger_and_preserves_claim PASS
test_registerBaseName_popFull_user_on_popFull_label_succeeds PASS
test_registerBaseName_rejects_a_label_that_is_not_letters_only PASS
test_registerBaseName_reverts_for_governance_length_name PASS
test_registerBaseName_reverts_when_origin_is_not_root PASS
test_registerBaseName_standalone_auto_relinquishes_users_other_reservation PASS
test_registerBaseName_standalone_emits_standalone_event_and_not_claim PASS
test_registerBaseName_standalone_succeeds_when_head_is_expired PASS
test_registerBaseName_standalone_succeeds_when_queue_empty PASS
test_registerBaseName_standalone_with_lite_link_silently_relinquishes PASS
test_registerBaseName_zero_length_label_reverts PASS
test_registered_controller_without_root_origin_cannot_enter_pop_flow PASS
test_relinquishReservation_promotes_next_waiter_when_head_leaves PASS
test_relinquishReservation_reverts_when_caller_has_no_reservation PASS
test_relinquish_last_releases_popRules_slot PASS
test_reservation_entrypoints_reject_a_label_that_is_not_letters_only PASS
test_reserveBaseNameOnly_does_not_mint_lite_or_base_name PASS
test_reserveBaseNameOnly_reverts_for_reserved_or_suffixed_labels PASS
test_reserveBaseNameOnly_reverts_when_label_already_registered PASS
test_reserveBaseNameOnly_reverts_when_origin_is_not_root PASS
test_reserveBaseNameOnly_same_user_can_replace_prior_reservation PASS
test_reserveBaseName_accepts_65_byte_chat_key PASS
test_reserveBaseName_enqueues_when_reserved_label_provided PASS
test_reserveBaseName_lite_and_base_legs_both_succeed_in_one_call PASS
test_reserveBaseName_mints_and_wires_registry_and_resolver PASS
test_reserveBaseName_reserved_label_classification_reverts PASS
test_reserveBaseName_reverts_for_digit_suffixed_reserved_base_label PASS
test_reserveBaseName_reverts_when_origin_is_not_root PASS
test_reserveBaseName_reverts_when_reserved_label_already_registered PASS
test_reserveLiteName_piles_second_pending_claim_when_caller_has_no_store PASS
test_reserveLiteName_reverts_for_non_lite_format PASS
test_reserveLiteName_reverts_when_origin_is_not_root PASS
test_reserveLiteName_reverts_when_suffix_is_not_exactly_two_digits PASS
test_reserveLiteName_reverts_when_the_stem_is_governance_reserved PASS
test_reserveLiteName_stores_the_label_with_its_separator PASS
test_reserveLiteName_succeeds_for_long_stem PASS
test_reserveLiteName_succeeds_regardless_of_base_reservation PASS
test_reservedBaseLabelOf_returns_label_or_empty PASS
test_revert_setReservationDuration_below_minimum PASS
test_same_stem_lite_and_base_occupy_distinct_registrar_tokens PASS
test_second_pop_lite_mint_of_same_label_reverts_at_registrar PASS
test_setReservationDuration_reverts_for_non_owner PASS
test_setReservationDuration_shortening_retroactively_expires_live_entries PASS
test_settlePendingClaims_bounded_settles_up_to_limit PASS
test_settlePendingClaims_on_empty_queue_returns_zero PASS
test_settle_after_reservation_duration_still_writes_label PASS
test_settle_at_exact_expiry_boundary_writes_label PASS
test_settle_deploys_store_and_writes_label_and_chat_key PASS
test_settle_deploys_store_when_user_has_none PASS
test_settle_emits_settled_and_name_registered PASS
test_settle_is_keyed_by_user_arg_other_stash_untouched PASS
test_settle_on_expiry_by_third_party_writes_label PASS
test_settle_with_empty_chat_key_skips_resolver_write PASS
test_split_gateway_flow_mints_lite_then_reserves_base PASS
test_supportsInterface_answers_the_controller_id PASS
test_third_party_settles_pending_claim_into_user_store PASS
test_user_settles_own_pending_claim_after_gateway_mint PASS

DotnsPopControllerFuzz (test/fuzz/registrar/DotnsPopControllerFuzz.t.sol)

Test Result Error
testFuzz_gatewayReserve_cold_user_stashes_label_and_chat_key_exactly PASS
testFuzz_isReservedForClaim_tracks_duration_boundary PASS
testFuzz_public_register_respects_popRules_reservation PASS
testFuzz_reserveBaseName_accepts_any_two_digit_suffix PASS
testFuzz_reserveBaseName_persists_chat_key_exact_bytes PASS
testFuzz_settle_settles_label_and_chat_key_exactly PASS
testFuzz_settle_writes_label_regardless_of_age PASS

DotnsPopResolverTests (test/unit/resolver/DotnsPopResolver.t.sol)

Test Result Error
test_rotating_pop_controller_changes_authorised_writer PASS
test_setChatKey_accepts_exactly_65_bytes PASS
test_setChatKey_accepts_zero_node_as_passthrough PASS
test_setChatKey_auth_check_precedes_length_check_on_bad_payload PASS
test_setChatKey_auth_check_runs_before_length_check PASS
test_setChatKey_reverts_for_64_byte_payload PASS
test_setChatKey_reverts_for_66_byte_payload PASS
test_setChatKey_reverts_for_empty_payload PASS
test_setChatKey_reverts_for_large_griefing_payload PASS
test_setChatKey_reverts_for_one_byte_payload PASS
test_setChatKey_reverts_for_unauthorised_caller PASS
test_setChatKey_writes_and_emits PASS
test_setLiteLink_accepts_zero_inputs_as_passthrough PASS
test_setLiteLink_chain_returns_to_original_without_drift PASS
test_setLiteLink_cross_chain_no_drift PASS
test_setLiteLink_idempotent_relink_keeps_both_indices PASS
test_setLiteLink_long_chain_invariant_holds_at_every_step PASS
test_setLiteLink_old_lite_reads_zero_after_relink PASS
test_setLiteLink_quadrangle_clears_both_stale_inverses PASS
test_setLiteLink_reverts_for_unauthorised_caller PASS
test_setLiteLink_same_full_node_relink_clears_old_reverse PASS
test_setLiteLink_same_lite_relink_clears_old_forward PASS
test_setLiteLink_with_zero_lite_is_passthrough PASS
test_setLiteLink_writes_and_emits PASS

DotnsProtocolRegistryTldTests (test/unit/registry/DotnsProtocolRegistry.t.sol)

Test Result Error
test_same_label_derives_distinct_node_and_token_id_per_tld PASS

DotnsProtocolRegistryFuzzTest (test/fuzz/registry/DotnsProtocolRegistryFuzz.t.sol)

Test Result Error
testFuzz_isRegisteredAddress_matches_ground_truth PASS
testFuzz_set_same_pair_is_no_op PASS
testFuzz_zero_address_never_registered PASS
test_initialise_reverts_on_empty_tld PASS
test_initialise_reverts_on_multi_label_tld PASS

DotnsRegistrarTests (test/unit/registrar/DotnsRegistrar.t.sol)

Test Result Error
test_add_controller PASS
test_add_controller_emits_event PASS
test_add_controller_reverts_for_non_owner PASS
test_approvals_work PASS
test_available_before_after_register PASS
test_available_is_false_while_released_token_is_inside_redeem_window PASS
test_available_when_redeem_window_elapsed_returns_true PASS
test_exists_reports_minted_state PASS
test_initialize_cannot_be_called_twice PASS
test_label_of_returns_empty_for_nonexistent_token PASS
test_label_of_returns_empty_when_owner_has_no_label_store PASS
test_label_of_returns_stripped_label PASS
test_pop_gateway_mint_emits_soulbound_true PASS
test_pop_gateway_mint_marks_soulbound PASS
test_public_mint_is_not_soulbound PASS
test_public_name_remains_transferable PASS
test_quote_transfer_fee_reverts_for_zero_recipient PASS
test_quote_transfer_fee_reverts_when_escrow_unconfigured PASS
test_quote_transfer_fee_zero_for_escrow_recipient PASS
test_quote_transfer_fee_zero_for_self_transfer PASS
test_quote_transfer_fee_zero_when_no_label_recorded PASS
test_register_emits_name_registered PASS
test_register_mints_to_owner PASS
test_register_reverts_for_non_controller PASS
test_register_reverts_when_name_not_available PASS
test_register_with_empty_label_skips_store_write PASS
test_register_writes_label_into_owner_label_store PASS
test_remove_controller PASS
test_remove_controller_emits_event PASS
test_remove_controller_reverts_for_non_owner PASS
test_self_transfer_skips_fee_charge PASS
test_soulbound_cannot_enter_escrow PASS
test_soulbound_operator_transfer_reverts_and_owner_unchanged PASS
test_soulbound_provenance_is_only_the_pop_controller PASS
test_soulbound_quote_transfer_fee_reverts PASS
test_soulbound_safe_transfer_from_reverts_and_owner_unchanged PASS
test_soulbound_safe_transfer_from_with_data_reverts_and_owner_unchanged PASS
test_soulbound_self_transfer_reverts PASS
test_soulbound_single_token_approvee_transfer_reverts PASS
test_soulbound_transfer_from_reverts_and_owner_unchanged PASS
test_soulbound_transfer_reverts_even_when_escrow_unconfigured PASS
test_soulbound_transfer_with_value_reverts_and_returns_value PASS
test_soulbound_with_label_transfer_reverts PASS
test_supports_ierc721_interface PASS
test_transfer_reverts_when_fee_required_but_no_value_attached PASS
test_upgrade_rejects_non_owner PASS
test_version_string PASS

DotnsRegistrarControllerTest (test/unit/registrar/DotnsRegistrarController.t.sol)

Test Result Error
test_a_grant_does_not_admit_a_different_owner PASS
test_available_reverts_for_dotted_label PASS
test_available_reverts_for_empty_label PASS
test_available_state_transitions PASS
test_commit_allows_recommit_after_expiry PASS
test_commit_allows_recommit_at_exact_expiry_boundary PASS
test_commit_sets_timestamp PASS
test_consumed_grant_cannot_mint_again PASS
test_controller_advertises_no_role_interface PASS
test_granted_beneficiary_can_register_reserved PASS
test_initialize_reverts_when_max_above_ceiling PASS
test_initialize_reverts_when_max_not_greater_than_min PASS
test_initialize_reverts_when_min_commitment_age_is_zero PASS
test_maxPrice_bound_into_commitment PASS
test_mint_does_not_trigger_store_write PASS
test_pricingVersion_bound_into_commitment PASS
test_public_register_reserves_no_base_name PASS
test_registerReserved_bypasses_closed_short_name_gate PASS
test_registerReserved_reverts_when_the_whitelist_is_unconfigured PASS
test_register_does_not_overwrite_third_party_reverse_record PASS
test_register_popfull_wires_all_records PASS
test_register_rejects_a_governance_reserved_label_for_a_cross_payer PASS
test_register_rejects_a_governance_reserved_label_for_the_owner PASS
test_register_rejects_a_two_digit_label PASS
test_register_reverts_at_exact_expiry_boundary PASS
test_register_reverts_for_dotted_label PASS
test_register_reverts_when_charge_exceeds_maxPrice PASS
test_register_succeeds_when_maxPrice_exactly_met PASS
test_registerreserved_reverts_without_a_grant PASS
test_registerreserved_writes_to_store PASS
test_relayer_can_submit_for_the_granted_beneficiary PASS
test_reveal_prices_at_committed_version_across_current_moves PASS
test_reveal_prices_at_committed_version_after_model_swap PASS
test_reveal_reverts_when_committed_version_not_current PASS
test_revert_cross_payer_sponsoring_unverified_owner PASS
test_revoked_grant_cannot_register_reserved PASS
test_root_mints_reserved_without_a_grant_and_leaves_grants_unspent PASS
test_root_mints_when_the_whitelist_is_unconfigured PASS
test_safe_transfer_writes_to_store PASS
test_transfer_back_skips_locked_entry PASS
test_transfer_clears_primary_reverse_name_when_current_name_is_moved PASS
test_transfer_round_trip_to_original_depositor_rebinds_position_back PASS
test_transfer_skips_store_deploy_when_label_empty PASS
test_transfer_via_approved_operator_writes_to_store PASS
test_transfer_writes_label_and_creates_store PASS
test_transfer_zero_fee_rebinds_position_to_new_holder PASS

DotnsRegistrarControllerFuzzTest (test/fuzz/registrar/DotnsRegistrarControllerFuzz.t.sol)

Test Result Error
testFuzz_NoStatus_transfer_rebinds_position_to_new_holder PASS
testFuzz_a_grant_admits_only_its_beneficiary PASS
testFuzz_grant_is_single_use PASS
testFuzz_granted_name_mints_to_the_beneficiary PASS
testFuzz_register_pushes_overpayment_back_to_eoa_payer PASS
testFuzz_register_refunds_overpayment_inline PASS
testFuzz_register_refunds_overpayment_to_payer_not_owner PASS
testFuzz_reserved_mint_leaves_the_reverse_record_untouched PASS
testFuzz_reserved_mint_requires_a_grant PASS
testFuzz_root_mints_without_a_grant PASS
testFuzz_third_party_registration_does_not_overwrite_owner_reverse PASS
testFuzz_transfer_clears_sender_primary_reverse PASS
testFuzz_transfer_writes_label_to_recipient_store PASS

DotnsRegistrarControllerLifecycleTest (test/unit/registrar/DotnsRegistrarControllerLifecycle.t.sol)

Test Result Error
test_register_creates_funded_position_for_self_registration PASS
test_register_cross_payer_charges_max_not_sum_of_price_and_reach PASS
test_register_cross_payer_routes_owner_price_to_protocol_fees PASS
test_register_overpayment_falls_back_to_ledger_on_rejecting_contract PASS
test_register_overpayment_pushed_directly_to_accepting_contract PASS
test_register_overpayment_pushed_directly_to_eoa_payer PASS
test_register_overpayment_reentrant_attacker_falls_back_to_ledger PASS
test_register_reclaim_state_consistent_during_safe_transfer_callback PASS
test_register_reclaim_succeeds_for_new_owner PASS
test_register_second_reserved_name_preserves_prior_primary_reverse_record PASS
test_revert_register_cross_payer_when_msg_value_below_max_of_price_and_reach PASS
test_revert_register_on_reentry_from_onerc721received PASS
test_revert_registerreserved_for_already_seeded_label PASS

DotnsRegistryTests (test/unit/registry/DotnsRegistry.t.sol)

Test Result Error
test_new_parent_can_reassign_after_erc721_transfer PASS
test_node_owner_can_clear_resolver_to_zero PASS
test_node_owner_creates_nested_subnode_with_canonical_parent_path PASS
test_node_owner_creates_subnode_emits_event_and_returns_expected_subnode PASS
test_node_owner_sets_resolver_emits_event_and_persists PASS
test_non_parent_cannot_call_setSubnodeResolver PASS
test_parent_can_set_resolver_on_subnode_via_setSubnodeResolver PASS
test_parent_reassigns_existing_subnode_owner PASS
test_parent_reassigns_subnode_to_self_then_sets_resolver PASS
test_protocol_registry_bound_at_init PASS
test_reassignment_emits_new_owner_event PASS
test_reassignment_resets_resolver_to_default PASS
test_registrar_controller_sets_owner_emits_event_and_sets_resolver PASS
test_revert_non_parent_cannot_reassign_subnode PASS
test_revert_subnode_owner_with_dotted_sublabel PASS
test_revert_subnode_owner_with_empty_sublabel PASS
test_revert_subnode_owner_with_parent_label_mismatch PASS
test_revert_subnode_owner_with_uppercase_parent_label PASS
test_revert_subnode_owner_with_uppercase_sublabel PASS
test_root_record_is_not_initialized PASS
test_same_sublabel_under_different_parents_owned_by_same_address PASS
test_setSubnodeResolver_reverts_on_nonexistent_subnode PASS
test_subname_label_carrying_a_separator_is_rejected PASS
test_subname_under_a_two_digit_name_does_not_collide_with_a_person PASS
test_subnode_owner_can_still_set_resolver_directly PASS
test_subnode_owner_creates_nested_subnode_under_owned_parent PASS

DotnsRegistryFuzzTest (test/fuzz/registry/DotnsRegistryFuzz.t.sol)

Test Result Error
testFuzz_non_parent_non_owner_cannot_reassign PASS
testFuzz_parent_can_reassign_subnode_to_any_owner PASS
testFuzz_reassignment_resets_resolver_to_default PASS

DotnsResolverTests (test/unit/resolver/DotnsResolver.t.sol)

Test Result Error
test_setaddress_emits_event_and_persists PASS
test_setaddress_overwrites_previous_value PASS

DotnsReverseResolverTests (test/unit/resolver/DotnsReverseResolver.t.sol)

Test Result Error
test_claim_reverse_record_after_receiving_transfer PASS
test_claim_reverse_record_emits_event PASS
test_claim_reverse_record_overwrites_existing_primary PASS
test_claim_reverse_record_sets_for_current_owner PASS
test_nameof_fails_closed_for_unminted_label PASS
test_nameof_fails_closed_when_caller_no_longer_owns_stored_name PASS
test_nameof_fails_closed_when_stored_lacks_tld_suffix PASS
test_nameof_returns_empty_when_unset PASS
test_protocol_registry_bound_at_init PASS
test_register_preserves_existing_reverse_record PASS
test_register_sets_reverse_record_for_owner PASS
test_revert_claim_reverse_record_when_caller_does_not_own PASS

DotnsScarcityPricingTests (test/unit/pop/DotnsScarcityPricing.t.sol)

Test Result Error
test_ceiling_boundary_is_exactly_safe PASS
test_constructor_reverts_above_ceiling PASS
test_constructor_reverts_for_zero_base_fee PASS
test_constructor_reverts_for_zero_floor PASS
test_constructor_reverts_when_floor_exceeds_base_fee PASS
test_doubles_below_nine PASS
test_floor_binds_for_long_names PASS
test_halves_above_nine PASS
test_pivot_at_nine_is_base_fee PASS
test_version_differs_on_differing_params PASS
test_version_stable_on_identical_redeploy PASS

LabelStoreTests (test/unit/store/LabelStore.t.sol)

Test Result Error
test_caller_becoming_unregistered_rejects_subsequent_write PASS
test_getLabels_caps_at_available PASS
test_getLabels_returns_empty_when_offset_past_end PASS
test_implementation_cannot_be_initialised_directly PASS
test_initialize_binds_owner_and_registry PASS
test_initialize_reverts_on_second_call PASS
test_initialize_reverts_on_zero_registry PASS
test_initialize_reverts_on_zero_user PASS
test_storeLabel_reverts_on_second_write_same_labelhash PASS
test_storeLabel_reverts_when_caller_not_registered PASS
test_storeLabel_reverts_when_labelhash_zero PASS
test_storeLabel_writes_locks_and_enumerates PASS

LabelStoreFuzzTest (test/fuzz/store/LabelStoreFuzz.t.sol)

Test Result Error
testFuzz_getLabels_pagination_consistent PASS
testFuzz_storeLabel_succeeds_for_arbitrary_inputs PASS

NameGrantFlow (test/intergration/NameGrantFlow.t.sol)

Test Result Error
test_a_relayer_can_submit_for_the_granted_beneficiary PASS
test_no_signed_account_can_grant PASS
test_reserved_registration_leaves_the_reverse_record_untouched PASS
test_root_grant_seeds_a_reserved_registration PASS

NoStatusDepositLifecycle (test/intergration/NoStatusDepositLifecycle.t.sol)

Test Result Error
test_NoStatus_register_then_transfer_then_holder_claims_refund PASS

PopRulesFuzzTest (test/fuzz/pop/PopFuzz.t.sol)

Test Result Error
testFuzz_expired_reservation_rolls_forward_to_next_lite_registrant PASS
testFuzz_governance_names_always_revert PASS
testFuzz_mixed_case_names_are_rejected PASS
testFuzz_nostatus_user_cannot_access_popfull PASS
testFuzz_popfull_user_can_access_nostatus PASS
testFuzz_popfull_user_can_access_poplite PASS
testFuzz_price_is_monotonic_and_floored PASS
testFuzz_price_matches_flat_model PASS
testFuzz_price_without_check_returns_price PASS
testFuzz_reservation_blocks_other_users PASS

PopLifecycleFlow (test/intergration/PopLifecycleFlow.t.sol)

Test Result Error
test_cold_gateway_reserve_then_user_settles_pending_claim PASS
test_gateway_name_with_live_pending_claim_is_soulbound_and_settles_for_owner PASS
test_lapsed_pending_claim_settles_and_deploys_store PASS
test_lite_via_gateway_then_full_via_public_after_upgrade PASS
test_pop_full_name_is_soulbound_but_fully_usable PASS
test_recover_full_username_from_lite_label PASS
test_reserve_settle_reserve_cycle_for_same_user PASS

PopRulesTests (test/unit/pop/PopRules.t.sol)

Test Result Error
test_a_lite_suffix_prices_as_its_stem PASS
test_base_reservation_blocks_others PASS
test_classify_accepts_any_flat_digit_count PASS
test_classify_admits_a_name_ending_in_a_digit PASS
test_classify_governance PASS
test_classify_nostatus PASS
test_classify_nostatus_no_digits PASS
test_classify_popfull PASS
test_classify_poplite PASS
test_classify_reverts_for_a_lite_suffix_of_the_wrong_length PASS
test_enabling_short_names_opens_the_market PASS
test_open_band_priced_while_short_names_closed PASS
test_popfull_user_can_access_poplite_name PASS
test_poplite_user_can_access_nostatus_name PASS
test_priceWithCheck_matches_model PASS
test_price_matches_model PASS
test_price_reverts_when_cost_model_unconfigured PASS
test_price_with_check_revert_full_needed PASS
test_price_with_check_revert_governance PASS
test_price_without_check_returns_price_for_reserved PASS
test_pricingVersion_matches_registry PASS
test_releaseBaseName_expired_slot_cleared_by_any_controller PASS
test_releaseBaseName_reverts_for_non_controller PASS
test_releaseBaseName_reverts_for_non_reserving_controller PASS
test_releaseBaseName_succeeds_for_reserving_controller PASS
test_reserveBaseNameForPop_refreshes_expiry_for_same_owner PASS
test_reserveBaseNameForPop_reverts_for_non_controller PASS
test_reserveBaseNameForPop_reverts_when_slot_held_by_other_user PASS
test_setShortNamesEnabled_emits PASS
test_setShortNamesEnabled_owner_reverts PASS
test_setShortNamesEnabled_requires_root PASS
test_setShortNamesEnabled_root_succeeds PASS
test_short_names_closed_reverts_direct_path PASS
test_short_names_closed_reverts_sponsored_path PASS
test_transferFloor_matches_model PASS
test_transfer_reprices_at_own_length PASS
test_verified_person_pays_the_deposit_for_premium PASS
test_writeReservation_preserves_original_controller_on_same_owner_refresh PASS

PopRulesClassificationTests (test/unit/pop/PopRulesClassification.t.sol)

Test Result Error
test_classify_accepts_a_flat_suffix_of_any_length PASS
test_classify_accepts_an_ordinary_flat_digit_suffix PASS
test_classify_differs_with_and_without_the_separator PASS
test_classify_measures_a_label_without_a_suffix_whole PASS
test_classify_measures_a_lite_label_by_its_stem PASS
test_classify_puts_a_lite_label_in_the_lite_tier_not_the_full_tier PASS
test_classify_reverts_for_a_malformed_lite_label PASS
test_isBaseName_answers_false_for_a_lite_label PASS
test_stripDigits_leaves_no_trailing_separator PASS
test_stripDigits_returns_a_suffixless_label_verbatim PASS
test_stripDigits_shortens_only_the_separated_form PASS

StoreFactoryTests (test/unit/store/StoreFactory.t.sol)

Test Result Error
test_beacon_owner_is_factory_for_both_beacons PASS
test_claimUserStore_frontrun_is_harmless PASS
test_claimUserStore_owner_is_caller PASS
test_claimUserStore_reverts_on_double_claim PASS
test_constructor_deploys_both_beacons_and_implementations PASS
test_constructor_reverts_on_zero_registry PASS
test_deployLabelStoreFor_reverts_for_unregistered_non_owner PASS
test_deployLabelStoreFor_reverts_on_double_deploy PASS
test_deployLabelStoreFor_reverts_on_zero_user PASS
test_deployLabelStoreFor_succeeds_for_owner PASS
test_deployLabelStoreFor_succeeds_for_registered_protocol PASS
test_getLabelStores_enumerates_in_deployment_order PASS
test_getUserStores_enumerates_in_claim_order PASS
test_upgradeLabelStoreImplementation_propagates_to_live_proxies PASS
test_upgradeLabelStoreImplementation_reverts_for_non_owner PASS
test_upgradeLabelStoreImplementation_reverts_on_zero_impl PASS

StoreIntegrationTest (test/intergration/StoreIntegration.t.sol)

Test Result Error
test_beacon_upgrade_preserves_label_store_state PASS
test_beacon_upgrade_preserves_user_store_state PASS
test_double_transfer_back_does_not_revert_on_existing_lock PASS
test_erc721_transfer_syncs_label_to_recipient_store PASS
test_main_controller_registration_writes_label_store PASS
test_pop_controller_registration_writes_label_store PASS
test_registration_reuses_factory_owner_predeployed_store PASS
test_user_claim_round_trip_with_history PASS

StoreStressTest (test/stress/store/StoreStress.t.sol)

Test Result Error
test_label_store_many_labels PASS
test_pagination_extreme_bounds PASS
test_user_store_deep_history PASS
test_user_store_large_value_round_trip PASS
test_user_store_many_keys PASS

StringUtilsTests (test/unit/utils/StringUtils.t.sol)

Test Result Error
test_isLitePersonLabel_accepts_the_dotted_shape PASS
test_isLitePersonLabel_bounds_the_stem_not_the_whole_label PASS
test_isLitePersonLabel_puts_no_floor_on_the_stem PASS
test_isLitePersonLabel_rejects_a_missing_or_repeated_separator PASS
test_isLitePersonLabel_rejects_a_non_canonical_stem PASS
test_isLitePersonLabel_rejects_any_suffix_but_two_digits PASS
test_isLitePersonLabel_rejects_the_empty_string PASS
test_isLitePersonLabel_requires_a_letters_only_stem PASS
test_isPersonLabel_accepts_letters_only PASS

UserStoreTests (test/unit/store/UserStore.t.sol)

Test Result Error
test_getHistory_pagination_bounds PASS
test_getKeys_pagination PASS
test_implementation_cannot_be_initialised_directly PASS
test_initialize_binds_owner PASS
test_initialize_reverts_on_second_call PASS
test_initialize_reverts_on_zero_user PASS
test_setValue_does_not_duplicate_key_list_entries PASS
test_setValue_empty_bytes_after_nonempty_snapshots_prior_value PASS
test_setValue_first_write_leaves_history_empty PASS
test_setValue_fresh_key_with_empty_bytes_writes_no_history PASS
test_setValue_reverts_for_non_owner PASS
test_setValue_reverts_for_zero_key PASS
test_setValue_second_write_snapshots_prev_into_history PASS

UserStoreFuzzTest (test/fuzz/store/UserStoreFuzz.t.sol)

Test Result Error
testFuzz_getHistory_pagination_consistent PASS
testFuzz_history_length_equals_prior_nonempty_writes PASS
testFuzz_setValue_accepts_arbitrary_inputs PASS

View full report | View logs

Contract Tests (Invariant)

CostModelVersionInvariantTest (test/invariant/pop/CostModelVersionInvariant.t.sol)

Test Result Error
invariant_current_pointer_is_coherent PASS
invariant_registered_versions_price_is_immutable PASS

DotnsNameEscrowInvariantTest (test/invariant/escrow/DotnsNameEscrowInvariant.t.sol)

Test Result Error
invariant_claimed_positions_have_zero_amount PASS
invariant_no_funds_in_controller PASS
invariant_position_recipient_mirrors_current_nft_holder PASS
invariant_protocol_fees_match_tracked_inflows PASS
invariant_reclaimable_positions_are_fundable PASS
invariant_released_count_consistent PASS
invariant_released_tokens_are_never_stuck PASS
invariant_released_tokens_in_escrow_custody PASS
invariant_reserves_match_positions PASS
invariant_solvency PASS
invariant_withdrawn_tokens_are_in_escrow_custody_and_available PASS

DotnsNameWhitelistInvariant (test/invariant/whitelist/DotnsNameWhitelistInvariant.t.sol)

Test Result Error
invariant_active_set_is_consistent PASS
invariant_claim_bounds PASS
invariant_winner_iff_claimed PASS

DotnsPopControllerInvariant (test/invariant/registrar/DotnsPopControllerInvariant.t.sol)

Test Result Error
invariant_fullClaim_liteLink_are_inverse PASS
invariant_gateway_and_public_provenance_are_disjoint PASS
invariant_no_stale_fullClaim PASS
invariant_no_stale_liteLink PASS
invariant_one_reservation_per_account_consistent PASS
invariant_pendingClaimUserCount_matches_enumeration_length PASS
invariant_pendingClaimUsers_mirrors_pendingClaims_mapping PASS
invariant_pending_claim_and_label_store_are_mutually_exclusive PASS
invariant_popIssued_is_never_cleared PASS
invariant_popRules_head_matches_queue_head_or_zero PASS
invariant_queue_length_bounded PASS
invariant_rival_hierarchy_never_passes_for_a_person PASS
invariant_settled_names_written_and_never_stranded PASS
invariant_subnames_never_reach_a_gateway_node PASS

DotnsRegistrarControllerInvariantTest (test/invariant/registrar/DotnsRegistrarControllerInvariant.t.sol)

Test Result Error
invariant_consumed_commitments_deleted PASS
invariant_current_owners_have_label_in_store PASS
invariant_no_stuck_funds PASS
invariant_ownership_consistency PASS
invariant_registered_names_unavailable PASS
invariant_registration_count_consistent PASS
invariant_reserved_names_have_reverse_resolution PASS
invariant_store_entries_locked PASS
invariant_transfer_recipients_have_store_entries PASS
invariant_value_conservation PASS

DotnsRegistrarReservedGrantInvariantTest (test/invariant/registrar/DotnsRegistrarReservedGrantInvariant.t.sol)

Test Result Error
invariant_a_grant_admits_only_its_beneficiary PASS
invariant_every_mint_spends_its_grant PASS
invariant_grants_are_pending_or_minted_never_both PASS
invariant_minted_names_belong_to_their_beneficiary PASS
invariant_no_grant_is_spent_twice PASS
invariant_no_ungranted_mint_succeeds PASS
invariant_reserved_path_never_writes_a_reverse_record PASS

DotnsRegistrarSoulboundInvariantTest (test/invariant/registrar/DotnsRegistrarSoulboundInvariant.t.sol)

Test Result Error
invariant_no_soulbound_transfer_succeeds PASS
invariant_soulbound_coverage_is_non_vacuous PASS
invariant_soulbound_owner_never_changes PASS

DotnsRegistryInvariantTest (test/invariant/registry/DotnsRegistryInvariant.t.sol)

Test Result Error
invariant_parent_can_always_reassign_subnodes PASS
invariant_subnames_never_produce_a_dotted_node PASS
invariant_subnode_owner_authorized PASS
invariant_subnodes_always_exist PASS

StoreInvariantTest (test/invariant/store/StoreInvariant.t.sol)

Test Result Error
invariant_at_most_one_store_of_each_type_per_user PASS
invariant_enumeration_matches_counters PASS
invariant_factory_owns_both_beacons PASS
invariant_locked_label_text_never_changes PASS
invariant_locked_labels_never_unlock PASS

View full report | View logs

Gas Report

DotnsProtocolRegistry

Function Master Current Diff
set 8,352 8,165 -187
initialize 2,717 2,637 -80
tld 2,311 2,308 -3
get 1,881 1,883 +2
isRegisteredAddress 1,861 1,862 +1
tldNode 1,984 1,983 -1

DotnsRegistrar

Function Master Current Diff
labelOf 21,465 20,458 -1,007
register 326,446 326,096 -350
addController 8,589 8,413 -176
quoteTransferFee 84,546 84,718 +172
transferFrom 509,853 510,017 +164
initialize 433 420 -13
controllers 1,192 1,199 +7
exists 847 851 +4
ownerOf 1,677 1,676 -1

DotnsRegistrarController

Function Master Current Diff
register 692,559 684,854 -7,705
registerReserved 404,889 404,799 -90
available 18,578 18,565 -13
MAX_ALLOWED_COMMITMENT_AGE 287 278 -9
commit 63,848 63,839 -9
commitments 2,569 2,560 -9
makeCommitment 1,343 1,334 -9
maxCommitmentAge 2,654 2,645 -9
minCommitmentAge 2,547 2,538 -9
supportsInterface 274 265 -9
initialize 259 251 -8

DotnsRegistry

Function Master Current Diff
setSubnodeResolver 43,675 40,334 -3,341
setSubnodeOwner 370,646 370,285 -361
owner 5,066 5,058 -8
initialize 258 251 -7
setOwner 36,985 36,989 +4

DotnsReverseResolver

Function Master Current Diff
nameOf 12,355 12,345 -10
initialize 259 251 -8

PopRules

Function Master Current Diff
releaseReservationForReclaim 19,051 0 -19,051
classifyName 5,834 10,762 +4,928
reserveBaseName 59,026 62,036 +3,010
priceWithCheckAtVersion 31,839 29,407 -2,432
stripDigits 8,310 6,363 -1,947
priceWithCheck 37,126 35,606 -1,520
priceWithoutCheckAtVersion 31,247 30,002 -1,245
priceWithoutCheck 38,407 38,605 +198
transferFloor 25,598 25,767 +169
getBaseNameReservation 9,400 9,517 +117
price 31,278 31,359 +81
isBaseName 4,964 4,983 +19
isBaseNameReserved 8,184 8,189 +5
initialize 130 126 -4
setShortNamesEnabled 139 135 -4

View full report | View logs

Deploy Contracts

Deployed addresses vs the committed manifest

Expected is the committed manifest; actual is this CI deployment of the same pipeline.

Contract Expected Actual Match
Create3Factory 0x8533c79E058c5a6489CAFeCA86dc600E029D75f5 0x8533c79E058c5a6489CAFeCA86dc600E029D75f5 match
DotnsContentResolver 0x7F74D7CD50f5a834270E2ad395a01b01891AB37d 0x7F74D7CD50f5a834270E2ad395a01b01891AB37d match
DotnsCostModelRegistry 0x8bfd1f0957e73716732e725802f13830B5682da4 0x8bfd1f0957e73716732e725802f13830B5682da4 match
DotnsFlatPricing 0xD839B281dF72Df44fF275305E72cAEEc0fDAA648 0xD839B281dF72Df44fF275305E72cAEEc0fDAA648 match
DotnsNameEscrow 0x4881Afb78e7C908cAe818168B926229D93376520 0x4881Afb78e7C908cAe818168B926229D93376520 match
DotnsNameWhitelist 0x420166cD67Ca0233094E492a4BbA67045eD7C38C 0x420166cD67Ca0233094E492a4BbA67045eD7C38C match
DotnsPopController 0xCC932348606cc1f3318cADeC5A5Cd2CA447f8a4b 0xCC932348606cc1f3318cADeC5A5Cd2CA447f8a4b match
DotnsPopLens 0xfe5A45f7fD58D1A6FE09455DB799405b1dcE9411 0xfe5A45f7fD58D1A6FE09455DB799405b1dcE9411 match
DotnsPopResolver 0xDaC984884EcA8Fc44011f1D6C49B27828390A72B 0xDaC984884EcA8Fc44011f1D6C49B27828390A72B match
DotnsProtocolRegistry 0xD19e3D0C97CF501125a04A97405e3e6592fa846E 0xD19e3D0C97CF501125a04A97405e3e6592fa846E match
DotnsRegistrar 0x4f06E818Ba3d987704fd91cf3d868E4b019106Ab 0x4f06E818Ba3d987704fd91cf3d868E4b019106Ab match
DotnsRegistrarController 0xBdaA01bD1bA67d709F2b1fF286Da0d854977EA30 0xBdaA01bD1bA67d709F2b1fF286Da0d854977EA30 match
DotnsRegistry 0xf34054fd76BbF85f216cf9908226D5f0A72E50CA 0xf34054fd76BbF85f216cf9908226D5f0A72E50CA match
DotnsResolver 0xbd1165E549DF96F083c0A16f61590927bC187009 0xbd1165E549DF96F083c0A16f61590927bC187009 match
DotnsReverseResolver 0xee3883d7eB60Ee9BCD7F3bcD8f2f05302A9Cc035 0xee3883d7eB60Ee9BCD7F3bcD8f2f05302A9Cc035 match
LabelStoreBeacon 0xb57Ebc2e7085616d4906D1fE49af1cE13f7dffeF 0xb57Ebc2e7085616d4906D1fE49af1cE13f7dffeF match
Multicall3 0xB4468000abD87D3c56cbFBd153161223D7b109e5 0xB4468000abD87D3c56cbFBd153161223D7b109e5 match
PopRules 0x747B456bE03aec0b42bd85C51513730FBD45DA31 0x747B456bE03aec0b42bd85C51513730FBD45DA31 match
StoreFactory 0x709A027F446a9e2a4BB9cb9a9c754435b19e32B7 0x709A027F446a9e2a4BB9cb9a9c754435b19e32B7 match
UserStoreBeacon 0xb7C995601679840d36F37E86DB2d7dF30797eC5C 0xb7C995601679840d36F37E86DB2d7dF30797eC5C match

View full logs

Labels

smartcontracts, scope: registration, scope: resolver, type: test, type: docs, scope: pop

@re-gius
re-gius marked this pull request as ready for review September 4, 2026 13:12

@sphamjoli sphamjoli left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few items that do not map to a line in this diff:

  • Queue invariant blind spot: invariant_popRules_head_matches_queue_head_or_zero (in DotnsPopControllerInvariant.t.sol, not touched here) asserts nothing when the queue head is empty, so an orphaned PopRules slot outliving the queue passes silently. Adding the zero-direction assertion (head empty implies the slot is cleared or not live) closes it.
  • Dead PopLite public-reserve branch: with the separated form, the priced.status == PopLite write in DotnsRegistrarController (not in this diff) is now unreachable and only kept alive by guard tests. Deleting the branch, or softening the "single cross-flow authority in both directions" comment, would match the new behaviour.
  • Description wording: "registrable" reads as open-sale, but web3 (Reserved) and andrew-x (PopFull) are grammar-valid rather than openly registrable; only longnamebob01 is. And "Closes #274" is accurate for the contract portion; the host and dotli criteria live in another repo.


/// @dev Reserved storage space to allow for layout changes in future upgrades.
uint256[50] private __gap;
uint256[49] private __gap;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We deploy this fresh rather than upgrading in place, so the gap does not need to shrink for the appended _popIssued. Put it back to uint256[50]. The upgrade-only additions can come out with it: the reconstructed legacy interface id in supportsInterface and the "survives an upgrade" wording have no upgrade path to serve on a fresh deploy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 5785561 for other contracts too

/// 100 reuses are swallowed by the caller's try/catch.
/// @dev Shape: `<tag><4 letters from actor>.<2 digits>`, the separated form the gateway
/// accepts. The stem is 7 characters, which classifies as PopLite under PopRules, and
/// the separator and digits are the suffix. Tag disambiguates the reserve vs claim call

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two properties this PR introduces are covered only by unit and single-fuzz cases, but both hold across arbitrary action sequences, so they belong in the invariant: first-to-mint arbitration between the gateway and the public register, and isPopIssued being written once and never cleared. The handler exposes no public-register action and no ERC-721 transfer action, so the campaign cannot reach either today. Adding those two actions, plus a ghost that fails if isPopIssued flips back to false after a transfer, would cover both at the right level.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 8983b38

/// which is the form People Chain holds, so no normalisation happens here. The shape check
/// runs before classification so a malformed label reverts
/// @custom:reverts InvalidLiteLabel, which the gateway pallet decodes by selector; letting
/// `_validateLiteLabel` catch it instead would surface an undecodable PopRules string.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @dev points at the wrong function: _validateLiteLabel reverts InvalidLiteLabel, a decodable selector, not an undecodable PopRules string. That string comes from classifyName via _requireLabel. The logic holds; only the named function is wrong.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, fixed in 8983b38

Comment thread contracts/utils/StringUtils.sol Outdated
/// People Chain emits and what DotNS accepts. The pallet treats the value as a
/// minimum; DotNS requires exactly this many, so a three-digit suffix is rejected
/// here even though the pallet would accept it.
uint256 internal constant MIN_LITE_SUFFIX_DIGITS = 2;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code uses this as an exact count: the separator is fixed at length - MIN_LITE_SUFFIX_DIGITS - 1, so a three-digit suffix is rejected, and the NatSpec then has to explain that the pallet treats it as a minimum. If exactly two is the intended policy, LITE_SUFFIX_DIGITS names the behaviour without the MIN_ the code contradicts.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed and fixed in 8983b38

/// @dev The union is the full set of issuable labels, so a near miss such as `alice.4` or
/// `a.b.42` still reverts. @custom:function _requireStem is the stricter guard for
/// reservation keys, which never carry a separator.
function _requireLabel(string calldata name) internal pure {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These views (classifyName, price, priceWithCheck and the rest) now accept a lite label through _requireLabel, so a dotted input returns a non-reverting answer where it used to revert. Minting is still gated at the controllers, so this does not change who can register, but any off-chain caller that read a non-revert here as proof of a public flat label needs to stop.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added to the PR description

/// resolves to depends on which reading you take, and provenance is what tells them
/// apart. No production controller entry point can create the parent: the public and
/// reserved paths require three characters, and both gateway paths are letters only.
/// An owner-authorised controller can still call the registrar directly, which is what

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These subname cases are thorough at the unit level, but the property they check is the one the dotted-name design rests on, and it holds across arbitrary sequences: no subname can create a dotted node, and no subname node can equal a PoP node. The registry invariant handler hardcodes the sublabel as "sub", so the campaign never fuzzes a separator into a subname, and no invariant interleaves subname creation with gateway mints. Fuzzing the sublabel and asserting no created node's label carries a dot, plus a combined handler that checks no subnode hash equals a tracked PoP node and that isPopIssued disambiguates, would lift this to the level it needs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both added in 8983b38

@re-gius

re-gius commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

A few items that do not map to a line in this diff:

* Queue invariant blind spot: `invariant_popRules_head_matches_queue_head_or_zero` (in `DotnsPopControllerInvariant.t.sol`, not touched here) asserts nothing when the queue head is empty, so an orphaned PopRules slot outliving the queue passes silently. Adding the zero-direction assertion (head empty implies the slot is cleared or not live) closes it.

Added the zero direction, restricted to head >= tail in 8983b38

* Dead PopLite public-reserve branch: with the separated form, the `priced.status == PopLite` write in `DotnsRegistrarController` (not in this diff) is now unreachable and only kept alive by guard tests. Deleting the branch, or softening the "single cross-flow authority in both directions" comment, would match the new behaviour.

Deleted

* Description wording: "registrable" reads as open-sale, but `web3` (Reserved) and `andrew-x` (PopFull) are grammar-valid rather than openly registrable; only `longnamebob01` is. And "Closes #274" is accurate for the contract portion; the host and dotli criteria live in another repo.

Reworded

@sphamjoli
sphamjoli self-requested a review September 4, 2026 19:04
@re-gius
re-gius merged commit a3fd462 into master Sep 4, 2026
25 of 26 checks passed
@re-gius
re-gius deleted the re-gius/dotted-lite-labels branch September 4, 2026 21:39
github-actions Bot added a commit that referenced this pull request Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat]: Keep the dot in lite PoP names using the gateway flag we already store

2 participants