msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Pending allowances can be exploited
Severity: high

Description:
Pending allowances can be exploited in multiple places in the codebase.\\n`TOFT::marketRemoveCollateralReceiver` has the following flow:\\nIt calls `removeCollateral` ona a market with the following parameters: `from = msg_user`, `to = msg_.removeParams.magnetar`.\\nInside the `SGLCollateral::removeCollateral` `_allowedBorrow` is called and check if the `from = msg_user` address has given enough `allowanceBorrow` to the `msg.sender` which in this case is the TOFT contract.\\nSo for a user to use this flow in needs to call:\\n```\\nfunction approveBorrow(address spender, uint256 amount) external returns (bool) {\\n        _approveBorrow(msg.sender, spender, amount);\\n        return true;\\n    }\\n```\\n\\nAnd give the needed allowance to the TOFT contract.\\nThis results in collateral being removed and transferred into the Magnetar contract with `yieldBox.transfer(address(this), to, collateralId, share);`.\\nThe Magnetar gets the collateral, and it can withdraw it to any address specified in the `msg_.withdrawParams`.\\nThis is problematic as the `TOFT::marketRemoveCollateralReceiver` doesn't check the `msg.sender`. In practice this means if Alice has called `approveBorrow` and gives the needed allowance with the intention of using the `marketRemoveCollateralReceiver` flow, Bob can use the `marketRemoveCollateralReceiver` flow and withdraw all the collateral from Alice to his address.\\nSo, any pending allowances from any user can immediately be exploited to steal the collateral.\\nOther occurrences\\nThere are a few other occurrences of this problematic pattern in the codebase.\\n`TOFT::marketBorrowReceiver` expects the user to give an approval to the Magnetar contract. The approval is expected inside the `_extractTokens` function where `pearlmit.transferFromERC20(_from, address(this), address(_token), _amount);` is called. Again, the `msg.sender` is not checked inside the `marketBorrowReceiver` function, so this flow can be abused by another user to borrow and withdraw the borrowed amount to his address.\\n`TOFT::mintLendXChainSGLXChainLockAndParticipateReceiver` also allows to borrow inside the BigBang market and withdraw the borrowed amount to an arbitrary address.\\n`TOF::exerciseOptionsReceiver` has the `_internalTransferWithAllowance` function that simply allows to transfer TOFT tokens from any `_options.from` address that has given an allowance to `srcChainSender`, by anyone that calls this function. It allows to forcefully call the `exerciseOptionsReceiver` on behalf of any other user.\\n`USDO::depositLendAndSendForLockingReceiver` also expects the user to give an allowance to the Magnetar contract, i.e. `MagnetarAssetXChainModule::depositYBLendSGLLockXchainTOLP` calls the `_extractTokens`.

Impact:
The impact of this vulnerability is that any pending allowances from any user can immediately be exploited to steal the collateral/borrowed amount.

Recommended mitigation:
There are multiple instances of issues with dangling allowances in the protocol. Review all the allowance flows and make sure it can't be exploited.
Vulnerability: Incorrect `tapOft` Amounts Will Be Sent to Desired Chains on Certain Conditions
Severity: medium

