Core Contracts

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

Type Weight Aggregation Inconsistencies

Summary

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/gauges/GaugeController.sol#L528C5-L564C6

Vulnerability Details

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/gauges/GaugeController.sol#L528C5-L564C6

/**
* @notice Distributes rewards to gauges of a specific type
* @dev Internal function to handle gauge reward distribution
* @param gaugeType Type of gauges to distribute to
* @param amount Total amount to distribute
*/
function _distributeToGauges(
GaugeType gaugeType,
uint256 amount
) internal {
uint256 totalTypeWeight = 0;
uint256[] memory gaugeWeights = new uint256[](_gaugeList.length);
uint256 activeGaugeCount = 0;
// First pass: calculate total weight and store gauge weights
for (uint256 i = 0; i < _gaugeList.length; i++) {
address gauge = _gaugeList[i];
if (gauges[gauge].isActive && gauges[gauge].gaugeType == gaugeType) {
gaugeWeights[i] = gauges[gauge].weight;
totalTypeWeight += gaugeWeights[i];
activeGaugeCount++;
}
}
if (totalTypeWeight == 0 || activeGaugeCount == 0) return;
// Second pass: distribute rewards
for (uint256 i = 0; i < _gaugeList.length; i++) {
address gauge = _gaugeList[i];
if (gauges[gauge].isActive && gauges[gauge].gaugeType == gaugeType) {
uint256 gaugeShare = (amount * gaugeWeights[i]) / totalTypeWeight;
if (gaugeShare > 0) {
IGauge(gauge).notifyRewardAmount(gaugeShare);
}
}
}
}
// First, let's look at where type weights are set
function setTypeWeight(GaugeType gaugeType, uint256 weight) external onlyRole(GAUGE_ADMIN) {
if (weight > MAX_TYPE_WEIGHT) revert InvalidWeight();
uint256 oldWeight = typeWeights[gaugeType];
typeWeights[gaugeType] = weight;
// Issue #1: No check for total type weights across all types
// Issue #2: No update to existing gauge weights after type weight change
}
// Now let's look at how these weights are used in distribution
function _distributeToGauges(GaugeType gaugeType, uint256 amount) internal {
uint256 totalTypeWeight = 0;
uint256[] memory gaugeWeights = new uint256[](_gaugeList.length);
uint256 activeGaugeCount = 0;
// Issue #3: Weight aggregation doesn't account for type weights
for (uint256 i = 0; i < _gaugeList.length; i++) {
address gauge = _gaugeList[i];
if (gauges[gauge].isActive && gauges[gauge].gaugeType == gaugeType) {
gaugeWeights[i] = gauges[gauge].weight;
totalTypeWeight += gaugeWeights[i]; // Raw weights added without type weight multiplier
activeGaugeCount++;
}
}
// Issue #4: Distribution uses raw weights instead of type-weighted values
for (uint256 i = 0; i < _gaugeList.length; i++) {
address gauge = _gaugeList[i];
if (gauges[gauge].isActive && gauges[gauge].gaugeType == gaugeType) {
uint256 gaugeShare = (amount * gaugeWeights[i]) / totalTypeWeight;
// Type weight not considered in distribution
if (gaugeShare > 0) {
IGauge(gauge).notifyRewardAmount(gaugeShare);
}
}
}
}
// The reward calculation has a different approach
function _calculateReward(address gauge) internal view returns (uint256) {
Gauge storage g = gauges[gauge];
uint256 totalWeight = getTotalWeight();
if (totalWeight == 0) return 0;
// Issue #5: Inconsistent weight calculation between distribution and rewards
uint256 gaugeShare = (g.weight * WEIGHT_PRECISION) / totalWeight;
uint256 typeShare = (typeWeights[g.gaugeType] * WEIGHT_PRECISION) / MAX_TYPE_WEIGHT;
uint256 periodEmission = g.gaugeType == GaugeType.RWA ?
_calculateRWAEmission() :
_calculateRAACEmission();
return (periodEmission * gaugeShare * typeShare) / (WEIGHT_PRECISION * WEIGHT_PRECISION);
}
// And getTotalWeight doesn't consider type weights
function getTotalWeight() public view override returns (uint256) {
uint256 total = 0;
// Issue #6: Total weight calculation ignores type weights
for (uint256 i = 0; i < _gaugeList.length; i++) {
if (gauges[_gaugeList[i]].isActive) {
total += gauges[_gaugeList[i]].weight;
}
}
return total;
}

Impact

Rewards calculations don't match actual distributions.

// Inconsistent weight handling across different functions:
_calculateReward() // Uses type weights
_distributeToGauges() // Ignores type weights
getTotalWeight() // Raw weights only
setTypeWeight() // No propagation of changes

Tools Used

Foundry

Recommendations

All the above need to be considered. The issue affects multiple functions

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Support

FAQs

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

Give us feedback!