msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: `withdraw` function does not comply with ERC5095
Severity: medium

Description:
The `withdraw` function of Tranche/PT does not comply with ERC5095 as it does not return the exact amount of underlying assets requested by the users.\\nQ: Is the code/contract expected to comply with any EIPs? Are there specific assumptions around adhering to those EIPs that Watsons should be aware of? EIP20 and IERC5095\\nFollowing is the specification of the `withdraw` function of ERC5095. It stated that the user must receive exactly `underlyingAmount` of underlying tokens.\\nwithdraw Burns principalAmount from holder and sends exactly underlyingAmount of underlying tokens to receiver.\\nHowever, the `withdraw` function does not comply with this requirement.\\nOn a high-level, the reason is that Line 337 will compute the number of shares that need to be redeemed to receive `underlyingAmount` number of underlying tokens from the adaptor. The main problem here is that the division done here is rounded down. Thus, the `sharesRedeem` will be lower than expected. Consequently, when `sharesRedeem` number of shares are redeemed at Line 346 below, the users will not receive an exact number of `underlyingAmount` of underlying tokens.\\n```\\nFile: Tranche.sol\\n    function withdraw(\\n        uint256 underlyingAmount,\\n        address to,\\n        address from\\n    ) external override nonReentrant expired returns (uint256) {\\n        GlobalScales memory _gscales = gscales;\\n        uint256 cscale = _updateGlobalScalesCache(_gscales);\\n\\n        // Compute the shares to be redeemed\\n        uint256 sharesRedeem = underlyingAmount.divWadDown(cscale);\\n        uint256 principalAmount = _computePrincipalTokenRedeemed(_gscales, sharesRedeem);\\n\\n        // Update the global scales\\n        gscales = _gscales;\\n        // Burn PT tokens from `from`\\n        _burnFrom(from, principalAmount);\\n        // Withdraw underlying tokens from the adapter and transfer them to `to`\\n        _target.safeTransfer(address(adapter), sharesRedeem);\\n        (uint256 underlyingWithdrawn, ) = adapter.prefundedRedeem(to);\\n```\\n

Impact:
The tranche/PT does not align with the ERC5095 specification.

Recommended mitigation:
Update the `withdraw` function to send exactly `underlyingAmount` number of underlying tokens to the caller so that the Tranche will be aligned with the ERC5095 specification.
Vulnerability: Permissioned rebalancing functions leading to loss of assets
Severity: medium

Description:
Permissioned rebalancing functions that could only be accessed by admin could lead to a loss of assets.\\nPer the contest's README page, it stated that the admin/owner is "RESTRICTED". Thus, any finding showing that the owner/admin can steal a user's funds, cause loss of funds or harm to the users, or cause the user's fund to be struck is valid in this audit contest.\\nQ: Is the admin/owner of the protocol/contracts TRUSTED or RESTRICTED?\\nRESTRICTED\\nThe following describes a way where the admin can block users from withdrawing their assets from the protocol\\nThe admin calls the `setRebalancer` function to set the rebalance to a wallet address owned by them.\\n```\\nFile: BaseLSTAdapter.sol\\n    function setRebalancer(address _rebalancer) external onlyOwner {\\n        rebalancer = _rebalancer;\\n    }\\n```\\n\\nThe admin calls the `setTargetBufferPercentage` the set the `targetBufferPercentage` to the smallest possible value of 1%. This will cause only 1% of the total ETH deposited by all the users to reside on the adaptor contract. This will cause the ETH buffer to deplete quickly and cause all the redemption and withdrawal to revert.\\n```\\nFile: BaseLSTAdapter.sol\\n    function setTargetBufferPercentage(uint256 _targetBufferPercentage) external onlyRebalancer {\\n        if (_targetBufferPercentage < MIN_BUFFER_PERCENTAGE || _targetBufferPercentage > BUFFER_PERCENTAGE_PRECISION) {\\n            revert InvalidBufferPercentage();\\n        }\\n        targetBufferPercentage = _targetBufferPercentage;\\n    }\\n```\\n\\nThe owner calls the `setRebalancer` function again and sets the rebalancer address to `address(0)`. As such, no one has the ability to call functions that are only accessible by rebalancer. The `requestWithdrawal` and `requestWithdrawalAll` functions are only accessible by rebalancer. Thus, no one can call these two functions to replenish the ETH buffer in the adaptor contract.\\nWhen this state is reached, users can no longer withdraw their assets from the protocol, and their assets are stuck in the contract. This effectively causes them to lose their assets.

