xanoutas/solidity-security-findings huggingface dataset:
Flora - An attacker can monitor `LiquidationRow.liquidateVaultsForToken()` function, then use flash loan to steal all rewards from users
Audit: 2023-06-tokemak-judging
Severity: HIGH

Flora

high

# An attacker can monitor `LiquidationRow.liquidateVaultsForToken()` function, then use flash loan to steal all rewards from users
## Summary

An attacker can monitor `LiquidationRow.liquidateVaultsForToken()` function, then use flash loan to steal all rewards from users.

## Vulnerability Detail

Here is an overview of the steps the attacker takes to exploit the vulnerability:

1. **Monitoring the Liquidation Process**: The attacker monitors the execution of the `LiquidationRow.liquidateVaultsForToken()` function, which is responsible for distributing rewards to destination vaults.

2. **Flash Loan Deposit**: When the rewards are distributed and available in the destination vaults, the attacker uses a flash loan to deposit a substantial amount of assets into `LMPVaults` associated with these destination vaults.

3. **Update Debt Reporting**: After depositing the assets, the attacker updates the debt reporting mechanism in order to claim rewards.

4. **Redeem Shares**: The attacker then redeems all of their shares and repays the flash loan, obtaining a substantial portion of the rewards.

Here is the POC for the attack:

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

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

        // Bob is going to deposit 2000 asset
        address bob = vm.addr(2222);
        vm.startPrank(bob);
        _asset.mint(bob, 2000);
        _asset.approve(address(_lmpVault), 2000);
        _lmpVault.deposit(2000, bob);
        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
        );

        // Distribute DV1 rewards
        // In a real scenario, the attacker will monitor when liquidationRow.liquidateVaultsForToken() function is called
        // for simplicity, in this poc, we use queueNewRewards() in place of liquidateVaultsForToken())
        _accessController.grantRole(
            Roles.DV_REWARD_MANAGER_ROLE,
            address(this)
        );
        _accessController.grantRole(Roles.LIQUIDATOR_ROLE, address(this));
        _asset.mint(address(this), 2000);
        _asset.approve(_destVaultOne.rewarder(), 2000);
        IMainRewarder(_destVaultOne.rewarder()).queueNewRewards(2000);

        // Roll the block so that the
ck - A malicious user can prevent the liquidator role receiving rewards
Audit: 2023-06-tokemak-judging
Severity: HIGH

ck

medium

# A malicious user can prevent the liquidator role receiving rewards
## Summary

A malicious user can prevent the liquidator role receiving aura rewards

## Vulnerability Detail

The `ConvexRewardsAdapter::_claimRewards` function is meant to transfer the claimed rewards to the `sendTo` address.

```solidity
             amountsClaimed[i] = balance - balancesBefore[i];
            if (sendTo != address(this) && amountsClaimed[i] > 0) {
                IERC20(rewardTokens[i]).safeTransfer(sendTo, amountsClaimed[i]);
            }
```
This function is called from `BalancerAuraDestinationVault::_collectRewards`:

```solidity
    function _collectRewards() internal virtual override returns (uint256[] memory amounts, address[] memory tokens) {
        (amounts, tokens) = AuraRewards.claimRewards(auraStaking, defaultStakingRewardToken, msg.sender);
    }
```

Which is called by `DestinationVault::collectRewards`:

```solidity
    function collectRewards()
        external
        virtual
        override
        hasRole(Roles.LIQUIDATOR_ROLE)
        returns (uint256[] memory amounts, address[] memory tokens)
    {
        (amounts, tokens) = _collectRewards();
    }
```

Note that the only address allowed to call the function must have the `LIQUIDATOR_ROLE`.
It is this address that the rewards are transfered to.

The issue is that Aura rewards can be claimed by anyone on behalf of the vault using the `getReward` function directly from the reward pool contract.

This would mean rewards would get sent to the Vault instead of the liquidator.

## Impact

Loss of rewards for the liquidator.

## Code Snippet

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

https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/BalancerAuraDestinationVault.sol#L162-L164

https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/DestinationVault.sol#L161-L169

## Tool used

Manual Review

## Recommendation

Tracking of rewards should be done in the vault to prevent the liquidator role missing out on them.

Duplicate of #738
0xvj - Destination vault rewards should be claimed before every withdrawal in LMPVault
Audit: 2023-06-tokemak-judging
Severity: HIGH

0xvj

medium

