---
title: "Typed vs arbitrary proposal execution"
url: "https://caper.network/wiki/dao-governance/concepts/voting/typed-vs-arbitrary-execution"
updated: 2026-08-11
license: CC-BY-4.0
license_url: "https://creativecommons.org/licenses/by/4.0/"
---

# Typed vs arbitrary proposal execution

|  |  |
| --- | --- |
| **Topic** | Whether a passed proposal may run _any_ call, or only one of a fixed set of actions |
| **The arbitrary end** | [OpenZeppelin Governor](/wiki/dao-governance/tooling/voting/openzeppelin-governor) · Compound Governor Bravo · [Baal](/wiki/dao-governance/tooling/frameworks/daohaus) multi-send — a proposal carries raw `calldata` |
| **The typed end** | Enumerated action kinds the contract itself understands; anything outside the enum is not expressible |
| **Core trade-off** | Generality (any future action, no upgrade needed) against **legibility** (a voter can tell what the proposal does) |
| **Mitigations in practice** | [Timelocks](/wiki/dao-governance/concepts/voting/governance-timelocks) · [Zodiac Roles](/wiki/dao-governance/tooling/frameworks/zodiac) scoping · simulation before the vote |
| **Worked failure** | Tornado Cash, May 2023 — a proposal whose code changed _after_ it passed |
| **Related** | [The proposal lifecycle](/wiki/dao-governance/concepts/voting/proposal-lifecycle) · [Governance attacks](/wiki/dao-governance/concepts/analysis/dao-security-and-governance-attacks) |

Every DAO framework has to answer one question about its proposals: **what is a passed vote allowed to do?** There are two families of answer. In an _arbitrary-execution_ design, a proposal carries raw transaction data and the governance contract will execute whatever that data says. In a _typed_ design, a proposal selects from a fixed list of action kinds that the contract itself understands, and nothing outside that list can be expressed at all.

This axis is rarely stated explicitly, but it decides more about a DAO's real risk profile than quorum or threshold settings do. It determines whether a voter can know what they are voting for by reading the ballot, and it is the axis on which the [most expensive governance failures](/wiki/dao-governance/concepts/analysis/dao-security-and-governance-attacks) turn.

## What arbitrary execution actually looks like