Impact:
Loss of assets for the victim.

Recommended mitigation:
To prevent the above scenario, the minimum `targetBufferPercentage` should be set to a higher percentage such as 5 or 10%, and the `requestWithdrawal` function should be made permissionless, so that even if the rebalancer does not do its job, anyone else can still initiate the rebalancing process to replenish the adaptor's ETH buffer for user's withdrawal.
Vulnerability: Napier pool owner can unfairly increase protocol fees on swaps to earn more revenue
Severity: medium

Description:
Currently there is no limit to how often a `poolOwner` can update fees which can be abused to earn more fees by charging users higher swap fees than they expect.\\nThe `NapierPool::setFeeParameter` function allows the `poolOwner` to set the `protocolFeePercent` at any point to a maximum value of 100%. The `poolOwner` is a trusted party but should not be able to abuse protocol settings to earn more revenue. There are no limits to how often this can be updated.\\n```\\n function test_protocol_owner_frontRuns_swaps_with_higher_fees() public whenMaturityNotPassed {\\n        // pre-condition\\n        vm.warp(maturity - 30 days);\\n        deal(address(pts[0]), alice, type(uint96).max, false); // ensure alice has enough pt\\n        uint256 preBaseLptSupply = tricrypto.totalSupply();\\n        uint256 ptInDesired = 100 * ONE_UNDERLYING;\\n        uint256 expectedBaseLptIssued = tricrypto.calc_token_amount([ptInDesired, 0, 0], true);\\n\\n        // Pool owner sees swap about to occur and front runs updating fees to max value\\n        vm.startPrank(owner);\\n        pool.setFeeParameter("protocolFeePercent", 100);\\n        vm.stopPrank();\\n\\n        // execute\\n        vm.prank(alice);\\n        uint256 underlyingOut = pool.swapPtForUnderlying(\\n            0, ptInDesired, recipient, abi.encode(CallbackInputType.SwapPtForUnderlying, SwapInput(underlying, pts[0]))\\n        );\\n        // sanity check\\n        uint256 protocolFee = SwapEventsLib.getProtocolFeeFromLastSwapEvent(pool);\\n        assertGt(protocolFee, 0, "fee should be charged");\\n    }\\n```\\n

Impact:
A malicious `poolOwner` could change the protocol swap fees unfairly for users by front-running swaps and increasing fees to higher values on unsuspecting users. An example scenario is:\\nThe `poolOwner` sets swap fees to 1% to attract users\\nThe `poolOwner` front runs all swaps and changes the swap fees to the maximum value of 100%\\nAfter the swap the `poolOwner` resets `protocolFeePercent` to a low value to attract more users

Recommended mitigation:
Introduce a delay in fee updates to ensure users receive the fees they expect.
Vulnerability: Benign esfrxETH holders incur more loss than expected
Severity: medium

Description:
Malicious esfrxETH holders can avoid "pro-rated" loss and have the remaining esfrxETH holders incur all the loss due to the fee charged by FRAX during unstaking. As a result, the rest of the esfrxETH holders incur more losses than expected compared to if malicious esfrxETH holders had not used this trick in the first place.\\n```\\nFile: SFrxETHAdapter.sol\\n/// @title SFrxETHAdapter - esfrxETH\\n/// @dev Important security note:\\n/// 1. The vault share price (esfrxETH / WETH) increases as sfrxETH accrues staking rewards.\\n/// However, the share price decreases when frxETH (sfrxETH) is withdrawn.\\n/// Withdrawals are processed by the FraxEther redemption queue contract.\\n/// Frax takes a fee at the time of withdrawal requests, which temporarily reduces the share price.\\n/// This loss is pro-rated among all esfrxETH holders.\\n/// As a mitigation measure, we allow only authorized rebalancers to request withdrawals.\\n///\\n/// 2. This contract doesn't independently keep track of the sfrxETH balance, so it is possible\\n/// for an attacker to directly transfer sfrxETH to this contract, increase the share price.\\ncontract SFrxETHAdapter is BaseLSTAdapter, IERC721Receiver {\\n```\\n\\nIn the SFrxETHAdapter's comments above, it is stated that the share price will decrease due to the fee taken by FRAX during the withdrawal request. This loss is supposed to be 'pro-rated' among all esfrxETH holders. However, this report reveals that malicious esfrxETH holders can circumvent this 'pro-rated' loss, leaving the remaining esfrxETH holders to bear the entire loss. Furthermore, the report demonstrates that the current mitigation measure, which allows only authorized rebalancers to request withdrawals, is insufficient to prevent this exploitation.\\nWhenever a rebalancers submit a withdrawal request to withdraw staked ETH from FRAX, it will first reside in the mempool of the blockchain and anyone can see it. Malicious esfrxETH holders can front-run it to withdraw their shares from the adaptor.\\nWhen the withdrawal request TX is executed, the remaining esfrxETH holders in the adaptor will incur the fee. Once executed, the malicious esfrxETH deposits back to the adaptors.\\nNote that no fee is charged to the users for any deposit or withdrawal operation. Thus, as long as the gain from this action is more than the gas cost, it makes sense for the esfrxETH holders to do so.

