Open hats-bug-reporter[bot] opened 9 months ago
Hello @0xmuxyz
Thanks for very long explanation for bug consideration, but at the end it comes down to mappings that you've outlined:
userBorrowShares[_nftId][_poolToken]
or as reference userMapping[_nftId][_poolToken]
when _amount of _shares are subtracted from these examples.
Have you considered that if the amount subtracted is greater than the value stored in the mapping, transaction would revert preventing this scenario to go through?
In other words, could you explain why is it necessary to put redundant require
check if the default behavior already reverts anyway preventing that scenario you are trying to explain?
I would understand your concern if the decrease is handled like so:
unchecked {
userMapping[_nftId][_poolToken] -= _amount;
}
but it is not, hence such operation is impossible to subtract more shares than user has, making that check redundant.
Hi @vm06007, thanks for quickly reviewing my submission!
Sorry, I realized my mistakes including what you pointed out. Thereby, I submitted the fixed version of this submission as another submission (named "Part 2"
).
So, could you review my another submission above?
Github username: @0xmuxyz Twitter username: -- Submission hash (on-chain): 0xc147bab03c54e837c56d6692076882146af531ef99fd92f2c3e9724d6f44f3d8 Severity: medium
Description:
Description
When a borrower would payback (repay) their ERC20 loan, the borrower would call the WiseLending#
paybackExactShares()
or WiseLending#paybackExactAmount()
. When the WiseLending#paybackExactShares()
or the WiseLending#paybackExactAmount()
would be called, the following internal functions would be called based on the following flow inside the either function like this:The flow of the internal function call: WiseLending#
paybackExactShares()
or WiseLending#paybackExactAmount()
-> WiseLending#_handlePayback()
-> MainHelper#_corePayback()
-> WiseLowLevelHelper#_decreasePositionMappingValue()
Description:
Within the WiseLending#
paybackExactAmount()
,paybackShares
would be calculated with a given_amount
via the MainHelper#calculateBorrowShares()
._amount
and thepaybackShares
-calculated.The given
_amount
of ERC20 would be transferred from the borrower's wallet (msg.sender
) to the WiseLending contract via the TransferHelper#_safeTransferFrom()
. \ https://github.com/hats-finance/Wise-Lending-0xa2ca45d6e249641e595d50d1d9c69c9e3cd22573/blob/7482c9fd1a27629e87a248c818758445fce6101a/contracts/WiseLending.sol#L1159 \ https://github.com/hats-finance/Wise-Lending-0xa2ca45d6e249641e595d50d1d9c69c9e3cd22573/blob/7482c9fd1a27629e87a248c818758445fce6101a/contracts/WiseLending.sol#L1165-L1168 \ https://github.com/hats-finance/Wise-Lending-0xa2ca45d6e249641e595d50d1d9c69c9e3cd22573/blob/7482c9fd1a27629e87a248c818758445fce6101a/contracts/WiseLending.sol#L1181-L1182 \ https://github.com/hats-finance/Wise-Lending-0xa2ca45d6e249641e595d50d1d9c69c9e3cd22573/blob/7482c9fd1a27629e87a248c818758445fce6101a/contracts/WiseLending.sol#L1185-L1189 \Within the WiseLending#
paybackExactShares()
,repaymentAmount
would be calculated with a given_shares
via the MainHelper#paybackAmount()
._shares
and therepaymentAmount
-calculated.The
repaymentAmount
-calculated of ERC20 would be transferred from the borrower's wallet (msg.sender
) to the WiseLending contract via the TransferHelper#_safeTransferFrom()
. \ https://github.com/hats-finance/Wise-Lending-0xa2ca45d6e249641e595d50d1d9c69c9e3cd22573/blob/7482c9fd1a27629e87a248c818758445fce6101a/contracts/WiseLending.sol#L1202 \ https://github.com/hats-finance/Wise-Lending-0xa2ca45d6e249641e595d50d1d9c69c9e3cd22573/blob/7482c9fd1a27629e87a248c818758445fce6101a/contracts/WiseLending.sol#L1208-L1210 \ https://github.com/hats-finance/Wise-Lending-0xa2ca45d6e249641e595d50d1d9c69c9e3cd22573/blob/7482c9fd1a27629e87a248c818758445fce6101a/contracts/WiseLending.sol#L1221 \ https://github.com/hats-finance/Wise-Lending-0xa2ca45d6e249641e595d50d1d9c69c9e3cd22573/blob/7482c9fd1a27629e87a248c818758445fce6101a/contracts/WiseLending.sol#L1225-L1229Within the MainHelper#
_corePayback()
, the WiseLowLevelHelper#_decreasePositionMappingValue()
would be called with theuserBorrowShares
storage and a given_shares
like this: \ https://github.com/hats-finance/Wise-Lending-0xa2ca45d6e249641e595d50d1d9c69c9e3cd22573/blob/7482c9fd1a27629e87a248c818758445fce6101a/contracts/MainHelper.sol#L769-L774Within the WiseLowLevelHelper#
_decreasePositionMappingValue()
, a givenamount
would be subtracted from a given_nftId
and_poolToken
of theuserMapping
(userMapping[_nftId][_poolToken]
) like this: \ https://github.com/hats-finance/Wise-Lending-0xa2ca45d6e249641e595d50d1d9c69c9e3cd22573/blob/7482c9fd1a27629e87a248c818758445fce6101a/contracts/WiseLowLevelHelper.sol#L379(NOTE:On the assumption that, the
userBorrowShares
is equal to theuserMapping
would be same (userBorrowShares == userMapping
)).When the WiseLowLevelHelper#
_decreasePositionMappingValue()
would be called via the MainHelper#_corePayback()
, a givenamount
to be paid back in the WiseLowLevelHelper#_decreasePositionMappingValue()
is supposed to be less than or equal to the maximum borrowed-shares (userMapping[_nftId][_poolToken]
).However, within the both functions (the MainHelper#
_corePayback()
and the WiseLowLevelHelper#_decreasePositionMappingValue()
), there is no validation to check whether or not a givenamount
to be paid back is supposed to be less than or equal to the maximum borrowed-shares (userBorrowShares[_nftId][_poolToken]
oruserMapping[_nftId][_poolToken]
) of the borrower.This allow a borrower to payback ERC20 loans by providing either exact payback
amount
orshares
via either the WiseLending#paybackExactShares()
or WiseLending#paybackExactAmount()
, which exceed their maximum borrowed-shares (userBorrowShares[_nftId][_poolToken]
oruserMapping[_nftId][_poolToken]
).PoC (Scenario)
This is problematic. Because, since the
_amount
or therepaymentAmount
of ERC20 to be paid back would be transferred from the borrower's wallet to the WiseLending contract via the TransferHelper#_safeTransferFrom()
when the WiseLending#paybackExactAmount()
or the WiseLending#paybackExactShares()
would be called, the excess amount of ERC20 to be paid back will be stuck inside the WiseLending contract and it will never be refunded to the borrower's wallet - if a borrower would payback ERC20 loans by providing either exact paybackamount
orshares
, which exceed their maximum borrowed-shares (userBorrowShares[_nftId][_poolToken]
oruserMapping[_nftId][_poolToken]
).As a result, the borrower would lose the excess amount of ERC20-paid back - due to that their excess amount of ERC20 to be paid back will be stuck inside the WiseLending contract and it will never be refunded to the borrower's wallet.
Recommendation
This can mitigate by either "Recommendation①" or "Recommendation②":
Recommendation①: \ Within the WiseLending#
paybackExactShares()
and the WiseLending#paybackExactAmount()
, consider a logic to refund the excess amount of ERC20 to be paid back.ref: The refund logic-implemented in the WiseLending#
paybackExactAmountETH()
seems to be a good reference.Recommendation②: \ Within the MainHelper#
_corePayback()
, consider adding a validation to check whether or not a givenamount
to be paid back is supposed to be less than or equal to the maximum borrowed-shares (userBorrowShares[_nftId][_poolToken]
) of the borrower like this:Or,
Within the WiseLowLevelHelper#
_decreasePositionMappingValue()
, consider adding a validation to check whether or not a givenamount
to be paid back is supposed to be less than or equal to the maximum borrowed-shares (userMapping[_nftId][_poolToken]
) of the borrower like this: