# tCDP: broken transferFrom allowance check drains holders

tCDP's transferFrom checked the wrong allowance mapping, letting an attacker self-approve and drain all tokens from three holders, then burn them to redeem Compound ETH.

- Payout: Real-world hack
- Severity: high
- Program: tcdp
- Credited: DeFiHackLabs
- Disclosed: 2025-04-28
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2025-04/tcdp_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20250428-tcdp
- Tags: domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm tech:solidity technique:source-review vuln:access-control vuln:business-logic

**Target:** ERC-20 token contract (tCDP) with Compound redemption
**Root cause:** transferFrom debits _allowed[msg.sender][to] after moving tokens from the supplied `from` address, instead of checking _allowed[from][msg.sender], so the caller's own allowance to itself is irrelevant to the victim's funds.
**Steps:**
1. Swap 0.1 ETH to DAI via Uniswap V2 router.
2. Approve tCDP for max DAI and call `tCDP.approve(address(this), type(uint256).max)` to self-approve.
3. For each holder, call `tCDP.transferFrom(holder, address(this), balanceOf(holder))` — succeeds because allowance check uses wrong mapping.
4. Call `tCDP.burn(stolenTcdp)` to redeem underlying Compound ETH collateral.
5. Swap remaining DAI back to ETH via router and forward profit to attacker.
**How found:** Source review of the token's transferFrom revealed the allowance was debited from _allowed[msg.sender][to] rather than _allowed[from][msg.sender], making any holder's balance drainable by a self-approved caller.
**Exploitation:** Attacker funded 0.1 ETH, bought DAI, self-approved tCDP, then used the broken transferFrom to pull the entire tCDP balance of three unrelated holders. Burning the stolen tCDP redeemed the protocol's Compound ETH collateral, which was swapped back to ETH for ~2.02 ETH profit.
**Insight:** When auditing ERC-20 forks, diff transferFrom against the canonical implementation and check which mapping key is debited — a self-approve plus transferFrom(victim,...) is the fastest confirmation of a swapped-allowance bug.
