xanoutas/solidity-security-findings huggingface dataset:
_repayAndWithdraw() _pendingAmount Incorrect calculation
Audit: 2024-05-bakerfi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Vulnerability details
when user call `vault.withdraw()` will execute `_repayAndWithdraw()`
```solidity
    function _repayAndWithdraw(
        uint256 withdrawAmountInETh,
        uint256 repayAmount,
        uint256 fee,
        address payable receiver
    ) internal {

        (uint256 collateralBalance,) = _getMMPosition();
        uint256 convertedAmount = _fromWETH(withdrawAmountInETh);
        uint256 withdrawAmount = collateralBalance > convertedAmount ? convertedAmount : collateralBalance;
        
@>      _repay(wETHA(), repayAmount);                        
        
@>      _withdraw(ierc20A(), withdrawAmount, address(this));

        // Convert Collateral to WETH
        uint256 wETHAmount = _convertToWETH(withdrawAmount);
        // Calculate how much ETH i am able to withdraw
@>      uint256 ethToWithdraw = wETHAmount - repayAmount - fee;
        // Unwrap wETH
        _unwrapWETH(ethToWithdraw);
        // Withdraw ETh to Receiver
        payable(receiver).sendValue(ethToWithdraw);
        emit StrategyUndeploy(msg.sender, ethToWithdraw);
@>      _pendingAmount = ethToWithdraw;
    }
```

This method calculates `_pendingAmount`, which represents the number of assets that `AAVE` has removed this time.
Current formula: `_pendingAmount = ethToWithdraw = wETHAmount - repayAmount - fee`.

The correct formula is `_pendingAmount = wETHAmount - repayAmount`.

Because, during execution, funds are deposited and withdrawn as follows:
1. _repay(wETHA(), repayAmount).   
2. _withdraw(ierc20A(), withdrawAmount, address(this)).

The above `AAVE` flow of funds has nothing to do with `fee`.

The `fee` is split from the `wETHAmount` and given to the `Balancer` to pay the fees for the flash loan, which shouldn't be counted in this formula.


## Impact
`_pendingAmount` will eventually be used in `undeploy()` : `_deployedAmount -= _pendingAmount`
And `_deployedAmount` is used in `harvest()` to calculate `balanceChange`, which affects the `fee` calculation of `rebalance()`.
Wrong `_pendingAmount` leads to wrong `fee` calculation

## Recommended Mitigation

```diff
    function _repayAndWithdraw(
        uint256 withdrawAmountInETh,
        uint256 repayAmount,
        uint256 fee,
        address payable receiver
    ) internal {

        (uint256 collateralBalance,) = _getMMPosition();
        uint256 convertedAmount = _fromWETH(withdrawAmountInETh);
        uint256 withdrawAmount = collateralBalance > convertedAmount ? convertedAmount : collateralBalance;
        
        _repay(wETHA(), repayAmount);                        
        
        _withdraw(ierc20A(), withdrawAmount, address(this));

        // Convert Collateral to WETH
        uint256 wETHAmount = _convertToWETH(withdrawAmount);
        // Calculate how much ETH i am able to withdraw
        uint256 e
Using round down in flashFee() could potentially lead to flash loan failures.
Audit: 2024-05-bakerfi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Vulnerability details
in `BalancerFlashLender.flashFee()`
Calculated using `round down`
```solidity
    function flashFee(address, uint256 amount) external view override returns (uint256) {
        uint256 perc = _balancerVault.getProtocolFeesCollector().getFlashLoanFeePercentage();
        if (perc == 0 || amount == 0) {
            return 0;
        }

@>      return (amount * perc) / _BALANCER_MAX_FEE_PERCENTAGE;
    }
```

But `balancer ` is used `round up'.

https://github.com/balancer/balancer-v2-monorepo/blob/ac63d64018c6331248c7d77b9f317a06cced0243/pkg/vault/contracts/Fees.sol#L60
```solidity
     * @dev Returns the protocol fee amount to charge for a flash loan of `amount`.
     */
    function _calculateFlashLoanFeeAmount(uint256 amount) internal view returns (uint256) {
        // Fixed point multiplication introduces error: we round up, which means in certain scenarios the charged
        // percentage can be slightly higher than intended.
        uint256 percentage = getProtocolFeesCollector().getFlashLoanFeePercentage();
@>      return FixedPoint.mulUp(amount, percentage);
    }
```

