Core Contracts

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

Permanent DoS due to non-shrinking array usage in unbounded loops

Summary

Unbounded loops iterating over _gaugeList in GaugeController.sol and managerList in BoostController.sol and potentially other contracts with similar unbounded array loops can lead to permanent Denial of Service (DoS).

Vulnerability Details

Functions Several contracts, particularly GaugeController.sol and BoostController.sol, use for loops to iterate over arrays (_gaugeList and managerList respectively) that can grow indefinitely over time. These arrays are state variables and do not have any built-in mechanism to shrink or limit their size.

Vulnerable Code Instances

GaugeController.sol.getTotalWeight:

419: function getTotalWeight() public view override returns (uint256) {
420: uint256 total = 0;
421: for (uint256 i = 0; i < _gaugeList.length; i++) { // <--- UNBOUNDED LOOP
422: if (gauges[_gaugeList[i]].isActive) {
423: total += gauges[_gaugeList[i]].weight;
424: }
425: }
426: return total;
427: }

GaugeController.sol.getActiveGauges:

476: function getActiveGauges() external view returns (address[] memory) {
477: uint256 activeCount = 0;
478: for (uint256 i = 0; i < _gaugeList.length; i++) { // <--- UNBOUNDED LOOP
479: if (gauges[_gaugeList[i]].isActive) activeCount++;
480: }
481:
482: address[] memory activeGauges = new address[](activeCount);
483: uint256 index = 0;
484: for (uint256 i = 0; i < _gaugeList.length; i++) { // <--- UNBOUNDED LOOP
485: if (gauges[_gaugeList[i]].isActive) {
486: activeGauges[index++] = _gaugeList[i];
487: }
488: }
489: return activeGauges;
490: }

BoostController.sol.updateUserBoost (Indirectly via updateTotalWeight ):


Impact

Permanent Denial of Service (DoS). As the _gaugeList and managerList arrays grow over time (as more gauges and managers are added), the gas cost of iterating over these arrays in the affected functions will increase linearly. Eventually, the gas cost of calling these functions will exceed the block gas limit, making them unusable and effectively causing a permanent DoS for the contract. This DoS is permanent because there is no mechanism in the current code to shrink these arrays.

Affected Functions (Potentially - Need to Review All Contracts for Unbounded Array Loops):

  • GaugeController.sol.getTotalWeight

  • GaugeController.sol.getActiveGauges

  • GaugeController.sol._distributeToGauges

  • Potentially other functions in GaugeController.sol and other contracts that iterate over unbounded arrays.

  • Potentially BoostController.sol.updateUserBoost and functions that call updateTotalWeight if user lists are added in future versions.

Tools Used

Manually reviewed

Recommendations

  1. Immediate Mitigation (Short-Term): Implement size limits on _gaugeList and managerList arrays to prevent unbounded growth and delay DoS. Define MAX_GAUGE_LIST_SIZE, MAX_MANAGER_LIST_SIZE constants and enforce them in array modification functions.

  2. Long-Term Solution (Fundamental Design Change): Re-architect code to avoid unbounded array loops. Use mapping-based aggregation or off-chain calculations for scalable solutions.

  3. Code Review: Identify and refactor all unbounded array loops in critical functions across the codebase.

  4. Testing: Implement multiple tests to simulate array growth and verify gas costs remain within acceptable limits to understand.

  5. Immediate Mitigation (Short-Term): Implement a reasonable limit on the maximum size of _gaugeList and managerList arrays. This will prevent unbounded growth and mitigate the DoS risk in the short term. However, this is not a long-term solution, as the limit could still be reached eventually.

    Example Mitigation (Adding Limit to GaugeController.sol.addGauge):

    function addGauge(
    address gauge,
    GaugeType gaugeType,
    uint256 initialWeight
    ) external onlyGaugeAdmin {
    if (gauges[gauge].lastUpdateTime != 0) revert GaugeAlreadyExists();
    if (gaugeType != GaugeType.RWA && gaugeType != GaugeType.RAAC) {
    revert InvalidGaugeType();
    }
    // ADD THIS CHECK: Limit gauge list size
    if (_gaugeList.length >= MAX_GAUGE_LIST_SIZE) revert GaugeListLimitExceeded(); // Define MAX_GAUGE_LIST_SIZE constant
    // ... rest of addGauge logic ...
    }
  6. Long-Term Solution (Fundamental Design Change): Re-architect the code to avoid unbounded array loops for critical operations. Consider alternative data structures and algorithmic approaches that do not rely on iterating over potentially very large arrays. Some options include:

    • Paginated or Chunked Processing: Process array data in smaller, manageable chunks, using pagination or similar techniques to limit the gas cost per transaction.

    • Mapping-Based Aggregation: Instead of iterating over arrays to calculate aggregates (like totalWeight or activeGaugeCount), maintain running totals or aggregate values in mappings that can be updated incrementally as gauges or managers are added or modified. This avoids the need for full array iterations.

    • Off-Chain Aggregation/Calculation: Move some of the aggregation or calculation logic off-chain, performing calculations in backend services or client-side applications and only submitting final results to the smart contract.

  7. Code Review: Thoroughly review all contracts for similar unbounded array loops in public or external functions or internal functions called by public/external functions.

  8. Testing: Implement unit tests and integration tests to verify the effectiveness of the chosen mitigation strategy and ensure that the contract remains usable even with a large number of gauges or managers.

Updates

Lead Judging Commences

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

GaugeController._distributeToGauges iterates twice over unbounded gauges list without error handling, causing DoS risk from out-of-gas or single gauge revert

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

GaugeController._distributeToGauges iterates twice over unbounded gauges list without error handling, causing DoS risk from out-of-gas or single gauge revert

Support

FAQs

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

Give us feedback!