xanoutas/solidity-security-findings huggingface dataset:
Inaccurate tappFee and callerFee calculations
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The `_liquidationFeeHandler` function calculation and distribution of the `tappFee` and `callerFee` is flawed. Specifically, there seems to be a potential issue with the logic in the `else` block, which is executed when `TAPP.ethEscrowed` is less than `callerFee`.

In the `else` block, the code attempts to give the caller a portion of the `tappFee` instead of the `gasFee`. However, the calculation appears to be incorrect.

## Proof of Concept

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

```solidity
VaultUser.ethEscrowed += callerFee - m.gasFee + tappFee;
```
This line is adding the `callerFee`, subtracting the `gasFee`, and then adding the `tappFee` to `VaultUser.ethEscrowed`. The issue is that the `tappFee` should not be added to the caller's escrow balance. The `tappFee` should be deducted from the `TAPP` escrow balance, as it is the fee meant for the TAPP contract.

Additionally, the line `m.totalFee -= m.gasFee;` in the `else` block seems unnecessary, as the `gasFee` is already accounted for in the `callerFee` calculation.

## Tools Used
Manual

## Recommended Mitigation Steps
```solidity
} else {
    // Give caller the full callerFee since TAPP.ethEscrowed is insufficient
    VaultUser.ethEscrowed += callerFee;
    TAPP.ethEscrowed = 0;
}
```

In this version, the caller receives the full `callerFee` when `TAPP.ethEscrowed` is insufficient. The `TAPP.ethEscrowed` is set to 0, effectively transferring the remaining balance to the caller.

It's also worth double-checking the calculations and logic for the distribution of fees to ensure that the intended behavior is correctly implemented.


## Assessed type

Math
Inconsistent redemption rate increase and fee calculation on redemption of undercollateralized assets
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details



## Impact
The redemption rate increases too much when redeeming undercollateralized short records. The redeemer pays too much (or inconsistent) fees.

