Core Contracts

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

proposals wont execute with the intended path due to onlyrole modifier

Vulnerability Details

The execute function in the Governance contract cannot successfully execute proposals through the TimelockController because the Governance contract lacks the EXECUTOR_ROLE in the Timelock. This results in proposals being queued but not being executed via intended path, despite meeting all voting requirements

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/proposals/Governance.sol#L522-L546

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/proposals/Governance.sol#L487-L505

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/proposals/TimelockController.sol#L121

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/proposals/TimelockController.sol#L168

root cause

The TimelockController requires the EXECUTOR_ROLE to execute proposals

here we can see that the function has a onlyrole modifier

function executeBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata calldatas,
bytes32 predecessor,
bytes32 salt
) external override payable nonReentrant onlyRole(EXECUTOR_ROLE) { //@audit
bytes32 id = hashOperationBatch(targets, values, calldatas, predecessor, salt);
function scheduleBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata calldatas,
bytes32 predecessor,
bytes32 salt,
uint256 delay
) external override onlyRole(PROPOSER_ROLE) returns (bytes32) { //@audit
// Input validation: check if the number of targets, values, and calldatas are the same

here we can see that the function in governance calls schedule batch and execute batch is which means thats the intended path

function _queueProposal(uint256 proposalId) internal {
ProposalCore storage proposal = _proposals[proposalId];
bytes32 salt = proposal.descriptionHash;
bytes32 id = _timelock.hashOperationBatch(
proposal.targets,
proposal.values,
proposal.calldatas,
bytes32(0),
salt
);
// Check if already queued
if (_timelock.isOperationPending(id)) {
revert ProposalAlreadyExecuted(proposalId, block.timestamp);
}
// Schedule in timelock
_timelock.scheduleBatch( ///@audit
proposal.targets,
function _executeProposal(uint256 proposalId) internal {
ProposalCore storage proposal = _proposals[proposalId];
bytes32 salt = proposal.descriptionHash;
bytes32 id = _timelock.hashOperationBatch(
proposal.targets,
proposal.values,
proposal.calldatas,
bytes32(0),
salt
);
// Check if ready for execution
if (!_timelock.isOperationReady(id)) {
revert ProposalNotQueued(proposalId, id);
}
// Execute through timelock
_timelock.executeBatch( //@audit
proposal.targets,

Impact

proposals never go through the intended path due to the onlyrole modifier

Recommendations

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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