Core Contracts

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

Lack of Validation on Vesting Start Time in `RAACReleaseOrchestrator`

Summary

The createVestingSchedule
function in the RAACReleaseOrchestrator contract accepts an arbitrary startTime parameter without validation. This may allow vesting schedules to be initialized with a start time in the past, enabling beneficiaries to immediately claim a significant portion—or even all—of their allocated tokens, contrary to the intended vesting logic.

Vulnerability Details

In the createVestingSchedule function, the contract sets the vesting schedule's start time as provided by the caller:

VestingSchedule storage schedule = vestingSchedules[beneficiary];
schedule.totalAmount = amount; // e.g., 100e18 tokens
schedule.startTime = startTime; // Arbitrary value provided by caller
schedule.duration = VESTING_DURATION; // e.g., 700 days (60480000 seconds)
schedule.initialized = true;

Issue:
No validation is performed to ensure that startTime is not in the past or within an unreasonable range. As a result, if an orchestrator sets startTime to a value in the past (or even far in the past), the vesting calculation can immediately release a large percentage of tokens, as the elapsed time will be exaggerated.

Proof-of-Concept (POC)

  1. Initial Conditions:

    • Assume block.timestamp is 1,000,000 seconds.

    • A vesting schedule is created with:

      • totalAmount = 100e18 tokens.

      • VESTING_DURATION = 60480000 seconds (700 days).

      • VESTING_CLIFF = 90 days (7776000 seconds).

      • startTime is set to block.timestamp - 100,000 seconds (i.e., 900,000 seconds).

  2. Expected Behavior:

    • Ideally, the vesting schedule should start at the time of creation (e.g., block.timestamp) so that token release gradually increases from that point.

  3. Actual Behavior:

    • With startTime set to 900,000 seconds, the elapsed time is:

      timeFromStart = block.timestamp - startTime = 1,000,000 - 900,000 = 100,000 seconds.
    • If the cliff is passed, the releasable amount is calculated as:

      vestedAmount = (totalAmount * timeFromStart) / duration.

      Substituting the values:

      vestedAmount = (100e18 * 100,000) / 60,480,000 ≈ 0.165e18 tokens.
    • While in this scenario the releasable amount is a fraction of the total, setting startTime much further in the past could cause nearly all tokens to be vested immediately. For instance, if startTime were set to block.timestamp - duration, then:

      timeFromStart = duration,
      vestedAmount = totalAmount - releasedAmount.
    • Consequently, the beneficiary could claim the entire allocation immediately, violating the intended vesting schedule.

Impact

Beneficiaries might immediately access a disproportionate amount of tokens if vesting schedules are initiated with a past startTime.

Tools Used

Manual Review

Recommendations

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();
// Check category allocation limits
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.startTime = block.timestamp;
schedule.duration = VESTING_DURATION;
schedule.initialized = true;
emit VestingScheduleCreated(beneficiary, category, amount, startTime);
}
Updates

Lead Judging Commences

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