Skip to content

fix(platform-wallet): build data contract config at the protocol-required version#3881

Merged
QuantumExplorer merged 2 commits into
v3.1-devfrom
fix/data-contract-config-v1
Jun 13, 2026
Merged

fix(platform-wallet): build data contract config at the protocol-required version#3881
QuantumExplorer merged 2 commits into
v3.1-devfrom
fix/data-contract-config-v1

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Jun 13, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Creating ANY data contract through the SwiftExampleApp (and any other client routing through platform-wallet) is rejected on protocol v12 (testnet) with:

Protocol error: Can't update Data Contract config: config version 0 is not supported, minimum version is 1

This blocks all contract creation — both token and document contracts.

Root cause: packages/rs-platform-wallet/src/wallet/identity/network/contract.rs (create_data_contract_with_signer) hardcoded "$formatVersion": "0" when tagging the assembled DataContractConfig. Since protocol v12, DataContractConfig V0 is no longer accepted — the data-contract-create basic-structure validator (rs-drive-abci/.../data_contract_create/basic_structure/v1/mod.rs) enforces config().version() >= platform_version.dpp.contract_versions.config.min_version, and that minimum is 1 for v12 (see rs-platform-version/.../dpp_contract_versions/v3.rs and v4.rs: config.min_version = 1, "V0 config no longer accepted — lacks sized_integer_types"). So every contract the wallet built carried a V0 config and was rejected.

What was done?

  • In contract.rs, the assembled config is now tagged with the running PlatformVersion's dpp.contract_versions.config.default_current_version (which equals min_version on v12) instead of a hardcoded "0".
  • A config block is now always inserted — including when the caller passes no config_json — so the version tag is explicit rather than relying on the serialization format's #[serde(default = "DataContractConfigV1::default_with_version")].
  • A flags-only or empty config dict deserializes into a valid V1: DataContractConfigV1 is #[serde(rename_all = "camelCase", default)], so every field (including sized_integer_types: true) has a serde default. No extra field population is needed.
  • Non-object config_json now returns a clear InvalidIdentityData error instead of being silently dropped.
  • Removed the now-duplicate let platform_version = self.sdk.version(); binding (hoisted earlier for the version lookup).

Token schema left unchanged (investigated): QuickBasicTokenView.swift emits "$formatVersion": "0" for the token schema. TokenConfiguration only has a V0 variant and there is no token-config min-version gate on v12 (token_versions carries only validate_structure_interval), so V0 is still the only valid token-configuration version. Bumping it would break deserialization, so it was intentionally left alone.

How Has This Been Tested?

  • cargo check -p platform-wallet passes (compiles clean, including transitive dpp/drive/dash-sdk).
  • cargo fmt --all --check passes.
  • Full end-to-end iOS contract creation against a v12 testnet node is being verified separately by the orchestrator.

Breaking Changes

None. This is a client-side fix in the platform-wallet crate; it changes the config version the wallet emits to match what the network already requires. No consensus or wire-format change.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed contract creation to properly validate and apply protocol-specified configuration format versions.
  • Tests

    • Added validation tests for contract configuration handling.

…ired version

The wallet contract-create path hardcoded `"$formatVersion": "0"` when
tagging the assembled `DataContractConfig`. Since protocol v12 the
network's data-contract-create basic-structure validator enforces
`config().version() >= contract_versions.config.min_version`, and that
minimum is 1 for v12 (V0 config lacks `sized_integer_types`). Every
contract the wallet built therefore carried a V0 config and was rejected
with "config version 0 is not supported, minimum version is 1",
blocking all contract creation (document and token contracts) from
clients such as the SwiftExampleApp.

Now the config is tagged with the running `PlatformVersion`'s
`contract_versions.config.default_current_version` (which equals
`min_version` on v12), and a config block is always inserted — including
when the caller supplies no config — so the version tag is explicit
rather than relying on the serialization format's serde default. A
flags-only or empty config dict deserializes into a valid V1 because
every `DataContractConfigV1` field carries a serde default
(`sized_integer_types: true` included), so no extra field population is
required.

The token-schema `"$formatVersion": "0"` emitted by QuickBasicTokenView
is left unchanged: `TokenConfiguration` only has a V0 variant and there
is no token-config min-version gate on v12, so V0 is still the only
valid token-configuration version.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 95384dd7-44ff-452e-9b97-09506bf3a4f9