Description:
TOFTOptionsReceiverModule::exerciseOptionsReceiver module, is responsible for facilitating users' token exercises between `mTOFT` and `tapOFT` tokens across different chains. In a `msg-type` where the user wishes to receive the `tapOFT` tokens on a different chain, the module attempts to ensure the amount sent to the user on the desired chain, aligns with the received tap amount in the current chain. However, a flaw exists where the computed amount to send is not updated in the send parameters, resulting in incorrect token transfer.\\nTOFTOptionsReceiverModule::exerciseOptionsReceiver module is a module that enables users to exercise their `mTOFT` tokens for a given amount of `tapOFT` option tokens.\\nWhen the user wishes to withdraw these `tapOft` tokens on a different chain, the withdrawOnOtherChain param will be set to true. For this composed call type, the contract attempts to ensure the amount to send to the user on the other chain isn't more than the received `tap amount`, by doing this:\\n```\\n           uint256 amountToSend = _send.amountLD > _options.tapAmount ? _options.tapAmount : _send.amountLD;\\n                if (_send.minAmountLD > amountToSend) {\\n                    _send.minAmountLD = amountToSend;\\n                }\\n```\\n\\nThe issue here is that, the computed amount to send, is never updated in the `lsSendParams.sendParam`, the current code still goes on to send the packet to the destination chain with the default input amount:\\n```\\n            if (msg_.withdrawOnOtherChain) {\\n                /// @dev determine the right amount to send back to source\\n                uint256 amountToSend = _send.amountLD > _options.tapAmount ? _options.tapAmount : _send.amountLD;\\n                if (_send.minAmountLD > amountToSend) {\\n                    _send.minAmountLD = amountToSend;\\n                }\\n\\n\\n                // Sends to source and preserve source `msg.sender` (`from` in this case).\\n                _sendPacket(msg_.lzSendParams, msg_.composeMsg, _options.from);\\n\\n\\n                // Refund extra amounts\\n                if (_options.tapAmount - amountToSend > 0) {\\n                    IERC20(tapOft).safeTransfer(_options.from, _options.tapAmount - amountToSend);\\n                }\\n```\\n\\nTo Illustrate:\\nassuming send `amountLD` = 100 and the user is to receive a tap amount of = 80 since `amountLD` is greater than tap amount, the amount to send should be 80, i.e. `msg_.lzSendParams.sendParam.amountLD` = 80 The current code goes on to send the default 100 to the user, when the user is only entitled to 80

Impact:
The user will always receive an incorrect amount of `tapOFT` in the desired chain whenever `amountLD` is greater than `tapAmount`

Recommended mitigation:
update the lz send param `amountLD` to the new computed `amountToSend` before sending the packet\\nI.e :\\n```\\nmsg_.lzSendParams.sendParam.amountLD = amountToSend;\\n```\\n\\nNote that the issue should also be fixed in Tapioca-Bar as well
Vulnerability: The repaying action in `BBLeverage.sellCollateral` function pulls YieldBox shares of asset from wrong address
Severity: medium

Description:
The `sellCollateral` function is used to sell a user's collateral to obtain YieldBox shares of the asset and repay the user's loan. However, in the BBLeverage contract, it calls `_repay` with the `from` parameter set to the user, even though the asset shares have already been collected by this contract beforehand.\\nIn `BBLeverage.sellCollateral`, the `from` variable (user) is used as the repayer address.\\n```\\nif (memoryData.shareOwed <= memoryData.shareOut) {\\n    _repay(from, from, memoryData.partOwed);\\n} else {\\n    //repay as much as we can\\n    uint256 partOut = totalBorrow.toBase(amountOut, false);\\n    _repay(from, from, partOut);\\n}\\n```\\n\\nTherefore, asset shares of user will be pulled in `BBLendingCommon._repay` function.\\n```\\nfunction _repay(address from, address to, uint256 part) internal returns (uint256 amount) {\\n    // rest of code\\n    // @dev amount includes the opening & accrued fees\\n    yieldBox.withdraw(assetId, from, address(this), amount, 0);\\n    // rest of code\\n```\\n\\nThis is incorrect behavior since the necessary asset shares were already collected by the contract in the `BBLeverage.sellCollateral` function. The repayer address should be `address(this)` for `_repay`.

Impact:
Mistakenly pulling user funds while the received asset shares remain stuck in the contract will result in losses for users who have sufficient allowance and balance when using the `BBLeverage.sellCollateral` functionality.

Recommended mitigation:
Should fix as following:\\n```\\nif (memoryData.shareOwed <= memoryData.shareOut) {\\n    _repay(address(this), from, memoryData.partOwed);\\n} else {\\n    //repay as much as we can\\n    uint256 partOut = totalBorrow.toBase(amountOut, false);\\n    _repay(address(this), from, partOut);\\n}\\n```\\n
Vulnerability: `leverageAmount` is incorrect in `SGLLeverage.sellCollateral` function due to calculation based on the new states of YieldBox after withdrawal
Severity: medium