Impact:
The rest of the esfrxETH holders incur more losses than expected compared to if malicious esfrxETH holders had not used this trick in the first place.

Recommended mitigation:
The best way to discourage users from withdrawing their assets and depositing them back to take advantage of a particular event is to impose a fee upon depositing and withdrawing.
Vulnerability: Lack of slippage control for `issue` function
Severity: medium

Description:
The lack of slippage control for `issue` function can lead to a loss of assets for the affected users.\\nDuring the issuance, the user will deposit underlying assets (e.g., ETH) to the Tranche contract, and the Tranche contract will forward them to the Adaptor contract for depositing at Line 208 below. The number of shares minted is depending on the current scale of the adaptor. The current scale of the adaptor can increase or decrease at any time, depending on the current on-chain condition when the transaction is executed. For instance, the LIDO's daily oracle/rebase update will increase the stETH balance, which will, in turn, increase the adaptor's scale. On the other hand, if there is a mass validator slashing event, the ETH claimed from the withdrawal queue will be less than expected, leading to a decrease in the adaptor's scale. Thus, one cannot ensure the result from the off-chain simulation will be the same as the on-chain execution.\\nHaving said that, the number of shared minted will vary (larger or smaller than expected) if there is a change in the current scale. Assuming that Alice determined off-chain that depositing 100 ETH would issue $x$ amount of PT/YT. When she executes the TX, the scale increases, leading to the amount of PT/YT issued being less than $x$. The slippage is more than what she can accept.\\nIn summary, the `issue` function lacks the slippage control that allows the users to revert if the amount of PT/YT they received is less than the amount they expected.\\n```\\nFile: Tranche.sol\\n    function issue(\\n        address to,\\n        uint256 underlyingAmount\\n    ) external nonReentrant whenNotPaused notExpired returns (uint256 issued) {\\n..SNIP..\\n        // Transfer underlying from user to adapter and deposit it into adapter to get target token\\n        _underlying.safeTransferFrom(msg.sender, address(adapter), underlyingAmount);\\n        (, uint256 sharesMinted) = adapter.prefundedDeposit();\\n\\n        // Deduct the issuance fee from the amount of target token minted + reinvested yield\\n        // Fee should be rounded up towards the protocol (against the user) so that issued principal is rounded down\\n        // Hackmd: F0\\n        // ptIssued\\n        // = (u/s + y - fee) * S\\n        // = (sharesUsed - fee) * S\\n        // where u = underlyingAmount, s = current scale, y = reinvested yield, S = maxscale\\n        uint256 sharesUsed = sharesMinted + accruedInTarget;\\n        uint256 fee = sharesUsed.mulDivUp(issuanceFeeBps, MAX_BPS);\\n        issued = (sharesUsed - fee).mulWadDown(_maxscale);\\n\\n        // Accumulate issueance fee in units of target token\\n        issuanceFees += fee;\\n        // Mint PT and YT to user\\n        _mint(to, issued);\\n        _yt.mint(to, issued);\\n\\n        emit Issue(msg.sender, to, issued, sharesUsed);\\n    }\\n\\n```\\n

Impact:
Loss of assets for the affected users.

