xanoutas/solidity-security-findings huggingface dataset:
caelumimperium - AbstractRewarder will pull more tokens than it should in `queueNewRewards()`
Audit: 2023-06-tokemak-judging
Severity: HIGH

caelumimperium

high

# AbstractRewarder will pull more tokens than it should in `queueNewRewards()`
## Summary
In `queueNewRewards()` it adds the already queued amount on top of the new one and pulls that from the caller. The extra amount is not accounted for and locked up in the rewarder contract.

## Vulnerability Detail

```sol
    function queueNewRewards(uint256 newRewards) external onlyWhitelisted {
        uint256 startingQueuedRewards = queuedRewards;
        uint256 startingNewRewards = newRewards;
        
        // @audit newRewards is modified to include the previously queued rewards
        newRewards += startingQueuedRewards;

        if (block.number >= periodInBlockFinish) {
            notifyRewardAmount(newRewards);
            queuedRewards = 0;
        } else {
            uint256 elapsedBlock = block.number - (periodInBlockFinish - durationInBlock);
            uint256 currentAtNow = rewardRate * elapsedBlock;
            uint256 queuedRatio = currentAtNow * 1000 / newRewards;

            if (queuedRatio < newRewardRatio) {
                notifyRewardAmount(newRewards);
                queuedRewards = 0;
            } else {
                queuedRewards = newRewards;
            }
        }

        emit QueuedRewardsUpdated(startingQueuedRewards, startingNewRewards, queuedRewards);

        // @audit this transfer new rewards & old rewards since `newRewards` was modified
        // Transfer the new rewards from the caller to this contract.
        IERC20(rewardToken).safeTransferFrom(msg.sender, address(this), newRewards);
    }
```

Given that Alice called `queueNewRewards()` with amount `X` while there was an active reward period and the new ratio is higher than the `newRewardRatio`. Then, `queuedRewards = X`. Now, Alice calls the function again to add an additional batch of rewards,  `Y`:

- `newRewards += startingQueueRewards = X + Y`
- `queuedRewards = X+Y`

That's correct. The issue is that at the end of the function, it pulls `newRewards` amount of tokens from the user. `newRewards = X + Y` but `X` was already pulled in the first transaction. Meaning, the balance is `X + X + Y` at the end with `X` not being accounted for in `queuedRewards`. Thus, you lock up the initially queued funds inside the contract.

## Impact
If you queue rewards multiple times you will lock up the funds queued prior to the current tx.

## Code Snippet
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/rewarders/AbstractRewarder.sol#L235-L261

## Tool used

Manual Review

## Recommendation
At the end of the function transfer `startingNewRewards` instead of `newRewards`.


Duplicate of #379
tives - Aura/Convex rewards are stuck after DOS
Audit: 2023-06-tokemak-judging
Severity: HIGH

tives

medium

# Aura/Convex rewards are stuck after DOS
## Summary

Since `_claimRewards` accounts for rewards with balanceBefore/After, and anyone can claim Convex rewards, then attacker can DOS the rewards and make them stuck in the LiquidationRow contract.

## Vulnerability Detail

Anyone can claim Convex rewards for any account.

https://etherscan.io/address/0x0A760466E1B4621579a82a39CB56Dda2F4E70f03#code

```solidity
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;
}
```

In ConvexRewardsAdapter, the rewards are accounted for by using balanceBefore/after.

```solidity
function _claimRewards(
    address gauge,
    address defaultToken,
    address sendTo
) internal returns (uint256[] memory amounts, address[] memory tokens) {

		uint256[] memory balancesBefore = new uint256[](totalLength);
    uint256[] memory amountsClaimed = new uint256[](totalLength);
...

		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];

	return (amountsClaimed, rewardTokens);
```

Adversary can call the external convex contract’s  `getReward(tokemakContract)`. After this, the reward tokens are transferred to Tokemak without an accounting hook.

Now, when Tokemak calls claimRewards, then no new rewards are transferred, because the attacker already transferred them. `amountsClaimed` will be 0.

## Impact

Rewards are stuck in the LiquidationRow contract and not queued to the MainRewarder.

## Code Snippet

```solidity
// 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]);
    }
}
```

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

## Tool used

Manual Review

## Recommendation

Don’t use balanceBefore/After. You could consider using `balanceOf(address(this))` after
harisnabeel - mint() and deposit() with ETH from LMPVaultRouter will always revert when users have 0 WETH
Audit: 2023-06-tokemak-judging
Severity: HIGH

harisnabeel

high

