When a black swan events happens and any collateral token losses a lot of value in a short period of time, EURO tokens can be stolen as they are borrowed for overvalued collateral. This is stretched out for an unnecessarily long time, as the protocol uses the average price of the last 4 hours to calculate the collateral value.
To see if a vault can be liquidated, or how much EURO tokens can be borrowed for the given collateral, the price of the collateral in EURO is calculated.
To calculate how much the collateral of a vault is worth in EUR, the protocol uses the tokenToEurAvg
function from the PriceCalculator
contract. This function calculates the average price of the last 4 hours of chainlink price feed data:
contract PriceCalculator is IPriceCalculator {
bytes32 private immutable NATIVE;
Chainlink.AggregatorV3Interface public immutable clEurUsd;
constructor (bytes32 _native, address _clEurUsd) {
NATIVE = _native;
clEurUsd = Chainlink.AggregatorV3Interface(_clEurUsd);
}
function avgPrice(uint8 _hours, Chainlink.AggregatorV3Interface _priceFeed) private view returns (uint256) {
uint256 startPeriod = block.timestamp - _hours * 1 hours;
uint256 roundTS;
uint80 roundId;
int256 answer;
(roundId, answer,, roundTS,) = _priceFeed.latestRoundData();
uint256 accummulatedRoundPrices = uint256(answer);
uint256 roundCount = 1;
while (roundTS > startPeriod && roundId > 1) {
roundId--;
try _priceFeed.getRoundData(roundId) {
(, answer,, roundTS,) = _priceFeed.getRoundData(roundId);
accummulatedRoundPrices += uint256(answer);
roundCount++;
} catch {
continue;
}
}
return accummulatedRoundPrices / roundCount;
}
function getTokenScaleDiff(bytes32 _symbol, address _tokenAddress) private view returns (uint256 scaleDiff) {
return _symbol == NATIVE ? 0 : 18 - ERC20(_tokenAddress).decimals();
}
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);
}
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();
uint256 collateralUsd = scaledCollateral * uint256(_tokenUsdPrice);
(, int256 eurUsdPrice,,,) = clEurUsd.latestRoundData();
return collateralUsd / uint256(eurUsdPrice);
}
function eurToToken(ITokenManager.Token memory _token, uint256 _eurValue) external view returns (uint256) {
Chainlink.AggregatorV3Interface tokenUsdClFeed = Chainlink.AggregatorV3Interface(_token.clAddr);
(, int256 tokenUsdPrice,,,) = tokenUsdClFeed.latestRoundData();
(, int256 eurUsdPrice,,,) = clEurUsd.latestRoundData();
return _eurValue * uint256(eurUsdPrice) / uint256(tokenUsdPrice) / 10 ** getTokenScaleDiff(_token.symbol, _token.addr);
}
}
If a black swan event occurs and any collateral token drops drastically in price, the average calculation will push up the amount of EURO tokens that can be borrowed against the collateral drastically.
Let's say the collateral rate is 120%. A malicious actor would be able to buy the collateral tokens for 5 dollar and borrow EURO tokens, which are worth more than 60 dollar.
Such events can not be totally avoided, but as it rounds to the average price of the last 4 hours, it will take much longer time until the price is adjusted to the new price. Therefore, such a scenario can be abused much longer till the EURO token is inflated to a point of no return.
Borrowers can steal EURO tokens on black swan events for an unnecessarily long period of time.
Use the latest data to decide if a borrower should be liquidated or EURO tokens can be borrowed for the given collateral. Or at least use the latest data on drastic price changes. Other protection mechanisms against such events should also be considered.