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
| Class | Trait | Purpose |
|---|---|---|
Noop | impl SyntarieContract for Noop | Trivial pass-through; used in tests + as a sanity baseline |
Echo | impl SyntarieContract for Echo | Returns its input; used in cross-contract message tests |
PolicyEngine | impl SyntarieContract for PolicyEngine | The expressive one. Evaluates a small typed AST against an invocation context, deciding admit/reject for transfers, releases, and cancellations |
Governance | impl SyntarieContract for Governance | Vote 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— PolicyEngineinterface/src/core/governance_contract.rs— Governanceinterface/src/core/money_contract.rs— MoneyContractV1interface/src/core/contract_execution_engine.rs— host action dispatcher (the gate)docs/decisions/goal-9-contract-boundary.md— the original boundary decision