msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Loss of precision in `twapPriceInEther` due to division before multiplication
Severity: low

Description:
When calculating `twapPriceInEther`, `twapPrice` is divided by 1e6 before multiplication with 1e18 is done.\\n```\\n    function baseOracleCircuitBreaker(\\n        uint256 protocolPrice,\\n        uint80 roundId,\\n        int256 chainlinkPrice,\\n        uint256 timeStamp,\\n        uint256 chainlinkPriceInEth\\n    ) private view returns (uint256 _protocolPrice) {\\n        \\n        // more code\\n\\n        if (invalidFetchData || priceDeviation) {\\n            uint256 twapPrice = IDiamond(payable(address(this))).estimateWETHInUSDC(\\n                Constants.UNISWAP_WETH_BASE_AMT, 30 minutes\\n            );\\n            uint256 twapPriceInEther = (twapPrice / Constants.DECIMAL_USDC) * 1 ether;\\n```\\n\\nhttps://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/libraries/LibOracle.sol#L64-L85\\nAccording to the above calculation, the `twapPrice` obtained would be precise upto 6 decimal places. Performing division before multiplying with 1e18 will result in loss of this precision and.\\nExample Scenario\\n```\\ntwapPrice = 1902501929\\ntwapPriceInEther = 1902000000000000000000\\n\\n// if multiplication is performed earlier,\\ntwapPriceInEther = 1902501929000000000000\\n```\\n

Impact:
Price used can have -1 (in 18 decimals) difference from the original price.

Recommended mitigation:
Perform the multiplication before division.\\n```\\ndiff // Remove the line below\\n// Remove the line below\\ngit a/contracts/libraries/LibOracle.sol b/contracts/libraries/LibOracle.sol\\nindex 23d1d0a..6962ad7 100644\\n// Remove the line below\\n// Remove the line below\\n// Remove the line below\\n a/contracts/libraries/LibOracle.sol\\n// Add the line below\\n// Add the line below\\n// Add the line below\\n b/contracts/libraries/LibOracle.sol\\n@@ // Remove the line below\\n82,7 // Add the line below\\n82,7 @@ library LibOracle {\\n             uint256 twapPrice = IDiamond(payable(address(this))).estimateWETHInUSDC(\\n                 Constants.UNISWAP_WETH_BASE_AMT, 30 minutes\\n             );\\n// Remove the line below\\n            uint256 twapPriceInEther = (twapPrice / Constants.DECIMAL_USDC) * 1 ether;\\n// Add the line below\\n            uint256 twapPriceInEther = (twapPrice * 1 ether) / Constants.DECIMAL_USDC;\\n             uint256 twapPriceInv = twapPriceInEther.inv();\\n             if (twapPriceInEther == 0) {\\n                 revert Errors.InvalidTwapPrice();\\n```\\n
Vulnerability: `onERC721Received()` callback is never called when new tokens are minted in Erc721Facet.sol
Severity: low

Description:
The ERC721Facet contract does not properly call the corresponding callback when new tokens are minted. The ERC721 standard states that the onERC721Received callback must be called when a mint or transfer operation occurs. However, the smart contracts interacting as users with `Erc721Facet.mintNFT()` will not be notified with the onERC721Received callback, as expected according to the ERC721 standard.\\n`onErc721Received()` isn't called on minting:\\n```\\n    function mintNFT(address asset, uint8 shortRecordId)\\n        external\\n        isNotFrozen(asset)\\n        nonReentrant\\n        onlyValidShortRecord(asset, msg.sender, shortRecordId)\\n    {\\n        if (shortRecordId == Constants.SHORT_MAX_ID) {\\n            revert Errors.CannotMintLastShortRecord();\\n        }\\n        STypes.ShortRecord storage short =\\n            s.shortRecords[asset][msg.sender][shortRecordId];\\n\\n        if (short.tokenId != 0) revert Errors.AlreadyMinted();\\n\\n        s.nftMapping[s.tokenIdCounter] = STypes.NFT({\\n            owner: msg.sender,\\n            assetId: s.asset[asset].assetId,\\n            shortRecordId: shortRecordId\\n        });\\n\\n        short.tokenId = s.tokenIdCounter;\\n\\n        //@dev never decreases\\n        s.tokenIdCounter += 1;\\n    }\\n```\\n

Impact:
It can create interoperability issues with users' contracts

Recommended mitigation:
Call `onErc721Received()`
Vulnerability: [L-4] Yield update will not happen at the 1k ETH threshold
Severity: low

