Summary
When usdTokens are issuanced or received from perpEngin, only market.netUsdTokenIssuance is updated.
This value is only related the realizedDebt of market.
Therefore zlpVault.realizedDebt is not updated.
Vulnerability Details
https://github.com/Cyfrin/2025-01-zaros-part-2/blob/main/src/market-making/branches/CreditDelegationBranch.sol#L246
246: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);
}
https://github.com/Cyfrin/2025-01-zaros-part-2/blob/main/src/market-making/leaves/Vault.sol#L245
https://github.com/Cyfrin/2025-01-zaros-part-2/blob/main/src/market-making/leaves/Vault.sol#L226
https://github.com/Cyfrin/2025-01-zaros-part-2/blob/main/src/market-making/leaves/Vault.sol#L218
unsettledRealizedDebtUsd = marketsRealizedDebtUsd - depositedUsdc
getTotalDebt = UnsettledRealizedDebt + marketsUnrealizedDebtUsd
TotalCreditCapacityUsd = totalAssetsUsd - getTotalDebt
https://github.com/Cyfrin/2025-01-zaros-part-2/blob/main/src/market-making/branches/StabilityBranch.sol#L401-L409
vault.marketsRealizedDebtUsd -= int128(ctx.amountIn);
ctx.usdToken.burn(ctx.amountIn);
IERC20(ctx.asset).safeTransferFrom(vault.indexToken, address(this), ctx.amountOut + ctx.protocolReward);
marketMakingEngineConfiguration.distributeProtocolAssetReward(ctx.asset, ctx.protocolReward);
When users swap the usdToken to assets in the zlpVault, the totalAssets
and marketRealizedDebt
are decreased.
As a result, even if marketMakingEngin
issuance usdTokens and users swap these to assets, the vault's TotalCreditCapacity
is not changed.
Impact
Vault.getTotalCreditCapacityUsd is incorrect.
Even if vault have no funds, it could supply credit to market, leading to users loss.
Recommendations
Consider reflecting market.netUsdTokenIssuance
in vault.realizedDebut
.