Summary
Auto deleverage system is used to reduce requested USD tokens for profitable users when the market is in net debt, but protocol fails to fill trader's order and to provide AutoDeleverageFactor
when market total debt is negative because of mistake in marketDebtRatio
calculation which leads to revert
due to underflow
.
Vulnerability Details
CreditDelegationBranch.sol:withdrawUsdTokenFromMarket() function gives USD tokens to perpsEngine
as incentive for trader if his old position had positive pnl then credit that to the trader (function called in SettlementBranch.sol:_fillOrder()):
function withdrawUsdTokenFromMarket(uint128 marketId, uint256 amount) external onlyRegisteredEngine(marketId) {
Market.Data storage market = Market.loadLive(marketId);
uint256[] memory connectedVaults = market.getConnectedVaultsIds();
Vault.recalculateVaultsCreditCapacity(connectedVaults);
SD59x18 marketTotalDebtUsdX18 = market.getTotalDebt();
UD60x18 delegatedCreditUsdX18 = market.getTotalDelegatedCreditUsd();
SD59x18 creditCapacityUsdX18 = Market.getCreditCapacityUsd(delegatedCreditUsdX18, marketTotalDebtUsdX18);
if (creditCapacityUsdX18.lte(SD59x18_ZERO)) {
revert Errors.InsufficientCreditCapacity(marketId, creditCapacityUsdX18.intoInt256());
}
UD60x18 amountX18 = ud60x18(amount);
uint256 amountToMint = amount;
if (market.isAutoDeleverageTriggered(delegatedCreditUsdX18, marketTotalDebtUsdX18)) {
UD60x18 adjustedUsdTokenToMintX18 =
market.getAutoDeleverageFactor(delegatedCreditUsdX18, marketTotalDebtUsdX18).mul(amountX18);
amountToMint = adjustedUsdTokenToMintX18.intoUint256();
market.updateNetUsdTokenIssuance(adjustedUsdTokenToMintX18.intoSD59x18());
} else {
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);
}
But let's imagine that market has marketTotalDebtUsdX18 = -10_000
and delegatedCreditUsdX18 = 20_000
:
We will pass credit capacity requirement because creditCapacityUsdX18 = marketTotalDebtUsdX18 + delegatedCreditUsdX18 = 10_000
:
if (creditCapacityUsdX18.lte(SD59x18_ZERO)) {
revert Errors.InsufficientCreditCapacity(marketId, creditCapacityUsdX18.intoInt256());
}
Which means that protocol is ready to incentive us with some amount of USD tokens.
Next we go to the line that checks if ADL system is triggered:
if (market.isAutoDeleverageTriggered(delegatedCreditUsdX18, marketTotalDebtUsdX18)) {
Market.sol:isAutoDeleverageTriggered():
function isAutoDeleverageTriggered(
Data storage self,
UD60x18 delegatedCreditUsdX18,
SD59x18 totalDebtUsdX18
)
internal
view
returns (bool triggered)
{
SD59x18 sdDelegatedCreditUsdX18 = delegatedCreditUsdX18.intoSD59x18();
if (sdDelegatedCreditUsdX18.lte(totalDebtUsdX18) || sdDelegatedCreditUsdX18.isZero()) {
return false;
}
UD60x18 marketDebtRatio = totalDebtUsdX18.div(sdDelegatedCreditUsdX18).intoUD60x18();
triggered = marketDebtRatio.gte(ud60x18(self.autoDeleverageStartThreshold));
}
sdDelegatedCreditUsdX18 > totalDebtUsdX18
, means we will go to marketDebtRatio
calculation which will revert because totalDebtUsdX18
is negative:
UD60x18 marketDebtRatio = -10000/20000 = uint256(-0.5);
Impact
Protocol fails to fill trader's order and to provide him USD tokens when market total debt is negative and traders PnL is positive.
Tools Used
Manual Review
Recommendations
Use abs()
for marketDebtRatio
calculation:
UD60x18 marketDebtRatio = totalDebtUsdX18.abs().div(sdDelegatedCreditUsdX18).intoUD60x18();