# A locked fighter can be transferred; leads to game server unable to commit transactions, and unstoppable fighters

FighterFarm overrode only two-arg transferFrom/safeTransferFrom, leaving the inherited safeTransferFrom(...,data) unguarded, letting locked/staked fighters be transferred and breaking game-server accounting.

- Payout: Contest reward
- Severity: high
- Program: AI Arena
- Credited: CodeWasp
- Disclosed: 2024-05-14
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2024-02-ai-arena-findings/issues/1709
- Library page: https://bugbountylibrary.com/r/c4%3A2024-02-ai-arena%3AH-01
- Tags: domain:web3 impact:dos impact:integrity kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:incomplete-fix technique:source-review vuln:access-control vuln:business-logic

**Target:** FighterFarm ERC721 contract (Solidity)
**Root cause:** Transferability restriction enforced by overriding transferFrom and safeTransferFrom(from,to,tokenId) instead of the _beforeTokenTransfer hook, so the inherited safeTransferFrom(from,to,tokenId,data) overload bypasses _ableToTransfer entirely.
**Steps:**
1. Mint a fighter and stake NRN via `stakeNRN(1e18, tokenId)`.
2. Game server records a win: `updateBattleRecord(tokenId, 0, 0, 1500, true)` adds accumulatedPointsPerAddress for owner.
3. Call inherited overload `safeTransferFrom(player, otherPlayer, tokenId, "")` — no _ableToTransfer check runs.
4. Game server attempts a loss: `updateBattleRecord(tokenId, 0, 2, 1500, true)` reverts because new owner has zero accumulated points to subtract.
5. For poison-pill: lose a battle so amountLost is set, unstake remaining, transfer fighter to victim via data overload, then fighter wins reducing victim's amountLost; victim's own fighter win underflows amountLost and reverts.
**How found:** Code reading: noticed FighterFarm overrides only the 3-arg transferFrom/safeTransferFrom with _ableToTransfer, then checked OpenZeppelin ERC721 for other transfer entry points and found the 4-arg safeTransferFrom(...,data) overload inherited unchecked.
**Exploitation:** Player transfers a staked/locked fighter to a fresh address after each win using the unguarded safeTransferFrom data overload; the new address has empty accumulatedPointsPerAddress so any loss transaction reverts, making the fighter effectively immortal and blocking game-server commits. Alternatively transfer a stake-at-risk fighter to a victim so the victim's amountLost underflows on a win, trapping them in the losing zone.
**Escalation:** Chaining transfer-after-win to farm unbounded points; or gifting a poisoned fighter to a rival to permanently block their exit from the losing zone.
**Insight:** When access control is enforced by overriding individual ERC721 transfer functions, enumerate every transfer entry point in the base contract (including the bytes-data safeTransferFrom overload and _safeMint paths) — the correct chokepoint is _beforeTokenTransfer/_update, not per-function overrides.
