xanoutas/solidity-security-findings huggingface dataset:
Potential DOS to attempts in getting the LSTs out of the protocol
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/BridgeRouterFacet.sol#L101-L123


# Vulnerability details


## Proof of Concept

Take a look at https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/BridgeRouterFacet.sol#L101-L123

```solidity
    function withdraw(address bridge, uint88 dethAmount) external nonReentrant {
        if (dethAmount == 0) revert Errors.ParameterIsZero();

        (uint256 vault, uint256 bridgePointer) = _getVault(bridge);

        uint88 fee;
        if (vault == VAULT.ONE) {
            uint88 dethAssessable = vault.assessDeth(bridgePointer, dethAmount, rethBridge, stethBridge);
            if (dethAssessable > 0) {
                uint256 withdrawalFeePct = LibBridgeRouter.withdrawalFeePct(bridgePointer, rethBridge, stethBridge);
                if (withdrawalFeePct > 0) {
                    fee = dethAssessable.mulU88(withdrawalFeePct);
                    dethAmount -= fee;
                    s.vaultUser[vault][address(this)].ethEscrowed += fee;
                }
            }
        }

        uint88 ethAmount = _ethConversion(vault, dethAmount);
        vault.removeDeth(dethAmount, fee);
        IBridge(bridge).withdraw(msg.sender, ethAmount);
        emit Events.Withdraw(bridge, msg.sender, dethAmount, fee);
    }
```

This function is used in order to withdraw LST out of the protocol, it inlcudes an important logic of deducting the the withdrwal fee percentage, i.e https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibBridgeRouter.sol#L111-L141

