MEDIUM severity: The vulnerability in the DittoETH protocol's collateral ratio calculation causes potential loss due . The vulnerability in the DittoETH protocol's collateral ratio calculation causes potential loss due to rounding-up. This occurs when the user's collateral ratio is calculated by dividing the collateral by the ERC-20 debt, multiplied by the oracle price, before multiplying the result by the oracle pr...

Vulnerable code:
```\nuint256 cRatio = \n    short.getCollateralRatioSpotPrice(LibOracle.getSavedOrSpotOraclePrice(asset));\n```\n```\nuint256 oraclePrice = LibOracle.getOraclePrice(asset);\nuint256 cRatio = short.getCollateralRatioSpotPrice(oraclePrice);\n```\n```\nFile: contracts/libraries/LibOracle.sol\n\nfunction getOraclePrice(address asset) internal view returns (uint256) {\n    AppStorage storage s = appStorage();\n    AggregatorV3Interface baseOracle = AggregatorV3Interface(s.baseOracle);\n    uint256 protocolPrice = getPrice(asset);\n\n    (\n        uint80 baseRoundID,\n        int256 basePrice,\n        //,\n        uint256 baseTimeStamp,\n        //,\n        uint80 baseAnsweredInRound\n    ) = baseOracle.latestRoundData();\n\n    AggregatorV3Interface oracle = AggregatorV3Interface(s.asset[asset].oracle);\n    if (address(oracle) == address(0)) revert Errors.InvalidAsset();\n\n    if (oracle == baseOracle) {\n        uint256 basePriceInEth = basePrice > 0\n           ? uint256(basePrice * Constants.BASE_ORACLE_DECIMALS).inv()\n            : 0;\n        basePriceInEth = baseOracleCircuitBreaker(\n            protocolPrice, baseRoundID, basePrice, baseTimeStamp, basePriceInEth\n        );\n        return basePriceInEth;\n    } else {\n        (\n            uint80 roundID,\n            int256 price,\n            //,\n            uint256 timeStamp,\n            //,\n            uint80 answeredInRound\n        ) = oracle.latestRoundData();\n        uint256 priceInEth = uint256(price).div(uint256(basePrice));\n        oracleCircuitBreaker(\n            roundID, baseRoundID, price, basePrice, timeStamp, baseTimeStamp\n        );\n        return priceInEth;\n    }\n}\n```\n```\nFile: contracts/libraries/LibOracle.sol\n\nfunction getSavedOrSpotOraclePrice(address asset) internal view returns (uint256) {\n    if (LibOrders.getOffsetTime() - getTime(asset) < 15 minutes) {\n        return getPrice(asset);\n    } else {\n        return getOraclePrice(asset);\n    }\n}\n```\n```\nFile: contracts/libraries/LibShortRecord.sol\n\nfunction getCollateralRatioSpotPrice(\n    STypes.ShortRecord memory short,\n    uint256 oraclePrice\n) internal pure returns (uint256 cRatio) {\n    return short.collateral.div(short.ercDebt.mul(oraclePrice));\n}\n```\n```\nif (short.getCollateralRatioSpotPrice(LibOracle.getSavedOrSpotOraclePrice(asset)) < LibAsset.primaryLiquidationCR(asset)):\n```\n```\nshortCollateral / (shortErcDebt * (oracleN / oracleD))\n```\n```\n      (shortCollateral * oracleD) / (shortErcDebt * oracleN)\n```\n```\nuint256 private short_collateral = 100361729669569000000;\nuint256 private short_ercDebt = 100000000000000000000000;\nuint256 private price = 99995505;\nuint256 private basePrice = 199270190598;\nuint256 private primaryLiquidationCR = 2000000000000000000;\n\nuint256 private priceInEth = price / basePrice;\n```\n```\nif (\n    short.getCollateralRatioSpotPrice(LibOracle.getSavedOrSpotOraclePrice(asset)) \n    >= LibAsset.primaryLiquidationCR(asset)\n) {\n    revert Errors.SufficientCollateral();\n```\n```\n// SPDX-License-Identifier: GPL-3.0-only\npragma solidity 0.8.21;\n\nimport {U256} from "contracts/libraries/PRBMathHelper.sol";\nimport {OBFixture} from "test/utils/OBFixture.sol";\nimport {console} from "contracts/libraries/console.sol";\n\ncontract IncorrectCRatioCheck is OBFixture {\n    using U256 for uint256;\n\n    uint256 private short_collateral = 85307470219133700000; // ~ 85.3 ether\n    uint256 private short_ercDebt = 100000000000000000000000; // 100_000 ether\n    uint256 private price = 99995505; // oracleN\n    uint256 private basePrice = 199270190598; // (as on forked mainnet)  // oracleD\n    uint256 private primaryLiquidationCR = 1700000000000000000; // 1.7 ether (as on forked mainnet)\n\n    function _getSavedOrSpotOraclePrice() internal view returns (uint256) {\n        uint256 priceInEth = price / basePrice;\n        return priceInEth; // will return 501808648347845 =~ 0.0005 ether  // (as on forked mainnet)\n    }\n\n    function getCollateralRatioSpotPrice_IncorrectStyle_As_In_Existing_DittoProtocol(\n        uint256 oraclePrice\n    ) internal view returns (uint256) {\n        return short_collateral / short_ercDebt * oraclePrice;\n    }\n\n    function getCollateralRatioSpotPrice_CorrectStyle(uint256 oracleN, uint256 oracleD)\n        internal\n        view\n        returns (uint256)\n    {\n        return (short_collateral * oracleD) / (short_ercDebt * oracleN);\n    }\n\n    function test_GetCollateralRatioSpotPrice_IncorrectStyle_As_In_Existing_DittoProtocol()\n        public\n        view\n    {\n        uint256 cRatio = getCollateralRatioSpotPrice_IncorrectStyle_As_In_Existing_DittoProtocol(\n            _getSavedOrSpotOraclePrice()\n        );\n        console.log("cRatio calculated (existing style) =", cRatio);\n        if (cRatio >= primaryLiquidationCR) {\n            console.log("Errors.SufficientCollateral; can not be flagged");\n        } else {\n            console.log("InsufficientCollateral; can be flagged");\n        }\n    }\n\n    function test_Get\n```\n```\nLogs:\n    cRatio calculated (existing style) = 1700000000000000996\n    Errors.SufficientCollateral; can not be flagged\n```\n```\nLogs:\n    cRatio calculated (correct style) = 1699999999999899995\n    InsufficientCollateral; can be flagged\n```