Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: medium
Valid

Incorrect Premium/Discount Factor Application in USD Token Swaps can Lead to Vault Indebtedness

Summary

The UsdTokenSwapConfig.getPremiumDiscountFactor() function implements an inverted logic for applying premiums and discounts during USD token swaps. This causes economic incentives to work against the protocol's stability goals, leading to increased vault debt and inefficient capital utilization.

Vulnerability Details

fullfillSwap fulfills a USD token swap request of the user by converting the specified amount of USD tokens to a collateral asset.

function fulfillSwap(
address user,
uint128 requestId,
bytes calldata priceData,
address engine
)
external
onlyRegisteredSystemKeepers
{

It calls getAmountOfAssetOut to calculate the amount of swapped assets:
StabilityBranch.sol#L373

// get amount out asset
ctx.amountIn = request.amountIn;
ctx.amountOutBeforeFeesX18 = getAmountOfAssetOut(ctx.vaultId, ud60x18(ctx.amountIn), ctx.priceX18);

The getAmountOfAssetOut function calculates the amount of assets to be received in a swap based on the input USD amount, its current price, and a premium or discount. The premium or discount is determined by the vault's debt/TVL ratio:
StabilityBranch.sol#L97-L125

function getAmountOfAssetOut(
uint128 vaultId,
UD60x18 usdAmountInX18,
UD60x18 indexPriceX18
)
public
view
returns (UD60x18 amountOutX18)
{
Vault.Data storage vault = Vault.load(vaultId);
UD60x18 vaultAssetsUsdX18 = ud60x18(IERC4626(vault.indexToken).totalAssets()).mul(indexPriceX18);
if (vaultAssetsUsdX18.isZero()) revert Errors.InsufficientVaultBalance(vaultId, 0, 0);
SD59x18 vaultDebtUsdX18 = vault.getTotalDebt();
@> UD60x18 premiumDiscountFactorX18 =
UsdTokenSwapConfig.load().getPremiumDiscountFactor(vaultAssetsUsdX18, vaultDebtUsdX18);
@> amountOutX18 = usdAmountInX18.div(indexPriceX18).mul(premiumDiscountFactorX18);
}

The premium/discount is calculated using the getPremiumDiscountFactor function from UsdTokenSwapConfig, and then applied as a multiplier to the output asset.
UsdTokenSwapConfig.sol#L137-L139

UD60x18 premiumDiscountFactorX18 =
UsdTokenSwapConfig.load().getPremiumDiscountFactor(vaultAssetsUsdX18, vaultDebtUsdX18);

the getPremiumDiscountFactor returns the premium or discount to be applied to the amount out of a swap, based on the vault's debt and the system configured premium / discount curve parameters:

// if the vault is in credit, we apply a discount, otherwise, we apply a premium
premiumDiscountFactorX18 =
vaultDebtUsdX18.lt(SD59x18_ZERO) ? UD60x18_UNIT.sub(pdCurveYX18) : UD60x18_UNIT.add(pdCurveYX18);
  1. Credit Scenario (Debt < 0):

  • vaultDebtUsdX18.lt(SD59x18_ZERO) is true

  • Factor = UD60x18_UNIT.sub(pdCurveYX18) < 1

  • When multiplied with output: User gets FEWER assets (discount on price)

  1. Debt Scenario (Debt > 0):

  • vaultDebtUsdX18.lt(SD59x18_ZERO) is false

  • Factor = UD60x18_UNIT.add(pdCurveYX18) > 1

  • When multiplied with output: User gets MORE assets (premium on price)

The economic incentives are currently misaligned because:

  1. When a vault is in debt:

  • Current: Users get MORE assets for their USD (discount)

  • Should: Users should get FEWER assets for their USD (premium) to incentivize USD deposits and discourage asset withdrawals, helping reduce the debt

  1. When a vault has excess collateral:

  • Current: Users get FEWER assets for their USD (premium)

  • Should: Users should get MORE assets for their USD (discount) to incentivize asset withdrawals and reduce excess collateral

Impact

It creates wrong economic incentives that work against the protocol's stability. Vaults in debt will become more indebted as users are incentivized to withdraw more assets.
Vaults with excess collateral will accumulate even more collateral as users are disincentivized from withdrawing assets.

Tools Used

Manual Review

Recommendations

The logic in getPremiumDiscountFactor should be reversed:

// if the vault is in credit (debt < 0), we apply a premium, otherwise, we apply a discount
premiumDiscountFactorX18 =
vaultDebtUsdX18.lt(SD59x18_ZERO) ? UD60x18_UNIT.add(pdCurveYX18) : UD60x18_UNIT.sub(pdCurveYX18);
Updates

Lead Judging Commences

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

The getPremiumDiscountFactor() function applies premiums and discounts inversely to what would maintain protocol stability

Support

FAQs

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