xanoutas/solidity-security-findings huggingface dataset:
Decimals not scaled correctly in `getOraclePrice()` cause huge price discrepancies
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Summary

Where the Oracle called is not the `baseOracle`, the values returned are not scaled correctly; leading to either a very large overpricing or very large underpricing

## Vulnerability Details

In the `getOraclePrice()` `try` block, `baseOracle` returned price `basePrice` is scaled up from `8` to `18` decimals, however the same is not done for other oracles.
Given that other oracles (e.g. JPY, XAU) will also return `8` decimal `USD` price; this means that their returned value will be significantly overpriced.

```
    try baseOracle.latestRoundData() returns (uint80 baseRoundID, int256 basePrice, uint256,uint256 baseTimeStamp, uint80) {
        if (oracle == baseOracle) {
            // SOME CODE
        } else {

            // SOME CODE
>>>         uint256 priceInEth = uint256(price).div(uint256(basePrice));
            oracleCircuitBreaker(roundID, baseRoundID, price, basePrice, timeStamp, baseTimeStamp);
>>>         return priceInEth;
        }
    }
```

In the `catch` block, `twapInv` is an `18` decimal value returned from `twapCircuitBreaker()` which is then multiplied by `uint256(price * C.BASE_ORACLE_DECIMALS)`.
This equates to `(8 dec * 10 dec) * 18 dec`, which will be a `36` decimal value and a massive underpricing of `priceinEth`.

```
    } catch {
        if (oracle == baseOracle) {
            return twapCircuitBreaker();
        } else {

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

## Impact

The vastly incorrect pricing of "other" stable assets such as `JPY` and `XAU` will lead to easy exploitation; costing users to lose a lot of money and rendering their markets unusable.

## Tools Used

Manual Review
Foundry Testing

## Recommendations

Scale up in the `try` block as:

```diff
    try baseOracle.latestRoundData() returns (uint80 baseRoundID, int256 basePrice, uint256,uint256 baseTimeStamp, uint80) {
        if (oracle == baseOracle) {
            // SOME CODE
        } else {

            // SOME CODE
           uint256 priceInEth = uint256(price).div(uint256(basePrice));
+          priceInEth = priceInEth.mul(10**C.BASE_ORACLE_DECIMALS);
            oracleCircuitBreaker(roundID, baseRoundID, price, basePrice, timeStamp, baseTimeStamp);
            return priceInEth;
        }
    }
```

Scale down in the `catch` block. First add a new variable `ETH_DECIMALS = 10 ** 18` to `Constants.sol` and use that to scale down in the `catch` block as:

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

// Scale down from 1e36 to 1e18 by dividing by 1e18
priceInEth = priceInEth / C.ETH_DECIMALS;

```


## Assessed type

Oracle
Unhandled edge case in bid matching algorithm
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The identified issue in the bid matching algorithm could have several significant impacts:

- Earning "free money": Malicious actors could exploit this vulnerability by creating multiple sell orders and then creating multiple buy orders that exactly match the created sell orders. Because the protocol does not update the `lowestSell.ercAmount` upon an exact match, it allows these sell orders to remain unchanged, essentially allowing the actor to earn "free money" without actually selling anything and thereby draining the protocol.

- Inaccurate Order Book: If `lowestSell.ercAmount` remains unchanged after an exact match, it may lead to inaccuracies in the order book. Sell orders that have been fully filled may still appear in the order book with non-zero amounts, misleading traders about the available liquidity.

## Proof of Concept

The issue was identified through a manual review of the bid matching algorithm code in the provided GitHub repository. Specifically, the relevant code snippet where the concern was raised is as follows:

```solidity
 function bidMatchAlgo(
        address asset,
        STypes.Order memory incomingBid,
        MTypes.OrderHint[] memory orderHintArray,
        MTypes.BidMatchAlgo memory b
    ) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
            // more code

         if (incomingBid.ercAmount == lowestSell.ercAmount) {
            // @audit-issue Oversight, if we don't update lowestSell.ercAmount = 0 here, it means that it will remain unchanged, leading to issues

             if (lowestSell.isShort()) {
                  b.matchedShortId = lowestSell.id;
                  b.prevShortId = lowestSell.prevId;
                  LibOrders.matchOrder(s.shorts, asset, lowestSell.id);
               } else {
                   b.matchedAskId = lowestSell.id;
                    LibOrders.matchOrder(s.asks, asset, lowestSell.id);
                      }
               }

```

As the audit-issue comment suggests, `lowestSell.ercAmount` should be set to 0 after a bid matches exactly with the sell amount. However, there is no such update in the code, leaving `lowestSell.ercAmount` unchanged on the order book. This could lead to the same sell order being executed for all bids that match it, allowing malicious actors to exploit the system for financial gain.

