The governance system (Governance.sol
and veRAACToken.sol
) allows malicious actors to force through any governance proposal by manipulating voting power at the last minute. The root cause is that voting power is calculated at vote time rather than being snapshotted at proposal creation.
Problem is the checkpoints used to track user balances for a specific time are not used in the Governance
contract. The checkpoints are a well-known mechanism used in OZ Governance and ERC20Votes contract, but in RAAC they are not being used.
Example of checking for when user is locking RAAC tokens.
Notice veRAAC
has the function getPastVotes
, that returns the user balance for that block number.
This function is well known in the integration of the Governor from OZ with ERC20Votes, as it is used to cast the user votes properly, taking into account the block/time of the proposal creation.
Now checking the Governance
contract, we see votingPower
is used instead of the checkpoint(snapshot) balance.
The system uses current voting power when votes are cast, making it vulnerable to last-minute manipulation:
Look how the voting power is calculated when user locks his tokens calling veRAAC.lock
:
This is how the getVotingPower
logic works: the voting power calculation decreases over time from the initial lock period. The closer to when tokens were first locked, the higher the voting power. An attacker could exploit this by locking tokens just before voting, ensuring their voting power starts at maximum, nearly equal to their deposited amount.
So 1 hour after locking the tokens for 4 years, the voting power number is almost the same as the amount deposited. I.e:
Notice the Governance
contract uses the voting power to account for the votes:
The second issue here is the quorum. The quorum calculation uses current total voting power, which can also be increase if attacker locks a certain amount of tokens to pass the quorum threshold.
This opens the possibility for the following attack scenario:
Attacker creates a proposal for any malicious action(to upgrade contracts to malicious versions, withdraw all funds, modify access control to take over the protocol, etc).
Now the attacker will take action:
Current proposal state: 900K in voting power voted against the proposal.
Attacker creates 1 account in the last minute of voting for that proposal.
Attacker locks 10M RAAC for 4 years = after 1 second the attacker has > 9.9M in voting power.
Attacker votes for "yes" in the proposal. New vote totals: 9.9M "yes" vs 900K "no".
Attacker proposal succeeds and he takes over the protocol.
The logic of voting power gives more voting power to recently locked positions than previous existing positions, creating the ideal scenario for governance manipulation.
Malicious actors can force through ANY governance proposal by acquiring and locking enough tokens.
The entire protocol's security model is compromised as critical parameters, contracts, and funds can be controlled through governance.
Long-term token holders' voting power is effectively nullified by last-minute large token locks.
Manual Review
The governance should use a voting power snapshot at proposal creation time, similar to how Compound and other major governance systems work. OZ contracts(Governance.sol
and ERC20Votes.sol
) are recommended.
Consider not using VotingPowerLib
accounting for the votes, as users' voting power decreases over time due to the decay logic and new locks have a higher amount of voting power.
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.