Core Contracts

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

emergencyRevoke() does not deduct unreleasedAmount from categoryUsed mapping

Description

When createVestingSchedule() is called for a beneficiary, the categoryUsed[category] variable is incremented by amount:

File: contracts/core/minters/RAACReleaseOrchestrator/RAACReleaseOrchestrator.sol
77: function createVestingSchedule(
78: address beneficiary,
79: bytes32 category,
80: uint256 amount,
81: uint256 startTime
82: ) external onlyRole(ORCHESTRATOR_ROLE) whenNotPaused {
83: if (beneficiary == address(0)) revert InvalidAddress();
84: if (amount == 0) revert InvalidAmount();
85: if (vestingSchedules[beneficiary].initialized) revert VestingAlreadyInitialized();
86: if (categoryAllocations[category] == 0) revert InvalidCategory();
87:
88: // Check category allocation limits
89:@---> uint256 newCategoryTotal = categoryUsed[category] + amount;
90: if (newCategoryTotal > categoryAllocations[category]) revert CategoryAllocationExceeded();
91:@---> categoryUsed[category] = newCategoryTotal;
92:
93: VestingSchedule storage schedule = vestingSchedules[beneficiary];
94: schedule.totalAmount = amount;
95: schedule.startTime = startTime;
96: schedule.duration = VESTING_DURATION;
97: schedule.initialized = true;
98:
99: emit VestingScheduleCreated(beneficiary, category, amount, startTime);
100: }

However, when emergencyRevoke() is called it does not deduct the unreleasedAmount from categoryUsed[category]:

File: contracts/core/minters/RAACReleaseOrchestrator/RAACReleaseOrchestrator.sol
126: function emergencyRevoke(address beneficiary) external onlyRole(EMERGENCY_ROLE) {
127: VestingSchedule storage schedule = vestingSchedules[beneficiary];
128: if (!schedule.initialized) revert NoVestingSchedule();
129:
130: uint256 unreleasedAmount = schedule.totalAmount - schedule.releasedAmount;
131: delete vestingSchedules[beneficiary];
132:
133: if (unreleasedAmount > 0) {
134: raacToken.transfer(address(this), unreleasedAmount);
135: emit EmergencyWithdraw(beneficiary, unreleasedAmount);
136: }
137:
138: emit VestingScheduleRevoked(beneficiary);
139: }

In fact, there is no way to know which category a beneficiary's vesting schedule belongs to!

Impact

Protocol can't create vesting schedule for new users even though the categoryAllocations[category] limit has not been reached.

1. categoryAllocations[] for category1 is configured as 200
2. createVestingSchedule() called for user1 for category1 for amount 100
3. createVestingSchedule() called for user2 for category1 for amount 90
4. emergencyRevoke() called for user2
5. createVestingSchedule() called for user2 for category1 for amount 50 reverts because protocol erroneously believes it is just 10 below the categoryAllocations[category1] limit even though it has room for 100 more.

Mitigation

  1. First, modify the VestingSchedule struct to track the category:

struct VestingSchedule {
uint256 totalAmount;
uint256 startTime;
uint256 duration;
uint256 releasedAmount;
uint256 lastClaimTime;
bool initialized;
+ bytes32 category; // Add category tracking
}
  1. Then add this inside createVestingSchedule():

vestingSchedules[beneficiary].category = category;
  1. Lastly, add this inside emergencyRevoke():

categoryUsed[schedule.category] = categoryUsed[schedule.category] - unreleasedAmount;
Updates

Lead Judging Commences

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