Summary
When calculating the CreditCapacity, there is an adding delegatedCredit and totalDebt instead of subtracting them.
function getCreditCapacityUsd(
UD60x18 delegatedCreditUsdX18,
SD59x18 totalDebtUsdX18
)
internal
pure
returns (SD59x18 creditCapacityUsdX18)
{
creditCapacityUsdX18 = delegatedCreditUsdX18.intoSD59x18().add(totalDebtUsdX18);
}
Vulnerability Details
function isAutoDeleverageTriggered(
...
)
...
{
SD59x18 sdDelegatedCreditUsdX18 = delegatedCreditUsdX18.intoSD59x18();
if (sdDelegatedCreditUsdX18.lte(totalDebtUsdX18) || sdDelegatedCreditUsdX18.isZero()) {
340: return false;
}
...
}
When delegatedCredit <= totalDebt, the isAutoDeleverageTriggered function return false.
246:function withdrawUsdTokenFromMarket(uint128 marketId, uint256 amount) external onlyRegisteredEngine(marketId) {
...
SD59x18 marketTotalDebtUsdX18 = market.getTotalDebt();
UD60x18 delegatedCreditUsdX18 = market.getTotalDelegatedCreditUsd();
260: SD59x18 creditCapacityUsdX18 = Market.getCreditCapacityUsd(delegatedCreditUsdX18, marketTotalDebtUsdX18);
...
if (creditCapacityUsdX18.lte(SD59x18_ZERO)) {
revert Errors.InsufficientCreditCapacity(marketId, creditCapacityUsdX18.intoInt256());
}
...
if (market.isAutoDeleverageTriggered(delegatedCreditUsdX18, marketTotalDebtUsdX18)) {
UD60x18 adjustedUsdTokenToMintX18 =
market.getAutoDeleverageFactor(delegatedCreditUsdX18, marketTotalDebtUsdX18).mul(amountX18);
amountToMint = adjustedUsdTokenToMintX18.intoUint256();
market.updateNetUsdTokenIssuance(adjustedUsdTokenToMintX18.intoSD59x18());
} else {
295: market.updateNetUsdTokenIssuance(amountX18.intoSD59x18());
}
MarketMakingEngineConfiguration.Data storage marketMakingEngineConfiguration =
MarketMakingEngineConfiguration.load();
UsdToken usdToken = UsdToken(marketMakingEngineConfiguration.usdTokenOfEngine[msg.sender]);
usdToken.mint(msg.sender, amountToMint);
emit LogWithdrawUsdTokenFromMarket(msg.sender, marketId, amount, amountToMint);
}
When delegatedCredit <= totalDebt, the withdrawUsdTokenFromMarket function send all pnl(amount) to perpEngin, instead of reverting or deducting.
Market.sol
264:function getTotalDebt(Data storage self) internal view returns (SD59x18 totalDebtUsdX18) {
totalDebtUsdX18 = getUnrealizedDebtUsd(self).add(getRealizedDebtUsd(self));
}
function getRealizedDebtUsd(Data storage self) internal view returns (SD59x18 realizedDebtUsdX18) {
...
248: realizedDebtUsdX18 = creditDepositsValueUsdX18.intoSD59x18().add(sd59x18(self.netUsdTokenIssuance));
}
If totalDebt exceeds delegatedCredit due to price fluctuations in the collateral, the withdrawUsdTokenFromMarket function will issue full requsted usdToken without reverting or deducting.
And UsdTokenIssuance is increased. Therefore, totalDebt is also increased.
Impact
The market falls into an irreversible state, and users lose money by owning usdToken that were issued without collateral.
Recommendations
function getCreditCapacityUsd(
UD60x18 delegatedCreditUsdX18,
SD59x18 totalDebtUsdX18
)
internal
pure
returns (SD59x18 creditCapacityUsdX18)
{
-197: creditCapacityUsdX18 = delegatedCreditUsdX18.intoSD59x18().add(totalDebtUsdX18);
+ creditCapacityUsdX18 = delegatedCreditUsdX18.intoSD59x18().sub(totalDebtUsdX18);
}