This vulnerability introduces a denial-of-service (DoS) condition that prevents users from locking their RAACTokens for veRAAC during a specific time frame due to an inconsistent period validation mechanism. This issue becomes particularly critical in governance scenarios, where users need to lock tokens to participate in voting. If this bug occurs right before an important governance vote, affected users will be unable to obtain voting power, leading to unrepresentative governance decisions. Additionally, malicious actors could exploit this bug by strategically extending totalDuration to prevent new users from locking tokens before a vote, consolidating voting power among a smaller group of existing veRAAC holders. This could compromise protocol integrity, allowing governance control to be manipulated.
A user can lock RAACTokens to gain veRAAC tokens via veRAACToken::lock. See below:
This function calls veRAACToken::_updateBoostState which in turn calls BoostCalculator::updateBoostPeriod which is our main focus.
The idea of this function is that it creates a period struct that uses time weighted averages to keep track of all user's locked voting weight by measuring their voting power weighted against how long they have been locked for. This period is a global period that measures the boost multiplier of all users based on their weighted values. If a period has already been started but the period has ended, the function calls TimeWeightedAverage::createPeriod to create a new period. The criteria for a period ending is determined when the current timestamp is >= period start time + the boost window of the period which is initialised in veRAACToken as 7 days.
The bug occurs via the following check in TimeWeightedAverage::createPeriod:
self.totalduration is a variable in the point struct that is initially set to the boost window but whenever the period is updated via TimeWeightedAverage::updateValue, self.totalDuration is incremented. See below:
As a result, if the period is updated, the endTime is not consistent with the expected endTime in BoostCalculator::updateBoostPeriod. Since the endTime in TimeWeightedAverage::createPeriod is no longer periodStart + state.boostWindow due to the increment of the totalDuration variable, if a user attempts to lock tokens between the expected endTime in BoostCalculator::updateBoostPeriod and the expected endTime in TimeWeightedAverage::createPeriod , the transaction will revert which prevents the user from being able to lock their RAACTokens for a period of time which causes an unnecessary DOS.
This test was run in the veRAACToken.test.js file in the "Lock Mechamism" describe block
This is the process flow of the DOS with an example :
A user creates a period at T=20 with a boostWindow = 10.
This sets endTime = 20 + 10 = 30.
totalDuration is now initialized to 10.
Another user locks tokens at T=25 with a boostWindow = 10.
Since the current time (T=25) is less than the period's endTime (30), the contract calls TimeWeightedAverage::updateValue().
Within this function:
duration is calculated as currentTime - lastUpdateTime = 25 - 20 = 5.
totalDuration increases by 5, making it 15.
A new user attempts to lock tokens at T=32.
The contract checks:
periodStart > 0 → True (period started at T=20).
currentTime (32) > periodStart + boostWindow (30) → True.
Since the period is assumed to have ended, the contract attempts to create a new period by calling TimeWeightedAverage::createPeriod().
Inside createPeriod(), the function checks:
Substituting values:
self.startTime = 20
currentTime = 32
self.totalDuration = 15
The condition evaluates to:
Since the condition is true, the function reverts—even though it should not.
As a result, the user attempting to lock tokens at T=32 encounters an unexpected reversion.
Note: For test to run successfully, I added the following to veRAACToken::BoostStateView :
This was a view function and I adapted it to allow for viewing the period's data.
This vulnerability poses a critical governance risk, as it can prevent users from locking RAACTokens and obtaining veRAAC voting power during key decision-making periods. If a governance vote is scheduled while this bug is active, affected users will be unable to participate, leading to skewed governance outcomes. Malicious actors could strategically trigger this issue to block new veRAAC holders from voting, ensuring only pre-existing stakeholders influence decisions. This creates centralization risks, as governance control shifts to a small group of participants. Additionally, users unable to lock tokens may miss out on governance incentives.
Manual Review, Hardhat
Update Period Validation in createPeriod
Modify the if condition in TimeWeightedAverage::createPeriod to use boostWindow rather than totalDuration:
This ensures that period validation remains consistent and does not revert incorrectly.
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.