Skip to main content

Smart contracts overview

Dyma's campaign reward escrow backs the automatic distribution mode. It is written in Rust for CosmWasm 2.2 and targets Safrochain (safrochain-1: safrochaind v0.2.2, wasmd v0.54.1, wasmvm v2.2.4). Version 0.1.0.

:::caution Audit status Internal pre-audit review: done — four independent review lenses (access control, funds and math, state machine and time, DoS/deployment), 32 findings (1 high, 6 medium, 15 low, 10 informational), every one fixed with a regression test or documented as an accepted risk. External audit: pending. Until an external audit report is published, Dyma's runbook allows only small internal canary amounts on mainnet. Nothing in Dyma's products may describe the escrow as "audited". :::

Architecture​

┌──────────── dyma-escrow-factory (one per chain) ────────────┐
Dyma API ─ ticket ─►│ config: owner, operator, ticket signer, pauser, guardian, │
(signs off chain) │ treasury, escrow code id + admin, max fee, limits │
│ registry: campaign → latest funding attempt (+ history) │
owner wallet ── create_campaign{ticket, signature} + pool ──► verify ─► instantiate2
└───────────────────────────────────────────────────────┬──────┘
▼
dyma-campaign-escrow (one per campaign funding attempt) holds the pool
ContractCrateRole
Escrow factorydyma-escrow-factoryone per chain. Holds the trust configuration, verifies API-signed campaign tickets, creates one escrow per campaign funding attempt with instantiate2 (salt = sha256("<campaign_id>:<funding_attempt>"), so the address is known before funding), keeps the registry and the global pause ledger
Campaign escrowdyma-campaign-escrowone per campaign funding attempt. Holds exactly one asset (a bank denom — native, IBC, tokenfactory — or an approved CW20), takes the operator's sorted allocation, snapshots its balance at finalize, runs the challenge window, pays through a permissionless crank or self-claims, refunds only the owner

Design choices:

  • Economic rules frozen per escrow — fee, cap, minimum payout, windows and a copy of the factory's limits are fixed at creation; the treasury is fixed at finalize. Governance changes reach new campaigns only.
  • Live roles — operator, pauser, guardian and treasury are read from the factory at call time, so a compromised key can be rotated on every escrow at once.
  • Escrow clock — every deadline after the end date runs on a per-escrow clock that stops while the escrow is paused.
  • Factory independence — a finalized escrow pays out even if the factory can no longer be queried.
  • Fault isolation — each payout is a sub-message whose failure only marks that entry failed.

Lifecycle​

UPCOMING → ACTIVE → ENDED → COMMITTING → CHALLENGE (→ DISPUTED) → DISTRIBUTING → COMPLETED
↘ CANCELLED (owner before start, guardian before finalize) ↘ EXPIRED (not finalized in time: owner reclaims)
payout_i = min( floor(D × weight_i / W), cap ), skipped below min_payout
D = balance at finalize − fee reserve
fee = floor(paid_total × fee_bps / 10 000), charged once at completion (Dyma default: 0)

The product-level explanation (timeline, disputes, pauses, leftovers) is in Automatic distribution; the split, with worked examples, in Distribution modes and the split.

Roles​

RoleHolder (production)Powers
Factory owner (governance)dyma-gov 3-of-5 multisigupdate config (new campaigns), global pause, rescue funds sent to the factory by mistake, ownership transfer
Operatordyma-operator hot key (AWS KMS)commit, reset and finalize allocations
Ticket signerdyma-ticket hot key (AWS KMS)signs funding tickets off chain; only its public key is on chain
Pauserdyma-pauser hot key (AWS KMS)rate-limited pauses (budget per escrow)
Guardiandyma-guardian 2-of-3 multisigreject an allocation, resolve a dispute, cancel before finalize, pause/unpause
Treasuryfee recipientreceives the fee (0 by default)
Upgrade admindyma-upgrade 3-of-5 multisig, or nonemigrate the factory and instances (security fixes only)
Campaign ownerthe funding wallettop up, cancel before start, dispute, ownership transfer before start; receives every refund
Recipientscommitted addressesclaim their own entry
Anyonedistribute, retry failed payouts, reclaim an expired escrow, sweep, refund stray assets

Role separation is enforced by the factory: the guardian differs from the operator and the pauser; the upgrade admin differs from the operator, the pauser and the treasury; the operator is not the treasury. Privileged addresses can never be committed as recipients.

Reference documents​

The contracts repository (Dyma-IO/dyma-contracts, private until the audit report is published) carries the authoritative documents. This site summarises them:

DocumentSummary here
docs/SPEC.md — interface, roles, state machine, math, events, measured gasMessages, Events
docs/THREAT_MODEL.md — trust assumptions, threats, invariants, residual risksSecurity model
docs/RUNBOOK.md — key ceremony, deployment, multisig flow, incident playbooksEscrow operations
docs/AUDIT_SCOPE.md — what an auditor reviews and how to build and test itSecurity model
docs/INTERNAL_AUDIT.md — internal findings and outcomesSecurity model
contracts/*/schema/ — machine-readable JSON schemagenerated from packages/escrow-types

Build: release artifacts come from cosmwasm/optimizer:0.16.1 (Rust 1.81) and are gated by cosmwasm-check 2.2.x — newer toolchains emit WebAssembly features Safrochain's VM refuses at upload.