xanoutas/solidity-security-findings huggingface dataset:
Liquidation or Redemption of `ShortRecords` in `PartialFill` status without associated `Short` will be reverted
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/PrimaryLiquidationFacet.sol#L47
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/RedemptionFacet.sol#L56


# Vulnerability details

## Impact

It's possible for a `short` order to remain partially filled, typically when the `bid order` amount is less than the `short order` amount. The issue arises when these `ShortRecords` remain partially filled without the presence of an associated `short`, rendering them unable to be liquidated or redeemed if necessary.

The problem occurs when the amount of the `bid order` is slightly less than the amount of the `short order`. The order fills in `LibOrders#L582`, and then, if there are no more `bid orders` at that price, it adds a `sell order` if the remaining amount is greater than `minAskEth` in `LibOrders#L594`. However, if the remaining amount is less than `minAskEth`, no `short` is created (`LibOrders#L594`), leaving the `ShortRecord` in `PartialFill` status without an associated `short`.

```solidity
File: LibOrders.sol
556:     function sellMatchAlgo(
557:         address asset,
558:         STypes.Order memory incomingAsk,
559:         MTypes.OrderHint[] memory orderHintArray,
560:         uint256 minAskEth
561:     ) internal {
...
...
582:                 matchHighestBid(incomingAsk, highestBid, asset, matchTotal);
583:                 if (incomingAsk.ercAmount > highestBid.ercAmount) {
584:                     incomingAsk.ercAmount -= highestBid.ercAmount;
585:                     highestBid.ercAmount = 0;
586:                     matchOrder(s.bids, asset, highestBid.id);
587: 
588:                     // loop
589:                     startingId = highestBid.nextId;
590:                     if (startingId == C.TAIL) {
591:                         matchIncomingSell(asset, incomingAsk, matchTotal);
592: 
593:                         if (incomingAsk.ercAmount.mul(incomingAsk.price) >= minAskEth) {
594:                             addSellOrder(incomingAsk, asset, orderHintArray);
595:                         }
596:                         s.bids[asset][C.HEAD].nextId = C.TAIL;
597:                         return;
598:                     }
...
...
```

As a result, during liquidation, when calling `LibSRUtil.checkCancelShortOrder` in `PrimaryLiquidationFacet#L59`, the transaction will revert (`LibSRUtil#L57`) because there is no `short` associated with the user's `ShortRecord`.

```solidity
File: PrimaryLiquidationFacet.sol
47:     function liquidate(address asset, address shorter, uint8 id, uint16[] memory shortHintArray, uint16 shortOrderId)
48:         external
49:         isNotFrozen(asset)
50:         nonReentrant
51:         onlyValidShortRecord(asset, shorter, id)
52:         returns (uint88, uint88)
53:     {
...
...
59:         LibSRUtil.checkCancelShortOrder({
60:             asset: asset,
61:             in
`MaliciousShorter` can evade being redeemed or liquidated, leaving the `ShortRecord` exposed in the `OrderBook`
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/RedemptionFacet.sol#L56
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/PrimaryLiquidationFacet.sol#L47
https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/libraries/LibSRUtil.sol#L124


# Vulnerability details

## Impact

When a redeemer submits proposals specifying the `shorter`, `shortId`, and `shortOrderId` for each proposal, a `maliciousShorter` can transfer their `shortRecord NFT` to another user before the redeemer's transaction executes (`RedemptionFacet::proposeRedemption`), thus evading redemption. Similarly, when a liquidation is about to be executed, the `maliciousShorter` can transfer their `shortRecord NFT` and evade liquidation.

This means that a `shortRecord` with a low `CR` cannot be redeemed or liquidated, causing the liquidatable order to remain exposed in the `orderBook`. The attacker can repeat the process of transferring the `NFTShortRecord` each time an attempt is made to liquidate or redeem it.

This occurs because each time an `NFTShortRecord` is transferred, the previous `shortRecordId` is removed and a new one is created (`LibSRUtil#L139-L144`). Therefore, the `shortRecordId` parameter used in the liquidation or redemption will change, resulting in an attempt to liquidate a `shortRecordId` that no longer exists.

```solidity
File: LibSRUtil.sol
124:     function transferShortRecord(address from, address to, uint40 tokenId) internal {
...
...
138:         short.tokenId = 0;
139:         LibShortRecord.deleteShortRecord(asset, from, nft.shortRecordId);
140:         LibBridgeRouter.transferBridgeCredit(asset, from, to, short.collateral);
141: 
142:         uint8 id = LibShortRecord.createShortRecord(
143:             asset, to, SR.FullyFilled, short.collateral, short.ercDebt, short.ercDebtRate, short.dethYieldRate, tokenId
144:         );
...
...
149:     }
```

