Clawditor
← all research
post-mortemcritical$4.8M lost

X100 Governance Takeover: Flash-Loaned Senate Seats Drain $4.85M

Clawditor Research·Published Oct 25, 2026·Incident Oct 24, 2026
X100

An attacker flash-loaned X tokens to seize two of three 'Senate' governance seats in a single transaction, then drained $4.85M USDT from the X100 treasury and Uniswap V3 pool. The root cause was a governance design with no snapshot mechanism, no stake-age requirement, and no timelock on proposal execution.

Root Cause

X100's on-chain 'Senate' governance assigned seats to the addresses with the largest current frozen token balances, accepted new members for a flat fee of 100 X, and allowed proposals to be voted on and executed within the same transaction. There was no balance-snapshot from a prior block, no minimum stake age, and no timelock. A flash loan could therefore temporarily inflate a single address's frozen balance, buy a Senate seat instantly, submit a treasury-drain proposal, vote it through (2/3 majority), execute it, and repay the loan — all atomically.

// Reconstructed Senate seat assignment (from on-chain bytecode analysis)
function joinSenate() external {
    require(frozenBalance[msg.sender] >= SENATE_MIN); // checked at call time
    // Seat granted based on *current* balance — no snapshot
    _assignSeat(msg.sender, frozenBalance[msg.sender]);
}

// No timelock on execution
function executeProposal(uint256 id) external {
    require(_senateApproved(id)); // 2-of-3 sufficient
    _execute(proposals[id].target, proposals[id].data); // immediate
}

Attack Steps

#Action
1Flash-borrow large X balance
2Freeze X tokens → become top frozen-balance holder
3Pay 100 X fee; receive 2 of 3 Senate seats immediately
4Submit proposal: transfer 400,000 X from treasury to attacker-controlled LP
5Vote yes + execute in same transaction (no timelock)
6Sell treasury X into self-created LP → realise ~$4.75M USDT
7Second attacker destroys Uniswap V3 Freezer position via same governance path
8Third transaction drains ~550,000 X from the depleted Uniswap pool
9Repay flash loan; net ~$4.85M USDT

Impact

  • ~$4.85M USDT drained across three separate exploit transactions on Ethereum mainnet
  • Treasury emptied, Uniswap V3 liquidity position destroyed
  • Multiple independent attackers exploited the same governance flaw within hours
  • X100 token price collapsed; project liquidity is effectively gone

Lessons for Auditors

  1. Governance snapshot required. Voting power must be determined from a balance snapshot taken at a fixed block before the proposal was created — never from the current balance. ERC20Votes (getPastVotes) is the standard pattern.
  2. Timelock is not optional. Any proposal that moves treasury funds or upgrades contracts must pass through a timelock contract (minimum 24–48 hours for high-value protocols).
  3. Seat assignment with instant effect. Any governance structure that grants immediate control based on real-time on-chain state is flash-loan attackable. Require stake age (e.g., tokens frozen for ≥ 1 epoch) before seat eligibility.
  4. Governance + flash loans = critical risk. When reviewing on-chain governance, explicitly test whether a sufficiently large flash loan can reach quorum. If it can, the protocol is broken by design.
  5. Consider OpenZeppelin Governor. It ships snapshot voting, configurable quorum, and TimelockController as composable, audited primitives.
attack patterns
governanceflashloansdefi-ammaccess-control
sources