Skip to content

feat(sdk): propertyConstraints rules and pre-check in the Swift SDK and iOS example app - #5064

Merged
QuantumExplorer merged 2 commits into
v4.2-devfrom
claude/property-constraints-swift
Sep 27, 2026
Merged

QuantumExplorer merged 2 commits into
v4.2-devfrom
claude/property-constraints-swift

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 27, 2026 •

Copy link
Copy Markdown
Member

Basic explanation

What this does: The Swift SDK and the iOS example app can now list a document type's propertyConstraints rules and check a new document against them before sending it. Two new C functions in rs-sdk-ffi do all the work in Rust, with the same check consensus runs; Swift only passes the contract bytes, the properties and the owner in, and decodes the JSON that comes back.

Value: The example app shows each rule on the document type screen, and refuses to broadcast a document that breaks one, naming the rule and the reason. Without this, the user finds out only after paying for a transition that consensus refuses with error 10422. The same two C functions will back the Kotlin SDK next, so every SDK reports rules and violations in the same shape as the JS SDK (#5051).

Risks: Low. Everything is additive except one small refactor: dash_sdk_document_create now shares its properties parsing and document building with the pre-check through two helpers, with the same steps and the same error messages. Nothing changes in consensus, Drive or DPP. The rules are read at the SDK's current protocol version, so an SDK that has not yet learned the network is at 14 reports no rules: the pre-check can then miss a broken rule (consensus still refuses it), but it never blocks a valid document.

Issue being fixed or feature implemented

The propertyConstraints series (#4962, then #5036 through #5048) gave consensus a rich rule language, but the Swift SDK only surfaced the 10422 error after the fact. This is the second of three SDK PRs:

  1. JS/WASM: #5051.
  2. Swift SDK and iOS example app: this PR.
  3. Kotlin SDK and Android example app: follows, as a JNI shim over the two C functions added here.

What was done?

rs-sdk-ffi: two C functions (src/data_contract/property_constraints.rs)

// The rules of a document type, as a JSON array (in name order)
struct DashSDKResult dash_sdk_data_contract_get_property_constraints(
    const struct SDKHandle *sdk_handle,
    const uint8_t *serialized_contract, uintptr_t serialized_contract_len,
    const char *document_type);

// The first rule a document to create breaks, as JSON, or JSON `null`
struct DashSDKResult dash_sdk_data_contract_check_property_constraints(
    const struct SDKHandle *sdk_handle,
    const uint8_t *serialized_contract, uintptr_t serialized_contract_len,
    const char *document_type,
    const char *properties_json,   // as dash_sdk_document_create takes it
    const uint8_t *owner_id);      // 32 bytes, what $ownerId reads

Both return a DashSDKResult whose data is a C string (free with dash_sdk_string_free), or an error (free with dash_sdk_error_free): NotFound for an unknown document type, SerializationError for bytes that are not a contract, InvalidParameter for null or empty arguments and for properties that are not a JSON object. The JSON matches #5051 key for key:

[
  { "name": "perUnitFee",
    "rule": { "greaterThanOrEqual": [{ "divide": ["price", "fee"] }, 1] },
    "reads": [{ "path": "price", "kind": "value" }, { "path": "fee", "kind": "value" }],
    "readsOwner": false },
  { "name": "sellerIsOwner",
    "rule": { "anyOf": [{ "absent": "sellerId" }, { "equal": ["sellerId", "$ownerId"] }] },
    "reads": [{ "path": "sellerId", "kind": "presence" }, { "path": "sellerId", "kind": "identifier" }],
    "readsOwner": true }
]
{ "rule": "perUnitFee", "violation": "DivisionByZero", "message": "it divides by zero" }
  • The rules: names, reads and readsOwner come from DPP's parsed rules (property_constraints(), property_reads(), reads_owner()); rule is the declaration as the document type's schema holds it.
  • The check: the properties JSON goes through the create path's own steps (the parsing, sanitize_document_properties and create_document_from_data of dash_sdk_document_create, now shared as parse_document_properties_json and build_document_from_properties), so integers, identifiers and nested identifiers are typed as they would be sent. The document is then judged by DPP's validate_property_constraints, the method consensus calls on a create: every rule in name order through PropertyConstraint::violation, with the owner for $ownerId. No rule logic lives in the FFI or in Swift. Only the rules are checked, not the JSON schema.
  • Why the contract arrives as bytes: the app does not hold a contract handle in the create flow (it goes through platform_wallet_create_document_with_signer), and the only handle constructors fetch from the network. Both example apps already persist the contract's platform serialization next to its JSON (PersistentDataContract.binarySerialization on iOS), and the crate already reads those bytes elsewhere (dash_sdk_add_known_contracts, the token transitions). The functions read them at the SDK's protocol version at call time. Looking the contract up in the trusted context provider instead would reuse a parse made at SDK start: the app loads its known contracts while the protocol version refresh is still in flight, so on testnet they are parsed at the version 13 floor and would show no rules.

Swift SDK

  • Core/Utils/DocumentPropertyConstraints.swift: DocumentPropertyConstraint (name, ruleJSON kept as the declared JSON, prettyRuleJSON, reads, readsOwner), PropertyConstraintRead (path, kind), PropertyConstraintViolation (rule, violation, message, a LocalizedError), and two thin wrappers on SDK: documentPropertyConstraints(serializedContract:documentType:) and checkDocumentPropertyConstraints(serializedContract:documentType:propertiesJSON:ownerId:). A read kind or violation name a later protocol version adds decodes as .other(name) instead of failing.
  • PersistentDocumentType: propertyConstraints(using:) and propertyConstraintViolation(propertiesJSON:ownerId:using:) pass the parent contract's stored binarySerialization, and declaresPropertyConstraints reads the keyword off the persisted schema. Nothing new is stored, so no entity hash or schema version moves.
  • SDK.processStringResult is now internal (was private) so the new wrappers reuse it.

SwiftExampleApp

Document type details. Before: the screen listed settings, indices and properties; a type's rules were visible only by reading the raw contract JSON. After: a "Property Constraints (N)" section lists each rule:

Property Constraints (2)
  perUnitFee
    {
      "greaterThanOrEqual" : [ { "divide" : [ "price", "fee" ] }, 1 ]
    }
    Reads: price (value), fee (value)

  sellerIsOwner                                        [$ownerId]
    {
      "anyOf" : [ { "absent" : "sellerId" }, { "equal" : [ "sellerId", "$ownerId" ] } ]
    }
    Reads: sellerId (presence), sellerId (identifier)
    Reads $ownerId, the document's owner: transfers and purchases are judged against this rule too.
  Every created or replaced document must meet each rule, checked in name order. A document
  breaking one is refused (error 10422) and the fee is still charged.

The section reloads when the app learns the network's protocol version; a schema declaring rules that the current version does not enforce says so instead.

Create document. Before: an offer with {"price": 100, "fee": 0} was broadcast, and consensus refused it with DocumentPropertyConstraintViolatedError (10422) after charging the fee. After: tapping "Create / Broadcast" runs the pre-check first and nothing is sent:

Not sent: a property constraint is broken
Rule: perUnitFee
Violation: DivisionByZero
Reason: it divides by zero

A document meeting every rule is broadcast as before. When the pre-check cannot run (no SDK, no stored contract bytes), it logs and lets the create proceed, since consensus judges the document either way.

How Has This Been Tested?

  • rs-sdk-ffi, 9 new tests in data_contract/property_constraints.rs, calling the C functions end to end on a contract whose offer type declares five rules (a string const with present, an integer comparison, a divide, $ownerId with absent, and in) beside a plain type declaring none:
    • rules listed in name order with the declared rule, reads and readsOwner; [] for a type with none; NotFound for an unknown type;
    • the check: null for a valid document (an identifier sent as base58 is typed as the create path types it, so it equals the owner); the first broken rule in name order (NotMet for the string, integer and in rules, DivisionByZero for fee: 0); $ownerId read from the owner passed in;
    • an SDK at protocol version 13 reports no rules and no violation;
    • bytes that are not a contract, properties that are not JSON or not an object, and null or empty arguments are refused with their codes.
  • cargo test -p rs-sdk-ffi --lib: 340 passed, 1 ignored (the new 9 included; the existing dash_sdk_document_create tests cover the shared parsing and its messages).
  • cargo clippy -p rs-sdk-ffi --all-targets -- -D warnings: clean. The two functions appear in the generated rs-sdk-ffi.h and in the xcframework headers.
  • Swift, new SwiftTests/SwiftDashSDKTests/DocumentPropertyConstraintsTests.swift (16 tests): decoding of the rules and violations (order, reads, readsOwner, the rule kept as declared JSON, pretty printing, unknown names kept as .other, malformed JSON refused), round trips of both wrappers through the FFI on the mock SDK with a protocol version 13 contract fixture, error codes kept (notFound, serializationError, invalidParameter), the 32-byte owner guard, and the PersistentDocumentType helpers reading the stored contract bytes (or refusing without them).
  • ./build_ios.sh --target sim (release; includes the SwiftExampleApp build with warnings as errors): succeeded.
  • xcodebuild -project SwiftExampleApp/SwiftExampleApp.xcodeproj -scheme SwiftExampleApp -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 17,arch=arm64' -quiet clean build: succeeded (iPhone 17, as no iPhone 16 simulator is installed here).
  • SKIP_EXAMPLE_APP_BUILD=1 ./build_ios.sh --target tests (adds the macOS slice), then swift test --filter DocumentPropertyConstraintsTests: 16 passed; swift test --filter "DataContractParser|DocumentTypeImmutabilityTests|DocumentTypedArray|SDKMethodTests|DashModelMigrationTests|DashReleasedSchemaTests": 111 passed, 3 skipped.
  • Not run: the app against a live protocol version 14 network (the rules section and the refusal alert were not exercised on a simulator), and the SwiftExampleApp XCTest targets.

Breaking Changes

None. New C functions and Swift API; dash_sdk_document_create keeps its behaviour and error messages.

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
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · a5fac13

  • Bots — coderabbitai ✓ · thepastaclaw not yet — /skip-bots proceeds without the ones not yet reported
  • Self-review — post /self-reviewed once the bots are done
  • Within your 5 open PRs — this one is beyond the limit; it waits until one merges
  • Build running
  • Approvals
    • rust-sdk-ffi (packages/rs-sdk-ffi/src/data_contract/mod.rs, packages/rs-sdk-ffi/src/data_contract/property_constraints.rs, packages/rs-sdk-ffi/src/document/create.rs and 2 more) — lklimek or shumkov
    • swift-sdk (packages/swift-sdk/Sources/SwiftDashSDK/Core/Utils/DocumentPropertyConstraints.swift, packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentDocumentType.swift, packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentTypeDetailsView.swift and 2 more) — llbartekll or romchornyi

When every box is checked the PR Hygiene check passes and this can merge.

Summary by CodeRabbit

  • New Features
    • View property-constraint rules for document types, including the properties they inspect and whether they depend on the owner ID.
    • Check proposed document properties against applicable rules before submission. When a violation is found, submission is stopped and the rule and reason are shown.
    • See a notice when a document type declares rules that the current protocol version does not enforce.
    • SDK methods are available to retrieve document-type rules and check properties for violations.

…nd iOS example app

rs-sdk-ffi gains dash_sdk_data_contract_get_property_constraints (a
document type's rules as a JSON array, in name order, with what each
reads and whether it reads $ownerId) and
dash_sdk_data_contract_check_property_constraints (the first rule a
document to create breaks, or JSON null). Both take the contract as its
platform serialization, read at the SDK's protocol version, and match
wasm-dpp2's JSON shapes. The check builds the document the way
dash_sdk_document_create does (its parsing and building now shared as
two helpers) and judges it with DPP's validate_property_constraints.

The Swift SDK decodes both into DocumentPropertyConstraint and
PropertyConstraintViolation behind thin SDK wrappers, with
PersistentDocumentType helpers reading the stored contract bytes. The
example app lists the rules on the document type screen and refuses to
broadcast a document that breaks one, naming the rule and the reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: dashpay/platform/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: b63f6feb-61d8-48f7-99e2-255d51917569

📥 Commits

Reviewing files that changed from the base of the PR and between 4d4f38d and a5fac13.

📒 Files selected for processing (10)
  • packages/rs-sdk-ffi/src/data_contract/mod.rs
  • packages/rs-sdk-ffi/src/data_contract/property_constraints.rs
  • packages/rs-sdk-ffi/src/document/create.rs
  • packages/rs-sdk-ffi/src/document/helpers.rs
  • packages/rs-sdk-ffi/src/document/mod.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/Core/Utils/DocumentPropertyConstraints.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentDocumentType.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentTypeDetailsView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift
  • packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DocumentPropertyConstraintsTests.swift

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Adds protocol-version-aware property-constraint retrieval and checking to the Rust FFI and Swift SDK. Persisted document types expose the operations. The example app displays declared constraints and checks proposed document properties before broadcasting.

Changes

Document property constraints

Layer / File(s) Summary
FFI constraint operations and document construction
packages/rs-sdk-ffi/src/data_contract/*, packages/rs-sdk-ffi/src/document/*
Adds FFI functions to retrieve constraint rules and check proposed document properties. Shares document-property parsing and construction helpers. Tests cover rule metadata, violations, protocol versions, and invalid inputs.
Swift constraint models and SDK methods
packages/swift-sdk/Sources/SwiftDashSDK/Core/Utils/DocumentPropertyConstraints.swift, packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DocumentPropertyConstraintsTests.swift
Adds Swift types for rules, property reads, and violations, with SDK methods to retrieve and check constraints. Tests cover decoding, FFI calls, and error handling.
Persisted document types and example app checks
packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentDocumentType.swift, packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentTypeDetailsView.swift, packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift, packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DocumentPropertyConstraintsTests.swift
Adds constraint access through persisted document types. The app displays rules and checks proposed documents before broadcasting. Tests cover persisted access and missing contract serialization.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant CreateDocumentView
  participant PersistentDocumentType
  participant SDK
  participant RustFFI
  participant DocumentBroadcaster
  CreateDocumentView->>PersistentDocumentType: check proposed properties and owner ID
  PersistentDocumentType->>SDK: checkDocumentPropertyConstraints
  SDK->>RustFFI: dash_sdk_data_contract_check_property_constraints
  RustFFI-->>SDK: violation or JSON null
  SDK-->>PersistentDocumentType: decoded violation or nil
  PersistentDocumentType-->>CreateDocumentView: violation or nil
  CreateDocumentView->>DocumentBroadcaster: broadcast when no violation blocks submission
Loading

Suggested reviewers: lklimek, llbartekll

Merge Risk: ⚪ Minimal · up to a5fac

No confirmed issue prevents merging after normal checks.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to a5fac

The pre-check can miss a rule when contract data or the protocol version is stale, or when the check fails. A document may then be sent and rejected at a cost. Network validation still decides whether the document is accepted.

Retained concerns

  • Medium · reliability · observed: The new pre-broadcast gate treats an unavailable SDK, absent cached rule declaration, or check error as no violation. This limits its ability to prevent a paid, rejected submission; it does not bypass consensus.
Security review details

Security Blast Radius

  • inferred — A misleading result affects client-side submission decisions for the document being created. The inspected check has no write path, and network validation remains the authority for accepting a broadcast.

Trust Boundaries and Controls

  • observed — Persisted contract bytes and the selected owner cross into the native checker as inputs to an advisory decision. The Swift wrapper checks owner length and holds buffers for the call; Rust parses the contract as untrusted data. Neither step proves that those inputs match the eventual broadcast's contract and signer.

Resilience and Maintainability Implications

  • inferred — The pre-check must not be treated as a security authorization decision: its deliberate fail-open behavior preserves the ability to submit, while authoritative validation remains downstream.

Hardening Proposals

  • proposed — If preventing paid invalid submissions is a firm app guarantee, distinguish “check unavailable” from “no violation” and verify that the checked contract and protocol version match those used for creation before relying on the result.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.62% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 65 functions across 10 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main changes: property-constraint rules and document pre-checking in the Swift SDK and iOS example app.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

@github-actions github-actions Bot added this to the v4.2.0 milestone Sep 27, 2026
@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Sep 27, 2026
@thepastaclaw

thepastaclaw commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

🔍 Review in progress — actively reviewing now (commit a5fac13) · triage: normal

The workspace build compiles dpp's JSON schema validation in, and the
protocol version 13 meta-schema refuses the propertyConstraints keyword,
so the fixture could not be created at 13. The test now reads the bytes a
network at 14 returns with an SDK still at 13, the case it is meant to
cover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer merged commit a5a1af5 into v4.2-dev Sep 27, 2026
34 of 36 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/property-constraints-swift branch September 27, 2026 16:03
QuantumExplorer added a commit that referenced this pull request Sep 27, 2026
#5064 merged as a5a1af5. The only conflict was the add/add of
rs-sdk-ffi's property_constraints.rs, resolved to v4.2-dev (the squash
tree equals the final #5064 head a5fac13, including the pre-v14
fixture fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-bots Waiting for the review bots to report on this head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants