msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Use A's staked token balance can be used to mint option token as reward for User B if the payout token equals to the stake token
Severity: medium

Description:
User's staked token balance can be used to mint option token as reward if the payout token equals to the stake token, can cause user to loss fund\\nIn OTLM, user can stake stakeToken in exchange for the option token minted from the payment token\\nwhen staking, we transfer the stakedToken in the OTLM token\\n```\\n// Increase the user's stake balance and the total balance\\nstakeBalance[msg.sender] = userBalance + amount_;\\ntotalBalance += amount_;\\n\\n// Transfer the staked tokens from the user to this contract\\nstakedToken.safeTransferFrom(msg.sender, address(this), amount_);\\n```\\n\\nbefore the stake or unstake or when we are calling claimReward\\nwe are calling _claimRewards -> _claimEpochRewards -> we use payout token to mint and create option token as reward\\n```\\n payoutToken.approve(address(optionTeller), rewards);\\n optionTeller.create(optionToken, rewards);\\n\\n // Transfer rewards to sender\\n ERC20(address(optionToken)).safeTransfer(msg.sender, rewards);\\n```\\n\\nthe problem is, if the stake token and the payout token are the same token, the protocol does not distingush the balance of the stake token and the balance of payout token\\nsuppose both stake token and payout token are USDC\\nsuppose user A stake 100 USDC\\nsuppose user B stake 100 USDC\\ntime passed, user B accure 10 token unit reward\\nnow user B can claimRewards,\\nthe protocol user 10 USDC to mint option token for B\\nthe OTLM has 190 USDC\\nif user A and user B both call emergencyUnstakeAll, whoeve call this function later will suffer a revert and he is not able to even give up the reward and claim their staked balance back\\nbecause a part of the his staked token balance is treated as the payout token to mint option token reward for other user

Impact:
If there are insufficient payout token in the OTLM, the expected behavior is that the transaction revert when claim the reward and when the code use payout token to mint option token\\nand in the worst case, user can call emergencyUnstakeAll to get their original staked balane back and give up their reward\\nhowever, if the staked token is the same as the payout token,\\na part of the user staked token can be mistakenly and constantly mint as option token reward for his own or for other user and eventually when user call emergencyUnstakeAll, there will be insufficient token balance and transaction revert\\nso user will not able to get their staked token back

Recommended mitigation:
Seperate the accounting of the staked user and the payout token or check that staked token is not payout token when creating the OTLM.sol
Vulnerability: IERC20(token).approve revert if the underlying ERC20 token approve does not return boolean
Severity: medium

Description:
IERC20(token).approve revert if the underlying ERC20 token approve does not return boolean\\nWhen transferring the token, the protocol use safeTransfer and safeTransferFrom\\nbut when approving the payout token, the safeApprove is not used\\nfor non-standard token such as USDT,\\ncalling approve will revert because the solmate ERC20 enforce the underlying token return a boolean\\n```\\n    function approve(address spender, uint256 amount) public virtual returns (bool) {\\n        allowance[msg.sender][spender] = amount;\\n\\n        emit Approval(msg.sender, spender, amount);\\n\\n        return true;\\n    }\\n```\\n\\nwhile the token such as USDT does not return boolean

Impact:
USDT or other ERC20 token that does not return boolean for approve is not supported as the payout token

Recommended mitigation:
Use safeApprove instead of approve
Vulnerability: Division before multiplication result in loss of token reward if the reward update time elapse is small
Severity: medium

Description:
Division before multiplication result in loss of token reward\\nWhen calcuting the reward, we are calling\\n```\\n function currentRewardsPerToken() public view returns (uint256) {\\n        // Rewards do not accrue if the total balance is zero\\n        if (totalBalance == 0) return rewardsPerTokenStored;\\n\\n        // @audit\\n        // loss of precision\\n        // The number of rewards to apply is based on the reward rate and the amount of time that has passed since the last reward update\\n        uint256 rewardsToApply = ((block.timestamp - lastRewardUpdate) * rewardRate) /\\n            REWARD_PERIOD;\\n\\n        // The rewards per token is the current rewards per token plus the rewards to apply divided by the total staked balance\\n        return rewardsPerTokenStored + (rewardsToApply * 10 ** stakedTokenDecimals) / totalBalance;\\n    }\\n```\\n\\nthe precision loss can be high because the accumulated reward depends on the time elapse:\\n(block.timestamp - lastRewardUpdate)\\nand the REWARD_PERIOD is hardcoded to one days:\\n```\\n    /// @notice Amount of time (in seconds) that the reward rate is distributed over\\n    uint48 public constant REWARD_PERIOD = uint48(1 days);\\n```\\n\\nif the time elapse is short and the currentRewardsPerToken is updated frequently, the precision loss can be heavy and even rounded to zero\\nthe lower the token precision, the heavier the precision loss\\nSome tokens have low decimals (e.g. USDC has 6). Even more extreme, some tokens like Gemini USD only have 2 decimals.\\nconsider as extreme case, if the reward token is Gemini USD, the reward rate is set to 1000 * 10 = 10 ** 4 = 10000\\nif the update reward keep getting called within 8 seconds\\n8 * 10000 / 86400 is already rounded down to zero and no reward is accuring for user

