The Standard

The Standard
DeFiHardhat
20,000 USDC
View results
Submission Details
Severity: medium
Invalid

Borrowers can steal EURO tokens on black swan events for an unnecessarily long period of time

Summary

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.

Vulnerability Details

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:

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));
}
}

Here we can see the PriceCalculator contract and the tokenToEurAvg function:

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.

For example:

Price 1: $100

Price 2: $101

Price 3: $99

Price 4: $5

Avg Price: (100 + 101 + 99 + 5) / 4 = $76.25

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.

Impact

Borrowers can steal EURO tokens on black swan events for an unnecessarily long period of time.

Recommendations

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.

Updates

Lead Judging Commences

hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Chainlink-price

cosine Submitter
over 1 year ago
hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

Chainlink-price

Support

FAQs

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