# Destination vault rewards should be claimed before every withdrawal in LMPVault
## Summary
An `LMPVault` user will loose some base assets while withdrawing as pending rewards from the destination vaults are not being claimed before a LMPVault withdraw. Because of this a user will receive less baseAssets then what he should receive.

## Lines Of Code
[https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L400-L419](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L400-L419)

[https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L422-L434](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L422-L434)

## Vulnerability Detail
1. In `withdraw` and `redeem` functions of `LMPVault` the pending rewards that the vault has in destination vault rewarder are not being considered when calculating share value.
2. Due to this a user who is withdrawing funds will loose his share of rewards that the `LMPVault` has earned  from the point where the last  debt report is occured to now.

## Impact

A user who is withdrawing funds from the LMPVault will loose some share of his rewards.

## Code Snippet

## Tool used

Manual Review

## Recommendation
Claim pending rewards from all the destination vaults and add it to the `totalIdle` before calculating share price in `withdraw` and redeem functions.



Duplicate of #620
pks_ - ConvexRewardsAdapter#claimRewards rewards can be locked into the contract forever
Audit: 2023-06-tokemak-judging
Severity: HIGH

pks_

high

# ConvexRewardsAdapter#claimRewards rewards can be locked into the contract forever
## Summary

ConvexRewardsAdapter#claimRewards function can be called by any user, and it can be called by malicious user to lock protocol ubclaimed reward forever.

## Vulnerability Detail

ConvexRewardsAdapter#_claimRewards function use convex `rewardPool.getReward(address _account, bool _claimExtras)` to get unclaimed rewards for users, but this function can be called by any user, see `getReward` [detail](https://github.com/convex-eth/platform/blob/a5da3f127a321467a97a684c57970d2586520172/contracts/contracts/BaseRewardPool.sol#L263). Malicious user can front-run by calling `rewardPool.getReward(address _account, bool _claimExtras)` function to get unclaimed rewards from Convex protocol to contract previously. After that, the available rewards will be 0 because protocol calculate the rewards by `rewardPool.getReward(address _account, bool _claimExtras)` function.

```solidity
// claim rewards
bool result = rewardPool.getReward(account, true);
if (!result) {
    revert RewardAdapter.ClaimRewardsFailed();
}

// get balances after and calculate amounts claimed
for (uint256 i = 0; i < totalLength; ++i) {
    uint256 balance = 0;
    // Same check for "stash tokens"
    if (IERC20(rewardTokens[i]).totalSupply() > 0) {
        balance = IERC20(rewardTokens[i]).balanceOf(account);
    }

    amountsClaimed[i] = balance - balancesBefore[i];

    if (sendTo != address(this) && amountsClaimed[i] > 0) {
        IERC20(rewardTokens[i]).safeTransfer(sendTo, amountsClaimed[i]);
    }
}
```

After that, the unclaimed reward will be locked into the contract forever.

## Impact

Malicious user can lock protocol ubclaimed reward forever.

## 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


## Tool used

Manual Review

## Recommendation

Use `getReward()` instead of `getReward(address _account, bool _claimExtras)`


Duplicate of #738
caelumimperium - Vault uses new balance when updating the rewarder for a given user
Audit: 2023-06-tokemak-judging
Severity: HIGH

caelumimperium

high

# Vault uses new balance when updating the rewarder for a given user
## Summary
When vault shares are transferred, the rewarder state is updated for the sender and the receiver. But, for the receiver it uses the new balance to calculate the earned rewards until the time of the transfer. That results in them receiving more rewards than they should.

## Vulnerability Detail
When vault shares are transferred, the rewarder contract is updated in `beforeTokenTransfer()` and `afterTokenTransfer()`:

```sol
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override whenNotPaused {
        // Nothing to do really do here
        if (from == to) {
            return;
        }

        // If this isn't a mint of new tokens, then they are being transferred
        // from someone who is "staked" in the rewarder. Make sure they stop earning
        // When they transfer those funds
        if (from != address(0)) {
            rewarder.withdraw(from, amount, true);
        }

        // Make sure the destination wallet total share balance doesn't go above the
        // current perWalletLimit
        if (balanceOf(to) + amount > perWalletLimit) {
            revert OverWalletLimit(to);
        }
    }

    function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual override {
        // Nothing to do really do here
        if (from == to) {
            return;
        }

        // If this isn't a burn, then the recipient should be earning in the rewarder
        // "Stake" the tokens there so they start earning
        if (to != address(0)) {
            rewarder.stake(to, amount);
        }
    }
```

