xanoutas/solidity-security-findings huggingface dataset:
Incorrect handling of improper input in proposeRedemption() allows a redeemer to call claimRedemption() without having to wait timeToDispute
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Incorrect handling of improper input in `RedemptionFacet::proposeRedemption()` allows a redeemer to call `RedemptionFacet::claimRedemption()` without having to wait `timeToDispute`. In reality, `timeToDispute` is zero however that shouldn't be the case and it is only possible because of improper code. `RedemptionFacet::proposeRedemption()` requires an array of proposals with `CRs` sorted from lowest to highest. Then, the `CR` at the last spot is being used to compute the `timeToDispute` as it is expected to be with the highest value. However, this can be manipulated and `timeToDispute` can become `0` even though it should actually be a value that is higher than `0`. This allows a redeemer to immediately call `RedemptionFacet::claimRedemption()` successfully severely breaking the intended usage and idea of the facet.

## Proof of Concept
`RedemptionFacet::proposeRedemption()` relies on this check to see if the `CRs` are properly sorted from lowest to highest.
```solidity
            p.currentCR = currentSR.getCollateralRatio(p.oraclePrice);

            // @dev Skip if proposal is not sorted correctly or if above redemption threshold
            if (p.previousCR > p.currentCR || p.currentCR >= C.MAX_REDEMPTION_CR) continue;
```

While the check properly checks for that, `p.currentCR` is still being rewritten to the `CR` even if `p.currentCR` is lower than `p.previousCR`.

Imagine a scenario where a redeemer provides 3 proposals. The first 2 have a `CR` above `1.1 ether` and the last one has a `CR` of `1.1 ether` or below. `timeToDispute` is computed based on the highest `CR` and if the `CR` is below `1.1 ether`, then `timeToDispute` will be 0 but if it is above, then `timeToDispute` should be above 0. The loop goes through the proposals and on the last one, it checks whether `p.previousCR (above 1.1) is of higher value than p.currentCR (1.1 and below)`. Since this is the case, the final iteration of the loop is skipped. However, `p.currentCR` is still rewritten to the `CR` of the last proposal with a `CR` of `1.1 ether` or below.

Then, after the loop, the `timeToDispute` is computed using those `if` checks.
```solitity
if (p.currentCR > 1.7 ether) {
            m = uint256(3 ether).div(0.3 ether);
            redeemerAssetUser.timeToDispute = protocolTime + uint32((m.mul(p.currentCR - 1.7 ether) + 3 ether) * 1 hours / 1 ether);
        } else if (p.currentCR > 1.5 ether) {
            m = uint256(1.5 ether).div(0.2 ether);
            redeemerAssetUser.timeToDispute =
                protocolTime + uint32((m.mul(p.currentCR - 1.5 ether) + 1.5 ether) * 1 hours / 1 ether);
        } else if (p.currentCR > 1.3 ether) {
            emit Events.Log2("this is the ratio");
            m = uint256(0.75 ether).div(0.2 ether);
            redeemerAssetUser.timeToDispute =
                protocolTime +
Potential Loss of Collateral Due to Incomplete Short Record Clearance in Smart Contract
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
description: The smart contract contains a vulnerability where users' collateral may not be properly set to zero after being disbursed, which can lead to the permanent loss of collateral. The `_claimRemainingCollateral` function adds the collateral to `ethEscrowed` and calls `LibSRUtil.disburseCollateral` to transfer it, but without visibility into this function's implementation, it cannot be confirmed that the collateral is set to zero post-transfer. Subsequently, `LibShortRecord.deleteShortRecord` removes the short record, potentially leaving no trace of the collateral if not zeroed out. If `disburseCollateral` fails to zero the collateral, users may permanently lose access to their funds after the deletion of the short record, despite the collateral not being successfully transferred to them.
## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
https://github.com/code-423n4/2024-03-dittoeth/blob/main/contracts/facets/RedemptionFacet.sol#L310-L334
## Tools Used

## Recommended Mitigation Steps
**Finding Recommendation:**

To address the potential vulnerability related to users permanently losing a portion of their collateral, it is crucial to ensure that the collateral amount is set to zero before deleting the short record. 

To mitigate this risk, review and update the `LibSRUtil.disburseCollateral` function to include setting the `collateral` amount to zero after successfully transferring the funds to the user. This step is essential to prevent the scenario where collateral remains in the system but becomes inaccessible after the short record is deleted.

By implementing this precautionary measure, you can enhance the security and reliability of the smart contract, safeguarding users' collateral and ensuring that all processes related to collateral management are executed accurately and securely.


## Assessed type

Reentrancy
Users will not be eligible to update their yield if they deposit sufficiently large funds
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
***The impact of this finding lies in the potential for misleading yield update decisions due to integer division truncation in the maybeUpdateYield function. When a user calls the deposit function to deposit funds into a specific bridge, the maybeUpdateYield function is subsequently called to determine whether to update the yield based on certain thresholds. However, due to integer division truncation, the comparison between the deposited amount and the total deposited amount (dethTotal) may lead to incorrect yield update decisions.***

## 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);
    uint88 dethAmount = uint88(IBridge(bridge).deposit(msg.sender, amount)); // Safe cast

    vault.addDeth(bridgePointer, dethAmount);
    maybeUpdateYield(vault, dethAmount);
    emit Events.Deposit(bridge, msg.sender, dethAmount);
}

```
  function depositEth(address bridge) external payable nonReentrant {
        if (msg.value < C.MIN_DEPOSIT) revert Errors.UnderMinimumDeposit();

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

        uint88 dethAmount = uint88(IBridge(bridge).depositEth{value: msg.value}()); // Assumes 1 ETH = 1 DETH
        vault.addDeth(bridgePointer, dethAmount);
        maybeUpdateYield(vault, dethAmount);
        emit Events.DepositEth(bridge, msg.sender, dethAmount);
    }
