Skip to main content

TAM: Mental Model & Lifecycle

Think of the system as two clean layers: a shared core that owns the lifecycle and accounting, and a mechanism that supplies policy through gated hooks. The core guarantees timing, state transitions, signature safety, ERC‑20 share rules, and permissionless queuing. Your mechanism defines who may participate, how votes cost power, what “quorum” means, and how votes convert to shares (e.g., α‑weighted QF). This separation keeps behavior predictable, testable, and easy to evolve without widening the audit surface.

Lifecycle Summary

The round advances through a strict, time‑boxed sequence. The shared core enforces the windows and state transitions; your hooks are invoked at each step to apply policy. Where it matters operationally: finalization is owner → proxy → implementation, queuing is permissionless, and share transfers are allowed only during the redemption window.

  1. Register → voting power. Voters call signup(deposit) (or its signature variant). The core moves assets and asks your hooks to validate eligibility and compute deterministic power; multiple signups accumulate power, which is appropriate for QF.
  2. Propose. Authorized proposers create proposals with a unique, non‑zero recipient. The default quadratic mechanism restricts proposing to keeper/management; you can widen or tighten this in your hook.
  3. Vote. A voter casts exactly one vote per proposal. The core pins the expectedRecipient to avoid ambiguity and calls your vote‑processing hook to charge quadratic cost—W votes cost power—and to update tallies. The provided QVM admits For only (intentionally); other choices can be enabled by policy if you need them.
  4. Finalize. After the voting window, the owner finalizes the tally. The core records totalAssets using your calculation hook (e.g., to include matching pools) and schedules the global redemption window (timelock + grace). This step is one‑way.
  5. Queue. Anyone can queue a successful proposal. The core checks quorum via your hook, converts votes→shares via your α‑weighted QF hook, and either mints shares to the recipient or executes your custom distribution and reduces totalAssets by the exact assets transferred to maintain accounting equality.
  6. Redeem / Sweep. Recipients redeem shares for assets only inside the redemption window and the core enforces via your withdrawal‑limit hook; after the grace period ends, the owner may sweep unclaimed funds. Outside this window, share transfers are blocked by the core.

Required Hooks (your override surface)

Hooks are your policy engine. They must be deterministic, defensive, and—unless documented otherwise—view‑only. The core calls them via onlySelf wrappers so end‑users cannot bypass invariants.

  • _beforeSignupHook(user) — Eligibility. Decide who may register (e.g., whitelist). Keep it consistent and cheap to evaluate. The core handles zero‑address checks and accumulation.

  • _getVotingPowerHook(user, deposit) — Power function. Return a deterministic mapping from deposits to power; QVM normalizes to 18 decimals for consistency across assets.

  • _beforeProposeHook(proposer) / _validateProposalHook(pid) — Proposal gates. Authorize proposers and enforce proposal ID sanity. QVM defaults to keeper/management‑only creation.

  • _processVoteHook(pid, voter, choice, weight, oldPower) — Quadratic cost + single vote. Enforce cost, conserve power (newPower ≤ oldPower), and update tallies. QVM reverts unless choice == For.

  • _hasQuorumHook(pid) — Quorum test. Provide an unambiguous, deterministic quorum rule (e.g., minimum QF funding or participation).

  • _convertVotesToShares(pid) — in QF this is α‑weighted QF mapping, but the implementation can be modified to support other voting mechanisms. Map tallies to shares using

    Fj=α(∑ici,j)2+(1−α)∑ici,jF_j = \alpha(\sum_i \sqrt{c_{i,j}})^2 + (1-\alpha)\sum_i c_{i,j}. Implement with **incremental** state for Sj=∑cS_j=\sum\sqrt{c} and Sumj=∑c\mathrm{Sum}_j=\sum c to match full recomputation exactly at lower cost.

  • _getRecipientAddressHook(pid) — Recipient resolution. Return a stable, non‑zero address; one recipient per proposal for the entire round.

  • _requestCustomDistributionHook(recipient, shares) — Optional distribution. If you directly transfer assets here, return (true, assetsTransferred) so the core reduces totalAssets by that exact amount; otherwise return (false, 0) and let the core mint shares. You may impose additional access policy here while keeping accounting exact.

  • _availableWithdrawLimit(owner) — Redemption window. Return 0 before timelock and after grace; return type(uint256).max inside the window. This is how the core blocks redemptions and enables sweep only after expiry.

  • _calculateTotalAssetsHook() — Asset accounting at finalize. Include matching pools and any external balances so queued allocations cannot over‑ or under‑mint.

Safety properties (what must always hold)

This lifecycle and hook split borrows the proven “tokenized strategy” pattern (storage in the instance, logic in a shared implementation, and explicit extension points), adapted here for governance rather than yield. These are the invariants you should assert in tests; they make the system robust to refactors and hook changes.

  • Windows are law. Voting, finalization, redemption, and sweep are mutually exclusive where required; redemptions should be impossible outside the global window. Alter and acknowledge the inherent risk (not recommended).
  • Power never increases on vote. _processVoteHook must reduce or retain power, never increase it; one vote per proposal is enforced.
  • Accounting is exact. If your distribution hook transfers assets, totalAssets is reduced by precisely assetsTransferred; otherwise the core mints shares which can later be redeemed.
  • Transfers are gated. Share transfers are blocked until redemption starts; they are permitted only during the redemption window.
  • QF math is correct and bounded. Incremental updates maintain an accurate tally; the global‑vs‑per‑project rounding gap is bounded by ≤ 2(|P|−1) wei per project, avoiding over‑allocation.