Core Contracts

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

Emergency revoke transfers unreleased tokens to the contract, making them inaccessible in `RAACReleaseOrchestrator` contract

Summary

In RAACReleaseOrchestrator contract, the emergencyRevoke() function can lead to the loss of unreleased tokens. When called, it removes a beneficiary’s vesting schedule and transfers the remaining RAAC tokens back to the contract. However, since the contract does not provide a way to reassign or recover these tokens, they effectively become inaccessible, leading to a permanent loss.

Vulnerability Details

The emergencyRevoke() function is designed to immediately revoke a beneficiary’s vesting schedule and transfer their remaining tokens to the contract itself:

function emergencyRevoke(address beneficiary) external onlyRole(EMERGENCY_ROLE) {
VestingSchedule storage schedule = vestingSchedules[beneficiary];
if (!schedule.initialized) revert NoVestingSchedule();
uint256 unreleasedAmount = schedule.totalAmount - schedule.releasedAmount;
delete vestingSchedules[beneficiary];
if (unreleasedAmount > 0) {
@> raacToken.transfer(address(this), unreleasedAmount);
emit EmergencyWithdraw(beneficiary, unreleasedAmount);
}
emit VestingScheduleRevoked(beneficiary);
}

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/minters/RAACReleaseOrchestrator/RAACReleaseOrchestrator.sol#L126C3-L139C6

Since the contract does not provide a function to redistribute or withdraw these tokens, they remain stuck in the contract indefinitely.

Impact

Any revoked tokens are permanently stuck in the contract, effectively leading to a loss of unreleased tokens. This could result in a significant financial loss if large amounts are revoked.

Tools Used

Manual Review

Recommendations

Modify emergencyRevoke() to allow the contract owner to redistribute or withdraw the locked tokens.

function emergencyRevoke(address beneficiary) external onlyRole(EMERGENCY_ROLE) {
VestingSchedule storage schedule = vestingSchedules[beneficiary];
if (!schedule.initialized) revert NoVestingSchedule();
uint256 unreleasedAmount = schedule.totalAmount - schedule.releasedAmount;
delete vestingSchedules[beneficiary];
if (unreleasedAmount > 0) {
- raacToken.transfer(address(this), unreleasedAmount);
+ raacToken.transfer(owner(), unreleasedAmount);
emit EmergencyWithdraw(beneficiary, unreleasedAmount);
}
emit VestingScheduleRevoked(beneficiary);
}
Updates

Lead Judging Commences

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

RAACReleaseOrchestrator::emergencyRevoke sends revoked tokens to contract address with no withdrawal mechanism, permanently locking funds

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

RAACReleaseOrchestrator::emergencyRevoke sends revoked tokens to contract address with no withdrawal mechanism, permanently locking funds

Support

FAQs

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