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 10 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!