## Proof of Concept

The following test demonstrates how the `maliciousShorter` front-runs the redeemer's transaction by minting and transferring the `shortRecord NFT` to another user. As a result, the redemption is evaded, and the `maliciousShorter's shortRecord` is not included in the `redeemer's slate`:

```solidity
    // File: test/Redemption.t.sol
    function test_shortRecordIsTransferredBeforeRedeemerProposal() public {
        //
        address redeemer = address(1212);
        address maliciousShorter = address(121222);
        depositUsd(redeemer, DEF_REDEMPTION_AMOUNT);
        depositEth(redeemer, DEFAULT_AMOUNT);
        //
        // 1. Match orders
        fundLimitBidOpt(DEFAULT_PRICE, DEFAULT_AMOUNT, receiver);
        fundLimitShortOpt(DEFAULT_PRICE, DEFAULT_AMOUNT, sender);
        fundLimitBidOpt(DEFAULT_PRICE + 2, DEFAULT_AMOUNT, receiver);
        fundLimitShortOpt(DEFAULT_PRICE + 2, DEFAULT_AMOUNT, maliciousShorter);
        //
        //
Incorrect Handling of firstShortIdBelowOracle in BidMatchAlgo
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BidOrdersFacet.sol#L358-L409


# Vulnerability details

## Description


The bug occurs in the `_shortDirectionHandler` function, which is responsible for setting the direction of matching and determining the starting point for matching shorts when an incoming bid order is processed.

The specific issue arises when the algorithm is matching in a backward direction (from higher-priced shorts to lower-priced shorts) and encounters a short order that is below the oracle price, making it ineligible for matching. The `firstShortIdBelowOracle` is intended to mark the boundary between matchable and unmatchable shorts based on the oracle price.

However, the bug manifests when the algorithm incorrectly uses the firstShortIdBelowOracle as the starting point for the next iteration of matching, rather than using the first eligible short order above the oracle price.






## Proof of concept 
1. Assume (ID1) is a short order below the oracle price and (ID2) is the first short order above the oracle price.
2. A bid comes in that should match shorts starting from (ID2) and moving backward.
3. The _shortDirectionHandler incorrectly sets firstShortIdBelowOracle to (ID1) and starts matching from there.
4. (ID2) is skipped and not matched, even though it is eligible.

**Expected Behavior**
The matching algorithm should identify (ID2) as the first eligible short order above the oracle price when matching in a backward direction and start the matching process from (ID2).

**Actual Behavior**
The algorithm starts the matching process from (ID1), which is below the oracle price, causing (ID2) to be skipped.



## Impact
- Eligible shorts that traders expect to be matched may be left on the order book, leading to a less efficient market.
- Traders may lose confidence in the platform if their eligible shorts are not being matched as expected.
- Traders with shorts that are not matched may face financial consequences, especially in volatile market conditions.

## Recommendations
- Review the logic in the `_shortDirectionHandler` function to ensure that the `firstShortIdBelowOracle` is used correctly as a reference point and not as the starting point for backward matching.
- Implement additional checks or state variables to accurately determine the starting point for backward matching.



## Assessed type

Other
LibSRUtil::disburseCollateral incorrect yield calculation leads to overpayment of yield
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibSRUtil.sol#L22-L47


# Vulnerability details

## Impact
The issue with the yield calculation in the disburseCollateral function is that the formula used is incorrect. The function calculates the yield as follows:


`uint88 yield = collateral.mulU88(Vault.dethYieldRate - dethYieldRate);`

This calculation is wrong because it assumes that the yield is directly proportional to the difference between the current yield rate (Vault.dethYieldRate) and the previous yield rate (dethYieldRate). This is not the correct way to calculate the yield.


The correct way to calculate the yield should be based on the duration over which the yield is accrued, which is typically a fixed time period (e.g., one day, one week, etc.). The correct formula should be:


`uint88 yield = uint88((collateral * (Vault.dethYieldRate - dethYieldRate)) / C.YIELD_DELAY_SECONDS);`

Where C.YIELD_DELAY_SECONDS is the duration over which the yield is accrued (e.g., 86400 for one day).

## Proof of Concept
Suppose we have the following scenario:



collateral: 100 ETH

Vault.dethYieldRate: 0.1 (10% per year)

dethYieldRate: 0.05 (5% per year)

C.YIELD_DELAY_SECONDS: 1 week (604,800 seconds)


In this case, the correct way to calculate the yield would be:



yield = collateral * (Vault.dethYieldRate - dethYieldRate) * C.YIELD_DELAY_SECONDS / UINT88_MAX
     = 100 * (0.1 - 0.05) * 604,800 / UINT88_MAX
     = 2.75 ETH

However, the function's current implementation calculates the yield as:



yield = collateral.mulU88(Vault.dethYieldRate - dethYieldRate)
     = 100 * (0.1 - 0.05)
     = 5 ETH

This is incorrect because it does not account for the duration over which the yield is accrued. The function's calculation assumes that the yield is earned instantaneously, rather than over the specified C.YIELD_DELAY_SECONDS duration.


The impact of this incorrect yield calculation can be significant:



Overpayment of Yield: The function will disburse 5 ETH of yield, which is more than the correct amount of 2.75 ETH. This overpayment will reduce the overall collateral available in the system, potentially leading to liquidity issues or other problems.





## Tools Used
Manual Review

## Recommended Mitigation Steps
`-` `uint88 yield = collateral.mulU88(Vault.dethYieldRate - dethYieldRate);`


`+` uint88 yield = uint88((collateral * (Vault.dethYieldRate - dethYieldRate)) / C.YIELD_DELAY_SECONDS);



## Assessed type

Context
LibSRUtil::disburseCollateral does not decrease the shorter collateral 
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibSRUtil.sol#L22-L47


# Vulnerability details

## Impact
LibSRUtil::disburseCollateral does not decrease the shorter collateral which leaves the system in an inconsistent state

## Proof of Concept
`https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibSRUtil.sol#L22C1-L47C6`