# mint() and deposit() with ETH from LMPVaultRouter will always revert when users have 0 WETH
## Summary
When users want to deposit() or mint() with ETH using LMPVaultRouterBase, these transactions always revert when user have 0 WEH balance as pullTokens() will attempt to pull tokens from caller as specified in params even after getting ETH as converting them to WETH. 

## Vulnerability Detail

Suppose Alice have 0 WETH and 1 ETH in his account, he wants to deposit 1 ETH to get 1e18 shares. Alice calls deposit() function of LMPVaultRouterBase with params below:
```solidity
deposit(wethVaultAddress,callerAddress,1e18,1e18)
```

Following line will convert the deposited ETH into WETH
```solidity
        _processEthIn(vault);
``` 
Now that the ETH is converted to WETH. The following line tries to pull 1 WETH from the Alice.
```solidity

        pullToken(vaultAsset, amount, address(this));

```
Alice had 0 WETH so the transaction will revert.

Same pattern is used inside the mint() function of LMPVaultRouterBase that will cause revert when minting with ETH.

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


## Impact
Vault router will always revert and the core functionality will not work as expected.

## Code Snippet
Links are given above in references

## Tool used

Manual Review

## Recommendation
Only pull WETH from user when msg.value < maxAmountInORamount.
Make sure to pull the difference between msg.value and maxAmountInORamount like below:
```solidity
if(msg.value < maxAmountInORamount){
uint amountToPull = maxAmountInORamount - msg.value;
pullToken(vaultAsset, amountToPull, address(this));
}
```

Duplicate of #1
bitsurfer - `queueNewRewards` contains wrong logic, resulting double transfer
Audit: 2023-06-tokemak-judging
Severity: HIGH

bitsurfer

high

# `queueNewRewards` contains wrong logic, resulting double transfer
## Summary

`queueNewRewards` contains wrong logic, resulting double transfer

## Vulnerability Detail

There is a storage variable `queuedRewards` which is storing the amount of rewards waiting in the queue to be distributed.

```js
File: AbstractRewarder.sol
60:     /// @notice The amount of rewards waiting in the queue to be distributed.
61:     uint256 public queuedRewards;
```

Initially, the value of `queuedRewards` is set to 0. Subsequently, when the `queueNewRewards` function is called, the amount of newRewards is transferred from the sender (msg.sender) to the contract. In the code snippet provided, at line 253, if the condition `queuedRatio < newRewardRatio` evaluates to false, the value of `queuedRewards` is set to be the same as newRewards. This value of `queuedRewards` is then carried over to the next invocation of the `queueNewRewards` function.

At another instance, within the `queueNewRewards` function, the parameter `newRewards` is augmented with the previous value of `queuedRewards` (line 239).

Ultimately, the value of `newRewards` is used to determine the amount to be transferred from the sender. Consequently, this results in the rewardToken being doubled, encompassing both the newly added rewards (newRewards) as well as the rewards that were previously queued (queuedRewards).

```js
File: AbstractRewarder.sol
235:     function queueNewRewards(uint256 newRewards) external onlyWhitelisted {
236:         uint256 startingQueuedRewards = queuedRewards;
237:         uint256 startingNewRewards = newRewards;
238:
239:         newRewards += startingQueuedRewards;
240:
241:         if (block.number >= periodInBlockFinish) {
242:             notifyRewardAmount(newRewards);
243:             queuedRewards = 0;
244:         } else {
245:             uint256 elapsedBlock = block.number - (periodInBlockFinish - durationInBlock);
246:             uint256 currentAtNow = rewardRate * elapsedBlock;
247:             uint256 queuedRatio = currentAtNow * 1000 / newRewards;
248:
249:             if (queuedRatio < newRewardRatio) {
250:                 notifyRewardAmount(newRewards);
251:                 queuedRewards = 0;
252:             } else {
253:                 queuedRewards = newRewards;
254:             }
255:         }
256:
257:         emit QueuedRewardsUpdated(startingQueuedRewards, startingNewRewards, queuedRewards);
258:
259:         // Transfer the new rewards from the caller to this contract.
260:         IERC20(rewardToken).safeTransferFrom(msg.sender, address(this), newRewards);
261:     }
```

The potential issue here is that the `newRewards` value used for the token transfer has been modified by adding startingQueuedRewards to it. This means that the contract might transfer more tokens than intended if queuedRewards was not 0 when the function was called.

## Impact

