xanoutas/solidity-security-findings huggingface dataset:
Failure to update Oracle due to rounding issue leading incorrectness of the orderbook
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

To determine whether to update oracle and starting short ID , the difference between the incoming order price and the saved price divided by the saved price is checked if it is >   chainlink threshold of 0.5%. if the price difference divided by the saved price is greater than 0.5%, updateoracleandstartingshort function is invoked. The issue is that implementation of these calculation is prone to rounding errors rising from integer division.This is because the protocol uses PRB math which at the moment only supports rounding down to zero. As a result, the price deviation check might indicate that there is no  need to update the oracle and starting short ID although it should be updated.

## Impact
Incorrectness of the orderbook due to wrong oracle price and starting short ID.
## Proof of Concept

        // @dev handle .5% deviations in either directions
        if (incomingOrder.price >= savedPrice) {
            orderPriceGtThreshold = (incomingOrder.price - savedPrice).div(savedPrice) > 0.005 ether;
        } else {
            orderPriceGtThreshold = (savedPrice - incomingOrder.price).div(savedPrice) > 0.005 ether;
        }


        if (orderPriceGtThreshold) {
            _updateOracleAndStartingShort(asset, shortHintArray);
        }
The above code is from updateOracleAndStartingShortViaThreshold function. Depending on which is greater,(the incoming price or the saved price) it calculates the difference then divides it by the saved price then checks if it is > 0.005 ether. As you can note it directly divides the integers in question meaning it is prone to rounding down to zero errors.  
Example
lets assume; the incoming price = 1555
               the saved price = 1466
      the result is (1555-1466)div 1466 which is 0.06.
Now since the protocol utilizes prb math library, it rounds the above result to 0  which implies that it is not greater than 0.05 although it actually is greater than 0.05. As a result , the oracle price and starting short ID isn't updated as it should.
You can check that prb math only supports rounding down to zero here:https://github.com/PaulRBerg/prb-math/discussions/186
## Tools Used

## Recommended Mitigation Steps
Since PRB math  Operates with signed and unsigned denary fixed-point numbers, with 18 trailing decimals recommend modifying the above code to this;

uint256 priceDifference;
if (incomingOrder.price >= savedPrice) {
    priceDifference = incomingOrder.price.sub(savedPrice);
} else {
    priceDifference = savedPrice.sub(incomingOrder.price);
}

uint256 percentageDifference = priceDifference.mul(1e18).div(savedPrice);
orderPriceGtThreshold = percentageDifference > 5000; // 5000 represents 0.5%





## Assessed type

Math
Incorrect Price Conversion Due to Decimal Precision Discrepancy
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The project aims to support various asset classes, as mentioned in the Litepaper: "DittoETH can support pegged assets for fiat currencies, cryptocurrencies (long and short), and commodities..." To obtain prices for different assets, especially non-USD assets, the project plans to utilize asset-specific oracles. However, a critical vulnerability arises when fetching the asset price from the asset-specific oracle and performing calculations to determine the asset price without considering whether there is a discrepancy in decimals between baseOracle and the asset-specific oracle. This could result in incorrect values, potentially leading to users or the protocol losing funds.

## Proof of Concept

The bug occurs in the `LibOracle::getOraclePrice()` function, specifically when the `oracle` is not equal to `baseOracle`, and we enter the else case. Here, the price is retrieved from the Chainlink price feed without checking for any decimal discrepancies, leading to unintended results. While the `baseOracle` always will be the Chainlink's ETH/USD price feed which has 8 decimals, asset-specific oracles may have a different decimals, for example the [AMPL/ETH oracle](https://etherscan.io/address/0x492575FDD11a0fCf2C6C719867890a7648d526eB#readContract), which has 18 decimals. In such cases, operations like uint256(2518992200000000000).div(uint256(326640991229)) result in values like 7711806, which will lead to all the user trades being performed with highly inaccurate asset prices. Unfortunately, the current implementation of `oracleCircuitBreaker` also won't help with this issue, as it does not consider decimal discrepancies and therefore the incorrect price will be returned.

