emergencyWithdraw allows a user to withdraw their locked tokens in an emergency scenario after a delay emergencyWithdrawDelay. It deletes the user’s lock _lockState.locks[msg.sender] and voting power state _votingState.points[msg.sender], burns their veTokens (currentPower), and transfers the locked amount back to the user. However, it does not call _checkpointState.writeCheckpoint(msg.sender, 0) to update the user’s checkpoint history, which tracks their voting power over time. This omission leaves the checkpoint state inconsistent with the user’s actual voting power (now zero) after the withdrawal.
Omission of _checkpointState.writeCheckpoint: After burning the user’s veTokens (_burn(msg.sender, currentPower)), reducing their voting power to zero, the function does not record this change in the checkpoint state via _checkpointState.writeCheckpoint(msg.sender, 0).
Inconsistent State Management: Other functions like lock, increase, extend and withdraw call writeCheckpoint whenever voting power changes (e.g., minting or burning veTokens), but emergencyWithdraw skips this step despite nullifying the user’s power.
Outdated Checkpoint History:
The user’s last checkpoint (e.g., from a prior lock or extend) remains in _checkpointState.userCheckpoints[msg.sender], showing a non-zero voting power (e.g., currentPower) even though their actual power is now zero after _burn.
getPastVotingPower and getPastVotes rely on userCheckpoints to return historical voting power at a given blockNumber. Without an updated checkpoint at the withdrawal block, these functions return the previous power value for blocks after the withdrawal which could result to misinformation .
manual review
Add _checkpointState.writeCheckpoint:
Insert _checkpointState.writeCheckpoint(msg.sender, 0) after _burn to record the power dropping to zero:
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.