📥 Commits

Reviewing files that changed from the base of the PR and between 65042eb and 4c31f24.

📒 Files selected for processing (1)
  • packages/rs-platform-wallet/src/wallet/identity/network/contract.rs

📝 Walkthrough

Walkthrough

This PR updates contract creation to use protocol-defined config format versions instead of hardcoded defaults. The change derives the version from the running PlatformVersion, adds a helper function to validate and assemble the config object, and ensures the config block is always included in the contract serialization with the correct version tag.

Changes

Data Contract Config Format Version

Layer / File(s) Summary
Documentation and Version Setup
packages/rs-platform-wallet/src/wallet/identity/network/contract.rs
config_json documentation clarified to indicate protocol-required format versions; platform_version and config_format_version computed during contract creation from the running PlatformVersion.
Config Assembly Helper and Validation Tests
packages/rs-platform-wallet/src/wallet/identity/network/contract.rs
New build_config_object helper enforces JSON object shape, merges caller-supplied fields, and unconditionally sets $formatVersion to protocol version. Tests validate absent/empty config handling, field preservation, version-tag overwrite, non-object rejection, and DataContract validation at latest protocol version.
Main Function Integration and Cleanup
packages/rs-platform-wallet/src/wallet/identity/network/contract.rs
Contract assembly now always builds config via build_config_object, replacing prior conditional insertion and hardcoded V0 tags. Redundant platform_version binding removed.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

ready for final review

Suggested reviewers

  • shumkov
  • lklimek
  • llbartekll
  • ZocoLini

Poem

🐰 A rabbit hops through version streams,
Where configs now align with dreams—
Protocol tags, no hardcoded sigh,
Each contract crafted right on high!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: updating data contract config building to use the protocol-required version instead of a hardcoded value. The title directly captures the fix's core objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/data-contract-config-v1

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@thepastaclaw

thepastaclaw commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

✅ Review complete (commit 4c31f24)

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Small, focused fix that correctly tags the assembled DataContractConfig with the running PlatformVersion's contract_versions.config.default_current_version, resolving the v12 testnet rejection where the basic-structure validator enforces config().version() >= contract_versions.config.min_version. Implementation is sound; no blocking issues. Two suggestions: add a regression test pinning the version-tag selection, and decide explicitly how to handle a caller-supplied $formatVersion (currently silently overridden).

🟡 1 suggestion(s) | 💬 1 nitpick(s)

