Core Contracts

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

Inadequate Emergency Shutdown Role Enforcement in RAACMinter: Denial of Service in Emergency Response

Summary

The RAACMinter contract is designed with a robust role-based access control system to manage various operational functions, including pausing, updating parameters, and handling emergency shutdowns. It designates multiple roles such as DEFAULT_ADMIN_ROLE, PAUSER_ROLE, UPDATER_ROLE, and EMERGENCY_SHUTDOWN_ROLE. However, the emergencyShutdown function is currently restricted using the DEFAULT_ADMIN_ROLE instead of the dedicated EMERGENCY_SHUTDOWN_ROLE. This misconfiguration means that even if a trusted admin is granted the EMERGENCY_SHUTDOWN_ROLE, they are unable to execute the emergency shutdown unless they also hold the DEFAULT_ADMIN_ROLE. In critical situations, this could delay or prevent necessary shutdowns, exposing the protocol to risk.

Vulnerability Details

Code Analysis

The problematic function in the RAACMinter contract is implemented as follows:

function emergencyShutdown(bool updateLastBlock, uint256 newLastUpdateBlock)
external
// @info: not using EMERGENCY_SHUTDOWN_ROLE
onlyRole(DEFAULT_ADMIN_ROLE)
{
emissionRate = 0;
_pause();
if (updateLastBlock) {
_setLastUpdateBlock(newLastUpdateBlock);
}
emit EmergencyShutdown(msg.sender, lastUpdateBlock);
}

Issues Identified:

  • Incorrect Role Restriction:
    The function is protected by onlyRole(DEFAULT_ADMIN_ROLE), meaning that only accounts with the default admin role can perform an emergency shutdown. However, the contract design includes a dedicated EMERGENCY_SHUTDOWN_ROLE intended for this purpose.

  • Access Control Ambiguity:
    In the event that the initial owner (DEFAULT_ADMIN_ROLE) delegates emergency shutdown authority to another trusted admin by granting them EMERGENCY_SHUTDOWN_ROLE, that admin will be unable to call emergencyShutdown because they do not possess DEFAULT_ADMIN_ROLE.

  • Operational Impact:
    This oversight may lead to delays or failure in executing emergency shutdowns, which are critical for protecting the protocol during unforeseen events.

Proof of Concept

Test Suite Walkthrough

The following Foundry test demonstrates the issue. In this test, the RAACMinter contract is deployed, and the RAAC_Minter owner grants the EMERGENCY_SHUTDOWN_ROLE to ALICE. When ALICE attempts to call emergencyShutdown, the call reverts because she lacks the DEFAULT_ADMIN_ROLE.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} from "forge-std/Test.sol";
import {RAACMinter} from "../src/core/minters/RAACMinter/RAACMinter.sol";
import {RAACToken} from "../src/core/tokens/RAACToken.sol";
contract RAACMinterTest is Test {
RAACToken raacToken;
RAACMinter raacMinter;
address RAAC_OWNER = makeAddr("RAAC_OWNER");
address RAAC_MINTER_OWNER = makeAddr("RAAC_MINTER_OWNER");
uint256 initialRaacSwapTaxRateInBps = 200; // 2%
uint256 initialRaacBurnTaxRateInBps = 150; // 1.5%
address ALICE = makeAddr("ALICE");
address BOB = makeAddr("BOB");
address CHARLIE = makeAddr("CHARLIE");
address DEVIL = makeAddr("DEVIL");
address stabilityPool = makeAddr("STABILITY_POOL");
address lendingPool = makeAddr("LENDING_POOL");
function setUp() public {
raacToken = new RAACToken(RAAC_OWNER, initialRaacSwapTaxRateInBps, initialRaacBurnTaxRateInBps);
vm.warp(block.timestamp + 1 days);
raacMinter = new RAACMinter(address(raacToken), stabilityPool, lendingPool, RAAC_MINTER_OWNER);
}
function testRAACMinterAdminWithProperEmergencyShutdownRoleCannotExecuteEmergencyShutdownFunction() public {
vm.startPrank(RAAC_MINTER_OWNER);
// Grant the EMERGENCY_SHUTDOWN_ROLE to ALICE
raacMinter.grantRole(raacMinter.EMERGENCY_SHUTDOWN_ROLE(), ALICE);
vm.stopPrank();
vm.startPrank(ALICE);
// ALICE, having EMERGENCY_SHUTDOWN_ROLE but not DEFAULT_ADMIN_ROLE, attempts to perform an emergency shutdown.
// Expect revert because the function only allows DEFAULT_ADMIN_ROLE.
vm.expectRevert(
abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", ALICE, bytes32(0))
);
raacMinter.emergencyShutdown(false, 0);
vm.stopPrank();
}
}

