In Governance.sol, execute()
function can be called by any user. To determine a proposal's state and whether it is successful, it internally calls the state()
function. However, in state()
, the calculation of the required quorum is based on the current total voting power, instead of limiting to only capturing the voting power at the start of the voting period. This can lead to vote manipulation due to the dynamic value of the required quorum.
Assume this scenario whereby a proposal has ended and no votes can be cast. The snippet below is from the state()
function, showing the logic in determining the state of the proposal.
Malicious user calls getVotes()
to get the proposal's forVotes
and againstVotes
. This would be the currentQuorum
of the proposal.
Malicious user calls quorum()
to get the current total voting power and the requiredQuorum
to allow the proposal to succeed. The user realises with the current total voting power, the proposal will indeed succeed.
As seen below, the quorum()
function retrieves the current voting power, which is based on the total supply of veRAAC tokens
The malicious user sees that the execute()
function is in the mempool for this proposal. As the user wishes for this proposal to be defeated, the user now calls veToken.lock()
before execute()
function is executed, in order to lock RAAC tokens and get minted veRAAC tokens, hence increasing the total supply
Since total supply of veRAAC tokens has now increased, the current total voting power now increased, leading to requiredQuorum
to increase as well.
Now, when execute()
function is executed, the currentQuorum
will not hit the requiredQuorum
. The state()
function called internally will return ProposalState.Defeated
. The execute()
function will revert.
As seen above, the current total voting power is dynamic and compared against the current quorum of the proposal, which will not be changed once voting period ends. Hence, the outcome of proposals can be manipulated easily.
Additionally, the execute()
can be called as many times as long as it has not been executed. Since the determining of the proposal state is dependent on the current total voting power at the time the function is executed, it is possible for voters to manipulate the proposal outcome and eventually result in the proposal to succeed and execute.
This results in a largely flawed voting system.
Manual
Capture a snapshot of the total voting power at the start of the proposal voting period to determine the required quorum that is not dynamic.
If proposal outcome == defeated, execute()
function should not be able to be called for that proposal
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.