## Proof of Concept
When proposing a redemption the amount proposed is what is redeemed as collateral from a short record. When an undercollateralized short record is redeemed on the `colRedeemed`, accounting for how much collateral has been redeemed, it is capped to the short record's collateral balance. The amount proposed is not similarly reduced, however.
[RedemptionFacet.sol#L116-L125](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/RedemptionFacet.sol#L116-L125):
```solidity
p.colRedeemed = p.oraclePrice.mulU88(p.amountProposed);
if (p.colRedeemed > currentSR.collateral) {
    p.colRedeemed = currentSR.collateral;
}

currentSR.collateral -= p.colRedeemed;
currentSR.ercDebt -= p.amountProposed;

p.totalAmountProposed += p.amountProposed;
p.totalColRedeemed += p.colRedeemed;
```

In this case `p.totalColRedeemed` is therefore smaller in value than `p.totalAmountProposed`.

[These are the values used to calculate the redemption fee](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/RedemptionFacet.sol#L204):
```solidity
uint88 redemptionFee = calculateRedemptionFee(asset, p.totalColRedeemed, p.totalAmountProposed);
```

Specifically, [the redemption fee is proportional to `p.totalColRedeemed`](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/RedemptionFacet.sol#L402), but [the increase in base rate is proportional to `p.totalColRedeemed`](https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/RedemptionFacet.sol#L394)

Note how this effect can be used to assist an exploit described in my report titled "A successfully disputed redemption proposal has still increased the redemption fee base rate; exploit to depeg dUSD".

## Recommended Mitigation Steps
If the collateral is insufficient to cover the proposed amount, reduce the proposed amount accordingly.


## Assessed type

Other
Can proposeRedemption based on an outdated price
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

If the collateral ratio is calculated based on an incorrect price, a ShortRecord that is not a target for redemption at the current price can be proposed.

## Proof of Concept

It uses the cached price for calculating the collateral ratio in `proposeRedemption`. It uses it without checking whether the cached price information is outdated. If the collateral ratio is calculated based on an incorrect price, a ShortRecord that is not a target for redemption at the current price can be proposed.

```solidity
function proposeRedemption(
    address asset,
    MTypes.ProposalInput[] calldata proposalInput,
    uint88 redemptionAmount,
    uint88 maxRedemptionFee
) external isNotFrozen(asset) nonReentrant {
    if (proposalInput.length > type(uint8).max) revert Errors.TooManyProposals();
    MTypes.ProposeRedemption memory p;
    p.asset = asset;
    STypes.AssetUser storage redeemerAssetUser = s.assetUser[p.asset][msg.sender];
    uint256 minShortErc = LibAsset.minShortErc(p.asset);

    if (redemptionAmount < minShortErc) revert Errors.RedemptionUnderMinShortErc();

    if (redeemerAssetUser.ercEscrowed < redemptionAmount) revert Errors.InsufficientERCEscrowed();

    // @dev redeemerAssetUser.SSTORE2Pointer gets reset to address(0) after actual redemption
    if (redeemerAssetUser.SSTORE2Pointer != address(0)) revert Errors.ExistingProposedRedemptions();

@>  p.oraclePrice = LibOracle.getPrice(p.asset); // no price update before use this cached price

    bytes memory slate;
    for (uint8 i = 0; i < proposalInput.length; i++) {
        ...
@>      p.currentCR = currentSR.getCollateralRatio(p.oraclePrice);

        ...
@>      p.colRedeemed = p.oraclePrice.mulU88(p.amountProposed);
        if (p.colRedeemed > currentSR.collateral) {
            p.colRedeemed = currentSR.collateral;
        }
        ...
    }
    ...
}
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

```diff
function proposeRedemption(
    address asset,
    MTypes.ProposalInput[] calldata proposalInput,
    uint88 redemptionAmount,
    uint88 maxRedemptionFee
) external isNotFrozen(asset) nonReentrant {
    if (proposalInput.length > type(uint8).max) revert Errors.TooManyProposals();
    MTypes.ProposeRedemption memory p;
    p.asset = asset;
    STypes.AssetUser storage redeemerAssetUser = s.assetUser[p.asset][msg.sender];
    uint256 minShortErc = LibAsset.minShortErc(p.asset);

    if (redemptionAmount < minShortErc) revert Errors.RedemptionUnderMinShortErc();

    if (redeemerAssetUser.ercEscrowed < redemptionAmount) revert Errors.InsufficientERCEscrowed();

    // @dev redeemerAssetUser.SSTORE2Pointer gets reset to address(0) after actual redemption
    if (redeemerAssetUser.SSTORE2Pointer != address(0)) revert Errors.ExistingProposedRedemptions();

-    p.oraclePrice = LibO
Redeemer will pay extra `redemptionFee` when effective disputes occurs
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

When a redeemer submits proposals for redemption, they are charged a `redemptionFee` based on the total collateral `RedemptionFacet#L204` summed across all the `shortRecords` proposals (`p.totalColRedeemed` and `p.totalAmountProposed`):

```solidity
File: RedemptionFacet.sol
055:      */
056:     function proposeRedemption(
057:         address asset,
058:         MTypes.ProposalInput[] calldata proposalInput,
059:         uint88 redemptionAmount,
060:         uint88 maxRedemptionFee
061:     ) external isNotFrozen(asset) nonReentrant {
...
...
204:         uint88 redemptionFee = calculateRedemptionFee(asset, p.totalColRedeemed, p.totalAmountProposed);
205:         if (redemptionFee > maxRedemptionFee) revert Errors.RedemptionFeeTooHigh();
206: 
207:         STypes.VaultUser storage VaultUser = s.vaultUser[Asset.vault][msg.sender];
208:         if (VaultUser.ethEscrowed < redemptionFee) revert Errors.InsufficientETHEscrowed();
209:         VaultUser.ethEscrowed -= redemptionFee;
210:         emit Events.ProposeRedemption(p.asset, msg.sender);
211:     }
```

The issue arises when there are effective disputes, as the total amounts of collateral decrease since this collateral is returned to the `shortRecord` (`RedemptionFacet#L267-L268`):

```solidity
File: RedemptionFacet.sol
224:     function disputeRedemption(address asset, address redeemer, uint8 incorrectIndex, address disputeShorter, uint8 disputeShortId)
225:         external
226:         isNotFrozen(asset)
227:         nonReentrant
228:     {
...
...
261:             // @dev All proposals from the incorrectIndex onward will be removed
262:             // @dev Thus the proposer can only redeem a portion of their original slate
263:             for (uint256 i = incorrectIndex; i < decodedProposalData.length; i++) {
264:                 currentProposal = decodedProposalData[i];
265: 
266:                 STypes.ShortRecord storage currentSR = s.shortRecords[d.asset][currentProposal.shorter][currentProposal.shortId];
267:                 currentSR.collateral += currentProposal.colRedeemed;
268:                 currentSR.ercDebt += currentProposal.ercDebtRedeemed;
269: 
270:                 d.incorrectCollateral += currentProposal.colRedeemed;
271:                 d.incorrectErcDebt += currentProposal.ercDebtRedeemed;
272:             }
...
...
```

Consequently, the `redemptionFee` paid by the redeemer would be higher when there are effective disputes. If the redeemer is already paying a penalty, it would be appropriate to refund the fee for the `shortRecords` proposals that will no longer be processed due to an effective dispute.

## Proof of Concept

Consider the following scenario:

1. `UserA` submits proposals `shortRecordsId` `1` and `3`, totaling a collateral to receive, for example, `10 ETH` (5 ETH co
Cached Oracle Price Discrepancy in Redemption Process
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
useing a cached oracle price (LibOracle.getPrice(p.asset)) with a 15-minute validity window to enhance the efficiency of the hint system for order placement. This design choice can lead to scenarios where the cached price does not reflect the current market conditions accurately due to volatility in the cryptocurrency markets
The discrepancy between the cached oracle price and the actual market price at the time of redemption can result in unfair redemption outcomes. Proposers of redemptions might either benefit unduly or suffer losses due to the stale price information,

## Proof of Concept
here is an example show the bug :
Initial Cached Oracle Price: 100
Elapsed Time Since Last Oracle Update: 10 minutes (within the 15-minute cache window)
Market Price: 120
Proposer's Price View: 118 (The price proposer believes to be accurate based on their information)

- The contract's cached oracle price is 100, which is outdated but still within the cache window, so it hasn't been updated.
- A user proposes a redemption based on their view that the oracle price is 118, which is close to the actual market price of 120. 
- The contract compares the proposer's price view (118) against the cached oracle price (100).
- The price discrepancy (|118 - 100| = 18) exceeds 5% of the cached oracle price (5% of 100 = 5), leading to an "Unfair Redemption" classification due to significant price movement not reflected in the cached price

## Tools Used
manual review 
## Recommended Mitigation Steps

need to add update mechanism that adjusts the frequency of oracle price updates based on observed market volatility.


## Assessed type

Other
Inconsistent checks on Oracle price lead to inaccurate pricing
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Summary

Inconsistent price checks in `getOraclePrice()` will lead to wrong prices being used with detrimental effects to stability of protocol .

## Vulnerability Details

In `getOraclePrice()`, the returned price value for "other" oracles (e.g. JPY, XAU) is checked inconsistently.

Firstly, it is mentioned in the `Known Issues` regarding heartbeats:

> " 2 hours staleness means it can be somewhat out of date "

However, both the `try` block and the `catch` block are missing even any heartbeat check as it is stated in the docs that there should be [here](https://dittoeth.com/technical/oracles#invalid-fetch-data).
The `try` block does price checks in `OracleCircuitBreaker()` as below:

```
    function oracleCircuitBreaker(
        uint80 roundId,
        uint80 baseRoundId,
        int256 chainlinkPrice,
        int256 baseChainlinkPrice,
        uint256 timeStamp,
        uint256 baseTimeStamp
    ) private view {
>>>        bool invalidFetchData = roundId == 0 || timeStamp == 0 || timeStamp > block.timestamp || chainlinkPrice <= 0
>>>         || baseRoundId == 0 || baseTimeStamp == 0 || baseTimeStamp > block.timestamp || baseChainlinkPrice <= 0;

        if (invalidFetchData) revert Errors.InvalidPrice();
    }
```

Secondly, there is a missing check in the `catch` block that the timestamp is not equal to `0`, which is present in `oracleCircuitBreaker()` i.e. `timeStamp == 0`.

Thirdly, there is an inconsistent use of relational operators, where in the `try` block there is a check for negative prices but in the `catch` block the is just a check that the value is not equal to `0`.

In `oracleCircuitBreaker()` we see the check `chainlinkPrice <= 0` reverts if there is a negative price returned which, given that Chainlink's prices are `int` values, is relevant.
However in the `catch` block, as below, we see the check `price == 0`.

```
if (roundID == 0 || price == 0 || timeStamp > block.timestamp) revert Errors.InvalidPrice();

```

## Impact

Regarding the heartbeat and timestamp issues, these are esstential missing checks which protect the protocol from using stale prices.

Regarding the inconsistent application of relational operators, this means that if there is a failure in the `try` block because a negative price reverted there, that same incorrect price will be accepted and used in the `catch` block.

Both of these issues will cause huge issues to the correct functioning of the protocol via incorrect prices being fetched.

## Tools Used

Manual Review
Foundry Testing

## Recommendations

Given that different feeds have different heartbeats, create an Oracle data struct whereby their specific infomation can be stored and fetche
Holding both rEth and stEth can lead to funds getting locked in the protocol
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Summary

The ability to withdraw deposited funds and accrued yield is core functionality of the protocol; depositors will be unable to do so under certain conditions.

## Vulnerability Details

`assessDeth()` is called by `withdraw()` for `VAULT.ONE` withdrawals to calculate how much of the specified withdrawal, `dethAmount`, may be subject to fees.
This function also prevents a requester, who holds both `creditReth` & `creditSteth`, from withdrawing anything above their credits, i.e. yield, until they have first withdrawn those credits.
So a user's yield is essentially stuck in the protocol until all credits have been withdrawn.

If user is unable to withdraw one of those credits, because a bridge has less `dethValue` available than the user has credit, their yield is stuck in the protocol.

```
>>> if (IBridge(stethBridge).getDethValue() < C.ROUNDING_ZERO) {
        // Can withdraw RETH using STETH credit when STETH bridge is empty
        if (creditSteth >= amount) {
            VaultUser.bridgeCreditSteth -= amount;
            return 0;
        } else {
            VaultUser.bridgeCreditSteth = 0;
            return amount - creditSteth;
        }
    } else {
        // Must use available bridge credits on withdrawal
        // @dev Prevents abusing bridge for arbitrage
>>>     revert Errors.MustUseExistingBridgeCredit();
    }
```

This scenario can arise because, while user's are prevented from playing arbitrage on their credits, the protocol still permits arbitrage on yield.
Depositors with both tokens can choose which token to withdraw their yield in. If more of one is being withdrawn than the other, some users will be left unable to withdraw.

## POC

> Bob deposits `10 rETH` => `10 creditReth`

> Bob earns `1e18` yield => `10 creditReth + 1e18 yield`

> Bob deposits `10 stETH` => `10 creditReth + 10 creditstEth + 1e18 yield`

> Bob withdraws `10 reth` => `10 creditstEth + 1e18 yield`

> Bob tries to withdraw `11 stEth` but reverts because the bridge does only holds `5 stEth`

> Bob's yield is stuck in the protocol; he can withdraw maximum `5 stEth` until more funds are deposited into the bridge.

## Impact

User's with both credits can have their funds locked in the protocol indefinitely if one bridge is low.

## Tools Used

Manual Review
Foundry Testing

## Recommendations

Similarly to how credits work, track `yieldSteth` and `yieldReth` allowing user's only withdraw yield in the token in which it was earned unless the vault in which it was earned is empty.


## Assessed type

Other
The `LibOracle::getOraclePrice()` can return miscalculated prices due to flash crash events
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L33-L46
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L50-L65
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L43
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L37
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L27
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L54
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L44
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L125-L126
https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOracle.sol#L60


# Vulnerability details

## Impact

The `LibOracle::getOraclePrice()` does not detect or handle the Chainlink aggregators' `minAnswer` and `maxAnswer`. For instance, in case of a significant price drop, the asset price reported by the Chainlink price feed will continue to be at the pre-determined `minAnswer` instead of the actual price (below the `minAnswer`). For more, refer to the case of [Venus Protocol and Blizz Finance in the crash of LUNA](https://rekt.news/venus-blizz-rekt/).

In other words, in the flash crash event, Chainlink's price feeds can report incorrect `basePrice` and/or `price` variables. These incorrectly reported price variables lead to miscalculating the `basePriceInEth` (base price in ETH) or `priceInEth` (non-base price in ETH).

Whereas the calculation of the `basePriceInEth` has a price deviation detection mechanism to partially mitigate the issue, the calculation of the `priceInEth` does not validate or handle the issue.

Subsequently, the `getOraclePrice()` will return the miscalculated `basePriceInEth` and/or `priceInEth` (especially this one) to the protocol's core functions to consume, harming the protocol's and users' funds.

*Please refer to the `Proof of Concept` section for more details.*

## Proof of Concept

This PoC section can be categorized into three sub-sections.
1. **Introduction**
2. **Vulnerability Details**
3. **Lack of Detecting Chainlink Aggregators' `minAnswer` and `maxAnswer`**

### 1. Introduction

The `getOraclePrice()` calculates the base price in ETH (`ETH/USD` asset oracle) or non-base price in ETH (`non-ETH/USD` asset oracle) for the protocol's core functions to consume. The function can be divided into four code paths.
1. To calculate the [base price in ETH (ETH/USD asset oracle) in a `try` case](https:
Shorter can force Recovery Mode to push other users into liquidation
Audit: 2024-03-dittoeth-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Summary

A shorter can repay the ercDebt on their Short Record to trigger Recovery Mode and then liquidate users

## Vulnerability Details

The idea behind Recovery Mode is a higher CR limit, which is imposed on Short Records when certain functions are called on them.
Whether the higher CR limit gets imposed is determined by `checkRecoveryModeViolation()` and the calculation of an Asset's CR as a whole:

```
uint256 assetCR = Asset.dethCollateral.div(oraclePrice.mul(Asset.ercDebt));
```

This effectively gives external control over the setting of `recoveryCR`. When a shorter reduces their Short Record's `ercDebt` they will cause `Asset.dethCollateral` to decrease also.
This could be achieved by the repayment of Short Records with very healthy CRs via the short repayment functions in `ExitShortFacet.sol` which call `disburseCollateral()`:

```
    function disburseCollateral(address asset, address shorter, uint88 collateral, uint256 dethYieldRate, uint32 updatedAt)
        internal
    {
        // SOME CODE

        Asset.dethCollateral -= collateral;

        // SOME CODE
    }

```

Although `recoveryCR` will equal `liquidationCR` initially for dUSD, this will not be the case for all assets, and maybe not `dUSD` in long term.

## Impact

Being able to trigger a change of CR to a higher level can be very damaging to users of the protocol, allowing an attacker to build up large short positions before crashing `assetCR` and liquidatin users before they have time to adjust to the new CR requirement.

## Tools Used

Manual Review
Foundry Testing

## Recommendations

Consider a reduced incentive for liquidators at this higher level





## Assessed type

Other