xanoutas/solidity-security-findings huggingface dataset:
bin2chen - executeWithdrawal() minLongTokenAmount/minShortTokenAmount doesn't work
Audit: 2023-02-gmx-judging
Severity: HIGH

bin2chen

medium

# executeWithdrawal() minLongTokenAmount/minShortTokenAmount doesn't work

## Summary
SwapUtils.swap() when swapPathMarkets.length == 0, not check OutputAmount cannot be less than minLongTokenAmount, which puts the withdrawal user at risk of slippage


## Vulnerability Detail

Users can create withdrawal requests via createWithdrawal(), and due to slippage issues such as exchange rates etc, users will specify minLongTokenAmount/minShortTokenAmount to ensure their benefits
Execution flow： _executeWithdrawal()->swap()->SwapUtils.swap()   code as follows
```solidity
    function _executeWithdrawal(
        ExecuteWithdrawalParams memory params,
        Withdrawal.Props memory withdrawal
    ) internal {
....

        swap(
            params,
            market,
            market.longToken,
            cache.longTokenOutputAmount,
            withdrawal.longTokenSwapPath(),
            withdrawal.minLongTokenAmount(),  //@audit <------------pass minLongTokenAmount
            withdrawal.receiver(),
            withdrawal.shouldUnwrapNativeToken()
        );
......
function swap(
        ExecuteWithdrawalParams memory params,
        Market.Props memory market,
        address tokenIn,
        uint256 amountIn,
        address[] memory swapPath,
        uint256 minOutputAmount,
        address receiver,
        bool shouldUnwrapNativeToken
    ) internal {
        Market.Props[] memory swapPathMarkets = MarketUtils.getEnabledMarkets(params.dataStore, swapPath);

        SwapUtils.swap(
            SwapUtils.SwapParams(
                params.dataStore, // dataStore
                params.eventEmitter, // eventEmitter
                params.oracle, // oracle
                Bank(payable(market.marketToken)), // bank
                tokenIn, // tokenIn
                amountIn, // amountIn
                swapPathMarkets, // swapPathMarkets
                minOutputAmount, // minOutputAmount    //@audit <----------------call   SwapUtils.swap 
                receiver, // receiver
                shouldUnwrapNativeToken // shouldUnwrapNativeToken
            )
        );
    }
```
SwapUtils.swap()：

```solidity
    function swap(SwapParams memory params) external returns (address, uint256) {
        if (params.swapPathMarkets.length == 0) {  
            if (address(params.bank) != params.receiver) {
                params.bank.transferOut(
                    params.tokenIn,
                    params.receiver,
                    params.amountIn,
                    params.shouldUnwrapNativeToken
                );
            }
            return (params.tokenIn, params.amountIn); //@audit <--------if length==0 don't check  minOutputAmount
        }
...

```
The problem is in SwapUtils.swap():
If swapPathMarkets.length==0, the funds are transferred directly to the receiver, and there is no check minOutputAmount.
It is normal to want market.longToken dir
bin2chen - createDeposit() steal wnt
Audit: 2023-02-gmx-judging
Severity: HIGH

bin2chen

high

# createDeposit() steal wnt

## Summary
createDeposit() Wrong use of market.longToken/shortToken for judgment,lead to  malicious user can use non-wnt as executionFee, but refund is get wnt,thus making a profit from it

## Vulnerability Detail
User submits a deposit request via createDeposit()
For the way executionFee is collected, there are three cases as follows.
1. When initialLongToken == wnt, it is deducted directly from initialLongTokenAmount
2. When initialShortToken == wnt, it is deducted directly from initialShortTokenAmount
3. If none of the above, take it from vault, depositVault.recordTransferIn(wnt)
The implementation code is as follows:
```solidity
    function createDeposit(
        DataStore dataStore,
        EventEmitter eventEmitter,
        DepositVault depositVault,
        address account,
        CreateDepositParams memory params
    ) external returns (bytes32) {
        Market.Props memory market = MarketUtils.getEnabledMarket(dataStore, params.market);

        uint256 initialLongTokenAmount = depositVault.recordTransferIn(params.initialLongToken);
        uint256 initialShortTokenAmount = depositVault.recordTransferIn(params.initialShortToken);

        address wnt = TokenUtils.wnt(dataStore);

        if (market.longToken == wnt) {   //@audit<---------This place is misused market.longToken，Correct should be initialLongToken
            initialLongTokenAmount -= params.executionFee;
        } else if (market.shortToken == wnt) {  //@audit <---------This place is misused market.shortToken，Correct should be initialShortToken
            initialShortTokenAmount -= params.executionFee;
        } else {
            uint256 wntAmount = depositVault.recordTransferIn(wnt);
            if (wntAmount < params.executionFee) {
                revert InsufficientWntAmountForExecutionFee(wntAmount, params.executionFee);
            }

            GasUtils.handleExcessExecutionFee(
                dataStore,
                depositVault,
                wntAmount,
                params.executionFee
            );
        }
```
There is a problem with the above implementation.
Wrong use of market.longToken/shortToken for judgment
The correct should use initialLongToken/initialShortToken