In `rewarder.stake()`, it executes `updateReward()` which uses the user's current balance to determine the amount of rewards they have earned until now:

```sol
    function stake(address account, uint256 amount) public onlyStakeTracker {
        _updateReward(account);
        _stake(account, amount);

        for (uint256 i = 0; i < extraRewards.length; ++i) {
            IExtraRewarder(extraRewards[i]).stake(account, amount);
        }
    }

    function _updateReward(address account) internal {
        uint256 earnedRewards = 0;
        rewardPerTokenStored = rewardPerToken();
        lastUpdateBlock = lastBlockRewardApplicable();

        if (account != address(0)) {
            earnedRewards = earned(account);
            rewards[account] = earnedRewards;
            userRewardPerTokenPaid[account] = rewardPerTokenStored;
        }

        emit UserRewardUpdated(account, earnedRewards, rewardPerTokenStored, lastUpdateBlock);
    }

    function earned(address account) public view returns (uint256) {
        return (balanceOf(account) * (rewardPerToken() - userRewardPerTokenPaid[account]) / 1e18) + rewards[account];
    }
```

`balanceOf()` is the account's current balance. For the recipient of vault shares, that amount includes the shares that were just trans
Breeje - User can pay double amount in Vault Router and Excess amount can be stolen
Audit: 2023-06-tokemak-judging
Severity: HIGH

Breeje

medium

# User can pay double amount in Vault Router and Excess amount can be stolen
## Summary

In case, the vault's asset is `weth` and user wants to pay in `eth` which is accepted by the contract, user can end up forced to pay twice. Furthermore, this excess amount can be stolen by anyone.

## Vulnerability Detail

Same vulnerability exists in both `mint` and `deposit` functions in `LMPVaultRouterBase` contract. So focusing on just `mint` function for details.

`mint` function in `LMPVaultRouterBase` contract is a publicly access function which any user can use to mint shares of any vault by depositing the base asset of that vault.

```solidity
File: LMPVaultRouterBase.sol

  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)); 
        vaultAsset.safeApprove(address(vault), assets);

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

```
[Link to Code](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVaultRouterBase.sol#L30-L34)

Here's the scenario of how User can end up paying double the asset:

1. Assume Alice wants to `mint` shares of a vault whose underlying base asset is `weth`.

2. Alice sends the `2 ethers` while calling `mint` function to get it's equivalent of shares from the vault.

3. The contract will call `_processEthIn(vault)` to check whether the underlying asset is `weth` or not.

```solidity
File: LMPVaultRouterBase.sol

    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 }();
        }
    }

```

4. As in this scenario, `vault.asset()` is `weth9`, the contract will `deposit` to `weth9` to convert it to `weth`.

5. Next, total assets to be send will be calculated using `previewMint` and again, the tokens are pulled from the user to the contract worth of `assets` value (marked with @-> above).

As Alice has already sent the `eth`, taking `assets`' value worth of `weth` again is a redundent call which results in loss of funds for Alice.

Also, there is a public function `unwrapWETH9`:

```solidity
File: PeripheryPayments.sol

  function unwrapWETH9(uint256 amountMinimum, address recipient) public payable { // @audit-info Can Steal any Weth from the router
      uint256 balanceWETH9 = weth9.balanceOf(address(this));

      if (balanceWETH9 < amountMinimum)
caelumimperium - Vault uses outdated data for share price calculation
Audit: 2023-06-tokemak-judging
Severity: HIGH

caelumimperium

medium

# Vault uses outdated data for share price calculation
## Summary
`updateDebtReporting` modifies `totalAssets` (idle & debt). `totalAssets` is used in the vault's share price calculation. Unless it's called right before the calculation, the vault will use outdated data.

## Vulnerability Detail
`updateDebtReporting()` updates `idle` and `debt` which combined represent the vault's `totalAssets`. `totalAssets` is used to calculate the share price:

```sol
    /**
     * @dev Internal conversion function (from assets to shares) with support for rounding direction.
     */
    function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256 shares) {
        uint256 supply = totalSupply();

        // slither-disable-next-line incorrect-equality
        shares = (assets == 0 || supply == 0) ? assets : assets.mulDiv(supply, totalAssets(), rounding);
    }

    /// @dev Internal conversion function (from shares to assets) with support for rounding direction.
    function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256 assets) {
        uint256 supply = totalSupply();
        assets = (supply == 0) ? shares : shares.mulDiv(totalAssets(), supply, rounding);
    }