Description:
Yield updates happen for a vault when the `BRIDGE_YIELD_UPDATE_THRESHOLD` is met for the vault after a large bridge deposit. The `maybeUpdateYield` function handles this logic for updates when that happens (1000 ETH to be exact).\\nThreshold constant from:\\n```\\nFILE: 2023-09-ditto/contracts/libraries/Constants.sol\\n\\nLine 17:\\nuint256 internal constant BRIDGE_YIELD_UPDATE_THRESHOLD = 1000 ether;\\n\\nLine 18:\\nuint256 internal constant BRIDGE_YIELD_PERCENT_THRESHOLD = 0.01 ether; // 1%\\n```\\n\\n```\\nFILE: 2023-09-ditto/contracts/facets/BridgeRouterFacet.sol\\n\\nfunction maybeUpdateYield(uint256 vault, uint88 amount) private {\\n        uint88 zethTotal = s.vault[vault].zethTotal;\\n        if (\\n            zethTotal > Constants.BRIDGE_YIELD_UPDATE_THRESHOLD\\n                && amount.div(zethTotal) > Constants.BRIDGE_YIELD_PERCENT_THRESHOLD\\n        ) { // @audit should be >= to account for when threshold is met\\n            // Update yield for "large" bridge deposits\\n            vault.updateYield();\\n        }\\n    }\\n```\\n

Impact:
In reality the yield update for the vault will not happen in the instances of 1000 ETH deposits unless the bridge deposit amount into the vault is > 1000 ETH and the percent is greater than 1%.

Recommended mitigation:
Change the `>` operand in the `maybeUpdateYield` function to be `>=`.
Vulnerability: Possible DOS on deposit(), withdraw() and unstake() for BridgeReth, leading to user loss of funds
Severity: medium

Description:
Future changes on deposit delay on rETH tokens would prevent DittoETH users to use deposit(), withdraw() and unstake() for BridgeReth, which would make its transfering and burning impractical, leading to user funds losses.\\nRocketPool rETH tokens has a deposit delay that prevents any user who has recently deposited to transfer or burn tokens. In the past this delay was set to 5760 blocks mined (aprox. 19h, considering one block per 12s). This delay can prevent DittoETH users from transfering if another user staked recently.\\nFile: RocketTokenRETH.sol\\n```\\n  // This is called by the base ERC20 contract before all transfer, mint, and burns\\n    function _beforeTokenTransfer(address from, address, uint256) internal override {\\n        // Don't run check if this is a mint transaction\\n        if (from != address(0)) {\\n            // Check which block the user's last deposit was\\n            bytes32 key = keccak256(abi.encodePacked("user.deposit.block", from));\\n            uint256 lastDepositBlock = getUint(key);\\n            if (lastDepositBlock > 0) {\\n                // Ensure enough blocks have passed\\n                uint256 depositDelay = getUint(keccak256(abi.encodePacked(keccak256("dao.protocol.setting.network"), "network.reth.deposit.delay")));\\n                uint256 blocksPassed = block.number.sub(lastDepositBlock);\\n                require(blocksPassed > depositDelay, "Not enough time has passed since deposit");\\n                // Clear the state as it's no longer necessary to check this until another deposit is made\\n                deleteUint(key);\\n            }\\n        }\\n    }\\n```\\n\\nAny future changes made to this delay by the admins could potentially lead to a denial-of-service attack on the `BridgeRouterFacet::deposit` and `BridgeRouterFacet::withdraw` mechanism for the rETH bridge.

Impact:
Currently, the delay is set to zero, but if RocketPool admins decide to change this value in the future, it could cause issues. Specifically, protocol users staking actions could prevent other users from unstaking for a few hours. Given that many users call the stake function throughout the day, the delay would constantly reset, making the unstaking mechanism unusable. It's important to note that this only occurs when stake() is used through the rocketDepositPool route. If rETH is obtained from the Uniswap pool, the delay is not affected.\\nAll the ETH swapped for rETH calling `BridgeReth::depositEth` would become irrecuperable, leading to a user bank run on DittoETH to not be perjudicated of this protocol externalization to all the users that have deposited.

Recommended mitigation:
Possible DOS on deposit(), withdraw() and unstake() for BridgeReth, leading to user loss of funds\\nConsider modifying Reth bridge to obtain rETH only through the UniswapV3 pool, on average users will get less rETH due to the slippage, but will avoid any future issues with the deposit delay mechanism.
Vulnerability: ETH cannot always be unstaked using Rocket Pool
Severity: low