This will lead to a problem: a malicious user can specify a market.longToken==wnt, but initialLongToken==SOL(or other not wnt), so createDeposit() give SOL, but when executeDeposit() or cancelDeposit() will return wnt

If the value of initialLongToken is much smaller than wnt, and the specified executionFee is large, it will be very profitable

Example:
market = {longToken=wnt,....}
initialLongToken=SOL ,  initialLongTokenAmount=1000
initialShortToken=Token_A , initialShortTokenAmount =1
executionFee = 1000


1. alice call createDeposit() will sucess:
deposit.initialLongToken=SOL
deposit.initialLongTokenAmount=1000-1000 = 0
deposit.initialShortToken=Token_A
deposit.initialShortTokenAmount=1
deposit.executionFee = 1000

2. when executeDe
Bauchibred - MavEthOracle.sol's current implementation  is massively prone to manipulation
Audit: 2023-06-tokemak-judging
Severity: HIGH

Bauchibred

medium

# MavEthOracle.sol's current implementation  is massively prone to manipulation

## Summary

Crux of the issue is that the reserves of the boosted positions are directly used to determine both `totalBoostedPositionValueTokenA` and `totalBoostedPositionValueTokenB` hence the determination of pricing does not use a fair LP pricing formular

## Vulnerability Detail