```

Given that:
- totalSupply = 95e18
- totalAssets = 100e18
- amountIn = 1e18

Then, `shares = 1e18 * 95e18 / 100e18 = 9.5e17`.

Now, `updateDebtReporting()` is executed and `totalAssets` is increased to 102e18. If the user would have deposited after that, they would have received: `1e18 * 95e18 / 102e18 = 9.3e17` shares instead.

The higher the variance of `totalAssets` the bigger the impact on share calculation.

## Impact
This will lead to users receiving more/less shares when depositing and vice versa for withdrawals. It also opens up an arbitrage opportunity where an attacker can sandwich the `updateDebtReporting()` tx:

Given that:
- totalSupply = 95e18
- totalAssets = 100e18
- `updateDebtReporting()` increases `totalAssets` to 105e18

The attacker frontruns `updateDebtReporting()` and deposits 10e18 assets: `10e18 * 95e18 / 100e18 = 9.5e18` shares.
Now, `updateDebtReporting()` is executed. The attacker backruns that and redeems their shares: `9.5e18 * 115e18 / 104.5e18 = 10.45e18` assets. That's a profit of 0.45e18 assets.

## Code Snippet
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L628
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L587-L598

## Tool used

Manual Review

## Recommendation
The best solution would be to redesign the system so that `totalAssets` is always up-to-date. Second best is to call `updateDebtReporting` frequently so that the variance in `totalAssets` is minuscule. 


Duplicate of #611
l3r0ux - Lost funds in Reward Distribution Process
Audit: 2023-06-tokemak-judging
Severity: HIGH

l3r0ux

medium

# Lost funds in Reward Distribution Process
## Summary
When the rewarder has already tokens for distribution queued, and more reward tokens get distributed, then a higher than expected amount of tokens is transfered from the sender (liquidator), resulting in either lost funds, or a broken liquidation / reward distribution process due to a revert.

## Vulnerability Detail
The function `queueNewRewards(uint256)` in the contract `AbstractRewarder.sol` is used to queue tokens for reward distribution. The new tokens can either be directly send to distribution, or can be queued for a later distribution, if some certain condition is met - the _newRewardRatio_. In the latter case, the new token amount gets added to the _queuedRewards_ storage variable.

Early in the function, we see the following lines:
```solidity
uint256 startingQueuedRewards = queuedRewards;
(..)
newRewards += startingQueuedRewards;
```
Here the new tokens get added to the old queued token amount, to later perform the calculation for the _newRewardRatio_.

At the end of the function, the newly added tokens are transfered from the sender to the rewarder:
`IERC20(rewardToken).safeTransferFrom(msg.sender, address(this), newRewards);`

We see here, that the _newRewards_ value is used, which includes not only the newly added tokens, but also the old queued ones. Therefore a higher than expected amount is transfered.


The following test/POC demonstrates this behaviour:
```solidity
    function test_QueueNewRewards_RewarderSendsTooManyTokens() public {
        console.log("=== Before any rewards - starting conditions ===");
        console.log("currentRewards: ", rewarder.currentRewards());
        console.log("Queue Rewards: ", rewarder.queuedRewards());
        console.log("NewRewardRatio: ", rewarder.newRewardRatio());
        console.log("RewardToken balance of rewarder: ", rewardToken.balanceOf(address(this)));
        console.log("RewardToken balance of liquidator: ", rewardToken.balanceOf(address(liquidator)));
        console.log("");

        vm.prank(liquidator);
        rewarder.queueNewRewards(100000000);

        console.log("=== First round of rewards were queued ===");
        console.log("currentRewards: ", rewarder.currentRewards());
        console.log("Queue Rewards: ", rewarder.queuedRewards());
        console.log("NewRewardRatio: ", rewarder.newRewardRatio());
        console.log("RewardToken balance of rewarder: ", rewardToken.balanceOf(address(this)));
        console.log("RewardToken balance of liquidator: ", rewardToken.balanceOf(address(liquidator)));
        console.log("");

        vm.roll(block.number + durationInBlock / 2);

        console.log("=== Rolling forward half the reward time ===");
        console.log("currentRewards: ", rewarder.currentRewards());
        console.log("Queue Rewards: ", rewarder.queuedRewards());
        console.log("NewRewardRatio: ", rewarder.newRewardRatio());
        console.log("RewardToken balance of rewarder: