Core Contracts

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

Revoked Vesting Tokens Are Locked in the Contract with No Withdrawal Mechanism

Summary

When a vesting schedule is revoked using emergencyRevoke, the unreleased tokens are transferred back to the contract (address(this)). However, there is no function to withdraw or reuse these tokens, effectively locking them forever. Additionally, categoryUsed[category] is not updated after revocation, preventing reallocation of these tokens to new vesting schedules.

Vulnerability Details

The issue originates from how the emergencyRevoke function handles revoked tokens. When a vesting schedule is revoked, the contract transfers the remaining (unreleased) tokens to itself but does not provide any function to withdraw or reallocate them. As a result, these tokens remain inaccessible and unusable.

The emergencyRevoke function is responsible for revoking a vesting schedule:

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

The line raacToken.transfer(address(this), unreleasedAmount); transfers the revoked tokens back to the contract, but there is no function to withdraw them later. These tokens are effectively lost unless an additional function is introduced to retrieve them.

The function does not update categoryUsed[category], meaning the revoked tokens are still counted as used. This prevents the team from creating new vesting schedules for the same category, even though tokens are available.

Popular DeFi projects ensure that revoked tokens are either sent back to the treasury or reassigned for future use.

Impact

  1. Since 65% of the total supply is allocated to vesting, any revoked schedules result in permanently locked tokens, reducing the circulating supply.

  2. The treasury (5% allocation) is relatively small. Losing access to revoked tokens limits the protocol's ability to reallocate incentives or fund future initiatives.

  3. If an advisor/team member leaves and their schedule is revoked, the protocol should be able to reuse those tokens, but right now, it cannot.

Tools Used

Manual Code Review

Recommendations

  1. Add a function that allows the protocol to withdraw revoked tokens to a treasury or designated address.

    function withdrawRevokedTokens(address to, uint256 amount) external onlyRole(DEFAULT_ADMIN_ROLE) {
    raacToken.transfer(to, amount);
    }
  2. Update categoryUsed[category] After Revocation: Modify emergencyRevoke to ensure that revoked tokens are properly deducted from categoryUsed so they can be reassigned.

    categoryUsed[category] -= unreleasedAmount;
Updates

Lead Judging Commences

inallhonesty Lead Judge 6 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 6 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.