Core Contracts

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

Proposals cannot contain duplicate transactions

Summary

When queueing a proposal in the TimelockController contract, there is no check done for each proposed action which verifies that this action is not being done already in this proposal.

Vulnerability Details

  • As we can see, the scheduleBatch function lacks verification to check if the proposed action has already been processed.

function scheduleBatch(
address[] calldata targets,
uint256[] calldata values,
bytes[] calldata calldatas,
bytes32 predecessor,
bytes32 salt,
uint256 delay
) external override onlyRole(PROPOSER_ROLE) returns (bytes32) {
// Input validation: check if the number of targets, values, and calldatas are the same
if (targets.length == 0 || targets.length != values.length || targets.length != calldatas.length) {
revert InvalidTargetCount();
}
// Check if the delay is within the allowed range
if (delay < _minDelay || delay > _maxDelay) {
revert InvalidDelay(delay);
}
// Check predecessor if specified
if (predecessor != bytes32(0)) {
if (!isOperationDone(predecessor) && !isOperationPending(predecessor)) {
revert PredecessorNotExecuted(predecessor);
}
}
bytes32 id = hashOperationBatch(targets, values, calldatas, predecessor, salt);
if (_operations[id].timestamp != 0) revert OperationAlreadyScheduled(id);
uint256 timestamp = block.timestamp + delay;
_operations[id] = Operation({
timestamp: timestamp.toUint64(),
executed: false
});
emit OperationScheduled(id, targets, values, calldatas, predecessor, salt, delay);
return id;
}
  • Once the vote succeeds and the timelock delay has elapsed, the successful proposal can be executed through execute function.

function execute(uint256 proposalId) external override nonReentrant {
ProposalCore storage proposal = _proposals[proposalId];
if (proposal.executed) revert ProposalAlreadyExecuted(proposalId, block.timestamp);
ProposalState currentState = state(proposalId);
// Check if the proposal is in the correct state for execution
if (currentState == ProposalState.Succeeded) {
// Queue the proposal
_queueProposal(proposalId);
} else if (currentState == ProposalState.Queued) {
// Execute the queued proposal
_executeProposal(proposalId);
} else {
// If not in Succeeded or Queued state, revert
revert InvalidProposalState(
proposalId,
currentState,
currentState == ProposalState.Active ? ProposalState.Succeeded : ProposalState.Queued,
"Invalid state for execution"
);
}
}

Impact

Executing duplicate transactions can lead to unintended behavior.

Tools Used

Manual audit

Recommendations

I recommend implementing a check for each proposed action to verify that it is not already being executed within the same proposal.

Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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

Give us feedback!