🤖 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-platform-wallet/src/wallet/identity/network/contract.rs`:
- [SUGGESTION] packages/rs-platform-wallet/src/wallet/identity/network/contract.rs:258-271: Add a regression test pinning the config $formatVersion to the protocol's required version
  This PR fixes a real consensus-rejection bug (v12 rejects V0 DataContractConfig because basic-structure validation requires config().version() >= contract_versions.config.min_version). The whole correctness boundary of the change lives in this JSON assembly block, but there's no test in rs-platform-wallet that exercises it, so a future refactor could silently reintroduce the hardcoded "0" or omit the config block without failing CI.

  Extracting the config-Map assembly into a small pure helper, e.g. `fn build_config_object(config_json: Option<&str>, version: u16) -> Result<serde_json::Map<String, serde_json::Value>, PlatformWalletError>`, makes this testable without the SDK runtime / signer / wallet_manager dependencies. Three cases are sufficient to lock the fix down:
    - `None` → resulting object has `$formatVersion == default_current_version.to_string()` for the current PlatformVersion (1 on v12).
    - `Some(flags-only object)` → caller fields preserved, version tag set to the protocol default.
    - `Some(non-object)` (array/string/number) → returns `PlatformWalletError::InvalidIdentityData`.

  Ideally a fourth case round-trips through `DataContractInSerializationFormat` + `DataContract::try_from_platform_versioned` at the latest PlatformVersion to assert the assembled bytes actually validate. The PR description's own test checklist is unchecked — given this is a fix for a v12 consensus rejection that blocked all contract creation, a guard test here is well worth the few lines.

Comment thread packages/rs-platform-wallet/src/wallet/identity/network/contract.rs Outdated
Comment thread packages/rs-platform-wallet/src/wallet/identity/network/contract.rs Outdated
@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.20%. Comparing base (65042eb) to head (4c31f24).
⚠️ Report is 8 commits behind head on v3.1-dev.

Additional details and impacted files
@@              Coverage Diff              @@
##           v3.1-dev    #3881       +/-   ##
=============================================
- Coverage     87.22%   71.20%   -16.03%     
=============================================
  Files          2641       20     -2621     
  Lines        328569     2837   -325732     
=============================================
- Hits         286597     2020   -284577     
+ Misses        41972      817    -41155     
Components Coverage Δ
dpp ∅ <ø> (∅)
drive ∅ <ø> (∅)
drive-abci ∅ <ø> (∅)
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value ∅ <ø> (∅)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier ∅ <ø> (∅)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

✅ DashSDKFFI.xcframework built for this PR.

SwiftPM (host the zip at a stable URL, then use):

.binaryTarget(
  name: "DashSDKFFI",
  url: "https://your.cdn.example/DashSDKFFI.xcframework.zip",
  checksum: "bd4a9a9822c33e2d6d494f8bdd30db3d87cd27471a461e62a7ce5b58254f43ff"
)

Xcode manual integration:

  • Download 'DashSDKFFI.xcframework' artifact from the run link above.
  • Drag it into your app target (Frameworks, Libraries & Embedded Content) and set Embed & Sign.
  • If using the Swift wrapper package, point its binaryTarget to the xcframework location or add the package and place the xcframework at the expected path.

@QuantumExplorer

Copy link
Copy Markdown
Member Author

QA follow-up (testnet, found while exercising this fix end-to-end): this change is necessary but not sufficient on its own.

It reads the config version from self.sdk.version(). On the SwiftExampleApp the wallet's SDK does not ratchet to the network's protocol version — it sits at a stale low seed (≤ v8, where CONTRACT_VERSIONS_V1 makes config.default_current_version = 0), so this fix still injects a V0 config and contract creation is still rejected with "config version 0 is not supported, minimum version is 1".

I tried refining both the create and update paths to build the config at the client's compiled dpp::version::PlatformVersion::latest() (= v12 → V1) instead of self.sdk.version(). That builds a V1 DataContract object locally, but the broadcast put_to_platform_and_wait_for_response(&self.sdk, …) serializes against the stale SDK version, so a V0 config still goes on the wire and is rejected.

Conclusion: the real fix is making the wallet's Sdk ratchet its protocol_version to the network's actual version (v12) — the same stale-SDK-version root cause that blocks shielded identity-create-from-pool (empty denomination set) and originally caused the shielded fee skew (#3877). This PR should still land (correct direction), but contract creation from the app won't actually work until the SDK protocol-version ratchet is fixed. Suggest building config at latest() here regardless, and tracking the SDK-ratchet fix as the blocking dependency.

…h a regression test

Extract the config-Map assembly from `create_data_contract_with_signer`
into a pure, unit-testable `build_config_object(config_json, version)`
helper, and add a `#[cfg(test)] mod tests` covering:
  - None / empty input -> `$formatVersion == default_current_version`
  - flags-only object -> caller fields preserved + version tag set
  - caller-supplied `$formatVersion` -> overwritten with the protocol
    version (deliberate; the helper always emits a network-acceptable tag)
  - non-object JSON (array/string/number) -> InvalidIdentityData
  - round-trip: helper output validates as a DataContract at the latest
    PlatformVersion (the assertion that would have caught the original
    hardcoded-"0" v12 consensus rejection at unit-test time)

Behavior of the create path is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@QuantumExplorer QuantumExplorer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed

@QuantumExplorer QuantumExplorer merged commit e93b0d2 into v3.1-dev Jun 13, 2026
17 of 18 checks passed
@QuantumExplorer QuantumExplorer deleted the fix/data-contract-config-v1 branch June 13, 2026 19:50

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Carried-forward prior findings: prior-1 and prior-2 are both FIXED in the latest delta. New findings in the latest delta: one nitpick — the public Swift docstring for createDataContract still claims the library injects $formatVersion: "0" if missing, which now contradicts the Rust helper's documented unconditional-overwrite-with-protocol-required-version behavior introduced by this PR. The Rust fix itself looks correct, well-scoped, dependency-direction clean, and the new tests (build_config_object_* plus the latest-version DataContract::try_from_platform_versioned round-trip) would have caught the original v12 rejection bug.

💬 1 nitpick(s)

1 additional finding(s) omitted (not in diff).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants