Repository navigation
feat(platform)!: non-transferable tokens (PV14) - #5353
Conversation
A token configuration of format 1 gains `transferable`, true when absent and fixed at creation. A non-transferable token cannot change hands: - a TokenTransfer is refused (TokenNotTransferableError, 40726) in batch advanced structure validation 1, so the mempool refuses it too; - a document type of its own contract must burn it rather than pay the contract owner (NonTransferableTokenPaymentMustBurnError, 10280); - another contract's document type cannot charge it (40726 at contract create and update, reusing the read that checks the token exists); - it cannot have a shielded pool (NonTransferableTokenShieldedPoolError, 10279), since unshielding can pay any identity. Mints, claims, direct purchases, burns, freezes, pause and burn payments for its own documents are unchanged. Yappr needs this for YAPP: pausing it forever also refuses its document payments from protocol version 14, and before 14 never stopped transfers routed through a contract. The parser, validate_shielded_pool_rules and contract create/update state validation 0 are edited in place: only a format 1 configuration can be non-transferable, and the pre-activation gate refuses that format before protocol version 14. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ransferable-token-36a052 # Conflicts: # packages/rs-platform-version/src/version/v14.rs
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (20)
🚧 Files skipped from review as they are similar to previous changes (1)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughAdds a ChangesNon-transferable tokens
Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~50 minutes Change: Feature Sequence Diagram(s)sequenceDiagram
participant BatchValidation
participant TokenConfiguration
participant TokenNotTransferableError
BatchValidation->>TokenConfiguration: Check is_transferable
TokenConfiguration-->>BatchValidation: Return transferability
BatchValidation->>TokenNotTransferableError: Return error when token is not transferable
Suggested reviewers: Merge Risk: ⚪ Minimal · up to The checked transfer and contract-update paths preserve the stated restrictions. No merge-blocking issue remains after normal checks. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)✅ Passed checks (4 passed)Full details: Docstring CoverageExplanation Docstring coverage is 64.37% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 87 functions across 34 files. (1 skipped: 1 unsupported.)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Waiting for bot review — coderabbitai ✓ · thepastaclaw not yet. Wait for the missing reviews, or a writer can post |
|
📖 Book Preview built successfully. Download the preview from the workflow artifacts. Updated at 2026-10-09T05:31:07.237Z |
|
⚡ Priority review — 1st in line, starts as soon as a slot frees (commit a2dcb1b) |
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Phase 1 + Phase 2
Verified all supplied findings against head 94de788 and consolidated the overlapping reports into one non-blocking configuration-handling issue; no blocking finding was confirmed. This was a static review with no local builds or tests. The supplied CI snapshot reports successful Rust workspace and wasm-dpp2 tests, while several JavaScript suites and Docker builds remain pending.
🟡 1 suggestion(s)
Review provenance
Source: reviewer 1: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 7: gemini-3.8-flash-high (agent: phase1-reviewer, role: general); reviewer 8: gemini-3.8-flash-high (agent: phase1-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
criticalbygpt-6.1-sol(effort low) — The cross-cutting change modifies consensus acceptance and token movement restrictions in batch/advanced_structure/v1/mod.rs and data_contract_create/state/v0/mod.rs, alongside token configuration serialization and canonicalization. - Phase 1 reviewers:
gemini-3.8-flash-high— general (completed, effort high); agentphase1-reviewer,gemini-3.8-flash-high— rust-quality (completed, effort high); agentphase1-reviewer - Phase 1 model:
gemini-3.8-flash-high— antigravity quota: weekly 65% left, 5h 73% left - Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
- Fresh verifier:
gpt-6.1-sol— final-verifier; agentsol-verifier - Phase 2 reviewers:
gpt-6.1-sol— general (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— architecture-layering (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— ffi-engineer (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— platform-versioning (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— security-auditor (completed, effort xhigh); agentphase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/mod.rs`:
- [SUGGESTION] packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/mod.rs:281-283: Clear pool-only settings when disabling a pool but retaining V1
Retaining V1 preserves non-transferability, but now also retains the removed pool's threshold and change-control rules. Before registration, start with a pooled configuration whose threshold is Some(9), call set_transferable(false), then set_has_shielded_pool(false): the pool-less configuration still reports 9. Reversing those calls discards the threshold through the V0 downgrade. In WASM, assigning minimumPoolNotesForOutgoing = undefined cannot repair the retained value because the new no-pool branch returns success without modifying V1. Non-default pool rules also remain in all_used_group_positions() and all_change_control_rules().
The same no-pool assumption is enforced only by the WASM threshold writer: native callers can construct a pool-less, non-transferable V1 with an in-bounds threshold, and validate_token_configurations accepts it. V1's threshold authorization/application methods likewise do not check pool presence. This produces inconsistent native/WASM behavior and conflicts with wasm-dpp2/CONVENTIONS.md's delegation rule.
Clear the threshold and reset its change-control rules when disabling the pool, independently of whether the configuration downgrades. Define shared no-pool threshold handling in DPP and delegate the WASM writer to it. Extend the pool-removal tests with a nonzero threshold, non-default rules, both setter orders, and an explicit undefined assignment.
|
Bots are done — your move: post |
- Removing a non-transferable token's pool clears the pool threshold and resets its rules even though the configuration stays at format 1; TokenConfiguration::set_minimum_pool_notes_for_outgoing holds the "a threshold needs a pool" rule and wasm-dpp2 delegates to it. - The pool threshold TokenConfigUpdate items are refused on a format 1 configuration without a pool. - Document-base state validation 2 refuses paying the contract owner in the contract's own non-transferable token at payment time too, from the configuration in hand. - The external token refusal names the document type and its tokenCost actions, through one shared helper, and the create and update loops look each position up once. - Tests: protocol version 13 still refuses a non-transferable token as an unsupported format; mint, claim and direct purchase still credit identities; the payment-time refusal; both orders of removing a pool; the burn fixture no longer carries a cost registration would refuse; shared test helpers. - Docs: v14 note renumbered to 89, base supply destination corrected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Waiting for bot review — coderabbitai ✓ · thepastaclaw not yet. Wait for the missing reviews, or a writer can post |
Basic explanation
A token can now be marked
transferable: false. Holders keep what they are given and can spend it on the documents of the token's own contract by burning it, but nobody can hand it to anyone else, either with aTokenTransferor by routing it through a contract that pays its owner.Issue being fixed or feature implemented
Yappr wants YAPP to work this way: users get YAPP (claim or mint), spend it on posts, replies and likes, and cannot give it away. The only tool so far was to pause the token forever, and from 5.0.0-beta.3 a paused token also refuses document payments (
TokenIsPausedError, 40711), burns included. Before protocol version 14 pausing never stopped a transfer either: any contract may charge another contract's token and pay its own owner, so anyone could move a "paused" token by registering such a contract.What was done?
Before / after
Token configuration (format version 1, protocol version 14):
{ "$formatVersion": "1", "baseSupply": 0, "transferable": false, ... }TokenTransferTokenNotTransferableError(40726), in check tx and paid in a blockeffect: 0(pay the contract owner)NonTransferableTokenPaymentMustBurnError(10280), and again at payment time (40726)effect: 1(burn)TokenNotTransferableError(40726)hasShieldedPool: trueNonTransferableTokenShieldedPoolError(10279)TokenConfigUpdateitems (MinimumPoolNotesForOutgoing*)effectitself is not new: it has been part oftokenCostsince protocol version 9 (0pays the contract owner and is the default,1burns; only a contract's own token can be burned, 10261). The only change is that a non-transferable token requireseffect: 1.Changes
TokenConfigurationV1.transferable(serde defaulttrue, appended on the bincode wire),TokenConfigurationV1Getters::is_transferable(a V0 configuration is always transferable) andTokenConfigurationV1Setters::set_transferable. The canonical V0/V1 form keeps V1 for a non-transferable token without a pool: disabling the pool used to downgrade every V1 configuration to V0, which would have dropped the flag. Disabling the pool now clears its threshold and resets its rules whether or not the configuration downgrades, andTokenConfiguration::set_minimum_pool_notes_for_outgoing(refusing a threshold on a token without a pool) is the one place that rule lives; wasm-dpp2 delegates to it.TokenConfigurationV1::can_apply_token_configuration_itemrefuses the three pool threshold items on a configuration without a pool.validate_shielded_pool_rulesrefuses a non-transferable pooled token. The document type token cost parser refuses an own non-transferable token cost that pays the owner. Three consensus errors: 10279 and 10280 (basic, data contract band) and 40726 (state, token band), appended to their enums (discriminants 204, 205 and 173).TokenTransferof a non-transferable token. It reads the configuration from the contract the action already carries, so there is no extra read and the mempool refuses it too. Contract create and update state validation 0 refuse an external token cost on a non-transferable token, inside the loop that already fetches that contract to check the token exists, so fees do not change; the refusal names the document type and thetokenCostactions (data_contract_common::non_transferable_token_cost). Document-base state validation 2 (protocol version 14 only) refuses a payment to the contract owner in the contract's own non-transferable token again, from the configuration in hand, so the rule holds at payment time as well as at registration.TokenConfigurationaccepts and exposestransferable(getter and setter); the pool threshold writer no longer assumes every format 1 configuration has a pool.data-model/non-transferable-tokens.md, updates tocontract-keywords/token-cost.mdanddata-model/token-shielded-pools.md.The flag is fixed at creation: there is no
TokenConfigUpdateitem for it, and a contract update cannot change an existing token's configuration (the existing V1 field-guard test invalidate_updatenow coverstransferable). Every rule is therefore enforced at registration or on the transfer, and a document payment never has to look the flag up.In-place changes to shipped generations
parse_token_costs, shared by parser generations 1 to 3)validate_is_allowedthroughvalidate_token_configurations) refuses format 1 unpaid before protocol version 14, ahead of any parsing.validate_shielded_pool_rulesTokenConfigurationV1::can_apply_token_configuration_item(used by token config update state validation 0)Batch advanced structure validation 1 and document-base state validation 2 are selected only by protocol version 14, which is unreleased, so they are extended in place without a shipped-generation argument. A protocol version 13 test (
should_still_refuse_a_non_transferable_token_as_an_unsupported_format_at_protocol_version_13) shows a contract with a non-transferable token and a pay-the-owner cost is still refused unpaid as an unsupported format there, before any edited code runs.For Yappr social v14
"$formatVersion": "1","transferable": false,"startAsPaused": false.post,reply,likeandlikeReplycreate costs: add"effect": 1. Left at the default (pay the owner), the contract is refused with 10280.mintingAllowChoosingDestinationis already true) or the once-per-identity distribution. A base supply would stay with the maker, who cannot send it either, sobaseSupply: 0is the clean choice.How Has This Been Tested?
validate_update(including the V1 field guard extended totransferable).TokenConfiguration.spec.tsagainst a fresh build, 16 passing including 7 newtransferablecases.cargo fmt --all;cargo clippy -p dpp -p drive-abci --all-features --all-targets -- -D warningsclean; wasm-dpp and wasm-dpp2 build forwasm32-unknown-unknownwith no warnings in the changed files.Breaking Changes
Consensus-breaking at protocol version 14: new validation rules and three new consensus errors.
transferableis appended toTokenConfigurationV1's bincode encoding, like the other in-place protocol version 14 changes, so a network that already stored a format 1 token (a token with a shielded pool) under an earlier 5.0.0 beta cannot decode it with this build and needs a re-cut. Earlier protocol versions are unchanged.Checklist:
structure.rs, regeneratedgrovedb-structure.json, and checked the structure viewer link posted on this pull requestFor repository code-owners and collaborators only
🤖 Generated with Claude Code
Summary by CodeRabbit
PR Hygiene ·
a2dcb1b/skip-botsproceeds without the ones not yet reported/self-reviewedWhen every merge requirement is met, the
PR Hygienecheck passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.