```

function maybeUpdateYield(uint256 vault, uint88 amount) private {
    uint88 dethTotal = s.vault[vault].dethTotal;
    if (dethTotal > C.BRIDGE_YIELD_UPDATE_THRESHOLD && amount.div(dethTotal) > C.BRIDGE_YIELD_PERCENT_THRESHOLD) {
        vault.updateYield();
    }
}
```
>The maybeUpdateYield() function will only be called if large deposits are made in bridges. The deth total will be the total deth in the contract, given if there were a sufficiently large deposit made in any of the bridges. The deposit of LST into any vault/bridge will return you equivalent deth 1:1.

***Suppose there was a huge deposit made, user will be unable to get a yield because of invalid validation before yielding to users.***

***Let's assume the user deposited 5000 amounts LST(reth/stETH)  the deth accounts get credited and bridge credits if applicable. It now says that in the function logic: [dethTo
Can bypass the updatedAt check in disputeRedemption. Attacker can earn fees, cancel redemption for a particular short, or DoS redemption.
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Attacker can cancel the redemption proposal to get a fee, or cancel the redemption proposal for its own short record, DoS redemption to disrupt the depegging mitigation. 

## Proof of Concept

`disputeRedemption` restricts only short records updated more than one hour before the redemption proposal is created to be available for dispute. 

```solidity
function disputeRedemption(address asset, address redeemer, uint8 incorrectIndex, address disputeShorter, uint8 disputeShortId)
    external
    isNotFrozen(asset)
    nonReentrant
{
    ...

@>  if (disputeCR < incorrectProposal.CR && disputeSR.updatedAt + C.DISPUTE_REDEMPTION_BUFFER <= redeemerAssetUser.timeProposed)
    {
        ...
```

However, the `decreaseCollateral` does not update `shortRecord.updatedAt`. Therefore, after the proposal is created, attaker can lower the CR of the short record and use it for dispute.

If the dispute is successful, redemption proposals with a CR higher than the dispute CR are canceled. The attacker can get fees and give a penalty to the redeemer, can cancel the redemption proposal for its own short record(with other account). They can also DoS the normal progress of redemption and disrupt the depegging mitigation.

This is PoC. Add to Redemption.t.sol file and run.