## Tools Used

VSCode

## Recommended Mitigation Steps

To address this issue, it is recommended to update the `lowestSell.ercAmount` to 0 after an exact match occurs. This ensures that the order book accurately reflects the filled orders and prevents the risk of double execution.



## Assessed type

Other
When a closed ShortRecord is canceled redemption at disputeRedemption, the user loses the collateral
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The user loses collateral.

## Proof of Concept

If the Redemption requested ShortRecord is closed for various reasons and then canceled redemption by `disputeRedemption`, the user cannot get the collateral back because the ShortRecord has already been closed.

```solidity
function disputeRedemption(address asset, address redeemer, uint8 incorrectIndex, address disputeShorter, uint8 disputeShortId)
    external
    isNotFrozen(asset)
    nonReentrant
{
    ...
    MTypes.ProposalData[] memory decodedProposalData =
        LibBytes.readProposalData(redeemerAssetUser.SSTORE2Pointer, redeemerAssetUser.slateLength);

    ...

    if (disputeCR < incorrectProposal.CR && disputeSR.updatedAt + C.DISPUTE_REDEMPTION_BUFFER <= redeemerAssetUser.timeProposed)
    {
        // @dev All proposals from the incorrectIndex onward will be removed
        // @dev Thus the proposer can only redeem a portion of their original slate
        for (uint256 i = incorrectIndex; i < decodedProposalData.length; i++) {
@>          currentProposal = decodedProposalData[i];

@>          STypes.ShortRecord storage currentSR = s.shortRecords[d.asset][currentProposal.shorter][currentProposal.shortId]; // The currentSR might be closed
@>          currentSR.collateral += currentProposal.colRedeemed;
@>          currentSR.ercDebt += currentProposal.ercDebtRedeemed;

            d.incorrectCollateral += currentProposal.colRedeemed;
            d.incorrectErcDebt += currentProposal.ercDebtRedeemed;
        }

        ...
    } ...
}
```

For example:

1. Alice ShortRecord #3 is in `proposesRedemption` . There is remaining ercDebt and collateral in ShortRecord #3 due to `redemptionAmount`.
2. Alice calls `exitShortWallet` . Now the ShortRecord #3 is closed.
3. Bob calls `disputeRedemption`, canceling Alice's ShortRecord #3 redemption. It returns ercDebt and collateral to ShortRecord #3.
4. But Alice's ShortRecord #3 is already closed. Alice can't handle the collateral or ercDebt of this short.

There are many scenarios where ShortRecord #3 could be closed unintentionally. For example, if the same ShortRecord is called `proposeRedemption` twice due to `redemptionAmount` , first claim could close ShortRecord #3. Even if a dispute occurs in the other proposal later, Alice cannot handle the returned funds. Since it's closed, it can't be requested to redeem or liquidate again.

This is PoC. Add it to the Redemption.t.sol file.

```solidity
function test_PoC_AlreadyClosedSR() public {
    address shorter = sender;
    uint88 _redemptionAmounts = DEFAULT_AMOUNT + DEFAULT_AMOUNT / 2; // C.SHORT_STARTING_ID + 2 partial redeemed

    makeShorts({singleShorter: true});

    MTypes.ProposalInput[] memory proposalInputs =
        makeProposalInputsForDispute({shortId1: C.SHORT_STARTING_ID, shortId2: C.SHORT
cancelShort will cause users loss of funds
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

[cancelShort](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/OrdersFacet.sol#L51) will not refund a shorter after his short being canceled.

## Proof of Concept

When a user add a short order using [createLimitShort](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ShortOrdersFacet.sol#L35) method, [LibOrders.addShort](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ShortOrdersFacet.sol#L86) will be called in case `incomingShort.price < p.oraclePrice`.

```
LibOrders.addShort(asset, incomingShort, orderHintArray);
```

In [addShort](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOrders.sol#L128-L144), `eth` value will be deducted from the shorter's `ethEscrowed` at [s.vaultUser\[vault\]\[order.addr\].ethEscrowed -= eth](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOrders.sol#L143)

The issue here that [cancelShort](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/OrdersFacet.sol#L51) will not refund `ethEscrowed` that's taken from the shorter if the short is closed.

What exactly cancelShort do:  
`cancelShort` will call [LibOrders.cancelShort](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/OrdersFacet.sol#L55), if (shortRecord.status == SR.Closed) [deleteShortRecord](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOrders.sol#L901) will be called for the shortRecordId, Inside `deleteShortRecord` method there is no refund for `ethEscrowed` that was taken from the shorter balance when the short was added in \[s.vaultUser\[vault\]\[order.addr\].ethEscrowed -= eth\].

Remember that when a short order is added, the status of the shortRecord initiated as [SR.Closed](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ShortOrdersFacet.sol#L48).

## Tools Used

Manual review

## Recommended Mitigation Steps

Refund the shorter the amount that was deducted from `ethEscrowed` on `cancelShort`


## Assessed type

Other
Loss of funds of new deposits in case bridges were slashed already
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

&nbsp;Loss of funds for new depositors in case slashing happened already before their deposits which is unfair. 

## Proof of Concept

Looking at the code below in function [_ethConversion](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/BridgeRouterFacet.sol#L169), the protocol is covering the loss proportionally on withdrawing, according to the comment:

> // Accounting for situations when the vault (via bridges) experiences loss in value

```
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 issue here if bridges balances get slashed (a penalty is done on bridges Steth,Reth in some situations) then `dethTotalNew` will be less than `dethTotal`. However, new depositors will incur the loss (proportionally).

