Skip to content

Latest commit

 

History

History
61 lines (45 loc) · 3.13 KB

File metadata and controls

61 lines (45 loc) · 3.13 KB

Cheerful Taffy Dolphin

Medium

Lack of LEVERAGE_BUFFER Check in updateLeverage() Causes Position Operations to Revert Due to Storage-Execution Mismatch

Description

The updateLeverage function in Vault.sol allows setting leverage values without validating against the LEVERAGE_BUFFER constraint (1.2e6) defined in MakerStrategyLib.sol. While MakerStrategyLib enforces this constraint during strategy execution, the lack of validation at the leverage update level creates a potential mismatch between stored leverage values and what can be safely executed.

https://github.com/sherlock-audit/2025-01-perennial-v2-4-update/blob/main/perennial-v2/packages/vault/contracts/Vault.sol#L215

// Vault.sol
function updateLeverage(uint256 marketId, UFixed6 newLeverage) external onlyOwner {
    rebalance(address(0));
    if (marketId >= totalMarkets) revert VaultMarketDoesNotExistError();
    _updateMarket(marketId, UFixed6Lib.MAX, newLeverage);
}
// MakerStrategyLib.sol
UFixed6 public constant LEVERAGE_BUFFER = UFixed6.wrap(1.2e6);

function _allocateMarket(...) private pure returns (Target memory target, UFixed6 marketCollateral) {
    marketCollateral = marketContext.margin
        .add(collateral.sub(totalMargin).mul(marketContext.registration.weight));

    // LEVERAGE_BUFFER enforcement
    UFixed6 marketAssets = assets
        .mul(marketContext.registration.weight)
        .min(marketCollateral.mul(LEVERAGE_BUFFER));
}

Scenario:

  1. Owner calls updateLeverage with a leverage value > 1.2e6
  2. The update succeeds and stores the high leverage value
  3. When strategy executes, operations fail due to LEVERAGE_BUFFER check in MakerStrategyLib
  4. Results in positions that cannot be properly adjusted

Impact

When updateLeverage() allows setting leverage values above LEVERAGE_BUFFER (1.2e6), it creates a critical state inconsistency since MakerStrategyLib.allocate() enforces this limit during position calculations and strategy execution. This leads to a scenario where the stored leverage value in Registration.leverage is higher than what allocate() will allow during position adjustments, causing allocate() to calculate invalid position sizes based on unusable leverage values

Subsequent calls to update() or rebalance() will fail when MakerStrategyLib attempts to execute positions with the stored leverage, as the calculations in _allocateMarket() involving marketContext.registration.leverage will exceed LEVERAGE_BUFFER constraints.

This temporarily locks vault funds in existing positions, as any attempt to modify positions through the strategy will revert due to the leverage mismatch between storage and execution constraints - until an owner updates the leverage to a value within LEVERAGE_BUFFER limits.

Recommendation

Add validation in updateLeverage to enforce the LEVERAGE_BUFFER constraint:

function updateLeverage(uint256 marketId, UFixed6 newLeverage) external onlyOwner {
    rebalance(address(0));
    if (marketId >= totalMarkets) revert VaultMarketDoesNotExistError();
    if (newLeverage.gt(LEVERAGE_BUFFER)) revert InvalidLeverageError();
    _updateMarket(marketId, UFixed6Lib.MAX, newLeverage);
}