# A malicious user can prevent vote creation at almost no cost

Attacker front-runs createVote by transferring wstETH to Controller, inflating aEthValue past dEthValue and reverting the vote, then withdraws via earningWithdraw.

- Payout: Contest reward
- Severity: high
- Program: Goat
- Credited: b0g0
- Disclosed: 2024-06-12
- Write-up quality: 7/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_goat_mar2024.pdf#3.1.14
- Library page: https://bugbountylibrary.com/r/cantina%3Agoat_mar2024%3A3.1.14
- Tags: domain:web3 impact:dos kind:contest platform:cantina sev:high tech:evm tech:solidity technique:race-window technique:source-review vuln:business-logic vuln:dos vuln:governance

**Target:** Controller.createVote / Voting.sol on Arbitrum
**Root cause:** createVote derives attacker stake aEthValue from _geth.balanceOf(address(this)) instead of a user-supplied parameter, so any direct wstETH transfer to Controller inflates it and breaks the aEthValue <= dEthValue_ check.
**Steps:**
1. Attacker observes victim's pending `createVote(defender, dEthValue, ...)` tx.
2. Front-run: `WSTETH.transfer(address(controller), dEthValue - aEthValue + 1 wei)` so Controller balance exceeds dEthValue_.
3. Victim's `createVote` reverts with `aEthValue invalid`.
4. Back-run: call `controller.earningWithdraw(true, 0, payable(attacker), 0)` to unwrap and recover the wstETH, costing only gas.
**How found:** Code review of createVote's validation: noticed aEthValue is read from Controller's live wstETH balance rather than a caller-supplied value, making it manipulable by direct transfer; confirmed with a Foundry fork test showing revert and refund.
**Exploitation:** Sandwich the victim's createVote: transfer wstETH into Controller to push aEthValue above dEthValue_, causing revert, then immediately call earningWithdraw to unwrap and reclaim the funds. Net cost is gas only, permanently blocking reputation challenges against a defender.
**Insight:** When a contract derives a user's stake from its own token balance, any external transfer can grief checks comparing that balance to a target amount; look for balanceOf(address(this)) used in validation logic.