In different words, if a new user deposited an amount after a slashed already was done in the bridges, he will lose a partial fund of his deposit when he try to withdraw his deposit. which is unfair to the user since he made the deposit after, not before.

What is pool getting slashed? a reference from rocketpool [https://docs.rocketpool.net/guides/node/responsibilities#:~:text=Penalties ​,network%2C it may get slashed](https://docs.rocketpool.net/guides/node/responsibilities#:~:text=Penalties%20%E2%80%8B,network%2C%20it%20may%20get%20slashed).

> Validators are penalized for small amounts of ETH if they are offline and fail to perform their assigned duties. This is called leaking. If a validator violates one of the core rules of the Beacon chain and appears to be attacking the network, it may get slashed.

## Tools Used

Manual Review

## Recommended Mitigation Steps


## Assessed type

Other
Decrease collateral can be used to dispute redemptions and steal from redemptors
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
[`decreaseCollateral()`](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ShortRecordFacet.sol#L81) can reduce the CR of a position down to the asset.initialCR (in the deploy script this value is set to CR 1.7). The current redemptionCR is set to 2.

Shorter can dispute with 100% consistency all redemptions of Short Record positions that are between initialCR and redemptionCR (CR 1.7 - 2.0) by using decreaseCollateral to set his SR to CR 1.7. 

In addition, if the oracle price changes within 0-3 hours the shorter is going to be able to dispute redemptions of Short Records that have CR down to 1.35 by using his Short Record that is CR 1.7.

Despite that users correctly created redemption proposals, shorters have the power to dispute them incorrectly and steal up to 33% of their erc assets.

## Proof of Concept
`test_decreaseCollateral_poc_1` shows how shorter disputes all redemptions towards his large position and all other redemptions that are between CR 1.7-2.

`test_decreaseCollateral_poc_2_belowInitialCR` shows how shorter can dispute positions with low CR such as 1.37-1.41 with his position that is CR 1.7 when there is a new oracle asset price.

To run the tests use `forge test --match-contract Exploit -vv`.

Create a new Exploit.t.sol file inside the test folder and paste the following:

```solidity
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.21;

import {stdError} from "forge-std/StdError.sol";
import {U256, U88, U80} from "contracts/libraries/PRBMathHelper.sol";
import {C} from "contracts/libraries/Constants.sol";
import {STypes, MTypes, O} from "contracts/libraries/DataTypes.sol";

import {OBFixture} from "test/utils/OBFixture.sol";
import {console} from "contracts/libraries/console.sol";

contract Exploit is OBFixture {
    event LOG(string message);

    using U256 for uint256;
    using U88 for uint88;
    using U80 for uint80;

    function setUp() public override {
        super.setUp();

        // Set initialCR to 170 (CR 1.7X) as it is in DeployHelper.sol
        vm.prank(owner);
        diamond.setInitialCR(asset, 170);
        initialCR = diamond.getAssetStruct(asset).initialCR;
    }

    function createCustomLimitShort(address shorter, uint80 price, uint88 ercAmount, uint16 cr) private {
        uint256 convertedCR = (uint256(cr) * 1 ether) / C.TWO_DECIMAL_PLACES;
        depositEth(shorter, price.mulU88(ercAmount).mulU88(convertedCR));
        uint16[] memory shortHintArray = setShortHintArray();
        MTypes.OrderHint[] memory orderHintArray = diamond.getHintArray(asset, price, O.LimitShort, 1);
        vm.prank(shorter);
Incorrectly implemented if check causes ercDebt and collateral to be unexpectedly transferred to a different shortRecord
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
An incorrectly implemented if check that is using `&&` instead of `||` causes a random newly created `shortRecord` to get the `ercDebt` and `collateral` of a previously deleted `shortRecord`.

## Proof of Concept
Imagine the following scenario:
1. `Shorter` has a few shortRecords
2. `Redeemer` proposes a redemption for one of the shortRecords owned by `shorter`
3. The `timeToDispute` passes
4. `Redeemer2` proposes redemption for another one of the shortRecords owned by `shorter`
5. Even though `timeToDispute` has not passed for `redeemer2`, `shorter` is able to call `RedemptionFacet::claimCollateral()` successfully because of a flawed if check
```solidity
if (claimProposal.shorter != msg.sender && claimProposal.shortId != id) revert Errors.CanOnlyClaimYourShort();
```
5.1 `Shorter` calls `RedemptionFacet::claimRemainingCollateral()` with the address of `redeemer` and with the ID of the shortRecord proposed by `redeemer2`

5.2 redeemerAssetUser in the function is now `redeemer`

5.3 The check for time to dispute passes as enough time has passed for `redeemer`'s `timeToDispute`

5.4 claimProposal is the decodedProposalData of `redeemer`

5.5 The if check doesn't lead to a revert because it is flawed, `claimProposal.shorter` is == to `msg.sender` as he is the owner of the initial shortRecord however the `claimProposal.shortId` is not equal to the given ID since `claimProposal.shortId` is based on the initial redeemer proposal and the given ID is the ID for the shortRecord proposed by `redeemer2` which makes the if check (true && false) resulting in false and the code continues

5.6 _claimRemainingCollateral() is called with the `shorter` address (msg.sender) and `shortId` equal to the given ID (ID of the shortRecord proposed by `redeemer2`)

5.7 Check successfully passes and `shorter` claims collateral and deletes shortRecord without having to wait the timeToDispute

6. After the shortRecord is deleted, now a user could create a new shortRecord. Due to the implemented ID reusage optimizations, the new shortRecord will take the ID of the previously deleted shortRecord.
7. As the shortRecord is deleted before `timeToDispute` is over, `RedemptionFacet::disputeRedemption()` is still callable
8. `Disputer` successfully disputes the already deleted shortRecord
9. `RedemptionFacet::disputeRedemption()` unexpectedly gives back the `collateral` and `ercDebt` to the newly created shortRecord that could have been created by any user
```solidity
for (uint256 i = incorrectIndex; i < decodedProposalData.length; i++) {
                currentProposal = decodedProposalData[i];

                STypes.ShortRecord storage currentSR = s.shortRecords[d.asset][currentProposal.shorter][currentProposal.shortId];
                currentSR.collateral += currentProposal.colRedeemed;
                currentSR.ercDebt += curre
malicious shorter can escape liquidation of their unhealthy positions
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ExitShortFacet.sol#L41
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ExitShortFacet.sol#L87
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ExitShortFacet.sol#L142
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/PrimaryLiquidationFacet.sol#L47


# Vulnerability details

## Impact
Malicious user can avoid liquidation by frontruning a liquidation call to liquidate their unhealthy position. This can jeopardise the protocol’s solvency.

## Proof of Concept

This vulnerability is borne from the fact there is a missing check for state of health of the SR’s position in the [the `exitShortWallet(...)`](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ExitShortFacet.sol#L41), [`exitShortErcEscrowed(...)`](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ExitShortFacet.sol#L87) and [`exitShort(...)`](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ExitShortFacet.sol#L142) functions respectively. and as such even unhealthy SRs can be liquidated

### Scenario

- Malicious shorter’s position becomes under collateralised
- Liquidator calls [the `Liquidate(...)`](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/PrimaryLiquidationFacet.sol#L47) to liquidate the position
- Malicious shorter notices the liquidators transaction in the mempool and [calls `exitShortErcEscrowed(...)`](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ExitShortFacet.sol#L87) to exit his short making the liquidators call to fail and gets

## Tools Used
Manual review

## Recommended Mitigation Steps
My recommendation here is only part of the recommendation I gave in a separate issue I submitted for this contest titled *Bad debt socialisation can lead to premature liquidation of healthy positions that exit partially*

In the [`exitShortWallet(...)`](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ExitShortFacet.sol#L41), [`exitShortErcEscrowed(...)`](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ExitShortFacet.sol#L87) and [`exitShort(...)`](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/ExitShortFacet.sol#L142) functions, add a check to ensure the SR's CR is above liquidations CR is properly collateralised [before `updateErcDebt(...)`](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46