Core Contracts

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

Vesting Schedule Bypass in RAACReleaseOrchestrator.sol

Summary

The release() function in RAACReleaseOrchestrator.sol does not verify whether the vesting schedule has been refreshed before allowing claims. This leads to a potential bypass of vesting conditions, allowing users to withdraw more tokens than intended.

/**
* @notice Releases vested tokens for the caller
*/
function release() external nonReentrant whenNotPaused {
address beneficiary = msg.sender;
VestingSchedule storage schedule = vestingSchedules[beneficiary];
if (!schedule.initialized) revert NoVestingSchedule();
uint256 releasableAmount = _calculateReleasableAmount(schedule);
if (releasableAmount == 0) revert NothingToRelease();
schedule.releasedAmount += releasableAmount;
schedule.lastClaimTime = block.timestamp;
raacToken.transfer(beneficiary, releasableAmount);
emit TokensReleased(beneficiary, releasableAmount);
}

Vulnerability Details

The release() Function Lacks a Proper Refresh Check.

Issues:

  • Does not check if the vesting schedule was refreshed before allowing claims.

  • Assumes lastClaimTime is always valid and up-to-date.

  • Allows repeated calls before vestingSchedules is updated properly, leading to unauthorized claims.

_calculateReleasableAmount() Does Not Validate Price Changes

/**
* @notice Calculates releasable amount for a vesting schedule
* @param schedule The vesting schedule to calculate for
* @return The amount of tokens that can be released
*/
function _calculateReleasableAmount(
VestingSchedule memory schedule
) internal view returns (uint256) {
if (block.timestamp < schedule.startTime + VESTING_CLIFF) return 0;
if (block.timestamp < schedule.lastClaimTime + MIN_RELEASE_INTERVAL) return 0;
uint256 timeFromStart = block.timestamp - schedule.startTime;
if (timeFromStart >= schedule.duration) {
return schedule.totalAmount - schedule.releasedAmount;
}
uint256 vestedAmount = (schedule.totalAmount * timeFromStart) / schedule.duration;
return vestedAmount - schedule.releasedAmount;
}

Issue:

  • The function assumes vesting schedules are valid without checking if eligibility has changed.

  • If price or governance conditions change, users can still withdraw based on outdated calculations.

Impact

Governance Abuse: Attackers can withdraw locked tokens before intended.

Token Drain: Large portions of RAAC supply could be released prematurely.

Tools Used

Manual Review

Recommendations

Enforce Eligibility Refresh Before Releasing Funds

Modify release() to validate and refresh eligibility before transferring tokens.

function release() external nonReentrant whenNotPaused {
address beneficiary = msg.sender;
VestingSchedule storage schedule = vestingSchedules[beneficiary];
if (!schedule.initialized) revert NoVestingSchedule();
// Refresh eligibility before calculating releasable amount
_checkAndUpdateEligibility(schedule);
uint256 releasableAmount = _calculateReleasableAmount(schedule);
if (releasableAmount == 0) revert NothingToRelease();
schedule.releasedAmount += releasableAmount;
schedule.lastClaimTime = block.timestamp;
raacToken.transfer(beneficiary, releasableAmount);
emit TokensReleased(beneficiary, releasableAmount);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!