Smart contracts on Platform: staged design and implementation plan
This document proposes how Dash Platform should add smart contracts: Rust contracts compiled to WebAssembly, executed by an unmodified and pinned wasmi interpreter, with a host layer and guest SDK owned by Dash, delivered in protocol-version-gated stages. Contract state stays document-shaped so proofs, indexes, fees, and refunds apply unchanged, and user code proposes operations that the existing Drive pipeline validates and applies. It records the options that were evaluated and why they were rejected or deferred, the target architecture, six delivery phases with task checklists, the cross-cutting engineering disciplines (determinism, security, fees), the developer-experience plan, and the open questions reviewers are asked to decide. Every checkbox is intended to be one reviewable unit of work.
Status: proposal for review. No DIP, issue, or code existed before this document. Opened 2026-09-08.
This is a proposal, not an approved roadmap, so it is one issue rather than a set of tracking issues. The task lists are here to be argued with: they show the shape and size of each phase. If the plan is accepted, the phases become their own tracking issues carrying these checkboxes, and this issue becomes the rationale they link back to.
How to review: the argument runs in order, so sections 1 to 4 carry the reasoning and the rejected alternatives, sections 5 to 10 are the phases, and sections 11 to 14 are the cross-cutting disciplines and delivery. Please quote the section and heading you are addressing, since the document spans several comments. Section 14 lists the open questions that need a decision before Phase 0 can close.
Contents
- Summary, goals, non-goals, and design principles
- Current state of Platform: what exists, what is missing
- Options considered and why they were rejected or deferred
- Target architecture
- Phase 0: decisions, DIP, and groundwork
- Phase 1: VM-independent infrastructure
- Phase 2: bounded expression guards
- Phase 3: WASM validation predicates
- Phase 4: declared-footprint reads
- Phase 5: constrained entry points
- Cross-cutting: determinism, security, and engine governance
- Cross-cutting: fees and economics
- Developer experience, SDK, and tooling plan
- Delivery: sequencing, testing, rollout, success criteria, risks, open questions
1. Summary, goals, non-goals, and design principles
Path prefixes as in section 02: abci/ is packages/rs-drive-abci/src, pv/ is packages/rs-platform-version/src/version.
1.1 What is proposed
User-authored contract logic on Dash Platform as Rust compiled to WebAssembly, executed inside Drive on an exactly pinned wasmi 2.0-line interpreter (MSRV 1.86 against the workspace's Rust 1.92, rust-toolchain.toml), behind a host layer and guest SDK that Dash writes and owns. The pin is a criterion, not a version: the earliest 2.0.x that passes section 3.11's differential fuel and trap corpus and has run for a Phase 0-decided number of months under section 13's native harness and fuel corpus before the PV17 PR takes the dependency. wasmi 2.0.0 shipped on 2026-09-01 on a rewritten IR built around three new accumulator registers, with no patch release yet (crates.io, 2026-09-07), and its 1.0 line needed a limit-check fix (1.0.4), an execution fix (1.0.5, rem_s(MAX, -1)) and translation fixes in 1.0.6, 1.0.7, 1.0.8 and 1.0.9, all inside ten weeks of the 1.0 release (1.0.0 on 2025-12-03, 1.0.9 on 2026-02-09; wasmi CHANGELOG), so the bake criterion is an open Phase 0 decision. v0 meters with the engine's operator_cost table; install-time instrumentation (the Internet Computer's model) is the engine-independent fallback if the corpus shows patch-level drift. wasmi has one maintainer and its sponsorship ends in October 2026 (external-engines facts): Dash is maintainer of last resort for a fork if that maintainer leaves, and fork-and-freeze versus co-sponsorship (section 14, question 4) is a Phase 0 decision.
Delivery is staged, one masternode-voted protocol version per stage: VM-independent infrastructure (fuel priced into processing fees, so gas_wanted (abci/abci/handler/check_tx.rs:66-69) carries each transition's full fuel bound to Tenderdash under the genesis block.max_gas (packages/dashmate/configs/defaults/getBaseConfigFactory.js:436), a mempool parameter no Drive code enforces, so Drive adds the consensus-side budget of goal 4; a code-blob channel), with native primitives and oracle feeds as Phase 1b, a value-moving track that no VM phase waits for; then statically costed expression guards, WASM validation predicates on document types, declared-footprint reads, and finally constrained entry points returning a bounded list of proposed document, token and credit operations.
Contract state stays document-shaped under the contract's documents subtree, so proofs, indexes and the end-of-block conservation check apply unchanged. Fee mechanics are unchanged; attribution for entry-point writes is decided in Phase 0 beside D1 and D3 (section 05), default: the signer is balance-checked and pays; each written element carries in its StorageFlags the acting owner the operation names (caller or principal), who gets the refund (refunds fold per identifier, packages/rs-drive/src/fees/op.rs:275-300); entry points touch only documents the caller or the principal owns (deletes are owner-checked today, document_delete_transition_action/state_v0/mod.rs:97-103). Code blobs are immutable, hash-addressed and reference-counted, deletable with refund only at zero references (section 06.2); module bytes pay the disk rate plus a fixed module fee in a new FeeDataContractRegistrationVersion generation (section 12); a contract is permanent unless its config sets can_be_deleted (packages/rs-dpp/src/data_contract/config/v1/mod.rs:25), so a referenced blob's storage is permanent in practice.
Guest code never writes GroveDB: it accepts, rejects or proposes; the pipeline prices under estimated >= actual (goal 4) and applies (principle 2). Authorization is new consensus surface (a Phase 5 task, section 10): a proposed operation may act on assets the caller owns (the caller signed) or on assets the principal owns, the keyless identity subtype a contract holds (section 04.4), authorized solely by that contract's entry point with no key path; principal balances sit in the identity balance tree, so process_block_fees_and_validate_sum_trees (process_block_fees_and_validate_sum_trees/mod.rs:38) covers them.
README.md:49-50 says smart contract support is "planned for Platform v4.0 (targeted for mainnet in 2027)" and the comparison table's smart-contract cell reads "Coming in v4.0" (README.md:63); dash.org lists a VM milestone at v5.0, Q1 2027. The repository has no design, issue or VM dependency behind either. The latest protocol version in tree is 14 (pv/mod.rs:36, unreleased); this plan targets 15 and later (calendar in 1.6).
1.2 Status of this document
A proposal for review, not a decision record: no DIP, code or protocol version exists for this work. After the open questions in section 14 are settled, the consensus design moves to a DIP in dashpay/dips (the platform repository does not accept committed design documents, CLAUDE.md:96-100) and the task lists here become issues and PRs. Repository facts were verified at v4.2-dev commit a3c36f3 on 2026-09-07; external engine facts are dated where they appear. The research synthesis named wasmi 1.0; wasmi 2.0.0 has since shipped, and where they differ this document governs (section 08).
1.3 Goals
- Let third parties attach network-enforced logic to their own data contracts without a Dash-shipped protocol change per application; today custom rules are Rust data triggers behind a fn-pointer type (batch/data_triggers/mod.rs:26-30), dispatched from a hard-coded per-protocol-version binding list only Dash can extend (batch/data_triggers/bindings/list/mod.rs:11). 1.6 maps which phase delivers which part.
- Keep all contract-visible state provable to light clients through the existing query and proof machinery, the property the README comparison table sells (README.md:52-63).
- Execute identically on every node and architecture, including identical error classification (principle 6): user code must never surface as
InternalError (validators reject the proposal, abci/abci/handler/process_proposal.rs:285-307) or as a block-event Err (node halt).
- Bound and price all execution under
estimated >= actual, the invariant the code already enforces: the affordability dry run prices with apply_drive_operations(apply = false), execution charges the actual metered cost, execute_event/v0/mod.rs:186-206 records the 2026-08-14/15 mainnet stalls as estimated < actual, and an uncoverable charge is Err (apply_balance_change_from_fee_to_identity/v0/mod.rs:92-100). Fuel is unknowable before a run, so (a) every hook-bearing transition has a fuel bound fixed before execution, the SystemLimits per-call cap for predicates and a caller-declared max_fuel at or below that cap for calls, priced with the instantiation, translation, memory and context terms into one figure: B at PV17 and PV18 (8.2.6), max_fee at PV19 (10.2.4). (b) That bound is checked against the payer before the engine is entered, not after validation: with the signer's PartialIdentity already loaded, the seam requires live balance minus the processing accrued so far in this transition to cover the bound plus the nonce-bump cost, both sides after apply_user_fee_increase, and otherwise returns InsufficientBalanceForHookExecution, paid at the affordable minimum with the nonce bumped and no instantiation (8.2.6, 11.2.3). No literal debit and credit-back. Checking after validation is too late: an unaffordable event executes as UnpaidConsensusExecutionError, no charge and no bump, and check_tx prices each transition against the last committed balance, so one balance admits N sequential-nonce transitions of which N-1 would run to the bound for free. (c) consume_fuel traps at the bound and the charge never exceeds what was priced: out-of-fuel pays the whole bound it consumed, a trap pays fuel to the trap plus the base, so failed executions are paid. (d) A declared bound above the cap is a structural error at check_tx and recheck. The block budget, max_block_processing_credits (new SystemLimits field, section 06.1), is enforced twice: the declared bounds of a proposal's hook-bearing transitions, summed from the decoded transitions at the fuel rate, reject an over-budget proposal before any hook runs; 06.1's running cutoff on accrued processing credits then marks later transitions NotExecuted under a new NotExecutedReason value beside ProposerRanOutOfTime (abci/platform_types/state_transitions_processing_result/mod.rs:13-16), which validators reject. No execution-result variant is added, so the exhaustive rollback match's NotExecuted(_) arm already covers it (process_raw_state_transitions/v0/mod.rs:252-262); the new reason's TxAction classification in prepare_proposal must agree with that arm. The per-transition cap is strictly below the budget, so every transition is includable; failed executions count, since they run and pay. block.max_gas and the budget are calibrated together (section 12) against the proposer-only 5000 ms proposer_tx_processing_time_limit (process_raw_state_transitions/v0/mod.rs:149-158; packages/rs-drive-abci/.env.mainnet:92; validators pass none, process_proposal.rs:235-241).
- Ship stages that are each independently useful, PV-gated, and reused by the next, so nothing is throwaway if the plan stops early.
- Give authors a Rust-native loop (guest SDK, native harness under
cargo test, simulator; section 13) as a funded precondition: NEAR and Soroban ran SDK teams for years and ink! halted in January 2026 when funding failed (report 07), so Phase 0 records the SDK and developer-tooling commitment (owner, engineer count, funded duration) as a row of its own in section 05's register beside D1 to D13, proposed as D14 and due before the PV17 PR-opening stack; if it is unfunded at Phase 3's PV-opening PR the plan stops at guards. Authors: Phase 2 guards, today's data-contract authors in any SDK (js-evo-sdk, Swift, Kotlin); Phases 3 to 5, Rust developers writing no_std guests against a Dash-specific ABI (bespoke onboarding whatever the language, report 09 on Stacks), a population that today exists only inside Dash's own SDK and FFI crates (packages/rs-sdk, packages/rs-sdk-ffi).
- Define engine governance before the engine ships (section 11): every activated engine stays compiled in as a vendored fork under its own package name (the soroban-wasmi pattern), since Cargo unifies semver-compatible versions (
=2.0.3 and =2.0.4 cannot coexist), selected by wasm_engine_version beside grove_version (pv/drive_versions/mod.rs:40-46); CI replays a recorded block corpus under every shipped engine (abci/replay/runner.rs); fuel vectors are recorded per engine, since 2.0's fuel is "relatively stable" and "less precise" (wasmi docs/migration-v1-to-v2.md); a security patch that changes an executed result activates as a new protocol version with a documented divergence height, never a silent patch bump (1.0.5 is the precedent, 1.1); a previously accepted module that fails to instantiate under a new engine is a class-1 error (fail closed, paid); the upgrade suite instantiates every registered testnet and mainnet module under the candidate engine before the vote; an owner has a re-registration path. Stewardship is governance too: Dash owns the fork if the single maintainer leaves (1.1), and 3.11 scores wasmi and every rejected engine on one test: maintainers, funding horizon, production embedders.
- Compose with identities, tokens, groups,
ChangeControlRules, and DPNS rather than a shadow layer; no in-contract balances outside the sum trees, and principal assets move only under 1.1's authorization rule.
1.4 Non-goals
- EVM bytecode or Solidity compatibility, an eth JSON-RPC surface, or secp256k1 accounts beside identities: rejected in section 3 on structural cost (account model, a parallel MPT, an RPC node); the 2026 outcomes of the EVM ports corroborate but are not the standard (Aurora down 99% in TVL with a July 2026 halt, Evmos shut down in May 2026, Botanix shut down twelve months after launch; report 06, principle 9).
- General contract-to-contract calls, reentrancy, or nested revert semantics before Phase 5 has shipped and demand is demonstrated; Phase 5 is one entry point per state transition proposing operations on its own contract, under principle 2's nesting rule.
- Native AMM or lending primitives; Phase 1b ships only primitives whose mechanism design is settled (section 6).
- CosmWasm, Move, or SVM ABI compatibility as a delivery target (principle 5 answers CosmWasm), and opaque raw key-value storage as the primary contract state model (section 4).
- Floating point, SIMD, threads, multiple memories, or a
start function in guest modules; all rejected at registration.
- Off-chain execution (zk-coprocessor, TEEs, optimistic verification) as deliverables; the zk-predicate variant gets a parallel design study (section 14) since Drive already verifies Orchard proofs in consensus.
- Changes to Tenderdash; where a limit is Tenderdash-coupled (max_state_transition_size 20480, pv/system_limits/v4.rs:23) the plan prefers Drive-side mechanisms.
- A JIT engine tier: deferred in section 10.7, with no protocol-version assignment here.
1.5 Design principles
- Engine-independent, protocol-version-frozen metering is the requirement; determinism over speed follows. A divergence halts the chain, validators re-execute with no consensus-visible bound, and the engine is off the critical path at Platform's block cadence (section 3.10), so any deterministic slowdown is affordable and a codegen or stack-depth divergence is not. wasmi over wasmtime 36 LTS with Pulley plus finite-wasm (section 14, question 4(c)): a heap-allocated guest stack, so stack limits are engine numbers rather than native frames; no code generator, while Pulley is Tier 2 (wasmtime docs/stability-tiers.md:93); Soroban's shape. Against: one maintainer, sponsorship ending October 2026, a major released this month, hence 1.1's pin by criterion. The interpreter is the only tier in Phases 3 to 5.
- User code proposes, Drive disposes. The guest returns a verdict or a bounded list of proposed operations that the native pipeline validates, prices and applies, keeping indexes, proofs, fees and conservation unchanged. Application is sequential: each proposed operation is transformed, validated and applied one at a time in list order, each against the state after its predecessors, inside the transition's transaction under one savepoint per call and one operation per GroveDB batch group, fees summed (section 10.2.3). Validating the whole list against pre-call state instead would let two creates that both pass uniqueness collide at apply as
DriveError::CorruptedContractIndexes (packages/rs-drive/src/error/drive.rs:112), an Err and a rejected block on every validator. One operation per group predates Phase 5: the pipeline refuses more than one document operation per GroveDB batch at every protocol version, because flattened operations cannot see each other and leave phantom index groups that still rank and prove (max_transitions_in_documents_batch: 1, pv/system_limits/v1.rs:8-33, pinned at system_limits/mod.rs:114-141); the cross-sibling derived-entry tracking those comments name is a Phase 5 prerequisite. Nesting depth is one: an entry point proposes, a proposed document operation runs its type's guards, predicates and triggers, which only accept or reject, so no predicate re-enters an entry point; all runs share the transition's one fuel bound; proposed operations are capped in SystemLimits; any rejection fails the whole transition as a paid consensus error with nothing applied, so no revert overlay exists.
- Proofs for everything survive. Contract state is documents, because opaque bytes would exempt exactly the applications people care about from the verification story that differentiates Platform.
- Every engine change is a protocol version: engine version, operator cost table, host imports and module limits are
PlatformVersion table fields activated by masternode vote, under goal 7's coexistence, replay, re-validation and patch rules.
- No framework import, for reasons that hold. CosmWasm's address format is host-defined and its callbacks return
GasInfo with a separate externally_used channel, so neither the account model nor the processing/storage split is the obstacle (report 05). The disqualifiers: a wasmer JIT pinned at =5.0.6 in consensus, source of a metering escape (CWA-2024-007, incorrect gas tracking in Wasmer) alongside a host-panic gas escape in the same patch round (CWA-2024-008); a submessage, reply and migration surface non-goal 2 excludes anyway, whose keeper is wasmd's Go with no maintained Rust port; JSON at every boundary (serde-json-wasm carried CWA-2024-001); cw20/cw721 balances in contract storage that escape conservation. Phase 5 is CosmWasm's Response pattern minus reply and cross-contract dispatch. Why not the cosmwasm-std entry-point and Response ABI on wasmi with Custom messages: the interface_version export ties every guest to cosmwasm-vm's release cadence, which Dash would freeze per protocol version without controlling; JSON costs fuel and bytes under a 20480-byte transition cap; and the libraries that compatibility buys (cw-storage-plus, cw-multi-test) assume flat KV with ordered iteration, which document-shaped state lacks. Its lessons (module limits, capability imports, hash-addressed code) are adopted now; a CosmWasm ABI layer stays a 10.7 option.
- Failures are typed; only guest-caused failures are consensus errors. Class 1, guest-observable and deterministic (trap, out-of-fuel, memory or table limit, footprint miss, malformed guest pointer or length, bad host-call argument, document not found on a declared read, rejected proposed operation, accepted module that no longer instantiates), maps through one function to typed
ConsensusError variants enumerated in dpp and gated per protocol version: paid, nonce bumped. Class 2, anything from Drive or GroveDB storage, stays Err, hence InternalError and a rejected proposal (process_proposal.rs:285-307), a node fault as today. The guard is in the type system: host imports return a host-error enum with no From<drive::Error> impl, so a Drive error cannot become class 1 without a reviewed match (today's trigger seam propagates every Err with ?, data_triggers/executor.rs:47); a test proves every class-1 variant reachable from guest input and no class-2 path. Panics are forbidden: drive-abci builds with panic = "unwind" (root Cargo.toml:72, :82 set abort only for iOS profiles) and process_proposal holds .write().unwrap() (process_proposal.rs:31, :272-273), so a guest-reachable panic poisons the lock on every node at once. Every host import reads guest memory through one bounds-checked accessor returning a class-1 error; the host crate denies clippy::indexing_slicing, clippy::unwrap_used, clippy::expect_used and clippy::arithmetic_side_effects; host imports are fuzzed with arbitrary guest input; a catch_unwind around the engine call is defense in depth, mapping a caught panic to one class-1 variant on every node; any occurrence is a consensus vulnerability.
- The v0 ABI is designed for the end state. The context struct, capability imports, and fee tables anticipate reads and entry points from the first release, because retrofitting state access onto a predicate-only ABI changes fee model, mempool policy, and ABI in one consensus-breaking step.
- The guest observes the block and the transition, nothing node-local: block height, time and epoch from
BlockInfo; protocol and fee versions; caller identity id; the transition payload; declared reads served from the state the phase names (section 09: committed block N-1 for Phase 4, the block transaction for Phase 5), fixed by protocol version. Excluded: wall clock, node configuration, mempool, the node's ProTxHash, the proposer's fee-version cache. Test rule: the context is built from BlockInfo and the transition alone, with no Platform config reachable from the host import layer. use_document_triggers (abci/config.rs:92-93) is the precedent not to repeat: a hook runs on every validator or none.
- Precedents are read symmetrically. Of three bespoke-host-on-wasmi embedders, Soroban runs on Stellar mainnet (on a stellar/wasmi fork), Radix is in managed decline (report 09), and Polkadot retired pallet-contracts plus ink! in favour of pallet-revive (Solidity on REVM, live on Polkadot Hub 2026-01-27; PolkaVM early-stage), ink! halting maintenance in January 2026 when treasury funding failed (report 07, external-engines facts). The distinguishers: this plan does not compete for the EVM developer funnel, so the pallet-contracts outcome is not its failure mode; its buyers are existing Platform applications needing enforced rules over document-shaped, proven state, which no imported VM offers; each stage is a voted protocol version useful on its own (goal 5); goal 6's SDK gate is the ink! lesson applied to Dash. No adoption figure is claimed for either path: EVM ports are rejected on structural cost, Soroban is an existence proof.
1.6 How to read this document
Sections 2 to 4 are the baseline (what exists in tree, options rejected or deferred, target architecture); 5 to 10 the six phases, each with Reasoning, Design, Tasks, Tests, Acceptance criteria and Risks; 11 and 12 determinism, security, engine governance and fees; 13 the developer surface; 14 sequencing, rollout, success criteria and open questions. Every - [ ] line is one reviewable unit of work (one PR or one recorded decision) naming the crate or module it touches and the protocol version gating it; nested checkboxes ship with their parent. Phase order is a dependency order; protocol-version assignments are targets, not commitments.
| Phase (section) |
PV |
Goals served |
A third party can |
Cannot |
| 0 decisions, DIP (05) |
none |
7 (on paper) |
nothing new |
|
| 1 VM prerequisites (06: 6.1, 6.2, 6.3, the 6.5 queue) |
15 |
4 (block budget) |
upload code blobs, permissionless at PV15 (the deployer allowlist starts with guard-bearing contracts at PV16, 11.2.6) |
attach logic |
| 1b primitives and feeds (06: 6.4, 6.5) |
its own PV, PV15 at the earliest |
8 (primitives) |
use escrow, allowance and auction primitives and oracle feeds |
attach logic; nothing the VM phases need waits for it |
| 2 guards (07) |
16 |
1 (partial), 2, 3 |
enforce cross-field invariants and authorization with up to ten declared document reads, statically costed |
loop, hold state machines, write |
| 3 predicates (08) |
17 |
1 (partial), 3, 4, 6, 7 |
reject transitions by arbitrary computation over the transition context |
read other documents or balances (no cross-document uniqueness, quotas or balance checks) |
| 4 footprint reads (09) |
18 |
1 (wider), 2 (footprint proofs) |
read declared documents, indexes and balances of its own contract |
write |
| 5 entry points (10) |
19 |
1 (own-contract logic), 8 (principal) |
move its own and its principal's documents, tokens and credits through proposed operations |
call another contract, write another contract's state |
Calendar: v9 to v14 opened on 2025-01-16, 2025-06-29, 2025-12-09, 2026-01-24, 2026-07-17 and 2026-08-03 (git log on pv/), one every 113 days on average; each activates on mainnet only after release, signalling past protocol_version_upgrade_percentage_needed (check_for_desired_protocol_upgrade/v1/mod.rs:16-31) and one to two 9.125-day epochs (section 14.4); v14, five weeks old, is unreleased. At that cadence PV19 opens about fifteen months after PV15, which is not open today, so Phase 5 cannot activate on mainnet before 2028 unless the cadence accelerates, against README.md:49-50's 2027 mainnet target for v4.0 and dash.org's v5.0, Q1 2027. Phase 0's docs PR corrects README and book (section 05); the dash.org milestone goes to its owner with this table.
This document exceeds GitHub's issue body limit. It continues in the comments below: parts 2 to 14 of 14, in order. Please keep discussion in threads that quote the section you are addressing.
Smart contracts on Platform: staged design and implementation plan
This document proposes how Dash Platform should add smart contracts: Rust contracts compiled to WebAssembly, executed by an unmodified and pinned wasmi interpreter, with a host layer and guest SDK owned by Dash, delivered in protocol-version-gated stages. Contract state stays document-shaped so proofs, indexes, fees, and refunds apply unchanged, and user code proposes operations that the existing Drive pipeline validates and applies. It records the options that were evaluated and why they were rejected or deferred, the target architecture, six delivery phases with task checklists, the cross-cutting engineering disciplines (determinism, security, fees), the developer-experience plan, and the open questions reviewers are asked to decide. Every checkbox is intended to be one reviewable unit of work.
Status: proposal for review. No DIP, issue, or code existed before this document. Opened 2026-09-08.
This is a proposal, not an approved roadmap, so it is one issue rather than a set of tracking issues. The task lists are here to be argued with: they show the shape and size of each phase. If the plan is accepted, the phases become their own tracking issues carrying these checkboxes, and this issue becomes the rationale they link back to.
How to review: the argument runs in order, so sections 1 to 4 carry the reasoning and the rejected alternatives, sections 5 to 10 are the phases, and sections 11 to 14 are the cross-cutting disciplines and delivery. Please quote the section and heading you are addressing, since the document spans several comments. Section 14 lists the open questions that need a decision before Phase 0 can close.
Contents
1. Summary, goals, non-goals, and design principles
Path prefixes as in section 02:
abci/is packages/rs-drive-abci/src,pv/is packages/rs-platform-version/src/version.1.1 What is proposed
User-authored contract logic on Dash Platform as Rust compiled to WebAssembly, executed inside Drive on an exactly pinned wasmi 2.0-line interpreter (MSRV 1.86 against the workspace's Rust 1.92, rust-toolchain.toml), behind a host layer and guest SDK that Dash writes and owns. The pin is a criterion, not a version: the earliest 2.0.x that passes section 3.11's differential fuel and trap corpus and has run for a Phase 0-decided number of months under section 13's native harness and fuel corpus before the PV17 PR takes the dependency. wasmi 2.0.0 shipped on 2026-09-01 on a rewritten IR built around three new accumulator registers, with no patch release yet (crates.io, 2026-09-07), and its 1.0 line needed a limit-check fix (1.0.4), an execution fix (1.0.5,
rem_s(MAX, -1)) and translation fixes in 1.0.6, 1.0.7, 1.0.8 and 1.0.9, all inside ten weeks of the 1.0 release (1.0.0 on 2025-12-03, 1.0.9 on 2026-02-09; wasmi CHANGELOG), so the bake criterion is an open Phase 0 decision. v0 meters with the engine'soperator_costtable; install-time instrumentation (the Internet Computer's model) is the engine-independent fallback if the corpus shows patch-level drift. wasmi has one maintainer and its sponsorship ends in October 2026 (external-engines facts): Dash is maintainer of last resort for a fork if that maintainer leaves, and fork-and-freeze versus co-sponsorship (section 14, question 4) is a Phase 0 decision.Delivery is staged, one masternode-voted protocol version per stage: VM-independent infrastructure (fuel priced into processing fees, so
gas_wanted(abci/abci/handler/check_tx.rs:66-69) carries each transition's full fuel bound to Tenderdash under the genesisblock.max_gas(packages/dashmate/configs/defaults/getBaseConfigFactory.js:436), a mempool parameter no Drive code enforces, so Drive adds the consensus-side budget of goal 4; a code-blob channel), with native primitives and oracle feeds as Phase 1b, a value-moving track that no VM phase waits for; then statically costed expression guards, WASM validation predicates on document types, declared-footprint reads, and finally constrained entry points returning a bounded list of proposed document, token and credit operations.Contract state stays document-shaped under the contract's documents subtree, so proofs, indexes and the end-of-block conservation check apply unchanged. Fee mechanics are unchanged; attribution for entry-point writes is decided in Phase 0 beside D1 and D3 (section 05), default: the signer is balance-checked and pays; each written element carries in its
StorageFlagsthe acting owner the operation names (caller or principal), who gets the refund (refunds fold per identifier, packages/rs-drive/src/fees/op.rs:275-300); entry points touch only documents the caller or the principal owns (deletes are owner-checked today, document_delete_transition_action/state_v0/mod.rs:97-103). Code blobs are immutable, hash-addressed and reference-counted, deletable with refund only at zero references (section 06.2); module bytes pay the disk rate plus a fixed module fee in a newFeeDataContractRegistrationVersiongeneration (section 12); a contract is permanent unless its config setscan_be_deleted(packages/rs-dpp/src/data_contract/config/v1/mod.rs:25), so a referenced blob's storage is permanent in practice.Guest code never writes GroveDB: it accepts, rejects or proposes; the pipeline prices under
estimated >= actual(goal 4) and applies (principle 2). Authorization is new consensus surface (a Phase 5 task, section 10): a proposed operation may act on assets the caller owns (the caller signed) or on assets the principal owns, the keyless identity subtype a contract holds (section 04.4), authorized solely by that contract's entry point with no key path; principal balances sit in the identity balance tree, soprocess_block_fees_and_validate_sum_trees(process_block_fees_and_validate_sum_trees/mod.rs:38) covers them.README.md:49-50 says smart contract support is "planned for Platform v4.0 (targeted for mainnet in 2027)" and the comparison table's smart-contract cell reads "Coming in v4.0" (README.md:63); dash.org lists a VM milestone at v5.0, Q1 2027. The repository has no design, issue or VM dependency behind either. The latest protocol version in tree is 14 (pv/mod.rs:36, unreleased); this plan targets 15 and later (calendar in 1.6).
1.2 Status of this document
A proposal for review, not a decision record: no DIP, code or protocol version exists for this work. After the open questions in section 14 are settled, the consensus design moves to a DIP in dashpay/dips (the platform repository does not accept committed design documents, CLAUDE.md:96-100) and the task lists here become issues and PRs. Repository facts were verified at v4.2-dev commit a3c36f3 on 2026-09-07; external engine facts are dated where they appear. The research synthesis named wasmi 1.0; wasmi 2.0.0 has since shipped, and where they differ this document governs (section 08).
1.3 Goals
InternalError(validators reject the proposal, abci/abci/handler/process_proposal.rs:285-307) or as a block-eventErr(node halt).estimated >= actual, the invariant the code already enforces: the affordability dry run prices withapply_drive_operations(apply = false), execution charges the actual metered cost, execute_event/v0/mod.rs:186-206 records the 2026-08-14/15 mainnet stalls asestimated < actual, and an uncoverable charge isErr(apply_balance_change_from_fee_to_identity/v0/mod.rs:92-100). Fuel is unknowable before a run, so (a) every hook-bearing transition has a fuel bound fixed before execution, theSystemLimitsper-call cap for predicates and a caller-declaredmax_fuelat or below that cap for calls, priced with the instantiation, translation, memory and context terms into one figure:Bat PV17 and PV18 (8.2.6),max_feeat PV19 (10.2.4). (b) That bound is checked against the payer before the engine is entered, not after validation: with the signer'sPartialIdentityalready loaded, the seam requires live balance minus the processing accrued so far in this transition to cover the bound plus the nonce-bump cost, both sides afterapply_user_fee_increase, and otherwise returnsInsufficientBalanceForHookExecution, paid at the affordable minimum with the nonce bumped and no instantiation (8.2.6, 11.2.3). No literal debit and credit-back. Checking after validation is too late: an unaffordable event executes asUnpaidConsensusExecutionError, no charge and no bump, and check_tx prices each transition against the last committed balance, so one balance admits N sequential-nonce transitions of which N-1 would run to the bound for free. (c)consume_fueltraps at the bound and the charge never exceeds what was priced: out-of-fuel pays the whole bound it consumed, a trap pays fuel to the trap plus the base, so failed executions are paid. (d) A declared bound above the cap is a structural error at check_tx and recheck. The block budget,max_block_processing_credits(newSystemLimitsfield, section 06.1), is enforced twice: the declared bounds of a proposal's hook-bearing transitions, summed from the decoded transitions at the fuel rate, reject an over-budget proposal before any hook runs; 06.1's running cutoff on accrued processing credits then marks later transitionsNotExecutedunder a newNotExecutedReasonvalue besideProposerRanOutOfTime(abci/platform_types/state_transitions_processing_result/mod.rs:13-16), which validators reject. No execution-result variant is added, so the exhaustive rollback match'sNotExecuted(_)arm already covers it (process_raw_state_transitions/v0/mod.rs:252-262); the new reason'sTxActionclassification in prepare_proposal must agree with that arm. The per-transition cap is strictly below the budget, so every transition is includable; failed executions count, since they run and pay.block.max_gasand the budget are calibrated together (section 12) against the proposer-only 5000 msproposer_tx_processing_time_limit(process_raw_state_transitions/v0/mod.rs:149-158; packages/rs-drive-abci/.env.mainnet:92; validators pass none, process_proposal.rs:235-241).cargo test, simulator; section 13) as a funded precondition: NEAR and Soroban ran SDK teams for years and ink! halted in January 2026 when funding failed (report 07), so Phase 0 records the SDK and developer-tooling commitment (owner, engineer count, funded duration) as a row of its own in section 05's register beside D1 to D13, proposed as D14 and due before the PV17 PR-opening stack; if it is unfunded at Phase 3's PV-opening PR the plan stops at guards. Authors: Phase 2 guards, today's data-contract authors in any SDK (js-evo-sdk, Swift, Kotlin); Phases 3 to 5, Rust developers writingno_stdguests against a Dash-specific ABI (bespoke onboarding whatever the language, report 09 on Stacks), a population that today exists only inside Dash's own SDK and FFI crates (packages/rs-sdk, packages/rs-sdk-ffi).=2.0.3and=2.0.4cannot coexist), selected bywasm_engine_versionbesidegrove_version(pv/drive_versions/mod.rs:40-46); CI replays a recorded block corpus under every shipped engine (abci/replay/runner.rs); fuel vectors are recorded per engine, since 2.0's fuel is "relatively stable" and "less precise" (wasmi docs/migration-v1-to-v2.md); a security patch that changes an executed result activates as a new protocol version with a documented divergence height, never a silent patch bump (1.0.5 is the precedent, 1.1); a previously accepted module that fails to instantiate under a new engine is a class-1 error (fail closed, paid); the upgrade suite instantiates every registered testnet and mainnet module under the candidate engine before the vote; an owner has a re-registration path. Stewardship is governance too: Dash owns the fork if the single maintainer leaves (1.1), and 3.11 scores wasmi and every rejected engine on one test: maintainers, funding horizon, production embedders.ChangeControlRules, and DPNS rather than a shadow layer; no in-contract balances outside the sum trees, and principal assets move only under 1.1's authorization rule.1.4 Non-goals
startfunction in guest modules; all rejected at registration.1.5 Design principles
DriveError::CorruptedContractIndexes(packages/rs-drive/src/error/drive.rs:112), anErrand a rejected block on every validator. One operation per group predates Phase 5: the pipeline refuses more than one document operation per GroveDB batch at every protocol version, because flattened operations cannot see each other and leave phantom index groups that still rank and prove (max_transitions_in_documents_batch: 1, pv/system_limits/v1.rs:8-33, pinned at system_limits/mod.rs:114-141); the cross-sibling derived-entry tracking those comments name is a Phase 5 prerequisite. Nesting depth is one: an entry point proposes, a proposed document operation runs its type's guards, predicates and triggers, which only accept or reject, so no predicate re-enters an entry point; all runs share the transition's one fuel bound; proposed operations are capped inSystemLimits; any rejection fails the whole transition as a paid consensus error with nothing applied, so no revert overlay exists.PlatformVersiontable fields activated by masternode vote, under goal 7's coexistence, replay, re-validation and patch rules.GasInfowith a separateexternally_usedchannel, so neither the account model nor the processing/storage split is the obstacle (report 05). The disqualifiers: a wasmer JIT pinned at=5.0.6in consensus, source of a metering escape (CWA-2024-007, incorrect gas tracking in Wasmer) alongside a host-panic gas escape in the same patch round (CWA-2024-008); a submessage, reply and migration surface non-goal 2 excludes anyway, whose keeper is wasmd's Go with no maintained Rust port; JSON at every boundary (serde-json-wasm carried CWA-2024-001); cw20/cw721 balances in contract storage that escape conservation. Phase 5 is CosmWasm'sResponsepattern minus reply and cross-contract dispatch. Why not the cosmwasm-std entry-point andResponseABI on wasmi withCustommessages: theinterface_versionexport ties every guest to cosmwasm-vm's release cadence, which Dash would freeze per protocol version without controlling; JSON costs fuel and bytes under a 20480-byte transition cap; and the libraries that compatibility buys (cw-storage-plus, cw-multi-test) assume flat KV with ordered iteration, which document-shaped state lacks. Its lessons (module limits, capability imports, hash-addressed code) are adopted now; a CosmWasm ABI layer stays a 10.7 option.ConsensusErrorvariants enumerated in dpp and gated per protocol version: paid, nonce bumped. Class 2, anything from Drive or GroveDB storage, staysErr, henceInternalErrorand a rejected proposal (process_proposal.rs:285-307), a node fault as today. The guard is in the type system: host imports return a host-error enum with noFrom<drive::Error>impl, so a Drive error cannot become class 1 without a reviewed match (today's trigger seam propagates everyErrwith?, data_triggers/executor.rs:47); a test proves every class-1 variant reachable from guest input and no class-2 path. Panics are forbidden: drive-abci builds withpanic = "unwind"(root Cargo.toml:72, :82 setabortonly for iOS profiles) and process_proposal holds.write().unwrap()(process_proposal.rs:31, :272-273), so a guest-reachable panic poisons the lock on every node at once. Every host import reads guest memory through one bounds-checked accessor returning a class-1 error; the host crate deniesclippy::indexing_slicing,clippy::unwrap_used,clippy::expect_usedandclippy::arithmetic_side_effects; host imports are fuzzed with arbitrary guest input; acatch_unwindaround the engine call is defense in depth, mapping a caught panic to one class-1 variant on every node; any occurrence is a consensus vulnerability.BlockInfo; protocol and fee versions; caller identity id; the transition payload; declared reads served from the state the phase names (section 09: committed block N-1 for Phase 4, the block transaction for Phase 5), fixed by protocol version. Excluded: wall clock, node configuration, mempool, the node's ProTxHash, the proposer's fee-version cache. Test rule: the context is built fromBlockInfoand the transition alone, with noPlatformconfig reachable from the host import layer.use_document_triggers(abci/config.rs:92-93) is the precedent not to repeat: a hook runs on every validator or none.1.6 How to read this document
Sections 2 to 4 are the baseline (what exists in tree, options rejected or deferred, target architecture); 5 to 10 the six phases, each with Reasoning, Design, Tasks, Tests, Acceptance criteria and Risks; 11 and 12 determinism, security, engine governance and fees; 13 the developer surface; 14 sequencing, rollout, success criteria and open questions. Every
- [ ]line is one reviewable unit of work (one PR or one recorded decision) naming the crate or module it touches and the protocol version gating it; nested checkboxes ship with their parent. Phase order is a dependency order; protocol-version assignments are targets, not commitments.Calendar: v9 to v14 opened on 2025-01-16, 2025-06-29, 2025-12-09, 2026-01-24, 2026-07-17 and 2026-08-03 (git log on pv/), one every 113 days on average; each activates on mainnet only after release, signalling past
protocol_version_upgrade_percentage_needed(check_for_desired_protocol_upgrade/v1/mod.rs:16-31) and one to two 9.125-day epochs (section 14.4); v14, five weeks old, is unreleased. At that cadence PV19 opens about fifteen months after PV15, which is not open today, so Phase 5 cannot activate on mainnet before 2028 unless the cadence accelerates, against README.md:49-50's 2027 mainnet target for v4.0 and dash.org's v5.0, Q1 2027. Phase 0's docs PR corrects README and book (section 05); the dash.org milestone goes to its owner with this table.This document exceeds GitHub's issue body limit. It continues in the comments below: parts 2 to 14 of 14, in order. Please keep discussion in threads that quote the section you are addressing.