Description:
See vulnerability detail\\n`SGLLeverage.sellCollateral` function attempts to remove the user's collateral in shares of YieldBox, then withdraws those collateral shares to collect collateral tokens. Subsequently, the received collateral tokens can be used to swap for asset tokens.\\nHowever, the `leverageAmount` variable in this function does not represent the actual withdrawn tokens from the provided shares because it is calculated after the withdrawal.\\n```\\nyieldBox.withdraw(collateralId, address(this), address(leverageExecutor), 0, calldata_.share);\\nuint256 leverageAmount = yieldBox.toAmount(collateralId, calldata_.share, false);\\n\\namountOut = leverageExecutor.getAsset(\\n    assetId, address(collateral), address(asset), leverageAmount, calldata_.from, calldata_.data\\n);\\n```\\n\\n`yieldBox.toAmount` after withdrawal may return different from the actual withdrawn token amount, because the states of YieldBox has changed. Because the token amount is calculated with rounding down in YieldBox, `leverageAmount` will be higher than the actual withdrawn amount.\\nFor example, before the withdrawal, YieldBox had 100 total shares and 109 total tokens. Now this function attempt to withdraw 10 shares (calldata_.share = 10) -> the actual withdrawn amount = 10 * 109 / 100 = 10 tokens After that, leverageAmount will be calculated based on the new yieldBox's total shares and total tokens -> leverageAmount = 10 * (109 - 10) / (100 - 10) = 11 tokens\\nThe same vulnerability exists in `BBLeverage.sellCollateral` function.

Impact:
Because `leverageAmount` can be higher than the actual withdrawn collateral tokens, `leverageExecutor.getAsset()` will revert due to not having enough tokens in the contract to pull. This results in a DOS of `sellCollateral`, break this functionality.

Recommended mitigation:
`leverageAmount` should be obtained from the return value of YieldBox.withdraw:\\n```\\n(uint256 leverageAmount, ) = yieldBox.withdraw(collateralId, address(this), address(leverageExecutor), 0, calldata_.share);\\n```\\n
Vulnerability: Multiple contracts cannot be paused
Severity: medium

Description:
For safety, tapioca has added `whenNotPaused` restrictions to multiple contracts But there is no method provided to modify the `_paused` state If a security event occurs, it cannot be paused at all\\nTake `mTOFT.sol` as an example, multiple methods are `whenNotPaused`\\n```\\n    function executeModule(ITOFT.Module _module, bytes memory _data, bool _forwardRevert)\\n        external\\n        payable\\n      whenNotPaused\\n        returns (bytes memory returnData)\\n    {\\n// rest of code\\n    function sendPacket(LZSendParam calldata _lzSendParam, bytes calldata _composeMsg)\\n        public\\n        payable\\n      whenNotPaused\\n        returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt)\\n    {\\n```\\n\\nBut the contract does not provide a `public` method to modify `_paused` Note: `Pausable.sol` does not have a `public` method to modify `_paused`\\nIn reality, there have been multiple reports of security incidents where the protocol side wants to pause to prevent losses, but cannot pause, strongly recommend adding\\nNote: The following contracts cannot be paused\\nmTOFT\\nTOFT\\nUsdo\\nAssetToSGLPLeverageExecutor

Impact:
Due to the inability to modify `_paused`, it poses a security risk

Recommended mitigation:
```\\n// Add the line below\\n    function pause() external onlyOwner{\\n// Add the line below\\n        _pause();\\n// Add the line below\\n    }\\n\\n// Add the line below\\n    function unpause() external onlyOwner{\\n// Add the line below\\n        _unpause();\\n// Add the line below\\n    }\\n```\\n
Vulnerability: StargateRouter cannot send payloads and rebalancing of ERC20s is broken
Severity: medium

