xanoutas/solidity-security-findings huggingface dataset:
jecikpo - All Rewards can be collected without waiting from mainRewarder
Audit: 2023-06-tokemak-judging
Severity: HIGH

jecikpo

high

# All Rewards can be collected without waiting from mainRewarder
## Summary
The improper accounting at MainRewarder and LMPVault allows a user to take all queued up rewards without waiting the necessary time.

## Vulnerability Detail
During minting of the LMPVault tokens the rewards are calculated improperly for the previously staked tokens. When new LMPVault tokens are minted, the rewards are calculated and collected for the already minted tokens, however the amount used to calculate that is unnecessarily including the just minted tokens (instead of doing it only for the amount before the current mint).

## Impact
Any user can take all amounts of rewards queued within the MainRewarder contract in a single transaction by leveraging a flashloan. 

## Code Snippet
 1. call LMPVault.claimRewards() which will call the MainRewarder._updateReward()
 2. We roll the blocks
 3. We deposit a big amount through the LMPVault.deposit()
 4. We do again LMPVault.claimRewards()
 5. We LMPVault.withdraw()

The code to achieve that:
```solidity
    function test_stealSomeRewards() public {
        uint256 private rewardAmount = 100_000_000;
        liquidator = vm.addr(234);
        _accessController.grantRole(Roles.LIQUIDATOR_ROLE, liquidator);       
        mainRewardVault = _lmpVault.rewarder();
        _toke.mint(liquidator, rewardAmount); // mint 

        vm.startPrank(liquidator);
        _toke.approve(address(mainRewardVault), rewardAmount);
        mainRewardVault.queueNewRewards(rewardAmount);
        vm.stopPrank();

        address user1 = vm.addr(100);
        uint amount = 100_000_000_000;
        vm.label(user1, "user1");

        _asset.mint(user1, amount);

        vm.startPrank(user1);
        _lmpVault.claimRewards();
        vm.stopPrank();

        vm.roll(block.number + 100);

        vm.startPrank(user1);
        _asset.approve(address(_lmpVault), amount);
        _lmpVault.deposit(amount, user1);
        _lmpVault.claimRewards(); // rewards are claimed as if the 'amount' was staked for 100 blocks.
        vm.stopPrank();

        uint rewardsClaimed = _lmpVault.rewarder().balanceOf(user1);

        console.log("Rewards collected: ", rewardsClaimed);
    }
```
## Tool used

Manual Review

## Recommendation

The rewarder should use its own accounting of staked tokens instead of relying on the LMPVault balances.




Duplicate of #603
nobody2018 - LMPVault._withdraw may cause some idleIncrease to be ignored, causing _baseAsset to get stuck in LMPVault
Audit: 2023-06-tokemak-judging
Severity: HIGH

nobody2018

high

# LMPVault._withdraw may cause some idleIncrease to be ignored, causing _baseAsset to get stuck in LMPVault
## Summary

Users can withdraw _baseAsset(WETH) via LMPVault.[[withdraw](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L400)]/[[redeem](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L422)], which call [[_withdraw](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L448)](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L448) internally. If `totalIdle` is less than the amount the user wants to withdraw, then `_withdraw` will withdraw enough _baseAsset from destVault via [[destVault.withdrawBaseAsset](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L483)](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L483). The current implementation of `_deposit` causes [[info.idleIncrease to be overridden](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L494)](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L494).

## Vulnerability Detail

