Skip to main content

platform_version/version/
v14.rs

1use crate::version::consensus_versions::ConsensusVersions;
2use crate::version::dpp_versions::dpp_asset_lock_versions::v1::DPP_ASSET_LOCK_VERSIONS_V1;
3use crate::version::dpp_versions::dpp_contract_versions::v6::CONTRACT_VERSIONS_V6;
4use crate::version::dpp_versions::dpp_costs_versions::v1::DPP_COSTS_VERSIONS_V1;
5use crate::version::dpp_versions::dpp_document_versions::v4::DOCUMENT_VERSIONS_V4;
6use crate::version::dpp_versions::dpp_factory_versions::v1::DPP_FACTORY_VERSIONS_V1;
7use crate::version::dpp_versions::dpp_identity_versions::v1::IDENTITY_VERSIONS_V1;
8use crate::version::dpp_versions::dpp_method_versions::v3::DPP_METHOD_VERSIONS_V3;
9use crate::version::dpp_versions::dpp_state_transition_conversion_versions::v2::STATE_TRANSITION_CONVERSION_VERSIONS_V2;
10use crate::version::dpp_versions::dpp_state_transition_method_versions::v2::STATE_TRANSITION_METHOD_VERSIONS_V2;
11use crate::version::dpp_versions::dpp_state_transition_serialization_versions::v3::STATE_TRANSITION_SERIALIZATION_VERSIONS_V3;
12use crate::version::dpp_versions::dpp_state_transition_versions::v4::STATE_TRANSITION_VERSIONS_V4;
13use crate::version::dpp_versions::dpp_token_versions::v3::TOKEN_VERSIONS_V3;
14use crate::version::dpp_versions::dpp_validation_versions::v5::DPP_VALIDATION_VERSIONS_V5;
15use crate::version::dpp_versions::dpp_voting_versions::v2::VOTING_VERSION_V2;
16use crate::version::dpp_versions::DPPVersion;
17use crate::version::drive_abci_versions::drive_abci_checkpoint_parameters::v1::DRIVE_ABCI_CHECKPOINT_PARAMETERS_V1;
18use crate::version::drive_abci_versions::drive_abci_method_versions::v10::DRIVE_ABCI_METHOD_VERSIONS_V10;
19use crate::version::drive_abci_versions::drive_abci_query_versions::v3::DRIVE_ABCI_QUERY_VERSIONS_V3;
20use crate::version::drive_abci_versions::drive_abci_structure_versions::v2::DRIVE_ABCI_STRUCTURE_VERSIONS_V2;
21use crate::version::drive_abci_versions::drive_abci_validation_versions::v10::DRIVE_ABCI_VALIDATION_VERSIONS_V10;
22use crate::version::drive_abci_versions::drive_abci_withdrawal_constants::v3::DRIVE_ABCI_WITHDRAWAL_CONSTANTS_V3;
23use crate::version::drive_abci_versions::DriveAbciVersion;
24use crate::version::drive_versions::v9::DRIVE_VERSION_V9;
25use crate::version::fee::v3::FEE_VERSION3;
26use crate::version::protocol_version::PlatformVersion;
27use crate::version::system_data_contract_versions::v3::SYSTEM_DATA_CONTRACT_VERSIONS_V3;
28use crate::version::system_limits::v4::SYSTEM_LIMITS_V4;
29use crate::version::ProtocolVersion;
30
31pub const PROTOCOL_VERSION_14: ProtocolVersion = 14;
32
33/// v14 hosts six consensus changes:
34///
35/// 1. **Contract-level ranked aggregates**: an index can
36///    declare that its groups are rankable by an aggregate, so a query like
37///    "top 5 restaurants by average grade" is served from an ordered
38///    secondary tree in O(log n + k) with a proof, instead of being rejected.
39/// 2. **The shared-prefix aggregate index fix**: a data contract declaring
40///    an aggregating (countable / summable) index that terminates at a
41///    property which is also the prefix of a compound index (e.g. summable
42///    `[a]` next to `[a, b]`) registered successfully but rejected every
43///    document insert for most flag combinations, because Drive could not
44///    legally hang the compound continuation tree under the aggregating
45///    per-value tree. The v2 document index walkers (plus the v1 update
46///    walker) that fix it gate here as well: tree types derive through a
47///    shared continuation-demotion helper (provable count-bearing value
48///    trees with compound continuations demote to `CountSumTree`, since
49///    grovedb rejects count-suppressed children under provable count
50///    parents by design) and continuation inserts route through the
51///    completed zero-contribution wrapper matrix. No state migration is
52///    needed: shapes without compound continuations produce bit-identical
53///    operations, the broken shapes could never hold documents, and the
54///    one previously-insertable shape the demotion changes (a provable
55///    count-bearing value tree whose continuations were all sum-bearing —
56///    insertable pre-v14 only through an unenforced grovedb batch guard)
57///    simply gets `CountSumTree` value trees for values first seen at
58///    v14+, which readers treat identically.
59/// 3. **The contested vote poll index cross-check**: the index named by a
60///    document create transition's prefunded voting balance keys the vote
61///    poll, its stored info, its end-date entry and its prefunded
62///    specialized balance, while the contested index the contender is
63///    inserted under always comes from the document type. Up to v13 nothing
64///    tied the two together, so a submitter could register and fund a
65///    contest under a vote poll describing a different index than the one
66///    the contest was created on — which halts the chain when that poll
67///    ends — or open a contest for a document that is not a contested
68///    resource at all. State validation also prevents a non-contested create
69///    from occupying a live contested document's id before the contest winner
70///    is awarded into primary storage. Drive's contested insert also recreates
71///    an abstain or lock vote tree over the storage an earlier poll's cleanup
72///    left orphaned (it only removed the trees that received votes), so a
73///    resource can be contested again instead of failing with
74///    `CorruptedContractIndexes`.
75/// 4. **Relative daily withdrawal limit**: the flat 2000 Dash per 24 hours that
76///    applied from v8 becomes 15% of the total credits Platform held a day ago
77///    (`SYSTEM_LIMITS_V4.daily_withdrawal_limit_percent`, read by
78///    `daily_withdrawal_limit` v2 through `DPP_METHOD_VERSIONS_V3`), never below
79///    one maximal withdrawal (`max_withdrawal_amount`) so every accepted
80///    withdrawal eventually fits and cannot block the pooling queue. The base is
81///    capped at `max_daily_withdrawal_amount` (4000 Dash, Core's unlock capacity
82///    per day under V24 as written); the credit inflows of the active window —
83///    every credit mint, recorded per block by
84///    `record_credit_inflows_for_withdrawals` in the credit inflows sum tree —
85///    are added after the cap, so the limit counts net outflow and a matching
86///    deposit -> withdraw cycle does not consume the capped budget of other
87///    users (#4471). Outflow funded by same-window deposits may therefore
88///    exceed the cap; this mirrors the net credit-pool rule Core adopts for V24
89///    alongside this change (tracked in #4471), which must land before V24
90///    activates. Both the inflows and the pooled reservations count over the
91///    interval after the base snapshot only — an entry the snapshot already
92///    reflects is neither added nor subtracted again. The base is
93///    the total credits recorded at the latest block at least 24 hours before
94///    the current one: `DRIVE_ABCI_METHOD_VERSIONS_V10` turns on
95///    `record_total_credits_history_for_withdrawals`, which checks the total
96///    credits every block once fees and epoch rewards are in, writes it under
97///    the withdrawals tree keyed by block time whenever it changed (an entry
98///    describes the total until the next one) and prunes entries older than the
99///    one the limit reads, and `DRIVE_VERSION_V9`'s identity withdrawal table
100///    bumps `calculate_current_withdrawal_limit` to 1 to read that lagged
101///    value. Until an entry is a day old — the first day after activation — the
102///    flat 2000 Dash keeps applying, so the lag cannot be skipped by inflating
103///    the total before or at activation. The lag is the guardrail: a sudden
104///    jump in the total credits does not raise the limit for a day. Amounts
105///    already pooled in the last 24 hours keep counting against the maximum
106///    exactly as before. Pre-V24 Core caps unlocks at `LimitAmountV22` (2000
107///    Dash) per *block*, with the amount checked only at block level, so any
108///    daily total is still minable across blocks; V24's 4000 Dash per 576-block
109///    window matches the capped base and is raised to the same net rule before
110///    activation (see above).
111/// 5. **Time-range indexes**: an index can declare a `timeRange` transform
112///    that buckets a required system timestamp (`$createdAt` /
113///    `$updatedAt` / `$transferredAt`) into fixed-length, regularly-spaced,
114///    optionally overlapping windows declared in seconds (`range` / `step`,
115///    plus an optional `phase < step` alignment offset). Each grid gets its
116///    own index subtree — the level is keyed by the property name qualified
117///    with the grid — so several grids may bucket one timestamp side by
118///    side. A document is stored once per containing bucket per grid (the
119///    v2 insert/delete and v1 update walkers carry the fan-out; the
120///    per-document write amplification is capped per index by
121///    `SystemLimits::max_time_range_overlap_factor`), and the v1
122///    `getDocuments` handler resolves the new `IN_TIME_RANGE` operator —
123///    a typed `TimeRangeSelection` operand: `NEWEST`/`OLDEST` (resolved to
124///    a bucket-start equality from committed block time) or `BY_START`
125///    (naming any window, current or historic, by its grid-aligned start),
126///    with a `grid` member naming one grid where several bucket the field
127///    — making trending/leaderboard document and count/sum/avg queries
128///    provable over the current or any named window. `unique: true` is
129///    admitted only for non-overlapping windows (`range == step`) sourced
130///    from the immutable `$createdAt`.
131/// 6. **Deterministic token reward math**: `DistributionFunction::evaluate`
132///    (logarithmic, inverted-logarithmic, exponential and polynomial perpetual
133///    distributions) computes `ln`/`exp`/`pow` through the pinned pure-Rust
134///    `libm` crate instead of the platform C library. musl's `log` takes an
135///    FMA path on aarch64 and a non-FMA path on x86_64, so the two disagree by
136///    1 ulp on some inputs; a contract owner could pick parameters whose reward
137///    sat within that ulp of an integer, and `floor` then minted different
138///    amounts on the two architectures, splitting the app hash both at claim
139///    time and at contract registration (validation evaluates the start
140///    value). Gated on `distribution_function_evaluate_version` so both
141///    architectures switch at the same height; pre-v14 blocks replay on the
142///    old math byte-for-byte. `log`/`exp` have no architecture dispatch and
143///    `pow`'s only arch-touching call is the correctly-rounded `sqrt`, so the
144///    result is bit-identical on every target Platform builds for. The goal
145///    is determinism, not correct rounding: on a boundary tuple the host
146///    libm (glibc, macOS) can still be 1 ulp away, so anything predicting
147///    rewards with host math may differ from consensus by one unit.
148///
149/// The first two are orthogonal by construction: the ranked upgrade decides the
150/// *property-name* tree type, the demotion decides the *value* tree type
151/// one level below it, and a demoted `CountSumTree` value tree contributes
152/// its (count, sum) to a ranked indexed parent exactly as the provable
153/// variant did — so ranked secondaries keep ranking correctly over
154/// shared-prefix shapes.
155///
156/// Until a contract uses the ranked or time-range grammar, the only v14
157/// behavior changes are the shared-prefix fix, the contested-index
158/// cross-check, the index-reorder schema-compatibility fix and the relative
159/// daily withdrawal limit; everything else matches v13:
160///
161/// * `CONTRACT_VERSIONS_V6` points `document_type_schema` at the v3 document
162///   meta-schema, which hosts the ranked index keywords
163///   (`rankedCountable` / `rankedSummable` / `rankedAverageable`), the
164///   `refersTo` reference keyword and the `timeRange` index transform. v13
165///   keeps validating against meta-schema v2, where those keys are rejected
166///   as unknown properties, so a pre-v14 contract cannot smuggle them in.
167///   It also bumps `validate_schema_compatibility` to 1, which strips the
168///   top-level `indices` key before diffing the old and new document type
169///   schemas: index immutability is enforced by `validate_update` v1's
170///   name-keyed comparison, so a contract update that merely reorders the
171///   `indices` array validates cleanly instead of hitting the
172///   unsupported-keyword hard error (an internal error under v13).
173/// * `DRIVE_VERSION_V9` carries `DRIVE_DOCUMENT_METHOD_VERSIONS_V4`, adding
174///   the `detect_ranked_mode` routing slot, plus the grove-method slots for
175///   creating the three indexed tree variants and the verify-method slot for
176///   `verify_ranked_top_k_proof`. All are 0 today. The same table bumps the
177///   four index walkers to v2 and the document update walker to v1 for the
178///   shared-prefix fix; those same walker versions carry the time-range
179///   bucket fan-out, so both features gate on one table entry.
180/// * `DRIVE_ABCI_QUERY_VERSIONS_V3` bumps
181///   `document_query_helpers.compute_aggregate_mode_and_check_limit` 0 → 2,
182///   opening two routes on the v1 document-query handler: the ranked path
183///   (a grouped aggregate whose single `order_by` names the selected
184///   aggregate — `ORDER BY <agg> [ASC|DESC] LIMIT n [OFFSET m]`) and the
185///   boolean-`HAVING` range path (a grouped aggregate carrying exactly one
186///   `having` clause on the selected aggregate — `GROUP BY p HAVING <agg>
187///   <op> <value> LIMIT n`), the latter served as a value-bounded range
188///   read of the covering ranked index's axis secondary. v13 and earlier
189///   keep the v1 table and therefore keep rejecting both shapes, so
190///   mixed-version networks agree across the upgrade.
191/// * `DRIVE_ABCI_VALIDATION_VERSIONS_V10` bumps
192///   `document_create_transition_structure_validation` 0 → 1, requiring a
193///   contested create transition's prefunded voting balance to name the
194///   same vote poll the document itself resolves to, and rejecting one on a
195///   document that resolves to no contested index. It also bumps document
196///   create state validation to 2, enforcing `refersTo` document references
197///   and rejecting a non-contested create whose id is already present in the
198///   contested tree. Document replace state validation 1 enforces the same
199///   reference checks. v13 keeps the v9 table and therefore keeps
200///   accepting all of these, so replay of pre-upgrade blocks is unchanged.
201/// * `DOCUMENT_VERSIONS_V4` bumps `document_serialization_version` to
202///   default 3: documents are stamped with the contract version their bytes
203///   conform to (a varint after the format prefix), enabling the
204///   `requiredSince` property keyword — a contract update may add a new
205///   required property annotated with the version that update creates.
206///   Documents stamped below a property's `requiredSince` keep the
207///   presence-flagged layout they were written with, so the latest contract
208///   alone reconstructs every stamp's layout and no historical contract
209///   lookups are ever needed. Reads dispatch on the byte prefix, so
210///   formats 0–2 (all pre-v14 documents) deserialize exactly as before with
211///   an unstamped (pre-annotation) layout.
212/// 7. **Client-side GroveDB proof envelope floor**:
213///    `SYSTEM_LIMITS_V4.minimum_grovedb_proof_envelope_version` becomes 1, so
214///    a client verifying with v14 tables rejects the legacy V0 proof
215///    envelope before its bytes reach Drive (`drive-proof-verifier`,
216///    `wasm-drive-verify`, and the nested compacted address proofs). V0's
217///    item binding lets a prover return different item bytes under the same
218///    authenticated root; every live network has emitted V1 envelopes since
219///    v13 (grove version 3), so no honest response is affected.
220/// 8. **Epoch-based perpetual distribution claims stop wrapping**:
221///    `RewardDistributionType::max_cycle_moment` (the cap on how far one claim
222///    may redeem, selected by
223///    `TOKEN_VERSIONS_V3.reward_distribution_max_cycle_moment_version` 1)
224///    computes `start + interval * cycles` in `u64` with saturating
225///    arithmetic and narrows back to `EpochIndex` only after capping at the
226///    last completed cycle moment (`current cycle moment - interval`, the
227///    previous epoch for an interval of one as before; for wider intervals the
228///    same cycles are paid, but the cap now sits on a cycle boundary, the only
229///    shape in which `evaluate_interval`'s fixed-amount step count and its
230///    per-cycle loop agree). Up to v13 the sum was taken in `u16`: a
231///    fixed-amount function allows 32,767 cycles, so any epoch interval of
232///    three or more with a nonzero start (or two with a start at epoch two
233///    or later) pushed the cap past `u16::MAX`. Release builds wrap, the cap landed below the
234///    start, `evaluate_interval` saw an empty range and the claim was
235///    refused with `InvalidTokenClaimNoCurrentRewards` on every attempt. The
236///    v0 arithmetic is kept, wrapping explicitly, so those refusals replay.
237/// 9. **Evonode reward cycles weighted by the epochs they span**: the
238///    per-cycle evaluator in `DistributionFunction::evaluate_interval` asks
239///    the participation ratio for the epochs a cycle covers
240///    (`TOKEN_VERSIONS_V3.distribution_function_cycle_epochs_version` 1:
241///    `cycle moment - interval + 1 ..= cycle moment`). Up to v13 it passed the
242///    cycle's step index as if it were an epoch, which coincides only for an
243///    interval of one; for a wider interval it named epochs before the
244///    distribution started, outside the epoch window the claim loads, and an
245///    `EvonodesByParticipation` claim with a function other than a fixed
246///    amount failed as an internal error (reachable only once item 8 let the
247///    cap stop wrapping). Interval-one distributions are unchanged.
248/// 10. **A zero epoch interval is rejected at registration**:
249///     `RewardDistributionType::validate_structure_interval` v1
250///     (`CONTRACT_VERSIONS_V6.token_versions.validate_structure_interval`)
251///     refuses an `EpochBasedDistribution` with `interval: 0` with the new
252///     `InvalidTokenDistributionEpochIntervalTooShortError` (code 10828) on
253///     contract create and update. Up to v13 the epoch arm enforced nothing,
254///     so such a contract registered and every claim on it failed as an
255///     internal error, since no cycle can be computed from a zero step. Block
256///     and time minimums are unchanged.
257///
258/// * `ShieldFromIdentity` (state transition type 21) activates:
259///   `SHIELD_FROM_IDENTITY_INITIAL_PROTOCOL_VERSION = 14` gates it in
260///   `is_allowed`, and `DRIVE_ABCI_VALIDATION_VERSIONS_V10` is the first
261///   table whose `shield_from_identity_state_transition` row enables basic
262///   structure, identity signature, and nonce validation. It moves credits
263///   from an identity balance straight into the shielded pool: the funding
264///   side is identity-signed like `IdentityCreditTransferToAddresses`, the
265///   pool side is an outputs-only Orchard bundle like `Shield`, and the fee
266///   is metered plus the shielded compute fee, paid from the identity.
267///
268/// * `IdentityTopUpFromShieldedPool` (state transition type 22) activates at the
269///   same gate (`IDENTITY_TOP_UP_FROM_SHIELDED_POOL_INITIAL_PROTOCOL_VERSION = 14`,
270///   `DRIVE_ABCI_VALIDATION_VERSIONS_V10` row). It spends shielded notes like
271///   `Unshield` and credits an EXISTING identity's balance instead of a platform
272///   address: pool-paid flat fee (`compute_shielded_identity_top_up_fee`), no
273///   platform signature, the target identity and gross amount bound into the
274///   Orchard sighash, and no system-credit adjustment (pool and identity balances
275///   are both conservation-equation terms).
276///
277/// The wire surface changes only additively: `GetDocumentsRequestV1`
278/// already carries `selects` / `group_by` / `order_by` / `limit` /
279/// `offset`; the ranked response is an additive `ResultData.ranked`
280/// variant, whose `skipped` field is likewise additive; and the v1
281/// where-clause operator enum gains `IN_TIME_RANGE = 11`, which pre-v14
282/// servers reject as an unknown operator rather than misread (the v0 wire
283/// has no time-range operator at all).
284/// Contract-bound authentication keys activate through contract-bounds validation v2,
285/// identity-signature validation v1 and batch advanced-structure v1. Identity creation
286/// validates key bounds (state v1) and identity-update state v1 retains the contract
287/// lookup fees; Drive identity methods v2 index and refresh the bound keys.
288/// Contract group bounds on authentication keys ride the same versions: contract-bounds
289/// validation v2 admits them, batch transform v2 resolves the member contract's group
290/// memberships into the action (only for a group-bound signing key) for advanced-structure v1 to judge, and shielded-proof validation v1 refuses them in identity creation from the
291/// shielded pool, whose sighash preimage layout predates them.
292/// A transition carrying such a key is inactive before this version (`active_version_range`),
293/// so earlier protocol versions reject it without charging, as a binary that cannot decode it does.
294/// Authentication keys may carry a budget and an expiry (the version 1 public key format, which
295/// `StateTransition::active_version_range` admits from 14). Key structure validation v1
296/// (`STATE_TRANSITION_METHOD_VERSIONS_V2`) and `validate_identity_public_keys_limits` decide
297/// which keys may carry them; Drive identity methods v2 write the remaining budget when the key
298/// is added; identity-signature validation v1 refuses a key whose budget is spent;
299/// `validate_fees_of_event` v1 refuses an expired key and a spend the remaining budget does not
300/// cover (only metered processing may overshoot); `execute_event` v1 deducts what was spent.
301/// Shielded-proof validation v1 refuses a key that carries a budget or an expiry in identity
302/// creation from the shielded pool, whose sighash preimage does not cover the limits.
303/// `IdentityKeyLimitsUpdate` (state transition type 23, gated by
304/// `IDENTITY_KEY_LIMITS_UPDATE_INITIAL_PROTOCOL_VERSION`) raises a key's total budget, and the
305/// remaining budget with it, or moves its expiry later; it only ever loosens limits. Signed by a
306/// MASTER key or by a CRITICAL key without limits (`DRIVE_ABCI_VALIDATION_VERSIONS_V10` turns
307/// its gates on; Drive identity methods v2 rewrite the key and raise the remaining budget).
308pub const PLATFORM_V14: PlatformVersion = PlatformVersion {
309    protocol_version: PROTOCOL_VERSION_14,
310    drive: DRIVE_VERSION_V9, // changed: drive document method versions v4 — v2 index walkers (shared-prefix aggregate indexes become insertable) + the detect_ranked_mode slot
311    drive_abci: DriveAbciVersion {
312        structs: DRIVE_ABCI_STRUCTURE_VERSIONS_V2, // changed: saved platform state structure 1 keeps masternodes and validator sets as one aux entry each
313        methods: DRIVE_ABCI_METHOD_VERSIONS_V10, // changed: records the per-block total credits history for the daily withdrawal limit
314        validation_and_processing: DRIVE_ABCI_VALIDATION_VERSIONS_V10, // changed: contested-index cross-check + refersTo document reference validation
315        withdrawal_constants: DRIVE_ABCI_WITHDRAWAL_CONSTANTS_V3, // changed: prune bound for the total credits history
316        query: DRIVE_ABCI_QUERY_VERSIONS_V3, // changed: ranked + boolean-HAVING routing gate; the v1 handler also resolves IN_TIME_RANGE from committed block time
317        checkpoints: DRIVE_ABCI_CHECKPOINT_PARAMETERS_V1,
318    },
319    dpp: DPPVersion {
320        costs: DPP_COSTS_VERSIONS_V1,
321        validation: DPP_VALIDATION_VERSIONS_V5,
322        state_transition_serialization_versions: STATE_TRANSITION_SERIALIZATION_VERSIONS_V3, // changed: the indexOnly delete-by-values kind (documentIndexOnlyDelete) joins the wire
323        state_transition_conversion_versions: STATE_TRANSITION_CONVERSION_VERSIONS_V2,
324        state_transition_method_versions: STATE_TRANSITION_METHOD_VERSIONS_V2, // changed: public keys in creation may carry a budget or an expiry
325        state_transitions: STATE_TRANSITION_VERSIONS_V4,
326        contract_versions: CONTRACT_VERSIONS_V6, // changed: v3 document meta-schema hosts the ranked, refersTo, requiredSince and timeRange keywords; validate_structure_interval v1 rejects a zero epoch interval
327        document_versions: DOCUMENT_VERSIONS_V4, // changed: document serialization format 3 — the contract version stamp that enables `requiredSince` properties
328        identity_versions: IDENTITY_VERSIONS_V1,
329        voting_versions: VOTING_VERSION_V2,
330        token_versions: TOKEN_VERSIONS_V3, // changed: distribution_function_evaluate v1 — deterministic libm for token reward math; reward_distribution_max_cycle_moment v1: the epoch claim cap no longer wraps; distribution_function_cycle_epochs v1: evonode cycles weighted by the epochs they span
331        asset_lock_versions: DPP_ASSET_LOCK_VERSIONS_V1,
332        methods: DPP_METHOD_VERSIONS_V3, // changed: daily_withdrawal_limit v2 — a percentage of the total credits a day ago
333        factory_versions: DPP_FACTORY_VERSIONS_V1,
334    },
335    system_data_contracts: SYSTEM_DATA_CONTRACT_VERSIONS_V3, // changed: DashPay v2 adds profile payment address fields (DIP-33); withdrawals v2 admits the terminal FAILED status
336    // The TTL ephemeral-bytes rate (270 credits/byte to processing) rides
337    // the shared storage table; it is dead below v14 (the `ttl` grammar
338    // does not parse), so no table fork is needed.
339    fee_version: FEE_VERSION3, // changed: contested document contribution reduced to 0.1 DASH
340    system_limits: SYSTEM_LIMITS_V4, // changed: daily withdrawal limit becomes 15% of the total credits a day ago + time-range overlap-factor cap (24) + time-range TTL cap (1 week) and per-write drop cap (32) + GroveDB proof envelope floor (V1)
341    consensus: ConsensusVersions {
342        tenderdash_consensus_version: 1,
343    },
344};
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349    use crate::version::v13::PLATFORM_V13;
350
351    #[test]
352    fn should_halve_only_the_contested_document_fee_at_protocol_14() {
353        for protocol_version in 1..14 {
354            let version = PlatformVersion::get(protocol_version).expect("known protocol version");
355            assert_eq!(
356                version
357                    .fee_version
358                    .vote_resolution_fund_fees
359                    .contested_document_vote_resolution_fund_required_amount,
360                20_000_000_000,
361                "protocol {protocol_version} must preserve the 0.2 DASH contribution"
362            );
363        }
364
365        let mut expected_fees = PLATFORM_V13.fee_version.clone();
366        expected_fees
367            .vote_resolution_fund_fees
368            .contested_document_vote_resolution_fund_required_amount = 10_000_000_000;
369        assert_eq!(PLATFORM_V14.fee_version, expected_fees);
370    }
371
372    /// The ranked / boolean-HAVING routing gate lives in v14's own query
373    /// table, so flipping it touches only v14: a v13 node keeps running
374    /// the v0 helper, which rejects every non-empty HAVING, so a
375    /// mixed-version network agrees until the upgrade vote carries.
376    ///
377    /// v14 selects the v2 helper, which routes the ranked shape
378    /// (`ORDER BY <agg> LIMIT n`) to `dispatch_ranked_v1` and the
379    /// boolean-HAVING range shape (exactly one `having` clause on the
380    /// selected aggregate) to `dispatch_having_v1`. A change that made
381    /// v13 non-zero here would be consensus-breaking for
382    /// already-deployed nodes, which is exactly what the v13 half of
383    /// this assertion guards.
384    #[test]
385    fn ranked_having_routing_gate_is_v14_only() {
386        assert_eq!(
387            PLATFORM_V13
388                .drive_abci
389                .query
390                .document_query_helpers
391                .compute_aggregate_mode_and_check_limit,
392            0
393        );
394        assert_eq!(
395            PLATFORM_V14
396                .drive_abci
397                .query
398                .document_query_helpers
399                .compute_aggregate_mode_and_check_limit,
400            2
401        );
402    }
403
404    /// The ranked index keywords are gated by the meta-schema version, so v14
405    /// must select meta-schema v3 while v13 stays on v2.
406    #[test]
407    fn ranked_index_keywords_are_gated_by_meta_schema_v3() {
408        assert_eq!(
409            PLATFORM_V13
410                .dpp
411                .contract_versions
412                .document_type_versions
413                .schema
414                .document_type_schema,
415            2
416        );
417        assert_eq!(
418            PLATFORM_V14
419                .dpp
420                .contract_versions
421                .document_type_versions
422                .schema
423                .document_type_schema,
424            3
425        );
426    }
427
428    /// The ranked grammar lives in its own document-type parser generation
429    /// rather than behind a version gate inside a shipped one, so v14 must
430    /// select generation 3 while v13 stays on generation 2. Pinned here
431    /// because it is the whole reason generations 0/1/2 can stay byte-identical
432    /// to what consensus already ran: a historical block replayed at v13 is
433    /// parsed by a generation that has never heard of the ranked keywords.
434    /// The grove v4 cleanup gates (batch overwrite inspection + delete-tree
435    /// actual-type cleanup) exist for the indexed trees that ranked indexes
436    /// lay down, so v14 must select grove protocol 4 while v13 stays on 3.
437    /// The gates are cost-neutral — they derive the old element from data the
438    /// merk apply already loads — and the fee-constant tests pin identical
439    /// fees on both sides of the boundary. Platform flows cannot themselves
440    /// overwrite a ranked index (the flags are immutable on contract update
441    /// and new indexes cannot be added to an existing document type), so the
442    /// cleanup behavior itself is exercised by grovedb's own overwrite suites
443    /// at the pinned revision; this test pins that v14 actually activates
444    /// them.
445    #[test]
446    fn grove_v4_cleanup_gates_activate_at_v14() {
447        assert_eq!(PLATFORM_V13.drive.grove_version.protocol_version, 3);
448        assert_eq!(PLATFORM_V14.drive.grove_version.protocol_version, 4);
449    }
450
451    #[test]
452    fn ranked_grammar_gets_its_own_parser_generation() {
453        assert_eq!(
454            PLATFORM_V13
455                .dpp
456                .contract_versions
457                .document_type_versions
458                .class_method_versions
459                .try_from_schema,
460            2
461        );
462        assert_eq!(
463            PLATFORM_V14
464                .dpp
465                .contract_versions
466                .document_type_versions
467                .class_method_versions
468                .try_from_schema,
469            3
470        );
471    }
472
473    /// v14 introduces the slots but activates none of them yet. If a later
474    /// change flips one of these, it must do so deliberately — and update this
475    /// test — rather than by inheriting a default.
476    #[test]
477    fn ranked_feature_slots_exist_but_are_dormant() {
478        assert_eq!(
479            PLATFORM_V14.drive.methods.document.query.detect_ranked_mode,
480            0
481        );
482        assert_eq!(
483            PLATFORM_V14.drive.methods.document.query.detect_having_mode,
484            0
485        );
486        assert_eq!(
487            PLATFORM_V14
488                .drive
489                .methods
490                .verify
491                .document_ranked
492                .verify_ranked_top_k_proof,
493            0
494        );
495        assert_eq!(
496            PLATFORM_V14
497                .drive
498                .methods
499                .verify
500                .document_ranked
501                .verify_having_range_proof,
502            0
503        );
504        let grove = &PLATFORM_V14.drive.grove_methods.batch;
505        assert_eq!(grove.batch_insert_empty_provable_count_indexed_tree, 0);
506        assert_eq!(grove.batch_insert_empty_provable_sum_indexed_tree, 0);
507        assert_eq!(
508            grove.batch_insert_empty_provable_count_provable_sum_indexed_tree,
509            0
510        );
511    }
512
513    /// The contested vote poll index cross-check changes accept/reject
514    /// behavior for document create transitions, so it lives in v14's own
515    /// validation table: a v13 node keeps running structure validation v0,
516    /// which validates only the prefunded amount and ignores the index name.
517    /// A change that made v13 non-zero here would retroactively reject
518    /// transitions already in the chain.
519    #[test]
520    fn contested_index_cross_check_is_v14_only() {
521        assert_eq!(
522            PLATFORM_V13
523                .drive_abci
524                .validation_and_processing
525                .state_transitions
526                .batch_state_transition
527                .document_create_transition_structure_validation,
528            0
529        );
530        assert_eq!(
531            PLATFORM_V14
532                .drive_abci
533                .validation_and_processing
534                .state_transitions
535                .batch_state_transition
536                .document_create_transition_structure_validation,
537            1
538        );
539        assert_eq!(
540            PLATFORM_V13
541                .drive_abci
542                .validation_and_processing
543                .state_transitions
544                .batch_state_transition
545                .document_create_transition_state_validation,
546            1
547        );
548        assert_eq!(
549            PLATFORM_V14
550                .drive_abci
551                .validation_and_processing
552                .state_transitions
553                .batch_state_transition
554                .document_create_transition_state_validation,
555            2
556        );
557        assert_eq!(
558            PLATFORM_V13
559                .drive
560                .methods
561                .document
562                .insert_contested
563                .add_contested_vote_subtree_for_non_identities_operations,
564            0
565        );
566        assert_eq!(
567            PLATFORM_V14
568                .drive
569                .methods
570                .document
571                .insert_contested
572                .add_contested_vote_subtree_for_non_identities_operations,
573            1
574        );
575    }
576}