Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: medium
Valid

PremiumDiscountFactor Feature is Not Working

Summary

PremiumDiscountFactor feature is not working in the protocol.

Vulnerability Details

Protocol uses PremiumDiscountFactor to calculate assets amountOut during the swap in Market Making Engine, however, it was observed that there is no way to initialize few state variables that UsdTokenSwapConfig.sol:getPremiumDiscountFactor() function uses: pdCurveYMin, pdCurveYMax, pdCurveXMin, pdCurveXMax, pdCurveZ, as a result premiumDiscountFactor always will be equal to UD60x18_UNIT.

/// @notice Returns the premium or discount to be applied to the amount out of a swap, based on the vault's debt
/// and the system configured premium / discount curve parameters.
/// @dev The following invariant defining the premium / discount curve must hold true:
/// f(x) = y_min + Δy * ((x - x_min) / (x_max - x_min))^z | x ∈ [x_min, x_max]
/// @dev The proposed initial curve is defined as:
/// f(x) = 1 + 9 * ((x - 0.3) / 0.5)^3
/// @dev If no premium or discount has to be applied, the function returns 1 as UD60x18.
/// @dev Using the proposed z value of 3, the slope of f(x) near the upper bound of x is steeper than near the
/// lower bound, meaning the premium or discount accelerates faster as the vault's debt / tvl ratio increases.
function getPremiumDiscountFactor(
Data storage self,
UD60x18 vaultAssetsValueUsdX18,
SD59x18 vaultDebtUsdX18
)
internal
view
returns (UD60x18 premiumDiscountFactorX18)
{
// calculate the vault's tvl / debt absolute value, positive means we'll apply a discount, negative means
// we'll apply a premium
UD60x18 vaultDebtTvlRatioAbs = vaultDebtUsdX18.abs().intoUD60x18().div(vaultAssetsValueUsdX18);
// cache the minimum x value of the premium / discount curve
UD60x18 pdCurveXMinX18 = ud60x18(self.pdCurveXMin);
// cache the maximum x value of the premium / discount curve
UD60x18 pdCurveXMaxX18 = ud60x18(self.pdCurveXMax);
// if the vault's debt / tvl ratio is less than or equal to the minimum x value of the premium / discount
// curve, then we don't apply any premium or discount
if (vaultDebtTvlRatioAbs.lte(pdCurveXMinX18)) {
premiumDiscountFactorX18 = UD60x18_UNIT;
return premiumDiscountFactorX18;
}
// if the vault's debt / tvl ratio is greater than or equal to the maximum x value of the premium / discount
// curve, we use the max X value, otherwise, use the calculated vault tvl / debt ratio
UD60x18 pdCurveXX18 = vaultDebtTvlRatioAbs.gte(pdCurveXMaxX18) ? pdCurveXMaxX18 : vaultDebtTvlRatioAbs;
// cache the minimum y value of the premium / discount curve
UD60x18 pdCurveYMinX18 = ud60x18(self.pdCurveYMin);
// cache the maximum y value of the premium / discount curve
UD60x18 pdCurveYMaxX18 = ud60x18(self.pdCurveYMax);
// cache the exponent that determines the steepness of the premium / discount curve
UD60x18 pdCurveZX18 = ud60x18(self.pdCurveZ);
// calculate the y point of the premium or discount curve given the x point
UD60x18 pdCurveYX18 = pdCurveYMinX18.add(
pdCurveYMaxX18.sub(pdCurveYMinX18).mul(
pdCurveXX18.sub(pdCurveXMinX18).div(pdCurveXMaxX18.sub(pdCurveXMinX18)).pow(pdCurveZX18)
)
);
// if the vault is in credit, we apply a discount, otherwise, we apply a premium
premiumDiscountFactorX18 =
vaultDebtUsdX18.lt(SD59x18_ZERO) ? UD60x18_UNIT.sub(pdCurveYX18) : UD60x18_UNIT.add(pdCurveYX18);
}

By description in the comments to the code:

// if the vault is in credit, we apply a discount, otherwise, we apply a premium

But premiumDiscountFactor always will be equal to UD60x18_UNIT.

Impact

premiumDiscountFactor always will be equal to UD60x18_UNIT no matter if vault is in credit or not.

Tools Used

Manual Review

Recommendations

Update MarketMakingEngineConfiguration.sol:configureUsdTokenSwapConfig():

function configureUsdTokenSwapConfig(
uint128 baseFeeUsd,
uint128 swapSettlementFeeBps,
uint128 maxExecutionTime,
+ uint128 pdCurveYMin,
+ uint128 pdCurveYMax,
+ uint128 pdCurveXMin,
+ uint128 pdCurveXMax,
+ uint128 pdCurveZ
)
external
onlyOwner
{
if (maxExecutionTime == 0) {
revert Errors.ZeroInput("maxExecutionTime");
}
// emits event internally
UsdTokenSwapConfig.update(
baseFeeUsd,
swapSettlementFeeBps,
maxExecutionTime,
+ pdCurveYMin,
+ pdCurveYMax,
+ pdCurveXMin,
+ pdCurveXMax,
+ pdCurveZ
);
}

And UsdTokenSwapConfig.sol:update():

function update(
uint128 baseFeeUsd,
uint128 swapSettlementFeeBps,
uint128 maxExecutionTime,
+ uint128 pdCurveYMin,
+ uint128 pdCurveYMax,
+ uint128 pdCurveXMin,
+ uint128 pdCurveXMax,
+ uint128 pdCurveZ
) internal {
Data storage self = load();
self.baseFeeUsd = baseFeeUsd;
self.swapSettlementFeeBps = swapSettlementFeeBps;
self.maxExecutionTime = maxExecutionTime;
+ self.pdCurveYMin = pdCurveYMin;
+ self.pdCurveYMax = pdCurveYMax;
+ self.pdCurveXMin = pdCurveXMin;
+ self.pdCurveXMax = pdCurveXMax;
+ self.pdCurveZ = pdCurveZ;
emit LogUpdateUsdTokenSwapConfig(baseFeeUsd, swapSettlementFeeBps, maxExecutionTime);
}
Updates

Lead Judging Commences

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

PremiumDiscountFactor feature cannot be properly configured / used

Support

FAQs

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