```solidity
    function getOraclePrice(address asset) internal view returns (uint256) {
        ...
            } else {
                // prettier-ignore
                (
                    uint80 roundID,
                    int256 price,
                    /*uint256 startedAt*/
                    ,
                    uint256 timeStamp,
                    /*uint80 answeredInRound*/
                ) = oracle.latestRoundData();
                uint256 priceInEth = uint256(price).div(uint256(basePrice)); // @audit-issue if there is decimal discrepancy - this will lead to issues.
                oracleCircuitBreaker(roundID, baseRoundID, price, basePrice, timeStamp, baseTimeStamp);
                return priceInEth;
            }
        ...
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

Check the decimals of the asset-specific oracle before calculating the asset price and scale the basePrice accordingly.



## Assessed type

Decimal
increaseCollateral() function makes shorter lose yield
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Shorter will lose so far gained yield if chooses to increase the collateral of his position. ShortRecord.updatedAt and ShortRecord.dethYieldRate are updated but the yield is not given to the user.

## Proof of Concept
You can place the following 2 tests at the bottom of the file in BidOrders.t.sol.

Example of a flow with increased collateral and the lost yield:
```solidity
function test_increaseCollateral_lostYield() public {
        address seller = makeAddr("seller");
        address buyer = makeAddr("buyer");

        // 1. Create Short Record
        fundLimitShortOpt(DEFAULT_PRICE, DEFAULT_AMOUNT, seller);
        STypes.Order[] memory shorts = diamond.getShorts(asset);
        uint8 shortRecordId = shorts[0].shortRecordId;

        fundLimitBidOpt(DEFAULT_PRICE, DEFAULT_AMOUNT, buyer);

        STypes.ShortRecord memory shortRecordAfterMatch = diamond.getShortRecord(asset, seller, shortRecordId);

        // 2. Pass time and mimic rate change
        skip(3600); // 1 hour
        diamond.setDethYieldRate(vault, 1e15);

        // 3. Increase collateral with 1 eth
        uint88 increaseCollateralAmount = 1 ether;
        depositEth(seller, increaseCollateralAmount);
        vm.prank(seller);
        diamond.increaseCollateral(asset, shortRecordId, increaseCollateralAmount);

        // 4. Exit Short Record
        depositUsd(seller, DEFAULT_AMOUNT);
        vm.prank(seller);
        diamond.exitShortErcEscrowed(asset, shortRecordId, DEFAULT_AMOUNT, 0);

        STypes.VaultUser memory userVaultAfterExit = diamond.getVaultUserStruct(1, seller);

        // no yield was gained
        assert(userVaultAfterExit.ethEscrowed == increaseCollateralAmount + shortRecordAfterMatch.collateral);
    }

