# Migration::withdrawContribution falsely assumes that user should get exactly his original contribution back

Migration.withdrawContribution refunds each user's original contribution, but Buyout returns a reduced pool after buyFractions, causing deficits, reverts, and theft of other users' funds.

- Payout: Contest reward
- Severity: high
- Program: Fractional
- Credited: kenzo
- Disclosed: 2022-09-27
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-07-fractional-findings/issues/375
- Library page: https://bugbountylibrary.com/r/c4%3A2022-07-fractional%3AH-05
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** Migration.sol withdrawContribution / Buyout settlement
**Root cause:** Migration stores each user's original ETH/fraction contribution and refunds exactly that on withdrawal, but after a committed proposal interacts with Buyout the returned pool is smaller than the sum of contributions, so the accounting no longer matches actual balances.
**Steps:**
1. User calls joinProposal; Migration records `userProposalEth[id][user] += msg.value` and `userProposalFractions[id][user] += _amount`.
2. Another user calls `Buyout.buyFractions`, which transfers fractions out of the pool, reducing `IERC1155(token).balanceOf(address(this), id)`.
3. Proposal fails; Buyout sends back only the remaining `tokenBalance` and reduced ETH to Migration.
4. User calls `withdrawContribution`; Migration transfers `userProposalFractions[id][user]` fractions and `userProposalEth[id][user]` ETH.
5. If pool is short, transfer reverts for insufficient balance; if other users' funds remain, the caller drains them.
**How found:** Code reading of Migration's join/withdraw accounting versus Buyout's buyFractions and failed-proposal settlement, noticing the refunded amount is fixed to original contribution rather than a pro-rata share of the returned pool.
**Exploitation:** Attacker contributes to a proposal, waits for other users to contribute and for buyFractions to reduce the pool, then calls withdrawContribution to pull out their original amount, taking other users' remaining ETH/fractions; honest users later cannot withdraw due to deficit.
**Insight:** When a module refunds users from a shared pool that a downstream contract can shrink (buyouts, partial fills, fees), verify the refund is computed from actual returned balances/pro-rata shares, not from per-user stored original deposits.