Impact:
Division before multiplication result in loss of token reward if the reward update time elapse is small

Recommended mitigation:
Avoid division before multiplcation and only perform division at last
Vulnerability: FixedStrikeOptionTeller: create can be invoked when block.timestamp == expiry but exercise reverts
Severity: medium

Description:
In `FixedStrikeOptionTeller` contract, new option tokens can be minted when `block.timestamp == expiry` but these option tokens cannot be exercised even in the same transaction.\\nThe `create` function has this statement:\\n```\\n        if (uint256(expiry) < block.timestamp) revert Teller_OptionExpired(expiry);\\n```\\n\\nThe `exercise` function has this statement:\\n```\\n        if (uint48(block.timestamp) >= expiry) revert Teller_OptionExpired(expiry);\\n```\\n\\nNotice the `>=` operator which means when `block.timestamp == expiry` the `exercise` function reverts.\\nThe `FixedStrikeOptionTeller.create` function is invoked whenever a user claims his staking rewards using `OTLM.claimRewards` or `OTLM.claimNextEpochRewards`. (here)\\nSo if a user claims his rewards when `block.timestamp == expiry` he receives the freshly minted option tokens but he cannot exercise these option tokens even in the same transaction (or same block).\\nMoreover, since the receiver do not possess these freshly minted option tokens, he cannot `reclaim` them either (assuming `reclaim` function contains the currently missing `optionToken.burn` statement).

Impact:
Option token will be minted to user but he cannot exercise them. Receiver cannot reclaim them as he doesn't hold that token amount.\\nThis leads to loss of funds as the minted option tokens become useless. Also the scenario of users claiming at expiry is not rare.

Recommended mitigation:
Consider maintaining a consistent timestamp behaviour. Either prevent creation of option tokens at expiry or allow them to be exercised at expiry.
Vulnerability: claimRewards() If a rewards is too small, it may block other epochs
Severity: medium

Description:
When `claimRewards()`, if some `rewards` is too small after being round down to 0 If `payoutToken` does not support transferring 0, it will block the subsequent epochs\\nThe current formula for calculating rewards per cycle is as follows.\\n```\\n    function _claimEpochRewards(uint48 epoch_) internal returns (uint256) {\\n// rest of code\\n      uint256 rewards = ((rewardsPerTokenEnd - userRewardsClaimed) * stakeBalance[msg.sender]) /\\n            10 ** stakedTokenDecimals;\\n        // Mint the option token on the teller\\n        // This transfers the reward amount of payout tokens to the option teller in exchange for the amount of option tokens\\n        payoutToken.approve(address(optionTeller), rewards);\\n        optionTeller.create(optionToken, rewards);\\n```\\n\\nCalculate `rewards` formula : uint256 `rewards` = ((rewardsPerTokenEnd - userRewardsClaimed) * stakeBalance[msg.sender]) /10 ** stakedTokenDecimals;\\nWhen `rewardsPerTokenEnd` is very close to `userRewardsClaimed`, `rewards` is likely to be round downs to 0 Some tokens do not support transfer(amount=0) This will revert and lead to can't claims

Impact:
Stuck `claimRewards()` when the rewards of an epoch is 0