Description:
The `Balancer.sol` contract can't perform the rebalancing of ERC20s across chains as the Stargate router is not able to send any payload and will immediately revert the transaction if a payload is included. In this instance payload is hardcoded to `"0x"`.\\n`Balancer.sol` contract has a `rebalance` function that is supposed to perform a rebalancing of `mTOFTs` across chains. In case the token being transferred through Stargate is an ERC20 it is using the Stargate router to initiate the transfer. The issue however is that the stargate router is not able to send any payload and will immediately revert the transaction if a payload is included.\\nIf we take a look at the code, there is a payload equal to "0x" being sent with the transaction:\\n```\\n## Balancer.sol\\n\\n    router.swap{value: msg.value}(\\n        _dstChainId,\\n        _srcPoolId,\\n        _dstPoolId,\\n        payable(this),\\n        _amount,\\n        _computeMinAmount(_amount, _slippage),\\n        IStargateRouterBase.lzTxObj({dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x0"}),\\n        _dst,\\n    "0x" => this is the payload that is being sent with the transaction\\n    );\\n```\\n\\nAs a proof of concept we can try to send a payload through the stargate router on a forked network and see that the transaction will revert. p.s. make sure to run on it on a forked network on Ethereum mainnet.\\n```\\nfunction testStargateRouterReverting() public {\\n    vm.createSelectFork(vm.envString("MAINNET_RPC_URL"));\\n    \\n    address stargateRouter = 0x8731d54E9D02c286767d56ac03e8037C07e01e98;\\n    address DAIWhale = 0x7A8EDc710dDEAdDDB0B539DE83F3a306A621E823;\\n    address DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;\\n    IStargateRouter.lzTxObj memory lzTxParams = IStargateRouter.lzTxObj(0, 0, "0x00");\\n\\n    vm.startPrank(DAIWhale);\\n    vm.deal(DAIWhale, 5 ether);\\n    IERC20(DAI).approve(stargateRouter, 1e18);\\n    IStargateRouter(stargateRouter).swap{value: 1 ether}(\\n        111, 3, 3, payable(address(this)), 1e18, 1, lzTxParams, abi.encode(address(this)), "0x"\\n    );\\n}\\n```\\n\\nIt fails with the following error:\\nBoth `StargateRouter` and StargateComposer have the `swap` interface, but the intention was to use the `StargateRouter` which can be observed by the `retryRevert` function in the `Balancer.sol` contract.\\n```\\n## Balancer.sol\\n\\nfunction retryRevert(uint16 _srcChainId, bytes calldata _srcAddress, uint256 _nonce) external payable onlyOwner {\\n    router.retryRevert{value: msg.value}(_srcChainId, _srcAddress, _nonce);\\n}\\n```\\n\\nAs this makes the rebalancing of `mTOFTs` broken, I'm marking this as a high-severity issue.

Impact:
Rebalancing of `mTOFTs` across chains is broken and as it is one of the main functionalities of the protocol, this is a high-severity issue.

Recommended mitigation:
Use the `StargateComposer` instead of the `StargateRouter` if sending payloads.
Vulnerability: `getCollateral` and `getAsset` functions of the AssetTotsDaiLeverageExecutor contract decode data incorrectly
Severity: medium

Description:
See vulnerability detail\\nIn AssetTotsDaiLeverageExecutor contract, `getCollateral` function decodes the data before passing it to `_swapAndTransferToSender` function.\\n```\\nSLeverageSwapData memory swapData = abi.decode(data, (SLeverageSwapData));\\nuint256 daiAmount =\\n    _swapAndTransferToSender(false, assetAddress, daiAddress, assetAmountIn, swapData.swapperData);\\n```\\n\\nHowever, `_swapAndTransferToSender` will decode this data again to obtain the swapperData:\\n```\\nfunction _swapAndTransferToSender(\\n    bool sendBack,\\n    address tokenIn,\\n    address tokenOut,\\n    uint256 amountIn,\\n    bytes memory data\\n) internal returns (uint256 amountOut) {\\n    SLeverageSwapData memory swapData = abi.decode(data, (SLeverageSwapData));\\n    // rest of code\\n```\\n\\nThe redundant decoding will cause the data to not align as expected, which is different from `SimpleLeverageExecutor.getCollateral()` function (code snippet)