```

Example of a flow without increased collateral and gained yield:
```solidity
function test_gainedYield() public {
        address seller = makeAddr("seller");
        address buyer = makeAddr("buyer");

        // 1. Create Short Record
        fundLimitShortOpt(DEFAULT_PRICE, DEFAULT_AMOUNT, seller);
        STypes.Order[] memory shorts = diamond.getShorts(asset);
        uint8 shortRecordId = shorts[0].shortRecordId;

        fundLimitBidOpt(DEFAULT_PRICE, DEFAULT_AMOUNT, buyer);

        STypes.ShortRecord memory shortRecordAfterMatch = diamond.getShortRecord(asset, seller, shortRecordId);

        // 2. Pass time and mimic rate change
        skip(3600); // 1 hour
        diamond.setDethYieldRate(vault, 1e15);

        // 3. Exit Short Record
        depositUsd(seller, DEFAULT_AMOUNT);
        vm.prank(seller);
        diamond.exitShortErcEscrowed(asset, shortRecordId, DEFAULT_AMOUNT, 0);

        STypes.VaultUser memory userVaultAfterExit = diamond.getVaultUserStruct(1, seller);

        // yield was gained
        assert(userVaultAfterExit.ethEscrowed !
Shorters could never get yield
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Shorters are able to get yield whenever they disburse their collateral. However, `YIELD_DELAY_SECONDS` must pass for them to not lose their yield to the `TAPP`. Since, users are able to propose redemptions for particular shorts, they can always do it before `YIELD_DELAY_SECONDS` have passed and thus, the shorters might never get the yield.

## Proof of Concept
1. User A creates a short.
2. `YIELD_DELAY_SECONDS` have not passed.
3. User B creates a proposal for that short.
4. After most of the `RedemptionFacet::proposeRedemption()` function has executed, there is this line:
```solidity
LibSRUtil.disburseCollateral(p.asset, p.shorter, p.colRedeemed, currentSR.dethYieldRate, currentSR.updatedAt);
```
And this is what the `LibSRUtil::disburseCollateral` function does:
```solidity
function disburseCollateral(address asset, address shorter, uint88 collateral, uint256 dethYieldRate, uint32 updatedAt)
        internal
    {
        AppStorage storage s = appStorage();

        STypes.Asset storage Asset = s.asset[asset];
        uint256 vault = Asset.vault;
        STypes.Vault storage Vault = s.vault[vault];

        Vault.dethCollateral -= collateral;
        Asset.dethCollateral -= collateral;
        // Distribute yield
        uint88 yield = collateral.mulU88(Vault.dethYieldRate - dethYieldRate);
        if (yield > 0) {
            /*
            @dev If somebody exits a short, gets liquidated, decreases their collateral before YIELD_DELAY_SECONDS duration is up,
            they lose their yield to the TAPP
            */
            bool isNotRecentlyModified = LibOrders.getOffsetTime() - updatedAt > C.YIELD_DELAY_SECONDS;
            if (isNotRecentlyModified) {
                s.vaultUser[vault][shorter].ethEscrowed += yield;
            } else {
                s.vaultUser[vault][address(this)].ethEscrowed += yield;
            }
        }
    }
```
5. Since `YIELD_DELAY_SECONDS` have not passed, the shorter loses his yield to the `TAPP`.

## Tools Used
Manual Review

## Recommended Mitigation Steps
You can have a check whether `C.YIELD_DELAY_SECONDS` have passed and if not enough time has passed, then revert.


## Assessed type

Other
Potential loss of collateral due to short order matching in exit process
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ExitShortFacet.sol#L185-L197
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/BidOrdersFacet.sol#L243-L252


# Vulnerability details

## Impact

This discovery reveals a serious weakness within the system that could greatly affect users. If taken advantage of, users might suffer losses in the collateral they put up when trying to close a short position. This vulnerability occurs when a ShortRecord, which is only partly filled, matches with the user's own short order during the closing process and manages to pay off the entire debt. Consequently the collateral that was initially put up in the short order may end up being lost, potentially causing financial harm to the users involved.

## Proof of Concept

When a user attempts to exit their short position, the system performs a critical check to see if the debt has been fully paid back before refunding the collateral. However, before reaching this point, there's an intermediate step where partially filled short orders are matched with incoming bids. This matching process occurs within the `matchlowestSell()`function in the `BidOrdersFacet`. Here, the system updates the debt and collateral of the short position without thorough validation, potentially leading to inaccuracies.

Later on, in the `exitShort()` function from the `ExitShortFacet`, the system performs the crucial check to determine if the debt has been fully paid back. If it mistakenly believes that the debt is fully paid back due to the earlier inaccuracies, it proceeds to refund the collateral prematurely. This premature collateral refunding occurs within the same exitShort function, resulting in users losing their collateral unexpectedly.

#### Relevant Code Snippets:

`ExitShortFacet.exitShort()`

```
// Create bid with current msg.sender
        (e.ethFilled, e.ercAmountLeft) =
            IDiamond(payable(address(this))).createForcedBid(msg.sender, e.asset, price, e.buybackAmount, shortHintArray);
        if (e.ethFilled == 0) revert Errors.ExitShortPriceTooLow();
        e.ercFilled = e.buybackAmount - e.ercAmountLeft;
        Asset.ercDebt -= e.ercFilled;
        s.assetUser[e.asset][msg.sender].ercEscrowed -= e.ercFilled;

        // Refund the rest of the collateral if ercDebt is fully paid back
        if (e.ercDebt == e.ercFilled) {
            // Full Exit
            LibSRUtil.disburseCollateral(e.asset, msg.sender, e.collateral, short.dethYieldRate, short.updatedAt);
            LibShortRecord.deleteShortRecord(e.asset, msg.sender, id); // prevent reentrancy
        }

```

`BidOrdersFacet.matchlowestSell()`

```
          LibShortRecord.fillShortRecord(
                asset,
                lowestSell.addr,
                lowestSell.shortRecordId,
                status,
                shortFillEth,
                fillErc,
Withdraw deth from other vault gone wrong
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
A user has the option to deposit Deth into VAULT.ONE (reth/steth bridge) or directly into the vault. However, a
vulnerability arises when a user attempts to withdraw Deth at a 1:1 ratio from a vault other than VAULT.ONE, as
the getDethTotal function only checks for bridges of a specific vault.

## Proof of Concept
```
 function deposit(address bridge, uint88 amount) external nonReentrant {
        if (amount < C.MIN_DEPOSIT) revert Errors.UnderMinimumDeposit();

        (uint256 vault, uint256 bridgePointer) = _getVault(bridge);
        // @dev amount after deposit might be less, if bridge takes a fee
        uint88 dethAmount = uint88(IBridge(bridge).deposit(msg.sender, amount)); // @dev(safe-cast)

        vault.addDeth(bridgePointer, dethAmount);
        maybeUpdateYield(vault, dethAmount);
        emit Events.Deposit(bridge, msg.sender, dethAmount);
    }
```
```
 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);
    }
