Core Contracts

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

Emergency Unlock Non-Functional Due to Missing Check in veRAACToken::withdraw Function

Summary

The emergencyUnlockEnabled flag is set in the contract, but the withdraw function does not check it, rendering the emergency unlock feature ineffective. This issue prevents users from withdrawing locked tokens during an emergency unlock scenario before their lock expires.

Vulnerability Details

Inwithdraw function in the veRAACToken contract, the executeEmergencyUnlock function that sets the emergencyUnlockEnabled flag.

  • The withdraw function only checks whether the lock has expired (block.timestamp < userLock.end) and does not consider the emergencyUnlockEnabled flag.

  • While the emergencyUnlockEnabled flag can be set via the executeEmergencyUnlock function, it has no effect on the withdraw function, which is the primary mechanism for users to retrieve their tokens after lock expiry.

  • This makes the emergency unlock feature non-functional in the context of normal withdrawal, as it doesn’t override the LockNotExpired check.

  • withdrawfunction

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);
}
  • executeEmergencyUnlock function:

function executeEmergencyUnlock() external onlyOwner withEmergencyDelay(EMERGENCY_UNLOCK_ACTION) {
emergencyUnlockEnabled = true;
emit EmergencyUnlockEnabled();
}

Impact

  • Users cannot withdraw locked tokens during an emergency unlock scenario before their lock expires, defeating the purpose of the emergencyUnlockEnabled feature.

  • The emergencyUnlockEnabled flag becomes redundant, as users must rely on the emergencyWithdraw function, which has its own activation process (enableEmergencyWithdraw) and delay (emergencyWithdrawDelay).

  • Users may expect an "emergency unlock" to enable immediate withdrawal without needing a separate emergencyWithdraw call

Tools Used

Recommendations

  • Modify the withdraw function to respect the emergencyUnlockEnabled flag, allowing withdrawal before lock expiry when enabled:

function withdraw() external nonReentrant {
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
if (userLock.amount == 0) revert LockNotFound();
if (block.timestamp < userLock.end && !emergencyUnlockEnabled) 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);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

veRAACToken::executeEmergencyUnlock is dormant, it configures a system that's never used

Support

FAQs

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