This way when we set the allowance in `StrategyLeverage`, it may be `1 wei` smaller than it actually needs to be.
Take `deploy()` for example
```solidity
    function deploy() external payable onlyOwner nonReentrant returns (uint256 deployedAmount) {
        if (msg.value == 0) revert InvalidDeployAmount();
        // 1. Wrap Ethereum
        address(wETHA()).functionCallWithValue(abi.encodeWithSignature("deposit()"), msg.value);
        // 2. Initiate a WETH Flash Loan
        uint256 leverage = calculateLeverageRatio(
            msg.value,
            getLoanToValue(),
            getNrLoops()
        );
        uint256 loanAmount = leverage - msg.value;
@>      uint256 fee = flashLender().flashFee(wETHA(), loanAmount);
        //§uint256 allowance = wETH().allowance(address(this), flashLenderA());
@>      if(!wETH().approve(flashLenderA(), loanAmount + fee)) revert FailedToApproveAllowance();
        if (
            !flashLender().flashLoan(
                IERC3156FlashBorrowerUpgradeable(this),
                wETHA(),
                loanAmount,
                abi.encode(msg.value, msg.sender, FlashLoanAction.SUPPLY_BOORROW)
            )
        ) {
            revert FailedToRunFlashLoan();
        }
...
```

Finally `receiveFlashLoan()`check allowances or  `balancer` will fail at the end of the flash loan when getting the fee due to insufficient allowances leading to `revert`, which will cause all methods relying on the flash loan to fail

## Impact

A flash loan will cause several core functions of the protocol to fail, such as valult's `deposit/withdraw/rebalance`.

## Recommended Mitigation

Use `round up` for `flashFee()` calculations


## Assessed
deposit() incorrect use balanceOf(msg.sender)
Audit: 2024-05-bakerfi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Vulnerability details
in `Vault.deposit()`
We will limit the user's maximum deposit cannot exceed `settings().getMaxDepositInETH()`.
```solidity
    function deposit(
        address receiver
    )
        external
        payable
        override
        nonReentrant
        whenNotPaused
        onlyWhiteListed
        returns (uint256 shares)
    {
        if (msg.value == 0) revert InvalidDepositAmount();
        uint256 maxPriceAge = settings().getPriceMaxAge();
        Rebase memory total = Rebase(_totalAssets(maxPriceAge), totalSupply());
        if (
            // Or the Rebase is unititialized
            !((total.elastic == 0 && total.base == 0) ||
                // Or Both are positive
                (total.base > 0 && total.elastic > 0))
        ) revert InvalidAssetsState();
        // Verify if the Deposit Value exceeds the maximum per wallet
        uint256 maxDeposit = settings().getMaxDepositInETH();
        if (maxDeposit > 0) {
            uint256 afterDeposit = msg.value +
@>              ((balanceOf(msg.sender) * _tokenPerETH(maxPriceAge)) / 1e18);
            if (afterDeposit > maxDeposit) revert MaxDepositReached();
        }

....

        uint256 amount = abi.decode(result, (uint256));
        shares = total.toBase(amount, false);
@>      _mint(receiver, shares);
        emit Deposit(msg.sender, receiver, msg.value, shares);
    }
```
First, we need to calculate `afterDeposit = msg.value + ((balanceOf(receiver) * _tokenPerETH(maxPriceAge)) / 1e18)`. 
The usage of `balanceOf(msg.sender)` inside is incorrect because we ultimately deposit is into `receiver`: `_mint(receiver, shares)`. 
So, it should be corrected to `balanceOf(receiver)`.

## Impact

An incorrect calculation `afterDeposit` can result in exceeding `getMaxDepositInETH` or prematurely triggering a `MaxDepositReached` revert.
users may not be able to deposit properly.
## Recommended Mitigation
```diff
    function deposit(
        address receiver
    )
...
        uint256 maxDeposit = settings().getMaxDepositInETH();
        if (maxDeposit > 0) {
            uint256 afterDeposit = msg.value +
-               ((balanceOf(msg.sender) * _tokenPerETH(maxPriceAge)) / 1e18);
+               ((balanceOf(receiver) * _tokenPerETH(maxPriceAge)) / 1e18);
            if (afterDeposit > maxDeposit) revert MaxDepositReached();
        }
...
        uint256 amount = abi.decode(result, (uint256));
        shares = total.toBase(amount, false);
        _mint(receiver, shares);
        emit Deposit(msg.sender, receiver, msg.value, shares);
    }
```


