Core Contracts

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

Emergency revoke in RAACReleaseOrchestrator will freeze revoked RAAC tokens in orchestrator

Summary

Incorrect acounting of categoryUsed in RAACReleaseOrchestrator will cause revoked RAACToken to be stuck in orchestrator.

Vulnerability Details

When creating a vesting schedule, RAACReleaseOrchestrator accounts total allocated RAACToken amount in categoryUsed state variable:

uint256 newCategoryTotal = categoryUsed[category] + amount;
if (newCategoryTotal > categoryAllocations[category]) revert CategoryAllocationExceeded();
categoryUsed[category] = newCategoryTotal;

However, when a vesting is emergency revoked, revoked amount is not deducted from categoryUsed.

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]; // @audit unreleasedAmount is not deducted from categoryUsed
if (unreleasedAmount > 0) {
raacToken.transfer(address(this), unreleasedAmount);
emit EmergencyWithdraw(beneficiary, unreleasedAmount);
}
emit VestingScheduleRevoked(beneficiary);
}

This will prevent rellocation of those unreleased RAACToken and those cannot be reallocated to other categories either. Because other categories have their own max allocations.

POC

First, integrate foundry and run the following test with forge.

pragma solidity ^0.8.19;
import "../lib/forge-std/src/Test.sol";
import {RAACToken} from "../contracts/core/tokens/RAACToken.sol";
import {RAACReleaseOrchestrator} from "../contracts/core/minters/RAACReleaseOrchestrator/RAACReleaseOrchestrator.sol";
contract RAACReleaseOrchestratorTest is Test {
RAACToken raacToken;
RAACReleaseOrchestrator orchestrator;
function setUp() external {
raacToken = new RAACToken(address(this), 0, 0);
orchestrator = new RAACReleaseOrchestrator(address(raacToken));
deal(address(raacToken), address(orchestrator), orchestrator.getTotalAllocation());
}
function testRevertEmergencyRevoke() external {
uint256 startTime = block.timestamp;
address team = makeAddr("team");
bytes32 category = orchestrator.TEAM_CATEGORY();
orchestrator.createVestingSchedule(team, category, 18_000_000 ether, startTime);
orchestrator.emergencyRevoke(team);
vm.expectRevert(abi.encodeWithSignature("CategoryAllocationExceeded()"));
orchestrator.createVestingSchedule(team, category, 1 ether, startTime);
}
}

Impact

Revoked RAACTokens will be stuck at orchestrator

Tools Used

Manual Review, Foundry

Recommendation

Deducted unreleased amount from categoryUsed on emergency revoke

Updates

Lead Judging Commences

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

RAACReleaseOrchestrator::emergencyRevoke sends revoked tokens to contract address with no withdrawal mechanism, permanently locking funds

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

RAACReleaseOrchestrator::emergencyRevoke sends revoked tokens to contract address with no withdrawal mechanism, permanently locking funds

Support

FAQs

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

Give us feedback!