Core Contracts

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

Portion of revenue to be distributed for gauges remains undistributed

Summary

The distributeRevenue() function in the contract has a flaw in its revenue distribution logic, particularly when distributing rewards to active gauges of a specific type. This issue arises due to integer truncation during the calculation of gauge shares, which can lead to leftover undistributed revenue.

Vulnerability Details

When the distributeRevenue() function is called with a specific gaugeType and an amount, the function calculates the share for veRAAC holders and attempts to distribute this share among active gauges.

>> uint256 veRAACShare = amount * 80 / 100; // 80% to veRAAC holders
uint256 performanceShare = amount * 20 / 100; // 20% performance fee
revenueShares[gaugeType] += veRAACShare;
>> _distributeToGauges(gaugeType, veRAACShare);

However, due to integer division, the calculated shares may not sum up to the total revenue share during distibution.

// Second pass: distribute rewards
for (uint256 i = 0; i < _gaugeList.length; i++) {
address gauge = _gaugeList[i];
if (gauges[gauge].isActive && gauges[gauge].gaugeType == gaugeType) {
>> // @audit-info Some ramin undistributed
uint256 gaugeShare = (amount * gaugeWeights[i]) / totalTypeWeight;
if (gaugeShare > 0) {
IGauge(gauge).notifyRewardAmount(gaugeShare);
}
}
}

This results in unallocated funds.

Example Scenario

Consider the following scenario:

  • amount = 2500

  • veRAACShare = 2000 (calculated as 2500 * 80 / 100)

  • Active gauges: 3 with weights of 100 each (total weight = 300)

During distribution:

gaugeShare = (2000 * 100) / 300; // Results in approximately 66.67

Due to truncation, each gauge receives:

gaugeShare = 666; // After truncation

Total distributed to 3 gauges:

Total Distributed = 666 * 3 = 1998

Leftover amount:

Leftover = 2000 - 1998 = 2; // This amount is not distributed

Impact

The failure to distribute the entire revenue share can lead to inefficiencies in the reward distribution mechanism where rewards intended to be ditributed to gauges remain unallocated.

Tools Used

Manual Review

Recommendations

The distribution logic should be modified to ensure that any leftover amount is allocated to the last gauge during the distribution process.

// Second pass: distribute rewards
+ uint256 totalDistributed = 0; // Track the total amount distributed
+ uint256 lastActiveGaugeIndex = 0; // Track the index of the last active gauge
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);
totalDistributed += gaugeShare;
}
+ lastActiveGaugeIndex = i; // Update the last active gauge index
}
}
+ // Allocate any remaining amount to the last active gauge
+ if (totalDistributed < amount) {
+ uint256 remainder = amount - totalDistributed;
+ address lastGauge = _gaugeList[lastActiveGaugeIndex];
+ IGauge(lastGauge).notifyRewardAmount(remainder);
+ }
Updates

Lead Judging Commences

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

GaugeController's _distributeToGauges function leaves revenue undistributed due to integer division truncation when calculating gauge shares

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

GaugeController's _distributeToGauges function leaves revenue undistributed due to integer division truncation when calculating gauge shares

Support

FAQs

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