# ERC721Votes: Token owners can double voting power through self delegation

ERC721Votes treats unset delegation as address(0), so a first-time self-delegation credits votes without debiting, doubling the owner's voting power once.

- Payout: Contest reward
- Severity: high
- Program: Nouns Builder
- Credited: zkhorse
- Disclosed: 2022-11-03
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-09-nouns-builder-findings/issues/413
- Library page: https://bugbountylibrary.com/r/c4%3A2022-09-nouns-builder%3AH-04
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:governance

**Target:** ERC721Votes governance token delegation contract
**Root cause:** _delegate reads prevDelegate directly from the delegation mapping, which defaults to address(0) instead of the owner, so _moveDelegateVotes skips the debit branch (from==0) while still crediting the owner.
**Steps:**
1. Mint or acquire N ERC721Votes tokens; getVotes(owner) == N.
2. Call `token.delegate(owner)` (or delegateBySig) with self as delegate as the first-ever delegation.
3. prevDelegate = delegation[owner] = address(0); _moveDelegateVotes(address(0), owner, N).
4. from==0 skips the debit checkpoint; to==owner credits +N, so getVotes(owner) == 2N.
5. Repeat self-delegation: prevDelegate now non-zero, no further doubling.
**How found:** Code review of _delegate/_moveDelegateVotes: noticed prevDelegate defaults to address(0) for first delegation and that _moveDelegateVotes treats from==0 as a mint (credit without debit).
**Exploitation:** Owner quietly accumulates multiple tokens, then performs a single self-delegation to double voting weight in governance, enabling outsized influence over proposals/fund movement in a low-supply DAO.
**Escalation:** Accumulate many tokens before the one-time self-delegation to maximize the doubled weight.
**Insight:** When a checkpoint/delegation system uses address(0) as a sentinel for mint/burn, check whether the default mapping value collides with that sentinel on first interaction; compare against the reference NounsDAO implementation which resolves zero to self via a delegates() helper.
