xanoutas/solidity-security-findings huggingface dataset:
l3r0ux - Rewarders can be drained
Audit: 2023-06-tokemak-judging
Severity: HIGH

l3r0ux

high

# Rewarders can be drained
## Summary

The rewarder contracts are heavily based on the Synthetix rewarder. However, contrary to Synthetix, here the staking is done and tracked in an external contract (the stakeTracker), and the rewarder contract just keeps track of the rewards. Because not all actions of the stakeTracker do update the reward state in the right moment, a mismatch between the _totalSupply_ and _balanceOf_ values of the stakeTracker can be crafted and abused, and an attacker can inflate its own rewards and drain all rewards.

## Vulnerability Detail

Relevant to understand this vulnerability are these functions in the `AbstractRewarder.sol` contract (which is inherited by the `MainRewarder.sol` contract) and their interactions:

1. `_updateReward(address)`
2. `rewardPerTokenStored()`
3. `earned(address)`

Another central element is the _stakeTracker_. According to the Discord messages of the protocol team, the _stakeTracker_ will be "either the LMPVault or the DestinationVault", we will assume an `LMPVault` here. 

The rewarder is looking up the total supply and the balance of a user by accessing the according functions (`totalSupply()`, `balanceOf(address)`) of the vault.

`earned(address)` is calculating the amount of rewards earned by an account, `rewardPerToken()` calculates the current reward per token value. They are both view functions and therefore do not modify the state.
`_updateReward(address)` uses these to functions and updates reward state variables - _rewardPerTokenStored_, _rewards[account]_ and _userRewardPerTokenPaid[account]_.

`rewardPerToken()` is taking `totalSupply()` into account, whereas `earned(address)` is taking `balanceOf(address)` into account. 
`rewardPerToken()` is executed by `earned(address)`, so there is an implied assumption in the code, that these to values (the total supply and the user balance) are always balanced - when a user deposits tokens into the vault, the balance of his account will increase, but also the total supply will increase, which will reduce the reward per share. 
This relation is not linear - as with a growing balance also the relative proportion of it grows and the account should get more rewards, but for example a doubling of the staked tokens of a user will not double his rewards. The increase of reward per additional staked token slows down and is capped to the rewardRate as a maximum (per reward time unit, a block in the case of Tokemak).

