xanoutas/solidity-security-findings huggingface dataset:
KupiaSec - Title: The `DsFlashSwap.getAmountOutSellDS()` function considers the exchange rate between `RA` and `CT + DS` to be `1:1`
Audit: 2024-08-cork-protocol-judging
Severity: HIGH

KupiaSec

High

# Title: The `DsFlashSwap.getAmountOutSellDS()` function considers the exchange rate between `RA` and `CT + DS` to be `1:1`

## Summary

The `DsFlashSwap.getAmountOutSellDS()` function considers the exchange rate between `RA` and `CT + DS` to be `1:1`.

## Vulnerability Detail

Let's consider the scenario where users sell `DS`.

The algorithm is as follows (according to the documentation):

1. Seller sends Depeg Swap into a swap contract
2. The contract borrows an equivalent amount of Cover Token from the pool
3. The Depeg Swap and Cover Token are used to redeem the Redemption Asset
4. Redemption Asset is sent to the seller (or routed to any major tokens)
5. A portion of the Redemption Asset is sold to the pool for Cover Token to return the amount from step 2


The `FlashSwapRouter.__swapDsforRa()` function calculates `amountOut`, which represents the amount of `RA` users will receive, by using the `DsFlashSwap.getAmountOutSellDS()` function (line 295).

```solidity
    function __swapDsforRa(
        AssetPair storage assetPair,
        Id reserveId,
        uint256 dsId,
        uint256 amount,
        uint256 amountOutMin
    ) internal returns (uint256 amountOut) {
295     (amountOut,) = assetPair.getAmountOutSellDS(amount);

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

        __flashSwap(assetPair, assetPair.pair, 0, amount, dsId, reserveId, false, amountOut);
    }
```

In the `DsFlashSwap.getAmountOutSellDS()` function:

- `amount` refers to the quantity of `DS` the user sells.
- `repaymentAmount` is the amount of `RA` that must be repaid to the pool in step 5.

In step 3, `amount * (CT + DS)` is exchanged for `RA`. Therefore, the amount of `RA` exchanged is `amount * exchangeRate`. Consequently, the total amount of `RA` the user should receive is `amount * exchangeRate - repaymentAmount`. However, the `DsFlashSwap.getAmountOutSellDS()` function calculates this as `amount - repaymentAmount`, as seen in line 170. This discrepancy results in a loss of funds for users when the `exchangeRate` exceeds 1.

```solidity
    function getAmountOutSellDS(AssetPair storage assetPair, uint256 amount)
        internal
        view
        returns (uint256 amountOut, uint256 repaymentAmount)
    {
        (uint112 raReserve, uint112 ctReserve) = getReservesSorted(assetPair);
        // we calculate the repayment amount based on the imbalanced ct reserve since we borrow CT from the AMM
        repaymentAmount = MinimalUniswapV2Library.getAmountIn(amount, raReserve, ctReserve - amount);

        // the amountOut is essentially what the user receive, we can calculate this by simply subtracting the repayment amount
        // from the amount, since we're getting back the same RA amount as DS user buy, this works. to get the effective price per DS,
        // you would devide this by the DS amount user bought.
        // note that we subtract 1 to enforce uni v2 rules
170     amountOut = am
KupiaSec - Incorrect return value of the `VaultLib._redeemCtDsAndSellExcessCt()` function
Audit: 2024-08-cork-protocol-judging
Severity: HIGH

KupiaSec

High

# Incorrect return value of the `VaultLib._redeemCtDsAndSellExcessCt()` function

## Summary

In the `VaultLib._redeemCtDsAndSellExcessCt()` function, `PsmLibrary.lvRedeemRaWithCtDs(self, redeemAmount, dsId)` is called to redeem `RA` using `(CT + DS) * redeemAmount`. In this case, the redeemed amount of `RA` is `redeemAmount * exchangeRate`. However, it accounts the redeemed amount of `RA` as `redeemAmount` instead of `redeemAmount * exchangeRate`.

## Vulnerability Detail

As noted at line 332 of the `_redeemCtDsAndSellExcessCt()` function, it redeems `RA` using `(CT + DS) * redeemAmount`. Based on the exchange rate between `RA` and `CT + DS`, the redeemed amount of `RA` should equal `redeemAmount * ds.exchangeRate()`. However, at line 331, only `redeemAmount`—instead of `redeemAmount * ds.exchangeRate()`—is added to `ra`. Consequently, the `_redeemCtDsAndSellExcessCt()` function miscalculates the redeemed amount of `RA`, resulting in a smaller value than intended. As a result, users will receive less `RA` than they should when redeeming `lv` before expiry, since the `redeemEarly()` function uses `_redeemCtDsAndSellExcessCt()` to determine the amount of `RA` users will receive (through [redeemEarly](https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/VaultLib.sol#L656) -> [_liquidateLpPartial](https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/VaultLib.sol#L315) -> [_redeemCtDsAndSellExcessCt](https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/VaultLib.sol#L331-L332)).

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

331     ra += redeemAmount;
332     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];
        }
    }
```

