Reverting of the close
function due to the incorrect calculation in getHealthFactor
Description
Oasys Hub, the blockchain where Palmy Finance is deployed, only allows authorized users to deploy contracts. This imposes a limit to a user on calling multiple functions once unless one of the deployed contracts supports the specific series of calls.
The Leverager contract provides the utility functions that allow users to open or close the leveraged position on behalf of themselves. Opening a leveraged position works by repeatedly depositing an asset and borrowing the asset using the deposited asset as collateral. The other way around, closing a leveraged position, works by repeatedly repaying the debt and withdrawing the corresponding collateral.
The close
function, which closes a leveraged position, involves calculating the withdrawable amount of the asset at each iteration. During this calculation, the close
function calls the getHealthFactor
function, which calculates the health factor after the user withdraws the given amount of the given asset. The source code of the getHealthFactor
function is as follows:
function getHealthFactor(
address account,
address asset,
uint256 withdrawAmount
) public view returns (uint256 healthFactor) {
// ...
uint256 reserveUnitPrice = priceOracleGetter.getAssetPrice(asset);
// ...
uint256 amountETH;
amountETH = (withdrawAmount * reserveUnitPrice);
totalCollateralAfter = (totalCollateral > amountETH)
? totalCollateral - amountETH
: 0;
// ...
}
The amountETH
variable is supposed to represent the total price of the asset to be withdrawn and is calculated by multiplying the withdrawAmount
and the reserveUnitPrice
. However, it is incorrect, because the the unit amount for the reserveUnitPrice
is instead of .
If the decimal of the asset is not zero, the total collateral after the withdrawal will be underestimated due to this incorrect calculation. In most cases, this makes the function revert since this number is clamped to zero and used as a divisor.
Impact
A user will not be able to close a leveraged position through this contract. It is important to note that there are no other ways to close a leveraged position in a single transaction because a user is not allowed to deploy a contract that can perform this in a single transaction.
Users can manually close the position by interacting repeatedly with the lending pool contract. However, this process requires more time and exposes the user to the risk of being liquidated if they don't close the position promptly.
Recommendations
Consider fixing the calculation in the getHealthFactor
function.
Remediation
This issue has been acknowledged by Familia Labs Ltd., and a fix was implemented in commit d97128aa↗.