DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: high
Invalid

Partial Swap Failures will lead to inconsistency in protocol accounting

Summary

The _doDexSwap function in the PerpetualVault contract does not handle partial swap completions, leading to potential accounting errors and mixed collateral holdings.

Vulnerability Details

Description:
The _doDexSwap function performs a DEX swap using ParaSwap. However, it does not check for partial completions of the swap. If the swap only partially completes due to insufficient liquidity, the protocol assumes full conversion but holds mixed collateral. This can lead to incorrect share calculations and potential accounting errors.

Root Cause:
The root cause of this issue is the lack of a check for partial swap completions in the _doDexSwap function. The function assumes that the swap will fully complete and does not handle cases where only a partial amount is swapped.

Proof of Concept:

// Test function to demonstrate the issue
function test_PartialSwapFailure() public {
// Setup deposit
address alice = makeAddr("alice");
uint256 depositAmount = 1e10;
depositFixture(alice, depositAmount);
// Get collateral token
IERC20 collateralToken = PerpetualVault(vault).collateralToken();
// Verify initial vault balance
uint256 initialVaultBalance = collateralToken.balanceOf(vault);
// Create swap data that will partially complete
bytes[] memory swapData = new bytes[](1);
address[] memory gmxPath = new address[](1);
gmxPath[0] = address(0x70d95587d40A2caf56bd97485aB3Eec10Bee6336);
uint256 minOutputAmount = depositAmount / 2; // Set to half the deposit amount to simulate partial completion
swapData[0] = abi.encode(PROTOCOL.DEX, abi.encode(gmxPath, depositAmount, minOutputAmount));
// Execute partial swap
address keeper = PerpetualVault(vault).keeper();
vm.prank(keeper);
PerpetualVault(vault).run(true, true, mockData.getMarketPrices(), swapData);
// Simulate partial swap completion
GmxOrderExecuted(true);
// Verify partial swap completion
uint256 outputAmount = collateralToken.balanceOf(vault);
assertLt(outputAmount, depositAmount, "Swap did not partially complete");
// Verify mixed collateral holdings
uint256 remainingCollateral = collateralToken.balanceOf(vault);
assertGt(remainingCollateral, 0, "No remaining collateral");
// Verify incorrect share calculations
uint256 userShares = PerpetualVault(vault).getUserShares(alice);
assertEq(userShares, depositAmount * 1e8, "Incorrect share calculations");
}

Impact

  • Protocol assumes full conversion but holds mixed collateral: The protocol may hold a mix of the input and output tokens if the swap only partially completes.

  • Accounting Error: Share calculations use the full amount assumption, leading to incorrect share allocations and potential financial discrepancies.

Tools Used

Manual

Recommendations

To mitigate this issue, implement a check for partial swap completions in the _doDexSwap function. If the swap only partially completes, handle the remaining collateral appropriately.

Example of improved _doDexSwap function:

function _doDexSwap(bytes memory data, bool isCollateralToIndex) internal returns (uint256 outputAmount) {
(address to, uint256 amount, bytes memory callData) = abi.decode(data, (address, uint256, bytes));
IERC20 inputToken;
IERC20 outputToken;
if (isCollateralToIndex) {
inputToken = collateralToken;
outputToken = IERC20(indexToken);
} else {
inputToken = IERC20(indexToken);
outputToken = collateralToken;
}
uint256 balBefore = outputToken.balanceOf(address(this));
ParaSwapUtils.swap(to, callData);
outputAmount = IERC20(outputToken).balanceOf(address(this)) - balBefore;
// Check for partial swap completion
if (outputAmount < amount) {
// Handle remaining collateral appropriately
uint256 remainingAmount = amount - outputAmount;
inputToken.safeTransfer(address(this), remainingAmount);
}
emit DexSwap(address(inputToken), amount, address(outputToken), outputAmount, isCollateralToIndex);
}
Updates

Lead Judging Commences

n0kto Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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