```solidity
File: v2-core-audit-2023-07-14\src\vault\LMPVault.sol
448:     function _withdraw(
......
464:         // If not enough funds in idle, then pull what we need from destinations
465:         if (info.totalAssetsToPull > 0) {
466:             uint256 totalVaultShares = totalSupply();
467: 
468:             // Using pre-set withdrawalQueue for withdrawal order to help minimize user gas
469:             uint256 withdrawalQueueLength = withdrawalQueue.length;
470:             for (uint256 i = 0; i < withdrawalQueueLength; ++i) {
471:                 IDestinationVault destVault = IDestinationVault(withdrawalQueue[i]);
......//_calcUserWithdrawSharesToBurn omitted
482:->               uint256 assetPreBal = _baseAsset.balanceOf(address(this));
483:->               uint256 assetPulled = destVault.withdrawBaseAsset(sharesToBurn, address(this));
......
487:->               info.idleIncrease += _baseAsset.balanceOf(address(this)) - assetPreBal - assetPulled;
488:                 info.totalAssetsPulled += assetPulled;
489:                 info.debtDecrease += totalDebtBurn;
......
493:                 if (info.totalAssetsPulled > info.totalAssetsToPull) {
494:->                   info.idleIncrease = info.totalAssetsPulled - info.totalAssetsToPull;
495:                     info.totalAssetsPulled = info.totalAssetsToPull;
496:                     break;
497:                 }
......
502:                 if (info.totalAssetsPulled == info.totalAssetsToPull) {
503:                     break;
504:                 }
505:             }
506:
vinica_boy - Missing slippage protection when redeeming rewards from LV
Audit: 2024-08-cork-protocol-judging
Severity: HIGH

vinica_boy

High

# Missing slippage protection when redeeming rewards from LV

## Summary

Slippage protection is crucial for ensuring users receive the rewards they expect when removing liquidity or executing a swap in an AMM pair. However, the current protocol implementation does not offer any slippage protection mechanism. Instead, it enforces a 100% tolerance by design to prevent transaction failures, which leaves users vulnerable to receiving less than expected due to unfavorable price movements or liquidity changes.

To enhance user security, the protocol should allow users to specify their acceptable slippage tolerance during transactions. This way, users can control how much price fluctuation they are willing to accept, ensuring they can opt out of transactions if the slippage exceeds their chosen limit.
## Vulnerability Detail

Both the `_redeemCtDsAndSellExcessCt()` and `__liquidateUnchecked()` functions execute liquidity removal and swap operations on the AMM pair without providing slippage protection. This lack of slippage safeguards exposes the protocol to **sandwich attacks**, where an attacker can manipulate the price between the time a transaction is initiated and when it is executed.

```solidity
function _redeemCtDsAndSellExcessCt(
        State storage self,
        uint256 dsId,
        IUniswapV2Router02 ammRouter,
        IDsFlashSwapCore flashSwapRouter,
        uint256 ammCtBalance
    ) internal returns (uint256 ra) {
        uint256 reservedDs = flashSwapRouter.getLvReserve(self.info.toId(), dsId);

        uint256 redeemAmount = reservedDs >= ammCtBalance ? ammCtBalance : reservedDs;

        reservedDs = flashSwapRouter.emptyReservePartial(self.info.toId(), dsId, redeemAmount);

        ra += redeemAmount;
        PsmLibrary.lvRedeemRaWithCtDs(self, redeemAmount, dsId);

        uint256 ctSellAmount = reservedDs >= ammCtBalance ? 0 : ammCtBalance - reservedDs;

        DepegSwap storage ds = self.ds[dsId];
        address[] memory path = new address[](2);
        path[0] = ds.ct;
        path[1] = self.info.pair1;

        ERC20(ds.ct).approve(address(ammRouter), ctSellAmount);

        if (ctSellAmount != 0) {
            // 100% tolerance, to ensure this not fail
            ra += ammRouter.swapExactTokensForTokens(ctSellAmount, 0, path, address(this), block.timestamp)[1];
        }
    }

