feat: price dotNS names on a scarcity curve - #208
Conversation
Replace flat pricing with a geometric curve on base length: the price doubles for each character below nine and flattens to the base fee from nine upward. Every caller pays the curve; personhood gates access to the premium band rather than discounting it. Transfers re-price at the name's own length. Rename the escrow insurance fund to protocol fees, a non-refundable pot that only accumulates, and remove the dead refund-shortfall draw so refunds draw solely on the per-asset reserve.
CI Summary
4naly3er AnalysisMedium (5)
Low (10)
Gas (15)
Informational (11)
Slither AnalysisHigh (3)
Medium (42)
Low (73)
Informational (62)
Contract Tests (Unit + Fuzz)BasicDotnsIntegrationReverts (test/intergration/BasicDotns.reverts.t.sol)
BasicDotnsIntegration (test/intergration/BasicDotns.t.sol)
DeployCreate3FactoryTest (test/unit/deploy/DeployCreate3Factory.t.sol)
DeterministicDeploymentTest (test/unit/deploy/DeterministicDeployment.t.sol)
DotnsContentResolverTests (test/unit/resolver/DotnsContentResolver.t.sol)
DotnsCostModelRegistryTests (test/unit/pop/DotnsCostModelRegistry.t.sol)
DotnsFlatPricingTests (test/unit/pop/DotnsFlatPricing.t.sol)
DotnsNameEscrowTest (test/unit/escrow/DotnsNameEscrow.t.sol)
DotnsNameEscrowFuzzTest (test/fuzz/escrow/DotnsNameEscrowFuzz.t.sol)
DotnsNameEscrowRedeemTest (test/unit/escrow/DotnsNameEscrowRedeem.t.sol)
DotnsNameEscrowRefundsTest (test/unit/escrow/DotnsNameEscrowRefunds.t.sol)
DotnsNameWhitelistTests (test/unit/whitelist/DotnsNameWhitelist.t.sol)
DotnsNameWhitelistFuzz (test/fuzz/whitelist/DotnsNameWhitelistFuzz.t.sol)
DotnsPopControllerTests (test/unit/registrar/DotnsPopController.t.sol)
DotnsPopControllerFuzz (test/fuzz/registrar/DotnsPopControllerFuzz.t.sol)
DotnsPopResolverTests (test/unit/resolver/DotnsPopResolver.t.sol)
DotnsProtocolRegistryTldTests (test/unit/registry/DotnsProtocolRegistry.t.sol)
DotnsProtocolRegistryFuzzTest (test/fuzz/registry/DotnsProtocolRegistryFuzz.t.sol)
DotnsRegistrarTests (test/unit/registrar/DotnsRegistrar.t.sol)
DotnsRegistrarControllerTest (test/unit/registrar/DotnsRegistrarController.t.sol)
DotnsRegistrarControllerFuzzTest (test/fuzz/registrar/DotnsRegistrarControllerFuzz.t.sol)
DotnsRegistrarControllerLifecycleTest (test/unit/registrar/DotnsRegistrarControllerLifecycle.t.sol)
DotnsRegistryTests (test/unit/registry/DotnsRegistry.t.sol)
DotnsRegistryFuzzTest (test/fuzz/registry/DotnsRegistryFuzz.t.sol)
DotnsResolverTests (test/unit/resolver/DotnsResolver.t.sol)
DotnsReverseResolverTests (test/unit/resolver/DotnsReverseResolver.t.sol)
DotnsScarcityPricingTests (test/unit/pop/DotnsScarcityPricing.t.sol)
LabelStoreTests (test/unit/store/LabelStore.t.sol)
LabelStoreFuzzTest (test/fuzz/store/LabelStoreFuzz.t.sol)
NoStatusDepositLifecycle (test/intergration/NoStatusDepositLifecycle.t.sol)
PopRulesFuzzTest (test/fuzz/pop/PopFuzz.t.sol)
PopLifecycleFlow (test/intergration/PopLifecycleFlow.t.sol)
PopRulesTests (test/unit/pop/PopRules.t.sol)
RootGatewayDispatcherTests (test/unit/registrar/RootGatewayDispatcher.t.sol)
StoreFactoryTests (test/unit/store/StoreFactory.t.sol)
StoreIntegrationTest (test/intergration/StoreIntegration.t.sol)
StoreStressTest (test/stress/store/StoreStress.t.sol)
UserStoreTests (test/unit/store/UserStore.t.sol)
UserStoreFuzzTest (test/fuzz/store/UserStoreFuzz.t.sol)
WhitelistOperatorFlow (test/intergration/WhitelistOperatorFlow.t.sol)
Contract Tests (Invariant)CostModelVersionInvariantTest (test/invariant/pop/CostModelVersionInvariant.t.sol)
DotnsNameEscrowInvariantTest (test/invariant/escrow/DotnsNameEscrowInvariant.t.sol)
DotnsNameWhitelistInvariant (test/invariant/whitelist/DotnsNameWhitelistInvariant.t.sol)
DotnsPopControllerInvariant (test/invariant/registrar/DotnsPopControllerInvariant.t.sol)
DotnsRegistrarControllerInvariantTest (test/invariant/registrar/DotnsRegistrarControllerInvariant.t.sol)
DotnsRegistrarControllerRoleInvariantTest (test/invariant/registrar/DotnsRegistrarControllerRoleInvariant.t.sol)
DotnsRegistryInvariantTest (test/invariant/registry/DotnsRegistryInvariant.t.sol)
StoreInvariantTest (test/invariant/store/StoreInvariant.t.sol)
Gas ReportDotnsProtocolRegistry
DotnsRegistrar
DotnsRegistrarController
DotnsRegistry
DotnsReverseResolver
PopRules
StoreFactory
Deploy ContractsDeployed addresses vs the committed manifestExpected is the committed manifest; actual is this CI deployment of the same pipeline.
Labelsdependencies, smartcontracts, other, scope: registration, scope: resolver, type: test, type: docs, scope: pop |
…in state The protocol-fee conservation invariant mirrored the on-chain balance delta into its own tracker, so it compared the escrow balance against an echo of itself and could never catch a miscredit. The handler now accumulates the independently computed charge and quoted transfer fee, and a new invariant asserts those tracked inflows equal the on-chain protocol fees. Dead insurance-draw scaffolding, its recorded-log parsing, and stale insurance wording are removed to match the protocol-fee model.
…in state (#210) ## Description Follow-up to the scarcity-pricing change. The protocol-fee conservation invariant used to read the on-chain protocol-fee balance before and after each action and fold the difference into its own tracker. Because both sides came from the same source, the check compared the balance against an echo of itself and would have passed even if a caller had been charged the wrong amount. The handler now accumulates the fee it independently expects (the charge on a cross-paid registration, the quoted fee on a payable transfer), so the invariant proves the escrow credited exactly what the caller was asked to pay. A second invariant asserts that the tracked inflows sum to the on-chain protocol-fee balance, which holds because protocol fees only ever accrue. ## Type - [ ] Bug fix - [ ] Feature - [ ] Breaking change - [ ] Documentation - [ ] Chore - [ ] Refactor - [ ] Security ## Scope - [ ] Registration - [ ] Resolver - [ ] Store - [ ] Proof of Personhood - [ ] Deployment scripts - [x] Tests ## Related Issues Stacked on #208. ## Fixes ## Checklist ### Code - [x] Follows project style - [x] `forge build` passes - [x] `forge test` passes - [x] No new compiler warnings ### Testing - [ ] New tests added for changed behavior - [ ] Fuzz tests added where applicable - [x] Invariant tests verified ### Security - [x] No new `selfdestruct` or `delegatecall` - [x] Access control reviewed - [x] No storage layout conflicts (for upgradeable contracts) ### Documentation - [x] NatSpec updated on changed interfaces - [x] README updated if needed ### Breaking Changes - [x] No breaking changes - [ ] Breaking changes documented below **Breaking changes:** ## How to test ```bash forge test --mc DotnsNameEscrowInvariantTest ``` ## Notes Stacks on #208; review and merge that first. The new pricing-invariant suite is tracked separately in #209.
The base-fee setter rejects a value large enough to overflow the scarcity multiplier, so short-name pricing and transfers cannot be knocked offline by an oversized fee. Cross-payer registration charges the name's own curve price directly; the floor comparison that could never change the amount is removed. The transfer-fee event fires only when a fee is taken, so a zero-fee position rebind emits nothing. The escrow transfer-fee parameter and its registrar call sites read transferFee to match the value they carry, and the transfer-floor and fee-parameter documentation is completed.
The free name is claimed per wallet through the personhood gateway, which waives the price. The gateway lane accepts a name of any length and applies no public band gate; it only refuses the governance-reserved stems of five characters or fewer.
|
I would keep this PR on hold and have a broader discussion on this argument. This can be taken as a concrete proposal. |
re-gius
left a comment
There was a problem hiding this comment.
Requesting changes based on the design proposed in #209 (comment).
The curve itself is fine, and the work here isn't wasted. Under the agreed design the pricing function moves into a standalone economics contract that governance owns and can replace, and a modified version of this curve can become its first proposal.
|
On the standalone economics contract: I've put the case for keeping pricing in PopRules and adding commitment-to-version binding in #209 (comment) |
re-gius
left a comment
There was a problem hiding this comment.
Thanks for your effort and all these changes, it's much closer to what I was expecting. However, there are some fundamental design choices I would still change:
- v1 must reproduce today's on-chain prices: remove the scarcity pricing exponential model from this PR. Default deposit for v1 will be 10 DOT constant for all 9+ char names like today, while your scarcity model (and maybe some others) will be candidates for replacement in v2. We don't have any clarity on that yet.
- Bind the pricing version and enforce it: we may add
currentVersion()tocommitand make sure reveal requires a version check - Allow governance to upgrade the economics:
DotnsCostModelRegistry.sol:27 and :37 areonlyOwnerwith nooriginIsRoot()check. You can do what you did already for whitelisting.
re-gius
left a comment
There was a problem hiding this comment.
LGTM overall - just a nit comment.
Thanks, great effort! This is actually a major improvement.
|
Sorry, one important last item: please add DotnsCostModelRegistry, DotnsFlatPricing, IDotnsCostModelRegistry and IDotnsPricing to .github/abi-contracts.txt |
Description
Moves name pricing into a swappable cost model that governance owns, and ships a flat launch model that reproduces today's on-chain price: a single 10 DOT deposit for every name admitted to sale. The amount is no longer fixed in the registration path.
PopRulesresolves a cost model through the protocol registry under a newcostModelkey and reads the price from it, so the model can be replaced without touchingPopRulesor its consumers.The launch model,
DotnsFlatPricing, returns one deposit for every base length. A length-sensitive scarcity curve,DotnsScarcityPricing, ships alongside it as a candidate for a later version but is not the registered default.Band gating is unchanged. Names of nine characters or more are open to anyone at the deposit. Names of six to eight characters are the premium band, registrable on the public paid path only by a verified person and only while the short-name switch is on. Names of five characters or fewer are never sold on the public path; they enter circulation only through the reserved path at zero base cost. Personhood gates who may buy, not the amount.
Versioned pricing
Models are swapped, not upgraded. Registering a model in
DotnsCostModelRegistryadds it under a fresh version and moves the current pointer; earlier versions stay priceable, so a registration committed against an earlier version settles at the amount it committed to.A registration binds the version current when it commits.
commitstampscurrentVersion()on the commitment, and the reveal reverts withPricingVersionMismatchif it is presented at a different version. So a caller cannot bind an older, cheaper model, and a model change between commit and reveal cannot move the amount. Governance can also point the current version back at an earlier registered model. The cost-model registry is owner-gated.The model
nis the base length: the characters left once a trailing two-digit number is set aside.Dis the launch deposit, 10 DOT. Every name the bands admit costsD; base length decides which band a name falls in and who may register it, not the amount.Type
Scope
Related Issues
Transfer-fee cleanup that composes with soulbinding is tracked in #212.
Fixes
Closes #209.
Checklist
Code
forge buildpassesforge testpassesTesting
Security
selfdestructordelegatecallDocumentation
Breaking Changes
Breaking changes: name pricing moves out of
PopRulesinto a versioned cost model.PopRules.initializenow takes only the protocol registry; the price is served by aDotnsCostModelRegistryregistered under thecostModelprotocol-registry key, andPopRulesresolves it on every pricing read. The registrar'sRegistrationcarries apricingVersionbound at commit and checked at reveal. The escrow fee pot is renamed from the insurance fund to protocol fees:insuranceFund()becomesprotocolFees(),depositInsurancebecomesdepositProtocolFee, and theInsuranceDrawshortfall event and its top-up path are removed; refunds are backed by the per-asset reserve alone. Consumers reading the fee balance or the price must move to the new names.How to test
Notes
Governance is trusted for economic policy; the contract enforces only coherence (a strictly positive deposit, no zero price) at model construction. There is no on-chain band or timelock on the deposit by design, so any rate or notice guarantee is the governance owner's. Fees accumulate in one pot with no withdrawal path; there is no burn or treasury routing in these contracts.