Recommended mitigation:
```\\n    function _claimEpochRewards(uint48 epoch_) internal returns (uint256) {\\n// rest of code..\\n\\n        uint256 rewards = ((rewardsPerTokenEnd - userRewardsClaimed) * stakeBalance[msg.sender]) /\\n            10 ** stakedTokenDecimals;\\n+      if (rewards == 0 ) return 0;\\n        // Mint the option token on the teller\\n        // This transfers the reward amount of payout tokens to the option teller in exchange for the amount of option tokens\\n        payoutToken.approve(address(optionTeller), rewards);\\n        optionTeller.create(optionToken, rewards);\\n```\\n
Vulnerability: Owners will incur loss and bad debt if the value of a token crashes
Severity: medium

Description:
If the value of the swapped tokens crash, many users will choose not to claim the orders, which result in the owner being unable to recoup back the gas fee the owner has already paid for automating the fulfillment of the orders, incurring loss and bad debt.\\n```\\nFile: LimitOrderRegistry.sol\\n    function claimOrder(uint128 batchId, address user) external payable returns (ERC20, uint256) {\\n..SNIP..\\n        // Transfer fee in.\\n        address sender = _msgSender();\\n        if (msg.value >= userClaim.feePerUser) {\\n            // refund if necessary.\\n            uint256 refund = msg.value - userClaim.feePerUser;\\n            if (refund > 0) sender.safeTransferETH(refund);\\n        } else {\\n            WRAPPED_NATIVE.safeTransferFrom(sender, address(this), userClaim.feePerUser);\\n            // If value is non zero send it back to caller.\\n            if (msg.value > 0) sender.safeTransferETH(msg.value);\\n        }\\n```\\n\\nUsers only need to pay for the gas cost for fulfilling the order when they claim the order to retrieve the swapped tokens. When the order is fulfilled, the swapped tokens will be sent to and stored in the `LimitOrderRegistry` contract.\\nHowever, in the event that the value of the swapped tokens crash (e.g., Terra's LUNA crash), it makes more economic sense for the users to abandon (similar to defaulting in traditional finance) the orders without claiming the worthless tokens to avoid paying the more expensive fee to the owner.\\nAs a result, many users will choose not to claim the orders, which result in the owner being unable to recoup back the gas fee the owner has already paid for automating the fulfillment of the orders, incurring loss and bad debt.

Impact:
Owners might be unable to recoup back the gas fee the owner has already paid for automating the fulfillment of the orders, incurring loss and bad debt.

Recommended mitigation:
Consider collecting the fee in advance based on a rough estimation of the expected gas fee. When the users claim the order, any excess fee will be refunded, or any deficit will be collected from the users.\\nIn this case, if many users choose to abandon the orders, the owner will not incur any significant losses.
Vulnerability: Bypass the blacklist restriction because the blacklist check is not done when minting or burning
Severity: high

Description:
Bypass the blacklist restriction because the blacklist check is not done when minting or burning\\nIn the whitepaper:\\nthe protocol emphasis that they implement a blacklist feature for enforcing OFAC, AML and other account security requirements A blacklisted will not able to send or receive tokens\\nthe protocol want to use the whitelist feature to be compliant to not let the blacklisted address send or receive dSahres\\nFor this reason, before token transfer, the protocol check if address from or address to is blacklisted and the blacklisted address can still create buy order or sell order\\n```\\n   function _beforeTokenTransfer(address from, address to, uint256) internal virtual override {\\n        // Restrictions ignored for minting and burning\\n        // If transferRestrictor is not set, no restrictions are applied\\n\\n        // @audit\\n        // why don't you not apply mint and burn in blacklist?\\n        if (from == address(0) || to == address(0) || address(transferRestrictor) == address(0)) {\\n            return;\\n        }\\n\\n        // Check transfer restrictions\\n        transferRestrictor.requireNotRestricted(from, to);\\n    }\\n```\\n\\nthis is calling\\n```\\nfunction requireNotRestricted(address from, address to) external view virtual {\\n // Check if either account is restricted\\n if (blacklist[from] || blacklist[to]) {\\n  revert AccountRestricted();\\n }\\n // Otherwise, do nothing\\n}\\n```\\n\\nbut as we can see, when the dShare token is burned or minted, the blacklist does not apply to address(to)\\nthis allows the blacklisted receiver to bypass the blacklist restriction and still send and receive dShares and cash out their dShares\\nbecause the minting dShares is not blacklisted\\na blacklisted user create a buy order with payment token and set the order receiver to a non-blacklisted address\\nthen later when the buy order is filled, the new dShares is transferred and minted to an not-blacklisted address\\nbecause the burning dShares is not blacklisted\\nbefore the user is blacklisted, a user can frontrun the blacklist transaction to create a sell order and transfer the dShares into the OrderProcessor\\nthen later when the sell order is filled, the dShares in burnt from the SellOrderProcess escrow are burnt and the user can receive the payment token

Impact:
Bypass the blacklist restriction because the blacklist check is not done when minting or burning

Recommended mitigation:
Issue Bypass the blacklist restriction because the blacklist check is not done when minting or burning\\nimplement proper check when burning and minting of the dShares to not let user game the blacklist system, checking if the receiver of the dShares is blacklisted when minting, before filling sell order and burn the dShares, check if the requestor of the sell order is blacklisted\\ndo not let blacklisted address create buy order and sell order
Vulnerability: Cancellation refunds should return tokens to order creator, not recipient
Severity: medium

Description:
When an order is cancelled, the refund is sent to `order.recipient` instead of the order creator because it is the order creator (requestor) pay the payment token for buy order or pay the dShares for sell order\\nAs is the standard in many L1/L2 bridges, cancelled deposits should be returned to the order creator instead of the recipient. In Dinari's current implementation, a refund acts as a transfer with a middle-man.\\nSimply, the `_cancelOrderAccounting()` function returns the refund to the order.recipient:\\n```\\n    function _cancelOrderAccounting(OrderRequest calldata orderRequest, bytes32 orderId, OrderState memory orderState)\\n        internal\\n        virtual\\n        override\\n    {\\n        // rest of code\\n\\n        uint256 refund = orderState.remainingOrder + feeState.remainingPercentageFees;\\n\\n        // rest of code\\n\\n        if (refund + feeState.feesEarned == orderRequest.quantityIn) {\\n            _closeOrder(orderId, orderRequest.paymentToken, 0);\\n            // Refund full payment\\n            refund = orderRequest.quantityIn;\\n        } else {\\n            // Otherwise close order and transfer fees\\n            _closeOrder(orderId, orderRequest.paymentToken, feeState.feesEarned);\\n        }\\n\\n\\n        // Return escrow\\n        IERC20(orderRequest.paymentToken).safeTransfer(orderRequest.recipient, refund);\\n    }\\n```\\n\\nRefunds should be returned to the order creator in cases where the input recipient was an incorrect address or simply the user changed their mind prior to the order being filled.

Impact:
Potential for irreversible loss of funds\\nInability to truly cancel order

Recommended mitigation:
Return the funds to the order creator, not the recipient.
Vulnerability: `reduce_position` doesn't update margin mapping correctly
Severity: high

Description:
`reduce_position` function decrease the margin amount of the position but doesn't add it back to the user's margin mapping, making it impossible to withdraw the margin.\\nAfter selling some position tokens back against debt tokens using `reduce_position` function, `debt_shares` and `margin_amount` are reduced proportionally to keep leverage the same as before:\\nVault.vy#L313-L330\\n```\\ndebt_amount: uint256 = self._debt(_position_uid)\\n    margin_debt_ratio: uint256 = position.margin_amount * PRECISION / debt_amount\\n\\n\\n    amount_out_received: uint256 = self._swap(\\n        position.position_token, position.debt_token, _reduce_by_amount, min_amount_out\\n    )\\n\\n\\n    # reduce margin and debt, keep leverage as before\\n    reduce_margin_by_amount: uint256 = (\\n        amount_out_received * margin_debt_ratio / PRECISION\\n    )\\n    reduce_debt_by_amount: uint256 = amount_out_received - reduce_margin_by_amount\\n\\n\\n    position.margin_amount -= reduce_margin_by_amount\\n\\n\\n    burnt_debt_shares: uint256 = self._repay(position.debt_token, reduce_debt_by_amount)\\n    position.debt_shares -= burnt_debt_shares\\n    position.position_amount -= _reduce_by_amount\\n```\\n\\nHowever, even though some of the margin have been paid back (position.margin_amount has been reduced), `self.margin[position.account][position.debt_token]` mapping hasn't been updated by adding `reduce_margin_by_amount` which would allow the user to withdraw his margin.

Impact:
Users will lose their margin tokens.

Recommended mitigation:
Consider modifying the code like this:\\n```\\n    reduce_debt_by_amount: uint256 = amount_out_received - reduce_margin_by_amount\\n\\n\\n    position.margin_amount -= reduce_margin_by_amount\\n+   self.margin[position.account][position.debt_token] += reduce_margin_by_amount\\n\\n    burnt_debt_shares: uint256 = self._repay(position.debt_token, reduce_debt_by_amount)\\n    position.debt_shares -= burnt_debt_shares\\n    position.position_amount -= _reduce_by_amount\\n```\\n
Vulnerability: Interested calculated is ampliefied by multiple of 1000 in `_debt_interest_since_last_update`
Severity: high

Description:
Interest calculated in the `_debt_interest_since_last_update` function is amplified by multiple of 1000, hence can completely brick the system and debt calculation. Because we divide by PERCENTAGE_BASE instead of PERCENTAGE_BASE_HIGH which has more precision and which is used in utilization calculation.\\nFollowing function calculated the interest accured over a certain interval :\\n```\\ndef _debt_interest_since_last_update(_debt_token: address) -> uint256:\\n\\n    return (\\n\\n        (block.timestamp - self.last_debt_update[_debt_token])* self._current_interest_per_second(_debt_token)\\n        * self.total_debt_amount[_debt_token]\\n        / PERCENTAGE_BASE \\n        / PRECISION\\n    )\\n```\\n\\nBut the results from the above function are amplified by factor of 1000 due to the reason that the intrest per second as per test file is calculated as following:\\n```\\n    # accordingly the current interest per year should be 3% so 3_00_000\\n    # per second that is (300000*10^18)/(365*24*60*60)\\n    expected_interest_per_second = 9512937595129375\\n\\n    assert (\\n        expected_interest_per_second\\n        == vault_configured.internal._current_interest_per_second(usdc.address)\\n    )\\n```\\n\\nSo yearly interest has the precision of 5 as it is calculated using utilization rate and `PERCENTAGE_BASE_HIGH_PRECISION` is used which has precision of 5 .and per second has the precision of 18, so final value has the precision of 23.\\nInterest per second has precision = 23.\\nBut if we look at the code:\\n```\\n        (block.timestamp - self.last_debt_update[_debt_token])* self._current_interest_per_second(_debt_token)\\n        * self.total_debt_amount[_debt_token]\\n        / PERCENTAGE_BASE \\n        / PRECISION\\n```\\n\\nWe divide by PERCENTAGE_BASE that is = 100_00 = precision of => 2 And than by PRECISION = 1e18 => precision of 18. So accumulated precision of 20, where as we should have divided by value precises to 23 to match the nominator.\\nWhere is we should have divided by PERCENTAGE_BASE_HIGH instead of PERCENTAGE_BASE\\nHence the results are amplified by enormous multiple of thousand.

Impact:
Interest are too much amplified, that impacts the total debt calculation and brick whole leverage, liquidation and share mechanism.\\nNote: Dev confirmed that the values being used in the tests are the values that will be used in production.

Recommended mitigation:
Use PERCENTAGE_BASE_HIGH in division instead of PERCENTAGE_BASE.\\nEscalate for 10 USDC\\nThis should be high as described impact in the given submission and the duplicate too.\\nA magnitude of 1000 times of interest can be deemed as "material loss of funds".\\n141345\\nEscalate for 10 USDC The wrong calculation of interest rates will cause a direct loss of funds to users. This should definitely be high severity.\\nSame as above\\nhrishibhat\\nResult: High Has duplicates Considering this a valid high\\nsherlock-admin2\\nEscalations have been resolved successfully!\\nEscalation status:\\nNabeel-javaid: accepted\\ntwicek: accepted
Vulnerability: Failed withdrawals from VUSD#processWithdrawals will be lost forever
Severity: high

Description:
When withdrawals fail inside VUSD#processWithdrawals they are permanently passed over and cannot be retried. The result is that any failed withdrawal will be lost forever.\\nVUSD.sol#L75-L81\\n```\\n        (bool success, bytes memory data) = withdrawal.usr.call{value: withdrawal.amount}("");\\n        if (success) {\\n            reserve -= withdrawal.amount;\\n        } else {\\n            emit WithdrawalFailed(withdrawal.usr, withdrawal.amount, data);\\n        }\\n        i += 1;\\n```\\n\\nIf the call to withdrawal.usr fails the contract will simply emit an event and continue on with its cycle. Since there is no way to retry withdrawals, these funds will be permanently lost.

Impact:
Withdrawals that fail will be permanently locked

Recommended mitigation:
Cache failed withdrawals and allow them to be retried or simply send VUSD to the user if it fails.