Keyboard shortcuts

Press ← or β†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Part 3 β€” Module 8: Governance & DAOs

Difficulty: Intermediate

Estimated reading time: ~30 minutes | Exercises: ~2-3 hours

πŸ“š Table of Contents

  1. On-Chain Governance
  2. OpenZeppelin Governor in Practice
  3. Build Exercise: Governor + Timelock System
  4. ve-Tokenomics & the Curve Wars
  5. Build Exercise: Vote-Escrow Token
  6. Governance Security
  7. Governance Minimization
  8. Summary
  9. Resources

πŸ’‘ On-Chain Governance

Every major DeFi protocol needs a mechanism for parameter updates, upgrades, and strategic decisions. Governance is how protocols evolve after deployment β€” and also one of the most exploited attack surfaces in DeFi.

Why this matters for you:

  • Every DeFi protocol you work on will have governance β€” understanding the lifecycle and security is essential
  • ve-tokenomics (Curve, Velodrome) is one of the most important DeFi innovations β€” it reshapes protocol economics
  • The Beanstalk attack ($182M) shows what happens when governance security is wrong
  • Governance design is a frequent interview topic β€” β€œhow would you design governance for X protocol?”
  • Connection to Module 7: cross-chain governance (vote on L1, execute on L2) is the multi-chain standard
  • Connection to Part 2 Module 9: your capstone’s immutable design was itself a governance choice

πŸ’‘ Concept: Why Governance Exists

Protocols need to change after deployment:

  • Risk parameters: LTV ratios, interest rate curves, collateral types (Part 2 Module 4)
  • Fee management: swap fees, protocol revenue distribution
  • Treasury: fund allocation, grants, strategic investments
  • Upgrades: proxy implementations, new features (Part 1 Module 5)
  • Emergency: pause, parameter adjustment, shutdown (Part 2 Module 6 ESM)

The fundamental tension: Decentralization vs operational agility. A multisig can act in minutes; full on-chain governance takes 1-2 weeks. The right answer depends on what’s being governed and the protocol’s maturity stage.

The Proposal Lifecycle

Every on-chain governance system follows the same flow:

 PROPOSE        DELAY          VOTE          QUEUE         EXECUTE
───────── β†’ ─────────── β†’ ─────────── β†’ ─────────── β†’ ───────────
 Proposer     Community      Token         Timelock       Anyone
 submits      reviews        holders       enforces       triggers
 on-chain     proposal       vote          delay          execution
             (1-2 days)    (3-7 days)    (24-48h)

Total: 5-14 days from proposal to execution

Why each step exists:

  • Delay β€” prevents surprise proposals; community can review before voting starts
  • Vote β€” democratic decision with quorum requirements
  • Queue/Timelock β€” critical safety net: users who disagree can exit before changes take effect. If governance passes a malicious proposal, the timelock gives users time to withdraw.

Voting Power Mechanisms

MechanismFormulaProsConsUsed By
Token-weighted1 token = 1 voteSimple, transparentPlutocraticUniswap, Aave, Compound
DelegationDelegates accumulate voting powerReduces voter apathyDelegation centralizationAll major governors
Vote-escrow (ve)Lock duration Γ— amountAligns long-term incentivesComplex, illiquidCurve, Velodrome
Quadratic√tokens = votesMore egalitarianSybil-vulnerableGitcoin (off-chain)

πŸ’‘ OpenZeppelin Governor in Practice

πŸ’‘ Concept: The Standard Governance Stack

OpenZeppelin Governor is the industry standard β€” used by most new DeFi protocols. Understanding its code is essential.

The three contracts:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  ERC20Votes      β”‚     β”‚  Governor        β”‚     β”‚  Timelock        β”‚
β”‚                  β”‚     β”‚                  β”‚     β”‚  Controller      β”‚
β”‚  β€’ delegate()    │────→│  β€’ propose()     │────→│  β€’ schedule()    β”‚
β”‚  β€’ getVotes()    β”‚     β”‚  β€’ castVote()    β”‚     β”‚  β€’ execute()     β”‚
β”‚  β€’ getPastVotes()β”‚     β”‚  β€’ queue()       β”‚     β”‚  β€’ cancel()      β”‚
β”‚                  β”‚     β”‚  β€’ execute()     β”‚     β”‚                  β”‚
β”‚  Checkpointing:  β”‚     β”‚                  β”‚     β”‚  Roles:          β”‚
β”‚  records balance β”‚     β”‚  Checks quorum,  β”‚     β”‚  PROPOSER_ROLE   β”‚
β”‚  at each block   β”‚     β”‚  threshold,      β”‚     β”‚  EXECUTOR_ROLE   β”‚
β”‚                  β”‚     β”‚  voting period   β”‚     β”‚  CANCELLER_ROLE  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The ERC20Votes Token