Impact:
`getCollateral` and `getAsset` of AssetTotsDaiLeverageExecutor will not work as intended due to incorrectly decoding data.

Recommended mitigation:
The AssetTotsDaiLeverageExecutor contract should pass data directly to `_swapAndTransferToSender`, similar to the SimpleLeverageExecutor contract
Vulnerability: Blacklisted accounts can still transact.
Severity: medium

Description:
Accounts that have been blacklisted by the `BLACKLISTER_ROLE` continue to transact normally.\\nCurrently, the only real effect of blacklisting an account is the seizure of `Stablecoin` funds:\\n```\\n/**\\n * @notice Overrides Blacklist function to transfer balance of a blacklisted user to the caller.\\n * @dev This function is called internally when an account is blacklisted.\\n * @param user The blacklisted user whose balance will be transferred.\\n */\\nfunction _onceBlacklisted(address user) internal override {\\n  _transfer(user, _msgSender(), balanceOf(user));\\n}\\n```\\n\\nHowever, following a call to `addBlackList(address)`, the blacklisted account may continue to transact using `Stablecoin`.\\nCombined with previous audit reports, which attest to the blacklist function's susceptibility to frontrunning, the current implementation of the blacklist operation can effectively be considered a no-op.

Impact:
Medium, as this the failure of a manually administered security feature.

Recommended mitigation:
ERC20s that enforce blacklists normally prevent a sanctioned address from being able to transact:\\n📄 Stablecoin.sol\\n```\\n// Add the line below\\n error Blacklisted(address account);\\n\\n// Add the line below\\nfunction _update(address from, address to, uint256 value) internal virtual override {\\n// Add the line below\\n\\n// Add the line below\\n  if (blacklisted(from)) revert Blacklisted(from); \\n// Add the line below\\n  if (blacklisted(to)) revert Blacklisted(to);\\n// Add the line below\\n\\n// Add the line below\\n  super._update(from, to, value);\\n// Add the line below\\n}\\n```\\n
Vulnerability: `requestWithdrawal` doesn't estimate accurately the available shares for withdrawals
Severity: medium

Description:
The `requestWithdrawal` function inaccurately estimates the available shares for withdrawals by including funds stored in the deposit pool into the already deposited EigenLayer shares. This can potentially lead to blocking withdrawals or users receiving less funds for their shares.\\nFor a user to withdraw funds from the protocol, they must first request a withdrawal using the `requestWithdrawal` function, which queues the withdrawal in the current epoch by calling `withdrawalQueue().queueWithdrawal`.\\nTo evaluate the available shares for withdrawal, the function converts the protocol asset balance into shares:\\n```\\nuint256 availableShares = assetRegistry().convertToSharesFromAsset(asset, assetRegistry().getTotalBalanceForAsset(asset));\\n```\\n\\nThe issue arises from the `getTotalBalanceForAsset` function, which returns the sum of the protocol asset funds held, including assets already deposited into EigenLayer and assets still in the deposit pool:\\n```\\nfunction getTotalBalanceForAsset(\\n    address asset\\n) public view returns (uint256) {\\n    if (!isSupportedAsset(asset)) revert ASSET_NOT_SUPPORTED(asset);\\n\\n    address depositPool_ = address(depositPool());\\n    if (asset == ETH_ADDRESS) {\\n        return depositPool_.balance + getETHBalanceInEigenLayer();\\n    }\\n\\n    uint256 sharesHeld = getAssetSharesHeld(asset);\\n    uint256 tokensInRio = IERC20(asset).balanceOf(depositPool_);\\n    uint256 tokensInEigenLayer = convertFromSharesToAsset(\\n        getAssetStrategy(asset),\\n        sharesHeld\\n    );\\n\\n    return tokensInRio + tokensInEigenLayer;\\n}\\n```\\n\\nThis causes the calculated `availableShares` to differ from the actual shares held by the protocol because the assets still in the deposit pool shouldn't be converted to shares with the current share price (shares/asset) as they were not deposited into EigenLayer yet.\\nDepending on the current shares price, the function might over or under-estimate the available shares in the protocol. This can potentially result in allowing more queued withdrawals than the available shares in the protocol, leading to blocking withdrawals later on or users receiving less funds for their shares.