Description:
The protocol lets users unstake Ethereum using any bridge they want. Rocket Pool may not have enough ETH to satisfy unstake transactions, this will cause the transaction to revert.\\nWhen users try to unstake ETH using Rocket Pool, the transaction may revert because Rocket Pool may not have enough ETH in its deposit pool and rEth contract to satisfy the unstake request. Rocket pool sources ETH for unstaking from the rEth contract and deposit pool. When they are empty it cannot satisfy unstake requests. More information can be found in the Unstake section of the rocketPool documentation.\\nThe pools have been empty before. Here's a proof of concept of failed withdrawals when Rocket Pool's rEth contract and deposit pool were empty at block 15361748.\\n```\\n   function testWithdrawETHfromRocketPool() public{\\n    string memory MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL");\\n    uint256 mainnetFork = vm.createFork(MAINNET_RPC_URL, 15361748);\\n\\n    RocketTokenRETHInterface rEth = RocketTokenRETHInterface(0xae78736Cd615f374D3085123A210448E74Fc6393);\\n    vm.selectFork(mainnetFork);\\n    uint totalCollateral = rEth.getTotalCollateral();\\n    assertEq(totalCollateral, 0); // pools are empty\\n\\n    address owner = 0x50A78DFb9F5CC22ac8ffA90FA2B6C595881CCb97; // has rEth at block 15361748\\n    uint rEthBalance = rEth.balanceOf(owner);\\n    assertGt(rEthBalance, 0);\\n    \\n    vm.expectRevert("Insufficient ETH balance for exchange");\\n    vm.prank(owner); \\n    rEth.burn(rEthBalance);\\n  }\\n```\\n

Impact:
If Rocket Pool's rEth contract and deposit Pool do not have enough ETH to satisfy an unstake transaction the transaction will revert.

Recommended mitigation:
Check if Rocket Pool has enough ETH and if it doesn't, rEth can be exchanged for ETH on a DEX and sent to the user.
Vulnerability: Incorrect require in setter
Severity: low

Description:
There are 3 setters in `OwnerFacet.sol` which require statement doesn't match with the error message.\\n`_setInitialMargin`, `_setPrimaryLiquidationCR` and `_setSecondaryLiquidationCR` will revert for the value 100, which will revert with an incorrect error message, which is `"below 1.0"`. When 100 is 1.0, not below.\\n*Instances (3)`\\n```\\n    function _setInitialMargin(address asset, uint16 value) private {\\n        require(value > 100, "below 1.0"); // @audit a value of 100 is 1x, so this should be > 101\\n        s.asset[asset].initialMargin = value;\\n        require(LibAsset.initialMargin(asset) < Constants.CRATIO_MAX, "above max CR");\\n    }\\n\\n    function _setPrimaryLiquidationCR(address asset, uint16 value) private {\\n        require(value > 100, "below 1.0"); // @audit a value of 100 is 1x, so this should be > 101\\n        require(value <= 500, "above 5.0");\\n        require(value < s.asset[asset].initialMargin, "above initial margin");\\n        s.asset[asset].primaryLiquidationCR = value;\\n    }\\n\\n    function _setSecondaryLiquidationCR(address asset, uint16 value) private {\\n        require(value > 100, "below 1.0"); // @audit a value of 100 is 1x, so this should be > 101\\n        require(value <= 500, "above 5.0");\\n        require(value < s.asset[asset].primaryLiquidationCR, "above primary liquidation");\\n        s.asset[asset].secondaryLiquidationCR = value;\\n    }\\n```\\n\\nAs it is contrastable, in the below functions, this check is done correctly:\\n```\\n    function _setForcedBidPriceBuffer(address asset, uint8 value) private {\\n        require(value >= 100, "below 1.0");\\n        require(value <= 200, "above 2.0");\\n        s.asset[asset].forcedBidPriceBuffer = value;\\n    }\\n\\n    function _setMinimumCR(address asset, uint8 value) private {\\n        require(value >= 100, "below 1.0");\\n        require(value <= 200, "above 2.0");\\n        s.asset[asset].minimumCR = value;\\n        require(\\n            LibAsset.minimumCR(asset) < LibAsset.secondaryLiquidationCR(asset),\\n            "above secondary liquidation"\\n        );\\n    }\\n```\\n

Impact:
The incorrect value for the require statement could lead to a restriction of precion for this parameters, it wouldn't be possible to input a net value of 100.

