Skip to main content
Sigvex

veToken Governance

Detects vulnerabilities in vote-escrowed token governance systems including flash-vote attacks, lock manipulation, and decay bypass.

veToken Governance

Overview

The veToken governance detector identifies vulnerabilities in vote-escrowed (ve) token systems where users lock tokens for a period to gain voting power. These systems, popularized by the stable-swap AMM’s veCRV, are susceptible to flash-vote attacks, decay manipulation, lock extension bypasses, and checkpoint gaming.

Why This Is an Issue

veToken governance depends on a time-weighted locking mechanism: longer locks yield more voting power, which decays linearly toward zero. Flaws in this model allow:

  • Flash governance: Acquiring voting power just before a snapshot, voting, then exiting immediately.
  • Decay bypass: Extending lock duration without resetting the decay curve, inflating voting power.
  • Checkpoint manipulation: Triggering checkpoint writes at favorable times to capture inflated balances.

The Beanstalk governance attack ($182M, 2022) exploited flash-loaned governance tokens to pass a malicious proposal in a single transaction. While Beanstalk did not use veTokens specifically, the attack pattern applies directly to veToken systems with inadequate snapshot protection.

How to Resolve

// Before: Voting power based on current lock state -- flash-votable
function getVotingPower(address user) public view returns (uint256) {
    Lock memory lock = locks[user];
    if (lock.end <= block.timestamp) return 0;
    return lock.amount * (lock.end - block.timestamp) / MAX_LOCK;
}

// After: Voting power based on historical checkpoint -- flash-resistant
function getVotingPower(address user, uint256 timestamp) public view returns (uint256) {
    require(timestamp < block.timestamp, "Future lookup");
    Checkpoint memory cp = _findCheckpoint(user, timestamp);
    if (cp.lockEnd <= timestamp) return 0;
    return cp.amount * (cp.lockEnd - timestamp) / MAX_LOCK;
}

Examples

Vulnerable Code

contract VulnerableVeToken {
    struct Lock { uint256 amount; uint256 end; }
    mapping(address => Lock) public locks;

    function vote(uint256 proposalId) external {
        // Vulnerable: reads current state, not historical checkpoint
        uint256 power = getVotingPower(msg.sender);
        require(power > 0, "No voting power");
        _castVote(proposalId, msg.sender, power);
    }

    // Extend lock without proper decay reset
    function extendLock(uint256 newEnd) external {
        Lock storage lock = locks[msg.sender];
        require(newEnd > lock.end, "Must extend");
        lock.end = newEnd; // Does not reset decay -- inflated power
    }
}

Fixed Code

contract SafeVeToken {
    mapping(address => Checkpoint[]) public checkpoints;

    function vote(uint256 proposalId) external {
        // Use snapshot from proposal creation block -- immune to flash attacks
        uint256 snapshotTime = proposals[proposalId].snapshotTimestamp;
        uint256 power = getVotingPower(msg.sender, snapshotTime);
        require(power > 0, "No voting power at snapshot");
        _castVote(proposalId, msg.sender, power);
    }

    function extendLock(uint256 newEnd) external {
        Lock storage lock = locks[msg.sender];
        require(newEnd > lock.end, "Must extend");
        _writeCheckpoint(msg.sender); // Record state before change
        lock.end = newEnd;
    }
}

Sample Sigvex Output

{
  "detector_id": "vetoken-governance",
  "severity": "high",
  "confidence": 0.68,
  "description": "Voting power function at offset 0x2a4 reads current lock state without checkpoint-based historical lookup. Flash-vote attacks can acquire and exercise governance power within a single transaction.",
  "location": { "function": "vote(uint256)", "offset": 676 }
}

Detection Methodology

  1. Lock pattern identification: Detects time-locked balance structures (amount + end timestamp).
  2. Voting power analysis: Traces how voting power is calculated – current state vs. historical checkpoints.
  3. Decay curve validation: Checks whether lock extensions properly reset or adjust the decay function.
  4. Checkpoint coverage: Verifies that state-changing operations write checkpoints before mutations.

Limitations

  • Cannot evaluate the economic viability of flash-vote attacks (requires off-chain market analysis).
  • Governance systems using off-chain voting (Snapshot) are not analyzed.
  • Complex multi-token governance with delegation chains may produce false negatives.

References