```solidity

function test_PoC_disputeRedemption_updateAt_bypassed() public {
    
    // get this setting from DeployHelper
    vm.startPrank(owner);
    diamond.setInitialCR(asset, 170);
    initialCR = 170;
    diamond.setLiquidationCR(asset, 150);
    vm.stopPrank();

    fundLimitBidOpt(DEFAULT_PRICE, DEFAULT_AMOUNT, receiver);
    fundLimitShortOpt(DEFAULT_PRICE, DEFAULT_AMOUNT, sender);
    fundLimitBidOpt(DEFAULT_PRICE + 1, DEFAULT_AMOUNT + 1, receiver);
    fundLimitShortOpt(DEFAULT_PRICE + 1, DEFAULT_AMOUNT + 1, sender);
    fundLimitBidOpt(DEFAULT_PRICE * 3, 2 + DEFAULT_AMOUNT, receiver); //  C.SHORT_STARTING_ID + 2 has enough CR
    fundLimitShortOpt(DEFAULT_PRICE * 3, 2 + DEFAULT_AMOUNT, sender);

    skip(1 hours + 1 minutes);

    MTypes.ProposalInput[] memory proposalInputs = new MTypes.ProposalInput[](1);
    proposalInputs[0] = MTypes.ProposalInput({shorter: sender, shortId: C.SHORT_STARTING_ID, shortOrderId: 0}); // only C.SHORT_STARTING_ID proposed

    _setETH(2800 ether);

    address redeemer = receiver;

    vm.prank(redeemer);
    diamond.proposeRedemption(asset, proposalInputs, DEFAULT_AMOUNT, MAX_REDEMPTION_FEE);

    vm.prank(sender);
    diamond.decreaseCollateral(asset, C.SHORT_STARTING_ID + 2, uint80(DEFAULT_AMOUNT/730)); // this does not update timestamp

    address disputer = extra; // This is shoter's sub account

    uint256 oldErcEscrowedD
A user can escape redemption or liquidation by simply transfering its SR
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibSRUtil.sol#L139-L144
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts\facets\RedemptionFacet.sol#L87
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts\facets\RedemptionFacet.sol#L38
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/PrimaryLiquidationFacet.sol#L51-L51
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/AppStorage.sol#L86-L98
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/SecondaryLiquidationFacet.sol#L63-L63


# Vulnerability details

Ditto is a decentralized CDP protocol allowing users to get stablecoins (called dUSD here) against LST (Liquid Staking Tokens like stETH or rETH) collateral. 
Depositing LST grant dETH, a shares representing the value deposited in ETH.
Users can then open different kind of positions: bids, asks or shorts, each one having its own orderbook.

When a short is opened, an empty Short Record (SR) is created, linked to the short order and to the shorter address.
From a short record, an NFT can be minted, making it transferable.

[Transfering a SR consist in fact](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibSRUtil.sol#L139-L144) in deleting the SR, transfering the assets associated to this short to the new owner, and create a new SR. 
Deleting a SR [sets its status to `SR.Closed`](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibSRUtil.sol#L139-L144)

And this is the issue. The ability for a SR record owner to transfer its SR, and this changing its status, open up the possbility to escape redemptions and liquidations by front-running the calls.

This is because the [`RedemptionFacet::proposeRedemption` function before processing a short](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts\facets\RedemptionFacet.sol#L87), verify that the SR [has not been closed](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts\facets\RedemptionFacet.sol#L38)

Same idea for liquidations, where [`PrimaryLiquidationFacet::liquidate`](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/PrimaryLiquidationFacet.sol#L51-L51) through its [`onlyValidaShortRecord` modifier](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/AppStorage.sol#L86-L98) will revert in case if frontrun by a transfered SR.
Finaly, we see the same behavior here on [`SecondaryLiquidation::
Potential Loss of Collateral Due to Incomplete Short Record Clearance in Smart Contract
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
description: The smart contract contains a vulnerability where users' collateral may not be properly set to zero after being disbursed, which can lead to the permanent loss of collateral. The `_claimRemainingCollateral` function adds the collateral to `ethEscrowed` and calls `LibSRUtil.disburseCollateral` to transfer it, but without visibility into this function's implementation, it cannot be confirmed that the collateral is set to zero post-transfer. Subsequently, `LibShortRecord.deleteShortRecord` removes the short record, potentially leaving no trace of the collateral if not zeroed out. If `disburseCollateral` fails to zero the collateral, users may permanently lose access to their funds after the deletion of the short record, despite the collateral not being successfully transferred to them.
## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
https://github.com/code-423n4/2024-03-dittoeth/blob/main/contracts/facets/RedemptionFacet.sol#L310-L334
## Tools Used
Meat Scan
## Recommended Mitigation Steps
**Finding Recommendation:**