// Key concept: delegation activates voting power
// Holding tokens alone does NOT give voting power β€” you must delegate

import { ERC20, ERC20Votes, ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";

contract GovernanceToken is ERC20, ERC20Votes, ERC20Permit {
    constructor() ERC20("GovToken", "GOV") ERC20Permit("GovToken") {
        _mint(msg.sender, 1_000_000e18);
    }

    // Required overrides for ERC20Votes
    function _update(address from, address to, uint256 value)
        internal override(ERC20, ERC20Votes)
    {
        super._update(from, to, value);
    }

    function nonces(address owner)
        public view override(ERC20Permit, Nonces) returns (uint256)
    {
        return super.nonces(owner);
    }
}

Critical detail β€” checkpointing:

// ERC20Votes stores voting power snapshots at each block
// This is what prevents flash loan attacks

// When alice delegates to herself:
token.delegate(alice);  // at block 100

// Alice's voting power at block 100+: 1000 tokens
// If alice gets more tokens at block 200 (via flash loan):
// Her voting power at block 100 is still 1000 (historical snapshot)

// Governor uses getPastVotes(alice, proposalSnapshot):
uint256 votes = token.getPastVotes(alice, proposalSnapshot);
// proposalSnapshot = block when proposal was created
// Flash-borrowed tokens at block 200 don't count for a proposal created at block 100

Governor + Timelock Integration

import { Governor } from "@openzeppelin/contracts/governance/Governor.sol";
import { GovernorVotes } from "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
import { GovernorCountingSimple } from "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import { GovernorTimelockControl } from "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";

contract MyGovernor is Governor, GovernorVotes, GovernorCountingSimple, GovernorTimelockControl {

    constructor(IVotes _token, TimelockController _timelock)
        Governor("MyGovernor")
        GovernorVotes(_token)
        GovernorTimelockControl(_timelock)
    {}

    // Governance parameters β€” these define the security model
    function votingDelay() public pure override returns (uint256) {
        return 7200;    // ~1 day (in blocks, 12s/block)
    }

    function votingPeriod() public pure override returns (uint256) {
        return 50400;   // ~1 week
    }

    function proposalThreshold() public pure override returns (uint256) {
        return 10_000e18;  // need 10k tokens to propose
    }

    function quorum(uint256) public pure override returns (uint256) {
        return 100_000e18;  // 100k tokens must participate (10% of 1M supply)
    }
}

The Full Lifecycle in Code

// 1. PROPOSE β€” submit targets + values + calldatas + description
address[] memory targets = new address[](1);
targets[0] = address(myProtocol);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeCall(MyProtocol.setFee, (500)); // set fee to 5%

uint256 proposalId = governor.propose(
    targets, values, calldatas,
    "Proposal #1: Increase fee to 5%"
);
// Snapshot taken at this block β€” only current token holders can vote

// 2. VOTE β€” after votingDelay() blocks
governor.castVote(proposalId, 1);  // 0 = against, 1 = for, 2 = abstain

// 3. QUEUE β€” after votingPeriod() ends + quorum met + majority for
governor.queue(targets, values, calldatas, descriptionHash);
// β†’ Queued in TimelockController with delay

// 4. EXECUTE β€” after timelock delay expires
governor.execute(targets, values, calldatas, descriptionHash);
// β†’ TimelockController calls myProtocol.setFee(500)

πŸ’» Quick Try:

In Foundry, you can test the full governance lifecycle:

// In your test file:
function test_GovernanceLifecycle() public {
    // Setup: give alice tokens and have her delegate to herself
    token.transfer(alice, 200_000e18);
    vm.prank(alice);
    token.delegate(alice);
    vm.roll(block.number + 1); // checkpoint needs 1 block

    // Propose
    uint256 proposalId = governor.propose(targets, values, calldatas, "Set fee");

    // Advance past voting delay
    vm.roll(block.number + governor.votingDelay() + 1);

    // Vote
    vm.prank(alice);
    governor.castVote(proposalId, 1); // vote FOR

    // Advance past voting period
    vm.roll(block.number + governor.votingPeriod() + 1);

    // Queue in timelock
    governor.queue(targets, values, calldatas, keccak256("Set fee"));

    // Advance past timelock delay
    vm.warp(block.timestamp + timelock.getMinDelay() + 1);

    // Execute
    governor.execute(targets, values, calldatas, keccak256("Set fee"));

    // Verify the parameter changed
    assertEq(myProtocol.fee(), 500);
}

This test pattern is exactly what your Exercise 1 will use.

πŸ“– How to Study: OpenZeppelin Governor

  1. Start with ERC20Votes.sol β€” understand delegation and _checkpoints mapping
  2. Read Governor.propose() β€” how a proposal is created and the snapshot is taken
  3. Trace castVote() β†’ _countVote() β€” how votes are recorded and counted
  4. Follow queue() β†’ TimelockController.schedule() β€” how execution is delayed
  5. Study execute() β†’ TimelockController.execute() β€” how the timelock calls the target
  6. Read the access control: who can propose (threshold), who can execute (anyone after timelock)

πŸ” Code: OpenZeppelin Governor

πŸ’Ό Job Market Context

What DeFi teams expect you to know:

  1. β€œWalk through the lifecycle of an on-chain governance proposal.”
    • Good answer: β€œSomeone proposes a change, token holders vote, if it passes it goes through a timelock, then anyone can execute it.”
    • Great answer: β€œFive phases: (1) Propose β€” proposer submits targets, values, calldatas on-chain; a snapshot of all voting power is recorded. (2) Voting delay β€” 1-2 days for community review. (3) Active voting β€” holders cast for/against/abstain using power at the snapshot block, not current balance, preventing flash loan attacks. (4) Queue β€” if quorum met and majority for, queued in TimelockController with mandatory delay (24-48h) β€” the safety net giving users time to exit. (5) Execute β€” after timelock expires, anyone triggers execution. Total: 5-14 days.”

Interview Red Flags:

  • 🚩 Not knowing about snapshot-based voting and why it prevents flash loan attacks (votes recorded at proposal creation block, not at voting time)
  • 🚩 Not understanding why timelocks exist β€” it’s about user exit rights, not just β€œadding delay”
  • 🚩 Confusing delegation with voting β€” holding tokens alone does NOT give voting power; you must delegate first

Pro tip: In an interview, walk through the lifecycle with specific numbers (voting delay = 7200 blocks ~1 day, voting period = 50400 blocks ~1 week, timelock = 24-48h). Concrete parameters show you’ve actually configured governance, not just read about it.


🎯 Build Exercise: Governor + Timelock System

Workspace: workspace/src/part3/module8/

Build a complete on-chain governance system using OpenZeppelin Governor with TimelockController, and demonstrate that snapshot-based voting defeats flash loan attacks.

What you’ll implement:

  • GovernanceToken β€” ERC20Votes with delegation and checkpointing
  • MyGovernor β€” Governor with configurable voting delay, period, quorum, and threshold
  • Full proposal lifecycle: propose β†’ vote β†’ queue β†’ execute
  • Flash loan defense: prove snapshot voting blocks tokens acquired after proposal creation

Concepts exercised:

  • OpenZeppelin Governor framework integration
  • ERC20Votes delegation and checkpointing
  • TimelockController role configuration
  • The full governance lifecycle in code
  • Flash loan attack vector and why snapshots prevent it

🎯 Goal: Build production-standard governance and prove it’s secure against flash loan manipulation.

Run: forge test --match-contract GovernorTest -vvv


πŸ“‹ Summary: On-Chain Governance

βœ“ Covered:

  • Why governance exists: parameter updates, fee management, upgrades, emergency response
  • Proposal lifecycle: propose β†’ voting delay β†’ active vote β†’ queue in timelock β†’ execute
  • ERC20Votes: delegation activates voting power, checkpointing records snapshots per block
  • TimelockController: enforces execution delay, gives users exit rights before changes apply
  • Snapshot-based voting: prevents flash loan attacks by recording power at proposal creation
  • Quorum and threshold design: balancing spam prevention with accessibility
  • Governor + Timelock role configuration: proposer, executor, canceller

Next: ve-tokenomics and the Curve Wars β€” how vote-escrow transforms governance tokens into incentive-alignment tools.


πŸ’‘ ve-Tokenomics & the Curve Wars

πŸ’‘ Concept: Vote-Escrow: Locking for Influence

The ve (vote-escrow) model is one of DeFi’s most influential innovations. It transforms a governance token from a speculative asset into an incentive-alignment tool.

The veCRV Model

Lock CRV tokens for 1-4 years β†’ receive veCRV (non-transferable)

  Lock duration    veCRV per CRV
  ────────────     ─────────────
  4 years          1.00 veCRV      (maximum)
  2 years          0.50 veCRV
  1 year           0.25 veCRV
  1 week           0.0048 veCRV    (minimum)

πŸ” Deep Dive: veCRV Decay Math

The core formula β€” voting power decays linearly toward zero as the lock approaches expiry:

votingPower = lockedAmount Γ— (lockEnd - now) / MAX_LOCK_TIME

Where:
  lockedAmount = CRV tokens locked
  lockEnd = timestamp when lock expires
  MAX_LOCK_TIME = 4 years (126,144,000 seconds)
  now = current timestamp

Worked example:

Alice locks 10,000 CRV for 4 years:
  t = 0:   votingPower = 10,000 Γ— (4y - 0) / 4y = 10,000 veCRV
  t = 1y:  votingPower = 10,000 Γ— (4y - 1y) / 4y = 7,500 veCRV
  t = 2y:  votingPower = 10,000 Γ— (4y - 2y) / 4y = 5,000 veCRV
  t = 3y:  votingPower = 10,000 Γ— (4y - 3y) / 4y = 2,500 veCRV
  t = 4y:  votingPower = 10,000 Γ— (4y - 4y) / 4y = 0 veCRV (expired)

         veCRV
10,000 β”‚ ●
       β”‚   ●
 7,500 β”‚     ●
       β”‚       ●
 5,000 β”‚         ●
       β”‚           ●
 2,500 β”‚             ●
       β”‚               ●
     0 │─────────────────●──── time
       0    1y   2y   3y   4y

Why linear decay matters: It forces continuous re-locking. To maintain maximum voting power, you must keep extending your lock. This ensures that voters have ongoing skin in the game β€” they can’t vote and then immediately unlock.

In Solidity (simplified from Curve’s VotingEscrow.vy):

contract SimpleVotingEscrow {
    struct LockedBalance {
        uint256 amount;
        uint256 end;      // lock expiry timestamp
    }

    uint256 public constant MAX_LOCK = 4 * 365 days;
    mapping(address => LockedBalance) public locked;

    function createLock(uint256 amount, uint256 duration) external {
        require(duration >= 1 weeks && duration <= MAX_LOCK, "Invalid duration");
        require(locked[msg.sender].amount == 0, "Already locked");

        locked[msg.sender] = LockedBalance({
            amount: amount,
            end: block.timestamp + duration
        });

        token.transferFrom(msg.sender, address(this), amount);
    }

    function votingPower(address user) public view returns (uint256) {
        LockedBalance memory lock = locked[user];
        if (block.timestamp >= lock.end) return 0;
        return lock.amount * (lock.end - block.timestamp) / MAX_LOCK;
    }

    function withdraw() external {
        require(block.timestamp >= locked[msg.sender].end, "Still locked");
        uint256 amount = locked[msg.sender].amount;
        delete locked[msg.sender];
        token.transfer(msg.sender, amount);
    }
}

Three Powers of veCRV

1. Gauge voting β€” directing emissions:

Each Curve pool has a "gauge" that receives CRV emissions.
veCRV holders vote weekly on how to distribute emissions:

  Pool A (3CRV):     40% of votes β†’ 40% of weekly CRV emissions
  Pool B (stETH/ETH): 35% of votes β†’ 35% of weekly CRV emissions
  Pool C (FRAX/USDC):  25% of votes β†’ 25% of weekly CRV emissions

More emissions β†’ more rewards for LPs β†’ more liquidity β†’ deeper pool

2. Boosted LP rewards β€” up to 2.5x:

Base LP yield: 5% APR
With max boost (sufficient veCRV): 12.5% APR (2.5x)

Boost formula (simplified):
  boost = min(2.5, (0.4 * userLiquidity + 0.6 * totalLiquidity * (userVeCRV / totalVeCRV)) / userLiquidity)

Translation: your boost depends on your share of veCRV relative to your share of the pool

3. Protocol fee sharing β€” 50% of trading fees:

veCRV holders receive 50% of all Curve trading fees, distributed in 3CRV (the stablecoin LP token).

The Curve Wars

The gauge voting power creates a competitive market:

Protocol wants deep liquidity for its token pair on Curve
  β†’ Needs CRV emissions directed to its pool's gauge
  β†’ Options:
    1. Buy CRV, lock as veCRV, vote for own pool (expensive)
    2. Bribe existing veCRV holders to vote for their pool (cheaper!)

Enter Convex Finance:
  β†’ Aggregates CRV from thousands of users
  β†’ Locks ALL of it as veCRV (permanently!)
  β†’ vlCVX holders vote on how Convex directs its massive veCRV position
  β†’ Meta-governance: controlling Convex = controlling Curve emissions

The bribery market:
  Protocol pays $1 in bribes to vlCVX holders
  β†’ Those voters direct $1.50+ of CRV emissions to the protocol's pool
  β†’ Protocol gets $1.50 of liquidity incentives for $1 spent
  β†’ Voters earn $1 for voting (which is free for them)
  β†’ Everyone wins β€” the "Curve Wars" flywheel

  Bribe platforms: Votium, Hidden Hand, Paladin

Why this matters: The Curve Wars demonstrate that governance is not just about β€œvoting on proposals” β€” it’s an economic game where voting power has direct monetary value. Understanding this is essential for designing tokenomics.

Velodrome/Aerodrome: ve(3,3)

The ve(3,3) model fixes Curve’s incentive misalignment:

Curve's problem:
  veCRV holders earn fees from ALL pools, regardless of which pools they vote for
  β†’ Misaligned: voters can vote for bribed pools even if those pools generate no fees

Velodrome's fix:
  veVELO holders earn fees ONLY from pools they vote for
  β†’ Direct alignment: vote for high-volume pools = earn more fees
  β†’ No need for bribes on high-volume pools β€” the fees ARE the incentive
  β†’ Bribes only needed for new/low-volume pools that need bootstrapping

Anti-dilution: Voters receive proportional new emissions as a rebase. Non-voters get diluted over time. This incentivizes continuous participation.

Result: Velodrome is the dominant DEX on Optimism; Aerodrome is the dominant DEX on Base. The ve(3,3) model works especially well on L2s (Module 7 connection) because the cheap gas makes weekly voting/claiming practical for all users, not just whales.

πŸ”— DeFi Pattern Connection

Governance tokenomics across the curriculum:

PatternWhere It AppearsModule
Token-weighted votingUniswap, Aave, Compound governanceThis module
Vote-escrow (ve)Curve, Velodrome, AerodromeThis module
Gauge emissionsDirecting liquidity incentivesThis module, P2M2
Protocol-owned liquidityTreasury as strategic assetThis module
Immutable governanceLiquity zero-governanceThis module, P2M9 capstone
Cross-chain governanceVote on L1, execute on L2Modules 6, 7
Emergency shutdownMakerDAO ESMPart 2 Module 6

πŸ’Ό Job Market Context

What DeFi teams expect you to know:

  1. β€œHow does vote-escrow prevent governance manipulation?”

    • Good answer: β€œUsers lock tokens for a period, so they can’t flash-borrow or quickly acquire and dump voting power.”
    • Great answer: β€œThree layers of resistance: (1) Time commitment β€” tokens locked 1-4 years, making flash-borrow impossible. An attacker must buy AND lock tokens, creating massive economic exposure. (2) Linear decay β€” power decreases as the lock approaches expiry, forcing continuous re-locking. (3) Incentive alignment β€” voters earn protocol fees, so voting against the protocol’s interest reduces their own revenue. The formula amount * (lockEnd - now) / maxLock means 10,000 tokens locked 4 years = 40,000 tokens locked 1 year β€” the market prices this cost.”
  2. β€œExplain the Curve Wars β€” what are protocols competing for?”

    • Good answer: β€œProtocols compete for veCRV votes that direct CRV emissions to their pools, attracting liquidity.”
    • Great answer: β€œCurve emits CRV to LPs, but allocation depends on weekly gauge votes by veCRV holders. More emissions = higher LP rewards = more liquidity. Convex aggregates CRV permanently as veCRV; vlCVX holders control Convex’s votes, creating meta-governance. On Votium, protocols pay $1 in bribes to vlCVX holders, directing ~$1.50 of CRV emissions β€” profitable for both sides. The key insight: governance voting power has quantifiable economic value, and a market naturally forms around it.”

Interview Red Flags:

  • 🚩 Thinking governance is β€œjust voting” β€” ve-tokenomics proves it’s a complex economic system where voting power has direct monetary value
  • 🚩 Not knowing the decay formula (amount * (lockEnd - now) / maxLock) β€” this is the core mechanic that forces ongoing commitment
  • 🚩 Being unable to explain why Velodrome’s ve(3,3) improves on Curve’s model (voters earn fees only from pools they vote for)

Pro tip: If asked about tokenomics design, explaining the Curve Wars flywheel (lock β†’ vote β†’ bribe β†’ emissions β†’ liquidity) shows you understand governance as economic infrastructure, not just a voting mechanism. This perspective is what separates senior from junior DeFi engineers.


🎯 Build Exercise: Vote-Escrow Token

Workspace: workspace/src/part3/module8/

Build a simplified ve-token with time-weighted voting power, linear decay, and gauge-style emission allocation.

What you’ll implement:

  • createLock() β€” lock tokens for a specified duration (1 week to 4 years)
  • votingPower() β€” calculate current voting power with linear decay
  • increaseAmount() β€” add more tokens to an existing lock
  • increaseUnlockTime() β€” extend lock duration
  • voteForGauge() β€” allocate voting power to a gauge (emission target)
  • withdraw() β€” reclaim tokens after lock expires

Concepts exercised:

  • Vote-escrow mechanics (lock β†’ power β†’ decay)
  • Linear decay formula: amount Γ— (lockEnd - now) / maxLock
  • Gauge voting and weight allocation
  • The incentive structure that makes ve-tokenomics work
  • Why time-locking prevents governance manipulation

🎯 Goal: Build the core of a Curve-style vote-escrow system and understand why lock duration creates genuine skin-in-the-game for governance participants.

Run: forge test --match-contract VoteEscrowTest -vvv


πŸ“‹ Summary: ve-Tokenomics

βœ“ Covered:

  • Vote-escrow model: lock tokens for 1-4 years to receive non-transferable voting power
  • Linear decay formula: amount * (lockEnd - now) / maxLock forces continuous re-locking
  • Three powers of veCRV: gauge voting (directing emissions), boosted LP rewards (2.5x), fee sharing
  • The Curve Wars: Convex aggregation, vlCVX meta-governance, bribery markets (Votium, Hidden Hand)
  • Velodrome/Aerodrome ve(3,3): voters earn fees only from pools they vote for, fixing Curve’s incentive misalignment
  • L2 connection: cheap gas makes weekly voting/claiming practical for all participants, not just whales

Next: Governance security β€” how governance itself becomes an attack surface, from the Beanstalk exploit to emergency mechanisms.


⚠️ Governance Security

πŸ’‘ Concept: When Governance Itself Is the Attack Surface

The Beanstalk Attack ($182M, April 2022)

The most expensive governance attack in DeFi history β€” and entirely preventable.

What happened:

Beanstalk's governance had NO voting delay and NO timelock.
A proposal could be created, voted on, and executed in ONE transaction.

Attack:
1. Attacker flash-borrowed massive governance tokens from Aave + SushiSwap
2. Created a malicious proposal: "Transfer entire treasury to attacker"
3. Voted FOR with flash-borrowed tokens (overwhelming majority)
4. Proposal passed immediately (no quorum issues β€” massive tokens)
5. Executed the proposal in the SAME transaction
6. Returned flash-borrowed tokens
7. Kept $182M of drained treasury

Total time: 1 Ethereum transaction (~13 seconds)

Why it worked: No voting delay meant tokens acquired in the same block could vote. No timelock meant the proposal executed immediately. Flash loans provided unlimited temporary capital.

What would have prevented it:

Defense 1: SNAPSHOT-BASED VOTING (OpenZeppelin default)
  β†’ Voting power recorded at proposal creation block
  β†’ Tokens acquired AFTER snapshot don't count
  β†’ Flash-borrowed tokens are acquired after the proposal exists
  β†’ Attack fails: flash tokens have zero voting power

Defense 2: VOTING DELAY (1+ blocks)
  β†’ Gap between proposal creation and voting start
  β†’ Flash loan must span multiple blocks (impossible β€” single-tx only)
  β†’ Attack fails: can't vote in the proposal creation transaction

Defense 3: TIMELOCK
  β†’ Even if proposal passes, execution delayed 24-48h
  β†’ Community can review and respond
  β†’ Users can exit before malicious changes take effect
  β†’ Attack fails: treasury drain is visible and can be countered

Production protocols use ALL THREE. Beanstalk had NONE.

πŸ’» Quick Try:

In Foundry, prove that snapshot voting defeats flash loans:

contract FlashLoanDefenseDemo {
    // ERC20Votes token uses checkpoints β€” votes are recorded per block

    function test_flashLoanCantVote() public {
        // Block 100: proposal created, snapshot = block 100
        uint256 proposalId = governor.propose(...);

        // Block 100 (same block): attacker flash-borrows tokens
        // attacker's balance at block 100 BEFORE the borrow = 0
        // getPastVotes(attacker, block 100) = 0  ← checkpoint was 0!

        // Advance to voting period
        vm.roll(block.number + governor.votingDelay() + 1);

        // Attacker tries to vote β€” but has 0 votes at snapshot
        vm.prank(attacker);
        // governor.castVote(proposalId, 1);  ← would have 0 weight

        // Defense works: tokens acquired after snapshot have no power
    }
}

Other Governance Attack Vectors

Delegation attacks:

  • Accumulate delegated voting power from many small holders through social engineering
  • Vote maliciously before delegators can react and re-delegate
  • Defense: delegation monitoring, delegation caps, delegation lockup periods

Low-quorum exploitation:

  • Wait for low participation period (holidays, market crisis)
  • Pass controversial proposal with minimal opposition
  • Defense: adequate quorum thresholds, emergency guardian pause

Governance extraction:

  • Whale accumulates enough voting power to pass self-serving proposals
  • Example: redirect treasury funds to themselves, change fee structure
  • Defense: timelock (users can exit), guardian multisig (can veto), vote-escrow (long-term alignment)

Emergency Mechanisms

Production protocols combine governance with fast-response capabilities:

/// @notice Emergency guardian β€” can pause but NOT upgrade
contract EmergencyGuardian {
    address public guardian;  // multisig (e.g., 3/5 team members)
    IProtocol public protocol;

    // Guardian can PAUSE β€” immediate response to exploits
    function pause() external onlyGuardian {
        protocol.pause();
    }

    // Guardian can UNPAUSE β€” resume normal operation
    function unpause() external onlyGuardian {
        protocol.unpause();
    }

    // Guardian CANNOT: upgrade contracts, change parameters, move funds
    // Those require full governance (Governor + Timelock)
}

The pattern used by major protocols:

Aave:
  Guardian multisig β†’ can pause markets (fast, centralized)
  Governor + Timelock β†’ parameter changes, upgrades (slow, decentralized)

MakerDAO:
  Emergency Shutdown Module (ESM) β†’ anyone can trigger with enough MKR
  Governance β†’ parameter changes, new collateral types
  (Requires depositing MKR into ESM β€” tokens are burned, so it's costly to trigger)

Compound:
  Pause Guardian β†’ can pause individual markets
  Governor Bravo β†’ all parameter and upgrade changes

πŸ’Ό Job Market Context

What DeFi teams expect you to know:

  1. β€œHow did the Beanstalk governance attack work, and how would you prevent it?”
    • Good answer: β€œThe attacker flash-borrowed tokens, voted on a malicious proposal, and executed it all in one transaction. Prevention: snapshot voting and timelocks.”
    • Great answer: β€œBeanstalk had three fatal flaws: no snapshot voting (tokens acquired in the same block could vote), no voting delay (voting started immediately), and no timelock (proposals executed instantly). The attacker flash-borrowed $1B of tokens, created a proposal to drain the treasury, voted FOR, and executed it β€” all in one transaction ($182M loss). Standard OpenZeppelin Governor defaults prevent this entirely: snapshot voting means tokens must be held BEFORE proposal creation; voting delay prevents same-block voting; timelock delays execution 24-48h. The lesson: governance security is as critical as smart contract security.”

Interview Red Flags:

  • 🚩 Not knowing the Beanstalk attack β€” it’s the most important governance case study in DeFi ($182M, entirely preventable)
  • 🚩 Not being able to name the three defenses (snapshot voting, voting delay, timelock) and why each is necessary
  • 🚩 Thinking emergency mechanisms (guardian pause) and governance (Governor + Timelock) are the same thing β€” the guardian can pause but CANNOT upgrade or move funds

Pro tip: In interviews, showing awareness that governance is both a feature AND an attack surface immediately sets you apart. Most candidates think about governance from the β€œhow do we vote” perspective. Senior candidates think from β€œhow can this be exploited, and how do we minimize the attack surface.”


πŸ’‘ Governance Minimization

πŸ’‘ Concept: Less Governance Can Be Better

Every governable parameter is an attack surface. The more things governance can change, the more ways the protocol can be exploited or manipulated.

The Spectrum

FULL GOVERNANCE ──────────────────────────────── IMMUTABLE
  β”‚                    β”‚                    β”‚            β”‚
  Multisig          Governor +           Minimal       Zero
  (most agile)      Timelock            governance    governance
  β”‚                    β”‚                    β”‚            β”‚
  Team controls     Token holders       Only critical  Nothing
  everything        decide              params         changeable

  Risk: rug pull    Risk: slow to       Risk: can't    Risk: can't
                    respond             adapt quickly  fix bugs

  Example:          Example:            Example:       Example:
  Early protocols   Aave, Compound      Uniswap V2     Liquity

Liquity: Zero Governance

Liquity's approach:
  βœ“ All parameters hardcoded at deployment
  βœ“ Contracts are immutable (no proxy, no admin key)
  βœ“ Minimum collateral ratio: always 110%
  βœ“ Borrowing fee: algorithmic (not governed)
  βœ“ No admin, no multisig, no governance token

  Advantage: maximum trustlessness β€” "code is law" fully realized
  Disadvantage: can't fix bugs, can't adapt to market changes

  When this works: simple protocols with well-tested parameters
  When this doesn't: complex protocols that need ongoing tuning

Connection to Part 2 Module 9: Your capstone stablecoin was designed as immutable β€” no admin keys, no governance. This was a deliberate design choice that eliminates governance attack surfaces at the cost of adaptability.

Progressive Decentralization

Most protocols follow a maturation path:

Phase 1: MULTISIG (launch)
  Team controls everything via 3/5 or 4/7 multisig
  Fast iteration, bug fixes, parameter tuning
  Users must trust the team

Phase 2: GOVERNOR + TIMELOCK (growth)
  Token holders vote on changes
  Timelock gives users exit rights
  Team retains emergency guardian role

Phase 3: MINIMIZE GOVERNANCE (maturity)
  Reduce governable parameters over time
  Hardcode well-tested values
  Remove upgrade capability where possible
  Eventually: only emergency pause + critical parameters remain

Compound's progression:
  Admin key β†’ Governor Alpha β†’ Governor Bravo β†’ community governance
  Each step reduced team control and increased decentralization

The right question isn’t β€œgovernance or not” β€” it’s β€œwhat SHOULD be governable?”

SHOULD be governable:
  βœ“ Risk parameters (LTV, liquidation thresholds) β€” markets change
  βœ“ Fee levels β€” competitive dynamics
  βœ“ New asset listings β€” protocol growth
  βœ“ Emergency pause β€” security response

SHOULD NOT be governable (hardcode):
  βœ— Core accounting math β€” getting this wrong breaks everything
  βœ— Access control invariants β€” "only the borrower can repay their loan"
  βœ— Token supply (usually) β€” governance shouldn't be able to inflate supply

πŸ’Ό Job Market Context

What DeFi teams expect you to know:

  1. β€œWhat are the tradeoffs between governance and immutability?”
    • Good answer: β€œGovernance allows protocols to adapt but introduces attack surfaces. Immutability is more trustless but can’t fix bugs.”
    • Great answer: β€œThe spectrum runs from full governance (multisig) through token-based governance (Governor + Timelock) to zero governance (Liquity β€” no admin keys, no upgradability). Full governance enables rapid response but every governable parameter is an attack surface. Zero governance eliminates these risks but can’t adapt. The optimal approach is progressive decentralization: start with multisig, transition to token governance as the protocol matures, then systematically reduce what’s governable. The key principle: only make governable what MUST change β€” core accounting math should be immutable; risk parameters should be governable.”

Interview Red Flags:

  • 🚩 Treating all governance as good or all governance as bad β€” it’s a spectrum with real tradeoffs at every point
  • 🚩 Not being able to distinguish what SHOULD be governable (risk parameters, fees) from what should NOT (core accounting math, access control invariants)
  • 🚩 Not knowing about progressive decentralization β€” the standard maturation path from multisig to Governor to minimized governance

Pro tip: When asked β€œhow would you design governance for X protocol?”, frame your answer around what should be governable vs immutable, then describe the maturation path. This shows you think about governance as a design discipline, not just β€œadd a Governor contract.”


πŸ“‹ Summary: Governance & DAOs

βœ“ Covered:

  • On-chain governance: why it exists, the fundamental tension of decentralization vs agility
  • OpenZeppelin Governor: ERC20Votes, Governor, TimelockController β€” the full stack with code
  • Proposal lifecycle: propose β†’ delay β†’ vote β†’ queue β†’ execute
  • ve-tokenomics: veCRV model with decay math, gauge voting, boost, fee sharing
  • The Curve Wars: Convex meta-governance, bribery markets, the economics of voting power
  • Velodrome/Aerodrome ve(3,3): the incentive-alignment fix to Curve’s model
  • Governance security: Beanstalk attack deep dive, flash loan defenses, emergency mechanisms
  • Governance minimization: the spectrum from multisig to immutable, progressive decentralization

  • Token voting β†’ P2 M1 ERC-20 extensions, ERC20Votes delegation
  • Flash loan governance attacks β†’ P2 M5 flash loans for vote manipulation
  • Timelock patterns β†’ P1 M6 proxy upgrades via governance, admin controls
  • ve-tokenomics β†’ P2 M2 Curve AMM, gauge voting for liquidity direction
  • Security patterns β†’ P2 M8 access control, multisig validation
  • Treasury management β†’ P2 M7 vault strategies for DAO treasury yield

πŸ“– Production Study Order

  1. OpenZeppelin Governor.sol β€” proposal lifecycle, counting modules
  2. OpenZeppelin TimelockController.sol β€” delayed execution, role management
  3. Compound GovernorBravo β€” historical reference, delegation mechanics
  4. Curve VotingEscrow.vy β€” original ve implementation, decay math
  5. Convex CvxLocker β€” vlCVX vote locking, reward distribution
  6. Velodrome VotingEscrow β€” ve(3,3) implementation, rebasing
  7. MakerDAO DSChief β€” hat-based governance, historical significance

πŸ“š Resources

Production Code

Documentation

Key Reading

πŸ“– How to Study: DeFi Governance

  1. Start with OpenZeppelin Governor Guide β€” deploy a test governor in Foundry
  2. Read ERC20Votes.sol β€” understand checkpointing (this is what prevents flash loan attacks)
  3. Study the Beanstalk post-mortem β€” the most important governance attack
  4. Read Vitalik’s governance post β€” understand the limitations of token voting
  5. Explore Curve DAO docs β€” understand ve-tokenomics
  6. Read a16z governance minimization β€” the design philosophy

Navigation: ← Module 7: L2-Specific DeFi | Part 3 Overview | Next: Module 9 β€” Capstone β†’