The repayment process in the NFTPositionManager can sometimes be reverted
Summary
Users can supplyassets to the pools through the NFTPositionManager to earn rewards in zero tokens.
Functions like deposit, withdraw, repay, and borrow should operate normally.
However, due to an additional check, repayments might be reverted.
Vulnerability Detail
Here's the relationship between shares (s) and assets (a) in the Pool:
Share to Asset Conversion:a = [(s * I + 10^27 / 2) / 10^27] (rayMul)
function rayMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
assembly {
if iszero(or(iszero(b), iszero(gt(a, div(sub(not(0), HALF_RAY), b))))) { revert(0, 0) }
c := div(add(mul(a, b), HALF_RAY), RAY)
}
}
Asset to Share Conversion:s = [(a * 10^27 + I / 2) / I] (rayDiv)
function rayDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
assembly {
if or(iszero(b), iszero(iszero(gt(a, div(sub(not(0), div(b, 2)), RAY))))) { revert(0, 0) }
c := div(add(mul(a, RAY), div(b, 2)), b)
}
}
Numerical Example:
Suppose there is a poolP where users borrowassetsA using the NFTPositionManager.
The current borrow index of P is 2 * 10^27, and the share is 5.
The previous debt balance is as below (Line 119):
previousDebtBalance = [(s * I + 10^27 / 2) / 10 ^27] = [(5 * 2 * 10^27 + 10^27 / 2) / 10^27] = 10
If we are going to repay3assets:
The removed shares is:
[(a * 10^27 + I / 2) / I] = [(3 * 10^27 + 2 * 10^27 / 2) / (2 * 10^27)] = 2
Therefore, the remaining share is:
5 - 2 = 3
The current debt balance is as below (Line 121):
currentDebtBalance = [(s * I + 10^27 / 2) / 10 ^27] = [(3 * 2 * 10^27 + 10^27 / 2) / 10^27 = 6
Then in line 123, previousDebtBalance - currentDebtBalance would be 4 and repaid.assets is 3.
As a result, the repayment would be reverted.
This example demonstrates a `potential 1 wei mismatch` between `previousDebtBalance` and `currentDebtBalance` due to rounding in the calculations.
## Impact
This check seems to cause a `denial-of-service (DoS)` situation where `repayments` can fail due to small rounding errors.
This issue can occur with various combinations of `borrow index`, `share amounts`, and `repaid assets`.
## Code Snippet
https://github.com/sherlock-audit/2024-06-new-scope/blob/c8300e73f4d751796daad3dadbae4d11072b3d79/zerolend-one/contracts/core/pool/utils/WadRayMath.sol#L77
https://github.com/sherlock-audit/2024-06-new-scope/blob/c8300e73f4d751796daad3dadbae4d11072b3d79/zerolend-one/contracts/core/pool/utils/WadRayMath.sol#L93
https://github.com/sherlock-audit/2024-06-new-scope/blob/c8300e73f4d751796daad3dadbae4d11072b3d79/zerolend-one/contracts/core/positions/NFTPositionManagerSetters.sol#L119-L125
## Tool used
Manual Review
## Recommendation
Either remove this check or adjust it to allow a `1 wei mismatch` to prevent unnecessary reversion of `repayments`.
ether_sky
Medium
The repayment process in the NFTPositionManager can sometimes be reverted
Summary
Users can
supply
assets
to thepools
through theNFTPositionManager
to earnrewards
inzero
tokens. Functions likedeposit
,withdraw
,repay
, andborrow
should operate normally. However, due to an additional check,repayments
might be reverted.Vulnerability Detail
Here's the relationship between
shares
(s
) andassets
(a
) in thePool
:a = [(s * I + 10^27 / 2) / 10^27] (rayMul)
s = [(a * 10^27 + I / 2) / I] (rayDiv)
Numerical Example: Suppose there is a
pool
P
where usersborrow
assets
A
using theNFTPositionManager
.borrow index
ofP
is2 * 10^27
, and theshare
is5
.previous debt balance
is as below (Line 119
):previousDebtBalance = [(s * I + 10^27 / 2) / 10 ^27] = [(5 * 2 * 10^27 + 10^27 / 2) / 10^27] = 10
repay
3
assets
:shares
is:[(a * 10^27 + I / 2) / I] = [(3 * 10^27 + 2 * 10^27 / 2) / (2 * 10^27)] = 2
share
is:5 - 2 = 3
current debt balance
is as below (Line 121
):currentDebtBalance = [(s * I + 10^27 / 2) / 10 ^27] = [(3 * 2 * 10^27 + 10^27 / 2) / 10^27 = 6
Then inline 123
,previousDebtBalance - currentDebtBalance
would be4
andrepaid.assets
is3
. As a result, therepayment
would be reverted.DataTypes.SharesType memory repaid = pool.repay(params.asset, params.amount, params.tokenId, params.data);
121: uint256 currentDebtBalance = pool.getDebt(params.asset, address(this), params.tokenId);
123: if (previousDebtBalance - currentDebtBalance != repaid.assets) { revert NFTErrorsLib.BalanceMisMatch(); } }