How to Run the Test

  1. Initialize a Foundry Project:

    forge init my-foundry-project
  2. Place Contract Files:
    Ensure that RAACToken.sol, RAACMinter.sol, and related contracts are in the src directory.

  3. Create Test Directory:
    Create a test directory adjacent to src and add the test file (e.g., RAACMinterTest.t.sol).

  4. Run the Test:

    forge test --mt testRAACMinterAdminWithProperEmergencyShutdownRoleCannotExecuteEmergencyShutdownFunction -vv
  5. Expected Outcome:
    The test should revert with an "AccessControlUnauthorizedAccount" error, confirming that the emergency shutdown function is inaccessible to accounts with only the EMERGENCY_SHUTDOWN_ROLE.

Impact

  • Denial of Emergency Response:
    In the event of an emergency, if the designated admin with the EMERGENCY_SHUTDOWN_ROLE cannot execute the shutdown, the protocol may remain exposed to attacks or market volatility.

  • Operational Breakdown:
    The inability to trigger an emergency shutdown can delay critical interventions, potentially exacerbating any exploit or failure conditions.

  • Trust and Security Risks:
    Stakeholders may lose confidence in the protocol's safety mechanisms if emergency procedures cannot be executed by the intended personnel.

Tools Used

  • Manual Review

  • Foundry

Recommendations

To resolve this issue, the emergencyShutdown function should enforce access control based on the EMERGENCY_SHUTDOWN_ROLE rather than the DEFAULT_ADMIN_ROLE. Alternatively, it can require that the caller holds either role. The following diff shows how to update the function:

Proposed Diff Update

- function emergencyShutdown(bool updateLastBlock, uint256 newLastUpdateBlock)
- external
- // @info: not using EMERGENCY_SHUTDOWN_ROLE
- onlyRole(DEFAULT_ADMIN_ROLE)
- {
- emissionRate = 0;
- _pause();
- if (updateLastBlock) {
- _setLastUpdateBlock(newLastUpdateBlock);
- }
- emit EmergencyShutdown(msg.sender, lastUpdateBlock);
- }
+ function emergencyShutdown(bool updateLastBlock, uint256 newLastUpdateBlock)
+ external
+ onlyRole(EMERGENCY_SHUTDOWN_ROLE)
+ {
+ emissionRate = 0;
+ _pause();
+ if (updateLastBlock) {
+ _setLastUpdateBlock(newLastUpdateBlock);
+ }
+ emit EmergencyShutdown(msg.sender, lastUpdateBlock);
+ }

Benefits of This Approach

  • Proper Emergency Access:
    This update ensures that only accounts specifically granted the EMERGENCY_SHUTDOWN_ROLE can trigger an emergency shutdown, aligning the access control with the protocol's design intentions.

  • Enhanced Flexibility:
    It allows the protocol's admin to delegate emergency shutdown authority without giving full DEFAULT_ADMIN privileges, which is a more secure and granular access control practice.

  • Clear Role Separation:
    Distinguishing emergency functions from other administrative functions helps maintain a clear separation of duties, reducing the risk of unauthorized or accidental shutdowns.

  • Improved Security and Trust:
    By ensuring that emergency shutdowns can be executed by the designated emergency administrators, the protocol enhances its resilience against attacks or systemic failures, thereby maintaining stakeholder confidence.

Implementing these changes will ensure that the RAACMinter contract's emergency shutdown function is accessible only to the appropriate emergency administrators. This will prevent potential delays or failures in executing emergency procedures, thereby safeguarding the protocol during critical situations.

Updates

Lead Judging Commences

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

RAACMinter defines EMERGENCY_SHUTDOWN_ROLE but emergencyShutdown uses DEFAULT_ADMIN_ROLE, and EMERGENCY_SHUTDOWN_ROLE is never granted to any account

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

RAACMinter defines EMERGENCY_SHUTDOWN_ROLE but emergencyShutdown uses DEFAULT_ADMIN_ROLE, and EMERGENCY_SHUTDOWN_ROLE is never granted to any account

Support

FAQs

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

Give us feedback!