Recommended mitigation:
Implement a slippage control that allows the users to revert if the amount of PT/YT they received is less than the amount they expected.
Vulnerability: Users are unable to collect their yield if tranche is paused
Severity: medium

Description:
Users are unable to collect their yield if Tranche is paused, resulting in a loss of assets for the victims.\\nPer the contest's README page, it stated that the admin/owner is "RESTRICTED". Thus, any finding showing that the owner/admin can steal a user's funds, cause loss of funds or harm to the users, or cause the user's fund to be struck is valid in this audit contest.\\nQ: Is the admin/owner of the protocol/contracts TRUSTED or RESTRICTED?\\nRESTRICTED\\nThe admin of the protocol has the ability to pause the Tranche contract, and no one except for the admin can unpause it. If a malicious admin paused the Tranche contract, the users will not be able to collect their yield earned, leading to a loss of assets for them.\\n```\\nFile: Tranche.sol\\n    /// @notice Pause issue, collect and updateUnclaimedYield\\n    /// @dev only callable by management\\n    function pause() external onlyManagement {\\n        _pause();\\n    }\\n\\n    /// @notice Unpause issue, collect and updateUnclaimedYield\\n    /// @dev only callable by management\\n    function unpause() external onlyManagement {\\n        _unpause();\\n    }\\n```\\n\\nThe following shows that the `collect` function can only be executed when the system is not paused.\\n```\\nFile: Tranche.sol\\n    function collect() public nonReentrant whenNotPaused returns (uint256) {\\n        uint256 _lscale = lscales[msg.sender];\\n        uint256 accruedInTarget = unclaimedYields[msg.sender];\\n```\\n

Impact:
Users are unable to collect their yield if Tranche is paused, resulting in a loss of assets for the victims.

Recommended mitigation:
Consider allowing the users to collect yield even when the system is paused.
Vulnerability: `AccountV1#flashActionByCreditor` can be used to drain assets from account without withdrawing
Severity: high

Description:
`AccountV1#flashActionByCreditor` is designed to allow atomic flash actions moving funds from the `owner` of the account. By making the account own itself, these arbitrary calls can be used to transfer `ERC721` assets directly out of the account. The assets being transferred from the account will still show as deposited on the account allowing it to take out loans from creditors without having any actual assets.\\nThe overview of the exploit are as follows:\\n```\\n1) Deposit ERC721\\n2) Set creditor to malicious designed creditor\\n3) Transfer the account to itself\\n4) flashActionByCreditor to transfer ERC721\\n    4a) account owns itself so _transferFromOwner allows transfers from account\\n    4b) Account is now empty but still thinks is has ERC721\\n5) Use malicious designed liquidator contract to call auctionBoughtIn\\n    and transfer account back to attacker\\n7) Update creditor to legitimate creditor\\n8) Take out loan against nothing\\n9) Profit\\n```\\n\\nThe key to this exploit is that the account is able to be it's own `owner`. Paired with a maliciously designed `creditor` (creditor can be set to anything) `flashActionByCreditor` can be called by the attacker when this is the case.\\nAccountV1.sol#L770-L772\\n```\\nif (transferFromOwnerData.assets.length > 0) {\\n    _transferFromOwner(transferFromOwnerData, actionTarget);\\n}\\n```\\n\\nIn these lines the `ERC721` token is transferred out of the account. The issue is that even though the token is transferred out, the `erc721Stored` array is not updated to reflect this change.\\nAccountV1.sol#L570-L572\\n```\\nfunction auctionBoughtIn(address recipient) external onlyLiquidator nonReentrant {\\n    _transferOwnership(recipient);\\n}\\n```\\n\\nAs seen above `auctionBoughtIn` does not have any requirement besides being called by the `liquidator`. Since the `liquidator` is also malicious. It can then abuse this function to set the `owner` to any address, which allows the attacker to recover ownership of the account. Now the attacker has an account that still considers the `ERC721` token as owned but that token isn't actually present in the account.\\nNow the account creditor can be set to a legitimate pool and a loan taken out against no collateral at all.

Impact:
Account can take out completely uncollateralized loans, causing massive losses to all lending pools.

