Core Contracts

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

[H-2] Lack of Emergency Pause in `BaseGauge::stake` and `BaseGauge::withdraw

Description:
Staking and withdrawal functions are critical components of a contract’s security, especially in emergency scenarios. If the contract is compromised (e.g., hacked or exploited), it is crucial to immediately halt staking and withdrawal to prevent further loss of funds.

While BaseGauge.sol includes an setEmergencyPaused function to toggle the emergency pause state, this modifier is not applied to the stake and withdraw functions. As a result, even if an emergency pause is triggered, users—including attackers—can still stake or withdraw funds, rendering the emergency mechanism ineffective.

Impact:
In the event of an exploit or malicious activity, the protocol's administrators may attempt to activate setEmergencyPaused to stop unauthorized fund movements. However, since stake and withdraw do not respect the paused state, attackers can continue withdrawing or moving assets, potentially leading to significant financial losses.

Attack Scenario:

  1. A hacker gains unauthorized access and begins withdrawing large amounts of protocol funds.

  2. The EMERGENCY_ADMIN detects the exploit and activates setEmergencyPaused.

  3. Despite this, the hacker continues withdrawing or staking funds, since these functions lack the emergency pause restriction.

Proof of Concept:

The stake and withdraw functions lack the emergency pause modifier, allowing them to function even when the contract is paused:

function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {
//@audit lacks setEmergencyPaused modifier
if (amount == 0) revert InvalidAmount();
_totalSupply += amount;
_balances[msg.sender] += amount;
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
emit Staked(msg.sender, amount);
}
function withdraw(uint256 amount) external nonReentrant updateReward(msg.sender) {
//@audit lacks setEmergencyPaused modifier
if (amount == 0) revert InvalidAmount();
if (_balances[msg.sender] < amount) revert InsufficientBalance();
_totalSupply -= amount;
_balances[msg.sender] -= amount;
stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}

Meanwhile, the pause function exists but is not enforced in stake/withdraw:

function setEmergencyPaused(bool paused) external {
if (!hasRole(EMERGENCY_ADMIN, msg.sender)) revert UnauthorizedCaller();
if (paused) {
_pause();
} else {
_unpause();
}
}

Recommended Mitigation:
To ensure proper security in emergency scenarios, apply the whenNotPaused modifier to both stake and withdraw functions. This will prevent users—including potential attackers—from performing these actions when the contract is paused.

+ function stake(uint256 amount) external nonReentrant whenNotPaused updateReward(msg.sender) {
...
+ }
+ function withdraw(uint256 amount) external nonReentrant whenNotPaused updateReward(msg.sender) {
...
+ }

Tools Used:

  • Manual Review

Updates

Lead Judging Commences

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

BaseGauge::withdraw, stake, and checkpoint functions lack whenNotPaused modifier, allowing critical state changes even during emergency pause

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

BaseGauge::withdraw, stake, and checkpoint functions lack whenNotPaused modifier, allowing critical state changes even during emergency pause

Support

FAQs

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