The emergencyWithdraw function does not write the checkpoint when the user's lock is removed. This causes incorrect voting power calculation.
Vulnerability Details
emergencyWithdraw doesn't call _checkpointState.writeCheckpoint(msg.sender, 0); when the voting power is removed. It is an important step when user's power changes so that votes calculations are performed with reals votes owned by user.
function emergencyWithdraw() external nonReentrant {
if (
emergencyWithdrawDelay == 0 ||
block.timestamp < emergencyWithdrawDelay
) revert EmergencyWithdrawNotEnabled();
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
if (userLock.amount == 0) revert NoTokensLocked();
uint256 amount = userLock.amount;
uint256 currentPower = balanceOf(msg.sender);
delete _lockState.locks[msg.sender];
delete _votingState.points[msg.sender];
_burn(msg.sender, currentPower);
raacToken.safeTransfer(msg.sender, amount);
emit EmergencyWithdrawn(msg.sender, amount);
}
We can see that withdraw function writes the checkpoint when the user removes the lock.
function withdraw() external nonReentrant {
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
if (userLock.amount == 0) revert LockNotFound();
if (block.timestamp < userLock.end) revert LockNotExpired();
uint256 amount = userLock.amount;
uint256 currentPower = balanceOf(msg.sender);
delete _lockState.locks[msg.sender];
delete _votingState.points[msg.sender];
_checkpointState.writeCheckpoint(msg.sender, 0);
_burn(msg.sender, currentPower);
raacToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
Impact
User votes are going to be incorrectly calcualted leading to inflated votes. User can use inflated votes to vote for proposals even when he shouldn't be able to do that.
Tools Used
Manual Review, Hardhat
Recommendations
Add _checkpointState.writeCheckpoint(msg.sender, 0); to emergencyWithdraw function.