xanoutas/solidity-security-findings huggingface dataset:
Liquidator will brick the entire pool market, also stealing from the subsequent depositors
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseCore.sol#L460-L536
https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/MainHelper.sol#L752-L788


# Vulnerability details

## Impact

When the pool lacks sufficient funds to fully compensate the liquidator, it will reset both `totalPool` and `totalLendingShares` to 0, but will increase the `lendingShares` of the liquidator by the difference.

This enables the liquidator to retrieve it later when the pool liquidity rises once again, either by `paybacks` or `lp deposits`. Consequently, when someone deposits into the pool, the liquidator can claim the remaining portion of their liquidation reward, preventing the depositor from withdrawing their full amount. As we can observe this creates a weird scenario when every next liquidity provider tokens are “stolen” from the previous withdrawer. 

This is extremely bad for the market because unlike borrowing, where you are sure that these funds will be back into the pool either by repaying or liquidation when the liquidator that has received surplus shares withdraws them it will lead to a domino effect and the last withdrawer for this market will bear all the cost risking up to 100% of his deposits to be stolen.

When `pureCollateral` of a user isn’t enough for the liquidator to seize, `_withdrawOrAllocateSharesLiquidation` is executed:

[WiseCore.sol#L460-L536](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseCore.sol#L460-L536)

```solidity
function _withdrawOrAllocateSharesLiquidation(
    uint256 _nftId,
    uint256 _nftIdLiquidator,
    address _poolToken,
    uint256 _percentWishCollateral
)
    private
    returns (uint256)
{
 ...Code when withdrawAmount <= totalPoolToken
 

    uint256 totalPoolInShares = calculateLendingShares(
        {
            _poolToken: _poolToken,
            _amount: totalPoolToken,
            _maxSharePrice: false
        }
    );

    uint256 shareDifference = cashoutShares
        - totalPoolInShares;

    _coreWithdrawBare(
        _nftId,
        _poolToken,
        totalPoolToken,
        totalPoolInShares
    ); 
   
    _decreaseLendingShares(
        _nftId,
        _poolToken,
        shareDifference
    );

    _increasePositionLendingDeposit(
        _nftIdLiquidator,
        _poolToken,
        shareDifference
    );

    _addPositionTokenData(
        _nftIdLiquidator,
        _poolToken,
        hashMapPositionLending,
        positionLendTokenData
    );

    _removeEmptyLendingData(
        _nftId,
        _poolToken
    );
    
    //@audit after: 
    //totalPool = 0
    //totalDepositShares = 0
    //shares of liquidator = diff of cashout - total

    return totalPoolToken;
}
```

When assets of a pool are less than the amount that liquidator receives we can see that both 

- `lendingShares`
- `totalPool`

are being reset to 0.

And when borrowers payback, mappings that get increased are:

- `totalPool`
- `totalBorro
Small positions will not pose incentive to be liquidated bringing `poolMarkets` down
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseSecurity/WiseSecurity.sol#L1092-L1109
https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseLending.sol#L1553-L1581


# Vulnerability details

## Impact

There is no incentive for liquidators to liquidate small positions (even under $100), especially on Mainnet, this will result in bad debt accrued for the WiseLending system because no one will be willing to remove the position and `FEE_MANAGER` will not be able to split the bad debt between depositors.

## Proof of Concept

If we take a look at the current gas prices on Mainnet:

| Priority | Low | Average | High |
| --- | --- | --- | --- |
| Gwei | 75 | 75 | 80 |
| Price | $7 | $7 | $7,45 |

We can see the current transaction prices varying and in peak hours they can be up to 5 times more, without additional costs of liquidation logic execution.

That way if there are many borrow positions eligible for liquidation but with unsatisfactory incentives for liquidators (liquidator receives less than the cost of executing the liquidation) and they approach insolvency, receiving roughly 5.90 USD incentive for the 100 USD borrow, it will lead to big harm for the protocol since they will result in an unaccounted bad debt for the system, without a proper way to be mitigated. 

We can see in the code that there is no validation for the minimum borrowable amount, it has only to be enough to keep the position healthy. 

In fact, there is a check for the `minDepositEthValue` when depositing:

[WiseSecurity.sol#L1092-L1109](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseSecurity/WiseSecurity.sol#L1092-L1109)

```solidity
function _checkMinDepositValue(
    address _token,
    uint256 _amount
)
    private
    view
    returns (bool)
{
    if (minDepositEthValue == ONE_WEI) {
        return true;
    }

    if (_getTokensInEth(_token, _amount) < minDepositEthValue) {
        revert DepositAmountTooSmall();
    }

    return true;
}
```

But it can do nothing to prevent this malicious action to be executed. 

> Important note is that positions can become insolvent unintentionally, by borrowers leaving dust amounts of borrowed tokens when closing their positions.
> 

For the borrow flow, there is no minimum borrowable amount.

[WiseLending.sol#L1553-L1581](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseLending.sol#L1553-L1581)

```solidity
function _handleBorrowExactAmount(
    uint256 _nftId,
    address _poolToken,
    uint256 _amount,
    bool _onBehalf
)
    private
    returns (uint256)
{
    uint256 shares = calculateBorrowShares(
        {
            _poolToken: _poolToken,
            _amount: _amount,
            _maxSharePrice: true
        }
    );

    _coreBorrowTokens(
        {
            _caller: msg.sender,
            _nftId: _nftId,
            _poolToken: _poolToken,
            _amount: _amount,
            _shares: shares,
Chainlink oracles may return stale prices or may be unusable when aggregator roundId is less than 50
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseOracleHub/OracleHelper.sol#L627-L630
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseOracleHub/OracleHelper.sol#L658-L668


# Vulnerability details

## Vulnerability Details
WiseLending calibrates oracles to get a heartbeat which it uses for checking the staleness of prices returned from the oracle. 

To calibrate, it fetches between 3-50 inclusive historical prices and picks the second largest update time among those prices. It calls `_getIterationCount()` to know the number of historical prices it'll use. If the current `_latestAggregatorRoundId` is less than 50 (`MAX_ROUND_COUNT`) it uses `_latestAggregatorRoundId` else it uses 50.

**[OracleHelper.sol#L665-L667](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseOracleHub/OracleHelper.sol#L665-L667)**
```
        res = _latestAggregatorRoundId < MAX_ROUND_COUNT
            ? _latestAggregatorRoundId
            : MAX_ROUND_COUNT;
```

The issue with the snippet above is that `_latestAggregatorRoundId` will always be greater than 50 so the no. of historical prices it uses will always be 50.

It's always greater than 50 because it is fetched from the aggregator's proxy contract. The `roundId`s returned from the proxy are a combination of the current aggregator's roundId and phaseId. Check [Chainlink docs](https://docs.chain.link/data-feeds/historical-data#roundid-in-proxy) for more info. `getLatestRoundId()` returns the roundId.

**[OracleHelper.sol#L708-L715](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseOracleHub/OracleHelper.sol#L708-L715)**
```solidity
        (
            roundId
            ,
            ,
            ,
            ,
        ) = priceFeed[_tokenAddress].latestRoundData();
```


The `roundId` returned is used in the `_recalibratePreview()` function below to get previous `roundIds`. The iterationCount as we already mentioned will always be 50.


**[OracleHelper.sol#L620C1-L630C15](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseOracleHub/OracleHelper.sol#L620C1-L630C15)**
```
        uint80 i = 1;
        uint256 currentDiff;
        uint256 currentBiggest;
        uint256 currentSecondBiggest;

 ❌      while (i < iterationCount) {

            uint256 currentTimestamp = _getRoundTimestamp(
                _tokenAddress,
 ❌              latestRoundId - i
            );
```

The problem with the above call is that the argument, `latestRoundId-1` above may not have valid data for some rounds. So calls to the Chainlink oracle for those rounds will revert. 

This may occur because of the way proxy roundIds work.
 
E.g If the proxy returns 0x40000000000000010 as roundId.

The phaseId is 4 (roundId >> 64).
First depositor inflation attack in `PendlePowerFarmToken`
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/PowerFarms/PendlePowerFarmController/PendlePowerFarmController.sol#L53-L111
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol#L452-L463
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol#L465-L476


# Vulnerability details

## Impact

In certain scenarios, shares of a subsequent depositor can be heavily reduced, losing a large amount of his deposited funds, the attacker can increase the right side of the `previewMintShares` by adding rewards for compounding.

That way victim can lose 6e17 of his assets for a deposit of 1e18.

## Proof of Concept

Let’s see how a first user, can grief a subsequent deposit and reduce his shares from the desired 1:1 ratio to 1:0000000000000005.

First, he needs to choose `PowerFarmToken` with no previous deposits. 

1. He calls `depositExactAmount` with 2 wei which will also call `syncSupply` → `_updateRewards` which is a key moment of the attack, this will make it possible `PowerFarmController::exchangeRewardsForCompoundingWithIncentive` to be called when performing the donation.
2. With 2 wei user will mint 2 shares, so totalSupply = 3, underlyingLpAssetsCurrent = 3
3. Then, the victim comes and tries to deposit 1e18 of assets, but is front ran by the first user calling `PowerFarmController::exchangeRewardsForCompoundingWithIncentive` → `addCompoundRewards` with 999999999999999996 that will increase the `totalLpAssetsToDistribute`, which is added to `underlyingLpAssetsCurrent` in the `_syncSupply` function, called from the modifier before the main functions.

> The attacker does not lose his deposit of 1e18 because it is converted to `rewardTokens` and sent to him, basically making the inflation free.
> 

[PendlePowerFarmController.sol#L53-L111](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/PowerFarms/PendlePowerFarmController/PendlePowerFarmController.sol#L53-L111)

```solidity
function exchangeRewardsForCompoundingWithIncentive(
      address _pendleMarket,
      address _rewardToken,
      uint256 _rewardAmount
  ) external syncSupply(_pendleMarket) returns (uint256) {
      CompoundStruct memory childInfo = pendleChildCompoundInfo[_pendleMarket];

      uint256 index = _findIndex(childInfo.rewardTokens, _rewardToken);

      if (childInfo.reservedForCompound[index] < _rewardAmount) {
          revert NotEnoughCompound();
      }

      uint256 sendingAmount = _getAmountToSend(_pendleMarket, _getTokensInETH(_rewardToken, _rewardAmount));

      childInfo.reservedForCompound[index] -= _rewardAmount;
      pendleChildCompoundInfo[_pendleMarket] = childInfo;

      _safeTransferFrom(_pendleMarket, ms
The calls in Curve finance pools are done in an unsafe manner
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseSecurity/WiseSecurity.sol#L193-L232
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseSecurity/WiseSecurity.sol#L225-L227


# Vulnerability details

The protocol is doing external calls on curve pools as a security measures. However the calls are done in an [unsafe manner](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseSecurity/WiseSecurity.sol#L225-L227), this is important as every curve pool have different code and they are not standardized.
Multiple problems can arise form unsafe external calls threated this way:
1. Execution will fail however the transaction will not revert as the failed execution will only return a false.
2. The returned data amount is bigger then what the stack can hold and it will run in an out of gas scenario. 
## Impact
Posibile DOS or other unexpected events as transaction failing but call have succeded.

## Proof of Concept
## Tools Used
Manual Review
## Recommended Mitigation Steps
1. Wrap the call to curve pools in a YUL low level external call OR
2. Wrap the call to curve pools in a function similar to _callOptionalReturn that you are already using for tokens transfers and validate the returned data


## Assessed type

Context
Own implementaion of safeTransfer is not taking into account tokens like USDC that will not revert in case of fail transfer but will return false.
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/TransferHub/TransferHelper.sol#L13-L28
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/TransferHub/TransferHelper.sol#L34-L52


# Vulnerability details

The protocol have chosen to implement their own version of safeTransfers. The implementation is not compatible with tokens like USDT that will not revert in case of failed transfers as they are not checking the returned data of [_callOptionalReturn](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/TransferHub/CallOptionalReturn.sol#L12-L39) in [_safeTransfer](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/TransferHub/TransferHelper.sol#L13-L28) or [_safeTransferFrom](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/TransferHub/TransferHelper.sol#L34-L52). 

## Impact
USDT transfers could in reality fail but the logic will continue as they have passed.

## Proof of Concept
All the use of transfers inside the protocol represents a risk as the logic will continue normally even if the transfer have failed making the impression that tokens have been sent to the protocol/from the protocol in case of deposits or borrowing but in actual no transactions have taking place, crediting wrong/false amounts in balances.

## Tools Used
Manual Reivew

## Recommended Mitigation Steps
Check the return value of _callOptionalReturn and revert the transaction if it returns false.


## Assessed type

Token-Transfer
Withdrawing uncollateralized deposits is possible even though the position is in liquidation mode
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseSecurity/WiseSecurity.sol#L263-L265
https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseSecurity/WiseSecurity.sol#L125-L128


# Vulnerability details

## Impact
Users can withdraw uncollateralized deposits even though their position is liquidable, [as opposed to the README](https://github.com/code-423n4/2024-02-wise-lending/blob/main/README.md?plain=1#L137), if the position is in liquidation mode, users should use their uncollateralized deposits to avoid liquidation instead of removing them

## Proof of Concept
When withdrawing deposits from public pools, at the end of the tx is executed the [`WiseLending._healthStateCheck() function`](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseLending.sol#L77-L90), which depending on the value of the `powerFarmCheck` will determine if the position's collateral is enough to cover the borrows.
  - If `powerFarmCheck` is true, it will use the `bare` value of the collateral, meaning, the `collateralFactor` is not applied to the collateral's value
  - If `powerFarmCheck` is false, it will use the `weighted` value of the collateral, meaning, the `collateralFactor` is applied to the collateral's value.

When withdrawing an uncollateralized deposit, the [`WiseCore._coreWithdrawToken() function`](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseCore.sol#L44-L100) calls the [`WiseSecurity.checksWithdraw() function`](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseSecurity/WiseSecurity.sol#L237-L270) to determine the value of the `powerFarmCheck`, if the pool from where the tokens are being withdrawn is uncollateralized, the `powerFarmCheck` will be set to `true`, which will cause that the [`WiseLending._healthStateCheck() function`](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseLending.sol#L77-L90) uses the `bare` value of the full collateral to determine if the collateral can cover the existing borrows. 
- **Using the bare value of the collateral does not accurately reflect if a position is liquidable or not**, the liquidation's logic in the [`WiseSecurity.checksLiquidation() function`](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseSecurity/WiseSecurity.sol#L105-L137) uses the `weightedCollateral` instead of the `bareCollateral` to determine if the position is liquidable or not. So, this difference to determine if the position's collateral can cover the borrows when withdrawing uncollateralized deposits and when doing liquidation causes a discrepancy to allow the withdrawal of uncollateralized deposits even though the position is in liquidation mode.


For example, a user requests a withdrawal of an uncollateralized deposit in a position with the below values:
- 1.5e18 ETH of `bare collateral`
- 1.2e18 ETH of `weightedCollateral`
- 1.3e18 ETH of `borrows`
  - The with
Operations could be blocked as they are calculated based on block timestamp and on arbitrum 2 blocks can have the same timestamp
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L225-L227
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L234-L237
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L239-L242


# Vulnerability details

[In Arbitrum, block numbers and timestamps are reliable in long term but not reliable in short term](https://docs.arbitrum.io/for-devs/concepts/differences-between-arbitrum-ethereum/block-numbers-and-time#block-timestamps-arbitrum-vs-ethereum), one invariant of the block timestamp (In Arbitrum ) is that is must always be greater or 'equal' with the previous block timestamp, taking that into consideration. If the max share price was already hit by the previous block it will not change in the next block if it will have the same timestamp( as it is calculated based on timestamp) so no new operations will be possible in that block.

## Impact
If 2 or more consecutive blocks will have the same timestamp and the max share price was already hit, the deposits and borrow operations will fail. 

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

## Tools Used
Manual Review

## Recommended Mitigation Steps
When calling the function _compareSharePrices, save the last block number details, and always compare the block numbers timestamp, if 2 different block numbers have the same timestamp apply a different logic when calculating currentSharePriceMax 


## Assessed type

Context
Borrower can partially pay back and leave in their position a small borrowing amount/share leading to the protocol's bad debt
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L1088-L1155
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L1161-L1198
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L1204-L1238


# Vulnerability details

## Impact

The [`paybackExactAmountETH()`](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L1088-L1155), [`paybackExactAmount()`](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L1161-L1198), and [`paybackExactShares()`](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L1204-L1238) allow borrowers to pay back their borrowing positions.

However, those functions do not check the minimum leftover borrowing amount/share. A rogue borrower can partially pay back their position and leave a small borrowing amount/share in the position. One possible objective is to disincentivize a liquidator from liquidating their position.

Since the rogue borrower's position is too small for liquidators to gain liquidation profit compared to the liquidation cost (gas), the position will not be liquidated even if it is liquidatable. This eventually leaves bad debt to the protocol.

## Proof of Concept

> This section will refer to the `paybackExactAmountETH()` only for brevity's sake. The `paybackExactAmount()` and `paybackExactShares()` should apply the same PoC concept.

As you can see, the [paybackExactAmountETH()](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L1088-L1155) (and its dependent functions) does not check the minimum leftover borrowing amount/share.

Hence, a borrower can partially pay back and leave any borrowing amount/share in their position at will.

```solidity
// https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseLending.sol
function paybackExactAmountETH(
    uint256 _nftId
)
    external
    payable
    syncPool(WETH_ADDRESS)
    returns (uint256) //@audit -- No minimum leftover borrowing amount/share check
{
    uint256 maxBorrowShares = userBorrowShares[_nftId][WETH_ADDRESS];

    _validateNonZero(
        maxBorrowShares
    );

    uint256 maxPaybackAmount = paybackAmount(
        WETH_ADDRESS,
        maxBorrowShares
    );

    uint256 paybackShares = calculateBorrowShares(
        {
            _poolToken: WETH_ADDRESS,
            _amount: msg.value,
            _maxSharePrice: false
        }
    );

    _validateNonZero(
        paybackShares
    );

    uint256 refundAmount;
    uint256 requiredAmount = msg.value;

    if (msg.value > maxPaybackAmount) {

        unchecked {