## Impact

Loss of `RA` for users.

## Code Snippet

https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/VaultLib.sol#L318-L347

## Tool used

Manual Review
KupiaSec - The `PsmLib.lvRedeemRaWithCtDs()` function doesn't decrease the locked amount of `RA`
Audit: 2024-08-cork-protocol-judging
Severity: HIGH

KupiaSec

High

# The `PsmLib.lvRedeemRaWithCtDs()` function doesn't decrease the locked amount of `RA`

## Summary

The `lvRedeemRaWithCtDs()` function is invoked when exchanging `CT + DS` for `RA` (through [redeemEarly](https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/VaultLib.sol#L656) -> [_liquidateLpPartial](https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/VaultLib.sol#L315) -> [_redeemCtDsAndSellExcessCt](https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/VaultLib.sol#L332)). This function only burns `CT + DS` without decreasing the locked amount of `RA`.

## Vulnerability Detail

As you can see, the `lvRedeemRaWithCtDs()` function only burns `CT + DS` without decreasing the locked amount of `RA`. Consequently, `psm.balances.ra.locked` (which represents the amount of locked `RA`) will be greater than it should be, leading to a loss of funds for the protocol, as the locked `RA` will be distributed to users after expiry.

```solidity
    function lvRedeemRaWithCtDs(State storage self, uint256 amount, uint256 dsId) internal {
        DepegSwap storage ds = self.ds[dsId];
127     ds.burnBothforSelf(amount);
    }
```

## Impact

Loss of `RA` for the protocol.

## Code Snippet

https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/PsmLib.sol#L125-L128

## Tool used

Manual Review

## Recommendation

Also, decrease the locked amount of `RA`.

Duplicate of #166
KupiaSec - The `PsmLib.repurchase()` function doesn't increase the locked amount of `RA`
Audit: 2024-08-cork-protocol-judging
Severity: HIGH

KupiaSec

High

# The `PsmLib.repurchase()` function doesn't increase the locked amount of `RA`

## Summary

The `repurchase()` function is invoked when exchanging `RA` for `PA + DS`. It transfers `RA` from users but does not increase the locked amount of `RA`.

## Vulnerability Detail

As noted at line 310 of the `repurchase()` function, the `ra.lockUnchecked()` function is called. The `lockUnchecked()` function only brings the necessary `RA` from the buyer without increasing the locked amount of `RA`. Consequently, `psm.balances.ra.locked` (which represents the amount of locked `RA`) will be less than it should be, resulting in a loss of funds for users, as the locked `RA` will be distributed to users after expiry.

```solidity
PsmLib.sol

    function repurchase(
        State storage self,
        address buyer,
        uint256 amount,
        IDsFlashSwapCore flashSwapRouter,
        IUniswapV2Router02 ammRouter
    ) internal returns (uint256 dsId, uint256 received, uint256 feePrecentage, uint256 fee, uint256 exchangeRates) {
        DepegSwap storage ds;

        (dsId, received, feePrecentage, fee, exchangeRates, ds) = previewRepurchase(self, amount);

        // decrease PSM balance
        // we also include the fee here to separate the accumulated fee from the repurchase
        self.psm.balances.paBalance -= (received);
        self.psm.balances.dsBalance -= (received);

        // transfer user RA to the PSM/LV
310     self.psm.balances.ra.lockUnchecked(amount, buyer);

        // transfer user attrubuted DS + PA
        // PA
        (, address pa) = self.info.underlyingAsset();
        IERC20(pa).safeTransfer(buyer, received);

        // DS
        IERC20(ds._address).transfer(buyer, received);

        // Provide liquidity
        VaultLibrary.provideLiquidityWithFee(self, fee, flashSwapRouter, ammRouter);
    }

-----------------------

RedemptionAssetManagerLib.sol

    function lockUnchecked(PsmRedemptionAssetManager storage self, uint256 amount, address from) internal {
        IERC20(self._address).safeTransferFrom(from, address(this), amount);
    }
```

## Impact

Loss of `RA` for users.

## Code Snippet

https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/PsmLib.sol#L293-L322

https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/RedemptionAssetManagerLib.sol#L65-L67

## Tool used

Manual Review

## Recommendation

Also, increase the locked amount of `RA`.

Duplicate of #126
KupiaSec - Incorrect implementation of the `PsmLib.unsafeIssueToLv()` function
Audit: 2024-08-cork-protocol-judging
Severity: HIGH

KupiaSec

High

# Incorrect implementation of the `PsmLib.unsafeIssueToLv()` function

## Summary

The `unsafeIssueToLv()` function is called when exchanging `RA` for `CT + DS` (during [liquidity provision](https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/VaultLib.sol#L167) into the Uniswap pool). This function increases the locked amount of `RA` while simultaneously minting `CT + DS`. However, the issue arises because the increase in the locked amount of `RA` is equal to the amount of newly minted `CT + DS`, even though there is an exchange rate between `RA` and `CT + DS`.

## Vulnerability Detail

As indicated at line 120, the `unsafeIssueToLv()` function increases the value of `psm.balances.ra.locked` by `amount`, which represents the newly minted `CT + DS`. This is incorrect because there is an exchange rate (which may not be 1:1) between `RA` and `CT + DS`.

For instance, if the exchange rate is 1.17 and `amount` is 1000, the required amount of `RA` to mint 1000 `CT + DS` would be `1000 * 1.17 = 1170`. Therefore, the increase in the locked amount of `RA` should be 1170. However, in the current logic, it is only set to 1000.

As a result, `psm.balances.ra.locked` will be less than it should be, leading to a loss of funds for users, as the locked `RA` will be distributed to users after expiry.

```solidity
    function unsafeIssueToLv(State storage self, uint256 amount) internal {
        uint256 dsId = self.globalAssetIdx;

        DepegSwap storage ds = self.ds[dsId];

120     self.psm.balances.ra.incLocked(amount);

        ds.issue(address(this), amount);
    }
```

## Impact

Loss of `RA` for users.

## Code Snippet

https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/PsmLib.sol#L115-L123

## Tool used

Manual Review

## Recommendation

Revise the `unsafeIssueToLv()` function to accurately handle the increase in the locked amount of `RA`.

Duplicate of #119
KupiaSec - Incorrect calculations of `ra` and `ct` in the `MathHelper.calculateProvideLiquidityAmountBasedOnCtPrice()` function
Audit: 2024-08-cork-protocol-judging
Severity: HIGH

KupiaSec

High

# Incorrect calculations of `ra` and `ct` in the `MathHelper.calculateProvideLiquidityAmountBasedOnCtPrice()` function

## Summary

In the `VaultLib.__provideLiquidityWithRatio()` function, the amounts of `RA` and `CT` to be added to the Uniswap V2 pool are calculated using the `MathHelper.calculateProvideLiquidityAmountBasedOnCtPrice()` function. However, this approach is flawed. The `MathHelper.calculateProvideLiquidityAmountBasedOnCtPrice()` function requires that `ra + ct = amountra` (see line 34), which indicates that ct should represent an amount of RA rather than an amount of CT. This misunderstanding will lead to incorrect behavior in the subsequent action: `__provideLiquidity()`, where `ct` is intended to denote the amount of `CT` to be added to the Uniswap V2 pool.

## Vulnerability Detail

In the `VaultLib.__provideLiquidityWithRatio()` function, `amount` represents the total amount of `RA`, which should be divided into two parts. The first part is the amount of `RA` to be added to the Uniswap V2 pool, while the second part is to be exchanged for `CT`, which will also be added to the Uniswap V2 pool alongside the first part. In this function, `ra` corresponds to the first part, and `ct` represents the amount of `CT` exchanged from the second part. These values are calculated using the `MathHelper.calculateProvideLiquidityAmountBasedOnCtPrice()` function (line 134); however, this is incorrect. The `MathHelper.calculateProvideLiquidityAmountBasedOnCtPrice()` function requires that `ra + ct = amountra` (see line 34), implying that `ct` should represent an amount of `RA` corresponding to the second part, rather than the amount of `CT`. This misunderstanding will lead to incorrect behavior in the subsequent action: `__provideLiquidity()` (line 136), where `ct` is intended to denote the amount of `CT` to be added to the Uniswap V2 pool.

```solidity
VaultLib.sol

    function __provideLiquidityWithRatio(
        State storage self,
        uint256 amount,
        IDsFlashSwapCore flashSwapRouter,
        address ctAddress,
        IUniswapV2Router02 ammRouter
    ) internal returns (uint256 ra, uint256 ct) {
        uint256 dsId = self.globalAssetIdx;

        uint256 ctRatio = __getAmmCtPriceRatio(self, flashSwapRouter, dsId);

134     (ra, ct) = MathHelper.calculateProvideLiquidityAmountBasedOnCtPrice(amount, ctRatio);

136     __provideLiquidity(self, ra, ct, flashSwapRouter, ctAddress, ammRouter, dsId);
    }

------------------------

MathHelper.sol

    function calculateProvideLiquidityAmountBasedOnCtPrice(uint256 amountra, uint256 priceRatio)
        external
        pure
        returns (uint256 ra, uint256 ct)
    {
        ct = (amountra * 1e18) / (priceRatio + 1e18);
        ra = (amountra - ct);

34      assert((ct + ra) == amountra);
    }
```

Let's consider the following scenario:

1. `ctRatio = 1.1e18, amount = 210`.
2. `(ra, ct) = (110, 100)` (from the `Mat
KupiaSec - Improper resetting of `reservedDs` in the `VaultLib._redeemCtDsAndSellExcessCt()` function
Audit: 2024-08-cork-protocol-judging
Severity: HIGH

KupiaSec

High

# Improper resetting of `reservedDs` in the `VaultLib._redeemCtDsAndSellExcessCt()` function

## Summary

In the `VaultLib._redeemCtDsAndSellExcessCt()` function, the calculation of `ctSellAmount` is incorrect because it uses the reset `reservedDs` instead of the original value.

## Vulnerability Detail

In the `_redeemCtDsAndSellExcessCt()` function, `reservedDs` represents the amount of `DS`, while `ammCtBalance` indicates the amount of `CT`. The process begins by redeeming as much `RA` as possible using the total of `CT + DS`, and any remaining `CT` is subsequently sold. However, the remaining amount of `CT` is miscalculated because the calculation relies on a reset `reservedDs`.

Consider the following scenario:

1. `reservedDs = 100, ammCtBalance = 70`.
2. `redeemAmount = 70` (see line 327).
3. `reservedDs` is reset to 30 at line 329.
4. `ctSellAmount = 70 - 30 = 40` (see line 334).

In this scenario, `ctSellAmount` is incorrectly calculated. At step 2, since `redeemAmount` is 70, it implies that `70 * (CT + DS)` is used to redeem `RA`, leaving the remaining `CT` amount at 0. Therefore, `ctSellAmount` should actually be 0.

The issue arises from step 3, where `reservedDs` is reset at line 329.

Because `ctSellAmount` is calculated to be greater than the actual remaining `CT` amount, the transaction will revert at line 345 due to insufficient `CT` to sell. This will disrupt the core functionality `redeemEarly`, as it [invokes the `_liquidateLpPartial()` function](https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/VaultLib.sol#L656), which in turn [calls the `_redeemCtDsAndSellExcessCt()` function](https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/VaultLib.sol#L315).

```solidity
    function _redeemCtDsAndSellExcessCt(
        State storage self,
        uint256 dsId,
        IUniswapV2Router02 ammRouter,
        IDsFlashSwapCore flashSwapRouter,
        uint256 ammCtBalance
    ) internal returns (uint256 ra) {
325     uint256 reservedDs = flashSwapRouter.getLvReserve(self.info.toId(), dsId);

327     uint256 redeemAmount = reservedDs >= ammCtBalance ? ammCtBalance : reservedDs;

329     reservedDs = flashSwapRouter.emptyReservePartial(self.info.toId(), dsId, redeemAmount);

        ra += redeemAmount;
        PsmLibrary.lvRedeemRaWithCtDs(self, redeemAmount, dsId);

334     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
345         ra += ammRouter.swapExactTokensForTokens(ctSellAmount, 0, path, address(this), block.timestamp)[1];
KupiaSec - The `DsFlashSwap.emptyReserve()` function incorrectly always returns 0
Audit: 2024-08-cork-protocol-judging
Severity: HIGH

KupiaSec

High

# The `DsFlashSwap.emptyReserve()` function incorrectly always returns 0

## Summary

The `DsFlashSwap.emptyReserve()` function incorrectly returns 0 instead of the original reserve amount. This leads to erroneous behavior within the `VaultLib._liquidatedLp()` function, which relies on the `DsFlashSwap.emptyReserve()` function, as it should utilize the original reserve amount.

## Vulnerability Detail

As noted at line 77, the `DsFlashSwap.emptyReservePartial()` function returns the remaining reserve amount, not the removed amount. Consequently, the `DsFlashSwap.emptyReserve()` function always returns 0 (see line 67), as it removes the entire reserve amount. This, in turn, causes the `FlashSwapRouter.emptyReserve()` function to also return 0, since it calls the `DsFlashSwap.emptyReserve()` function at line 70.

```solidity
DsFlashSwap.sol

    function emptyReserve(ReserveState storage self, uint256 dsId, address to) internal returns (uint256 reserve) {
67      reserve = emptyReservePartial(self, dsId, self.ds[dsId].reserve, to);
    }

    function emptyReservePartial(ReserveState storage self, uint256 dsId, uint256 amount, address to)
        internal
        returns (uint256 reserve)
    {
        self.ds[dsId].ds.transfer(to, amount);

        self.ds[dsId].reserve -= amount;
77      reserve = self.ds[dsId].reserve;
    }

------------------------

FlashSwapRouter.sol

    function emptyReserve(Id reserveId, uint256 dsId) external override onlyOwner returns (uint256 amount) {
70      amount = reserves[reserveId].emptyReserve(dsId, owner());
        emit ReserveEmptied(reserveId, dsId, amount);
    }
```

This results in incorrect behavior within the `VaultLib._liquidatedLp()` function.

For instance, at line 374 of the `VaultLib._liquidatedLp()` function, the value of `reservedDs` is always 0 because the `flashSwapRouter.emptyReserve()` function consistently returns 0. This impacts `redeemAmount`, making it always 0 (see line 376), which leads to incomplete redemption of `RA`. Consequently, users will incur a loss of `RA`.

```solidity
    function _liquidatedLp(
        ...

374     uint256 reservedDs = flashSwapRouter.emptyReserve(self.info.toId(), dsId);

376     uint256 redeemAmount = reservedDs >= ctAmm ? ctAmm : reservedDs;
        PsmLibrary.lvRedeemRaWithCtDs(self, redeemAmount, dsId);

        ...
```

## Impact

Leading to incomplete redemption of `RA` when processing expired states, resulting in a loss of `RA` for users.

## Code Snippet

https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/DsFlashSwap.sol#L66-L78

https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/core/flash-swaps/FlashSwapRouter.sol#L69-L72

https://github.com/sherlock-audit/2024-08-cork-protocol/tree/main/Depeg-swap/contracts/libraries/VaultLib.sol#L349-L393

## Tool used

Manual Review

## Recommendation

Make the following fixes.

```diff
    function emptyReserve(R