Summary
The BaseGuage::updatePeriod()
function is as follow -
* @notice Updates the period and calculates new weights
*/
function updatePeriod() external override onlyController {
uint256 currentTime = block.timestamp;
uint256 periodEnd = periodState.periodStartTime + getPeriodDuration();
if (currentTime < periodEnd) {
revert PeriodNotElapsed();
}
uint256 periodDuration = getPeriodDuration();
@-> uint256 avgWeight = periodState.votingPeriod.calculateAverage(periodEnd);
uint256 nextPeriodStart = ((currentTime / periodDuration) + 2) * periodDuration;
periodState.distributed = 0;
periodState.periodStartTime = nextPeriodStart;
TimeWeightedAverage.createPeriod(
periodState.votingPeriod,
nextPeriodStart,
periodDuration,
avgWeight,
WEIGHT_PRECISION
);
}
calculateAverage()
function is as follow -
function calculateAverage(
Period storage self,
uint256 timestamp
) internal view returns (uint256) {
if (timestamp <= self.startTime) return self.value;
uint256 endTime = timestamp > self.endTime ? self.endTime : timestamp;
uint256 totalWeightedSum = self.weightedSum;
if (endTime > self.lastUpdateTime) {
uint256 duration = endTime - self.lastUpdateTime;
uint256 timeWeightedValue = self.value * duration;
if (duration > 0 && timeWeightedValue / duration != self.value) revert ValueOverflow();
totalWeightedSum += timeWeightedValue;
}
return totalWeightedSum / (endTime - self.startTime);
}
self.value = 0, determined in constructor.
so, timeWeightedValue
will also be 0. means return value will also be 0.
so avgWeight
will be zero, as-
uint256 avgWeight = periodState.votingPeriod.calculateAverage(periodEnd);
It means when new period will be created via TimeWeightedAverage.createPeriod
, -
TimeWeightedAverage.createPeriod(
periodState.votingPeriod,
nextPeriodStart,
periodDuration,
avgWeight,
WEIGHT_PRECISION
);
-
If means whenever new period P1, P2, P3... is created, the avgWeight accounting of previous periods is not tracked.
-
Which is very important aspect of protocol, to track the time-weighted average.
-
But with current implementation it's not happening, i.e. average weighted value is always 0.
-
NOTE - Developers may be assuming that they can set the weight of current period by calling setInitialWeight()
function, but they won't be able to -
function setInitialWeight(uint256 weight) external onlyController {
uint256 periodDuration = getPeriodDuration();
uint256 currentTime = block.timestamp;
uint256 nextPeriodStart = ((currentTime / periodDuration) + 2) * periodDuration;
TimeWeightedAverage.createPeriod(
periodState.votingPeriod,
nextPeriodStart,
periodDuration,
weight,
10000
);
periodState.periodStartTime = nextPeriodStart;
}
The reason why they won't be able to beacuse, there's check inside TimeWeightedAverage.createPeriod
function -
if (self.startTime != 0 && startTime < self.startTime + self.totalDuration) {
revert PeriodNotElapsed();
}
-
In simple words, controller can't update the weight of current period, more accuratly contraoller can't change any data of current ongoing period.
-
.value can only be updated after end of a period/ or during start of next period.
Vulnerability Details
Impact
Tools Used
Manual
Recommendations
In constructor, don't initialize initial value with 0, do it with any not-zero number.
The main cause of this issue is this, which is triggering as chain-reaction of avgWeight for all periods to 0.