Core Contracts

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

Emergency withdraw functionality in veRAACToken takes longer than expected

Summary

The veRAACToken::scheduleEmergencyAction function schadules an emergency withdrawal mechanism that must be executed after 3 days. However in the current implementation of this functionality the users are able to call the enableEmergencyWithdraw not after 3, but after 6 days!

Vulnerability Details

In case of an emergency the contract has a functionality that scadules an emergency withdraw after 3 days.

Consider the following scenario:

  1. The contract owner of veRAACToken needs to envoke the emergency withdraw function. He calls the scheduleEmergencyAction function with id - EMERGENCY_WITHDRAW_ACTION.

  2. Then after 3 days the owner calls the enableEmergencyWithdraw which has the withEmergencyDelay modifier that checks if 3 days have passed. Then enableEmergencyWithdraw as it says should Enable emergency withdrawal functionality. However this function sets emergencyWithdrawDelay to block.timestamp + EMERGENCY_DELAY.

  3. Then when a user tries to call the emergencyWithdraw function and it checks if block.timestamp < emergencyWithdrawDelay. This check will revert if another 3 days have not passed.

  4. This makes the total emergency withdraw duration not 3 but 6 days!

According to the documentation the Emergency actions require 3-day delay, but in current implementation the days are actually 6.
Docs:
https://github.com/Cyfrin/2025-02-raac/blob/main/docs/core/tokens/veRAACToken.md#notes

Impact

In case of an emergency withdrawal, the functionality will take longer to be available, which can lead to significant financial losses for users.

PoC

Foundry Test:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import {Test, console} from "forge-std/Test.sol";
import {RAACToken} from "../../../src/RAAC-Protocol/veRAACToken/RAACToken.sol";
import {veRAACToken} from "../../../src/RAAC-Protocol/veRAACToken/veRAACToken.sol";
import {IveRAACToken} from "../../../src/RAAC-Protocol/veRAACToken/interfaces/IveRAACToken.sol";
contract TestVeRAACToken is Test {
RAACToken public _raacToken;
veRAACToken public _veRAACToken;
address deployer = makeAddr("deployer");
address beneficiary = makeAddr("beneficiary");
address Bob = makeAddr("Bob");
uint256 initialTax = 500;
function setUp() public {
vm.startPrank(deployer);
// RAAC TOKEN
_raacToken = new RAACToken(deployer, initialTax, initialTax);
_raacToken.setMinter(deployer);
_raacToken.mint(address(beneficiary), 1000 ether);
_raacToken.mint(address(Bob), 1000 ether);
// VERAAC TOKEN
_veRAACToken = new veRAACToken(address(_raacToken));
vm.stopPrank();
vm.prank(beneficiary);
_raacToken.approve(address(_veRAACToken), type(uint256).max);
vm.prank(Bob);
_raacToken.approve(address(_veRAACToken), type(uint256).max);
vm.prank(deployer);
_raacToken.manageWhitelist(address(_veRAACToken), true);
}
error EmergencyWithdrawNotEnabled();
function testVulnerabilityEmergencyWithdraw() public {
uint256 duration = 730 days;
uint256 amount = 1000 ether;
vm.startPrank(Bob);
// Bob locks 1000 tokens for 730 days
_veRAACToken.lock(amount, duration);
vm.stopPrank();
vm.warp(500 days);
vm.startPrank(deployer);
// After 500 days there is an emergency and the owner calls `scheduleEmergencyAction`
_veRAACToken.scheduleEmergencyAction(_veRAACToken.EMERGENCY_WITHDRAW_ACTION());
vm.warp(503 days);
// 3 days pass and the owner enables the emergency withdrawal mechanism
_veRAACToken.enableEmergencyWithdraw();
vm.stopPrank();
// Getting the current snapshot only after 3 days have passed
uint256 threeDaysSnapshot = vm.snapshot();
vm.prank(Bob);
// Bobs `emergencyWithdraw` call fails when the `EMERGENCY_DELAY` has passed
vm.expectRevert(EmergencyWithdrawNotEnabled.selector);
_veRAACToken.emergencyWithdraw();
// Reverting back to the state
vm.revertTo(threeDaysSnapshot);
// Another 3 days pass
vm.warp(506 days);
vm.prank(Bob);
// Now after 6 days Bob is able to call `emergencyWithdraw` and withdraw his tokens
_veRAACToken.emergencyWithdraw();
}
}

Tools Used

Manual Review

Recommendations

Set the emergencyWithdrawDelay variable to only block.timestamp as shown:

function enableEmergencyWithdraw() external onlyOwner withEmergencyDelay(EMERGENCY_WITHDRAW_ACTION) {
- emergencyWithdrawDelay = block.timestamp + EMERGENCY_DELAY;
+ emergencyWithdrawDelay = block.timestamp;
emit EmergencyWithdrawEnabled(emergencyWithdrawDelay);
}
Updates

Lead Judging Commences

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

veRAACToken implements two consecutive 3-day emergency delays (totaling 6 days), hindering timely emergency response when funds need to be withdrawn quickly

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

veRAACToken implements two consecutive 3-day emergency delays (totaling 6 days), hindering timely emergency response when funds need to be withdrawn quickly

Support

FAQs

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

Give us feedback!