## Assessed type

Context
deposit() afterDeposit calculation formula is incorrect.
Audit: 2024-05-bakerfi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Vulnerability details
in `Vault.deposit()`
We will limit the user's maximum deposit cannot exceed `settings().getMaxDepositInETH()`.
```solidity
    function deposit(
        address receiver
    )
        external
        payable
        override
        nonReentrant
        whenNotPaused
        onlyWhiteListed
        returns (uint256 shares)
    {
        if (msg.value == 0) revert InvalidDepositAmount();
        uint256 maxPriceAge = settings().getPriceMaxAge();
        Rebase memory total = Rebase(_totalAssets(maxPriceAge), totalSupply());
        if (
            // Or the Rebase is unititialized
            !((total.elastic == 0 && total.base == 0) ||
                // Or Both are positive
                (total.base > 0 && total.elastic > 0))
        ) revert InvalidAssetsState();
        // Verify if the Deposit Value exceeds the maximum per wallet
        uint256 maxDeposit = settings().getMaxDepositInETH();
        if (maxDeposit > 0) {
            uint256 afterDeposit = msg.value +
@>              ((balanceOf(msg.sender) * _tokenPerETH(maxPriceAge)) / 1e18);
            if (afterDeposit > maxDeposit) revert MaxDepositReached();
        }

....


    function _tokenPerETH(uint256 priceMaxAge) internal view returns (uint256) {
        uint256 position = _totalAssets(priceMaxAge);
        if (totalSupply() == 0 || position == 0) {
            return 1 ether;
        }
@>      return (totalSupply() * 1 ether) / position;
    }
```

The code above uses `(balanceOf(msg.sender) * _tokenPerETH(maxPriceAge) / 1e18` to calculate the current ETH deposit. 
Based on the definition of the `_tokenPerETH()` method, this formula is incorrect. 
It should be `balanceOf(msg.sender) * 1e18 / _tokenPerETH(maxPriceAge)`.

## Impact

An incorrect calculation formula can result in exceeding `getMaxDepositInETH` or prematurely triggering a `MaxDepositReached` revert.
users may not be able to deposit properly.
## Recommended Mitigation
```diff
    function deposit(
        address receiver
    )
...
        // Verify if the Deposit Value exceeds the maximum per wallet
        uint256 maxDeposit = settings().getMaxDepositInETH();
        if (maxDeposit > 0) {
            uint256 afterDeposit = msg.value +
-               ((balanceOf(msg.sender) * _tokenPerETH(maxPriceAge)) / 1e18);
+               ((balanceOf(msg.sender) * 1e18) / _tokenPerETH(maxPriceAge));
            if (afterDeposit > maxDeposit) revert MaxDepositReached();
        }
```


## Assessed type

Context
wethLefts cannot be used
Audit: 2024-05-bakerfi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
WETH cannot be used, resulting in a loss of funds

## Proof of Concept
The `_payDebt` function may produce extra weth, 
these weth are provided to AAVE through `_supply` for use as collateral.

```solidity
 function _payDebt(uint256 debtAmount, uint256 fee) internal {
        .....
        uint256 wethLefts = output > (debtAmount + fee) ? output - (debtAmount + fee) : 0;
        if (wethLefts > 0) {
            _supply(wETHA(), wethLefts);
        }
        emit StrategyUndeploy(msg.sender, debtAmount);
    }
```

The problem is `StrategyAAVEv3` doesn't use WETH as collateral.

```solidity
    //_supplyAndBorrow(ierc20A(), collateralIn, wETHA(), loanAmount + fee);
    function _supplyAndBorrow(address assetIn,uint256 amountIn,address assetOut,uint256 borrowOut
    ) internal override virtual{
        _supply(assetIn, amountIn);
        //@audit assetIn is always ierc20A
        aaveV3().setUserUseReserveAsCollateral(assetIn, true);
        aaveV3().borrow(assetOut, borrowOut, 2, 0, address(this));
    }
```

So wethLefts cannot be used.

