Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: medium
Valid

incorrect weighted average calculation in the calculateTimeWeightedAverage function

Summary

calculateTimeWeightedAverage does not normalize overlapping periods or validate inputs, leading to incorrect results

Vulnerability Details

function calculateTimeWeightedAverage(
PeriodParams[] memory periods,
uint256 timestamp
) public pure returns (uint256 weightedAverage) {
uint256 totalWeightedSum;
uint256 totalDuration;
// We will iterate through each period and calculate the time-weighted average
for (uint256 i = 0; i < periods.length; i++) {
if (timestamp <= periods[i].startTime) continue;
uint256 endTime = timestamp > periods[i].endTime ? periods[i].endTime : timestamp;
uint256 duration = endTime - periods[i].startTime;
unchecked {
// Calculate time-weighted value by multiplying value by duration
// This represents the area under the curve for this period
uint256 timeWeightedValue = periods[i].value * duration;
if (timeWeightedValue / duration != periods[i].value) revert ValueOverflow();
totalWeightedSum += timeWeightedValue * periods[i].weight;
totalDuration += duration;
}
}
return totalDuration == 0 ? 0 : totalWeightedSum / (totalDuration * 1e18);
}
// calculateTimeWeightedAverage:
totalWeightedSum += timeWeightedValue * periods[i].weight;
return totalWeightedSum / (totalDuration * 1e18); // Incorrect if periods overlap

The function attempts to calculate a weighted average using this formula:

weightedAverage = (Σ(value * duration * weight)) / (totalDuration * 1e18)

This is mathematically incorrect because:

  • Numerator: Sum of (value × duration × weight) for all periods.

  • Denominator: totalDuration × 1e18 (total duration across periods × fixed scaling factor).

correct formula below:
weightedAverage = (Σ(value × duration × weight)) / Σ(duration × weight)

  • Denominator must be the sum of duration × weight for all periods, not totalDuration × 1e18.

The current implementation uses totalDuration (sum of durations) multiplied by 1e18 as the denominator. This:

  • Ignores individual period weights in the denominator.

  • Incorrectly assumes all weights are 1e18 (equivalent to a weight of 1.0).

Example:

  • Period 1:
    startTime=0, endTime=100 (duration=100), value=10, weight=0.5e18.

  • Period 2:
    startTime=100, endTime=300 (duration=200), value=20, weight=1.5e18.

  • Timestamp: 300.

Incorrect calculation from the above code:

totalDuration = 100 + 200 = 300
Denominator = 300 * 1e18 = 300e18
Weighted Average = 6,500e18 / 300e18 = 21.67 (This is incorrect)

Correct calculation should be:

Numerator = (10 * 100 * 0.5e18) + (20 * 200 * 1.5e18) = 500e18 + 6,000e18 = 6,500e18
Denominator = (100 * 0.5e18) + (200 * 1.5e18) = 50e18 + 300e18 = 350e18
Weighted Average = 6,500e18 / 350e18 = 18.57

The weights are misapplied because code treats 1e18 as a global scaling factor for weights, but weights are already scaled (e.g., 0.5e18 represents 0.5).

Denominator is incorrect because of using totalDuration * 1e18 instead of summing duration × weight per period.

Impact

Periods with weights ≠ 1e18 are misrepresented. In the example, Period 2’s weight (1.5x) is diluted because the denominator treats all weights as 1.0.

Results deviate significantly from true time-weighted values.

Tools Used

Manual Review

Recommendations

Add a variable to accumulate Σ(duration × weight). Modify the loop to track both sums and use correct denominator

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Validated
Assigned finding tags:

TimeWeightedAverage library fails to consistently apply weights in calculations, causing incorrect time-weighted averages

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.