User can lose funds when calling swap()
function in SmartVaultV3.sol
, due to how calculations in calculateMinimumAmountOut()
sets minimumAmountOut
in swap()
. An attacker can manipulate a users slippage during a swap.
swap()
function allows a user to swap their collateral with a different token. When a user calls swap()
, this function makes an internal call to calculateMinimumAmountOut()
function to set minimumAmountOut
and make it so user won’t be in a position to be liquidated.
For example a normal use case for this function can go like shown below:
User has minted 100 EUROs by depositing 150€ worth of wETH.
User notices ETH will lose value so they call swap()
function with _amount
set to 150 to swap their wETH to USDC in order to not be under collateralized.
swap()
function does a call to calculateMinimumAmountOut()
calculateMinimumAmountOut()
returns 120€ worth of tokens. Math for this calculation when collateralRate()
is set to 120 like in unit tests goes as shown below:
requiredCollateralValue
=
collateralValueMinusSwapValue
=
In this normal scenario user can get a minimum of 120€ worth of USDC from their swap because minimumAmountOut
is set to 120€ worth of tokens.
In a potential attack scenario this same swap can go like shown below:
User has minted 100 EUROs by depositing 150€ worth of wETH.
User notices ETH will lose value so they call swap()
function with _amount
set to 150 to swap their wETH to USDC in order to not be under collateralized.
Attacker does a frontrunning attack and calls burn()
function with _amount
= 100 (minted
becomes 0) and also performing a deposit into UniSwap pools to manipulate prices (first part of the sandwich attack).
swap()
function does a call to calculateMinimumAmountOut()
calculateMinimumAmountOut()
returns 0€ worth of tokens. Math for this calculation when collateralRate()
is set to 120 like in unit tests goes as shown below:
requiredCollateralValue
=
collateralValueMinusSwapValue
=
With this front running attack minimumAmountOut
is set to 0.
Attacker can now finish the sandwich attack in order to steal user’s funds and easily steal more than the money they have spent when calling burn()
. Using the numbers given in the example, attacker is able to steal up to 150€ worth of tokens while spending 100 EUROs to call the burn()
function. User risks losing all their collateral considering their minted
is set to 0 by the attacker, user would end up with a loss of 50€ worth of funds. Since value of collateral is always more than the value of minted
, user would always lose money.
Readme file of this protocol acknowledges a sandwich attack since the following line is in Known Issues section:
minimumAmountOut
can be set to 0 if there is no value required to keep collateral above required level. The user is likely to lose some value in the swap, especially when Uniswap fees are factored in, but this is at the user's discretion
However this attack is not due to a user error, this is an attacker causing minimumAmountOut
to be set to 0.
Numbers I have used are just for simplicity sake to explain the attack. Attacker doesn’t need to burn
all the minted
amount to set minimumAmountOut
to 0, attacker would simply need to burn
just enough to set collateralValueMinusSwapValue >= requiredCollateralValue
line to true
.
This vulnerability directly causes a users funds to be stolen.
Manual review.
A simple solution would be to add onlyOwner
modifier to burn()
function.
A better solution would be to add a new parameter for the user to set their minimum amount expected from the swap in swap()
function and implementing an if check. Updated swap()
function is shown below.
This allows users to have more protection with their swap and stops this potential frontrunning and sandwich attack. Consider implementing both solutions.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.