```

```solidity
function __liquidateUnchecked(
        State storage self,
        address raAddress,
        address ctAddress,
        IUniswapV2Router02 ammRouter,
        IUniswapV2Pair ammPair,
        uint256 lp
    ) internal returns (uint256 raReceived, uint256 ctReceived) {
        ammPair.approve(address(ammRouter), lp);

        // amountAMin & amountBMin = 0 for 100% tolerence
        (raReceived, ctReceived) =
            ammRouter.removeLiquidity(raAddress, ctAddress, lp, 0, 0, address(this), block.timestamp);

        (raReceived, ctReceived) = MinimalUniswapV2Library.reverseSortWithAmount224(
            ammPair.t
0x73696d616f - Lack of slippage protection in the `VaultLib` will lead to MEV and users will take losses
Audit: 2024-08-cork-protocol-judging
Severity: HIGH

0x73696d616f

Medium

# Lack of slippage protection in the `VaultLib` will lead to MEV and users will take losses

### Summary

When users call [VaultLib::redeemEarly()](https://github.com/sherlock-audit/2024-08-cork-protocol/blob/main/Depeg-swap/contracts/libraries/VaultLib.sol#L639), [VaultLib::_redeemCtDsAndSellExcessCt()](https://github.com/sherlock-audit/2024-08-cork-protocol/blob/main/Depeg-swap/contracts/libraries/VaultLib.sol#L318) is called which sells `ct` for `Ra` if there is not enough `Ds` reserve. The [UniswapRouter02::swapExactTokensForTokens()](https://github.com/sherlock-audit/2024-08-cork-protocol/blob/main/Depeg-swap/contracts/libraries/VaultLib.sol#L345) call does not send a minimum amount out nor a deadline, which means MEV bots are free to do sandwich attacks, which will lead to users getting less `Ra` then they would normally.

### Root Cause

In `VaultLib:345`, no minimum amount out nor deadline is set, which makes it vulnerable to MEV. Other instances in the codebase also do not set these parameters.

### Internal pre-conditions

1. There is not enough `Ds` reserve and `Ct` has to be swapped for `Ra`.

### External pre-conditions

None.

### Attack Path

1. MEV bot buys `Ra` for `Ct`.
2. User calls `Vault::redeemEarlyLv()`.
3. MEV bot sells `Ct` for more `Ra` than in 1.

### Impact

Ther user takes a loss.

### PoC

`VaultLib.sol`
```solidity
function _redeemCtDsAndSellExcessCt(
    State storage self,
    uint256 dsId,
    IUniswapV2Router02 ammRouter,
    IDsFlashSwapCore flashSwapRouter,
    uint256 ammCtBalance
) internal returns (uint256 ra) {
    ...
    if (ctSellAmount != 0) {
        // 100% tolerance, to ensure this not fail
        ra += ammRouter.swapExactTokensForTokens(ctSellAmount, 0, path, address(this), block.timestamp)[1];
    }
}
```

### Mitigation

Let the user send a minimum amount out and deadline.

Duplicate of #66
dimulski - If not all users have requested to redeem their LV tokens, some PA tokens will be locked in the protocol forever
Audit: 2024-08-cork-protocol-judging
Severity: HIGH

dimulski

High

# If not all users have requested to redeem their LV tokens, some PA tokens will be locked in the protocol forever

### Summary

The Corc protocol allows users to deposit RA tokens in order to get back CT and DS tokens via the [Psm::depositPsm()](https://github.com/sherlock-audit/2024-08-cork-protocol/blob/main/Depeg-swap/contracts/core/Psm.sol#L90-L101) function. CT and DS tokens have an expiration. There can be several DS and CT tokens minted for a RA:PA pair, but there is only one LV token per RA:PA pair. Users can mint the LV token by depositing the RA asset via the [Vault::depositLv()](https://github.com/sherlock-audit/2024-08-cork-protocol/blob/main/Depeg-swap/contracts/core/Vault.sol#L33-L37) function. The users also have the possibility to purchase RA tokens from the protocol by depositing DS and PA tokens via the [Psm::redeemRaWithDs()](https://github.com/sherlock-audit/2024-08-cork-protocol/blob/main/Depeg-swap/contracts/core/Psm.sol#L142-L159) function. When the CT and DS tokens have expired the protocol can issue new CT and DS tokens for the pair of RA:PA tokens. Users who have CT tokens can redeem a proportionate amount of the RA and PA tokens that are locked in the protocol via the [Psm::redeemWithCT()](https://github.com/sherlock-audit/2024-08-cork-protocol/blob/main/Depeg-swap/contracts/core/Psm.sol#L198-L210) function. The other part of the RA tokens that reside in the contract, based on the LV tokens minted, will be split to RA and the new CT token and provided as liquidity to the newly create UniV2 pair for the CT and RA tokens. In order to issue new DS and CT tokens for a pair of RA and PA assets the protocol has to call the [ModuleCore::issueNewDs()](https://github.com/sherlock-audit/2024-08-cork-protocol/blob/main/Depeg-swap/contracts/core/ModuleCore.sol#L57-L86) function. There are a couple of functions that are called by the [ModuleCore::issueNewDs()](https://github.com/sherlock-audit/2024-08-cork-protocol/blob/main/Depeg-swap/contracts/core/ModuleCore.sol#L57-L86) function, one of them is the [VaultLib::onNewIssuance()](https://github.com/sherlock-audit/2024-08-cork-protocol/blob/main/Depeg-swap/contracts/libraries/VaultLib.sol#L92-L108) function, which in turn calls the [VaultLib::_liquidatedLp()](https://github.com/sherlock-audit/2024-08-cork-protocol/blob/main/Depeg-swap/contracts/libraries/VaultLib.sol#L349-L393) function:
```solidity
    function _liquidatedLp(
        State storage self,
        uint256 dsId,
        IUniswapV2Router02 ammRouter,
        IDsFlashSwapCore flashSwapRouter
    ) internal {
        DepegSwap storage ds = self.ds[dsId];

        // if there's no LP, then there's nothing to liquidate
        if (self.vault.config.lpBalance == 0) {
            return;
        }

        // the following things should happen here(taken directly from the whitepaper) :
        // 1. The AMM LP is redeemed to receive CT + RA
        // 2. Any excess DS in the LV is paired with CT to redeem RA
KupiaSec - `DoS` to the `FlashSwapRouter.__afterFlashswapBuy()` function
Audit: 2024-08-cork-protocol-judging
Severity: HIGH

KupiaSec

Medium

# `DoS` to the `FlashSwapRouter.__afterFlashswapBuy()` function

## Summary

In the `FlashSwapRouter.__afterFlashswapBuy()` function, it exchanges `RA` for `CT + DS` and sends `CT` to the Uniswap V2 pool. However, the sent amount might be greater than the generated amount, which could lead to the reversal of the transaction.

## Vulnerability Detail

As you can see in the `__afterFlashswapBuy()` function:

1. It exchanges `RA` for `CT + DS` (line 368).
2. It sends `CT` to the `msg.sender` (line 377).

The amount of `RA` to be exchanged in step 1 is `dsAttributed`, so the amount of generated `CT + DS` is `dsAttributed / exchangeRate`. If the exchange rate is greater than 1, the amount of generated `CT` will be smaller than `dsAttributed`, resulting in the reversal of sending `CT` in step 2 due to insufficient `CT`.

Since the `__afterFlashswapBuy()` function is invoked when buying `DS`, this could lead to a `DoS` when buying `DS`.

```solidity
    function __afterFlashswapBuy(
        ReserveState storage self,
        Id reserveId,
        uint256 dsId,
        address caller,
        uint256 dsAttributed
    ) internal {
        AssetPair storage assetPair = self.ds[dsId];
        assetPair.ra.approve(owner(), dsAttributed);

        IPSMcore psm = IPSMcore(owner());
368     psm.depositPsm(reserveId, dsAttributed);

        // should be the same, we don't compare with the RA amount since we maybe dealing
        // with a non-rebasing token, in which case the amount deposited and the amount received will always be different
        // so we simply enforce that the amount received is equal to the amount attributed to the user

        // send caller their DS
        assetPair.ds.transfer(caller, dsAttributed);
        // repay flash loan
377     assetPair.ct.transfer(msg.sender, dsAttributed);
    }
```

## Impact

Buying `DS` could be `DoS`ed.

## Code Snippet

https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/core/flash-swaps/FlashSwapRouter.sol#L357-L378

## Tool used

Manual Review

## Recommendation

Sending amount should be fixed as follows.

```diff
    function __afterFlashswapBuy(
        ReserveState storage self,
        Id reserveId,
        uint256 dsId,
        address caller,
        uint256 dsAttributed
    ) internal {
        AssetPair storage assetPair = self.ds[dsId];
        assetPair.ra.approve(owner(), dsAttributed);

        IPSMcore psm = IPSMcore(owner());
        psm.depositPsm(reserveId, dsAttributed);

        // should be the same, we don't compare with the RA amount since we maybe dealing
        // with a non-rebasing token, in which case the amount deposited and the amount received will always be different
        // so we simply enforce that the amount received is equal to the amount attributed to the user

        // send caller their DS
        assetPair.ds.transfer(caller, dsA
KupiaSec - When users buy `DS`, the `FlashSwapRouter` assumes an exchange rate of `1:1` between `RA` and `CT + DS`
Audit: 2024-08-cork-protocol-judging
Severity: HIGH

KupiaSec

High

# When users buy `DS`, the `FlashSwapRouter` assumes an exchange rate of `1:1` between `RA` and `CT + DS`

## Summary

When users buy `DS`, the `FlashSwapRouter` assumes an exchange rate of `1:1` between `RA` and `CT + DS`.

## Vulnerability Detail

Let's consider the scenario where users buy `DS`.

The algorithm is as follows (according to the documentation):

1. Buyer sends Redemption Asset into the swap contract 
2. A contract withdraws more Redemption Asset from the AMM
3. The Redemption Asset is used to mint Cover Token and Depeg Swap
4. Depeg Swap is sent to the buyer
5. The Cover Token is sold for Redemption Asset to return the amount from step 2

The `FlashSwapRouter._swapRaforDs()` function calculates `borrowedAmount`, representing the amount of `RA` borrowed from the Uniswap V2 pool in step 2, by utilizing the `DsFlashSwap.getAmountOutBuyDS()` function (line 109).

```solidity
    function _swapRaforDs(
        ...

109     (amountOut, borrowedAmount,) = assetPair.getAmountOutBuyDS(amount);
        ...
```

The `DsFlashSwap.getAmountOutBuyDS()` function determines `borrowedAmount` by [referencing the `DsSwapperMathLib.getAmountOutDs()`](https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/DsFlashSwap.sol#L187) function.

In the `DsSwapperMathLib.getAmountOutDs()` function:

- s: Amount DS user received
- e: RA user provided
- x: RA reserve
- y: CT reserve
- r: RA needed to borrow from AMM

```solidity
    function getAmountOutDs(int256 x, int256 y, int256 e) external pure returns (uint256 r, uint256 s) {
        // first we solve the sqrt part of the equation first

        // E^2
        int256 q1 = e ** 2;
        // 2E(x + y)
        int256 q2 = 2 * e * (x + y);
        // (x - y)^2
        int256 q3 = (x - y) ** 2;

        // q = sqrt(E^2 + 2E(x + y) + (x - y)^2)
        uint256 q = SignedMath.abs(q1 + q2 + q3);
        q = Math.sqrt(q);

        // then we substitue back the sqrt part to the main equation
        // S = (E + x - y + q) / 2

        // r1 = x - y (to absolute, and we reverse the equation)
        uint256 r1 = SignedMath.abs(x - y);
        // r2 = -r1 + q  = q - r1
        uint256 r2 = q - r1;
        // E + r2
        uint256 r3 = r2 + SignedMath.abs(e);

        // S = r3/2 (we multiply by 1e18 to have 18 decimals precision)
        s = (r3 * 1e18) / 2e18;

        // R = s - e (should be fine with direct typecasting)
        r = s - SignedMath.abs(e);
    }
```

At step 2, `(e + r) * RA` is exchanged for `CT + DS`. Assuming the exchange rate between `RA` and `CT + DS` is `ER`, `((e + r) / ER) * (CT + DS)` will be generated. Consequently, `((e + r) / ER) * CT` will be repaid to the Uniswap V2 pool at step 5. Therefore, according to the rules of the Uniswap V2 pool, the following equation should hold:

```solidity
    x * y = (x - r) * (y + (e + r) / ER)
```

However, the `ge
KupiaSec - The `FlashSwapRouter` mistakenly transfers certain `RA` tokens to `DS` buyers, resulting in financial losses for `lv` holders
Audit: 2024-08-cork-protocol-judging
Severity: HIGH

KupiaSec

High

# The `FlashSwapRouter` mistakenly transfers certain `RA` tokens to `DS` buyers, resulting in financial losses for `lv` holders

## Summary

In the `_swapRaforDs()` function, the process begins by swapping a specified amount of `DS` (denoted as `amountSellFromReserve`) for `RA`. This generated `RA` is intended to be treated as a fee. However, instead of being processed as a fee, the generated `RA` is sent directly to the buyer, while the fees are deducted from the `ModuleCore` contract.

## Vulnerability Detail

In the `_swapRaforDs()` function, the following steps occur:

1. It swaps a specified amount of `DS` (denoted as `amountSellFromReserve`) for `RA` using the `__swapDsforRa()` function (line 124).
2. The generated `RA` is intended to be charged as a fee (line 125).

At line 124, the return value `vaultRa` represents the amount of `RA` generated in step 1. However, the `__swapDsforRa()` function has already sent this generated `RA` to the caller (in this case, the buyer). As a result, the fee charging in step 2 utilizes `RA` sourced from the `IVault(owner())`(the `ModuleCore` contract). This leads to a loss of funds for `lv` holders.

```solidity
    function _swapRaforDs(
        ReserveState storage self,
        AssetPair storage assetPair,
        Id reserveId,
        uint256 dsId,
        uint256 amount,
        uint256 amountOutMin
    ) internal returns (uint256 amountOut) {
        uint256 borrowedAmount;

        // calculate the amount of DS tokens attributed
        (amountOut, borrowedAmount,) = assetPair.getAmountOutBuyDS(amount);

        // calculate the amount of DS tokens that will be sold from LV reserve
        uint256 amountSellFromReserve =
            amountOut - MathHelper.calculatePrecentageFee(self.reserveSellPressurePrecentage, amountOut);

        // sell all tokens if the sell amount is higher than the available reserve
        amountSellFromReserve = assetPair.reserve < amountSellFromReserve ? assetPair.reserve : amountSellFromReserve;

        // sell the DS tokens from the reserve if there's any
        if (amountSellFromReserve != 0) {
            // decrement reserve
            assetPair.reserve -= amountSellFromReserve;

            // sell the DS tokens from the reserve and accrue value to LV holders
124         uint256 vaultRa = __swapDsforRa(assetPair, reserveId, dsId, amountSellFromReserve, 0);
125         IVault(owner()).provideLiquidityWithFlashSwapFee(reserveId, vaultRa);

            // recalculate the amount of DS tokens attributed, since we sold some from the reserve
            (amountOut, borrowedAmount,) = assetPair.getAmountOutBuyDS(amount);
        }

        if (amountOut < amountOutMin) {
            revert InsufficientOutputAmount();
        }

        // trigger flash swaps and send the attributed DS tokens to the user

        __flashSwap(assetPair, assetPair.pair, borrowedAmount, 0, dsId, reserveId, true, amountOut);
    }
```

This issue arises because the `__swapDsfo