Core Contracts

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

Missing start time validation in `RAACReleaseOrchestrator::createVestingSchedule`

Summary

The RAACReleaseOrchestrator::createVestingSchedule function does not validate whether the startTime is greater than the current timestamp (block.timestamp). If a vesting schedule is created with a past start time, it will lead to incorrect calculations when determining the releasable amount, potentially allowing premature or excessive token releases.

Vulnerability Details

The function allows setting startTime to any value, including timestamps in the past. This can result in incorrect calculations when determining how many tokens should be released, as the RAACReleaseOrchestrator::_calculateReleasableAmount function assumes that startTime is always in the future or the present.

Affected Code in RAACReleaseOrchestrator::createVestingSchedule

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();
// @audit-issue do not verify the startTime
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 issue might lead to incorrect releasable amount calculation

The RAACReleaseOrchestrator::_calculateReleasableAmount function assumes startTime is valid. However, if startTime is in the past, the function may incorrectly calculate timeFromStart, leading to an unintended immediate full release of the vesting schedule.

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; // @audit-issue incorrect startTime might lead to incorrect calculation
if (timeFromStart >= schedule.duration) {
return schedule.totalAmount - schedule.releasedAmount;
}
uint256 vestedAmount = (schedule.totalAmount * timeFromStart) / schedule.duration;
return vestedAmount - schedule.releasedAmount;
}

Steps to Reproduce

  1. Accidentally call createVestingSchedule with startTime set to a past timestamp.

  2. Accidentally call _calculateReleasableAmount and observe that the function treats the schedule as fully vested, allowing immediate full release.

  3. Beneficiary can withdraw the entire vested amount earlier than expected.

Impact

  • Immediate Full Vesting: Setting startTime in the past could cause the entire vesting schedule to be considered fully vested immediately, bypassing intended vesting restrictions.

  • Unexpected Fund Unlocking: Funds could be released earlier than designed, which could lead to liquidity issues or manipulation of vesting mechanics.

Tools Used

Manual Review

Recommendations

Modify createVestingSchedule to ensure that startTime is greater than or equal to block.timestamp

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();
+ if (startTime < block.timestamp) revert InvalidStartTime(); // @audit-fix Ensure valid start time
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);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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

Give us feedback!