queueNewRewards might transfer more tokens than intended if queuedRewards was no
bitsurfer - `totalIdle` may updated wrongly due to uncounted asset rewards from burned shares, affecting rebalance and resulting untracked asset
Audit: 2023-06-tokemak-judging
Severity: HIGH

bitsurfer

high

# `totalIdle` may updated wrongly due to uncounted asset rewards from burned shares, affecting rebalance and resulting untracked asset
## Summary

`totalIdle` may updated wrongly due to uncounted asset rewards from burned shares when `totalAssetsPulled > totalAssetsToPull`. This wrong `totalIdle` will affect rebalance, and this also may be categorized as loss asset due to untracked asset

## Vulnerability Detail

When there is a withdrawal, and the idle asset is less than amount requested from the withdrawal, the asset will be pulled from DVs, which is going through loop of withdrawalQueue.

On every loop, (assuming `sharesToBurn` != 0), on line 487, the `idleIncrease` is accumulated with the difference betwen current asset balance minus asset before withdrawing from DV (preBalance) minus the `assetPulled` value. As said in the comment, this idleIncrease is part of rewards from burned shares.

Then on line 488, the `totalAssetsPulled` is accumulated with the `assetPulled`

```js
File: LMPVault.sol
482:                 uint256 assetPreBal = _baseAsset.balanceOf(address(this));
483:                 uint256 assetPulled = destVault.withdrawBaseAsset(sharesToBurn, address(this));
484:
485:                 // Destination Vault rewards will be transferred to us as part of burning out shares
486:                 // Back into what that amount is and make sure it gets into idle
487:                 info.idleIncrease += _baseAsset.balanceOf(address(this)) - assetPreBal - assetPulled;
488:                 info.totalAssetsPulled += assetPulled;
489:                 info.debtDecrease += totalDebtBurn;
490:
491:                 // It's possible we'll get back more assets than we anticipate from a swap
492:                 // so if we do, throw it in idle and stop processing. You don't get more than we've calculated
493:                 if (info.totalAssetsPulled > info.totalAssetsToPull) {
494:                     info.idleIncrease = info.totalAssetsPulled - info.totalAssetsToPull;
495:                     info.totalAssetsPulled = info.totalAssetsToPull;
496:                     break;
497:                 }
498:
...
511:         // subtract what's taken out of idle from totalIdle
512:         // slither-disable-next-line events-maths
513:         totalIdle = info.currentIdle + info.idleIncrease - info.assetsFromIdle;
514:
```

The issue here is, one of loop breaker, which is when `info.totalAssetsPulled > info.totalAssetsToPull`, the `idleIncrease` is assigned (overwritten) with `totalAssetsPulled - totalAssetsToPull`. This is an issue because `totalAssetsPulled` is only incremented with `assetPulled`, the rewards asset from burned shares is not included or omitted.

Finally on line 513, the totalIdle will be the `idleIncrease`, which is wrong, because the previous `idleIncrease` by reward (from burned shares) is omitted. Thus the reward (from burned share) is untracked.

This wrong `totalIdle` will finally affecting rebalance, as this variabl
harisnabeel - User can lost funds when mint() with ETH from LMPVaultRouter
Audit: 2023-06-tokemak-judging
Severity: HIGH

harisnabeel

high

# User can lost funds when mint() with ETH from LMPVaultRouter
## Summary
When minting with ETH, **LMPVaultRouterBase**'s mint() function also pull WETH from caller if the caller had approved WETH back in some time. 

## Vulnerability Detail
1 - Suppose the ratio in vault is 1:1, Alice wants to mint 1e18 shares, as WETH and ETH are 1:1, so inside mint() of LMPVaultRouterBase ```_processEthIn()``` converts deposited ETH into WETH. The functions will be called with params like below.
```solidity 
mint(wethVaultAddress,userAddress,1e18,1e18)
```
2 - Assuming some users who have WETH and had approved to LMPVaultRouter back in some time. In spite of getting ETH, following line also pulls 1 WETH from user. 
```solidity
        pullToken(vaultAsset, assets, address(this));
```

3 - The user will get shares of 1 ETH, and will lose 1 WETH unknowingly. 

The WETH pulled from user will remain inside the LMPVaultRouter unless someone/BOT calls sweepToken() and gets those WETH.

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




## Impact

Those users who have WETH in their account and had approved WETH to LMPVaultRouter will lose WETH when minting with ETH.

