Summary
In RAACReleaseOrchestrator users with the role "ORCHESTRATOR_ROLE" can create vesting schedules. Due to a lack of parameter validation, it is possible to start the vesting schedule in the past, resulting in a partial or complete bypass of the scheduled token release.
Vulnerability Details
In RAACReleaseOrchestrator.sol:L77, there's a function that allows users with the role "ORCHESTRATOR_ROLE" to create vesting schedules.
Accidentally or intentionally, users may create a vesting schedule that starts in the past, as there is no validation on the function parameter startTime
.
function createVestingSchedule(
address beneficiary,
bytes32 category,
uint256 amount,
uint256 startTime
) external onlyRole(ORCHESTRATOR_ROLE) whenNotPaused {
if (beneficiary == address(0)) revert InvalidAddress();
if (amount == 0) revert InvalidAmount();
if (vestingSchedules[beneficiary].initialized) revert VestingAlreadyInitialized();
if (categoryAllocations[category] == 0) revert InvalidCategory();
uint256 newCategoryTotal = categoryUsed[category] + amount;
if (newCategoryTotal > categoryAllocations[category]) revert CategoryAllocationExceeded();
categoryUsed[category] = newCategoryTotal;
VestingSchedule storage schedule = vestingSchedules[beneficiary];
schedule.totalAmount = amount;
schedule.startTime = startTime;
schedule.duration = VESTING_DURATION;
schedule.initialized = true;
emit VestingScheduleCreated(beneficiary, category, amount, startTime);
}
The variable schedule.startTime
is then used to calculate the releasable amount.
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;
}
Impact
Affected users can withdraw their vested tokens partially or completely before the vesting process normally would have finished.
Tools Used
Recommendations
Consider validating the parameter startTime
so that it is greater than or equal to the current timestamp.
if (startTime < block.timestamp) revert StartInPast();