The GaugeController contract manages gauge reward distribution by tracking time‐weighted metrics using a period state (via the TimeWeightedAverage library). When a new gauge is added (using addGauge
), an initial period is created. Later, the updatePeriod
function is intended to update intermediate state—adjusting the time‐weighted values continuously—and only roll over to a new period when the current period has truly ended.
However, due to an omission in the implementation, the updatePeriod
function in GaugeController reverts with a PeriodNotElapsed
error when a call is made within the current period window. This prevents any intermediate updates. A similar issue exists in several functions in the BaseGauge contract (e.g., _updateWeights
and its own updatePeriod
), where instead of updating the gauge’s current weight during an active period, the functions simply create a new period only after the window expires. This behavior leads to stale gauge state, improper reward calculation, and ultimately may cause a denial-of-service (DoS) for gauge updates.
addGauge Initialization:
When adding a gauge, the contract creates the initial period using:
Note: The period is created with a fixed totalDuration
equal to duration
(which is a bug in itself), but the focus here is on the update mechanism.
updatePeriod Function:
The current implementation in GaugeController is:
Issue:
The function reverts if the current timestamp is less than period.startTime + period.totalDuration
. Thus, any attempt to update intermediate state (i.e., before the period ends) fails, leaving gauge state static.
BaseGauge also includes functions to update gauge weight and period state:
_updateWeights Function:
Issue:
This function always creates a new period rather than updating the current period’s state if an update occurs within the active window, resulting in a denial of service until the current period expires.
updatePeriod Function in BaseGauge:
Issue:
Like in GaugeController, this function does not allow for intermediate updates and instead forces a complete rollover only after the period ends, potentially causing DoS if weight update requests come in prematurely.
Gauge Setup:
An admin adds a gauge using addGauge
, which creates an initial period.
A user (ALICE) casts a vote, triggering gauge state updates.
Intermediate Update Attempt:
Before the current period expires (i.e., within the period window), ALICE (or another actor) attempts to update the period by calling updatePeriod
.
The function checks the condition:
and reverts, blocking the update.
Test Cases:
The following Foundry test suite demonstrates both the GaugeController and BaseGauge issues.
Step 1: Create a Foundry project:
Step 2: Remove unnecessary files.
Step 3: Convert your Hardhat project to Foundry by placing contracts in the src
directory.
Step 4: Create a test
directory adjacent to src
and include all necessary contract files and mocks.
Step 5: In the test
directory, create a test file (e.g., GaugeTest.t.sol
) and paste the above test suite.
Step 6: Run the tests:
Expected Output:
The first test should revert with PeriodNotElapsed()
for intermediate updates, and the second test (after fixing the totalDuration bug in createPeriod) will show that the period is updated (rolled over) even if the update window has not truly ended—demonstrating that the mechanism does not support genuine intermediate updates.
Stale Gauge Data:
Without intermediate period updates, the gauge’s time-weighted state remains unchanged, causing inaccurate reward distribution and misrepresentation of user voting power.
Reward Distribution Errors:
Inaccurate time-weighted averages lead to misallocation of rewards, distorting incentives.
Governance and Incentive Misalignment:
Outdated gauge state may lead to erroneous governance decisions based on stale data.
Potential Exploitation:
Attackers might exploit the lack of dynamic updates by timing their actions to benefit from outdated gauge information.
System Instability:
Overall protocol trust is undermined when gauge states fail to reflect real-time changes in voting power and participation.
Manual Review
Foundry
To address these vulnerabilities, modify the updatePeriod
functions in both GaugeController and BaseGauge contracts to update intermediate period states rather than reverting if the period has not fully elapsed. The new logic should allow updating the current period’s state (e.g., via TimeWeightedAverage.updateValue
) and only roll over to a new period when the period’s end time is reached.
For the BaseGauge contract functions, update _updateWeights
and updatePeriod
similarly to allow intermediate state updates rather than forcing a full period rollover:
This submission confuses period rollover with weight updates. Gauge weights update immediately when users vote via _updateGaugeWeight, while updatePeriod only handles period boundaries. The PeriodNotElapsed revert is intentional protection against premature rollover.
This submission confuses period rollover with weight updates. Gauge weights update immediately when users vote via _updateGaugeWeight, while updatePeriod only handles period boundaries. The PeriodNotElapsed revert is intentional protection against premature rollover.
This submission confuses period rollover with weight updates. Gauge weights update immediately when users vote via _updateGaugeWeight, while updatePeriod only handles period boundaries. The PeriodNotElapsed revert is intentional protection against premature rollover.
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.