The MavEthOracle contract is intended to return prices for Maverick's boosted position lp tokens.
Here is the [getPriceInEth()](https://github.com/sherlock-audit/2023-06-tokemak/blob/5d8e902ce33981a6506b1b5fb979a084602c6c9a/v2-core-audit-2023-07-14/src/oracles/providers/MavEthOracle.sol#L44) function:

```solidity
    function getPriceInEth(address _boostedPosition) external returns (uint256) {
        Errors.verifyNotZero(_boostedPosition, "_boostedPosition");

        IPoolPositionDynamicSlim boostedPosition = IPoolPositionDynamicSlim(_boostedPosition);
        IPool pool = IPool(boostedPosition.pool());

        Errors.verifyNotZero(address(pool), "pool");

        // Check that total width of all bins in position does not exceed what we deem safe.
        if (pool.tickSpacing() * boostedPosition.allBinIds().length > maxTotalBinWidth) {
            revert TotalBinWidthExceedsMax();
        }

        // Get reserves in boosted position.
        (uint256 reserveTokenA, uint256 reserveTokenB) = boostedPosition.getReserves();

        // Get total supply of lp tokens from boosted position.
        uint256 boostedPositionTotalSupply = boostedPosition.totalSupply();

        IRootPriceOracle rootPriceOracle = systemRegistry.rootPriceOracle();

        // Price pool tokens.
        uint256 priceInEthTokenA = rootPriceOracle.getPriceInEth(address(pool.tokenA()));
        uint256 priceInEthTokenB = rootPriceOracle.getPriceInEth(address(pool.tokenB()));

        // Calculate total value of each token in boosted position.
        uint256 totalBoostedPositionValueTokenA = reserveTokenA * priceInEthTokenA;
        uint256 totalBoostedPositionValueTokenB = reserveTokenB * priceInEthTokenB;

        // Return price of lp token in boosted position.
        return (totalBoostedPositionValueTokenA + totalBoostedPositionValueTokenB) / boostedPositionTotalSupply;
    }
```

As explained by [maverick's docs](https://docs.mav.xyz/guides/incentives/how-to-manage-a-boosted-position#managing-a-staked-boosted-position) anyone can add liquidity to a boosted Position, which even seems to be the main idea behind boosted positions, i.e making it easier for one to add liquidity, this effectively means that anyone can flash attack this and decide the value for the calculation of both `totalBoostedPositionValueTokenA` and `totalBoostedPositionValueTokenB`.
In the end this leads to attacker having control over this

```solidity
return (totalBoostedPositionValueTokenA + totalBoostedPositionValueTokenB) / boostedPositionTotalSupply;

```
Current implementation means that should a boosted position no
0xComfyCat - LMPVaultRouterBase incorrectly handle WETH transfer
Audit: 2023-06-tokemak-judging
Severity: HIGH

0xComfyCat

medium

# LMPVaultRouterBase incorrectly handle WETH transfer
## Summary

## Vulnerability Detail
Supplying ETH via `msg.value` doesn't work since native ETH gets wrapped by internal function `_processEthIn` then it will attempt to transfer WETH from user via `pullToken` which result in the assets being transfer twice (both ETH and WETH)

## Impact
Incorrect implementation causes contract to not function properly and initial ETH sent will be stuck in router contract

## Code Snippet
```solidity
        // handle possible eth
        _processEthIn(vault);  // @audit - ETH being wrapped

        IERC20 vaultAsset = IERC20(vault.asset());
        uint256 assets = vault.previewMint(shares);
        pullToken(vaultAsset, assets, address(this));  // @audit - WETH being transferred in
```
https://github.com/Tokemak/v2-core-audit-2023-07-14/blob/main/src/vault/LMPVaultRouterBase.sol#L29-L35

## Tool used

Manual Review

## Recommendation
skip pull WETH if WETH balance in contract as a result of wrapping is enough for `assets` amount


Duplicate of #1
minhtrng - Calling BaseRewardPool directly causes rewards to be locked
Audit: 2023-06-tokemak-judging
Severity: HIGH

minhtrng

high

# Calling BaseRewardPool directly causes rewards to be locked
## Summary

A malicious actor can directly call IBaseRewardPool.getReward for `CurveConvexDestinationVault` address, which will transfer rewards into the vault without a way to get them out. 

## Vulnerability Detail

The function `ConvexRewards._claimRewards` claims all rewards and checks the balance difference before and after the claim to determine how much needs to be send to `sendTo`

```js
bool result = rewardPool.getReward(account, true);
...
// get balances after and calculate amounts claimed
for (uint256 i = 0; i < totalLength; ++i) {
    ...
    amountsClaimed[i] = balance - balancesBefore[i];
    if (sendTo != address(this) && amountsClaimed[i] > 0) {
        IERC20(rewardTokens[i]).safeTransfer(sendTo, amountsClaimed[i]);
    }
}
```

The issue is that `BaseRewardPool.getReward` is openly accessible and the Vault does not account for that: 

[Link](https://github.com/convex-eth/platform/blob/a5da3f127a321467a97a684c57970d2586520172/contracts/contracts/BaseRewardPool.sol#L263C1-L279C6)

```js
    function getReward(address _account, bool _claimExtras) public updateReward(_account) returns(bool){
        uint256 reward = earned(_account);
        if (reward > 0) {
            rewards[_account] = 0;
            rewardToken.safeTransfer(_account, reward);
            IDeposit(operator).rewardClaimed(pid, _account, reward);
            emit RewardPaid(_account, reward);
        }

        //also get rewards from linked rewards
        if(_claimExtras){
            for(uint i=0; i < extraRewards.length; i++){
                IRewards(extraRewards[i]).getReward(_account);
            }
        }
        return true;
    }
```

So anyone can claim rewards for the vault, but the vault only sends them to `sendTo` if the vault has claimed them itself.

## Impact

Rewards lost due to being locked in the `CurveConvexDestinationVault`

## Code Snippet

https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/destinations/adapters/rewards/ConvexRewardsAdapter.sol#L89-L107

## Tool used

Manual Review

## Recommendation
Use the full token balance instead of calculating the difference. Unless there is a reason this cant be done, in which case another method for accounting the rewards needs to be adopted.

Duplicate of #738
ck - `LMPVaultRouterBase::mint` and `LMPVaultRouterBase::deposit` transfer extra assets from the user when ETH is provided
Audit: 2023-06-tokemak-judging
Severity: HIGH

ck

high

# `LMPVaultRouterBase::mint` and `LMPVaultRouterBase::deposit` transfer extra assets from the user when ETH is provided
## Summary

`LMPVaultRouterBase::mint` and `LMPVaultRouterBase::deposit` transfer extra assets from the user when ETH is provided

## Vulnerability Detail

When either `LMPVaultRouterBase::mint` or `LMPVaultRouterBase::deposit` are called, `_processEthIn` is called to handle possible ETH.

```solidity
    function deposit(
        ILMPVault vault,
        address to,
        uint256 amount,
        uint256 minSharesOut
    ) public payable virtual override returns (uint256 sharesOut) {
        // handle possible eth
        _processEthIn(vault);

        IERC20 vaultAsset = IERC20(vault.asset());
        pullToken(vaultAsset, amount, address(this));

        return _deposit(vault, to, amount, minSharesOut);
    }
```

```solidity
    function _processEthIn(ILMPVault vault) internal {
        // if any eth sent, wrap it first
        if (msg.value > 0) {
            // if asset is not weth, revert
            if (address(vault.asset()) != address(weth9)) {
                revert InvalidAsset();
            }

            // wrap eth
            weth9.deposit{ value: msg.value }();
        }
    }
```

The issue is that the vault asset will again be transfered from the user: `pullToken(vaultAsset, amount, address(this));`.
This is double charging the user in the scenario where they had chosen to provide ETH.

## Impact

User is double charged during minting and depositing.

## Code Snippet

https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVaultRouterBase.sol#L44-L57

https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVaultRouterBase.sol#L111-L122

## Tool used

Manual Review

## Recommendation

If ETH is provided, additional assets should not be transfered from the user.

Duplicate of #1
vagrant - wETH deposited/minted via LMPVaultRouterBase is taken twice from msg.sender if he chooses to deposit with msg.value
Audit: 2023-06-tokemak-judging
Severity: HIGH

vagrant

high

# wETH deposited/minted via LMPVaultRouterBase is taken twice from msg.sender if he chooses to deposit with msg.value
## Summary
LMPVaultRouterBase offers the user the option to send ETH with his calls to mint/deposit. This msg.value will be deposited into the wETH contract yet it will not be used for minting vault shares, instead the contract pulls the tokens required for minting vault shares from the user again, making him pay twice if he has wETH in his wallet. The wETH stranded in the contract can be easily stolen by anyone.

## Vulnerability Detail
LMPVaultRouterBase mint() and deposit():
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVaultRouterBase.sol#L23
```solidity
    function mint(
        ILMPVault vault,
        address to,
        uint256 shares,
        uint256 maxAmountIn
    ) public payable virtual override returns (uint256 amountIn) {
        // handle possible eth
        _processEthIn(vault);

        IERC20 vaultAsset = IERC20(vault.asset());
        uint256 assets = vault.previewMint(shares);
        pullToken(vaultAsset, assets, address(this)); // @audit why do we need to pull tokens from the user if he already sent ETH with the transaction?
        vaultAsset.safeApprove(address(vault), assets);

        amountIn = vault.mint(shares, to);
        if (amountIn > maxAmountIn) {
            revert MaxAmountError();
        }
    }

    /// @inheritdoc ILMPVaultRouterBase
    function deposit(
        ILMPVault vault,
        address to,
        uint256 amount,
        uint256 minSharesOut
    ) public payable virtual override returns (uint256 sharesOut) {
        // handle possible eth
        _processEthIn(vault);

        IERC20 vaultAsset = IERC20(vault.asset());
        pullToken(vaultAsset, amount, address(this)); // @audit same here

        return _deposit(vault, to, amount, minSharesOut);
    }

    /// @dev Assumes tokens are already in the router
    function _deposit(
        ILMPVault vault,
        address to,
        uint256 amount,
        uint256 minSharesOut
    ) internal returns (uint256 sharesOut) {
        approve(IERC20(vault.asset()), address(vault), amount);
        if ((sharesOut = vault.deposit(amount, to)) < minSharesOut) {
            revert MinSharesError();
        }
    }
```

_processEthIn():
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVaultRouterBase.sol#L111
```solidity
    function _processEthIn(ILMPVault vault) internal {
        // if any eth sent, wrap it first
        if (msg.value > 0) {
            // if asset is not weth, revert
            if (address(vault.asset()) != address(weth9)) {
                revert InvalidAsset();
            }

            // wrap eth
            weth9.deposit{ value: msg.value }();
        }
    }
```

as you can see, when minting/depositing, the contract first converts the msg.value into wETH if the vault.asset is wETH.
The contract
n33k - LMPVault: Wrong accounting because of unsynced `totalDebt`
Audit: 2023-06-tokemak-judging
Severity: HIGH

n33k

medium

# LMPVault: Wrong accounting because of unsynced `totalDebt`
## Summary

`totalDebt` is a cached storage variable to track assets in destination vaults. It could be unsynced when destinations incur profit or loss. `mint`, `deposit`, `withdraw` and `redeam` could use a stale value and result in wrong shares mint or burned. Or user could profit by sandwich `updateDebtReporting` transaction.

## Vulnerability Detail

The sum of `totalIdle` and cached `totalDebt` represents the AUM.

```solidity
function totalAssets() public view override returns (uint256) {
    return totalIdle + totalDebt;
}
```

`totalDebt` could be unsynced if destinations incur reward, profit or loss. `updateDebtReporting` is to sync this storage variable  and could be called by anyone.

Wrong shares will be burned or minted if `totalDebt` is unsynced.

If destinations incur reward and profit, user can sandwich `updateDebtReporting` with mint and burn transactions to profit. The mint will mint a larger shares because `totalAssets()` will be smaller than real.

## Impact

Wrong shares will be burned or minted. User could profit by sandwich `updateDebtReporting` transaction.

## Code Snippet

`totalAssets()`:

https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L304-L306

`updateDebtReporting()`:
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L628-L630

## Tool used

Manual Review

## Recommendation

Having a keeper to call `updateDebtReporting` to sync `totalDebt` constantly will lower the impact.

Duplicate of #611
Flora - When an underlying price increases, an attacker can update the debt info to profit by using flash loans
Audit: 2023-06-tokemak-judging
Severity: HIGH

Flora

high

# When an underlying price increases, an attacker can update the debt info to profit by using flash loans
## Summary

When an underlying price increases, an attacker can update the debt info to profit by using flash loans.

## Vulnerability Detail

The vulnerability allows the attacker to perform a series of actions that lead to substantial profits. The steps involved are as follows:

1. The attacker monitors the underlying prices of destination vaults.
2. When the underlying price of a destination vault increases, the attacker initiates a flash loan.
3. The attacker deposits assets gotten from the flash loan into the `LMPVault` related to the destination vault with the increased underlying price.
4. The attacker updates the debt reporting to reflect the increased debt value resulting from the increased underlying price. The increased debt value raises the total asset value of the `LMPVault`, which in turn increases the Net Asset Value (NAV).
6. Finally, the attacker redeems all of their shares, benefiting from the increased NAV.

This vulnerability allows the attacker to exploit pricing changes and leverage flash loans to maximize their profits.

Here is the coded scenario to demonstrate the vulnerability:


```solidity
    function test_attacker_gets_all_profit_when_underlying_price_increases() public {
        _accessController.grantRole(Roles.SOLVER_ROLE, address(this));
        _accessController.grantRole(Roles.LMP_FEE_SETTER_ROLE, address(this));

        // User is going to deposit 1000 asset
        address user = vm.addr(1111);
        vm.startPrank(user);
        _asset.mint(user, 1000);
        _asset.approve(address(_lmpVault), 1000);
        _lmpVault.deposit(1000, user);
        vm.stopPrank();

        // Deployed 1000 asset to DV1
        _underlyerOne.mint(address(this), 1000);
        _underlyerOne.approve(address(_lmpVault), 1000);
        _lmpVault.rebalance(
            address(_destVaultOne),
            address(_underlyerOne), // tokenIn
            1000,
            address(0), // destinationOut, none when sending out baseAsset
            address(_asset), // baseAsset, tokenOut
            1000
        );

        // Increase the price of DV1 to 200% of original, so that 1000 we transferred out is now worth 2000
        _mockRootPrice(address(_underlyerOne), 2e18);

        // The attacker uses flash loan to get 1_000_000_000 assets
        address attacker = vm.addr(12345);
        vm.startPrank(attacker);
        _asset.mint(attacker, 1_000_000);
        _asset.approve(address(_lmpVault), 1_000_000);

        // The attacker deposits 1_000_000_000 assets
        _lmpVault.deposit(1_000_000, attacker);

        // Updates the debt reporting
        address[] memory dvAddresses = new address[](1);
        dvAddresses[0] = address(_destVaultOne);
        _lmpVault.updateDebtReporting(dvAddresses);

        // Redeems all shares
        uint256 assetsAfter = _lmpVault.redeem(
            _lmpVault.balanceOf(att