# IdolsNFT: self-transfer reward loop drains stETH

IdolsNFT reward logic credited stETH rewards on self-transfers, letting an attacker loop safeTransferFrom to drain 97 stETH.

- Payout: Real-world hack
- Severity: high
- Program: IdolsNFT
- Credited: DeFiHackLabs
- Disclosed: 2025-01-14
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2025-01/IdolsNFT_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20250114-idolsnft
- Tags: domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic vuln:reward-distribution

**Target:** ERC721 reward-bearing NFT contract
**Root cause:** Reward accounting credits both sender and receiver on every NFT transfer, so transferring a token to oneself mints rewards without moving the token.
**Steps:**
1. Attacker transfers NFT token ID 940 to a freshly computed attack contract address.
2. Deploy AttackContract; constructor runs before code is at the address, bypassing Address.isContract() checks.
3. Loop up to 2000 times: read `allocatedStethRewards()` and `rewardPerGod()`; break if rewardPerGod > totalRewards.
4. Each iteration call `safeTransferFrom(address(this), address(this), TOKEN_ID)` to self-credit rewards.
5. After loop, transfer accumulated stETH balance to attacker and return NFT via `safeTransferFrom(address(this), ATTACKER, TOKEN_ID)`.
6. Approve attacker addresses for max stETH and `selfdestruct` to recover ETH.
7. Repeat the process 15 times across blocks 21624128–21626362.
**How found:** Likely spotted that reward distribution credits both transfer parties, making a self-transfer a zero-cost reward mint; constructor deployment evades contract checks.
**Exploitation:** Attacker used a constructor-deployed contract to bypass isContract checks, then repeatedly self-transferred the same NFT to inflate reward accounting, harvesting stETH each cycle and sweeping funds back to the EOA.
**Escalation:** Repeated the attack 15 times over multiple blocks to accumulate 97 stETH total.
**Insight:** In reward-bearing NFT or staking contracts, test self-transfers and zero-net-balance moves; also check whether constructor-time code absence bypasses isContract guards.
