xanoutas/solidity-security-findings huggingface dataset:
_payDebt() Lack of slippage protection
Audit: 2024-05-bakerfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-bakerfi/blob/59b1f70cbf170871f9604e73e7fe70b70981ab43/contracts/core/strategies/StrategyLeverage.sol#L545


# Vulnerability details

## Vulnerability details
in `_payDebt()` will perform the following steps
1. calculate how much `amountIn` is needed to pay back `debtAmount + fee` by using the `quoteExactOutputSingle()` method
2. call _withdraw(amountIn) to retrieve `ierc20A`.
3. call _swap(maxAmountIn = amountIn) to get `debtAmount + fee`.

```solidity
      function _payDebt(uint256 debtAmount, uint256 fee) internal {
        _repay(wETHA(), debtAmount);
        // Get a Quote to know how much collateral i require to pay debt
        (uint256 amountIn, , , ) = uniQuoter().quoteExactOutputSingle(
@>          IQuoterV2.QuoteExactOutputSingleParams(ierc20A(), wETHA(), debtAmount + fee, 500, 0)
        );    
        
@>      _withdraw(ierc20A(), amountIn, address(this) );

        uint256 output = _swap(
            ISwapHandler.SwapParams(
                ierc20A(),
                wETHA(),
                ISwapHandler.SwapType.EXACT_OUTPUT,
@>              amountIn,
                debtAmount + fee,
                _swapFeeTier,
                bytes("")
            )
        );
        // When there are leftovers from the swap, deposit then back
        uint256 wethLefts = output > (debtAmount + fee) ? output - (debtAmount + fee) : 0;
        if (wethLefts > 0) {
            _supply(wETHA(), wethLefts);
        }
        emit StrategyUndeploy(msg.sender, debtAmount);
    }
```

The first step to get `amountIn` uses the real time price, then in the third step the `amountIn` is used as the `Max Amount In`.

It's all real-time pricing, and there is no slippage protection

## Impact

`vault.rebalance()->strategy.harvest() ->_adjustDebt()->_payDebt()` Sandwich attack to manipulate prices

## Recommended Mitigation

Adding slippage protection: range calculation via `TWAP` prices



## Assessed type

Context
ETHOracle.getLatestPrice need to convert to 18 decimals
Audit: 2024-05-bakerfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-bakerfi/blob/59b1f70cbf170871f9604e73e7fe70b70981ab43/contracts/oracles/EthOracle.sol#L30


# Vulnerability details

## Vulnerability details
in `ETHOracle.sol` 
`getPrecision()` is defined as `10 ** 18`, but the actual oracle used is 8-decimals
https://data.chain.link/feeds/arbitrum/mainnet/eth-usd
this data feeds 's decimals` is 8
```solidity
/**
 *  ETH/USD Oracle using chainlink data feeds
 * 
 *  For more information about the feed go to 
@> *  https://data.chain.link/feeds/arbitrum/mainnet/eth-usd
 * 
 **/
contract ETHOracle is IOracle {
    IChainlinkAggregator private immutable _ethPriceFeed;
@>  uint256 private constant _PRECISION = 10 ** 18;
....
    function getLatestPrice() public view override returns (IOracle.Price memory price) {
        (, int256 answer, uint256 startedAt, uint256 updatedAt,) = _ethPriceFeed.latestRoundData();
        if ( answer<= 0 ) revert InvalidPriceFromOracle();        
        if ( startedAt ==0 || updatedAt == 0 ) revert InvalidPriceUpdatedAt();    

@>      price.price = uint256(answer);  //@audit 8 decimals
        price.lastUpdate = updatedAt;
    }
```

`PythOracle` is used to get prices for other tokens, also `getPrecision() == 18`