To address the potential vulnerability related to users permanently losing a portion of their collateral, it is crucial to ensure that the collateral amount is set to zero before deleting the short record. 

To mitigate this risk, review and update the `LibSRUtil.disburseCollateral` function to include setting the `collateral` amount to zero after successfully transferring the funds to the user. This step is essential to prevent the scenario where collateral remains in the system but becomes inaccessible after the short record is deleted.

By implementing this precautionary measure, you can enhance the security and reliability of the smart contract, safeguarding users' collateral and ensuring that all processes related to collateral management are executed accurately and securely.


## Assessed type

Reentrancy
Flawed if check causes inaccurate tracking of the protocol's ercDebt and collateral
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
A flawed if check using `&&` instead of `||` in `RedemptionFacet::claimRemainingCollateral()` leads to a break of one of the core protocol invariants. The total collateral and ercDebt of an asset should always equal the total collateral and ercDebt of all shortRecords combined. However, this will not be the case if the scenario explained below takes place. This results in the protocol holding inaccurate values of their ercDebt and collateral which are extremely important values used for very important calculations across the entire protocol.

## 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. As the shortRecord is deleted before `timeToDispute` is over, `RedemptionFacet::disputeRedemption()` is still callable
7. `Disputer` successfully disputes the already deleted shortRecord
8. `RedemptionFacet::disputeRedemption()` gives back the `collateral` and `ercDebt` to the already deleted shortRecord
9. It also increments the asset's `collateral` and `ercDebt`
```solidity
for (uint256 i = incorrectIndex; i < decodedProposalData.length; i++) {
                currentProposal = decodedProposalData[i];

                STypes.ShortRecord storage currentSR = s.shortRecords[d.asset][cu
Bid Amount is not Deducted After Selling Match Algo
Audit: 2024-03-dittoeth-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Unauthorized value can be gotten from Protocol as Bid Amount is not Deducted After Selling Match Algo when incomingAsk erc Amount is exactly equal highestBid erc Amount
## Proof of Concept
```solidity
 function sellMatchAlgo(
        address asset,
        STypes.Order memory incomingAsk,
        MTypes.OrderHint[] memory orderHintArray,
        uint256 minAskEth
    ) internal {
        AppStorage storage s = appStorage();
        uint16 startingId = s.bids[asset][C.HEAD].nextId;
        if (incomingAsk.price > s.bids[asset][startingId].price) {
          ...
        }
        // matching loop starts
        MTypes.Match memory matchTotal;
        while (true) {
            STypes.Order memory highestBid = s.bids[asset][startingId];
            if (incomingAsk.price <= highestBid.price) {
                // Consider ask filled if only dust amount left
                if (incomingAsk.ercAmount.mul(highestBid.price) == 0) {
                    ...
                }
                matchHighestBid(incomingAsk, highestBid, asset, matchTotal);
                if (incomingAsk.ercAmount > highestBid.ercAmount) {
                    incomingAsk.ercAmount -= highestBid.ercAmount;
  >>>                  highestBid.ercAmount = 0;
                    matchOrder(s.bids, asset, highestBid.id);

                    // loop
                    startingId = highestBid.nextId;
                    if (startingId == C.TAIL) {
                        ...
                    }
                } else {
>>>                   if (incomingAsk.ercAmount == highestBid.ercAmount) {
                        matchOrder(s.bids, asset, highestBid.id);
                        updateBidOrdersOnMatch(s.bids, asset, highestBid.id, true);
        ///@audit highestBid.ercAmount update is missing here like at the other pointers
                    } 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;
                }
            } else {
               ...
            }
        }
    }
```
The function above is showing how sellMatchAlgo(...) function is implemented in the LibOrders contract, as can be seen above 3 pointers are provided in the function above, it can be observed that the 3 poi