The Standard

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

Chainlink price feed data is not validated and can lead to loss of user funds

Summary

The chainlink price feed data is not validated at all in the distributeAssets function of the LiquidationPool contract, which can lead to a loss of user funds.

Vulnerability Details

The distributionAssets function is used to distribute collateral tokens from liquidations and reward them to the stakers, which buy them for EURO tokens on a discount. The function uses the chainlink price feed to calculate the amount of collateral tokens to distribute, depending on the current price of the collateral tokens. There are two different chainlink requests inside the function in neither implements the recommended security checks from chainlink.

Therefore, a stale price could be used for calculation, leading to a loss of funds as users could pay more EURO tokens for the collateral tokens instead of getting a discount.

Here we can see the distributeAssets function and that no security checks are implemented:

function distributeAssets(ILiquidationPoolManager.Asset[] memory _assets, uint256 _collateralRate, uint256 _hundredPC) external payable {
consolidatePendingStakes();
(,int256 priceEurUsd,,,) = Chainlink.AggregatorV3Interface(eurUsd).latestRoundData();
uint256 stakeTotal = getStakeTotal();
uint256 burnEuros;
uint256 nativePurchased;
for (uint256 j = 0; j < holders.length; j++) {
Position memory _position = positions[holders[j]];
uint256 _positionStake = stake(_position);
if (_positionStake > 0) {
for (uint256 i = 0; i < _assets.length; i++) {
ILiquidationPoolManager.Asset memory asset = _assets[i];
if (asset.amount > 0) {
(,int256 assetPriceUsd,,,) = Chainlink.AggregatorV3Interface(asset.token.clAddr).latestRoundData();
uint256 _portion = asset.amount * _positionStake / stakeTotal;
uint256 costInEuros = _portion * 10 ** (18 - asset.token.dec) * uint256(assetPriceUsd) / uint256(priceEurUsd)
* _hundredPC / _collateralRate;
if (costInEuros > _position.EUROs) {
_portion = _portion * _position.EUROs / costInEuros;
costInEuros = _position.EUROs;
}
_position.EUROs -= costInEuros;
rewards[abi.encodePacked(_position.holder, asset.token.symbol)] += _portion;
burnEuros += costInEuros;
if (asset.token.addr == address(0)) {
nativePurchased += _portion;
} else {
IERC20(asset.token.addr).safeTransferFrom(manager, address(this), _portion);
}
}
}
}
positions[holders[j]] = _position;
}
if (burnEuros > 0) IEUROs(EUROs).burn(address(this), burnEuros);
returnUnpurchasedNative(_assets, nativePurchased);
}

Impact

Loss of user funds.

Recommendations

The following best practices recommended by chainlink should be implemented to reduce the chance of working with stale prices:

  • Check the output against 0

  • Check the output to be in a min max range

  • Read the updatedAt parameter from the calls to latestRoundData() and check it against 0

  • Read the updatedAt parameter from the calls to latestRoundData() and check that it isn't older than a given maximum depending on the price feed

  • Check if the L2 Sequencer is down on chains like Arbitrum

  • Implement a way to update the eurUsd address as it could potentially be possible that the chainlink oracle will not work in the future

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.