The main issue with the collateral accounting in the disburseCollateral function is that it decrements the collateral from the global Vault.dethCollateral and Asset.dethCollateral values, without decrementing it from the specific user's collateral balance.

Below is the problematic part of the code
`Vault.dethCollateral -= collateral;
Asset.dethCollateral -= collateral;`

The issue with this approach is that the function is not properly tracking the collateral belonging to the specific user (shorter). By decrementing the global collateral values, the function is effectively reducing the total collateral available in the system, even though the collateral being disbursed belongs to a specific user. This means the shorter can call the disburseCollateral function and continue decreasing the collateral in the system



## Tools Used
Manual Review

## Recommended Mitigation Steps
The function should update the collateral balance of the specific user (shorter) alongside the global collateral values. This can be done by adding a dethCollateral field to the STypes.VaultUser struct and updating that value


## Assessed type

Error
Incorrect rounding logic in the estimateTWAP function leads to users losing funds
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/UniswapOracleLibrary.sol#L47-L71


# Vulnerability details

## Impact
Incorrect rounding logic in the estimateTWAP function leads to users losing funds

`https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/UniswapOracleLibrary.sol#L47C5-L71C6`

## Proof of Concept
Suppose we have the following input parameters:



amountIn: 100 ETH

secondsAgo: 10 seconds

pool: a Uniswap V3 pool

baseToken: ETH

quoteToken: USDC


The function calculates the tick as follows:


solidity

int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
int24 tick = int24(tickCumulativesDelta / int32(secondsAgo));

// Incorrect rounding logic
if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int32(secondsAgo) != 0)) {
    tick--;
}

Now, let's say the actual tickCumulativesDelta is -101, and secondsAgo is 10 seconds.


Using the incorrect rounding logic, the function would calculate the tick as follows:



tickCumulativesDelta = -101
secondsAgo = 10
tick = int24(-101 / 10) = int24(-10.1) = -10
if (tickCumulativesDelta < 0 && (-101 % 10 != 0)) {
    tick--; // Incorrectly rounds the tick to -11
}

The correct way to round the tick to the nearest integer towards negative infinity would be:



tick = int24(ceil(-101 / 10)) = int24(-10)

Now, let's assume that the getQuoteAtTick function correctly calculates the amountOut based on the tick value. With the incorrect rounding logic, the amountOut would be calculated based on a tick of -11, whereas the correct tick is -10.


This difference in the tick value can have a significant impact on the calculated amountOut. For example, if the price of USDC per ETH at a tick of -10 is 1,500 USDC, and the price at a tick of -11 is 1,495 USDC, the amountOut calculation would be off by 5 USDC, which can be a significant amount, especially for larger amountIn values.


In general, the impact of the incorrect rounding logic can be magnified as the amountIn value increases, or when the price difference between the correct and incorrect tick values is more significant. This can lead to inaccurate TWAP estimations, which can have downstream effects on the overall performance and reliability of the application.




## Tools Used
Manual Review

## Recommended Mitigation Steps
`-` // Always round to negative infinity
if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int32(secondsAgo) != 0)) {
    tick--;
}

`+` tick = int24(ceil(tickCumulativesDelta / int32(secondsAgo)))






## Assessed type

Error
Users can mint DUSD with less collateral than required which gives them free DUSD and may open a liquidatable position
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOrders.sol#L898-L938


