Core Contracts

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

Unrecorded performance fee in `GaugeController::distributeRevenue` leads to permanent loss of protocol revenue

Description

The GaugeController::distributeRevenue function calculates a 20% performance fee share but fails to store it in the performanceFees mapping. This results in the protocol losing track of 20% of distributed revenue, making these funds permanently inaccessible.

Proof of Concept

  1. Admin calls GaugeController::distributeRevenue with 100 ETH

  2. Function calculates 20 ETH as performance fee (amount * 20 / 100)

  3. Performance fee is neither stored in performanceFees mapping nor distributed

  4. 20 ETH becomes permanently locked in contract with no tracking

// GaugeController.sol
function distributeRevenue(GaugeType gaugeType, uint256 amount) external {
uint256 performanceShare = amount * 20 / 100; // Calculated but not stored
revenueShares[gaugeType] += veRAACShare; // Only 80% stored
// No storage of performanceShare
}

Test case showing missing fee tracking:

// Add to GaugeController.test.js
it("fails to track performance fees", async () => {
const initialRevenue = await gaugeController.revenueShares(0);
const initialPerformanceFee = await gaugeController.performanceFees(
await rwaGauge.getAddress()
);
// Distribute 100 ETH revenue
await gaugeController
.connect(emergencyAdmin)
.distributeRevenue(0, ethers.parseEther("100"));
// Verify only 80% recorded in revenueShares
expect(await gaugeController.revenueShares(0)).to.equal(
initialRevenue + ethers.parseEther("80")
);
// Performance fee remains unrecorded
expect(
await gaugeController.performanceFees(await rwaGauge.getAddress())
).to.equal(initialPerformanceFee);
});

Impact

High severity - Direct loss of protocol revenue. The unrecorded 20% performance fees become permanently inaccessible, violating core protocol economics and depriving fee recipients of their allocated share.

Recommendation

  • Store in type-specific performance fees:

function distributeRevenue(GaugeType gaugeType, uint256 amount) external {
uint256 performanceShare = amount * 20 / 100;
+ performanceFees[gaugeType] += performanceShare;
revenueShares[gaugeType] += veRAACShare;
}
  • Distribute to gauges immediately:

function distributeRevenue(GaugeType gaugeType, uint256 amount) external {
uint256 performanceShare = amount * 20 / 100;
revenueShares[gaugeType] += veRAACShare;
+ _distributeToGauges(gaugeType, performanceShare);
}
  • Create new mapping for type-based fees:

+ mapping(GaugeType => uint256) public typePerformanceFees;
function distributeRevenue(GaugeType gaugeType, uint256 amount) external {
uint256 performanceShare = amount * 20 / 100;
+ typePerformanceFees[gaugeType] += performanceShare;
revenueShares[gaugeType] += veRAACShare;
}
Updates

Lead Judging Commences

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

GaugeController.distributeRevenue calculates 20% performance fee but never transfers or allocates it to any recipient, causing loss of funds

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

GaugeController.distributeRevenue calculates 20% performance fee but never transfers or allocates it to any recipient, causing loss of funds

Support

FAQs

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