Bounded contracts

Syntarie ships exactly four production contract classes. Adding a fifth requires a code change, governance approval, and a chain-stability re-verify.

The four classes

ClassTraitPurpose
Noopimpl SyntarieContract for NoopTrivial pass-through; used in tests + as a sanity baseline
Echoimpl SyntarieContract for EchoReturns its input; used in cross-contract message tests
PolicyEngineimpl SyntarieContract for PolicyEngineThe expressive one. Evaluates a small typed AST against an invocation context, deciding admit/reject for transfers, releases, and cancellations
Governanceimpl SyntarieContract for GovernanceVote tracking, proposal lifecycle, validator-set transitions

Monetary effects (mint/burn/credit/debit) route through MoneyContractV1 only. PolicyEngine and Governance can EMIT host actions (e.g. TransferWithTicket), but they go through MoneyContractV1's authorization gate before any state mutates.

Why bounded

See Scope boundaries for the full argument. In short: blast radius, determinism, honesty.

What "PolicyEngine" can express

The PolicyEngine DSL is a typed AST, not a scripting language. Predicates are bounded:

  • Boolean compositions over tx-envelope facts (signer, height, value)
  • Account-state lookups (balance, nonce, guardian set)
  • Time / height bounds
  • Multi-sig conditions (N-of-M signatures)
  • Selective-disclosure assertions

It cannot:

  • Loop unbounded
  • Store arbitrary state across invocations (state mutations go through MoneyContractV1)
  • Call out to other contracts (the host action is the only side-effect path)
  • Execute Turing-complete logic

SECURITY-1 closure

Until 2026-04-26, evaluate_policy trusted some payload-supplied fields (caller identity, signatures, cached state facts). The Vault audit + fix sprint closed this fail-loud across 8 axes. Now, when payload claims conflict with the verified envelope, the policy evaluation is rejected rather than silently overridden.

[Verified] in interface/src/core/policy_engine_contract.rs + interface/src/core/policy_evaluator.rs. Regression tests pin it: tests_policy_engine_contract::policy_contract_evaluate_rejects_payload_context_caller_mismatch and 9 other Crucible-authored regression tests.

What this means for users

You don't write contracts on Syntarie. You compose intents that route through the existing classes. Most consortium use cases (whitelists, time-bounds, multi-sig conditions, governance flows) are expressible through PolicyEngine + Governance.

If you need a different bounded class, it's a governance proposal + code change. It's not "deploy a new contract" — it's "amend the protocol."

Source pointers

  • interface/src/core/policy_engine_contract.rs — PolicyEngine
  • interface/src/core/governance_contract.rs — Governance
  • interface/src/core/money_contract.rs — MoneyContractV1
  • interface/src/core/contract_execution_engine.rs — host action dispatcher (the gate)
  • docs/decisions/goal-9-contract-boundary.md — the original boundary decision