Summary
The getRealizedDebtUsd()
function incorrectly adds credit deposits to net USD token issuance instead of subtracting them, causing accounting issues throughout the market-making engine.
Vulnerability Details
When traders close profitable positions, the trading engine calls withdrawUsdTokenFromMarket()
which mints USD tokens and increases the market's netUsdTokenIssuance
:
File: CreditDelegationBranch.sol
283:
284:
285: if (market.isAutoDeleverageTriggered(delegatedCreditUsdX18, marketTotalDebtUsdX18)) {
286:
287:
288: UD60x18 adjustedUsdTokenToMintX18 =
289: market.getAutoDeleverageFactor(delegatedCreditUsdX18, marketTotalDebtUsdX18).mul(amountX18);
290:
291: amountToMint = adjustedUsdTokenToMintX18.intoUint256();
292: market.updateNetUsdTokenIssuance(adjustedUsdTokenToMintX18.intoSD59x18());
293: } else {
294:
295: market.updateNetUsdTokenIssuance(amountX18.intoSD59x18());
296: }
However, when traders are liquidated or settle negative PnL positions, collateral is transferred via depositCreditForMarket
which adds to creditDeposits
:
File: CreditDelegationBranch.sol
212:
213: if (collateralAddr == usdToken) {
214:
215: market.updateNetUsdTokenIssuance(unary(amountX18.intoSD59x18()));
216: } else {
217: if (collateralAddr == usdc) {
218: market.settleCreditDeposit(address(0), amountX18);
219: } else {
220:
221:
222: market.depositCredit(collateralAddr, amountX18);
223: }
224: }
And depositCredit()
increases the creditDeposits
mapping.
File: Market.sol
396: function depositCredit(Data storage self, address asset, UD60x18 amountX18) internal {
397: AssetToAmountMap.update(self.creditDeposits, asset, amountX18, true);
398: }
The key issue is that getRealizedDebtUsd()
adds these credit deposits to netUsdTokenIssuance
when calculating realized debt, instead of subtracting them to offset the debt.
File: Market.sol
234: function getRealizedDebtUsd(Data storage self) internal view returns (SD59x18 realizedDebtUsdX18) {
235:
236: UD60x18 creditDepositsValueUsdX18;
237:
238:
239: if (block.timestamp <= self.lastCreditDepositsValueRehydration) {
240: creditDepositsValueUsdX18 = ud60x18(self.creditDepositsValueCacheUsd);
241: } else {
242:
243: creditDepositsValueUsdX18 = getCreditDepositsValueUsd(self);
244: }
245:
246:
247:
248: realizedDebtUsdX18 = creditDepositsValueUsdX18.intoSD59x18().add(sd59x18(self.netUsdTokenIssuance));
249: }
Impact
The incorrect calculation of realized debt breaks core accounting functionality across the market-making engine since the value is used extensively for credit capacity and debt calculations.
Recommendations
Modify getRealizedDebtUsd()
to subtract credit deposits from net USD token issuance instead of adding them when calculating the realized debt.