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:
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
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
}
Then add this inside createVestingSchedule()
:
vestingSchedules[beneficiary].category = category;
Lastly, add this inside emergencyRevoke()
:
categoryUsed[schedule.category] = categoryUsed[schedule.category] - unreleasedAmount;