Repository navigation
feat(platform)!: token marketplace escrow trees and token market contract (PV15) - #5526
QuantumExplorer wants to merge 84 commits into
Conversation
…a keywords From protocol version 14 the DPNS `domain` create is checked by the contract's own keywords instead of most of its data trigger: - `normalizedLabel` and `normalizedParentDomainName` are generated from `label` and `parentDomainName` (`generatedFrom` homographSafeASCII); - `preorderSalt` reveals the writer's own preorder, made in an earlier block, and deletes it (`refersTo` with a sha256d `findBy`, `where` `$ownerId`, `minimumAgeBlocks: 1`, `consume`); the preorder's storage is refunded to the writer; - the `recordsIdentityIsOwner` rule holds a new name's `records.identity` to its owner until the name first changes hands; - `domain` sets `canBeDeleted: false`; `preorder` requires `$createdAtBlockHeight`. Positions, transient list and encoding are v2's. `create_domain_data_trigger` 2 keeps only the parent domain checks, and data trigger bindings 2 drop the `domain` Replace and Delete rejects. `transition_to_version_14` re-stores DPNS. A preorder recording no `$createdAtBlockHeight` (made before the upgrade) counts as old enough for `minimumAgeBlocks`. The legacy JS SDK hashes the preorder as the platform does: normalized label, ".", parent as sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A document recording no `$createdAtBlockHeight` is known only to come from a block before the protocol upgrade that re-stored its system contract, so it now meets `minimumAgeBlocks: 1` (the DPNS reveal's) and no larger minimum. Another contract revealing DPNS preorders with a larger minimum refuses a pre-upgrade preorder; a test covers it. Also: a trigger-refused DPNS reveal keeps its preorder for a retry (test), and the check_tx contract update fee pin follows the DPNS v3 preorder type the test copies in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…riscoll-44c981 # Conflicts: # packages/rs-platform-version/src/version/v14.rs
`User.DASH` hashes `user.DASH`, the parent as sent, while the domain carries `parentDomainName: "DASH"` and `normalizedParentDomainName: "dash"`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…riscoll-44c981 # Conflicts: # packages/rs-platform-version/src/version/v14.rs
Short `description`s and no `$comment`s in DPNS v3; the rules they restated are in the schema keywords. The check_tx contract update fee pin follows the smaller preorder type it copies in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…owner A copy of a pending preorder no longer blocks it, and the copier can no longer reveal the name with the copy once the owner's reveal shows the salt: - `preorder` is unique on (`$ownerId`, `saltedDomainHash`), replacing the unique index on the hash alone; - the domain's `preorderSalt` finds it by `$ownerId` and the sha256d of the writer's id, the salt, the normalized label, "." and the parent. The parser (meta-schema v3, parser generation 3) admits `"$ownerId"` as a findBy function param (`LookupKeyParam::Writer`), a `$ownerId` findBy key on a transferable type beside a function, and that key for `consume`. At protocol version 14 every existing preorder is deleted and the type is rebuilt as a fresh v3 chain lays it out (`Drive::apply_contract_rebuilding_document_types`), so the minimumAgeBlocks allowance for preorders recording no creation height is reverted. rs-sdk and js-dash-sdk compute the preorder hash from the contract's own declaration, so either side of the upgrade hashes as the platform does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…riscoll-44c981 # Conflicts: # packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/mod.rs # packages/rs-platform-version/src/version/v14.rs
A registration took the preorder's hash form from the DPNS contract the client already held: the context provider's for the SDK's protocol version (13 on a fresh SDK), or js-dash-sdk's app cache. Either could still be DPNS v2 after the network moved to v3, so the preorder committed to the old hash and the domain create could never find it (40120), after the preorder was paid. rs-sdk now refreshes the protocol version (proven) and fetches DPNS from state before hashing, fetching again when the response raised the version the contract was parsed under. A preorderSalt reference without a findBy function is an error instead of a silent fall back to the old hash. js-dash-sdk fetches the contract with a new `skipCache` option, which also replaces the cached contract both documents are created against. Tests: the SDK hash against the real DPNS v2 and v3 contracts, a missing hashed property, and a mock SDK seeded at 13 with a provider holding v2; js specs for a cache warmed with v2 and for `skipCache`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…arsed version Comparing the SDK's protocol version before and after the fetch missed a node behind the network: after the refresh learned protocol 14, a node at the block before activation could still prove DPNS v2 at 13, the ratchet ignores the lower version, and the registration would hash the old way. The registration now reads each DPNS contract response with its metadata and takes the first one proved at the version the SDK parsed it under, at most 3 times. A response proved at a newer version was parsed under an older one (decoding DPNS v3 under 13 drops the salt's reference) and has raised the SDK's version, so the next is parsed under it; one proved at an older version is skipped. Otherwise the registration fails before paying. The document building moves into `dpns_registration_documents`, so an offline test drives it against a context provider holding v2 and checks the preorder's owner-bound hash and the domain's salt. Scripted tests cover a response parsed under an older version (rebuilt from serialized bytes as a proof decode does), a node behind the network, and every node behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`apply_contract_rebuilding_document_types` stored the new contract through `update_contract_add_operations`, which only builds operations, so unlike an update it never put the rewritten contract in the block cache. Until the protocol change events refresh every system contract at their end, a read in the block could fall back to a global cache entry a committed-state read had filled with the old contract. The rebuild now fetches what it stored and seeds it with `insert_rewritten`, as `update_contract` does. Test: DPNS v2 warm in the global cache, the preorder type rebuilt, and a read in the block's transaction gets v3. The book's same-block transfer case says the rule still applies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…anwhile The version a registration compared a DPNS contract response with was read before the fetch awaited the network. SDK clones share that version, so another request could raise it from 13 to 14 while the fetch waited; a response from a node still at 13 proving DPNS v2 then matched the stale 13 and was taken, and the preorder hashed the old way. A response is now taken only when the SDK's version is unchanged across the fetch as well as equal to the version the node proved it at. The version only rises, so one unchanged across the fetch is the version the response was parsed under. Regression: the first fetch raises the shared version to the latest while returning DPNS v2 proved at 13, which is skipped, and the next response, v3, is taken. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…riscoll-44c981 # Conflicts: # packages/rs-platform-version/src/version/v14.rs
Now that `identifier` and `bytes` + `size` are in v5.0-dev (#5355), the DPNS v3 schema uses them: `preorderSalt` and `saltedDomainHash` are `"type": "bytes", "size": 32`, `records.identity` is `"type": "identifier"`. The parsed types are unchanged; the stored contract keeps the schema as written, so the check_tx contract-update fee pin moves (27003089990 to 27003077990). Protocol 13 cannot decode the shorthands, so the SDK test of a response parsed under an older version now uses DPNS v2 as the contract read under 13. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clients: - js-dash-sdk refuses a records.identity other than the registering identity before paying for the preorder when the contract declares recordsIdentityIsOwner; the legacy (pre-v3) hash of a top-level name is of the salt and the label as sent, as trigger v1 hashes it; the "no DPNS app" spec now reaches the code it names. - rs-sdk `salted_domain_hash` reads the salt and label from the domain's properties instead of separate parameters. Drive and drive-abci: - the preorder rebuild refuses a type with trees outside its own subtree (indexOnly, contested index, moderator deletions, ttl), deletes without the backward-reference scan Drive never needs, and drops the always-None storage flags parameter. - trigger v2 queries the parent under the top level directly (a parent domain name holds no dot) and gains in-module tests. Parser and docs: - the consume check moves into `writer_owns_found_document`, so the parse_reference_target match is no longer reflowed. - `LOOKUP_KEY_WRITER` gives way to the existing `OWNER_ID`; the unused `preorder::properties::SALTED_DOMAIN_HASH` and `hash_double` import go. - meta-schema v3, reference_lookup.rs, wasm-dpp2 and the book describe the findBy `$ownerId` entry (40120 for another identity's commitment), the per-owner preorder index and the legacy top-level hash. Tests: - the strategy tests hash preorders with the shared `dpns_salted_domain_hash` (now outside cfg(test)) instead of the rule under test, and lose the stale "disable document triggers" comments; dead `dpns < 3` arms, the pasted height-1 comments and the derivable flag go; the protocol 13 test is renamed `should_...`; preorders are read by id; the award test uses `fast_forward_to_block`; the upgrade layout reference is a bare genesis state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`DocumentPropertyReferenceTarget::lookup_hash_key` returns the computed key of the `findBy` function a target declares, the chain `as_any_document_reference` -> `lookup` -> `hash_key` that rs-sdk's DPNS preorder hash and document reference validation 0 each spelled out. Reference validation 0 is a shipped generation edited in place: the accessor returns exactly what the inline chain returned, so nothing changes at any protocol version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ties The register spec mocks `crypto.randomBytes` to zeros for the whole suite, so two `generateRandomIdentifier()` calls gave the same identity and the refusal never fired. The spec now uses two fixed, different ids. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s stay deletable documents.md stated without exception that a findBy `"$ownerId"` source needs a referring type that cannot be transferred or traded, for a property's findBy and for creatorRefersTo; beside a function the lookup is judged on the create alone and the source is allowed, as DPNS v3 declares. The deletion chapter's onlyWhenConsumed preorder example now says DPNS v3's own preorder keeps `canBeDeleted: true`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three Drive tests used the latest DPNS fixture for writes or proofs at
protocol version 13. DPNS v3 is now written with the `bytes` and
`identifier` type shorthands, which the protocol 13 parser refuses
("unsupported property type: bytes"). A chain at 13 stores DPNS v2, which
every later version reads too, so these tests now load it: the two
contested-poll tests (`..._poll_started_before_protocol_version_14`) and
`verify_batch_document_proof_before_protocol_version_14_carries_no_balance`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion the response reports
A fresh SDK starts at its network's floor (protocol version 13 on
mainnet, testnet and regtest) and learns the network's version only
from a verified response. DPNS v3 is written with the `bytes` and
`identifier` type shorthands, which the protocol version 13 parser
refuses, so on a network at 14 the first proof holding DPNS failed
("unsupported property type: bytes") before the SDK could learn 14,
every time.
When parsing under the SDK's version fails on an unpinned SDK below the
newest version it knows, `parse_proof_at_the_reported_version` parses
under that newest version, which verifies the proof and its quorum
signature over a state id that signs the response's protocol version,
and, when the reported version is another known one, parses again under
it. A pinned SDK, or one at the newest version, reports the first
failure. The fetch traits require cloneable responses (all are) for the
retry.
Tests: a stand-in proof readable only from protocol version 14 parses on
an SDK seeded at 13 and fails on one pinned at 13.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts with #5313, which verifies a response again after fetching a missing quorum key, in the same clone-and-retry spot as the protocol version fallback: - fetch.rs, fetch_many.rs: both sides require a cloneable response; the doc names both retries. - sdk.rs: the Dapi arm takes #5313's `verify_fetching_quorum_key`, whose two verifications now go through `parse_proof_at_the_reported_version` (the version passed in, so both start from the same one). A failure of the quorum signature or its key is reported as it is under either version (`fails_after_reading_the_proof`): the signature is checked once the proof was read, so a missing key still reaches the fetch and no extra quorum lookup is made. Test: `should_report_a_missing_quorum_key_found_under_the_newest_version`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… verification `parse_proof_at_the_reported_version` borrows the request and response and copies them once per parse, so `verify_fetching_quorum_key` keeps one copy for both of its verifications instead of copying it again before the first. The probe is now scripted by its request (the first version that decodes it, the version its response reports, the versions whose signature or key fails) and records every version it is parsed under. New tests: - a reported version between the SDK's and the newest is parsed again under it, and its failure there is reported; - a signature failure under the newest version is reported over the first decoding error, and one under the SDK's version is not retried; - through `verify_fetching_quorum_key`, a fresh SDK reads the proof and learns the reported version, and learns none when the signature fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h the version fallback The probe now asks the provider for its quorum's key instead of failing outright, so a key the provider fetches makes the second verification succeed. A new test drives `verify_fetching_quorum_key` with an SDK two versions back and a provider holding no key: one fetch, the parse sequence current, newest, current, newest, reported, and the SDK learns the reported version. A regression of the second verification to a direct parse at the SDK's version would fail it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…riscoll-44c981 # Conflicts: # book/src/contract-keywords/refers-to-lookup.md # book/src/data-model/documents.md # packages/rs-dpp/schema/meta_schemas/document/v3/document-meta.json # packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/v0/mod.rs # packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/state/v0/fetch_documents.rs
…riscoll-44c981 # Conflicts: # book/src/contract-keywords/refers-to-lookup.md # book/src/data-model/documents.md # packages/rs-dpp/schema/meta_schemas/document/v3/document-meta.json # packages/rs-dpp/src/data_contract/document_type/property/reference_lookup.rs
…ut a salt A DPNS v3 preorder now holds `ownerNameHash`, the sha256d of the owner's id, the normalized label, "." and the normalized parent, unique per owner (`ownerAndNameHash`), and `nameHash`, the same without the owner, indexed with its creation height (`nameHashAndHeight`). It carries no salt and lives an hour (`ttl` 3600, so it refunds nothing when consumed). The domain finds it through `creatorRefersTo` (findBy `$ownerId` and the `ownerNameHash` function, minimumAgeBlocks 1, consume). `preorderSalt` stays declared on the domain, transient and no longer required, so domains stored under v1 and v2 decode. `nameHash` is there for a first-committer refersTo keyword built in a separate PR: with it, a name whose older valid preorder belongs to someone else is refused, which stops a watcher who reads a pending name from the mempool from preordering and revealing it first under full blocks. No salted design can check that before the owner reveals. Without a salt an owner holds one pending preorder of a name: a second is refused as a duplicate (40105) while the first stands. `Sdk::register_dpns_name` reveals a pending one instead of preordering again. - Drive: `apply_contract_rebuilding_document_types` leaves a type already stored as declared (so the v14 upgrade stays idempotent), and rebuilds into a type with a ttl, which holds no document and so no expiry entry; a stored type with a ttl is still refused. - rs-sdk and js-dash-sdk build the preorder from the domain's creator reference (`nameHash` from its params without `$ownerId`) and send no salt from v3; v1 and v2 keep the salted hash. - Tests: dpp DPNS v3 shape and hash; drive rebuild (ttl new type, type left as stored, stored ttl refused); drive-abci dpns_v3 (one pending preorder per name, the contested retry reveals the same preorder, preorder and domain fees pinned: 62,554,803 credits a name at 14 against 76,862,280 at 13); the check_tx update pin gains the 0.01 Dash non-unique index; rs-sdk and js-dash-sdk preorder shapes. - Docs: v14 note 90, the book (commit and reveal, documents, deletion, transient). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st, and reuses a pending preorder - rs-sdk registers the DPNS contract it fetched and built the documents against with its context provider, so the preorder and domain confirmations decode under it. A known-contracts cache warmed with DPNS v2 would otherwise decode the v3 preorder's proof under v2's single property and refuse it, after the preorder was paid for. - The v3 preorder property names are spelled in the module rather than imported from the bundled DPNS contract, which the SDK builds only with that contract's feature (`cargo check -p dash-sdk --no-default-features` builds). - The pending preorder lookup is a shared query builder (`pending_preorder_query`). - js-dash-sdk looks for a pending v3 preorder by its owner and `ownerNameHash` before preordering, reveals it instead, and propagates a failed query; v1 and v2 keep the fresh salt. Tests: rs-sdk (the exact predicates, a pending preorder returned, none in a proven absence, a failed query propagated, no query for a salted preorder, the registration leaving a writable v2-seeded provider holding v3); js-dash-sdk (the query, the reuse, a failed query, no query under v2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…its cache `WasmTrustedContext` forwarded `get_data_contract` to its inner `TrustedHttpContextProvider` but not `register_data_contract`, so the trait's no-op ran. A DPNS registration that registers the v3 contract it built against left a cache seeded with DPNS v2 (by `addKnownContract`, a contract fetch or a restored store) in place, and the preorder's confirmation decoded under v2 and failed after the preorder was paid for. The call is now forwarded. Test: a context seeded with DPNS v2 resolves DPNS to v3 once v3 is registered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: - drive contract migration modules: #5350's `rekey_unsigned_integer_index_values` beside this branch's `apply_contract_rebuilding_document_types`. - v14 notes: #5350 took note 90 (unsigned integers keyed in value order), so the DPNS v3 note moves to 92, after the shorthands note 91; the upgrade's comment cites it as 92. #5369 gave the document reference targets `where_conditions`: the owner-keyed reveal test's expected `DeletableDocumentLookup` sets it empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#5378 wrote the book's examples with the type shorthands, among them the salted DPNS preorder and its reveal in refers-to-lookup, documents and deletion. This branch already writes them with the shorthands, for the unsalted DPNS v3 (`ownerNameHash`, `nameHash`, `creatorRefersTo`), so its versions are kept; documents.md no longer points at "the salt above". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The token's trade mode sets it; every fill reads it there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s decimals (PV15) An integer property can be generated with sys.tokens.decimals, the decimals of the token an identifier property names. The value is read from state when the document is created and kept after, unless a replace names another token. The create and replace transformers read it (billed), clients leave it out, and a proof verifier takes it from the proved document. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-market-escrow-contract # Conflicts: # packages/rs-platform-version/src/version/v15.rs
… token ask and bid declare decimals generatedFrom sys.tokens.decimals of tokenId, so an order keeps the token's decimals from when it was placed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Waiting for bot review — thepastaclaw not yet. Wait for the missing reviews, or a writer can post |
The genesis state at the latest protocol version changed (the token market contract's orders drop minFill and generate their decimals), so the settled hashes that cover GroveDB roots move. Every validation hash (actions, errors, fees) is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Waiting for bot review — thepastaclaw not yet. Wait for the missing reviews, or a writer can post |
thepastaclaw
left a comment
There was a problem hiding this comment.
Re-review — Final validation — Phase 1 + Phase 2
The PV15 marketplace implementation is internally consistent, and the prior review findings have been addressed or withdrawn. Two non-blocking issues remain: subscription filters cannot evaluate the new state-generated token-decimals field, and the PR's in-place-generation documentation does not list all shipped transformer/helper edits. CI shows no failed code checks; changed-package determination and PR Hygiene remain pending.
🟡 2 suggestion(s)
1 finding(s) not shown inline (the lines are not part of this PR's diff)
🟡 Suggestion: Account for state-generated fields in document transition filters
packages/rs-drive/src/query/filter.rs:418-432
data_as_stored only invokes fill_generated_properties, whose implementation handles parameter-derived string transformations and does not resolve state-reading functions such as sys.tokens.decimals. Consequently, a create or replace subscription filter that constrains decimals sees the transition's intentionally omitted field as absent and rejects the transition, even though platform execution will generate and store the value. Either provide this filter path with the required state reader or explicitly reject/document state-generated fields in transition filters rather than silently treating them as missing.
source: gpt-6.1-sol (phase2-reviewer: rust-quality)
Review provenance
Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); 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: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 8: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 9: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 10: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 11: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 12: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
criticalbygpt-6.1-sol(effort low) — This is a large, intricate change that modifies consensus-critical protocol-version activation, persisted-state genesis and upgrade migrations, token supply accounting, and escrow-related funds state in rs-drive and rs-drive-abci. - Phase 1 reviewers:
muse-spark-1.3-contributor— general (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— rust-quality (completed, effort xhigh); agentphase1-reviewer - Phase 1 model:
muse-spark-1.3-contributor— not quota-gated; passed overgemini-3.8-flash-high(antigravity quota lookup failed),glm-5.3-flash(not used above high effort; tier asks max) - Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
- Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
- 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— 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,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— 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-drive/src/query/filter.rs`:
- [SUGGESTION] packages/rs-drive/src/query/filter.rs:418-432: Account for state-generated fields in document transition filters
`data_as_stored` only invokes `fill_generated_properties`, whose implementation handles parameter-derived string transformations and does not resolve state-reading functions such as `sys.tokens.decimals`. Consequently, a create or replace subscription filter that constrains `decimals` sees the transition's intentionally omitted field as absent and rejects the transition, even though platform execution will generate and store the value. Either provide this filter path with the required state reader or explicitly reject/document state-generated fields in transition filters rather than silently treating them as missing.
In `packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_create_transition_action/v0/transformer.rs`:
- [SUGGESTION] packages/rs-drive/src/state_transition_action/batch/batched_transition/document_transition/document_create_transition_action/v0/transformer.rs:91-105: Document the inert edits to shipped document-transformer generations
The shipped v0 create and replace transformers now call `generate_state_read_properties`, and the shared DPP/proof helpers were extended to recognize state-generated token properties. The protocol-version gates make these edits inert before protocol 15 because earlier contract parsers cannot produce `TokenFunction`, but the PR's "In-place changes to shipped generations" section lists only the total-token, balance/refund, and test changes. Add these transformer/helper edits, the affected pre-15 generations, and their unreachable-by-construction rationale so the historical replay audit is complete.
|
Bots are done — your move: post |
…et-escrow-contract Both sides added DRIVE_VERSION_V10 for protocol version 15: the merged one is the token marketplace's, built on DRIVE_CONTRACT_METHOD_VERSIONS_V5 (compiled contract storage). v15 note 8 is now Contracts stored compiled; the generated token decimals become note 9 and the marketplace state note 10. The shielded characterization roots are re-pinned for the merged genesis, and token-market-contract follows the 5.0.1 version bump. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Waiting for bot review — thepastaclaw not yet. Wait for the missing reviews, or a writer can post |
…contract Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Waiting for bot review — thepastaclaw not yet. Wait for the missing reviews, or a writer can post |
thepastaclaw
left a comment
There was a problem hiding this comment.
Re-review — Final validation — Phase 1 + Phase 2
At the reviewed head, the protocol-15 escrow, token-market contract, activation, conservation, proof, and estimation changes are generally correctly versioned and wired. One in-scope suggestion remains: subscription transition filters reconstruct only stateless generated properties, so filters on the new state-generated token decimals field can miss valid create or replace transitions that omit decimals. CI has passed the Rust workspace and relevant package checks, while the broader suite and local-network checks were still running in the supplied snapshot.
🟡 1 suggestion(s)
1 finding(s) not shown inline (the lines are not part of this PR's diff)
🟡 Suggestion: Account for state-generated fields in document transition filters
packages/rs-drive/src/query/filter.rs:418-432
data_as_stored calls DocumentTypeBasicMethods::data_as_stored, which only invokes fill_generated_properties. That helper uses SystemFunction::apply, and TokenFunction::Decimals intentionally returns None because it requires a state read. The new token-market schemas declare decimals as generated by sys.tokens.decimals, and clients omit it so the Drive transformer can resolve it from token state. Therefore, a create or replace subscription filter clause on decimals evaluates against a map where the field is absent and can return Fail, even though the resulting stored document contains the generated value. Make this filter path state-aware by supplying the resolved document data, or explicitly reject filter clauses targeting state-generated properties instead of silently treating them as absent; add coverage for an omitted ask.decimals or bid.decimals field.
source: muse-spark-1.3-contributor (phase1-reviewer: general); gpt-6.1-sol (phase2-reviewer: general, architecture-layering, rust-quality)
Review provenance
Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 8: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 9: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 10: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 11: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 12: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
criticalbygpt-6.1-sol(effort low) — This is a large, intricate change that modifies consensus-critical protocol-version dispatch, persistent storage initialization and upgrade migrations, and token escrow/supply accounting in the genesis and protocol-upgrade paths. - Phase 1 reviewers:
muse-spark-1.3-contributor— general (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— rust-quality (completed, effort xhigh); agentphase1-reviewer - Phase 1 model:
muse-spark-1.3-contributor— not quota-gated; passed overgemini-3.8-flash-high(antigravity quota lookup failed),glm-5.3-flash(not used above high effort; tier asks max) - Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
- Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
- 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— 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,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— 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-drive/src/query/filter.rs`:
- [SUGGESTION] packages/rs-drive/src/query/filter.rs:418-432: Account for state-generated fields in document transition filters
`data_as_stored` calls `DocumentTypeBasicMethods::data_as_stored`, which only invokes `fill_generated_properties`. That helper uses `SystemFunction::apply`, and `TokenFunction::Decimals` intentionally returns `None` because it requires a state read. The new token-market schemas declare `decimals` as generated by `sys.tokens.decimals`, and clients omit it so the Drive transformer can resolve it from token state. Therefore, a create or replace subscription filter clause on `decimals` evaluates against a map where the field is absent and can return `Fail`, even though the resulting stored document contains the generated value. Make this filter path state-aware by supplying the resolved document data, or explicitly reject filter clauses targeting state-generated properties instead of silently treating them as absent; add coverage for an omitted `ask.decimals` or `bid.decimals` field.
|
Bots are done — your move: post |
…et-escrow-contract v5.1-dev adds the yes/no vote polls (#4899) and the read-only token config update refusal (#5632). DRIVE_VERSION_V10 keeps both sides: the marketplace's methods with the yes/no vote and verify tables. v15 notes 9 and 10 are v5.1-dev's; the generated token decimals become note 11 and the marketplace state note 12. transition_to_version_15 creates the vote decisions trees and then the marketplace's escrow trees and contract. The shielded characterization roots are re-pinned for the merged genesis. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Waiting for bot review — thepastaclaw not yet. Wait for the missing reviews, or a writer can post |
thepastaclaw
left a comment
There was a problem hiding this comment.
Re-review — Final validation — Phase 1 + Phase 2
The protocol-15 token-market escrow, conservation, versioning, schema, activation, and fee-estimation changes are correctly gated and the previously reported issues are addressed or no longer applicable. One in-scope correctness issue remains: subscription transition filters still cannot materialize the new state-generated token decimals property, so filters can reject valid creates or replaces. Relevant Rust, build, and several SDK checks passed in CI, while broader test and E2E checks were still queued or running at the evidence snapshot.
🟡 1 suggestion(s)
1 finding(s) not shown inline (the lines are not part of this PR's diff)
🟡 Suggestion: Account for state-generated fields in document transition filters
packages/rs-drive/src/query/filter.rs:418-432
data_as_stored reconstructs only the stateless generatedFrom properties through fill_generated_properties. The new token-market ask.decimals and bid.decimals properties use the state-reading sys.tokens.decimals function; those values are materialized separately by Drive's create and replace transformers using token state and fee-bearing reads. Consequently, a normal transition that correctly omits decimals is evaluated by this filter with no decimals field, even though the platform stores the generated value. A subscription clause such as decimals == 8 therefore fails for a valid create or replace. Add a state-aware filter path that resolves the property using the same Drive callback as transformation, or explicitly reject filters that depend on state-generated properties rather than treating them as absent.
source: muse-spark-1.3-contributor (phase1-reviewer: general); gpt-6.1-sol (phase2-reviewer: architecture-layering, rust-quality)
Review provenance
Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 3: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 8: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 9: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 10: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 11: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 12: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
criticalbygpt-6.1-sol(effort low) — This large, intricate diff changes consensus-critical token supply accounting and escrow state, while adding protocol-version-gated persistent trees and a genesis-to-upgrade storage migration in rs-drive and rs-drive-abci. - Phase 1 reviewers:
muse-spark-1.3-contributor— general (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— rust-quality (completed, effort xhigh); agentphase1-reviewer - Phase 1 model:
muse-spark-1.3-contributor— not quota-gated; passed overgemini-3.8-flash-high(antigravity quota lookup failed),glm-5.3-flash(not used above high effort; tier asks max) - Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
- Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
- 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— 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,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— 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-drive/src/query/filter.rs`:
- [SUGGESTION] packages/rs-drive/src/query/filter.rs:418-432: Account for state-generated fields in document transition filters
`data_as_stored` reconstructs only the stateless `generatedFrom` properties through `fill_generated_properties`. The new token-market `ask.decimals` and `bid.decimals` properties use the state-reading `sys.tokens.decimals` function; those values are materialized separately by Drive's create and replace transformers using token state and fee-bearing reads. Consequently, a normal transition that correctly omits `decimals` is evaluated by this filter with no `decimals` field, even though the platform stores the generated value. A subscription clause such as `decimals == 8` therefore fails for a valid create or replace. Add a state-aware filter path that resolves the property using the same Drive callback as transformation, or explicitly reject filters that depend on state-generated properties rather than treating them as absent.
|
Bots are done — your move: post |
Basic explanation
What this does: Prepares Platform's state for the token marketplace. It adds the two places where escrow will live: one tree for the tokens holders list for sale, and one for the credits buyers offer. It also adds a new system data contract, the token market contract, whose two document types,
ask(a listing) andbid(an offer), will hold the open orders. Platform's check that no tokens appear or vanish is taught to count the escrowed tokens. Nothing can place an order yet: the transitions that use all this come in the next pull requests of the stack.Value: The order book becomes readable with the document queries and proofs every SDK already has, including the depth at each price as one proven sum, without any new query endpoint. Each side's depth sums what it holds in escrow, so the sums can never overflow.
Risks: Medium. From protocol version 15 the genesis state and the upgrade both gain two trees and a contract, so every node must build them identically: a test checks that a chain born at 15 and a chain upgraded from 14 build byte-identical
[Tokens]and[PreFundedSpecializedBalances]subtrees. Before 15 nothing changes: the trees, the contract and the new supply calculator are selected only by the protocol version 15 tables. Fee numbers in tests that start at the latest version move, because genesis has one more contract.Issue being fixed or feature implemented
The second PR of the token marketplace stack (PV15), on top of #5524 (tradeable tokens) and #5604 (
generatedFromsys.tokens.decimals, which this branch merges; review #5604 first, its commits show here until it merges). A listing must hold the tokens it offers and an offer the credits it offers, so a fill always delivers. This PR adds that escrow storage, the contract that records the open orders, and the supply check that keeps counting escrowed tokens.What was done?
Escrow trees (
DRIVE_VERSION_V10, PV15)[Tokens, TOKEN_ORDER_ESCROW_KEY (96)]: a BigSumTree with one SumTree per token, created with the token's first listing, of SumItems keyed by order id.[PreFundedSpecializedBalances, PREFUNDED_BALANCES_FOR_TOKEN_ORDERS (32)]: a SumTree of SumItems keyed by order id.Drive::insert_token_order_escrow_trees, called last bycreate_initial_state_structure_v5(genesis) and bytransition_to_version_15(upgrade). Each new key becomes a leaf of its Merk, so neither root moves.DriveTokenMethodVersions::order_escrow: add, set, remove and fetch for both escrows, with cost estimation. Each is anOptionalFeatureVersion:NoneinDRIVE_TOKEN_METHOD_VERSIONS_V1(so in every table before 15),Some(0)inDRIVE_TOKEN_METHOD_VERSIONS_V3; called with an earlier protocol version, each returnsDriveError::VersionNotActiveinstead of building operations for trees that do not exist there.util::estimated_layers). The offers' escrow key, 32, sits on the third level of the prefunded specialized balances Merk (128 over 64 and 192), so the credit escrow describes that layer at level 2. The voting balance and contract fee pot estimations describe it at level 1, their keys' depth; from 15 (DRIVE_VERSION_V10) their new generations,estimated_cost_for_prefunded_specialized_balance_update2 andadd_estimation_costs_for_contract_fee_pot_update1, run the previous generation and put back a deeper description it replaced, so the order of registration no longer matters. Likewise a listing's escrow key sits on level 3 of the Tokens Merk, below the balances key the token balance estimation describes at level 0: from 15for_token_balances1 keeps a deeper description of the Tokens layer. Both escrows also sit deeper in the root tree than the identity balances (level 1) and token balances (level 2) keys, so from 15for_balances1,for_token_balances1 and the voting balance estimation 2 keep a deeper description of the root too: an offer's batch, which debits the buyer and credits the escrow, is estimated with the root at level 3 whichever operation is built first. The other writes of a batch (a document, a nonce) register their own descriptions, so the rule is also kept at the batch's boundary:apply_drive_operations2 (protocol 15) lowers each operation of an estimated batch with layer descriptions of its own and merges them into the batch's as one description covering both (merge_estimated_layer: the depth of the deeper byestimate_levelsand, at that depth, the nodes GroveDB's ownaverage_case_merk_propagateprices dearer, in credits at the version's fees, the shallower description being priced at the deeper one's depth; then a fixed tie-break; Drive keeps no size model of its own). GroveDB gives every operation at a path the one description the batch keeps, so no operation is priced below its own description, no operation's registration lowers another's, and the batch's estimate is the same whatever their order. This raises the estimated fee of any batch whose operations describe a shared layer differently, not only the marketplace's: an ordinary document transition's nonce update describes the root a level deeper than the document does, so its root is now estimated at the nonce's depth (the sponsored token payment ofnullifier_batch_guard: +171,440 estimated processing credits; the fee paid does not change). Applying a batch is unchanged. Before 15 they all run unchanged.Token conservation (
calculate_total_tokens_balance2)TotalTokensBalancegainstotal_token_order_escrow_balances(0 from v0 and v1), andok()/total_balances()add it to the balance side.Tokens moved from a balance into escrow, 1,000 minted and 400 listed:
The credit check needs no change: the prefunded specialized balances root is a SumTree and already counts every child.
Token market contract (new package
packages/token-market-contract,SystemDataContract::TokenMarket = 11, IDAE6yYq65VdfGCg2MSyV9vV7cWGiFWjbwBuy7THesDh4e)askandbid:tokenId,price(credits per whole token),amount,remaining,decimals(0 to 255, the token's decimals when the order was placed, which every fill prices it at), and forbidthe escrowedescrow.decimalsis declared"generatedFrom": { "function": "sys.tokens.decimals", "params": ["tokenId"] }(feat(platform)!: generatedFrom sys.tokens.decimals generates a token's decimals (PV15) #5604): generated when the order is created and kept after. Orders carry no minimum fill: the token's trade mode sets it (feat(platform)!: token marketplace listings (PV15) #5529).book(tokenId,decimals,price,$createdAt),depth(tokenId,decimals,price,summable+rangeSummable:remainingfor asks,escrowfor bids) andbyOwner. Prices are per whole token at each order'sdecimals, so the book and the depth sums keep each scale apart: a query names the token and its decimals, and a sum never adds prices of two scales.sizedIntegerTypes: false: every integer is i64, as a summed property must be, and a partial fill rewrites an order without changing its size.creationRestrictionMode2; the types stay mutable and deletable (as withdrawals do) so orders carry a revision and storage flags.create_genesis_statev1 behind>= TOKEN_MARKETPLACE_INITIAL_PROTOCOL_VERSION), stored without storage flags bytransition_to_version_15, served by the Drive system contract cache and the trusted context provider from 15.SYSTEM_DATA_CONTRACT_VERSIONS_V4gainstoken_market: 1(0 in V1 to V3).Data trigger bindings 3 (
DRIVE_ABCI_VALIDATION_VERSIONS_V11): v2 plusreject_data_triggeron Replace and Delete ofaskandbid.A user's document transition against an order, protocol version 15:
Test fixtures at the latest version: a genesis at 15 holds one more contract and two more trees, so four document fee baselines move (delete and replace -47_280, DPNS create +137_760, the two DPNS v3 rows at 15 +95_920; each comment says why) and the shielded pool characterization digests of
nullifier_batch_guardthat cover the state root are re-pinned (ten of them, re-pinned again when the orders gaineddecimals). The applied fees of the same tests still match; their estimated processing fees move +171,440 fromapply_drive_operations2, as above.Wiring: workspace and Yarn workspace, Dockerfile stages, package filters, CI test lists, codecov,
data-contracts/dpp/rs-sdk/rs-sdk-ffi/wasm-sdk/trusted context provider features, the GroveDB structure description (.since(15)) andgrovedb-structure.json, the book (data-model/token-marketplace.md, architecture overview) and v15 note 12.How Has This Been Tested?
cargo test -p token-market-contract -p data-contracts: the published ID, the schema shape, no contract below 15.cargo test -p dpp --features all_features --lib -- token_market_tests total_tokens_balance: the contract loads with full validation at the latest version (mutable, deletable, no user creation, i64 integers, summeddepth); the escrow term balances the check.cargo test -p drive --lib -- structure order_escrow should_serve_token_market calculate_total_tokens(52 passed): add/set/remove/fetch of both escrows; supply stays balanced when tokens move into escrow at 15 and reads as unbalanced with the PV14 calculator; the cache serves the contract from 15; a new structure fixture reaches every escrow node.cargo test -p drive --lib -- order_escrow estimated_layers fee_pot prefunded(59 passed):VersionNotActiveat protocol version 14 and works at the latest;apply_drive_operations2: a batch of an identity contract nonce update and a prefunded balance write (root level 3) is estimated alike in either order at the latest version, and at least as high as either order at protocol version 14, where the two orders differ;merge_estimated_layer: at one depthAllItems(32, 8)beatsAllSubtrees(32, AllBigSumTrees)in either order (GroveDB prices it 254,960 credits against 221,360), a deeper description of narrower nodes merges with a shallower one of dearer nodes into the deeper depth with the dearer nodes, priced at least as high as each, while a deeper description of dearer nodes is kept as is, andAllItems(32, 8)andAllReference(32, 8), priced alike, merge to the same one in either order;merge_estimated_layer: two descriptions at one depth merge to the one pricing the larger node in either order, a deeper one keeps its depth with the dearer of the two nodes, and two that price alike merge to the same one in either order;depthsum at decimals 8 is 500 and at 2 is 50, and the proven per-price sum at 8 verifies against the client's path query with one bucket of 500;data_trigger_bindings_listat the latest version: each replace and delete of anaskand abidmatches exactly one binding, and running it refuses withDataTriggerConditionError; protocol version 14 binds none of them.cargo test -p drive-abci --lib -- batch::tests::token batch::tests::document::{deletion,replacement,dpns} --skip shielded(344 passed): the PV15 token balance estimation moves no pinned fee.yarn workspace @dashevo/token-market-contract test:decimalsis required, 0 and 255 pass, 256 is refused (run beforeminFillwas dropped; the spec now has nominFillcases and has not been rerun).minFillwas dropped anddecimalsdeclared generated:cargo test -p token-market-contract,cargo test -p drive --lib order_escrow,cargo test -p drive-abci --lib -- token_market create_genesis_state(19 + 8 passed).cargo test -p drive-abci --lib -- create_genesis_state perform_events_on_first_block_of_protocol_change data_triggers batch::tests::document::{deletion,replacement,dpns,creation,index_only}:transition_to_version_15inserts it, served by the cache afterwards;[Tokens]and[PreFundedSpecializedBalances]subtrees and root elements;cargo test -p drive-abci --lib -- batch::tests::token state_transitions::data_contract_create state_transitions::data_contract_update(569 passed before the two re-pinned shielded pool digests; both pass after).should_refuse_a_preloaded_token_market_id_before_protocol_15_in_any_build).In-place changes to shipped generations
The new behaviour comes through new generations: the voting balance, contract fee pot, token balance and identity balance estimations and
apply_drive_operations(2, 1, 1, 1 and 2) are selected only byDRIVE_VERSION_V10(protocol version 15), and the escrow fields ofDRIVE_TOKEN_METHOD_VERSIONS_V1are new andNone. Code that earlier protocol versions run is edited in four places, each inert there, with a comment at the edit saying why:calculate_total_tokens_balance0 (protocol versions 1 to 13) and 1 (14):TotalTokensBalancegainstotal_token_order_escrow_balances, and both set it to 0, so the totals they report are unchanged.TotalTokensBalance::ok()andtotal_balances()(dpp, unversioned), run at every protocol version by the block-end supply check (validate_token_aggregated_balance0): they also refuse a negative escrow total and add it to the balance side. Below 15 the escrow total is always 0, so the refusal cannot fire and the sum is unchanged; only the overflow error's text differs, on an invariant that has already failed. The block-end check's comment now counts the escrow too.apply_drive_operations1 (14):forfeit_storage_refundsbecomespub(super)so generation 2 reuses it. Its body is unchanged, so protocol version 14 forfeits exactly as before.apply_drive_operations1's tests run at protocol version 14 and at the latest version (generation 2), so each generation keeps its coverage. Tests only.generatedFromsys.tokens.decimals), which this branch merges: the Drive document create and replace transformers (selected at every protocol version) callgenerate_state_read_properties;validate_generated_from_properties_v0(14) skips the value comparison for a function that reads state;validate_generated_from_declarations(parser generation 3, 14) checks an identifier param,indexOnlyand contested indexes for a token function;verify_state_transition_was_executed_with_proofv0 copies a state-generated value from the proved document (adopt_state_generated_properties); the create transformer adds its contest fee to the generation fee instead of assigning it. Each is inert before protocol version 15 by construction: onlyapply_generated_from1 (15) admits asys.tokensfunction, meta-schema v3 and generation 0 refuse it, so no type parsed earlier declares one, the generation returns at once with a zero fee, and nothing is skipped or copied. feat(platform)!: generatedFrom sys.tokens.decimals generates a token's decimals (PV15) #5604's description lists them too.Breaking Changes
Consensus, from protocol version 15: new state trees at genesis and on the upgrade, a new system data contract, a new token conservation calculator and new data trigger bindings. 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
PR Hygiene ·
3f80b80/self-reviewedgithub(.github/package-filters/js-packages-direct.yml,.github/package-filters/js-packages-no-workflows.yml,.github/package-filters/js-packages.ymland 6 more) — ktechmidas or shumkovsystem-contracts— you own itdpp— you own itrs-drive-abci— you own itrs-drive— you own itrust-sdk-ffi(packages/rs-sdk-ffi/Cargo.toml) — lklimek or shumkovrust-sdk(packages/rs-sdk/Cargo.toml) — lklimek or shumkovjs-wasm-sdk(packages/wasm-sdk/Cargo.toml) — shumkovWhen every merge requirement is met, the
PR Hygienecheck passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.