Recommended mitigation:
Value to which is checked the `>` operator should be 101, not 100.
Vulnerability: Instant arbitrage opportunity through rETH and stETH price discrepancy
Severity: low

Description:
User can choose to withdraw their zETH to be a rETH or stETH, while in reality most user will choose the best return (highest value between rETH and stETH), instant arbitrage will happen and this will trigger pool imbalance, draining one over the other.\\nIn DittoETH, they accept two special types of Ethereum tokens: rETH and stETH. These tokens are based on regular ETH but are designed to stay close in value to one regular Ether. However, in reality, they can have slightly different values. rETH, stETH.\\nIn practice, when user want to withdraw, they can choose between rETH and stETH based on which one is worth more at that moment. The system doesn't really care which one you put in when a user first deposited their asset.\\nNow, here's where it gets interesting. Because rETH and stETH can have slightly different values, a savvy user could deposit the cheaper one, get a zeth, and then withdraw the more valuable rETH and stETH. a quick way to make some extra profit\\nAs we can see on line 110-112, the rETH or stETH withdrawn is depends on `ethAmount`, which from `_ethConversion` it's amount is 'equal' between rETH and stETH\\n```\\nFile: BridgeRouterFacet.sol\\n    function withdraw(address bridge, uint88 zethAmount)\\n        external\\n        nonReentrant\\n        onlyValidBridge(bridge)\\n    {\\n        if (zethAmount == 0) revert Errors.ParameterIsZero();\\n        uint88 fee;\\n        uint256 withdrawalFee = bridge.withdrawalFee();\\n        uint256 vault;\\n        if (bridge == rethBridge || bridge == stethBridge) {\\n            vault = Vault.CARBON;\\n        } else {\\n            vault = s.bridge[bridge].vault;\\n        }\\n        if (withdrawalFee > 0) {\\n            fee = zethAmount.mulU88(withdrawalFee);\\n            zethAmount -= fee;\\n            s.vaultUser[vault][address(this)].ethEscrowed += fee;\\n        }\\n        uint88 ethAmount = _ethConversion(vault, zethAmount);\\n        vault.removeZeth(zethAmount, fee);\\n        IBridge(bridge).withdraw(msg.sender, ethAmount);\\n        emit Events.Withdraw(bridge, msg.sender, zethAmount, fee);\\n    }\\n// rest of code\\n    function _ethConversion(uint256 vault, uint88 amount) private view returns (uint88) {\\n        uint256 zethTotalNew = vault.getZethTotal();\\n        uint88 zethTotal = s.vault[vault].zethTotal;\\n        if (zethTotalNew >= zethTotal) {\\n            // when yield is positive 1 zeth = 1 eth\\n            return amount;\\n        } else {\\n            // negative yield means 1 zeth < 1 eth\\n            return amount.mulU88(zethTotalNew).divU88(zethTotal);\\n        }\\n    }\\n```\\n

Impact:
Instant arbitrage opportunity through rETH and stETH price discrepancy, will also trigger imbalance between rETH and stETH pool.

Recommended mitigation:
Consider to use oracle to adjust the price difference between rETH and stETH
Vulnerability: Division before multiplication results in lower `dittoMatchedShares` distributed to users
Severity: medium

Description:
Shares amount is rounded down to number of days staked. Max truncation is 1 day, min time is 14 days. At most 1 / 14 * 100% = 7.1% of accrued shares will be truncated.\\nDivision before multiplication\\n```\\n            uint88 shares = eth * (timeTillMatch / 1 days);\\n```\\n\\nSuppose `timeTillMatch = 14.99 days`, `eth = 1e18`. Expected result is `14.99 * 1e18 / 1 = 14.99e18 shares`. Actual result is `1e18 * (14.99 / 1) = 14e18 shares`

Impact:
Up to 7.1% of user's shares will be truncated

Recommended mitigation:
```\\n-           uint88 shares = eth * (timeTillMatch / 1 days);\\n+           uint88 shares = uint88(uint256(eth * timeTillMatch) / 1 days);\\n```\\n
Vulnerability: Malicious trader can intentionally obtain `dittoMatchedShares` in some edges cases
Severity: low

