Skip to content

feat: make reserved name releasable - #334

Merged
re-gius merged 12 commits into
masterfrom
feat/make-reservedName-releaseable
Oct 13, 2026
Merged

re-gius merged 12 commits into
masterfrom
feat/make-reservedName-releaseable

Conversation

@GHkrishna

@GHkrishna GHkrishna commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Description

A name issued through a governance grant could never be released by its holder.

registerReserved minted the name and stopped there. It never opened an escrow release position, and DotnsNameEscrow.release refuses any token that has no position recorded against it. So the holder was stuck with the name whether they wanted it or not, which means it never became reclaimable and into circulation. That matters most for short names, because a grant is the only way to mint them.

The fix is to seed a zero-amount position on the mint, which is exactly what the cross-payer branch of register already does. A grant is free, so there is no deposit to lock, but the position is also the marker that says "this name has a lifecycle", and release reads it.

There is a second part. available reports true both for a label nobody has minted and for one the escrow is still holding after its redeem window closed, and DotnsRegistrar.register refuses to mint an id the escrow holds. So registerReserved would have reverted on a name that had come back around. Seeding the position without fixing that would have left reserved-tier labels sitting in escrow, advertised as free, with nothing able to take them, because register turns those labels away. registerReserved now reclaims from escrow when the token already exists, the same way register does.

While doing that, the reclaim sequence and the position seeding were pulled into two small shared helpers (_reclaimFromEscrow and _seedReleasePosition) so both registration paths go through one copy of each. The reclaim ordering is load bearing: the registry write has to come after escrow.reclaim, because setOwner checks ownerOf and would otherwise see the escrow. Keeping one copy means one place to get that right. It also took about 290 bytes off the controller.

This fixes new mints only. Names minted through registerReserved before this upgrade have no escrow position and stay unreleasable, and there is no way to backfill one.

Type

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

Scope

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

Related Issues

#330

Fixes

Fixes #330

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:

How to test

forge test --mp 'test/intergration/NameGrantFlow.t.sol' -vv

# the invariant
forge test --mt invariant_every_granted_name_is_releasable

Notes

registerReserved now reads the escrow, so nameEscrow has to be set in the protocol registry or it reverts with EscrowNotConfigured. The wiring stage already sets it, so a normal deploy is fine. DEPLOYMENT_CHECKLIST.md now lists it next to the nameWhitelist check.

No new fuzz tests. The property here is that a piece of state exists after a mint, not that some input range behaves, so the coverage is an invariant over the whole minted set plus unit tests.

One difference from register on purpose: that path also clears a sibling controller's stale base-name reservation before reclaiming, and this one does not. Doing it would mean reading IPopRules from registerReserved, which would cost the function its Root callability. It is commented at the reclaim branch.

No owner != escrow guard on the reclaim branch because it is not reachable: reclaim moves the token with safeTransferFrom, which fires the escrow's own onERC721Received, and that requires a position reclaim has already deleted, so the call reverts UnsolicitedDeposit.

A reclaim through registerReserved now clears a base-name reservation that the previous holder still owns, the same as register. Both paths share one helper.

Signed-off-by: GHkrishna <krishna@parity.io>
Signed-off-by: GHkrishna <krishna@parity.io>
Signed-off-by: GHkrishna <krishna@parity.io>
Signed-off-by: GHkrishna <krishna@parity.io>
Signed-off-by: GHkrishna <krishna@parity.io>
Signed-off-by: GHkrishna <krishna@parity.io>
Signed-off-by: GHkrishna <krishna@parity.io>
Signed-off-by: GHkrishna <krishna@parity.io>
Signed-off-by: GHkrishna <krishna@parity.io>
@GHkrishna GHkrishna self-assigned this Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

CI Summary

Check Result
4naly3er Analysis Found 38 issues: 5 medium, 8 low, 13 gas, 12 informational - View Report
Slither Analysis Found 178 issues: 5 high, 40 medium, 74 low, 59 informational - View Report
Contract Tests (Unit + Fuzz) All tests passed (725 total) - View Report
Contract Tests (Invariant) All tests passed (59 total) - View Report
Gas Report 9 contracts analyzed - View Report
Coverage Failed - Tests failed, cannot compute coverage
Documentation Passed - 67 pages generated - View Docs
Format & Lint Passed - Code formatted correctly
File Validation Passed - All tracked files valid
Deploy Contracts Reproduces the expected address set; resume verified
PR Title PR Title Valid
Labels Unknown
Secret Scan Passed - No secrets detected

Signed-off-by: GHkrishna <krishna@parity.io>
@GHkrishna
GHkrishna marked this pull request as ready for review October 8, 2026 11:26
@GHkrishna
GHkrishna requested a review from a team as a code owner October 8, 2026 11:26

@re-gius re-gius left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The description could say that the fix covers new mints, and that testnet names minted through registerReserved before this upgrade stay unreleasable. Two reclaim cases have no test: a reclaim under Root (the existing test reclaims through a grant), and reclaiming a long label that still holds a refundable deposit, which should credit the previous holder's pull-payment balance.

registration, labelhash, node, 0, false, IDotnsReverseResolver(address(0)), isReclaim
);

// One deliberate divergence from @custom:function register, which also clears a sibling

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

registerReserved should clear a stale base-name reservation on reclaim, the same way register does, before _reclaimFromEscrow. Root calling this function doesn't prevent that: PopRules sees this controller as msg.sender, and releaseReservationForReclaim already accepts registrar controllers. This frame still must not read msg.sender, so read the escrow position's recipient first (reclaim deletes the position) and clear only when that recipient owns the reservation.
Otherwise the previous holder's slot stays live for up to 12 weeks after the name moved on.
The comment above this block should go too, since the Root constraint it describes doesn't apply.
I would suggest moving register's clean-up into a shared helper that both paths call before reclaiming, so they cannot drift apart again, and a test that reclaims through registerReserved under both Root and a grant would cover it.

@GHkrishna GHkrishna Oct 13, 2026 •

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.

Nice catch. Agreed.

Added _clearStaleReservation as a separate helper that both paths call, rather than folding it into _reclaimFromEscrow. In register it has to run before the price check, because priceWithCheck enforces reservations against the new owner and a leftover slot would revert the registration, while _reclaimFromEscrow runs after pricing. In registerReserved it runs right before the reclaim. Also removed the Root comment and added tests for reclaim under Root and a grant, the stale reservation being cleared, a reservation held by someone else surviving, and a long label’s deposit being credited to the previous holder.

Signed-off-by: GHkrishna <krishna@parity.io>
Signed-off-by: GHkrishna <krishna@parity.io>

@re-gius re-gius left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@re-gius
re-gius merged commit c0a54b0 into master Oct 13, 2026
26 of 27 checks passed
@re-gius
re-gius deleted the feat/make-reservedName-releaseable branch October 13, 2026 14:56
github-actions Bot added a commit that referenced this pull request Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: A name issued through registerReserved can never be released

3 participants