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