## Code Snippet
```solidity
   /// @inheritdoc ILMPVaultRouterBase
      function mint(
        ILMPVault vault,
        address to,
        uint256 shares,
        uint256 maxAmountIn
    ) public payable virtual override returns (uint256 amountIn) {
        // handle possible eth
        _processEthIn(vault); // @audit

        IERC20 vaultAsset = IERC20(vault.asset());
        uint256 assets = vault.previewMint(shares);
        pullToken(vaultAsset, assets, address(this)); // @audit
        vaultAsset.safeApprove(address(vault), assets);

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

## Tool used

Manual Review

## Recommendation
Only pull WETH from user when msg.value < maxAmountIn.
Make sure to pull the difference between msg.value and maxAmountIn like below:
```solidity
if(msg.value < maxAmountIn){
uint amountToPull = maxAmountIn - msg.value;
pullToken(vaultAsset, amountToPull, address(this));
}
```



Duplicate of #1
harisnabeel - User can lost funds during deposit() in LMPVaultRouter
Audit: 2023-06-tokemak-judging
Severity: HIGH

harisnabeel

high

# User can lost funds during deposit() in LMPVaultRouter
## Summary
When depositing with ETH, **LMPVaultRouterBase**'s deposit() function can also pull WETH from caller if the caller had approved WETH back in some time. 

## Vulnerability Detail
1 - Alice wants to deposit 1 ETH to mint shares, as WETH and ETH are 1:1, so inside deposit() of LMPVaultRouterBase ```_processEthIn()``` converts deposited ETH into WETH. The functions will be called with params like below.
```solidity 
deposit(wethVaultAddress,userAddress,1e18,1e18)

```
2 - Assuming some users who have WETH and had approved to LMPVaultRouter back in some time. In spite of getting ETH, following line also pulls 1 WETH from user. 
```solidity
        pullToken(vaultAsset, amount, address(this));
```

3 - The user will get shares of 1 ETH, and user will lose 1 WETH unknowingly. 

The WETH pulled from user will remain inside the LMPVaultRouter unless someone/BOT calls sweepToken() and gets those WETH.

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




## Impact

Those users who have WETH in their account and had approved WETH to LMPVaultRouter will lose WETH when depositing with ETH.

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

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

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

## Tool used

Manual Review

## Recommendation
Only pull WETH from user when msg.value < amount.
Make sure to pull the difference between msg.value and amount like below:
```solidity
if(msg.value < amount){
uint amountToPull = amount - msg.value;
pullToken(vaultAsset, amountToPull, address(this));
}
```



Duplicate of #1
ADM - LiquidationRow's claimsVaultRewards can be frontrun resulting in tokens being stuck in contract.
Audit: 2023-06-tokemak-judging
Severity: HIGH

ADM

high

# LiquidationRow's claimsVaultRewards can be frontrun resulting in tokens being stuck in contract.
## Summary
LiquidationRow's claimsVaultRewards claims any earned rewards from the selected destination vaults and then uses the amounts received to update the balances of the destinationVaults balances. However the amount of tokens recorded is determined by the difference of tokens before/after calling the claimReward function in the rewardAdapter contract. For certain protocols this claimReward function can be called by anyone allowing them to front run the call to claimsVaultRewards resulting in the tokens already being in the contract, so the difference in before/after when the adapter calls claimReward will be 0 and no tokens will be sent back to the liquidatorRow contract and the call to _increaseBalance will never run.

## Vulnerability Detail
Say there is a BalancerAuraDestinationVault that has earned 1,000 reward tokens and a liquidator wishes to claim these they would call LiquidationRow.claimsVaultRewards(). Another user would be able to frontrun this and call the AuraPool.getReward function first as it has no access control.
```Solidity
    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;
    }
```
When the liquidators call executes the _claimRewards function in the rewardsAdapter and the AuraPool.getReward function is called it will return no tokens as the rewards have already been claimed. The result is that the tokens never get sent back to the liquidatorRow contract as the balance before/after the getReward will return 0 and will be stuck in the destinationVault.
```Solidity
        // get balances before
        for (uint256 i = 0; i < totalLength; ++i) {
            // Using the totalSupply check to represent stash tokens. They sometimes
            // stand in as the rewardToken but they don't have a "balanceOf()"
            if (IERC20(rewardTokens[i]).totalSupply() > 0) {
                balancesBefore[i] = IERC20(rewardTokens[i]).balanceOf(account);   // Frontrunner can have tokens already sent to rewardAdapter
            }
        }

        // claim rewards
        bool result = rewardPool.getReward(account, true);                          // So This returns 0 as rewards are already claimed and sent
        if (!result) {
            revert RewardAdapter.ClaimRewardsFailed();
        }

        // get balances after and calculate amounts claimed