The Standard

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

The `LiquidationPool::distributeAssets()` Function does not check stale price which will result in wrong price calculation.

Summary

The distributeAssets() function is responsible to distribute assets rewards among the stakers when ever a vault is liquidated.This function rely on chainLink Price oracle to get The latest price from chainlink oracle contract, However it does not check for stale prices which will result in wrong price calculation for assets and staker position will be decreased by wrong value.

Vulnerability Details

Code
function distributeAssets(ILiquidationPoolManager.Asset[] memory _assets, uint256 _collateralRate, uint256 _hundredPC) external payable {
consolidatePendingStakes();
// @audit : check for stale values
@> (,int256 priceEurUsd,,,) = Chainlink.AggregatorV3Interface(eurUsd).latestRoundData();
uint256 stakeTotal = getStakeTotal();
uint256 burnEuros;
uint256 nativePurchased;
// console.log("holders.length" , holders.length);
for (uint256 j = 0; j < holders.length; j++) {
Position memory _position = positions[holders[j]];
uint256 _positionStake = stake(_position);
// console.log("_positionStake" , _positionStake);
if (_positionStake > 0) {
for (uint256 i = 0; i < _assets.length; i++) {
ILiquidationPoolManager.Asset memory asset = _assets[i];
// console.log("asset.token.addr" , asset.token.addr);
if (asset.amount > 0) {
@> (,int256 assetPriceUsd,,,) = Chainlink.AggregatorV3Interface(asset.token.clAddr).latestRoundData();
uint256 _portion = asset.amount * _positionStake / stakeTotal;
// @audit : this will not allow to add token with more than 18 decimal asset.token.dec
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;
// console.log("nativePurchased" , nativePurchased);
} else {
// console.log("asset.token.addr" , asset.token.addr);
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

The staker position in EUROs will be effected by wrong values. for simplicity i am going to ignore the decimals.

  1. Consider the current price of token and assets in USD is 1$. and the staking position of user is 10 TST, total stake is 20 TST , assets amount is 10, and _collateralRate and _hundredPC = 1.

  2. The equation to receive the Reward is => uint256 _portion = asset.amount * _positionStake / stakeTotal; putting the above mentioned values in this equation will return 5, so _portion = 5.

  3. oracle return the stale price of token as 0.8$.

  4. The equation to find the costinEUROs => uint256 costInEuros = _portion * 10 ** (18 - asset.token.dec) * uint256(assetPriceUsd) / uint256(priceEurUsd)
    * _hundredPC / _collateralRate;
    The user position will be decreased by 3.773584905660377 in EUROs but it was suppose to be decreased by 4.716981132075471. The difference is directly proportional with the _portion value.

Tools Used

Manual Review

Recommendations

Interoduces some Time Period and allow oracle perice only in that time frame.

function distributeAssets(ILiquidationPoolManager.Asset[] memory _assets, uint256 _collateralRate, uint256 _hundredPC) external payable {
....
consolidatePendingStakes();
// @audit : check for stale values
- (,int256 priceEurUsd,,,) = Chainlink.AggregatorV3Interface(eurUsd).latestRoundData();
+ (,int256 priceEurUsd, uint256 timeStamp,,) = Chainlink.AggregatorV3Interface(eurUsd).latestRoundData();
uint256 stakeTotal = getStakeTotal();
+ require(block.timestamp - PERIOD >= updatedAt , "Price is stale");
.....
if (asset.amount > 0) {
- (,int256 assetPriceUsd,,,) = Chainlink.AggregatorV3Interface(asset.token.clAddr).latestRoundData();
+ (,int256 assetPriceUsd,uint256 timeStamp,,) = Chainlink.AggregatorV3Interface(asset.token.clAddr).latestRoundData();
uint256 _portion = asset.amount * _positionStake / stakeTotal;
+ require(block.timestamp - PERIOD >= updatedAt , "Price is stale");
Updates

Lead Judging Commences

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

Chainlink-price

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.