When executing successful proposals, the RAAC protocol implements a time-lock mechanism. A proposal can only be scheduled if the predecessor has been executed. However, the value of the predecessor is hardcoded - all proposals can be scheduled and executed even if the previous batch has not been executed. The proposal queueing hence works incorrectly.
executeBatch()Assume this scenario:
execute()function in Goveernance.sol has been called
state()function is called to determine the state of the proposal. This function returns the state as ProposalState.Queued
Back to execute()function, due to the above returned state, _executeProposal()will be called
In the code snippet below from _executeProposal(), function checks if it is ready for execution. Assume _timelock.isOperationReady(id) = true and revert will not occur
_timelock.executeBatch()will be called, whereby the bytes32(0) is passed as the predecessor.
As seen in line 20 in the snippet below, the function checks for the predecessor whether it has been executed or not
since the value of predecessor is hardcoded to bytes32(0), the actual predecessor is never checked for.
The proposal can be executed even if the previous successful proposal has not been executed yet.
scheduleBatch()The hardcoded predecessor value of bytes32(0) also occurs in _queueProposal()when scheduling a batch. In TimelockController.scheduleBatch()which is internally called in _queueProposal(), it also checks if the predecessor has been executed or not.
The time-lock and executing mechanism is dysfunctional as it does not require the predecessor to be executed in order to execute or schedule the current proposal.
Manual
Ensure the predecessor value is not hard-coded to bytes32(0).
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.