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 7 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 7 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.

Give us feedback!