Core Contracts

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

Lack of Transparency in Emergency Revocation in emergencyRevoke() in RAACReleaseOrchestrator.sol

Summary

The emergencyRevoke function has two key issues:

  1. Tokens are transferred to the contract (address(this)) instead of a designated treasury wallet (TREASURY_WALLET). This could lead to inefficient fund management.

  2. The function does not include a reason parameter, making it harder to track why an emergency revocation was triggered.

Vulnerability Details

function emergencyRevoke(address beneficiary) external onlyRole(EMERGENCY_ROLE) { //@audit-issue : add string memory reason, whenPaused to prevent frontrunning
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); //@audit-issue : transfer to address treasury = TREASURY_WALLET; instead of address(this)
emit EmergencyWithdraw(beneficiary, unreleasedAmount);
}
emit VestingScheduleRevoked(beneficiary); // add reason to event
}

The function currently transfers unreleased vested tokens to address(this).

If the contract is compromised because some issues, these tokens may become inaccessible.

Instead, the funds should be transferred to a designated treasury wallet (TREASURY_WALLET)

Emergency revocations often happen due to fraud, compliance issues, or admin decisions.

Without a reason parameter, there is no way to track why a revocation occurred, leading to poor transparency and accountability.

Adding a reason (string) to the function and event ensures better documentation and future audits.

Impact

Locked or Mismanaged Funds: If tokens are sent to address(this), they may be permanently locked unless another function allows retrieval.

Transparency & Accountability Issues: Without a reason for revocation, it is difficult to justify emergency actions to stakeholders, auditors, or governance bodies.

Tools Used

Manual Review

Recommendations

Transfer Unreleased Tokens to Treasury (TREASURY_WALLET)

Modify the function signature to include a string memory reason:

Modify the event to contain "reason".

-- function emergencyRevoke(address beneficiary) external onlyRole(EMERGENCY_ROLE) {
++ function emergencyRevoke(address beneficiary, string memory reason) external onlyRole(EMERGENCY_ROLE) whenPaused {
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);
++ raacToken.transfer(TREASURY_WALLET, unreleasedAmount);
emit EmergencyWithdraw(beneficiary, unreleasedAmount);
}
-- emit VestingScheduleRevoked(beneficiary);
++ emit VestingScheduleRevoked(beneficiary, reason);
}
Updates

Lead Judging Commences

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