Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: high
Invalid

Missing transfer/approval validation for USD token in CreditDelegationBranch::withdrawUsdTokenFromMarket

Summary

The withdrawUsdTokenFromMarket function mints new USD tokens but does not verify whether the caller has approved the required amount for burning when realizing unrealized debt. This can lead to transaction failures and prevent proper debt realization.

Vulnerability Details

The function withdrawUsdTokenFromMarket calls market.updateNetUsdTokenIssuance(amountX18.intoSD59x18()); without checking if the caller has approved the required amount for burning. If updateNetUsdTokenIssuance requires an approved allowance to burn tokens and the approval is missing, the transaction will fail.

246: function withdrawUsdTokenFromMarket(uint128 marketId, uint256 amount) external onlyRegisteredEngine(marketId) {
.......
.......
if (market.isAutoDeleverageTriggered(delegatedCreditUsdX18, marketTotalDebtUsdX18)) {
// if the market is in the ADL state, it reduces the requested USD
// Token amount by multiplying it by the ADL factor, which must be < 1
UD60x18 adjustedUsdTokenToMintX18 =
market.getAutoDeleverageFactor(delegatedCreditUsdX18, marketTotalDebtUsdX18).mul(amountX18);
amountToMint = adjustedUsdTokenToMintX18.intoUint256();
292: @> market.updateNetUsdTokenIssuance(adjustedUsdTokenToMintX18.intoSD59x18());
} else {
// if the market is not in the ADL state, it realizes the full requested USD Token amount
295: @> market.updateNetUsdTokenIssuance(amountX18.intoSD59x18());
}
.......
.......
}
  • The contract assumes the caller has given the necessary allowance for burning, but it does not verify this before attempting the burn.

  • If the caller has not provided approval, the transaction will unexpectedly revert, causing disruptions in the protocol's operation.

  • This could prevent the realization of unrealized debt, leading to incorrect debt accounting.

Impact

  • If the approval is missing, transactions will revert unexpectedly.

  • If tokens cannot be burned, unrealized debt may remain unaccounted for, leading to incorrect protocol accounting.

  • Users may face issues withdrawing USD tokens due to missing approvals, affecting protocol stability.

Tools Used

  • Manual Code Review

Recommendations

Ensure that the contract verifies whether the caller has approved the required amount for burning before calling updateNetUsdTokenIssuance().

function withdrawUsdTokenFromMarket(uint128 marketId, uint256 amount) external onlyRegisteredEngine(marketId) {
// loads the market's data and connected vaults
Market.Data storage market = Market.loadLive(marketId);
uint256[] memory connectedVaults = market.getConnectedVaultsIds();
// once the unrealized debt is distributed update credit delegated
// by these vaults to the market
Vault.recalculateVaultsCreditCapacity(connectedVaults);
// cache the market's total debt and delegated credit
SD59x18 marketTotalDebtUsdX18 = market.getTotalDebt();
UD60x18 delegatedCreditUsdX18 = market.getTotalDelegatedCreditUsd();
// calculate the market's credit capacity
SD59x18 creditCapacityUsdX18 = Market.getCreditCapacityUsd(delegatedCreditUsdX18, marketTotalDebtUsdX18);
// enforces that the market has enough credit capacity, if it's a listed market it must always have some
// delegated credit, see Vault.Data.lockedCreditRatio.
// NOTE: additionally, the ADL system if functioning properly must ensure that the market always has credit
// capacity to cover USD Token mint requests. Deleverage happens when the perps engine calls
// CreditDelegationBranch::getAdjustedProfitForMarketId.
// NOTE: however, it still is possible to fall into a scenario where the credit capacity is <= 0, as the
// delegated credit may be provided in form of volatile collateral assets, which could go down in value as
// debt reaches its ceiling. In that case, the market will run out of mintable USD Token and the mm engine
// must settle all outstanding debt for USDC, in order to keep previously paid USD Token fully backed.
if (creditCapacityUsdX18.lte(SD59x18_ZERO)) {
revert Errors.InsufficientCreditCapacity(marketId, creditCapacityUsdX18.intoInt256());
}
// uint256 -> UD60x18
// NOTE: we don't need to scale decimals here as it's known that USD Token has 18 decimals
UD60x18 amountX18 = ud60x18(amount);
// prepare the amount of usdToken that will be minted to the perps engine;
// initialize to default non-ADL state
uint256 amountToMint = amount;
+ MarketMakingEngineConfiguration.Data storage marketMakingEngineConfiguration =
+ MarketMakingEngineConfiguration.load();
+ UsdToken usdToken = UsdToken(marketMakingEngineConfiguration.usdTokenOfEngine[msg.sender]);
+ // Check allowance before burning tokens
+ uint256 allowance = usdToken.allowance(msg.sender, address(this));
+ require(allowance >= amount, "Insufficient allowance for burn");
// now we realize the added usd debt of the market
// note: USD Token is assumed to be 1:1 with the system's usd accounting
if (market.isAutoDeleverageTriggered(delegatedCreditUsdX18, marketTotalDebtUsdX18)) {
// if the market is in the ADL state, it reduces the requested USD
// Token amount by multiplying it by the ADL factor, which must be < 1
UD60x18 adjustedUsdTokenToMintX18 =
market.getAutoDeleverageFactor(delegatedCreditUsdX18, marketTotalDebtUsdX18).mul(amountX18);
amountToMint = adjustedUsdTokenToMintX18.intoUint256();
market.updateNetUsdTokenIssuance(adjustedUsdTokenToMintX18.intoSD59x18());
} else {
// if the market is not in the ADL state, it realizes the full requested USD Token amount
market.updateNetUsdTokenIssuance(amountX18.intoSD59x18());
}
// loads the market making engine configuration storage pointer
- MarketMakingEngineConfiguration.Data storage marketMakingEngineConfiguration =
- MarketMakingEngineConfiguration.load();
// mint USD Token to the perps engine
- UsdToken usdToken = UsdToken(marketMakingEngineConfiguration.usdTokenOfEngine[msg.sender]);
usdToken.mint(msg.sender, amountToMint);
// emit an event
emit LogWithdrawUsdTokenFromMarket(msg.sender, marketId, amount, amountToMint);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
7 months ago
inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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