```solidity
    function withdrawalFeePct(uint256 bridgePointer, address rethBridge, address stethBridge) internal view returns (uint256 fee) {
        IBridge bridgeReth = IBridge(rethBridge);
        IBridge bridgeSteth = IBridge(stethBridge);

        // Calculate rETH market premium/discount (factor)
        uint256 unitRethTWAP = OracleLibrary.estimateTWAP(1 ether, 30 minutes, VAULT.RETH_WETH, VAULT.RETH, C.WETH);
        uint256 unitRethOracle = bridgeReth.getUnitDethValue();
        uint256 factorReth = unitRethTWAP.div(unitRethOracle);
        // Calculate stETH market premium/discount (factor)
        uint256 unitWstethTWAP = OracleLibrary.estimateTWAP(1 ether, 30 minutes, VAULT.WSTETH_WETH, VAULT.WSTETH, C.WETH);
        uint256 unitWstethOracle = bridgeSteth.getUnitDethValue();
        uint256 factorSteth = unitWstethTWAP.div(unitWstethOracle);
        if (factorReth > factorSteth) {
            // rETH market premium relative to stETH
            if (bridgePointer == VAULT.BRIDGE_RETH) {
                // Only charge fee if withdrawing rETH
                return factorReth.div(factorSteth) - 1 ether;
            }
        } else if (factorSteth > factorReth) {
            // stETH market premium relative to rETH
            if (bridgePointer == VAULT.BRIDGE_S
User can create small positions due to the current implementation of checks
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ShortOrdersFacet.sol#L35-L90


# Vulnerability details


## Proof of Concept

Protocol in some instances place an enforcal on the minimum order sizes before the bids have been matched, for example take a look at https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ShortOrdersFacet.sol#L35-L90

```solidity
    function createLimitShort(
        address asset,
        uint80 price,
        uint88 ercAmount,
        MTypes.OrderHint[] memory orderHintArray,
        uint16[] memory shortHintArray,
        uint16 shortOrderCR
    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant {
        MTypes.CreateLimitShortParam memory p;
        STypes.Asset storage Asset = s.asset[asset];
        STypes.Order memory incomingShort;


        // @dev create "empty" SR. Fail early.
        incomingShort.shortRecordId = LibShortRecord.createShortRecord(asset, msg.sender, SR.Closed, 0, 0, 0, 0, 0);


        uint256 cr = LibOrders.convertCR(shortOrderCR);
        if ((shortOrderCR + C.BID_CR) < Asset.initialCR || cr >= C.CRATIO_MAX_INITIAL) {
            revert Errors.InvalidCR();
        }


        // @dev minShortErc needs to be modified (bigger) when cr < 1
        p.minShortErc = cr < 1 ether ? LibAsset.minShortErc(asset).mul(1 ether + cr.inv()) : LibAsset.minShortErc(asset);
        p.eth = price.mul(ercAmount);
        p.minAskEth = LibAsset.minAskEth(asset);
        if (ercAmount < p.minShortErc || p.eth < p.minAskEth) revert Errors.OrderUnderMinimumSize();


        // For a short, need enough collateral to cover minting ERC (calculated using initialCR)
        if (s.vaultUser[Asset.vault][msg.sender].ethEscrowed < p.eth.mul(cr)) revert Errors.InsufficientETHEscrowed();


        incomingShort.addr = msg.sender;
        incomingShort.price = price;
        incomingShort.ercAmount = ercAmount;
        incomingShort.id = Asset.orderIdCounter;
        incomingShort.orderType = O.LimitShort;
        incomingShort.creationTime = LibOrders.getOffsetTime();
        incomingShort.shortOrderCR = shortOrderCR; // 170 -> 1.70x


        p.startingId = s.bids[asset][C.HEAD].nextId;
        STypes.Order storage highestBid = s.bids[asset][p.startingId];
        // @dev if match and match price is gt .5% to saved oracle in either direction, update startingShortId
        if (highestBid.price >= incomingShort.price && highestBid.orderType == O.LimitBid) {
            LibOrders.updateOracleAndStartingShortViaThreshold(asset, LibOracle.getPrice(asset), incomingShort, shortHintArray);
        }


        p.oraclePrice = LibOracle.getSavedOrSpotOraclePrice(asset);
        if (LibSRUtil.checkRecoveryModeViolation(asset, cr, p.oraclePrice)) {
            revert Errors.BelowRecoveryModeCR();
        }


        // @dev reading spot oracle price
        if (incomingShort.price < p.orac
Deviation in the stale checks would allow for wrong prices to be used
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L19-L67


# Vulnerability details


## Proof of Concept

Take a look at https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L19-L67

```solidity
    function getOraclePrice(address asset) internal view returns (uint256) {
        AppStorage storage s = appStorage();
        AggregatorV3Interface baseOracle = AggregatorV3Interface(s.baseOracle);
        uint256 protocolPrice = getPrice(asset);

        AggregatorV3Interface oracle = AggregatorV3Interface(s.asset[asset].oracle);
        if (address(oracle) == address(0)) revert Errors.InvalidAsset();

        try baseOracle.latestRoundData() returns (uint80 baseRoundID, int256 basePrice, uint256, uint256 baseTimeStamp, uint80) {
            if (oracle == baseOracle) {
                // @dev multiply base oracle by 10**10 to give it 18 decimals of precision
                uint256 basePriceInEth = basePrice > 0 ? uint256(basePrice * C.BASE_ORACLE_DECIMALS).inv() : 0;
                basePriceInEth = baseOracleCircuitBreaker(protocolPrice, baseRoundID, basePrice, baseTimeStamp, basePriceInEth);
                return basePriceInEth;
            } else {
                // prettier-ignore
                (
                    uint80 roundID,
                    int256 price,
                    /*uint256 startedAt*/
                    ,
                    uint256 timeStamp,
                    /*uint80 answeredInRound*/
                ) = oracle.latestRoundData();
                uint256 priceInEth = uint256(price).div(uint256(basePrice));
                oracleCircuitBreaker(roundID, baseRoundID, price, basePrice, timeStamp, baseTimeStamp);
                return priceInEth;
            }
        } catch {
            if (oracle == baseOracle) {
                //@audit
                return twapCircuitBreaker();
            } else {
                // prettier-ignore
                (
                    uint80 roundID,
                    int256 price,
                    /*uint256 startedAt*/
                    ,
                    uint256 timeStamp,
                    /*uint80 answeredInRound*/
                ) = oracle.latestRoundData();
                if (roundID == 0 || price == 0 || timeStamp > block.timestamp) revert Errors.InvalidPrice();

                uint256 twapInv = twapCircuitBreaker();
                uint256 priceInEth = uint256(price * C.BASE_ORACLE_DECIMALS).mul(twapInv);
                return priceInEth;
            }
        }
    }
```

This function is used to get the price from the oracle, it now includes a try/catch logic in order to ensure a more robust integration, issue here is that there are two deviaitional implementation of the price returned depending on Chainlink's `latestRoundData()`availaability, if the call in the try block fails for wh
Multiple Orders Requirement for Large Trades is Prone to Slippage Issues
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Users executing trades above the 300 million limit by splitting them into multiple orders incur higher transaction fees. Each additional transaction required to split the order increases the cost, impacting the overall efficiency and attractiveness of the platform for large trades.

Additionally, splitting large orders into smaller ones could lead to increased slippage, especially in less liquid markets. This slippage can negatively impact the execution price for the trader.

These large orders, when split and executed over time, can have a prolonged impact on the market, potentially leading to unfavorable price movements for the trader.


## Proof of Concept
https://github.com/code-423n4/2024-03-dittoeth//blob/main/contracts/facets/BidOrdersFacet.sol#L40

```solidity
function createBid(
        address asset,
        uint80 price,
     >>  uint88 ercAmount,
        bool isMarketOrder,
        MTypes.OrderHint[] calldata orderHintArray,
        uint16[] calldata shortHintArray
    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant returns (uint88 ethFilled, uint88 ercAmountLeft) {
        LibOrders.updateOracleAndStartingShortViaTimeBidOnly(asset, shortHintArray);

        return _createBid(msg.sender, asset, price, ercAmount, isMarketOrder, orderHintArray, shortHintArray);
    }

```


## Tools Used
Manual

## Recommended Mitigation Steps
1. Develop an order aggregation mechanism that allows users to submit large orders as a single transaction, which the smart contract then internally manages as multiple smaller orders. This approach would reduce the complexity and potentially lower the transaction costs for the user.


2. Introduce batch processing of orders where feasible, allowing multiple orders to be processed in a single transaction. This method can reduce gas costs and slippage by taking advantage of economies of scale.

3. Implement slippage protection mechanisms that allow users to specify a maximum acceptable slippage for their orders. If the execution price would result in slippage beyond this threshold due to order splitting, the transaction could be reverted or adjusted accordingly.



## Assessed type

Other
Bids not lesser than the minimum bids would wrongly get reverted as bids less than minimum bids due to an overview
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details


## Proof of Concept

Whenever an asked bid is to be matched the `sellMatchAlgo()` gets called, now take a look at this section of the function https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOrders.sol#L600-L615

```solidity
                    if (incomingAsk.ercAmount == highestBid.ercAmount) {
                        matchOrder(s.bids, asset, highestBid.id);
                        updateBidOrdersOnMatch(s.bids, asset, highestBid.id, true);
                    } else {
                        highestBid.ercAmount -= incomingAsk.ercAmount;
                        s.bids[asset][highestBid.id].ercAmount = highestBid.ercAmount;
                        updateBidOrdersOnMatch(s.bids, asset, highestBid.id, false);
                        // Check reduced dust threshold for existing limit orders
                        if (highestBid.ercAmount.mul(highestBid.price) < LibAsset.minBidEth(asset).mul(C.DUST_FACTOR)) {
                            cancelBid(asset, highestBid.id);
                        }
                    }
                    incomingAsk.ercAmount = 0;
                    matchIncomingSell(asset, incomingAsk, matchTotal);
                    return;
                }
```

This essectially checks to ensure that after matching the ask not less than dust is added back to the order book, i.e the `else` block since the if block is for when the amounts match, however the calculation to check for the remaining amount not being less than the minimum accepted value is flawed, because of an additional multiplication to `C_DUST_FACTOR` which is `0.5` ether.

The right check should be `if (highestBid.ercAmount.mul(highestBid.price) < LibAsset.minBidEth(asset)`, but with the addition of the multiplication to `C_DUST_FACTOR` which in short is `0.5 ether` this heavily overvalues the right side of the equation and makes bids that've beeen matched that didn't leave less than the minimum accepted eth value back in the order book to have the transaction revert

## Impact

Valid attempts would fail causing DOS at matching asks since now the `minEther` is very high

## Recommended Mitigation Steps

Consider not multiplying to `C_DUST_FACTOR` in `sellMatchAlgo()` and instead just check `if (highestBid.ercAmount.mul(highestBid.price) < LibAsset.minBidEth(asset)`



## Assessed type

Error
Heavy precision loss whenever increasing the shares on match
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details


## Proof of Concept

Take a look at https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOrders.sol#L40-L54

```solidity
    function increaseSharesOnMatch(address asset, STypes.Order memory order, MTypes.Match memory matchTotal, uint88 eth) internal {
        AppStorage storage s = appStorage();

        // @dev use the diff to get more time (2159), to prevent overflow at year 2106
        uint32 timeTillMatch = getOffsetTime() - order.creationTime;
        if (timeTillMatch > C.MIN_DURATION) {
            // shares in eth-days
            uint88 shares = eth * (timeTillMatch / 1 days);
            matchTotal.dittoMatchedShares += shares;

            uint256 vault = s.asset[asset].vault;
            s.vaultUser[vault][order.addr].dittoMatchedShares += shares;
        }
    }

```

This is used as a helper function whenvever closing, case is that this directlyh deals with shares and while calculating the division is always ensured to come before the multiplication, i.e ` shares = eth * (timeTillMatch / 1 days)`.

## Impact

Users unfairly have their shares truncated

> Keep in mind that the division is by `1 days` (86400) so this leads to quite a heavy truncation which is why this suffices as medium.

## Recommended Mitigation Steps

Consider multiplying before dividing in `increaseSharesOnMatch()`



## Assessed type

Token-Transfer
High-Frequency Trading and Front Running Risks
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/BidOrdersFacet.sol#L40


# Vulnerability details

## Impact
High-frequency trading and front-running practices can significantly impact the fairness and integrity of the trading platform in several ways:

- Entities with the ability to perform transactions more quickly due to technological advantages or network positioning can manipulate market prices to their benefit before others have a chance to react.

- Regular users may lose confidence in the trading platform if they perceive it to be dominated by high-frequency traders who can consistently outmaneuver them.

- The exploitation of order hint mechanisms and transaction sequencing can create an uneven playing field, where only a few participants can consistently secure profitable positions.

## Proof of Concept
These functions are designed to optimize the order placement and matching process by utilizing hint arrays, which can potentially be exploited by entities engaging in high-frequency trading strategies, aiming to preempt other transactions for advantageous positions.

- createBid
- createForcedBid
- _createBid
- bidMatchAlgo

## Tools Used
Manual

## Recommended Mitigation Steps
Implementing privacy-preserving mechanisms for transactions could help obscure the details of pending transactions, making it more difficult for front-runners to anticipate and exploit other users' trades. Techniques such as commit-reveal schemes or zero-knowledge proofs can be explored.




## Assessed type

MEV
The `bridgePointer` is not properly specified in `BridgeRouterFacet::_getVault`.
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibBridgeRouter.sol#L30
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/BridgeRouterFacet.sol#L157


# Vulnerability details

## Impact

When depositing via `BridgeRouterFacet::deposit`, the user needs to specify the `bridge` and the `amount`. The issue arises when the user specifies the `rethBridge`, but in `_getVault()`, the `bridgePointer` variable is left uninitialized, resulting in `bridgePointer=0`:

```solidity
File: BridgeRouterFacet.sol
157:         if (bridge == rethBridge) {
158:             vault = VAULT.ONE;
159:         }
```

As a result, `bridgePointer` is set to zero, causing the amount to be credited to `bridgeCreditSteth` instead of `bridgeCreditReth` in `LibBridgeRouter#L30`.

```solidity
File: LibBridgeRouter.sol
30:                 VaultUser.bridgeCreditSteth += amount;
```

This discrepancy in accounting may lead to errors in tracking the deposited amount.


## Tools used

Manual review

## Proof of Concept


## Recommended Mitigation Steps

To address this issue, ensure that the `bridgePointer` for the `rethBridge` is correctly specified in `_getVault`:

```diff
    function _getVault(address bridge) private view returns (uint256 vault, uint256 bridgePointer) {
        if (bridge == rethBridge) {
            vault = VAULT.ONE;
++          bridgePointer = VAULT.BRIDGE_RETH;
        } else if (bridge == stethBridge) {
            vault = VAULT.ONE;
            bridgePointer = VAULT.BRIDGE_STETH;
        } else {
            vault = s.bridge[bridge].vault;
            if (vault == 0) revert Errors.InvalidBridge();
        }
    }
```

By adding this modification, the system will correctly credit the deposited amount to `bridgeCreditReth`, ensuring accurate accounting.








## Assessed type

Context
Hint Array Inaccuracy Post-Reorganization
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/BidOrdersFacet.sol#L40


# Vulnerability details

## Impact
Blockchain reorganizations (reorgs) can lead to a scenario where the transactions that were considered final get replaced by alternative transactions. If these replaced transactions involve order placements, cancellations, or matches, the hint arrays passed to the functions above might no longer accurately represent the order book's state.

In the worst-case scenario, transactions might fail if the hint array suggests an order operation that is no longer valid, such as matching an order that has been canceled or filled by transactions included in the reorg.

## Proof of Concept
The issue primarily affects the functions and mechanisms that rely on hint arrays for optimizing gas usage during order placement and matching. This includes the following parts of the code

- `createBid(...)`
- `createForcedBid(...)`
- `_createBid(...)`
- `bidMatchAlgo(...)`
- `_shortDirectionHandler(...)`

These functions use hint arrays to efficiently place or match orders by providing a pre-sorted array of order IDs that reduce the need for on-chain sorting.



## Tools Used
Manual

## Recommended Mitigation Steps
Introduce a mechanism that validates hint arrays against the current state of the order book before proceeding with any order placement or matching. This would involve checking if the hint array still represents a valid path through the order book. If not, the transaction could revert or fall back to a less gas-optimized path that does not rely on the hint array.



## Assessed type

Context
Lack of Finalization for Partially Filled Asks
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The `matchlowestSell` function does not account for updating the `ercAmount` of ask orders that are only partially filled by incoming bids, potentially leading to inaccuracies in the displayed order book.

## Proof of Concept
[Github:](https://github.com/code-423n4/2024-03-dittoeth/blob/main/contracts/facets/BidOrdersFacet.sol#L224)

```solidity
if (lowestSell.orderType == O.LimitShort) {
// ...
} else {
    // Match ask
    s.vaultUser[s.asset[asset].vault][lowestSell.addr].ethEscrowed += fillEth;
    matchTotal.askFillErc += fillErc;
}
matchTotal.fillErc += fillErc;
matchTotal.fillEth += fillEth;
matchTotal.lastMatchPrice = lowestSell.price;
```

### Scenario Steps:

1. An incoming bid order is placed that matches with an existing ask order, but only partially fills it.
   
2. The `matchlowestSell` function is executed for the incoming bid, matching it against the lowest available ask order.

3. In the scenario where an ask order is partially filled, the `matchlowestSell` function correctly updates `ethEscrowed` for the seller and records the filled ERC amount in `matchTotal.askFillErc`. However, it fails to adjust the remaining `ercAmount` for the ask order that remains on the market. This oversight means the order book could display incorrect information about the available quantity for that ask order, potentially misleading future matching attempts.

4. The ask order remains in the order book with its original `ercAmount`, not reflecting the partial fill. This could lead to mismatches in the order book, affecting market integrity and participant decision-making.


## Tools Used
Manual

## Recommended Mitigation Steps
Implement a mechanism within `matchlowestSell` or at an appropriate point following a partial match, to update the remaining `ercAmount` for partially filled ask orders. 



## Assessed type

Context