Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: low
Valid

writeCheckpoint(msg.sender,0) is not called in VeRAACToken during emergency.

Summary

In VeRAACToken.sol, there is an emergency function emergencyWithdraw() that can be activated to allow users to bypass their withdrawal duration and withdraw their tokens.

In this function, users will burn their veRAACTokens and get back RAACTokens, and their lock position will be deleted. However, their checkpoints will not be deleted, as it should be when removing their lock position, similar to unlock().

Vulnerability Details

In veRAACToken.emergencyWithdraw(), 4 things happen, _lockState is deleted, _votingState is deleted, _burn() is called on their veRAACToken and raacToken is transferred to the caller,

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);
}

This process should be similar to withdraw() since they are both removing locks and burning veRAAC for RAAC tokens, but it misses calling _checkpointState().

This is the withdraw() function where 5 things happen during a withdrawal process.

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);
// Clear lock data
> delete _lockState.locks[msg.sender];
> delete _votingState.points[msg.sender];
// Update checkpoints
> _checkpointState.writeCheckpoint(msg.sender, 0);
// Burn veTokens and transfer RAAC
> _burn(msg.sender, currentPower);
> raacToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}

Impact

High Impact, Low likelihood for an emergency

The checkpointState is not updated for the user. The issue is that checkpointState() is used to query past votes, which is misleading and may lead the protocol to think that the user still has votes.

/**
* @notice Gets the historical voting power for an account at a specific block
* @dev Returns the voting power from the checkpoint at or before the requested block
* @param account The address to check voting power for
* @param blockNumber The block number to check voting power at
* @return The voting power the account had at the specified block
*/
function getPastVotes(address account, uint256 blockNumber) public view returns (uint256) {
> return _checkpointState.getPastVotes(account, blockNumber);
}

Tools Used

Manual Review

Recommendations

Call _checkpointState.writeCheckpoint(msg.sender, 0); in emergencyWithdraw() as it is done in withdraw().

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

veRAACToken::emergencyWithdraw doesn't update checkpoint - innacurate historical voting power, inconsistent state

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.