# A malicious user can inflate his voting power via merge()

ZeroLocker.merge() fails to reset ownershipChange on the destination NFT, letting one stake vote repeatedly and inflate governance voting power.

- Payout: Contest reward
- Severity: high
- Program: Zerolend
- Credited: elhaj
- Disclosed: 2024-09-16
- Write-up quality: 7/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_zerolend_jan2024.pdf#3.1.3
- Library page: https://bugbountylibrary.com/r/cantina%3Azerolend_jan2024%3A3.1.3
- Tags: domain:web3 impact:integrity kind:contest platform:cantina sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:business-logic vuln:governance

**Target:** ZeroLocker governance staking/locking contract (Solidity)
**Root cause:** merge() deposits the source lock's balance into the destination tokenId but never sets ownershipChange[_to], so balanceOfNFT does not zero out the destination's voting power for that block. The destination retains/accumulates bias and can be voted with again in the same transaction.
**Steps:**
1. Bob creates one large lock (100 ether, 20 weeks) and several tiny locks via `locker.createLock(100, 20 weeks)`.
2. Bob calls `locker.balanceOfNFT(tokenId)` (vote) on the large lock, accumulating voting power.
3. Bob calls `locker.merge(largeId, smallId)`; destination bias increases because ownershipChange[smallId] is not set.
4. Bob votes again with the merged smallId via `balanceOfNFT`, reusing the same stake.
5. Repeat merge into the next tiny lock and vote again, all within one transaction; PoC shows inflated power ~71.5e18 vs real ~7.9e18.
**How found:** Code review of balanceOfNFT/ownershipChange guard versus merge(): noticed merge() updates destination balance but not the ownershipChange timestamp that suppresses same-block voting, implying the guard can be bypassed.
**Exploitation:** Attacker with one significant lock plus many dust locks votes with the major lock, then chains merge() into each dust lock and votes again each time, multiplying effective voting power in a single transaction to pass malicious governance proposals.
**Escalation:** Repeated merge-and-vote chaining across many dust locks multiplies voting power arbitrarily, enabling governance takeover.
**Insight:** When a contract uses a same-block 'ownershipChange' guard to prevent double-voting, audit every state-mutating path (merge, split, transfer, deposit-for) that changes a token's balance and confirm each one refreshes that guard for the destination token.
