Open sherlock-admin4 opened 1 month ago
shouldn't this be high severity because the only way to retrieve the stuck funds would be for each individual user to somehow atomically update the interest rate before repayment?
Escalate
Final time to use this
Escalate
Final time to use this
You've created a valid escalation!
To remove the escalation from consideration: Delete your comment.
You may delete or edit your escalation comment anytime before the 48-hour escalation window closes. After that, the escalation becomes final.
This is the High severity rule:
Definite loss of funds without (extensive) limitations of external conditions. The loss of the affected party must exceed 1%.
Medium:
Causes a loss of funds but requires certain external conditions or specific states, or a loss is highly constrained. The loss of the affected party must exceed 0.01% and 10 USD. Breaks core contract functionality, rendering the contract useless or leading to loss of funds of the affected party larger than 0.01% and 10 USD.
We can see that in this issue, we have no loss of funds without any constrains.
The main impact is that the repay()
functionality may not work in certain circumstances, and more matches the rule for Medium severity. : Breaks core contract functionality, rendering the contract useless or leading to loss of funds of the affected party
.
Planning to reject the escalation and leave the issue as is.
@cvetanovv
The reason why it's high severity is that the user will not be able to withdraw a certain amount of collateral, since they cant repay.
Lets say they deposit $100 and borrow $80. (LTV is 80%)
Now they cant repay the $80, so their $100 is stuck forever. So they effectively lost $20.
@0xjuaan How it's stuck I don't understand ? It will revert after if statement.
@DemoreXTess repayment reverts, so they cant withdraw their collateral (they need to repay debt in order to withdraw collateral), so they lose funds since collateral value > debt value
I believe this issue is low severity.
The user can call pool.forceUpdateReserve
before repay
and repayETH
to update the borrowIndex
, and then the repayment will not revert. If the user fails to do so, then it is a user mistake.
As I wrote in my previous comment repay()
and repayETH()
do not work as they should. This is the main impact and because of this, I classify this issue as Medium severity. We have broken functionality.
My decision to reject the escalation remains.
Result: Medium Has duplicates
hyh
High
NFTPositionManager's
repay()
andrepayETH()
are unavailable unless preceded atomically by an accounting updating operationSummary
The check in
_repay()
cannot be satisfied if pool state was not already updated by some other operation in the same block. This makes any standalonerepay()
andrepayETH()
calls revert, i.e. core repaying functionality can frequently be unavailable for end users since it's not packed with anything by default in production usageRoot Cause
Pool state wasn't updated before
previousDebtBalance
was set:NFTPositionManager.sol#L115-L128
NFTPositionManagerSetters.sol#L105-L121
PoolGetters.sol#L94-L97
But it was updated in
pool.repay()
before repayment workflow altered the state:BorrowLogic.sol#L117-L125
ReserveLogic.sol#L87-L95
ReserveLogic.sol#L220-L238
This way the
previousDebtBalance - currentDebtBalance
consists of state change due to the passage of time since last update and state change due to repayment:NFTPositionManagerSetters.sol#L105-L125
previousDebtBalance
can be stale and, in general, it ispreviousDebtBalance - currentDebtBalance = (actualPreviousDebtBalance - currentDebtBalance) - (actualPreviousDebtBalance - previousDebtBalance) = repaid.assets - {debt growth due to passage of time since last update} < repaid.assets
, whereactualPreviousDebtBalance
ispool.getDebt()
result afterreserve.updateState()
, but before repaymentInternal pre-conditions
Interest rate and debt are positive, so there is some interest accrual happens over time. This is normal (going concern) state of any pool
External pre-conditions
No other state updating operations were run since the last block
Attack Path
No direct attack needed in this case, a protocol malfunction causes loss to some users
Impact
Core system functionality,
repay()
andrepayETH()
, are unavailable whenever aren't grouped with other state updating calls, which is most of the times in terms of the typical end user interactions. Since the operation is time sensitive and is typically run by end users directly, this means that there is a substantial probability that unavailability in this case leads to losses, e.g. a material share of NFTPositionManager users cannot repay in time and end up being liquidated as a direct consequence of the issue (i.e. there are other ways to meet the risk, but time and investigational effort are needed, while liquidations will not wait).Overall probability is medium: interest accrues almost always and most operations are stand alone (cumulatively high probability) and repay is frequently enough called close to liquidation (medium probability). Overall impact is high: loss is deterministic on liquidation, is equal to liquidation penalty and can be substantial in absolute terms for big positions. The overall severity is high
PoC
A user wants and can repay the debt that is about to be liquidated, but all the repayment transactions revert, being done straightforwardly at a stand alone basis, meanwhile the position is liquidated, bearing the corresponding penalty as net loss
Mitigation
Consider adding direct reserve update before reading from the state, e.g.:
NFTPositionManagerSetters.sol#L119