Core Contracts

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

`Timelock` emergency actions can be executed without enforcing the minimum delay

Description

Emergency actions in the TimelockController contract can be executed immediately after being scheduled due to lack of validation for the 1-day delay. While both the documentation and code state the existence of a 1-day delay for emergency actions, this is delay is never enforced.

Context

Impact

Medium. The lack of delay enforcement in emergency actions breaks a core invariant advertised to users, though damage is limited to the action being executed.

Likelihood

Medium. The issue can occur every time an emergency action is executed, breaking trust assumptions advertised to users.

Proof of Concept

The following proof of concept demonstrates that the emergency action delay, of 1 day, is not being enforced. To execute this proof of concept integrate foundry by running the following commands in your terminal, in the project's root directory:

# Create required directories
mkdir out lib
# Add `forge-std` module to `lib`
git submodule add https://github.com/foundry-rs/forge-std lib/forge-std
# Create foundry.toml
touch foundry.toml

Next, configure foundry by adding the following settings to foundry.toml:

[profile.default]
src = "contracts"
out = "out"
lib = "lib"

After that, create a foundry/ directory inside the test/ directory. Inside foundry/, create the following file:

  • ProposalsModule.t.sol

And then paste the following code to ProposalsModule.t.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} from "../../../../lib/forge-std/src/Test.sol";
import {Governance, IGovernance} from "../../../../contracts/core/governance/proposals/Governance.sol";
import {TimelockController} from "../../../../contracts/core/governance/proposals/TimelockController.sol";
import {RAACToken} from "../../../../contracts/core/tokens/RAACToken.sol";
import {veRAACToken, IveRAACToken} from "../../../../contracts/core/tokens/veRAACToken.sol";
contract ProposalsModuleTest is Test {
address public immutable OWNER = makeAddr("owner");
address public immutable PROPOSER = makeAddr("proposer");
address public immutable EXECUTOR = makeAddr("executor");
address public immutable MINTER = makeAddr("minter");
address public immutable USER = makeAddr("user");
Governance public governance;
TimelockController public timelock;
RAACToken public raac;
veRAACToken public veRaac;
function setUp() public {
vm.startPrank(OWNER);
// Set proposers and executors
address[] memory proposers = new address[](1);
proposers[0] = PROPOSER;
address[] memory executors = new address[](1);
executors[0] = EXECUTOR;
// Deploy governance contracts
timelock = new TimelockController(2 days, proposers, executors, OWNER);
raac = new RAACToken(address(timelock), 0, 0);
veRaac = new veRAACToken(address(raac));
governance = new Governance(address(veRaac), address(timelock));
// Grant proposer role to governance
timelock.grantRole(timelock.PROPOSER_ROLE(), address(governance));
timelock.grantRole(timelock.EXECUTOR_ROLE(), address(governance));
vm.stopPrank();
}
function test_poc_instant_emergency_withdraw() public {
// Set the owner as the sender.
vm.startPrank(OWNER);
// The owner computes the id of the emergency action.
address[] memory targets = new address[](1);
targets[0] = address(raac);
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSignature("setMinter(address)", MINTER);
bytes32 id = timelock.hashOperationBatch(targets, values, calldatas, bytes32(0), bytes32("1337"));
// The owner schedules an emergency action.
timelock.scheduleEmergencyAction(id);
// The owner can execute the action right after.
timelock.executeEmergencyAction(targets, values, calldatas, bytes32(0), bytes32("1337"));
}
}

Recommendations

  • Create a mapping in TimelockController that tracks the timestamp in which each emergency action will be ready for execution.

mapping(bytes32 => uint256) public emergencyTimestamps;
  • Store the timestamp in which the emergency action will be ready for execution in the TimelockController::scheduleEmergencyAction().

emergencyTimestamps[id] = block.timestamp + EMERGENCY_DELAY;
  • Validate that emergency action is ready for execution in the TimelockController::executeEmergencyAction().

if (block.timestamp < emergencyTimestamps[id]) revert EmergencyActionNotReady(id);
Updates

Lead Judging Commences

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

TimelockController emergency actions bypass timelock by not enforcing EMERGENCY_DELAY, allowing immediate execution

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

TimelockController emergency actions bypass timelock by not enforcing EMERGENCY_DELAY, allowing immediate execution

Support

FAQs

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