No checking for max/min price of collateral when calculating price of collateral for minting EUROs lead to scenario that user make profit when collateral price crash.
Chainlink aggregators have a built-in circuit breaker to prevent the price of an asset from deviating outside a predefined price range. This circuit breaker may cause the oracle to persistently return the minPrice instead of the actual asset price in the event of a significant price drop. Price of collateral is calculated as below:
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);
(, int256 eurUsdPrice,,,) = clEurUsd.latestRoundData();
return collateralUsd / uint256(eurUsdPrice);
}
As can be seen, price of collateral is calculated based on average price in 4 hours of token in chainlink by using function avgPrice(). It is used in function euroCollateral() to calculate max mintable EUROs token and check for liquidation in the vault:
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)); // <-- used to calculate total value of token
}
}
function maxMintable() private view returns (uint256) {
return euroCollateral() * ISmartVaultManagerV3(manager).HUNDRED_PC() / ISmartVaultManagerV3(manager).collateralRate(); // <--- used to check max mintable EUROs token
}
When token price crash, user can get profit by using following scenario:
1, Price of token massively drop under min range for 4 hours, currently price returned from tokenToEurAvg() function is equal to min range due to mechanism of chainlink
2, Attacker buy collateral token after price drop
3, Deposit token to vault
4, Minting EUROs token, get profit from them because collateral token is overpriced, leaving vault under collateralized.
In the past, it is already happened when luna collapse. In the token list, WBTC can be because it is a bridged asset and if the bridge is compromised/fails then WBTC will depeg and will no longer be equivalent to BTC, lead to massive drop
Attacker can get profit when price drop under min value.
Manual review
When checking max mintable, there should be minPrice and maxPrice for each token, if price of token is not in that range, skip them:
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];
+ if((token.minPrice * getAssetBalance(token.symbol, token.addr) > calculator.tokenToEurAvg(token, getAssetBalance(token.symbol, token.addr))
|| (token.maxPrice * getAssetBalance(token.symbol, token.addr) < calculator.tokenToEurAvg(token, getAssetBalance(token.symbol, token.addr)))
continue;
euros += calculator.tokenToEurAvg(token, getAssetBalance(token.symbol, token.addr));
}
}
For better soution, user other off-chain oracle providers, or using Uniswap's TWAP when the price is not in the range min/max price.
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.