# Upon changing of delegate, VoteDelegation updates both the previous and the current checkpoint

VoteEscrowDelegation's delegate/removeDelegation mutate the previous checkpoint via storage reference, retroactively corrupting historical vote counts.

- Payout: Contest reward
- Severity: high
- Program: Golom
- Credited: kenzo
- Disclosed: 2023-04-05
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-07-golom-findings/issues/81
- Library page: https://bugbountylibrary.com/r/c4%3A2022-07-golom%3AH-10
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:high tech:solidity technique:source-review vuln:accounting-error vuln:governance

**Target:** VoteEscrowDelegation.sol vote checkpointing
**Root cause:** delegate()/removeDelegation() read the latest checkpoint as a storage reference and push tokenId into it before calling _writeCheckpoint, which then writes a new checkpoint; the prior checkpoint is mutated too.
**Steps:**
1. Call `delegate(toTokenId, tokenId)` where toTokenId already has checkpoints.
2. Observe `Checkpoint storage checkpoint = checkpoints[toTokenId][nCheckpoints-1]` then `checkpoint.delegatedTokenIds.push(tokenId)`.
3. _writeCheckpoint sees oldCheckpoint.fromBlock != block.number and appends a new checkpoint with the same array.
4. Both previous and new checkpoint now contain tokenId; repeat via removeDelegation for the inverse.
**How found:** Code reading of checkpoint logic: noticed storage vs memory reference on the latest checkpoint combined with _writeCheckpoint's same-block update branch.
**Exploitation:** Attacker changes delegation to retroactively add/remove tokenIds from historical checkpoints, altering past vote tallies used for governance.
**Insight:** When a helper reads the last array element as `storage` and then passes it to a writer that may append a new entry, the prior entry is aliased and mutated—check storage-vs-memory on checkpoint/accumulator patterns.
