# ERC721Checkpointable: delegateBySig allows the user to vote to address 0, which causes the user to permanently lose his vote and cannot transfer his NFT.

delegateBySig lacks the address(0) guard that delegate() has, letting a signer delegate to zero and bricking vote accounting and NFT transfers.

- Payout: Contest reward
- Severity: high
- Program: Nouns DAO
- Credited: cccz
- Disclosed: 2022-09-30
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-08-nounsdao-findings/issues/157
- Library page: https://bugbountylibrary.com/r/c4%3A2022-08-nounsdao%3AH-01
- Tags: domain:web3 impact:dos kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:incomplete-fix technique:source-review vuln:accounting-error vuln:business-logic

**Target:** ERC721Checkpointable.sol (delegateBySig / _moveDelegates)
**Root cause:** delegate() remaps address(0) to msg.sender but delegateBySig() does not, so _delegates[user] can be set to address(0) while delegates() still returns the user, desyncing checkpoint accounting.
**Steps:**
1. Call `delegateBySig(address(0), nonce, expiry, v, r, s)` with a valid signature from user A.
2. Observe `_delegates[A] == address(0)` while `delegates(A)` returns A and `getCurrentVotes(A)` returns 0.
3. Attempt to delegate to another address or transfer the NFT: `_moveDelegates` computes `sub96(srcRepOld, amount)` with srcRepOld=0, reverting with 'amount underflows'.
4. Burn also reverts via `_beforeTokenTransfer` -> `_moveDelegates`, so the token is permanently stuck.
**How found:** Compared delegate() and delegateBySig() and noticed the address(0) normalization present in one but missing in the other; traced _delegate/_moveDelegates to see the underflow on subsequent transfers.
**Exploitation:** User signs a delegation to address(0); afterwards any transfer, re-delegation, or burn of that token reverts in _moveDelegates due to uint96 underflow, permanently locking the NFT and its votes.
**Insight:** When a contract has two entry points to the same internal function, diff their input normalization; a guard present in one path but absent in the signed/relayed path is a classic desync that can brick checkpoint accounting.
