Part 2

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

Users can initiate a swap against a Paused/inactive vault

Summary

The code implementation fails to check if a vault is Live during the initiation of a swap causing the swap to fail during fulfilment and costing the user Some funds as they will still be charged the base fee.

Vulnerability Details

No check in the swap initiation to ensure that the vault id is LIVE

function initiateSwap( //@AUDIT batching isssue NOTE
uint128[] calldata vaultIds,
uint128[] calldata amountsIn,
uint128[] calldata minAmountsOut
)
external
{
// Perform length checks
if (vaultIds.length != amountsIn.length) {
revert Errors.ArrayLengthMismatch(vaultIds.length, amountsIn.length);
}
if (amountsIn.length != minAmountsOut.length) {
revert Errors.ArrayLengthMismatch(amountsIn.length, minAmountsOut.length);
}
// working data
InitiateSwapContext memory ctx;
@audit>> // cache the vault's index token and asset addresses
@audit>> Vault.Data storage currentVault = Vault.load(vaultIds[0]);
ctx.initialVaultIndexToken = currentVault.indexToken;
ctx.initialVaultCollateralAsset = currentVault.collateral.asset;
// load collateral data; must be enabled
Collateral.Data storage collateral = Collateral.load(ctx.initialVaultCollateralAsset);
collateral.verifyIsEnabled();
// load market making engine config
MarketMakingEngineConfiguration.Data storage configuration = MarketMakingEngineConfiguration.load();
// load usd token swap data
UsdTokenSwapConfig.Data storage tokenSwapData = UsdTokenSwapConfig.load();
// cache additional common fields
// ctx.collateralPriceX18 in zaros internal precision
ctx.collateralPriceX18 = currentVault.collateral.getPrice();
ctx.maxExecTime = uint120(tokenSwapData.maxExecutionTime);
// ctx.vaultAssetBalance in native precision of ctx.initialVaultCollateralAsset
ctx.vaultAssetBalance = IERC20(ctx.initialVaultCollateralAsset).balanceOf(ctx.initialVaultIndexToken);
for (uint256 i; i < amountsIn.length; i++) {
@audit>> // for all but first iteration, refresh the vault and enforce same collateral asset
if (i != 0) {
@audit>> currentVault = Vault.load(vaultIds[i]);
// revert for swaps using vaults with different collateral assets
if (currentVault.collateral.asset != ctx.initialVaultCollateralAsset) {
revert Errors.VaultsCollateralAssetsMismatch();
}

But this was enforced in the fulfilment of swap causing a users swap call to fail.

function fulfillSwap(
address user,
uint128 requestId,
bytes calldata priceData,
address engine
)
external
onlyRegisteredSystemKeepers
{
// load request for user by id
UsdTokenSwapConfig.SwapRequest storage request = UsdTokenSwapConfig.load().swapRequests[user][requestId];
// revert if already processed
if (request.processed) {
revert Errors.RequestAlreadyProcessed(user, requestId);
}
// working data
FulfillSwapContext memory ctx;
// if request dealine expired revert
ctx.deadline = request.deadline;
if (ctx.deadline < block.timestamp) {
revert Errors.SwapRequestExpired(user, requestId, ctx.deadline);
}
// set request processed to true
request.processed = true;
// load market making engine config
MarketMakingEngineConfiguration.Data storage marketMakingEngineConfiguration =
MarketMakingEngineConfiguration.load();
// load vault data
ctx.vaultId = request.vaultId;
@audit>> Vault.Data storage vault = Vault.loadLive(ctx.vaultId);

causing the swap to fail and the user will incur a base fee regardless

// cache the usd token swap base fee
uint256 baseFeeUsd = tokenSwapData.baseFeeUsd;
// cache the amount of usd token previously deposited
@audit>> uint128 depositedUsdToken = request.amountIn;
// transfer base fee too protocol fee recipients
@audit>> marketMakingEngineConfiguration.distributeProtocolAssetReward(usdToken, baseFeeUsd);
// cache the amount of usd tokens to be refunded
@audit>> uint256 refundAmountUsd = depositedUsdToken - baseFeeUsd;
// transfer usd refund amount back to user
@audit>> IERC20(usdToken).safeTransfer(msg.sender, refundAmountUsd);

Impact

Swap initiation can be called against vaults that are paused(not LIVE) and the fulfilment of this call will revert costing the user base fee from their deposited amount-in.

Tools Used

Manual Review.

Recommendations

Check and ensure a vault is LIVE during the initiation of a swap.

Updates

Lead Judging Commences

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

`initiateSwap` allows users to initiate swap even when the vault is paused

Support

FAQs

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