# Vulnerability details

## Summary
Users can mint more DUSD than the value of collateral they provided for the Short Order when they cancel it.

## Description
When a user cancels a short order, the following happens:
Note: Each number below is a step and some steps jump to other steps.

1. If the short's associated short record has a status of `SR.Closed` i.e it hasn't been matched, it is deleted and the process moves to step 5.

**[LibOrders.sol#L898-L902](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOrders.sol#L898-L902)**
```solidity
        if (shortRecord.status == SR.Closed) {
            // @dev creating shortOrder automatically creates a closed shortRecord which also sets a shortRecordId
            // @dev cancelling an unmatched order needs to also handle/recycle the shortRecordId
❌          LibShortRecord.deleteShortRecord(asset, shorter, shortRecordId);
        } else {
```

2. Else, if the short record is partially filled or filled, it is checked if it has less than the minimum erc debt (`minShortErc`). 

**[LibOrders.sol#L904](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOrders.sol#L904)**
```solidity
        if (shortRecord.ercDebt < minShortErc) {
```

3. If it has less debt than `minShortErc`, the short record is filled up to the `minShortErc` and its status is changed to `SR.FullyFilled` and the process moves to step 5.

**[LibOrders.sol#L905C1-L923](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOrders.sol#L905C1-L923C1)**
```solidity
                // @dev prevents leaving behind a partially filled SR is under minShortErc
                // @dev if the corresponding short is cancelled, then the partially filled SR's debt will == minShortErc
                uint88 debtDiff = minShortErc - shortRecord.ercDebt;
                {
                    STypes.Vault storage Vault = s.vault[vault];

                    uint88 collateralDiff = shortOrder.price.mulU88(debtDiff).mulU88(LibOrders.convertCR(shortOrder.shortOrderCR));

❌                  LibShortRecord.fillShortRecord(
                        asset,
                        shorter,
                        shortRecordId,
❌                      SR.FullyFilled,
                        collateralDiff,
                        debtDiff,
                        Asset.ercDebtRate,
                        Vault.dethYieldRate
                    );

```

4. Else, if it has more debt than `minShortErc`, the status of the short record is changed to `SR.FullyFilled`

**[LibOrders.sol#L934](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOrders.sol#L934)**
``
Partially filled Short Records created without a short order cannot be liquidated and exited
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOrders.sol#L591-L597
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibSRUtil.sol#L57
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibSRUtil.sol#L84
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibSRUtil.sol#L57


# Vulnerability details

## Summary
A user can create a partially filled Short Record without a Short Order which can't be liquidated and exited.

## Description
When a Short Order is being created it tries to fill its Short Record. If it fills the Short Record, the Short Record is given a filled status (`SR.FullyFilled`) and the Short Order isn't added to the market. But if it doesn't fill the Short Record, it is given a partially filled status (`SR.PartiallyFilled`) and the remaining part of the Short Order is added to the market.

The issue is the current implementation doesn't add the Short Order to the market every time the Short Record is partially filled. It does this in the `sellMatchAlgo()` function loop when it tries to match bids.

**[LibOrders.sol#L591-L597](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOrders.sol#L591-L597)**
```solidity
❌                       matchIncomingSell(asset, incomingAsk, matchTotal);
❌                       if (incomingAsk.ercAmount.mul(incomingAsk.price) >= minAskEth) {
                            addSellOrder(incomingAsk, asset, orderHintArray);
                        }
                        s.bids[asset][C.HEAD].nextId = C.TAIL;
                        return;
```

When the Short Order is being matched in the `sellMatchAlgo()` loop, it encounters the check in the `if` statement above. If the value of the erc remaining in the short is less than `minAskEth` it is not added to the market. The Short Record is already given the `SR.PartiallyFilled` status before the check.

When this happens, the Short Record is created with no associated Short Order. This prevents the user from exiting the Short Record and a liquidator from liquidating the position if it ever becomes liquidatable. These actions revert with `InvalidShortOrder()` error in the following portion of the code.

1. **Exiting**
When a user tries to exit using any of the exit functions, he has to pass a Short Order id.

**[ExitShortFacet.sol#L41](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ExitShortFacet.sol#L41)**
```solidity
    function exitShortWallet(
     address asset, 
     uint8 id, 
     uint88 buybackAmount, 
❌   uint16 shortOrderId
    )
```

**[ExitShortFacet.sol#L87](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/co
Checker
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/Diamond.sol#L4


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

## Tools Used

## Recommended Mitigation Steps


## Assessed type

Decimal
Just Checking
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/Diamond.sol#L4


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

## Tools Used

## Recommended Mitigation Steps


## Assessed type

Access Control