`_getMMPosition` Obtaining `collateralBalance` also does not contain wETH
```solidity
    function _getMMPosition() internal virtual override view returns ( uint256 collateralBalance, uint256 debtBalance ) {
        DataTypes.ReserveData memory wethReserve = (aaveV3().getReserveData(wETHA()));
        DataTypes.ReserveData memory colleteralReserve = (aaveV3().getReserveData(ierc20A()));
        debtBalance = IERC20(wethReserve.variableDebtTokenAddress).balanceOf(address(this));
@>      collateralBalance = IERC20(colleteralReserve.aTokenAddress).balanceOf(address(this));
    }
```

## Tools Used
vscode, manual

## Recommended Mitigation Steps
Convert `WETH` to `ierc20A` and then call `_supply`






## Assessed type

Other
Anyone can trigger flashloan to a strategy
Audit: 2024-05-bakerfi-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/flashloan/BalancerFlashLender.sol#L98-L133


# Vulnerability details

## Proof of Concept
`BalancerFlashLender` is created to [initiate flashloans from strategies](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/strategies/StrategyLeverage.sol#L251-L255). So when `flashLoan` is called, then `BalancerFlashLender` [initiates flashloan on balancer](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/flashloan/BalancerFlashLender.sol#L86C9-L86C23) with borrower as encoded data.

Then later balancer will call `receiveFlashLoan` and `BalancerFlashLender` will trigger vault with [`onFlashLoan` callback](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/flashloan/BalancerFlashLender.sol#L117-L126).

First of all, [flashloan can be initiated on balancer by anyone](https://github.com/balancer/balancer-v2-monorepo/blob/master/pkg/vault/contracts/FlashLoans.sol#L38-L84) and even with amount 0. Also user can pass any data as `userData`. In our case, attacker will put address of Vault and additional params. He will provide `BalancerFlashLender` as `recipient`, so balancer will trigger BalancerFlashLender.

Then BalancerFlashLender will trigger vault, which was passed [as `borrower`](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/flashloan/BalancerFlashLender.sol#L109).

The check inside the vault [will also pass](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/strategies/StrategyLeverage.sol#L296) and call will be considered valid. Now attacker can use own crafted data to execute smth with vault. For example, he can use `_repayAndWithdraw` to withdraw any profit from contract to own address.

## Impact
Attacker can steal funds.

## Tools Used
VsCode
## Recommended Mitigation Steps
BalancerFlashLender should store some variable like: `flashloanInitated`, when someone requested flashloan through it. And then in case if balancer triggered `receiveFlashLoan`, BalancerFlashLender should check if the variable is indeed true and set it to false later.


## Assessed type

Error
StrategyAAVEv3WstETH makes users to pay fee for unwrapping
Audit: 2024-05-bakerfi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Proof of Concept
`StrategyAAVEv3WSTETH` [is `UseWstETH`](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/strategies/StrategyAAVEv3WSTETH.sol#L28C65-L28C74), but it doesn't use its function to wrap and unwrap eth. Instead it implements them by itself inside `_convertFromWETH` and `_convertToWETH` functions.

The problem is that `_convertToWETH` function [uses uniswap](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/strategies/StrategyAAVEv3WSTETH.sol#L90-L99) for conversion from wstEth to weth, which will make users to pay fee for a swap. It could be done without a swap directly with wstEth contract to avoid fee loss.

## Impact
Users pay extra fee to swap wstEth to weth.

## Tools Used
VsCode

## Recommended Mitigation Steps
You can use `_unwrapWstETH` function from `UseWstETH` contract and then wrap eth to weth.





## Assessed type

Error
Vault can be halted by malicious actor
Audit: 2024-05-bakerfi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Proof of Concept
`Vault.deposit` function checks that both `total.elastic && total.base` are 0 or non 0, otherwise it reverts.
https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/Vault.sol#L203-L209
```solidity
        Rebase memory total = Rebase(_totalAssets(maxPriceAge), totalSupply());
        if (
            // Or the Rebase is unititialized
            !((total.elastic == 0 && total.base == 0) ||
                // Or Both are positive
                (total.base > 0 && total.elastic > 0))
        ) revert InvalidAssetsState();
```

Attacker can send some aave collateral to the contract, just to make `_totalAssets(maxPriceAge)` to not be 0, before any shares where minted. As result no one will be able to deposit.

## Impact
Attacker can stop Vault from working.

## Tools Used
VsCode
## Recommended Mitigation Steps
I am not sure that check is needed.


## Assessed type

Error
Protocol receives less harvest fees
Audit: 2024-05-bakerfi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Proof of Concept
In case if position has grown, then protocol receives performance fee.
https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/Vault.sol#L153-L158
```solidity
                    uint256 feeInEthScaled = uint256(balanceChange) *
                        settings().getPerformanceFee();
                    uint256 sharesToMint = (feeInEthScaled * totalSupply()) /
                        _totalAssets(maxPriceAge) /
                        PERCENTAGE_PRECISION;
                    _mint(settings().getFeeReceiver(), sharesToMint);
```

We will check how shares amount is calculated and why it's less than should be.

Suppose that `totalSupply() == 100000` and `_totalAssets(maxPriceAge) == 100100`, so we earned 100 eth as additional profit. `balanceChange == 100` and performance fee is 10%, which is 10 eth.

`sharesToMint = 10 * 100000 / 100100 = 9.99001`
This means that with `9.990001` shares protocol should be able to grab 10 eth fee, which is indeed like that if we convert `9.99001 * 100100 / 100000 = 10`.

The problem is that minting is done later, which means that `totalSupply()` will increase with `9.99001` shares. So if we calculate fees amount now we will get smaller amount: `9.99001 * 100100 / 100009.99001 = 9.999001`

## Impact
Protocol receives smaller amount of fees

## Tools Used
VsCode

## Recommended Mitigation Steps
The formula should be adjusted to count increase of total supply.


## Assessed type

Error
max deposit check works wrong
Audit: 2024-05-bakerfi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Proof of Concept
User are restricted to not deposit [more than `settings().getMaxDepositInETH()`](https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/Vault.sol#L211-L215) to the Vault.

In order to check which amount user deposited they sum `msg.value + currentDepositedValue`.

To calculate user's current position they call `_tokenPerETH(maxPriceAge)`.
https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/Vault.sol#L334-L340
```solidity
    function _tokenPerETH(uint256 priceMaxAge) internal view returns (uint256) {
        uint256 position = _totalAssets(priceMaxAge);
        if (totalSupply() == 0 || position == 0) {
            return 1 ether;
        }
        return (totalSupply() * 1 ether) / position;
    }
```
This function calculates how many Vault tokens should be paid for ETH, but what we need to check max deposit breach is how many ETH 1 vault token costs.
As a result, max deposit check will work incorrectly.

## Impact
Max deposit check will work incorrectly.

## Tools Used
VsCode

## Recommended Mitigation Steps
Use `1/_tokenPerETH()` as Vault token price.


## Assessed type

Error
deposit/withdraw lack slippage protection
Audit: 2024-05-bakerfi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The user may pay too high a price when `deposit`, or too low a price when `withdraw`, resulting in a loss for the user

## Proof of Concept

On deposit, shares = amount * totalSupply / _totalAssets available to the user,

This is a variable price,

Also in withdraw, withdrawAmount = share * totalAssets() / totalSupply()

Therefore, the user may have too high or too low price deposit/withdraw, which is inconsistent with the user's expected result.

For example, after a user initiates a deposit/withdraw transaction, there is a transaction with a large amount in the same block, resulting in large price changes, and the user will suffer losses due to price changes.


## Tools Used
vscode, manual

## Recommended Mitigation Steps
Add slippage protection



## Assessed type

MEV
No check if sequencer is active
Audit: 2024-05-bakerfi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Proof of Concept
Protocol plans to be deployed on few L2s, like Arbitrum, Optimism, Base and it uses Chainlink oracles to fetch prices.

It's possible that L2 sequencer will stop working, which will affect ability of Chainlink nodes to provide price updates. But L2 will not stop and it will be still possible to initiate txs.

In such case protocol won't be able to detect stale prices and it's possible that someone will use it, which will cause incorrect calculations and may result in losses for protocol.

## Impact
Because sequencer is not checked to be down, it's possible that stale prices will be used for calculations.

## Tools Used
VsCode

## Recommended Mitigation Steps
When you fetch prices, you also need to query sequencer uptime oracle to know if it's secure to use price.
https://docs.chain.link/data-feeds/l2-sequencer-feeds#available-networks


## Assessed type

Error