```
```
 function _ethConversion(uint256 vault, uint88 amount) private view returns (uint88) {
        uint256 dethTotalNew = vault.getDethTotal();
        uint88 dethTotal = s.vault[vault].dethTotal;

        if (dethTotalNew >= dethTotal) {
            // when yield is positive 1 deth = 1 eth
            return amount;
        } else {
            // negative yield means 1 deth < 1 eth
            // @dev don't use mulU88 in rare case of overflow
            return amount.mul(dethTotalNew).divU88(dethTotal);
        }
```
The current withdrawal logic allows users to withdraw Deth with an incorrect conversion rate, leading to unfair
withdrawals. The withdraw function allows withdrawals from VAULT (reth/steth) or directly from the vault. Here’s
where the issue arises:

The code calculates an
An attacker can cancel other people's short orders
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Summary
`LibSRUtil.transferShortRecord` does not check the owner of `nft.shortOrderId` before canceling it.

`shortOrderId`s are reused after the order is canceled.

An attacker can create a state for an NFT where the short record's status is `SR.PartialFill`, but `nft.shortOrderId` is already reassigned to another user, yet still set in the attacker's NFT.

This will make the system think that it needs to cancel `nft.shortOrderId`, even though it belongs to another user already and does not reference the original order.
## Vulnerability Details
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibSRUtil.sol#L134-L136

```solidity
if (short.status == SR.PartialFill) {
            LibOrders.cancelShort(asset, nft.shortOrderId);
}
```

For the example, we will use ids that would be used by a new asset (starting ids, 2 for shortRecord and 100 for short order).
Steps for the attacker, the simplest attack (see `test1` function in PoC too):
1. Create a short record that will be partially filled
	1. The attacker can create a limit short + market bid in the same transaction. The market bid must not fully cover the limit short.
2. Then, possibly in the same transaction, they 
	1. mintNFT
		1. shortRecordId = 2
		2. shortOrderId = 100
			1. short order's linked list state:  HEAD <-> HEAD <-> 100 <-> TAIL
	2. cancel the short
		1. short order's linked list state HEAD <-> 100 <-> HEAD <-> TAIL
	3. cancel the short record with id 2 (`exitShortErcEscrowed` in PoC)
3. A victim creates a limit short
	1. shortOrderId is 100 again (reused), but it is a different order, it belongs to the victim now.
	2. Short order's linked list state HEAD <-> HEAD <-> 100 <-> TAIL
4. The attacker decides to cancel it
5. The attacker creates a new short record (the same way as in step 1). Note that because the last short record with id=2 was canceled its id will be reused
	1. Even though we canceled the short record in step 2.3, it is now recreated. So, the new record's id is 2 again and the `.status` is `SR.PartialFill`
6. Attacker does `transferFrom`
	1. All the checks are passed because the short record with id is legit, the status is `SR.PartialFill`, and the record belongs to the attacker
	2. The code will call `LibOrders.cancelShort(asset, nft.shortOrderId);`
		1. nft.shortOrderId is 100
	3. The short order with id 100 is canceled (victim's order)

The attacker can also create many NFTs and choose which orders to cancel as they wish (See the second function in PoC).

The attacker can then create the NFTs again and start the attack again (See the third PoC).

It's also possible to have several NFTs on the same address or different addresses with the same `nft.shortOrderId`. 
It may also lead to accidental cancellation even if a user i
Reverting on Liquidation Error can cause a the system to underflow
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Revertion of the liquidation function means even when most conditions are good, will make the system enter into error when the dethcollateral - eth will give a negative value thus underflowing.

## Proof of Concept
If there are no sell in the market. For any reason at all the system should balance up and ensure that the liquidation function works.
 If the liquidation function fails and the contract enters a negative balance, it indicates a critical problem. Negative balances are typically not permissible in financial systems as they can lead to various issues such as incorrect accounting, inability to execute transactions, or even potential exploitation by malicious actors. Note the function also reverts for other reasons.

## Tools Used
manual code analysis

## Recommended Mitigation Steps
review all potential point of reversion and provide a temporary solution. 
1. for the no sell reversion the contract can use the fees collected by tapp or hold ercamounts to be used as an alternative instead of reverting the entire function.


## Assessed type

Error