Recommended mitigation:
The root cause of this issue is that the account can own itself. The fix is simple, make the account unable to own itself by causing transferOwnership to revert if `owner == address(this)`
Vulnerability: `LendingPool#flashAction` is broken when trying to refinance position across `LendingPools` due to improper access control
Severity: medium

Description:
When refinancing an account, `LendingPool#flashAction` is used to facilitate the transfer. However due to access restrictions on `updateActionTimestampByCreditor`, the call made from the new creditor will revert, blocking any account transfers. This completely breaks refinancing across lenders which is a core functionality of the protocol.\\nLendingPool.sol#L564-L579\\n```\\nIAccount(account).updateActionTimestampByCreditor();\\n\\nasset.safeTransfer(actionTarget, amountBorrowed);\\n\\n{\\n    uint256 accountVersion = IAccount(account).flashActionByCreditor(actionTarget, actionData);\\n    if (!isValidVersion[accountVersion]) revert LendingPoolErrors.InvalidVersion();\\n}\\n```\\n\\nWe see above that `account#updateActionTimestampByCreditor` is called before `flashActionByCreditor`.\\nAccountV1.sol#L671\\n```\\nfunction updateActionTimestampByCreditor() external onlyCreditor updateActionTimestamp { }\\n```\\n\\nWhen we look at this function, it can only be called by the current creditor. When refinancing a position, this function is actually called by the pending creditor since the `flashaction` should originate from there. This will cause the call to revert, making it impossible to refinance across `lendingPools`.

Impact:
Refinancing is impossible

Recommended mitigation:
`Account#updateActionTimestampByCreditor()` should be callable by BOTH the current and pending creditor
Vulnerability: Long trader's deposited margin can be wiped out
Severity: high

Description:
Long Trader's deposited margin can be wiped out due to a logic error, leading to a loss of assets.\\n```\\nFile: FlatcoinVault.sol\\n    function settleFundingFees() public returns (int256 _fundingFees) {\\n..SNIP..\\n        // Calculate the funding fees accrued to the longs.\\n        // This will be used to adjust the global margin and collateral amounts.\\n        _fundingFees = PerpMath._accruedFundingTotalByLongs(_globalPositions, unrecordedFunding);\\n\\n        // In the worst case scenario that the last position which remained open is underwater,\\n        // we set the margin deposited total to 0. We don't want to have a negative margin deposited total.\\n        _globalPositions.marginDepositedTotal = (int256(_globalPositions.marginDepositedTotal) > _fundingFees)\\n            ? uint256(int256(_globalPositions.marginDepositedTotal) + _fundingFees)\\n            : 0;\\n\\n        _updateStableCollateralTotal(-_fundingFees);\\n```\\n\\nIssue 1\\nAssume that there are two long positions in the system and the `_globalPositions.marginDepositedTotal` is $X$.\\nAssume that the funding fees accrued to the long positions at Line 228 is $Y$. $Y$ is a positive value indicating the overall gain/profit that the long traders received from the LPs.\\nIn this case, the `_globalPositions.marginDepositedTotal` should be set to $(X + Y)$ after taking into consideration the funding fee gain/profit accrued by the long positions.\\nHowever, in this scenario, $X < Y$. Thus, the condition at Line 232 will be evaluated as `false,` and the `_globalPositions.marginDepositedTotal` will be set to zero. This effectively wipes out all the margin collateral deposited by the long traders in the system, and the deposited margin of the long traders is lost.\\nIssue 2\\nThe second issue with the current implementation is that it does not accurately capture scenarios where the addition of `_globalPositions.marginDepositedTotal` and `_fundingFees` result in a negative number. This is because `_fundingFees` could be a large negative number that, when added to `_globalPositions.marginDepositedTotal`, results in a negative total, but the condition at Line 232 above still evaluates as true, resulting in an underflow revert.

Impact:
Loss of assets for the long traders as mentioned above.

Recommended mitigation:
If the intention is to ensure that `_globalPositions.marginDepositedTotal` will never become negative, consider summing up $(X + Y)$ first and determine if the result is less than zero. If yes, set the `_globalPositions.marginDepositedTotal` to zero.\\nThe following is the pseudocode:\\n```\\nnewMarginTotal = globalPositions.marginDepositedTota + _fundingFees;\\nglobalPositions.marginDepositedTotal = newMarginTotal > 0 ? uint256(newMarginTotal) : 0;\\n```\\n