[M-01] KittyVault::getUserVaultMeowllateralInEuros doesnt check for heartbeat or price validation
Summary
Chainlink recommends that when utilizing its price feeds, developers should always verify the staleness of the data and validate the price values. The KittyVault
contract fails to implement these checks, particularly when retrieving different prices from Chainlink price feeds, which can lead to inaccuracies.
Vulnerability Details
The functions getTotalMeowllateralInAave
and getUserVaultMeowllateralInEuros
make multiple calls to Chainlink oracles for price data but lack the necessary validations for price staleness and correctness.
function getTotalMeowllateralInAave() public view returns (uint256) {
(uint256 totalCollateralBase, , , , , ) = i_aavePool.getUserAccountData(
address(this)
);
@> (, int256 collateralToUsdPrice, , , ) = i_priceFeed.latestRoundData();
return
totalCollateralBase.mulDiv(
PRECISION,
uint256(collateralToUsdPrice) * EXTRA_DECIMALS
);
}
function getUserVaultMeowllateralInEuros(
address _user
) external view returns (uint256) {
@> (, int256 collateralToUsdPrice, , , ) = i_priceFeed.latestRoundData();
@> (, int256 euroPriceFeedAns, , , ) = i_euroPriceFeed.latestRoundData();
uint256 collateralAns = getUserMeowllateral(_user).mulDiv(
uint256(collateralToUsdPrice) * EXTRA_DECIMALS,
PRECISION
);
return
collateralAns.mulDiv(
uint256(euroPriceFeedAns) * EXTRA_DECIMALS,
PRECISION
);
}
Impact
Without proper validation, the contract may use incorrect prices, leading to potential issues such as users being incorrectly liquidated when they are not at risk.
Tools Used
Manual Review
Recommendations
Implement a heartbeat check for each price feed to ensure the data is fresh and reliable.
Validate that each retrieved price is greater than 0 to catch and handle invalid data points.
Consider adding error handling mechanisms to gracefully manage instances where price data cannot be validated.
function getUserVaultMeowllateralInEuros(address _user) external view returns (uint256) {
- (, int256 collateralToUsdPrice, , , ) = i_priceFeed.latestRoundData();
- (, int256 euroPriceFeedAns, , , ) = i_euroPriceFeed.latestRoundData();
+ (uint80 roundID, int256 collateralToUsdPrice, , uint256 updatedAt, uint80 answeredInRound) = i_priceFeed.latestRoundData();
+ require(collateralToUsdPrice > 0, "Invalid collateral price");
+ require(block.timestamp - updatedAt > HEART_BEAT , "Stale collateral price");
+ (uint80 euroRoundID, int256 euroPriceFeedAns, , uint256 euroUpdatedAt, uint80 euroAnsweredInRound) = i_euroPriceFeed.latestRoundData();
+ require(euroPriceFeedAns > 0, "Invalid euro price");
+ require(block.timestamp - euroUpdatedAt > EURO_HEART_BEAT, "Stale euro price");
uint256 collateralAns = getUserMeowllateral(_user).mulDiv(uint256(collateralToUsdPrice) * EXTRA_DECIMALS, PRECISION);
return collateralAns.mulDiv(uint256(euroPriceFeedAns) * EXTRA_DECIMALS, PRECISION);
}