Core Contracts

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

Proposal Execution State Incorrectly Updated

Summary

The execute function marks a proposal as executed before verifying the success of the Timelock operation. If _timelock.executeBatch reverts, the proposal's executed flag is still set to true, leaving the proposal in an incorrect state.

Vulnerability Details

Initial State

  • A governance proposal has passed voting

  • The proposal is queued in the TimelockController

  • The proposal aims to update critical protocol parameters (e.g., MAX_WEEKLY_EMISSION in RAACGauge)

The Issue Unfolds.

The proposal reaches execution stage and someone calls execute(proposalId)

function _executeProposal(uint256 proposalId) internal {
// ... hash calculation ...
// Critical flaw: proposal marked executed before actual execution
_timelock.executeBatch(
proposal.targets,
proposal.values,
proposal.calldatas,
bytes32(0),
salt
);
proposal.executed = true; // <-- This happens even if executeBatch reverts
}
  1. The vulnerability manifests when:

    • _timelock.executeBatch() fails (e.g., due to invalid parameters)

    • The transaction reverts

    • But proposal.executed = true is already set

    • The ProposalExecuted event is emitted falsely

  2. The state becomes corrupted because:

    • The proposal is marked as executed in _proposals[proposalId]

    • Future execution attempts revert with ProposalAlreadyExecuted

    • The actual governance action never took effect

If a proposal to update FeeCollector.sol's fee distribution parameters fails during execution, the protocol continues with old parameters while governance records show the update as successful.

This mirrors the MakerDAO Governance Attack (2020) where a governance delay contract incorrectly marked proposals as executed. The attack could have allowed malicious proposals to be executed multiple times or prevent legitimate proposals from being executed at all.

Impact

Proposals may be marked as executed even if their actions failed, leading to governance actions not being performed despite being recorded as successful.

The _executeProposal() function violates a core principle of atomic state updates. The critical sequence:

_timelock.executeBatch(...);
proposal.executed = true;

Updates the state before confirming the execution's success. This creates a state-execution mismatch where the governance system's recorded state doesn't reflect reality.

Tools Used

vs

Recommendations

Modify the _executeProposal function to mark executed only after confirming the Timelock operation's success. Using try/catch (preferred approach).

function _executeProposal(uint256 proposalId) internal {
ProposalCore storage proposal = _proposals[proposalId];
bytes32 salt = proposal.descriptionHash;
try _timelock.executeBatch(
proposal.targets,
proposal.values,
proposal.calldatas,
bytes32(0),
salt
) returns (bool success) {
require(success, "Timelock execution failed");
proposal.executed = true;
emit ProposalExecuted(proposalId, msg.sender, block.timestamp);
} catch Error(string memory reason) {
emit ProposalExecutionFailed(proposalId, reason);
revert ExecutionFailed(proposalId, reason);
}
}

Using return value check.

This aligns with:

  • The protocol's existing error handling patterns

  • TimelockController's security model

  • The checks-effects-interactions pattern

function _executeProposal(uint256 proposalId) internal {
ProposalCore storage proposal = _proposals[proposalId];
bytes32 salt = proposal.descriptionHash;
bool success = _timelock.executeBatch(
proposal.targets,
proposal.values,
proposal.calldatas,
bytes32(0),
salt
);
require(success, "Timelock execution failed");
proposal.executed = true;
emit ProposalExecuted(proposalId, msg.sender, block.timestamp);
}
Updates

Lead Judging Commences

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

Support

FAQs

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