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.sol – updateVaultAndCreditDelegationWeight 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 over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!