The dominant Ethereum pattern is arbitrary. [OpenZeppelin's Governor](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/governance/Governor.sol) declares its entry point as `propose(address[] targets, uint256[] values, bytes[] calldatas, string description)`. The first three arrays are the proposal; the fourth argument, the human-readable description, is the only part a voter reads and the only part the contract does not enforce. Compound's [Governor Bravo](https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/GovernorBravoDelegate.sol) takes the same shape with an extra `signatures` array and caps a proposal at ten actions (`proposalMaxOperations = 10`).

Nothing in either contract relates the description to the calldata. A proposal titled “Fund the grants program for Q3” and a proposal that drains the treasury are, to the governance contract, the same kind of object: a list of addresses, values and byte strings. Verifying that the two agree is off-chain social work, and it is work almost nobody does — decoding calldata against an unverified target contract is a skill most delegates do not have and most voters do not attempt.

The upside is real and is why the pattern won. An arbitrary-execution DAO can do things its designers never anticipated: call a protocol that did not exist when the governance contract shipped, adopt a new standard, or wind itself down. A typed DAO can only ever do what its enumeration allows, and widening the enumeration means changing the contract.

## The failure mode: a proposal that changed after it passed

In May 2023 Tornado Cash lost governance control of itself to a proposal that looked like an earlier, legitimate one. Per [Halborn's analysis](https://www.halborn.com/blog/post/explained-the-tornado-cash-hack-may-2023), the attacker submitted a proposal that claimed to implement the same logic as a previously approved proposal, but whose deployed contract carried extra self-destruct logic. Voters, comparing it to the version they already trusted, passed it. The attacker then destroyed the proposal contract and redeployed different code at the same address. When the proposal executed — via `delegatecall`, so in governance's own storage context — it minted the attacker 1.2 million votes against roughly 70,000 legitimate ones, and about 483,000 TORN (~$2.17 million) was drained.

The instructive part is not the self-destruct trick. It is that **every governance rule worked correctly**. Quorum was met, the vote was honest, the timelock elapsed, and the contract executed exactly the bytes it was given. The failure was entirely in the gap between what the proposal _said_ and what its calldata _did_ — the gap that arbitrary execution creates by design and that no voting parameter can close.

## Narrowing the surface without giving up generality

Most of the tooling built around Governor over the past few years is, read charitably, an attempt to recover legibility that arbitrary execution gave away.

- **[Timelocks](/wiki/dao-governance/concepts/voting/governance-timelocks)** do not make a proposal legible; they buy time for someone else to notice. They convert an execution problem into a monitoring problem, which is an improvement only if somebody is monitoring.
- **Scoped permissions.** [Zodiac's Roles Modifier](/wiki/dao-governance/tooling/frameworks/zodiac) sits between a module and a [Safe](/wiki/dao-governance/tooling/treasury/safe) and, in its [own description](https://github.com/gnosisguild/zodiac-modifier-roles), lets holders of a role “unilaterally make calls to any approved addresses, approved functions, and using approved parameters.” That is typing applied at the permission layer rather than the proposal layer: the calldata stays arbitrary, but the set of calls that will be accepted is enumerated in advance.
- **Simulation.** Front-ends increasingly simulate a proposal's calls against forked state and show the resulting balance changes. This is the closest thing to making calldata readable, and it is why proposal tooling and [governance data](/wiki/dao-governance/tooling/analytics/boardroom) services matter more than their unglamorous positioning suggests.
- **Procedural checks.** DAOhaus's [published vote-decision flow](https://github.com/HausDAO/moloch-skills/blob/main/VOTE_DECISION_FLOW.md) for autonomous agent members is the clearest written statement of the problem: it classifies every proposal into one of six buckets, of which the last is plainly **“arbitrary execution”**, and requires the agent to ask “does action data match proposal text?” before voting. When a procedure has to name that question explicitly, the design has already conceded that the answer is not obvious.

## Why typed designs are rarer than they should be

Typing a proposal system has an obvious cost: the enumeration is a ceiling. Adding a genuinely new action means changing the contract that holds the state, which is exactly the kind of change a DAO is slowest at making and most reluctant to authorise. Frameworks that expect to serve arbitrary future protocols therefore default to arbitrary calldata, and the burden of understanding a proposal shifts to the voter.

The counter-argument is that most DAOs do not need generality. A treasury that in practice only pays contributors, buys assets, sells assets and votes in other organizations has a small action vocabulary, and paying for unlimited expressiveness it never uses buys nothing but attack surface. The right question is not “which design is safer” but **how wide the DAO's action vocabulary genuinely is** — and, if it is narrow, whether the framework will let it say so on-chain. Most will not. This is one of the places where framework choice, made once and early, quietly sets a ceiling on how [minimal](/wiki/dao-governance/concepts/fundamentals/governance-minimization) a DAO's trust assumptions can ever become.

## How Caper approaches this

A [caper](/wiki/foundations/what-is-a-caper) sits at the typed end, and deliberately. An executive [proposal](/wiki/governance/proposals) option carries a `kind` discriminator, and the contract recognises exactly five: pay an address from the treasury, buy another caper's token, sell one back down its curve, cast a vote inside another caper, or — on the platform's own caper only — swap the shared logic every caper runs. There is no sixth slot and no calldata field. A proposal cannot encode a call the contract does not already know how to make.

The consequences run both ways, and both are worth stating. A voter can read what an option does from its kind and its typed fields, so the Tornado failure mode — a description that does not match the payload — has no room to occur. But the enumeration really is a ceiling: adding a new kind requires the state-holding contract's cooperation, not merely a passed vote. Caper's answer to that is the fifth kind, an upgrade of the shared logic tier, which is itself a proposal subject to the same voting rules. The action vocabulary is fixed, but the process for widening it is inside governance rather than outside it. See [execution](/wiki/governance/execution) for how a winning option is settled.
