Old vulnerability in SwapRouterV2 is not fixed probably, attacker still can exploit when token price gradually reduce by abusing difference of value between average price and current price
As discussed by sponor, function calculateMinimumAmountOut()
is used to ensures that the amount out is minimum amount to keep the vault collateralised:
But it is not enough to avoid. Take a look at calculate amount out function:
function calculateMinimumAmountOut(bytes32 _inTokenSymbol, bytes32 _outTokenSymbol, uint256 _amount) private view returns (uint256) {
ISmartVaultManagerV3 _manager = ISmartVaultManagerV3(manager);
uint256 requiredCollateralValue = minted * _manager.collateralRate() / _manager.HUNDRED_PC();
uint256 collateralValueMinusSwapValue = euroCollateral() - calculator.tokenToEur(getToken(_inTokenSymbol), _amount); //<-----
return collateralValueMinusSwapValue >= requiredCollateralValue ?
0 : calculator.eurToToken(getToken(_outTokenSymbol), requiredCollateralValue - collateralValueMinusSwapValue);
}
collateralValueMinusSwapValue
is calculated based on two variable: variable returned from euroCollateral()
function, and variable returned from tokenToEur()
function. Function euroCollateral()
is calculated based on average price in 4 hours of token:
function euroCollateral() private view returns (uint256 euros) {
ITokenManager.Token[] memory acceptedTokens = getTokenManager().getAcceptedTokens();
for (uint256 i = 0; i < acceptedTokens.length; i++) {
ITokenManager.Token memory token = acceptedTokens[i];
euros += calculator.tokenToEurAvg(token, getAssetBalance(token.symbol, token.addr)); //<-------
}
}
tokenToEurAvg()
function:
function tokenToEurAvg(ITokenManager.Token memory _token, uint256 _tokenValue) external view returns (uint256) {
Chainlink.AggregatorV3Interface tokenUsdClFeed = Chainlink.AggregatorV3Interface(_token.clAddr);
uint256 scaledCollateral = _tokenValue * 10 ** getTokenScaleDiff(_token.symbol, _token.addr);
uint256 collateralUsd = scaledCollateral * avgPrice(4, tokenUsdClFeed); // <--- price of collateral to usd is calculated based on average price in 4 hours
(, int256 eurUsdPrice,,,) = clEurUsd.latestRoundData();
return collateralUsd / uint256(eurUsdPrice);
}
About tokenToEur()
function, it is calculated based on lastest price of token:
function tokenToEur(ITokenManager.Token memory _token, uint256 _tokenValue) external view returns (uint256) {
Chainlink.AggregatorV3Interface tokenUsdClFeed = Chainlink.AggregatorV3Interface(_token.clAddr);
uint256 scaledCollateral = _tokenValue * 10 ** getTokenScaleDiff(_token.symbol, _token.addr);
(,int256 _tokenUsdPrice,,,) = tokenUsdClFeed.latestRoundData(); // <---- get lastest price
uint256 collateralUsd = scaledCollateral * uint256(_tokenUsdPrice);
(, int256 eurUsdPrice,,,) = clEurUsd.latestRoundData();
return collateralUsd / uint256(eurUsdPrice);
}
The gap between average price
and actual price
can be exploited. Consider scenario, when collateralRate
= 110%:
Price of WBTC gradually reduce from 40000
to 30000
in 4 hours, assume at that time price returned from function tokenToEurAvg()
is 35000
Deposit 1 WBTC, which is worth 30000 in that time
Mint all EUROs token possible with that number of collateral
Swap WBTC to another token. At this step, collateralValueMinusSwapValue
should returned 0
, but it returned 5000
due to the gap between euroCollateral()
and calculator.tokenToEur(getToken(_inTokenSymbol), _amount)
Repeat old steps like the way SmartVaultV2 was exploited before, link. But not like old one, it only can be exploit by using gap of 2 variables
Profit from this attack will depend on collateral rate, minting fee and total % price drop in 4 hours. But as described below, this attack vector is possible
Attack can mint more EUROs token that he should, which will harm to protocol
Manual review
Let user submit their own minimumAmountOut
variable, but make sure that vault will not be under collateralised when swap
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.