Summary
The calculation of value doesn't consider the scaling of the fixed number which leads to return an incorrect result.
Vulnerability Details
The collateralValue relies on the price feed from the Chainlink Functions, and its value is a fixed number scaled by 1e2. See "NFT Pricing Mechanism" below. The userTotalDebt, on the other hand, is a fixed number scaled by 1e18.
The check in L344 compares collateralValue between userTotalDebt without considering the scaling of the fixed number.
contract LendingPool is ILendingPool, Ownable, ReentrancyGuard, ERC721Holder, Pausable {
function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
uint256 collateralValue = getUserCollateralValue(msg.sender);
uint256 userTotalDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex) + amount;
if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) {
revert NotEnoughCollateralToBorrow();
}
}
function getUserCollateralValue(address userAddress) public view returns (uint256) {
UserData storage user = userData[userAddress];
uint256 totalValue = 0;
for (uint256 i = 0; i < user.nftTokenIds.length; i++) {
uint256 tokenId = user.nftTokenIds[i];
uint256 price = getNFTPrice(tokenId);
totalValue += price;
}
return totalValue;
}
}
NFT Pricing Mechanism
According to Chainlink Functions' script, the price is a fixed number scaled by 1e2.
return Functions.encodeUint256(housePrice * 100)
The oracle fulfills the request and sets the price of NFT.
contract RAACHousePriceOracle is BaseChainlinkFunctionsOracle {
RAACHousePrices public housePrices;
uint256 public lastHouseId;
constructor(
address router,
bytes32 _donId,
address housePricesAddress
) BaseChainlinkFunctionsOracle(router, _donId) {
require(housePricesAddress != address(0), "HousePrices address must be set");
housePrices = RAACHousePrices(housePricesAddress);
}
function _beforeFulfill(string[] calldata args) internal override {
lastHouseId = args[0].stringToUint();
}
function _processResponse(bytes memory response) internal override {
uint256 price = abi.decode(response, (uint256));
housePrices.setHousePrice(lastHouseId, price);
emit HousePriceUpdated(lastHouseId, price);
}
}
contract RAACHousePrices is Ownable {
mapping(uint256 => uint256) public tokenToHousePrice;
address public oracle;
uint256 public lastUpdateTimestamp;
function getLatestPrice(
uint256 _tokenId
) external view returns (uint256, uint256) {
return (tokenToHousePrice[_tokenId], lastUpdateTimestamp);
}
function setHousePrice(
uint256 _tokenId,
uint256 _amount
) external onlyOracle {
tokenToHousePrice[_tokenId] = _amount;
lastUpdateTimestamp = block.timestamp;
}
}
Impact
If users already holds a small amount of debt, they will be unable to borrow any additional assets.
Tools Used
Manual.
Recommendations
Before comparing fixed numbers, it is necessary to ensure that their scaling is consistent.