★ delegatecall/call in proposal execution without allowlist
Uniswap (v2 + v3)'s assessment for RD-F-039 — scored yellow on the v1.7.0 rubric. The evidence below is the curator's reasoning for this score.
Evidence summary #
V2: not applicable (no governance). V3: Timelock.executeTransaction() uses target.call() with no target allowlist. Any passed proposal can call any contract. Mitigated by .call() (not delegatecall — cannot corrupt Timelock storage), 7-day vote, 40M UNI quorum, 2-day delay, and Seatbelt transparency. Combined: yellow (not red — .call() without allowlist is material but less severe than delegatecall pattern).
Detail #
Uniswap Timelock.sol (github.com/Uniswap/governance): executeTransaction() uses `bool success = target.call{value: value}(data)` with no target allowlist. Any governance proposal can call any contract address with any data. This is a .call() not delegatecall: the called contract cannot corrupt the Timelock's storage. Compensating controls: (1) 7-day voting period; (2) 40M UNI quorum threshold; (3) 2-day Timelock delay; (4) Governance Seatbelt pre-execution simulation tool provides transparency. Proposal-to-execution minimum is ~11 days — substantial window for community review. Critical factor threshold (red) requires delegatecall or equivalent storage-compromise risk; .call() without allowlist is yellow.
Sources #
- GitHubGovernorBravoDelegate sourceGovernorBravoDelegate: executes via timelock, no target allowlistretrieved 2026-04-29
- Uniswap Timelock.sol sourceTimelock.sol: executeTransaction uses target.call() with no allowlistretrieved 2026-04-29
Methodology #
Determine whether the governance executor contract uses `delegatecall` or `call` with proposal-supplied target, without enforcing an allowlist of permitted targets.
See the full factor methodology and distribution across all protocols →