```solidity
contract PythOracle is IOracle {
...
    uint256 private constant _PRECISION = 18;

    function _getPriceInternal(uint256 age) private view returns (IOracle.Price memory outPrice) {
        PythStructs.Price memory price = age == 0 ? 
            _pyth.getPriceUnsafe(_priceID): 
            _pyth.getPriceNoOlderThan(_priceID, age);

        if (price.expo >= 0) {
            outPrice.price =
                uint64(price.price) *
                uint256(10 ** (_PRECISION + uint32(price.expo)));
        } else {
            outPrice.price =
                uint64(price.price) *
                uint256(10 ** (_PRECISION - uint32(-price.expo)));
        }
        outPrice.lastUpdate = price.publishTime;
    }
```

Since a different precision is used, then the calculation of `totalCollateralInEth` at `StrategyLeverage` will be wrong.

```solidity
    function _getPosition(
        uint256 priceMaxAge
    ) internal view returns (uint256 totalCollateralInEth, uint256 totalDebtInEth) {
...
        totalCollateralInEth = 0;
        totalDebtInEth = 0;

        (uint256 collateralBalance,  uint256 debtBalance ) = _getMMPosition();
    
        if (collateralBalance != 0) {            
            IOracle.Price memory ethPrice = priceMaxAge == 0 ?
                _ethUSDOracle.getLatestPrice():
                _ethUSDOracle.getSafeLatestPrice(priceMaxAge);
            IOracle.Price memory collateralPrice = priceMaxAge == 0 ? 
                _collateralOracle.getLatestPrice():
                _collateralOracle.getSafeLatestPrice(priceMaxAge);
            if (
                !(priceMaxAge == 0 ||
                    (priceMaxAge > 0 && (ethPrice.lastUpdate >= (block.timestamp - priceMaxAge))) ||
First depositor can abuse exchange rate to steal funds from later depositors
Audit: 2024-05-bakerfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-bakerfi/blob/59b1f70cbf170871f9604e73e7fe70b70981ab43/contracts/core/Vault.sol#L280


# Vulnerability details

## Vulnerability details
Classic issue with vaults. 
First depositor can deposit a single wei then donate to the vault to greatly inflate share ratio. 
Due to truncation when converting to shares this can be used to steal funds from later depositors.
```solidity
    function deposit(
        address receiver
    )
