# Malicious user can steal all assets in BondNFT

Repeated claim() on an expired bond cumulatively inflates accRewardsPerShare, letting an attacker drain all BondNFT reward assets.

- Payout: Contest reward
- Severity: high
- Program: Tigris Trade
- Credited: hihen
- Disclosed: 2023-02-17
- Write-up quality: 8/10
- Original: https://github.com/code-423n4/2022-12-tigris-findings/issues/170
- Library page: https://bugbountylibrary.com/r/c4%3A2022-12-tigris%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:** BondNFT/Lock reward accounting contract
**Root cause:** claim() on an expired bond recomputes and re-adds _pendingDelta to accRewardsPerShare each call; since the expired bond's own pending is zeroed but the global accumulator keeps growing, repeated calls inflate other bonds' pending rewards.
**Steps:**
1. Create bond B1 (long expireEpoch) and B2 (short expireEpoch) with same asset.
2. Wait until B2 expires while B1 remains active.
3. Call `Lock.claim(B2)` repeatedly; each call adds _pendingDelta*1e18/totalShares to accRewardsPerShare even though B2 receives 0.
4. Observe `bond.pending(B1)` grows each iteration.
5. Loop until pending(B1) exceeds remaining bond balance; transfer the small diff to bond contract.
6. Call `Lock.claim(B1)` to withdraw inflated rewards, draining the contract.
**How found:** Code review of claim()'s expired-bond branch: noticed accRewardsPerShare is mutated on every call while the expired bond's own payout is zero, implying a cumulative inflation of other bonds' pending rewards.
**Exploitation:** Attacker stakes two bonds, waits for the short one to expire, then loops claim() on the expired bond (~41 iterations in PoC) to inflate the active bond's pending rewards to exceed the contract balance, tops up the tiny shortfall, and claims everything.
**Insight:** When a function both zeroes a user's pending and mutates a global accumulator, check whether the mutation is idempotent — repeated calls on a zeroed position can still pump the shared index.