Impact:
The `requestWithdrawal` function inaccurately estimates the available shares for withdrawals, potentially resulting in blocking withdrawals or users receiving less funds for their shares.

Recommended mitigation:
There is no straightforward way to handle this issue as the asset held by the deposit pool can't be converted into shares while they were not deposited into EigenLayer. The code should be reviewed to address this issue.
Vulnerability: Slashing penalty is unfairly paid by a subset of users if a deficit is accumulated.
Severity: medium

Description:
If a deficit is accumulated in the EigenPodManager due to slashing when ETH is being withdrawn the slashing payment will be taken from the first cohort to complete a withdrawal.\\nA deficit can happen in `podOwnerShares[podOwner]` in the EigenPodManager in the EigenLayer protocol. This can happen if validators are slashed when ETH is queued for withdrawal.\\nThe issue is that this deficit will be paid for by the next cohort to complete a withdrawal by calling `settleEpochFromEigenLayer()`.\\nIn the following code we can see how `epochWithdrawals.assetsReceived` is calculated based on the amount received from the `delegationManager.completeQueuedWithdrawal` call\\n```\\n        uint256 balanceBefore = asset.getSelfBalance();\\n\\n        address[] memory assets = asset.toArray();\\n        bytes32[] memory roots = new bytes32[](queuedWithdrawalCount);\\n\\n        IDelegationManager.Withdrawal memory queuedWithdrawal;\\n        for (uint256 i; i < queuedWithdrawalCount; ++i) {\\n            queuedWithdrawal = queuedWithdrawals[i];\\n\\n            roots[i] = _computeWithdrawalRoot(queuedWithdrawal);\\n            delegationManager.completeQueuedWithdrawal(queuedWithdrawal, assets, middlewareTimesIndexes[i], true);\\n\\n            // Decrease the amount of ETH queued for withdrawal. We do not need to validate the staker as\\n            // the aggregate root will be validated below.\\n            if (asset == ETH_ADDRESS) {\\n                IRioLRTOperatorDelegator(queuedWithdrawal.staker).decreaseETHQueuedForUserSettlement(\\n                    queuedWithdrawal.shares[0]\\n                );\\n            }\\n        }\\n        if (epochWithdrawals.aggregateRoot != keccak256(abi.encode(roots))) {\\n            revert INVALID_AGGREGATE_WITHDRAWAL_ROOT();\\n        }\\n        epochWithdrawals.shareValueOfAssetsReceived = SafeCast.toUint120(epochWithdrawals.sharesOwed);\\n\\n        uint256 assetsReceived = asset.getSelfBalance() - balanceBefore;\\n        epochWithdrawals.assetsReceived += SafeCast.toUint120(assetsReceived);\\n```\\n\\nthe amount received could be 0 if the deficit is larger than the amount queued for this cohort. See following code in `withdrawSharesAsTokens()` EigenPodManager\\n```\\n            } else {\\n                podOwnerShares[podOwner] += int256(shares);\\n                emit PodSharesUpdated(podOwner, int256(shares));\\n                return;\\n            }\\n```\\n\\nThese users will pay for all slashing penalties instead of it being spread out among all LRT holders.

Impact:
If a deficit is accumulated the first cohort to settle will pay for the entire amount. If they can not cover it fully, they will receive 0 and the following cohort will pay for the rest.

Recommended mitigation:
A potential solution to deal with this is to check if a deficit exists in `settleEpochFromEigenLayer()`. If it exists functionality has to be added that spreads the cost of the penalty fairly among all LRT holders.