Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: high
Invalid

Truncated `totalAssets()` Leading to Incorrect Credit Delegation and Systemic Instability

Summary

The Vault.updateVaultAndCreditDelegationWeight function truncates the 256-bit totalAssets() value to 128 bits, causing incorrect credit delegation weights. This propagates inaccurate debt values to connected markets, destabilizing the protocol’s financial accounting.


Vulnerability Details

Location

Vault.solupdateVaultAndCreditDelegationWeight function
Code Snippet:

function updateVaultAndCreditDelegationWeight(Data storage self, uint128[] memory connectedMarketsIdsCache) internal {
// ...
uint128 newWeight = uint128(IERC4626(self.indexToken).totalAssets()); // Truncation here
// ...
self.totalCreditDelegationWeight = newWeight; // Propagates truncated value
}

Technical Analysis

  1. Root Cause:

    • IERC4626.totalAssets() returns a uint256, but newWeight is stored as uint128.

    • If totalAssets() exceeds 2^128 - 1, the value is truncated, discarding higher-order bits.

    • Example:

      • Actual totalAssets() = 2^128 + 1 (340,282,366,920,938,463,463,374,607,431,768,211,457)

      • Truncated newWeight = 1 (due to uint128 overflow).

  2. Credit Delegation Impact:

    • Credit delegation shares are calculated as:

      UD60x18 creditDelegationShareX18 = ud60x18(creditDelegation.weight).div(ud60x18(totalCreditDelegationWeight));
    • Truncated weights distort this ratio, leading to incorrect debt distribution.

  3. Systemic Propagation:

    • Markets use these weights to calculate debtPerVaultShare, affecting global debt tracking.

    • Example: A vault with newWeight = 1 (instead of 2^128 + 1) would delegate ~0% of its credit capacity, starving markets of debt coverage.


Impact

  • High Severity

    • Undercollateralized Markets: Truncated weights underreport vault assets, leaving markets undercollateralized and vulnerable to liquidation cascades.

    • Debt Mismatches: Markets may accumulate unaccounted debt, leading to protocol insolvency.

    • Vault Exploitation: Attackers could intentionally overflow totalAssets() to manipulate credit delegation (e.g., reducing their debt share).


Proof of Concept (PoC)

Scenario Setup

  1. Vault Initialization:

    • A vault uses an ERC4626 token where totalAssets() returns 2^128 + 1.

`

  1. Market Impact:

    • A connected market calculates its credit share as creditDelegationShareX18 = 1 / 1 = 1e18 (100%), but the true share should be negligible.

    • The market incorrectly assumes it has 100% of the vault’s credit capacity, leading to overborrowing and eventual insolvency.


Recommendations

Immediate Fix

  1. Use Full 256-bit Precision:
    Replace uint128 with uint256 for credit delegation weights:

    uint256 newWeight = IERC4626(self.indexToken).totalAssets();
    self.totalCreditDelegationWeight = newWeight.toUint256(); // Use SafeCast
  2. Add Overflow Checks:
    Use OpenZeppelin’s SafeCast to prevent silent truncation:

    import { SafeCast } from "@openzeppelin/utils/math/SafeCast.sol";
    uint256 newWeight = IERC4626(self.indexToken).totalAssets();
    self.totalCreditDelegationWeight = SafeCast.toUint128(newWeight); // Reverts on overflow

Long-Term Mitigations

  1. Asset Limits:
    Enforce a maximum totalAssets() per vault to stay within uint128 bounds.

  2. Circuit Breakers:
    Pause vault operations if totalAssets() approaches 2^128 - 1.

  3. Monitoring:
    Track vault asset growth and trigger alerts near overflow thresholds.

Updates

Lead Judging Commences

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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