feat(rs-dpp): unify JSON/Value conversion traits + comprehensive round-trip tests#3573
Draft
feat(rs-dpp): unify JSON/Value conversion traits + comprehensive round-trip tests#3573
Conversation
Adds JsonConvertible / ValueConvertible impls (canonical traits in
packages/rs-dpp/src/serialization/serialization_traits.rs) to the
domain types catalogued in docs/json-value-conversion-inventory.md.
This is the unification first pass — round-trip correctness, tagged-
enum tag preservation, and integer-precision tests are deferred to the
second pass per the plan. Some impls may produce broken JSON or fail
round-trip until pass 2 fixes them; that's expected.
Coverage:
- Symmetrize V-only and J-only types (15+1).
- Add J+V to types missing both: top priorities (DataContract,
StateTransition, BatchTransition, Document, AssetLockProof,
AddressCreditWithdrawalTransition, Pooling) plus 22 batch transitions
and 19 leaf serde types.
Skipped: types without serde derives, lifetime-param refs, and the
wasm-dpp legacy crate per minimum-touch policy.
Approach: derive(JsonConvertible/ValueConvertible) where the type
already opts into the json_safe_fields macro ecosystem; empty manual
impl X {} (§6 escape hatch) elsewhere to bypass the JsonSafeFields
cascade. Both paths use the trait's default serde-delegating methods.
Adds planning docs:
- docs/json-value-conversion-inventory.md — structural inventory.
- docs/json-value-unification-plan.md — phased plan with critical
findings and per-mechanism deprecation decisions.
cargo check -p dpp passes with --features=json-conversion,value-conversion,serde-conversion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Updates the unification plan with: - Progress table tracking the 5 passes (1 done, 2 in progress). - Phase B/C status updated: ~80 types now have canonical impls. - Skip-list rationale for types we deliberately did NOT migrate (no serde derives, lifetime params, internal indirection). - Section 11 "Lessons learned from pass 1" — the JsonSafeFields cascade, BTreeMap-of-enum-keys serde helpers, what shipped in the 481 commits we pulled, test-fixture pattern, sandbox/sccache/gpg gotchas. - Reference to pass-1 commit 9f23d67. Companion doc gets a status banner pointing back to the plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ity) Adding empty impl JsonConvertible/ValueConvertible for DataContract in pass 1 collided with the existing DataContractJsonConversionMethodsV0:: to_json(&self, &PlatformVersion) at every call site that passes a PlatformVersion — Rust E0034 (multiple applicable items in scope). Per the unification plan §3.11 step 10, DataContract is KEEP-AS-EXCEPTION (version-aware serde via DataContractInSerializationFormat). The proper unification path renames the legacy methods to *_versioned first, then the canonical traits can layer on. That's a follow-up. For now, leave a comment in data_contract/mod.rs explaining the absence and pointing readers at DataContractInSerializationFormat (which DOES have the canonical traits) when they need a JSON shape. cargo test -p dpp --features=json-conversion,value-conversion,serde-conversion --lib json_convertible_tests now passes (10/10 — the 5 address-transition round-trip + tag-preservation tests from pass 1). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds json_round_trip + value_round_trip tests for 11 types covered by the pass-1 unification commit (9f23d67). All 28 tests in the new modules pass; no regressions in the existing 3432 dpp lib tests. Types covered: - Identity, IdentityV0, IdentityPublicKey - AddressCreditWithdrawalTransition - TokenContractInfo, TokenPaymentInfo - Document - Pooling - GroupStateTransitionInfo Types skipped with TODO (V0 inner lacks Default): - AssetLockValue (AssetLockValueV0) - GroupAction (GroupActionV0 has GroupActionEvent field with no Default) Pass-2 work continues: more types to follow, then bug discovery (StateTransition untagged, ExtendedDocument bug, Critical-1 / -2 / -4). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds round-trip tests for TokenEmergencyAction, GasFeesPaidBy, and YesNoAbstainVoteChoice — all flat enums with derive(Default). Also marks TokenMarketplaceRules and other types whose V0 lacks Default with TODO(unification pass 2) comments — they need explicit fixtures. 34 json_convertible_tests pass, no regressions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…DistributionType (pass 2) DocumentPatch has Default and J+V impls — round-trips cleanly. TokenDistributionType has Default but the J+V impls are on its variants (TokenDistributionTypeWithResolvedRecipient, TokenDistributionInfo), neither of which has Default — left as TODO for explicit fixture. 36/36 json_convertible_tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…perty assertions Per user direction, every J/V test must: 1. Use a NON-DEFAULT fixture (distinguishable values per field). 2. Round-trip via to_json/from_json (and to_object/from_object). 3. Assert each field of the recovered value individually — catches silent field drops, type narrowing, and PartialEq quirks that whole-struct equality can miss. IdentityCreateFromAddressesTransition is the canonical example — fixture has 6 non-default fields including a 2-entry inputs map with both P2PKH+P2SH addresses, a populated public key, two witness types, custom fee strategy, and non-zero user_fee_increase. All three tests pass (json_round_trip, value_round_trip, format_version_tag). Plan §8 updated with the new mandatory convention and rationale. Existing tests with Default fixtures are now legacy and will be upgraded as we revisit each type in pass 2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sferToAddresses tests Apply the new mandatory convention (non-default fixture + per-property assertions + round-trip) to two more address transitions. Both fixtures use distinguishable values for every field (identity_id, recipient_addresses, nonce, signature, fee strategy, witnesses, etc.) so the per-property assertions actually exercise data preservation. 3/5 address transitions now on the new convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Upgrade AddressFundingFromAssetLockTransition, AddressFundsTransferTransition,
and AddressCreditWithdrawalTransition tests to non-default fixture +
per-property assertions per the new convention.
Bug surfaced: AddressFundingFromAssetLockTransition.value_round_trip
fails — `OutPoint` inside `ChainAssetLockProof` cannot deserialize from
`platform_value::Value::Map` ("invalid type: map, expected an OutPoint").
JSON round-trip works fine. Marked the value test #[ignore] with the
reason and logged in plan §10b for pass-3 fix.
5/5 address transitions now on the new convention. 46 json_convertible_tests
pass, 3 ignored (1 OutPoint bug + 2 StateTransition untagged-enum known
failures).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…erty assertions Replaces the legacy Identity::default() fixture with one that has: - id: Identifier::new([0x42; 32]) - balance: 1_000_000 - revision: 7 - public_keys: BTreeMap with 2 distinct entries Per-property assertions check id, balance, revision, and public_keys count. Removes the duplicate empty-fixture test module that was leftover. 401 dpp lib tests pass (filtered to identity::identity). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… tests Apply non-default fixture + per-property assertion convention to: - IdentityPublicKey (8 distinguishable fields incl. disabled_at, contract_bounds) - TokenContractInfo (contract_id + token_contract_position; note: untagged enum) - Pooling (test all 3 variants — Never/IfAvailable/Standard) 48 json_convertible_tests pass, 3 ignored (1 OutPoint bug, 2 StateTransition). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces single-Default-fixture tests for unit enums with each_variant() pattern that exercises all variants in turn. This is the per-property-assertion equivalent for unit-only enums where each discriminant is the only "field". Upgrades: - TokenEmergencyAction (Pause, Resume) - GasFeesPaidBy (DocumentOwner, ContractOwner, PreferContractOwner) - YesNoAbstainVoteChoice (YES, NO, ABSTAIN) 48 json_convertible_tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Apply non-default fixture + per-property assertion convention to: - GroupStateTransitionInfo (group_contract_position=5, action_id=[0x33;32], action_is_proposer=true) - DocumentPatch (id=[0x77;32], 2 properties, revision=3, updated_at=1.7T) 48 json_convertible_tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…per-property 5-field fixture with all Option fields populated and gas_fees_paid_by set to a non-default variant. Per-property assertion verifies each field preserves through round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…er-property 5-field fixture (owner_id, transitions, user_fee_increase, signature_public_key_id, signature) with distinguishable values. transitions vec is empty since DocumentTransition sub-types are tested in their own modules. Per-property assertion verifies each field preserves through round-trip. 49 json_convertible_tests pass, 3 ignored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rk list Updates the plan with: - Pass-2 status table — 17/~80 types upgraded, 1 bug surfaced. - Explicit list of types still on Default fixtures or without tests. - Cost estimate: ~10-15 hours of focused work to finish pass 2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds basic round-trip + format version tag tests for: - IdentityCreateTransition (json/value tests #[ignore]: V0::default() has structurally invalid asset_lock_proof — needs explicit fixture) - IdentityTopUpTransition - IdentityCreditTransferTransition - MasternodeVoteTransition - IdentityPublicKeyInCreation - IdentityUpdateTransition - IdentityCreditWithdrawalTransition DataContractCreateTransition and DataContractUpdateTransition skipped: their V0 inners lack Default — needs explicit fixtures (TODO). 68 json_convertible_tests pass, 5 ignored (3 prior + 2 new IdentityCreateTransition pending real fixture). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds basic round-trip tests using Default fixture for: - BlockInfo (struct with Default) - Vote (manual Default impl) - VotePoll (manual Default impl) - ResourceVoteChoice (derived Default with #[default] variant) - InstantAssetLockProof (manual Default impl) Marks 6 types as TODO (no Default — needs explicit fixture): - ContractBoundSpecification, ChainAssetLockProof, - ExtendedBlockInfo, ExtendedEpochInfo, FinalizedEpochInfo, - IdentityTokenInfo, TokenStatus. 78 json_convertible_tests pass, 5 ignored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces TODOs with hand-built fixtures for:
- IdentityTokenInfo (frozen=true)
- TokenStatus (paused=true)
- ExtendedEpochInfo (6 fields, distinguishable values)
- FinalizedEpochInfo (12 fields incl. block_proposers map)
- ExtendedBlockInfo (8 fields incl. signature [u8;96])
Bug surfaced: ExtendedBlockInfo value_round_trip fails on signature
field round-trip via platform_value::Value ("Invalid symbol 17"). JSON
works. Marked #[ignore] and logged in plan §10b.
87 conversion tests pass, 6 ignored (3 prior + 1 new bug + 2 needs-fixture).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AssetLockValue uses AssetLockValue::new() factory (V0 fields are pub(super), can't be set directly). ChainAssetLockProof uses OutPoint::from_str factory; value test ignored due to known OutPoint round-trip bug. 90 conversion tests pass, 7 ignored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…IndexInformation)
…ourceVotePoll + ContestedDocumentVotePollWinnerInfo 102 conversion tests pass, 7 ignored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ansition Both use fully-qualified trait syntax to disambiguate from legacy StateTransitionValueConvert::to_object/to_json methods on the same type — known E0034 ambiguity per plan §3.11. 106 conversion tests pass, 7 ignored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DocumentReplaceTransition, DocumentTransferTransition, DocumentPurchaseTransition, DocumentUpdatePriceTransition — all use fully-qualified trait syntax to disambiguate from legacy methods. 116 conversion tests pass, 7 ignored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nMint 122 conversion tests pass, 7 ignored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…troyFrozenFunds 128 tests pass, 7 ignored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…y/Claim/DirectPurchase/SetPrice) 136 conversion tests pass, 7 ignored. All 17 of 19 batch sub-transitions now tested (only TokenConfigUpdate remaining — needs TokenConfigurationChangeItem fixture). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
154 conversion tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
160 conversion tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… tests 164 conversion tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
167 conversion tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
177 conversion tests pass, 12 ignored (3 platform_value [u8;N] bugs + 2 OutPoint bugs + 7 needs-fixture/known-fragile). All 5 shielded transitions now tested. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dAssetLockInfo StateTransitionProofResult was missing PartialEq, blocking simple round-trip assert_eq tests. Add PartialEq derive to it and to StoredAssetLockInfo (one of its variants' inner type). All variants now compose cleanly. Test simplified back to assert_eq from variant-matching workaround. 3603 dpp lib tests pass, no regressions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ansition tests Uses crate::tests::fixtures::get_data_contract_fixture (already gated on feature = "fixtures-and-mocks", available in test mode) + the existing TryFromPlatformVersioned<DataContract> factory to build the DataContractInSerializationFormat. No new visibility changes needed. Bug surfaced: JSON round-trip loses sized integer types in document_schemas — U32(63) becomes U64(63), I32(0) becomes U64(0). This is a Critical-1 manifestation (platform_value preserves sized integers; serde_json::Value has only one Number type). Marked the JSON tests #[ignore] with the reason; value round-trip and $formatVersion tag tests pass cleanly. Plan §10b updated with the new bug entry. 183 conversion tests pass, 14 ignored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…asset_lock_proof_fixture Real proof + correctly-computed identity_id (via create_identifier()) makes round-trip identity. 3 ignored tests now pass. 188 conversion tests pass, 12 ignored (down from 14). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…fixture) 189 conversion tests pass, 12 ignored. All 22/22 batch sub-transitions tested. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pass 2 done. Outstanding items: ValidatorSet (BLS crypto), ExtendedDocument (known-broken serde), StateTransition umbrella (untagged ambiguity). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…lize bug Constructed ValidatorSet fixture using SecretKey<Bls12381G2Impl>::random().public_key() (matches the existing pattern in v0/mod.rs:331). New bug surfaced: BlsPublicKey<Bls12381G2Impl>::deserialize requires a **borrowed** string (&str), but both serde_json::Value and platform_value::Value produce owned strings when traversed. Round-trip fails with "invalid type: string ..., expected a borrowed string" on both paths. Affects every V0 field of type BlsPublicKey<Bls12381G2Impl>: - ValidatorV0.public_key - ValidatorSetV0.threshold_public_key Upstream fix lives in dashcore::blsful — Deserialize impl needs to accept owned strings via visit_string in addition to visit_borrowed_str. Both tests #[ignore]d with the bug detail; logged in plan §10b. 197 tests pass, 14 ignored (3 platform_value bugs documented). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…serde Replaces the broken manual Serialize/Deserialize impl on ExtendedDocument with a derived impl using `#[serde(tag = "$extendedFormatVersion")]`. The previous manual impl wrote `version` on serialize, read `$version` on deserialize, and required a `data_contract` field that serialize never emitted — so `from_value(to_value(&doc))` always failed. Why a separate `$extendedFormatVersion` key (not the inner Document's `$formatVersion`): ExtendedDocument is an envelope wrapping Document plus the full DataContract plus `$entropy`/`$metadata`/`$tokenPaymentInfo`. The envelope can evolve independently of the inner Document — bumping ExtendedDocument to V1 shouldn't force a Document V1 it doesn't otherwise need. Two version dimensions, two keys. Naive `tag = "$formatVersion"` was rejected: serde emits both the outer enum tag AND the flattened inner Document tag at the same JSON level — duplicate keys in one object → undefined JSON behavior, deserialize fails. `value_round_trip` is `#[ignore]` pending Critical-1: Bytes32::deserialize unconditionally requires a base64 string, but platform_value emits bytes when is_human_readable=false. JSON path round-trips clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`Bytes32::deserialize` was branching on `is_human_readable` and only accepting one input shape per branch — base64 string in HR mode, byte array in non-HR mode. That breaks for any internally-tagged enum (e.g. `#[serde(tag = "$version")]`) wrapping a struct with a Bytes32 field, because serde's `ContentDeserializer` always reports `is_human_readable: true` regardless of the parent deserializer. Concretely: ExtendedDocument's new `#[serde(tag = "$extendedFormatVersion")]` combined with `platform_value::to_value` (HR=false, emits bytes) made the ContentDeserializer hand a byte array to a StringVisitor expecting a base64 string → "invalid type: byte array" failure on the `$entropy` field. Fix: both visitors now accept strings AND bytes. Mirrors the existing pattern in `BinaryData` and `Identifier` (the comments in those files document the same ContentDeserializer quirk). The `value_round_trip` test on ExtendedDocument now passes; un-ignored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ged enums The two helper modules used by `#[json_safe_fields]` to serialize `[u8;N]` and `Vec<u8>` fields branched on `is_human_readable` to choose between a base64-string path and a byte-buffer path. That broke for any internally- tagged enum (e.g. `#[serde(tag = "$formatVersion")]`) wrapping a struct with such a field, because serde's `ContentDeserializer` always reports `is_human_readable: true` regardless of the parent deserializer. Concretely: when the outer enum is tagged and the source goes through `platform_value::to_value` (HR=false, emits `Value::Bytes`), the buffer becomes `Content::ByteBuf`. Then the helper's HR branch called `<String>::deserialize`, which dispatched to `visit_str` with the bytes interpreted as UTF-8 — and base64-decoding that produced the "Invalid symbol 17, offset 0" error on the very first byte. Fix: a single visitor that accepts strings, bytes, byte_buf, and seq in both branches; HR branch dispatches via `deserialize_any` so true HR deserializers (serde_json) hit `visit_str` and ContentDeserializer-wrapped bytes hit `visit_bytes` cleanly. Mirrors the dual-visitor pattern used by `Bytes32`, `BinaryData`, and `Identifier` in `rs-platform-value`. Bonus cleanup: `ExtendedBlockInfoV0::signature` had its own custom `signature_serializer` module that did the same thing as serde_bytes but with the same bug. Removed; the field now picks up `serde_bytes` via the auto-injection. Six previously-ignored tests now pass: - `ExtendedBlockInfo::value_round_trip_with_per_property_assertions` - `ShieldTransition::value_round_trip` - `UnshieldTransition::value_round_trip` - `ShieldedTransferTransition::value_round_trip` - `ShieldFromAssetLockTransition::value_round_trip` - `ShieldedWithdrawalTransition::value_round_trip` dpp lib: 3627 passing (+6), 14 ignored (-6). platform-value: 1035 passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…enums
`AssetLockProof` is `#[serde(tag = "type")]`, which routes deserialization
through serde's `ContentDeserializer`. That deserializer always reports
`is_human_readable: true` regardless of the upstream source. dashcore's
`OutPoint::deserialize` (and the `Txid` it contains, via `hash_newtype!`)
uses two completely disjoint visitors per `is_human_readable` branch:
HR-only `StringVisitor` for `"txid:vout"` strings, non-HR-only
`StructVisitor` for `{txid, vout}` maps. When platform_value serializes
the OutPoint as a struct (HR=false), the buffered Content::Map is then
replayed into the HR `StringVisitor` → `"invalid type: map, expected an
OutPoint"`. Same exact pattern surfaces one level deeper for `Txid`
("bad hex string length 32").
Local fix: `outpoint_serde` wrapper module on `ChainAssetLockProof::out_point`
with a unified visitor that accepts string + struct + seq, plus a
`TxidCompat` newtype handling the same bug for Txid. HR branch dispatches
via `deserialize_any` (handles true-HR + ContentDeserializer); non-HR
branch uses `deserialize_struct` / `deserialize_byte_buf` (bincode rejects
deserialize_any with `Serde(AnyNotSupported)`).
The wrapper carries a TODO marker pointing at the upstream dashcore fix.
The proper fix is to apply the unified-visitor pattern inside dashcore's
`serde_struct_human_string_impl!` macro itself, which would benefit every
`hash_newtype!`-generated type (OutPoint, Txid, BlockHash, …) in one
stroke. Once that lands and we bump dashcore, drop this wrapper and the
`serde(with = …)` annotation.
Two previously-ignored tests now pass:
- `ChainAssetLockProof::value_round_trip_with_per_property_assertions`
- `AddressFundingFromAssetLockTransition::value_round_trip_with_per_property_assertions`
dpp lib: 3629 passing (+2), 12 ignored (-2). platform-value: 1035 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Unification work for the canonical
JsonConvertible/ValueConvertibletraits acrosspackages/rs-dpp. Two passes:Pass 1 — added trait impls to ~80 domain types (commit
9f23d675af). All types now expose the canonical conversion surface.Pass 2 — added 197 round-trip tests using non-default fixtures + per-property assertions per the new test convention. Surfaced 3 platform_value bugs that were previously silent.
Test results
json_convertible_testsacross ~95 types.#[ignore]d — each documents either a real bug found or a structural issue (BLS keys, untagged-enum ambiguity, known-broken serde).Bugs surfaced (logged in
docs/json-value-unification-plan.md§10b)OutPointround-trip viaplatform_value::Value::Mapfails — affectsChainAssetLockProof,AddressFundingFromAssetLockTransition,ShieldFromAssetLockTransition. JSON works.[u8; N]fixed-array fields with custom serializers fail platform_value round-trip ("Invalid symbol 17, offset 0") — affectsExtendedBlockInfosignature and all 5 shielded transitions' Orchard fields. JSON works.DataContractdocument_schemaslose sized integer types via JSON round-trip (U32(63)→U64(63),I32(0)→U64(0)) — Critical-1 manifestation; affects every state transition embedding a DataContract. Value round-trip works.Convention added
Every J/V test follows
docs/json-value-unification-plan.md§8:to_json→from_json(and same for value).$formatVersionpreservation test where applicable.Fixtures source priority: hand-built struct literals >
random_*constructors >from_*factories >crate::tests::fixtures::*helpers >Default::default()only for unit-only enums.Code improvements
PartialEqonStateTransitionProofResult+StoredAssetLockInfo(was missing, blocked round-tripassert_eq).Out of scope (genuinely residual — needs upstream work)
ValidatorSet— needs real BLS public key construction (crypto setup).ExtendedDocument— Critical-3 known-broken serde (writesversion, reads$version).StateTransitionumbrella —serde(untagged), deserialize ambiguity (logged#[ignore]).Docs
docs/json-value-conversion-inventory.md— pre-pass-1 structural inventory.docs/json-value-unification-plan.md— full plan with phases, bug log, lessons learned, fixture conventions.Test plan
cargo test -p dpp --features=json-conversion,value-conversion,serde-conversionand sees 3619 pass / 0 fail.docs/json-value-unification-plan.md§10b for the 3 surfaced bugs and decides priority for follow-up fix.🤖 Generated with Claude Code