Description:
Malicious trader can intentionally obtain `dittoMatchedShares` by creating a bid order using a low price that nobody will ask, then wait for more than 14 days and the same malicious trader create an ask order using the same bid's low price causing the increase of `dittoMatchedShares`.\\nMalicious trader can create a bid order using the BidOrdersFacet::createBid() function at very low price, then the same malicious trader can wait some days until the minumum required in order to get `dittoMatchedShares` and set a `ask` order using the bid's low price. Please consider the next scenario:\\n```\\nMarket status:\\nassetX: current price 100\\n```\\n\\nMalicious trader creates the `bid order` for the `assetX` using the `price: 10` (low price compared to the current 100 price) and `ercAmount 10`. The low price is because nobody wants to sell at that price so the order can stay there without be matched.\\nThe `bid order` will be submitted to the order book because there are not `asks/sells` to fill at that price.\\nMalicious trader waits for more than 14 days. Additionally the malicious trader needs to wait until there are not `asks/sells` in the order book.\\nOnce the step 3 is ok, the Malicious trader creates the `ask order` at `price 10 and ercAmount10` (the bid's order price from step 1). The order is matched with the `bid order` from the step 1 and `dittoMatchedShares` are assigned to the malicious trader.\\nIt is a very edge case because the malicious trader needs an empty `ask/sells` orderbook so he can put his own `ask order` at the malicious bid order price but in conditions where the asset is not very trader the malicious actor can benefit from this.

Impact:
Malicious actor can intentionally obtain `dittoMatchedShares` using `bid/asks` orders that he intentionally crafts. The `bid/ask` orders are created by the same malicious actor, so he won't lose assets.\\nTools used\\nManual review

Recommended mitigation:
Verify that the address from the `bid order` is not the same address who is creating the `ask` order.
Vulnerability: Event in secondaryLiquidation could be misused to show false liquidations
Severity: low

Description:
The `liquidateSecondary` function in the protocol is designed to emit events detailing the specifics of liquidation, which can be crucial for other protocols or front-end integrations that track secondary liquidations within the protocol. One of the values emitted is `batches`, which indicates which positions got liquidated. However the function emits the `batches` array as it initially receives it, even though it may skip positions that are not eligible for liquidation during its execution. This implies that the emitted event could represent incorrect data, indicating positions as liquidated even if they were not, due to their ineligibility.\\n```\\nfunction liquidateSecondary(\\n        address asset,\\n        MTypes.BatchMC[] memory batches,\\n        uint88 liquidateAmount,\\n        bool isWallet\\n    ) external onlyValidAsset(asset) isNotFrozen(asset) nonReentrant {\\n        // Initial code\\n\\n        emit Events.LiquidateSecondary(asset, batches, msg.sender, isWallet);\\n    }\\n```\\n

Impact:
This inconsistency in the emitted event data can lead to incorrect data, indicating positions as liquidated even if they were not.

Recommended mitigation:
Event in secondaryLiquidation could be misused to show false liquidations\\nModify the `batches` array before emitting it in the event, ensuring it accurately reflects the positions that were actually liquidated.
Vulnerability: The protocol allows less flags to be generated than possible which could lead to a DoS of the primary liquidation process
Severity: low

Description:
The maximum flags (for liquidation) that can exist at the same time should be limited by the maximum number, of flaggerIdCounter which is a uint24, but it is limited by the maximum number of a uint16 instead. Therefore, a maximum of 65535 shortRecords can be flagged for liquidation at the same time. This is way too less if the protocol is used a lot and a market goes up in price, and would therefore lead to a DoS of the liquidation process.\\nThe maximum of the flaggerIdCounter and therefore the maximum of flags that can exist at the same time is limited by the maximum number of a uint24:\\n```\\nuint24 flaggerIdCounter;\\n```\\n\\nIf there are no flags left to override the system tries to generate a new flagId, but it does not use the maximum number of uint24, it uses the maximum number of uint16 instead, which is 65535:\\n```\\n} else if (s.flaggerIdCounter < type(uint16).max) {\\n    //@dev generate brand new flaggerId\\n    short.flaggerId = flagStorage.g_flaggerId = s.flaggerIdCounter;\\n    s.flaggerIdCounter++;\\n} else {\\n    revert Errors.InvalidFlaggerHint();\\n}\\n```\\n\\nThis could be way to less if the protocol is used a lot and the price of a market goes up. Therefore it would prevent creating new flaggerIds and shortRecords with unhealthy CR can not be liquidated.

Impact:
DoS of the liquidation process, which could potentially lead to a lot of shortRecords with unhealthy CR, which could in the worst case lead to the situation that assets are no longer backed enough, and the market needs to be shut down. This would result in a big loss of user funds.

Recommended mitigation:
Set the check to type(uint24).max.