Core Contracts

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

Potential rejection of new vesting schedules

Summary

The RAACReleaseOrchestrator contract contains a vulnerability in the emergencyRevoke function, where the categoryUsed[category] value is not decremented when a vesting schedule is revoked. This inconsistency in state management can lead to inaccurate tracking of category allocations, potentially causing the contract to reject valid new vesting schedules due to perceived over-allocation of a category.

Vulnerability Details

The createVestingSchedule function increments categoryUsed[category] when a new vesting schedule is created, ensuring that the allocated amount is tracked correctly.

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.duration = VESTING_DURATION;
schedule.initialized = true;
emit VestingScheduleCreated(beneficiary, category, amount, startTime);
}

However, the emergencyRevoke function does not decrement categoryUsed[category] when a vesting schedule is revoked. This results in the revoked allocation being incorrectly counted as "used" even though the tokens are no longer allocated to the beneficiary.

function emergencyRevoke(address beneficiary) external onlyRole(EMERGENCY_ROLE) {
VestingSchedule storage schedule = vestingSchedules[beneficiary];
if (!schedule.initialized) revert NoVestingSchedule();
uint256 unreleasedAmount = schedule.totalAmount - schedule.releasedAmount;
delete vestingSchedules[beneficiary];
if (unreleasedAmount > 0) {
raacToken.transfer(address(this), unreleasedAmount);
emit EmergencyWithdraw(beneficiary, unreleasedAmount);
}
emit VestingScheduleRevoked(beneficiary);
}

Example Scenario:

  • Suppose the TEAM_CATEGORY has a total allocation of 18,000,000 tokens.

  • A vesting schedule for 5,000,000 tokens is created for a team member, incrementing categoryUsed[TEAM_CATEGORY] to 5,000,000.

  • Later, the vesting schedule is revoked via emergencyRevoke, but categoryUsed[TEAM_CATEGORY] remains at 5,000,000.

  • When attempting to create a new vesting schedule for 15,000,000 tokens, the contract incorrectly calculates the available allocation as 13,000,000 (18,000,000 - 5,000,000) instead of the correct 18,000,000, leading to the rejection of the new schedule.

Impact

  • If multiple vesting schedules are revoked, the categoryUsed[category] value remains artificially high.

  • This can lead to the contract rejecting new vesting schedules for the category, even if there is sufficient allocation available.

  • The issue undermines the contract's ability to accurately manage and enforce category allocation limits.

The impact is High, the likelihood is Medium, so the severity is High.

Tools Used

Manual Review

Recommendations

In createVestingSchedule function, it should record the category:

VestingSchedule storage schedule = vestingSchedules[beneficiary];
schedule.totalAmount = amount;
schedule.startTime = startTime;
schedule.duration = VESTING_DURATION;
schedule.initialized = true;
schedule.category = category;

In emergencyRevoke function, it should decrement categoryUsed[category] by the unreleased amount of the revoked vesting schedule:

function emergencyRevoke(address beneficiary) external onlyRole(EMERGENCY_ROLE) {
VestingSchedule storage schedule = vestingSchedules[beneficiary];
if (!schedule.initialized) revert NoVestingSchedule();
uint256 unreleasedAmount = schedule.totalAmount - schedule.releasedAmount;
// Decrement the categoryUsed value by the unreleased amount
bytes32 category = schedule.category;
categoryUsed[category] -= unreleasedAmount;
delete vestingSchedules[beneficiary];
if (unreleasedAmount > 0) {
raacToken.transfer(address(this), unreleasedAmount);
emit EmergencyWithdraw(beneficiary, unreleasedAmount);
}
emit VestingScheduleRevoked(beneficiary);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

RAACReleaseOrchestrator::emergencyRevoke fails to decrement categoryUsed, causing artificial category over-allocation and rejection of valid vesting schedules

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

RAACReleaseOrchestrator::emergencyRevoke fails to decrement categoryUsed, causing artificial category over-allocation and rejection of valid vesting schedules

Support

FAQs

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