However, looking at the calculation of `rewardPerToken()` [(link),](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/rewarders/AbstractRewarder.sol#L180) we notice that the current "old" _rewardPerTokenStored_ value is taken, and increased by a dynamic calculation to calculate the "new" value. The total supply is only taken into account in the second part. We also notice, that the "unrewarded" time (in number of blocks) is calculated using the _lastUpdateBlock_ storage value (which i
duc - Sender can lose more tokens when attempting to call function `AbstractRewarder.queueNewRewards()`
Audit: 2023-06-tokemak-judging
Severity: HIGH

duc

high

# Sender can lose more tokens when attempting to call function `AbstractRewarder.queueNewRewards()`
## Summary
The issue at hand involves the aggregation of `newRewards` with `queuedRewards`, which results in senders having to provide more reward tokens than originally anticipated.

## Vulnerability Detail
The `AbstractRewarder.queueNewRewards()` function is employed to queue the specified amount of new rewards for distribution to stakers. It's important to note that the rewarder employs a distribution mechanism: when the accumulated rewards significantly outweigh the new rewards, these new rewards are placed in a queue instead of being instantly distributed. This queue of rewards is stored in a storage variable named `queuedRewards`.

```solidity=
function queueNewRewards(uint256 newRewards) external onlyWhitelisted {
    uint256 startingQueuedRewards = queuedRewards;
    uint256 startingNewRewards = newRewards;

    newRewards += startingQueuedRewards;

    if (block.number >= periodInBlockFinish) {
        notifyRewardAmount(newRewards);
        queuedRewards = 0;
    } else {
        uint256 elapsedBlock = block.number - (periodInBlockFinish - durationInBlock);

        /// [#explain] Represents the rewards already distributed
        uint256 currentAtNow = rewardRate * elapsedBlock;
        uint256 queuedRatio = currentAtNow * 1000 / newRewards;

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

    emit QueuedRewardsUpdated(startingQueuedRewards, startingNewRewards, queuedRewards);
    
    // Transferring the new rewards from the caller to this contract.
    IERC20(rewardToken).safeTransferFrom(msg.sender, address(this), newRewards);
}
```

The implementation reveals that `newRewards` is incremented with the initial value of `startingQueuedRewards = queuedRewards` at the start of the function. As a result, the function requires the sender transfer tokens with `amount = newRewards`. However, the flaw arises from the fact that `newRewards` encompasses not only the new reward amount but also includes the accumulated rewards from past function calls. This forces the sender to send a higher quantity of tokens than originally intended, potentially leading to a loss of funds.

Furthermore, it's worth noting that one of the whitelisted senders of this function is the `LiquidationRow` contract. In the `_performLiquidation()` function, `LiquidationRow` solely approves the exact new rewards `amount`. This design may lead to a scenario where the liquidation process could be reversed due to inadequate approval, consequently causing the collected rewards to become trapped within the contract.

## Impact
* Whitelisted users in `AbstractRewarder` might incur unexpected token losses.
* The functionality of `LiquidationRow.liquidateVaultsForToken()` could be hindered, resulting in tokens becoming stuck wit
0x73696d616f - Anyone can claim rewards from the rewarders to the destination vaults, DoSing `LiquidationRow` and possibly leading to stuck tokens
Audit: 2023-06-tokemak-judging
Severity: HIGH

0x73696d616f

medium

# Anyone can claim rewards from the rewarders to the destination vaults, DoSing `LiquidationRow` and possibly leading to stuck tokens
## Summary
Rewards from pools are supposed to be claimed only by the [`LIQUIDATOR_ROLE`](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/DestinationVault.sol#L165) via `collectRewards()` of the destination vaults, but anyone can do it directly from the rewarder of the pool and send the rewards to the vault instead.

## Vulnerability Detail
Deposits in the destination vaults deposit and stake to `Convex`, `Balancer` or `Maverick` pools, yielding rewards from their respective rewarders. 

These rewards are periodically claimed by the `LIQUIDATOR_ROLE`, which should be `LiquidationRow`, using [`claimsVaultRewards()`](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/liquidation/LiquidationRow.sol#L104), which updates the balances of the destination vaults by the rewards received. Then, [`liquidateVaultsForToken()`](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/liquidation/LiquidationRow.sol#L167), gets the [stored balances](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/liquidation/LiquidationRow.sol#L175) of the destination vaults, [swaps](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/liquidation/LiquidationRow.sol#L251) for the base asset, optionally sends a fee to a [`feeReceiver`](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/liquidation/LiquidationRow.sol#L261) and then [queues the rewards](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/liquidation/LiquidationRow.sol#L277) pro-rata in the rewarders.

This flow can be bypassed by calling `getReward()` of the corresponding `Convex`, `Balancer` or `Maverick` pools directly, which is permissionless, sending the tokens to the destination vaults instead. Take for example the [convex rewarder](https://etherscan.io/address/0x0a760466e1b4621579a82a39cb56dda2f4e70f03#code) of `STETH_ETH_CURVE_POOL`, function `getReward()` is permissionless (Line 980).

Created the following POC in `CurveConvexDestinationVault.t.sol`, verifying that anyone can claim the rewards of the destination vault and these are sent to it, instead of the `LIQUIDATOR_ROLE`.
```solidity
function test_POC_CollectRewards_ViaBaseRewarderDirectly() public {
    // Get some tokens to play with
    vm.prank(LP_TOKEN_WHALE);
    _underlyer.transfer(address(this), 200e18);

    // Give us deposit rights
    _mockIsVault(address(this), true);

    // Deposit
    _underlyer.approve(address(_destVault), 100e18);
    _destVault.depositUnderlying(100e18);

    // Move 7 days later
    vm.roll(block.number + 7200 * 7);
    // solhint-disable-next-line not-rely-on-time
    vm.warp(block.timestamp + 7 day
n33k - LMPVaultRouterBase: Native ETH lost in router when mint&deposit
Audit: 2023-06-tokemak-judging
Severity: HIGH

n33k

high

# LMPVaultRouterBase: Native ETH lost in router when mint&deposit
## Summary

`LMPVaultRouter` inherits `LMPVaultRouterBase`'s `payable` `mint` and `deposit` functions. The native ETH handling logics in `mint` and `deposit` are faulty.

## Vulnerability Detail

Take `deposit` as example. It calls `_processEthIn` to handle eth values in transaction.

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

`_processEthIn` wraps ETH to WETH. The WETH will be used for LMPVault depositing and is already in router contract.

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

After `_processEthIn`, `pullToken` is called redundantly to pull WETH from user and the LMPVault is using the pulled tokens to deposit underlying. The wrapped ETH in `_processEthIn` is not used and will be left in router.

`mint` in `LMPVaultRouterBase` has the same problem.

## Impact

Native ETH sent by user is not used for minting LMPVault shares and will be left in router contract. The left assets in router can be taken out by anyone.

## Code Snippet

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

## Tool used

Manual Review

## Recommendation

Fix the logics in `mint` and `deposit`.

Duplicate of #1
MrjoryStewartBaxter - Flashloan to subtract rewards since users can claim rewards immediately after depositing into the vault.
Audit: 2023-06-tokemak-judging
Severity: HIGH

MrjoryStewartBaxter

high

# Flashloan to subtract rewards since users can claim rewards immediately after depositing into the vault.
## Summary

It is possible to abuse the current design of the rewarder since a user is able to claim rewards after depositing into the vault. With this scenario it is possible to execute a flashloan attack exclusively for claiming rewards. 

## Vulnerability Detail

Since the Rewarder model does not have a tracking mechanism and checkpoints in place it is possible to execute a flashloan attack. In one transaction a malicious user is able to send a flashloan (deposit a considerable big amount of assets and then withdraw), and claim the corresponding rewards.

### PoC
```Solidity
    function test_flash_loan() external {       
      
        address attacker = makeAddr("attacker");
        _asset.mint(attacker, 50 ether);        

        _accessController.grantRole(Roles.DV_REWARD_MANAGER_ROLE, address(this));
        _lmpVault.rewarder().addToWhitelist(address(this));      
        _toke.mint(address(this), 1000e18);
        _toke.approve(address(_lmpVault.rewarder()), 1000e18);
        _lmpVault.rewarder().queueNewRewards(1000e18);
      
        vm.roll(block.number + 10_000);
      
        vm.startPrank(attacker);
        _asset.approve(address(_lmpVault), 50 ether);
        _lmpVault.deposit(50 ether, attacker);
        _lmpVault.withdraw(50 ether, attacker, attacker);
        vm.stopPrank();
        assert(_toke.balanceOf(attacker) > 0);
        
    }
```
```Solidity
    ├─ [0] VM::stopPrank() 
    │   └─ ← ()
    ├─ [563] toke::balanceOf(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e]) [staticcall]
    │   └─ ← 1000000000000000000000 [1e21]
    └─ ← ()
```



As we can see in this short but illustrative PoC, it is possible for an attacker to introduce a huge amount of assets, withdraw them and then claim for rewards since the model allows to do that when immediately depositing into the vault. Basically this is almost like subtracting rewards for free. 


## Impact

Any user can claim for rewards after immediately depositing into the vault. This can lead to:

Flash Loan attack (like the PoC) to steal rewards
Steal rewards from users that are really providing liquidity and not just depositing and withdrawing in one transaction. This can be considered as DoS since an attacker is draining a considerable part of the rewards for well-behaved and loyal users. 

## Code Snippet

https://github.com/sherlock-audit/2023-06-tokemak/blob/5d8e902ce33981a6506b1b5fb979a084602c6c9a/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L854-L864 
https://github.com/sherlock-audit/2023-06-tokemak/blob/5d8e902ce33981a6506b1b5fb979a084602c6c9a/v2-core-audit-2023-07-14/src/rewarders/AbstractRewarder.sol#L128-L140
## Tool used

Manual Review

## Recommendation

A tracking mechanism must be implemented for transfers within the rewarder to prevent the above scenario. It is particularly important to update/ implement checkpoints w
Kalyan-Singh - Wrong procedure of minting & staking in lmpVault leads to theft of all rewards
Audit: 2023-06-tokemak-judging
Severity: HIGH

Kalyan-Singh

high

# Wrong procedure of minting & staking in lmpVault leads to theft of all rewards
## Summary
LmpVault has '\_beforeTokenTransfer' and '\_afterTokenTransfer' hooks before token transfer withdraws stake and \_afterTokenTransfer does the staking. So abstractRewarder's \_updateReward(account) function is called after the balance of the account has already increased. Which leads to overestimation of rewards in earned() function which relies of balanceOf(account)

## Vulnerability Detail

POC-
1. There are 1000 ethers in a vault, and 10 rewards are queued in block 50.
2. Now in the next block(51) after queuing of rewards alice deposits some xyz amount of ether in it lets say 10 ether(maybe a flashloan)
3. Now lets walkthrough how the deposit transaction traverses through the contracts
	a.\_beforeTokenTransfer does no state changes as from = address(0) while minting
	b. now shares are minted lets say 10* 1e18.
	c.\_afterTokenTransfer calls rewarder.stake(alice,10 ether)
	d. In mainRewarder \_updateReward(alice) is called where
```solidity
        if (account != address(0)) {
            earnedRewards = earned(account);
            rewards[account] = earnedRewards;
            userRewardPerTokenPaid[account] = rewardPerTokenStored;
        }
```
  e. earnedRewards = earned(account) but in earned(account)
```solidity
    function earned(address account) public view returns (uint256) {
        return (balanceOf(account) * (rewardPerToken() - userRewardPerTokenPaid[account]) / 1e18) + rewards[account];
    }
``` 
  f. ^ here balanceOf(account)  = 10ether, userRewardPerTokenPaid\[alice] = 0 <                  rewardPerToken().  Making the earned(alice) != 0. But it should be 0 as alice has just staked and not even a single block has passed.

4. Weaponising this alice creates a malicious contract which does the following 
	a. take a loan in the callback deposit in vault, call getRewards() then call withdraw.
	b. now send the withdrawn amount to a new babyContract which does the same steps in step (a). Do this in a for loop and get all the rewards transferred over to her account by babyContracts and repay the loan. 

Attaching a working poc, add it to LMPVault-Withdraw.t.sol file under the LMPVaultMintingTests contract.

```solidity
function test_stealing_rewards() public {
    uint256 rewardAmt = 10 ether;
    uint256 otherDeposits = 1000 ether;
    _asset.mint(address(this), otherDeposits);
    _asset.approve(address(_lmpVault), otherDeposits);
    assertEq(_lmpVault.balanceOf(address(this)), 0);
    assertEq(_lmpVault.rewarder().balanceOf(address(this)), 0);
    _lmpVault.deposit(otherDeposits, address(this));
    // queuing rewards

    _accessController.grantRole(Roles.DV_REWARD_MANAGER_ROLE, address(this));
    _lmpVault.rewarder().addToWhitelist(address(this));
    _toke.mint(address(this), rewardAmt);
    _toke.approve(address(_lmpVault.rewarder()), rewardAmt);
    _lmpVault.rewarder().queueNewRewards(rewardAmt);

    // roooolllingn
enfrasico - Wrong implementation in router mint and deposit
Audit: 2023-06-tokemak-judging
Severity: HIGH

enfrasico

high

# Wrong implementation in router mint and deposit
## Summary
Mint and deposit in router is implemented wrongly resulting in 2 consequences

1. Native ether cannot be used to mint or deposit
2. User will double pay for minting or deposit

## Vulnerability Detail
Without loss of generality, we will describe this vulnerability with the mint function. The same issue is found in the deposit function.

`mint()` is made payable. Users can send in an amount of `ether` which will be wrapped into WETH when they want to mint shares. They can choose the amount of `shares` that they want to mint. The amount of assets needed to mint the shares is calculated by `previewMint()`.

Now, the issue here is that tokens are pulled from `msg.sender` regardless of the amount of `ether` that user has sent in. The amount pulled is always equal to exactly the amount needed to mint the shares. This means that no matter that no matter the amount of `ether` sent in and converted to WETH by the user, the router will still try to pull the same amount of WETH.

For eg, user wants to mint 100 shares which requires 5 assets. User sends in `5 ether`, but contract will still pull 5 WETH from the user if they have approved the router and have the required WETH. User is paying 10 ETH to mint just 100 shares.

## Impact
Because router is always trying to pull WETH from user regardless of the native ether token sent, users can potentially be double paying for their shares.

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

## Tool used
Manual Review

## Recommendation
Consider changing `pullToken(vaultAsset, assets, address(this))` to `pullToken(vaultAsset, assets- msg.value, address(this))`


Duplicate of #1
enfrasico - Wrong decimal used for pricing wrongly sets `existing.fastFilterPrice` and `existing.slowFilterPrice` erroneously high
Audit: 2023-06-tokemak-judging
Severity: HIGH

enfrasico

high

# Wrong decimal used for pricing wrongly sets `existing.fastFilterPrice` and `existing.slowFilterPrice` erroneously high
## Summary
Calculation for average price is done incorrectly due to the multiplying of uneccessary decimals.

## Vulnerability Detail
When we retrieve our price, `uint256 price = pricer.getPriceInEth(token)`, we get back our price in ETH normalised to 18 decimals from our oracles.

But when we calculate our average price, `uint256 averagePrice = existing._initAcc * 1e18 / INIT_SAMPLE_COUNT`, we multiply it by 1e18 again. This is normally used to ensure our decimals is correct after the calculation if the denominator is in 18 decimals too. However, in this case, `INIT_SAMPLE_COUNT` is a constant set to 18. This means that we are wrongly calculating average price to be wrong with a magnitude of 18 decimals.

And hence, both `existing.fastFilterPrice` and `existing.slowFilterPrice` will be set wrongly when `existing._initCount == INIT_SAMPLE_COUNT`. 

If we look at our implementation of `getFilteredValue()`, we can confirm that decimals for price should be 18 decimals.

## Impact
The EWMA incentive pricing for the LMP is going to be very wrong causing potentially huge losses.

## Code Snippet
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/stats/calculators/IncentivePricingStats.sol#L137
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/stats/calculators/IncentivePricingStats.sol#L156
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/stats/Stats.sol#L79-L86

## Tool used
Manual Review

## Recommendation
Calculate `averagePrice = existing.initAcc / INIT_SAMPLE_COUNT` instead.

Duplicate of #600
Angry_Mustache_Man - Wrong approvals causes Complete Denial of Service of Liquidation Process
Audit: 2023-06-tokemak-judging
Severity: HIGH

Angry_Mustache_Man

high

# Wrong approvals causes Complete Denial of Service of Liquidation Process
## Summary
In `_performLiquidation` function of `LiquidationRow.sol`, before queuing of rewards , "amount" number of tokens are approved . But in `queueNewRewards` function of `AbstractRewarder.sol`, this "amount" is increased by the "queuedRewards"(i.e., amount +queuedRewards)  which would require approval of "amount + queuedRewards". But as we know approval was given only for "amount" number of tokens , causing failure of the process .
## Vulnerability Detail
In `_performLiquidation` function of `LiquidationRow.sol` ,  we can see an approval of "amount" number of tokens is given to the rewarder contract :
```solidity
            // approve main rewarder to pull the tokens
            LibAdapter._approve(IERC20(params.buyTokenAddress), address(mainRewarder), amount);
            mainRewarder.queueNewRewards(amount);
```
Then in the `queueNewRewards` function of rewarder contract , the "newRewards"(which is same as "amount" number of tokens) is added with  "queuedRewards".
```solidity
        uint256 startingQueuedRewards = queuedRewards;
        uint256 startingNewRewards = newRewards;

        newRewards += startingQueuedRewards;
```
Then in the upcoming step, the Rewarder contract tries to pull "amount" + "queuedRewards" number of tokens from `LiquidationRow.sol` which would revert as the approval is only given for "amount" number of tokens and not for "amount" + "queuedRewards" number of tokens.
```solidity
IERC20(rewardToken).safeTransferFrom(msg.sender, address(this), newRewards);
```
## Impact
Complete Denial of Service of the Liquidation process on presence of "queuedRewards".
## Code Snippet
https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/liquidation/LiquidationRow.sol#L275-#L277

https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/rewarders/AbstractRewarder.sol#L236-#L239

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

## Tool used

Manual Review

## Recommendation
Add a new `getqueuedRewards()` view function in the AbstractRewarder.sol contract which would return the `queuedRewards` state. It could be called by `_performLiquidation` function of `LiquidationRow.sol` and the `queuedRewards` can be added to the "amount" variable before approving.

Duplicate of #379