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(
uint128[] calldata vaultIds,
uint128[] calldata amountsIn,
uint128[] calldata minAmountsOut
)
external
{
if (vaultIds.length != amountsIn.length) {
revert Errors.ArrayLengthMismatch(vaultIds.length, amountsIn.length);
}
if (amountsIn.length != minAmountsOut.length) {
revert Errors.ArrayLengthMismatch(amountsIn.length, minAmountsOut.length);
}
InitiateSwapContext memory ctx;
@audit>>
@audit>> Vault.Data storage currentVault = Vault.load(vaultIds[0]);
ctx.initialVaultIndexToken = currentVault.indexToken;
ctx.initialVaultCollateralAsset = currentVault.collateral.asset;
Collateral.Data storage collateral = Collateral.load(ctx.initialVaultCollateralAsset);
collateral.verifyIsEnabled();
MarketMakingEngineConfiguration.Data storage configuration = MarketMakingEngineConfiguration.load();
UsdTokenSwapConfig.Data storage tokenSwapData = UsdTokenSwapConfig.load();
ctx.collateralPriceX18 = currentVault.collateral.getPrice();
ctx.maxExecTime = uint120(tokenSwapData.maxExecutionTime);
ctx.vaultAssetBalance = IERC20(ctx.initialVaultCollateralAsset).balanceOf(ctx.initialVaultIndexToken);
for (uint256 i; i < amountsIn.length; i++) {
@audit>>
if (i != 0) {
@audit>> currentVault = Vault.load(vaultIds[i]);
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
{
UsdTokenSwapConfig.SwapRequest storage request = UsdTokenSwapConfig.load().swapRequests[user][requestId];
if (request.processed) {
revert Errors.RequestAlreadyProcessed(user, requestId);
}
FulfillSwapContext memory ctx;
ctx.deadline = request.deadline;
if (ctx.deadline < block.timestamp) {
revert Errors.SwapRequestExpired(user, requestId, ctx.deadline);
}
request.processed = true;
MarketMakingEngineConfiguration.Data storage marketMakingEngineConfiguration =
MarketMakingEngineConfiguration.load();
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
uint256 baseFeeUsd = tokenSwapData.baseFeeUsd;
@audit>> uint128 depositedUsdToken = request.amountIn;
@audit>> marketMakingEngineConfiguration.distributeProtocolAssetReward(usdToken, baseFeeUsd);
@audit>> uint256 refundAmountUsd = depositedUsdToken - baseFeeUsd;
@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.