The veRAACToken
contract includes two functions, withdraw
and emergencyWithdraw
, which allow users to retrieve their locked tokens and clear storage upon successful withdrawal. While these functions correctly delete a user’s lock record from _lockState.locks[msg.sender]
, they fail to clear other critical storage mappings. Specifically, data related to voting power, checkpoints, and boost calculations remain in storage, even after a user's lock has been removed.
This oversight results in stale, inconsistent data persisting in storage, which can distort calculations related to governance voting power, historical snapshots, and reward distribution. If malicious actors exploit this, it can lead to unfair governance advantages, incorrect quorum requirements, and unintended consequences in the protocol’s boost mechanism.
Upon withdrawal or emergency withdrawal, the contract only deletes the user's lock record but fails to remove related records in other mappings. The following data remains in storage:
Voting Power Records (_votingState.points[msg.sender]
)
Impact: User’s historical voting power remains stored, which could be used incorrectly in future calculations.
Fix: Should be cleared upon withdrawal.
Checkpoint Data (_votingState.checkpoints[msg.sender]
and _checkpointState.userCheckpoints[msg.sender]
)
Impact: Old voting power records persist, which might artificially inflate governance weight or distort historical data.
Fix: Clear the checkpoint data upon withdrawal.
User-Specific Boost Periods (_boostState.userPeriods[msg.sender]
)
Impact: User-specific boost factors may remain, leading to incorrect calculations in rewards and governance participation.
Fix: Remove boost periods upon withdrawal.
In the withdraw
and emergencyWithdraw
functions, we see the following deletion logic:
However, the following key storage mappings are not cleared, leading to residual data that can persist indefinitely:
To demonstrate this vulnerability, the following Proof of Concept (PoC) is provided. The PoC is written using the Foundry tool.
Step 1: Create a Foundry project and place all the contracts in the src
directory.
Step 2: Create a test
directory and a mocks
folder within the src
directory (or use an existing mocks folder).
Step 3: Create all necessary mock contracts, if required.
Step 4: Create a test file (with any name) in the test
directory.
Step 5: Add the following test PoC in the test file, after the setUp
function.
Step 6: To run the test, execute the following commands in your terminal:
Step 7: Review the output. The expected output should indicate that user data still persist after withdrawal and cleanup.
As demonstrated, the test confirms that the user data persist in storage even after withdrawal and storage cleanup.
Notice: Some modifications have been made to address additional issues.
veRAACToken::withdraw:
(modified chunk)
veRAACToken::getUserBoostTimePeriods:
(newly introduced getter for ease)
veRAACToken::_updateBoostState
(modified) Although it's not necessary to modify therefore we can test for withdrawal just after the lock.
Distorted Governance Quorum & Voting Power:
Users may still be counted in governance calculations even after they have withdrawn their tokens.
This could lead to incorrect quorum requirements and potential governance manipulation.
Stale Boost Factors Affecting Rewards:
Since BoostState.userPeriods[msg.sender]
is not cleared, a user may continue to receive rewards based on an old boost period even after withdrawing.
This introduces an unfair advantage for some users and affects overall fairness in reward distribution.
Incorrect Historical Data & Voting Checkpoints:
Stale checkpoint records could affect historical governance analysis and mislead users into thinking past votes are still valid.
The protocol may continue referencing outdated checkpoints even when the user is no longer participating in governance.
Potential Exploitation by Malicious Users:
Users could withdraw, re-lock tokens under a new account, and still retain governance weight from the previous account.
This artificial inflation of governance power could be used to manipulate proposals unfairly.
Manual Review
Foundry
AI (ChatGpt, Phind)
SRC Mutation
Modify the withdraw
and emergencyWithdraw
functions to clear all related storage mappings to prevent stale data from persisting. Update the functions to include:
Emit events when clearing storage, such as:
This allows external auditors and governance participants to track proper state updates.
Implement a helper function that verifies whether a user's data has been completely cleared after withdrawal.
Example:
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.