...
    function deposit(
        address receiver
    )
        external
        payable
        override
        nonReentrant
        whenNotPaused
        onlyWhiteListed
        returns (uint256 shares)
    {
...
        Rebase memory total = Rebase(_totalAssets(maxPriceAge), totalSupply());
...
        uint256 amount = abi.decode(result, (uint256));
@>       shares = total.toBase(amount, false);
        _mint(receiver, shares);
```
 total.toBase formula： `amount * totalShares / totalAsset`

`_totalAssets` donation through strategy, like `AAVE3.supply(onBehalfOf=valult)`
```solidity
    function _totalAssets(uint256 priceMaxAge) private view returns (uint256 amount) {
        amount = _strategy.deployed(priceMaxAge);
    }
```
Consider the following scenario.
Alice wants to deposit 2M * 1e6 USDC to vault.
Bob observes Alice's transaction, frontruns to deposit 1 wei USDC to mint 1 wei share, and donation 1 M * 1e6 USDC to the vault.
Alice's transaction is executed, since _totalAsset = 1M * 1e6 + 1 and totalSupply = 1, Alice receives 2M * 1e6 * 1 / (1M * 1e6 + 1) = 1 share.
The vault now has 3M*1e6 +1 assets and distributed 2 shares. Bob profits 0.5 M and Alice loses 0.5 M USDC.

## Impact

First depositor can abuse exchange rate to steal funds from later depositors

## Recommended Mitigation

Either during creation of the vault or for first depositor, lock a small amount of the deposit to avoid this.




## Assessed type

Context
MEV bots can sandwich withdrawals from a Vault to extract funds from user's withdrawals
Audit: 2024-05-bakerfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/strategies/StrategyLeverage.sol#L612
https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/strategies/StrategyAAVEv3WSTETH.sol#L96
https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/hooks/UseSwapper.sol#L72


# Vulnerability details

## Impact
User's funds are at risk because the swaps don't enforce a correct slippage to prevent MEV manipulation.

## Proof of Concept
When withdrawing from a Vault, the Vault will burn shares from the withdrawer and will send him native ETH.

The withdrawal process looks like this:
1. The Vault computes the amount of ETH to withdraw for the specified amount of shares to be burnt
2. The Vault calls the [`StrategyLeverage.undeploy() function`](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/strategies/StrategyLeverage.sol#L327-L331) to proceed with the withdrawal of the corresponding amount of collateral from Aave.
3. The Strategy computes the delta of the Collateral and Debt expressed in their ETH value that is required to accommodate the withdrawal in Aave
4. The Strategy takes a flashloan in Balancer to borrow the deltaDebtInETH.
5. Using the flashloaned WETH, the strategy repays WETH debt in Aave
6. The Strategy withdraws (if enough deposits) the `deltaCollateralInETH` amount of collateral from Aave.
7. All the withdrawn collateral is swapped for WETH using a Uniswap.
8. The Strategy unwraps the difference between all the received WETH from the previous step and all the debt owed to balancer (flashloned amount + fees)
9. The native received from unwrapping WETH in the previous step is then sent to the Vault
10. Finally, the received native ETH from the Strategy is forwarded to the withdrawer.

The problem that allows MEV bots to steal from users is located in step 8.

When requesting the swap to the UniRouter to swap the withdrawn collateral for WETH, the Strategy doesn't compute a minimum amount of tokensOut to receive, it simply sets the `amountOutMinimum` parameter as 0.
- Note, the same problem applies for the [`StrategyAAVEv3WSTETH._convertToWETH() function`](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/strategies/StrategyAAVEv3WSTETH.sol#L87-L101), but, since the base and derived functions does a swap of Collateral for WETH, I'm using the base function for this report.

```
> StrategyLeverage.sol

function _convertToWETH(uint256 amount) internal virtual returns (uint256) {
 // 1.Swap cbETH -> WETH/wstETH/rETH
 return
 //@audit-info => Swaps a collateral for WETH
 _swap(
 ISwapHandler.SwapParams(
 ierc20A(), // Asset In
 wETHA(), // Asset Out

 //@audit-info => swap of type EXACT_INPUT
 ISwapHandler.SwapType.EXACT_INPUT, // Swap Mode

 //@audit-info => Swapp all the withdrawn collateral
 amount, // Amount In

 //@audit-info => amountOut is set as 0
 0, // Amount Out

 _swapFeeTier, // Fee Pair Tier
 bytes("") // User Payload
 )
 );
}

>
Vault is vulnerable to first depositor inflation attack.
Audit: 2024-05-bakerfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/Vault.sol#L190-L227


# Vulnerability details

## Impact
First depositor can manipulate the price of the shares at will, forcing new depositors to deposit more ETH for the same amount of shares that the first depositor paid

## Proof of Concept
Before diving into the details of how this attack is performed, let's understand how the Vault determines the amount of shares to mint for a deposited amount of ETH.

1. When doing a deposit, [the Vault creates a new variable of `Rebase` type by passing the resultant values of calling `_totalAssets()` and `totalSupply()` functions](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/Vault.sol#L203). By inpsecting the `Rebase` struct, we know that the `totalAssets()` will be the `elastic` portion, and the `totalSupply()` will be the `base`. Or in other words:
- `assets` are elastic.
- `shares` are the base

2. Now, let's see what values are returned on each of the two functions that are called when the `total` variable of `Rebase` type is created.
- [`_totalAssets()`](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/Vault.sol#L289-L291) represents the amount of assets owned by the strategy. By `own` it means the difference between the collateral value in ETH and all the WETH debt. For example, if the total collateral value in ETH is 100 ETH, and the total WETH debt is 70 WETH, then, the `totalAssets` would return 30 ETH. See below the exact code that is used to calculate the totalAssets:
```
> Vault.sol

function _totalAssets(uint256 priceMaxAge) private view returns (uint256 amount) {
    //@audit-info => totalAssets is difference between totalCollateralInETh - totalDebtInEth owned by the Strategy!
    amount = _strategy.deployed(priceMaxAge);
}

> StrategyLeverage.sol
function deployed(uint256 priceMaxAge) public view returns (uint256 totalOwnedAssets) {

    //@audit-info => totalCollateralInEth is the value of the aTokenCollateral owned by the Strategy worth in ETH
    //@audit-info => totalDebtInETH is the WETH debt in Aave that was taken to repay the flashloans used for leverage!
    (uint256 totalCollateralInEth, uint256 totalDebtInEth) = _getPosition(priceMaxAge);

    //@audit-info => The returned value from the `deployed()` is the difference between totalCollateralInETh -totalDebtInEth
    totalOwnedAssets = totalCollateralInEth > totalDebtInEth
        ? (totalCollateralInEth - totalDebtInEth)
        : 0;
}

function _getPosition(
    uint256 priceMaxAge
) internal view returns (uint256 totalCollateralInEth, uint256 totalDebtInEth) {
    totalCollateralInEth = 0;
    totalDebtInEth = 0;

    //@audit-info => debtBalance is the amount of WETH DebtToken owned by the Strategy contract!
    //@audit-info => collateralBalance is the amount of Collateral aToken owned by the Strategy contract
    (uint256 collateralBalance,  uint256 debtBalance ) = _getMMPosition();

    if (
_payDebt()  quoteExactOutputSingle should use _swapFeeTier
Audit: 2024-05-bakerfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-bakerfi/blob/59b1f70cbf170871f9604e73e7fe70b70981ab43/contracts/core/strategies/StrategyLeverage.sol#L546


# Vulnerability details

## Vulnerability details
in `_payDebt()`
We need to calculate `amountIn` first.
```solidity
     function _payDebt(uint256 debtAmount, uint256 fee) internal {
        _repay(wETHA(), debtAmount);
        // Get a Quote to know how much collateral i require to pay debt
        (uint256 amountIn, , , ) = uniQuoter().quoteExactOutputSingle(
@>          IQuoterV2.QuoteExactOutputSingleParams(ierc20A(), wETHA(), debtAmount + fee, 500, 0)
        );    
        
        _withdraw(ierc20A(), amountIn, address(this) );

        uint256 output = _swap(
            ISwapHandler.SwapParams(
                ierc20A(),
                wETHA(),
                ISwapHandler.SwapType.EXACT_OUTPUT,
                amountIn,
                debtAmount + fee,
@>              _swapFeeTier,
                bytes("")
            )
        );
        // When there are leftovers from the swap, deposit then back
        uint256 wethLefts = output > (debtAmount + fee) ? output - (debtAmount + fee) : 0;
        if (wethLefts > 0) {
            _supply(wETHA(), wethLefts);
        }
        emit StrategyUndeploy(msg.sender, debtAmount);
    }
```
In the above method, `QuoteExactOutputSingleParams.fee` is hardcoded to a fixed value of `500`
whereas it should correctly use `_swapFeeTier`.

## Impact

Using the incorrect `FeeTier` could lead to inaccuracies in `amountIn`, resulting in a failed swap.

## Recommended Mitigation

```diff
      function _payDebt(uint256 debtAmount, uint256 fee) internal {
        _repay(wETHA(), debtAmount);
        // Get a Quote to know how much collateral i require to pay debt
        (uint256 amountIn, , , ) = uniQuoter().quoteExactOutputSingle(
-           IQuoterV2.QuoteExactOutputSingleParams(ierc20A(), wETHA(), debtAmount + fee, 500, 0)
+           IQuoterV2.QuoteExactOutputSingleParams(ierc20A(), wETHA(), debtAmount + fee, _swapFeeTier, 0)
        );    
        
        _withdraw(ierc20A(), amountIn, address(this) );
```


## Assessed type

Context
_supplyAndBorrow() should not call setUserUseReserveAsCollateral(true) every time
Audit: 2024-05-bakerfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-bakerfi/blob/59b1f70cbf170871f9604e73e7fe70b70981ab43/contracts/core/strategies/StrategyAAVEv3.sol#L106


# Vulnerability details

## Vulnerability details
when user call `deposit()` protocol will call `_supplyAndBorrow()`
```solidity
    function _supplyAndBorrow(
        address assetIn,
        uint256 amountIn,
        address assetOut,
        uint256 borrowOut
    ) internal override virtual{
        _supply(assetIn, amount);
@>      aaveV3().setUserUseReserveAsCollateral(assetIn, true);
        aaveV3().borrow(assetOut, borrowOut, 2, 0, address(this));
    }

```
Each time the method is executed, `aaveV3().setUserUseReserveAsCollateral(true)` is called once. 
Its primary purpose is to set `assetIn` as collateral. 
However, if `assetIn` is already collateral, calling this method again will result in a revert.

https://github.com/aave/aave-protocol/blob/4b4545fb583fd4f400507b10f3c3114f45b8a037/contracts/lendingpool/LendingPool.sol#L782
```solidity
    function setUserUseReserveAsCollateral(address _reserve, bool _useAsCollateral)
        external
        nonReentrant
        onlyActiveReserve(_reserve)
        onlyUnfreezedReserve(_reserve)
    {
        uint256 underlyingBalance = core.getUserUnderlyingAssetBalance(_reserve, msg.sender);

        require(underlyingBalance > 0, "User does not have any liquidity deposited");

        require(
@>          dataProvider.balanceDecreaseAllowed(_reserve, msg.sender, underlyingBalance),
@>          "User deposit is already being used as collateral"
        );

        core.setUserUseReserveAsCollateral(_reserve, msg.sender, _useAsCollateral);

        if (_useAsCollateral) {
            emit ReserveUsedAsCollateralEnabled(_reserve, msg.sender);
        } else {
            emit ReserveUsedAsCollateralDisabled(_reserve, msg.sender);
        }
    }
```


## Impact

`deposit()` can only be executed once, subsequent execute will fail.

## Recommended Mitigation
when `StrategyAAVEv3.initialize()` call ` aaveV3().setUserUseReserveAsCollateral(true);` once
`_supplyAndBorrow ()` don't call 



## Assessed type

Context
Multiple swap lack slippage protection.
Audit: 2024-05-bakerfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-bakerfi/blob/59b1f70cbf170871f9604e73e7fe70b70981ab43/contracts/core/hooks/UseSwapper.sol#L72


# Vulnerability details

## Vulnerability details
The current protocol requires swapping tokens in multiple places, such as `weth -> ierc20A` or `ierc20A -> weth`. 
Primarily, these swaps are executed using the `_swap()` method.
```solidity
    function _swap(
        ISwapHandler.SwapParams memory params
    ) internal override returns (uint256 amountOut) {
        if (params.underlyingIn == address(0)) revert InvalidInputToken();
        if (params.underlyingOut == address(0)) revert InvalidOutputToken();
        uint24 fee = params.feeTier;
        if (fee == 0) revert InvalidFeeTier();

        // Exact Input
        if (params.mode == ISwapHandler.SwapType.EXACT_INPUT) {
            amountOut = _uniRouter.exactInputSingle(
                IV3SwapRouter.ExactInputSingleParams({
                    tokenIn: params.underlyingIn,
                    tokenOut: params.underlyingOut,
                    amountIn: params.amountIn,
@>                  amountOutMinimum: 0,   //@audit miss set params.amountOut
                    fee: fee,
                    recipient: address(this),
                    sqrtPriceLimitX96: 0
                })
            );
            if (amountOut == 0) {
                revert SwapFailed();
            }
            emit Swap(params.underlyingIn, params.underlyingOut, params.amountIn, amountOut);
            // Exact Output
        } else if (params.mode == ISwapHandler.SwapType.EXACT_OUTPUT) {
            uint256 amountIn = _uniRouter.exactOutputSingle(
                IV3SwapRouter.ExactOutputSingleParams({
                    tokenIn: params.underlyingIn,
                    tokenOut: params.underlyingOut,
                    fee: fee,
                    recipient: address(this),
                    amountOut: params.amountOut,
                    amountInMaximum: params.amountIn,
                    sqrtPriceLimitX96: 0
                })
            );
            if (amountIn < params.amountIn) {
                IERC20(params.underlyingIn).safeTransfer(address(this), params.amountIn - amountIn);
            }
            emit Swap(params.underlyingIn, params.underlyingOut, amountIn, params.amountOut);
            amountOut = params.amountOut;
        }
    }
```

This method does not set `amountOutMinimum`.

and when call same miss set `Amount Out `

```solidity
abstract contract StrategyLeverage is
    function _convertFromWETH(uint256 amount) internal virtual returns (uint256) {
        // 1. Swap WETH -> cbETH/wstETH/rETH
        return
            _swap(
                ISwapHandler.SwapParams(
                    wETHA(), // Asset In
                    ierc20A(), // Asset Out
                    ISwapHandler.SwapType.EXACT_INPUT, // Swap Mode
                    amount, // Amount In
                    //@audit miss slippage protection
@>
No slippage protection during the uniswap swap
Audit: 2024-05-bakerfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/hooks/UseSwapper.sol#L72


# Vulnerability details

## Proof of Concept
UseSwapper hook provides `_swap` function that allows exactIn and exactOut swaps in uniswap. When `exactIn` type is provided, then [function set 0 as `amountOutMinimum`](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/hooks/UseSwapper.sol#L72), which opens a window for sandwiching transaction to make profit.

`ISwapHandler.SwapType.EXACT_INPUT` type is used by protocol [inside `_convertFromWETH` and `_convertToWETH` functions](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/strategies/StrategyLeverage.sol#L579-L617). In both cases it's possible to steal some amount of funds.

This is how i see typical attack. User wants to withdraw his shares, so flashloan is taken to repay some debt, then collateral is withdrawn to swap it to weth. Attacker will sandwich the call to steal some amount from user, but will leave enough funds to cover flashloanloan + fees, so call doesn't revert.

## Impact
User's funds can be stolen.

## Tools Used
VsCode
## Recommended Mitigation Steps
You should use slippage protection and check if enough funds were returned by swap. You can calculate min amount with oracle prices and deduct some 1% slippage.


## Assessed type

Error
StrategyAAVEv3WSTETH._convertToWETH won't work and user will not be able to withdraw
Audit: 2024-05-bakerfi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/strategies/StrategyAAVEv3WSTETH.sol#L56


# Vulnerability details

## Proof of Concept
`StrategyAAVEv3WSTETH._convertToWETH` function is responsible to swap wstEth to weth using uniswap. It does it with [`_swap` function](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/strategies/StrategyAAVEv3WSTETH.sol#L90-L99).

But the problem is that `uniRouterA()` is granted with approve [by `stETH()`](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/strategies/StrategyAAVEv3WSTETH.sol#L56C14-L56C21) instead of wstEth. As result swap will always revert.

This is a big problem, because `_convertToWETH` is called, when users [want to withdraw](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/strategies/StrategyLeverage.sol#L704). Withdraw calls will always revert, because of that.

## Impact
No ability to withdraw.

## Tools Used
VsCode

## Recommended Mitigation Steps
You need to provide approve to wstEth tokens.


## Assessed type

Error