Part 2

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

Direct Collateral Injection Inflates Credit Capacity


Summary and Impact

The vulnerability arises from using the ERC‑4626 totalAssets() value in the vault’s credit capacity calculation without accounting for collateral deposited directly—outside the deposit routine. In essence, any direct transfer to the vault’s underlying asset increases totalAssets() but leaves the delegated credit and internal bookkeeping unchanged. As a result, the calculated credit capacity (i.e. collateral value minus debt) is artificially inflated, enabling an attacker to trigger excessive borrowing or undercollateralized positions. Such misjudgment of the vault’s actual collateralization risks systemic insolvency if leveraged positions are opened against this phantom capacity. This flaw violates the protocol’s invariant that the vault’s internal accounting must reflect only approved (and properly recorded) deposits, creating a high-level financial risk.


Vulnerability Details

The core of the vulnerability is found in the credit capacity calculation implemented in the vault contract. The relevant function is:

function getTotalCreditCapacityUsd(Data storage self) internal view returns (SD59x18 creditCapacityUsdX18) {
// load the collateral configuration storage pointer
Collateral.Data storage collateral = self.collateral;
// fetch the zlp vault's total assets amount
UD60x18 totalAssetsX18 = ud60x18(IERC4626(self.indexToken).totalAssets());
// calculate the total assets value in usd terms
UD60x18 totalAssetsUsdX18 = collateral.getAdjustedPrice().mul(totalAssetsX18);
// calculate the vault's credit capacity in usd terms
creditCapacityUsdX18 = totalAssetsUsdX18.intoSD59x18().sub(getTotalDebt(self));
}

Issue Explanation:

  • Direct Collateral Transfers:
    ERC‑4626 tokens expose a totalAssets() function that aggregates the vault’s token balance. If a user (or attacker) sends tokens directly to the vault contract—bypassing the deposit function—the totalAssets() increases even though no corresponding update occurs to the system’s internal delegated credit or debt accounting.

  • Inconsistent Invariants:
    The protocol assumes that collateral arrives only through the deposit process, which properly updates credit delegation and other state variables. Direct transfers violate this invariant, resulting in an inflated totalAssets() value and, consequently, an erroneously high credit capacity.

  • Exploit Scenario (Step-by-Step):

    1. Initial State:
      – The vault is initialized with 100 tokens; using an adjusted price of 1 (1e18), the system computes a collateral value of 100 units. With zero debt, the credit capacity is 100 units.

    2. Attack Execution:
      – An attacker transfers an additional 50 tokens directly to the vault’s underlying asset.
      – The ERC‑4626 function totalAssets() now returns 150 tokens, while the protocol’s internal record of delegated credit remains unchanged.

    3. Resulting Impact:
      – The credit capacity calculation becomes:
      creditCapacity = (1e18 × 150) − 0 = 150
      – The extra 50 tokens of apparent capacity enable over-leveraging, permitting the attacker (or malicious actor) to open excessive positions, thereby risking undercollateralization and potential insolvency.

Test Code Snippet:

Below is an excerpt from our Foundry test (simplified for clarity):

// VaultSimulator computes: (price × totalAssets) − debt
function getCreditCapacity() external view returns (int256) {
uint256 price = MockPriceAdapter(priceAdapter).getPrice(); // fixed at 1e18
uint256 totalAssets = indexToken.totalAssets();
uint256 totalAssetsUsdScaled = (price * totalAssets) / 1e18;
return int256(totalAssetsUsdScaled) - int256(debt);
}

In the test:

  • Before Direct Transfer:
    totalAssets() returns 100e18, so capacity = 100e18.

  • After Direct Transfer:
    totalAssets() is manually set to 150e18, resulting in capacity = 150e18.

This demonstrates that collateral sent directly (not via the deposit function) increases the reported capacity, exposing the protocol to risk.


Tools Used

  • Manual Review

  • Foundry


Recommendations

Restrict Direct Transfers:
Enforce that collateral can only be deposited through the designated deposit function. This can be achieved by:

  • Modifying the ERC‑4626 wrapper (or using a proxy) to reject transfers that do not trigger deposit logic.

  • Adding a modifier or check in the vault contract to ensure that only approved deposit functions update the vault’s internal state.


Updates

Lead Judging Commences

inallhonesty Lead Judge
6 months ago
inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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