xanoutas/solidity-security-findings huggingface dataset:
Missing an option to wrap Singularity tokens in `mintBBLendSGLLockTOLP` function to be able to lock into OLP 
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/modules/MagnetarMintModule.sol#L69-L84


# Vulnerability details

## Description
The `mintBBLendSGLLockTOLP` function of the MagnetarAssetModule attempts to lend to Singularity and then lock the received Singularity liquidity tokens into the TapiocaOptionLiquidityProvision contract.
```solidity=
uint256 fraction = _depositYBLendSGL(
    data.depositData, data.externalContracts.singularity, yieldBox_, data.user, data.lendAmount
);

// if `lockData.lock`:
//      - transfer `fraction` from data.user to `address(this)
//      - deposits `fraction` to YB for `address(this)`
//      - performs tOLP.lock
uint256 tOLPTokenId = _lockOnTOB(
    data.lockData,
    yieldBox_,
    fraction,
    data.participateData.participate,
    data.user,
    data.externalContracts.singularity
);
```
The [`TapiocaOptionLiquidityProvision.lock()`](https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/options/TapiocaOptionLiquidityProvision.sol#L187) function attempts to acquire YieldBox's shares of the original Singularity tokens. `_lockOnTOB` will retrieve the received Singularity tokens from the user to deposit into YieldBox before locking into TapiocaOptionLiquidityProvision.
```solidity=
function _lockOnTOB(
    IOptionsLockData memory lockData,
    IYieldBox yieldBox_,
    uint256 fraction,
    bool participate,
    address user,
    address singularityAddress
) internal returns (uint256 tOLPTokenId) {
    tOLPTokenId = 0;
    if (lockData.lock) {
        if (!cluster.isWhitelisted(0, lockData.target)) {
            revert Magnetar_TargetNotWhitelisted(lockData.target);
        }
        if (lockData.fraction > 0) fraction = lockData.fraction;

        // retrieve and deposit SGLAssetId registered in tOLP
        (uint256 tOLPSglAssetId,,) =
            ITapiocaOptionLiquidityProvision(lockData.target).activeSingularities(singularityAddress);
        if (fraction == 0) revert Magnetar_ActionParamsMismatch();

        //deposit to YieldBox
        _extractTokens(user, singularityAddress, fraction);
        yieldBox_.depositAsset(tOLPSglAssetId, address(this), address(this), fraction, 0);

        _setApprovalForYieldBox(lockData.target, yieldBox_);
        tOLPTokenId = ITapiocaOptionLiquidityProvision(lockData.target).lock(
            participate ? address(this) : user, singularityAddress, lockData.lockDuration, lockData.amount
        );
        _revertYieldBoxApproval(lockData.target, yieldBox_);
    }
}
```
However, on some specific chains, YieldBox uses the tOFT-wrapped version of Singularity liquidity tokens for SingularityAssetId. This means that after lending into SGL, the user needs to wrap the received Singularity tokens into the tOFT version of Singularity, then deposit them into YieldBox to obtain shares of SingularityAssetId. This information was provided by the sponsor, indicating that
Missing unwrap configuration when withdrawing cross-chain in the `depositYBLendSGLLockXchainTOLP()` function of MagnetarAssetXChainModule results in being unable to lock and participate on the destination chain
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/modules/MagnetarAssetXChainModule.sol#L104


# Vulnerability details

## Description
The `depositYBLendSGLLockXchainTOLP()` function attempts to lend into Singularity, then withdraws the Singularity tokens cross-chain to lock and participate on the destination chain. The Singularity tokens are wrapped as TOFT tokens to facilitate cross-chain transfer. 
```solidity=
uint256 fraction =
    _depositYBLendSGL(data.depositData, data.singularity, IYieldBox(yieldBox), data.user, data.lendAmount);

// wrap SGL receipt into tReceipt
// ! User should approve `address(this)` for `IERC20(data.singularity)` !
uint256 toftAmount = _wrapSglReceipt(IYieldBox(yieldBox), data.singularity, data.user, fraction, data.assetId);
```
This function calls `_withdrawToChain()` with the `unwrap` parameter set to false, indicating that TOFT-wrapped Singularity tokens will not be unwrapped upon receipt on the destination chain.
```solidity=
_withdrawToChain(
    MagnetarWithdrawData({
        yieldBox: yieldBox,
        assetId: data.assetId,
        unwrap: false,
        lzSendParams: data.lockAndParticipateSendParams.lzParams,
        sendGas: data.lockAndParticipateSendParams.lzSendGas,
        composeGas: data.lockAndParticipateSendParams.lzComposeGas,
        sendVal: data.lockAndParticipateSendParams.lzSendVal,
        composeVal: data.lockAndParticipateSendParams.lzComposeVal,
        composeMsg: data.lockAndParticipateSendParams.lzParams.sendParam.composeMsg,
        composeMsgType: data.lockAndParticipateSendParams.lzComposeMsgType,
        withdraw: true
    })
);
```
However, the [`TapiocaOptionLiquidityProvision.lock()`](https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/options/TapiocaOptionLiquidityProvision.sol#L187) function attempts to acquire YieldBox's shares of the original Singularity tokens. Therefore, upon receiving wrapped Singularity tokens on the destination chain, it should unwrap these tokens to facilitate the execution of subsequent actions.
## Impact
`depositYBLendSGLLockXchainTOLP()` will fail to execute the locking process after receiving wrapped Singularity tokens cross-chain.
## Tools Used
Manual review
## Recommended Mitigation Steps
`depositYBLendSGLLockXchainTOLP()` should call `_withdrawToChain()` with `unwrap` set to true.


## Assessed type

Context
pool rescue timelock can be bypassed
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/options/TapiocaOptionLiquidityProvision.sol#L275-L277


# Vulnerability details

## Description
In `TapiocaOptionLiquidityProvision` (`tOLP`) the owner can chose to set an asset in cooldown. This essentially closes down the asset and what has been locked against it, allowing users to withdraw ahead of their lock expiry.

This works under a cooldown where the owner must first call [`TapiocaOptionLiquidityProvision::requestSglPoolRescue`](https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/options/TapiocaOptionLiquidityProvision.sol#L283-L290):
```solidity
File: tap-token/contracts/options/TapiocaOptionLiquidityProvision.sol

287:        sglRescueRequest[_sglAssetID] = block.timestamp;
```

This starts a timelock timer until [`activateSGLPoolRescue`](https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/options/TapiocaOptionLiquidityProvision.sol#L294-L305) can be called which triggers the actual rescue mode:
```solidity
File: tap-token/contracts/options/TapiocaOptionLiquidityProvision.sol

300:        if (block.timestamp < sglRescueRequest[sgl.sglAssetID] + rescueCooldown) revert RescueCooldownNotReached();
301:
302:        activeSingularities[singularity].rescue = true;
```

`rescueCooldown` is by default [`2 days`](https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/options/TapiocaOptionLiquidityProvision.sol#L64), but can be changed by the owner in [`setRescueCooldown`](https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/options/TapiocaOptionLiquidityProvision.sol#L275-L277):
```solidity
File: tap-token/contracts/options/TapiocaOptionLiquidityProvision.sol

275:    function setRescueCooldown(uint256 _rescueCooldown) external onlyOwner {
276:        rescueCooldown = _rescueCooldown;
277:    }
```

The issue is that there is no cooldown on changing `rescueCooldown`. Hence the owner can call `requestSglPoolRescue` then set `setRescueCooldown` to `0` and lastly call `activateSGLPoolRescue` to instantly set the `sglAsset` in rescue mode.

## Impact
The owner can bypass the rescue timelock by changing the cooldown, effectively triggering rescue in the same tx as requesting it. This breaks the invariant that an asset must wait a cooldown period before going into rescue mode. Thus removing the possibility for users to act on the changes.

## Proof of Concept
Test in `tap-token/test/tOLP.t.sol`:
```solidity
pragma solidity 0.8.22;

import "forge-std/Test.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IPearlmit} from "tapioca-periph/pearlmit/PearlmitHandler.sol";
import {TapiocaOptionLiquidityProvision} from "tap-token/options/TapiocaOptionLiquidityProvision.sol";

contract tOLPTest is Test {

    uint256 constant AssetId = 1;
`tOLP` positions created through `MagnetarAction.Permit` can be stolen
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/Magnetar.sol#L199-L212
https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/Magnetar.sol#L304-L305


# Vulnerability details

## Description
This issue is a combination of three other issues:
- _`MagnetarAction.TapToken` integration will leave tokens stuck in `Magnetar` contract_
- _a lot of calls in `MagnetarAction.Permit` enables anyone to steal whitelisted tokens held by `Magnetar`_ (L-01 in QA report)
- _anyone can take any whitelisted tokens approved to `Magnetar`_

In short, the first issue describes that, due to how `MagnetarAction.TapToken` is setup, it will leave the `tOLP` position a user creates through [`TapiocaOptionBroker::participate`](https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/options/TapiocaOptionBroker.sol#L301) stuck in the `Magnetar` contract. As it mints the position to `msg.sender` which will be the `Magnetar` contract:
```solidity
File: tap-token/contracts/options/TapiocaOptionBroker.sol

301:        oTAPTokenID = oTAP.mint(msg.sender, lockExpiry, uint128(target), _tOLPTokenID);
```

The two following ones, describes how anyone can take any tokens that are in the `Magnetar` contract.

Either by granting themselves permissions to transfer any whitelisted tokens in using `MagnetarAction.Permit`.
Because of how [`MagnetarStorage::_checkSender`](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/MagnetarStorage.sol#L93-L97) validates that the first argument in the calldata to `MagnetarAction.Permit` is the same as `msg.sender`. This together with that a lot of the calls allowed through `MagnetarAction.Permit` (`IYieldBox::setApprovalForAll`, `IYieldBox::setApprovalForAsset`, `IERC20::approve`, and `IERC721::approve`) have `address to` i.e. the operator/approvee as the first argument. Hence any user can approve themselves to transfer tokens out of the contract.

Or by using `MagnetarAction.OFT`. Which allows anyone to transfer tokens out of `Magnetar` (and steal any approved tokens to `Magnetar`) since there is an unvalidated call done to any whitelisted contract in [`MagnetarAction.OFT`](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/Magnetar.sol#L325-L333).


The contract is not in itself supposed to hold any tokens hence in itself these issues are not that severe by themselves.

However these issues combined allows an attacker to steal the position completely. Since the first one makes the position be stuck in `Magnetar` and the two last ones makes it not actually stuck, but retrievable by anyone.

## Impact
If a user uses `MagnetarAction.TapToken` to create their position they can have their position and/or rewards stolen.

## Proof of Concept
Test in `tap-token/test/
Spearbit finding 5.2.2 not fixed
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/options/TapiocaOptionBroker.sol#L408-L414
https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/options/TapiocaOptionBroker.sol#L292-L298


# Vulnerability details

## Description
The Spearbit finding "5.2.2 It is possible to exercise TAP option an extra time compared to lock duration" is not fixed. The finding itself is a follow up on the C4 finding [#189](https://github.com/code-423n4/2023-07-tapioca-findings/issues/189). The description of the issue can be found in the Spearbit report.

The mitigation introduced in the [PR](https://github.com/Tapioca-DAO/tap-token/pull/155) seems lost in the code base at audit here.

## Impact
Quoting the impact from the Spearbit report:
> Attacker has removed one epoch of rewards from the long term stakers, receiving 2 tapOFT payoffs for 1 epoch long staking. More generally, an attacker can add 1 epoch of option rewards in excess to their actual locking time (as epsilon can be made minutes long and not significant position locking wise).
> This is a violation of base protocol [token economy](https://docs.tapioca.xyz/tapioca/token-economy/token-economy#call-option-incentive-tap-otap:~:text=Lenders%20with%20active%20oTAP%20positions%20will%20receive%20oTAP%20shares%20from%20the%20DSO%20program%20every%20week%20that%20their%20position%20remains%20locked%2C%20proportional%20to%20their%20positions%20share%20of%20the%20total%20supplied%20locked%20liquidity%20in%20the%20respective%20market%2C):
>> Lenders with active oTAP positions will receive oTAP shares from the DSO program every week that their position remains locked, proportional to their positions share of the total supplied locked liquidity in the respective market

## Proof of Concept
Test showing the issue is still there in `tap-token/test/OptionTest.t.sol`:
```solidity
    function testExerciseOptionTwice() public {
        pearlmit.approve(address(yieldbox), 1, address(tOLP), type(uint200).max, type(uint48).max);
        paymentToken.mint(address(this),1000e18);
        paymentToken.approve(address(pearlmit),type(uint256).max);
        pearlmit.approve(address(paymentToken), 0, address(tOB), type(uint200).max, type(uint48).max);

        // epoch timestamps
        uint256 epoch2 = block.timestamp + 7 days;
        uint256 epoch3 = epoch2 + 7 days;

        // step 1 participate right before end of epoch
        vm.warp(epoch2 - 5 minutes);

        uint256 tOLPId = tOLP.lock({
            _to: address(this),
            _singularity: IERC20(singularity), 
            _lockDuration: 7 days + 10 minutes,
            _ybShares: 1e18
        });

        tOLP.approve(address(pearlmit), tOLPId);
        pearlmit.approve(address(tOLP), tOLPId, address(tOB), 1, type(uint48).max);
        tOB.participate(tOLPId);

        // epoch changes
        vm.warp(epoch2);
        tOB.newEpoch();

        // step 2 r
anyone holding TapTokens needs to be in control of their address on all chains `TAP` is deployed on
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/tapiocaOmnichainEngine/TapiocaOmnichainReceiver.sol#L286-L292


# Vulnerability details

## Impact
Anyone not in control of their address on all chains TapToken is deployed on can have all their `TAP` stolen.

## Proof of Concept
`TapToken` has a feature where you can trigger a transfer cross chain using the LZCompose functionality. If you send a cross chain message with the type, `MSG_REMOTE_TRANSFER` that will transfer tokens from the destination chain to another chain. I.e. "pull" tokens from the destination chain.

The issue is that there is an assumption made in the [`TapiocaOmnichainReceiver::_internalTransferWithAllowance`](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/tapiocaOmnichainEngine/TapiocaOmnichainReceiver.sol#L286-L292):

```solidity
File: tapioca-periph/contracts/tapiocaOmnichainEngine/TapiocaOmnichainReceiver.sol

286:    function _internalTransferWithAllowance(address _owner, address srcChainSender, uint256 _amount) internal {
287:        if (_owner != srcChainSender) {
288:            _spendAllowance(_owner, srcChainSender, _amount);
289:        }
290:
291:        _transfer(_owner, address(this), _amount);
292:    }
```

Here, if the `srcChainSender`, `msg.sender` on the source chain, the one initiating the transfer is the same address the transfer will not check any allowance.

This makes an assumption about the holders of TapTokens. Since they must control their address on all chain TapToken is deployed on. As shown in the [wintermute hack](https://rekt.news/wintermute-rekt/) this is not always the case. Some older SAFE wallets can have their address taken on other chains.

Any user having `TAP` needs to be aware of this and know the risks with holding tap in different smart wallets.

## Tools Used
Manual audit

## Recommended Mitigation Steps
Consider actually requiring an allowance for users to do this, even if the address is the same. Or simply don't allow cross chain "pull" of tokens.


## Assessed type

Other
anyone with a `Pearlmit` approval to transfer `TapToken` can have their funds stolen
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/tapiocaOmnichainEngine/BaseTapiocaOmnichainEngine.sol#L63-L67


# Vulnerability details

## Description
When transferring `TapToken` there is an extra check done in [`BaseTapiocaOmnichainEngine::transferFrom`](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/tapiocaOmnichainEngine/BaseTapiocaOmnichainEngine.sol#L63-L67):
```solidity
File: tapioca-periph/contracts/tapiocaOmnichainEngine/BaseTapiocaOmnichainEngine.sol

63:        if (allowance(from, spender) < value) {
64:            // _transfer(from, to, value);
65:            bool isErr = pearlmit.transferFromERC20(from, to, address(this), value);
66:            if (isErr) revert BaseTapiocaOmnichainEngine_NotValid();
67:        } else {
```

Here there's if the spender is not allowed, the allowance is checked in `Pearlmit`. The issue is that, `Pearlmit` checkes the allowance against `msg.sender` which in this case will be the `TapToken` contract.

Hence any user with a allowance to the `TapToken` contract in `Pearlmit` can have their `TAP` stolen.

## Impact
If a user has allowed `TapToken` to transfer `TapToken` through `Pearlmit`, they can have all they have approved stolen. Since `TapToken` does a lot of token handling in composed messages this is likely to happen.

## Proof of Concept
Test in `tap-token/test/TapToken.t.sol`:
```solidity
    function testStealTapTokenUsingPearlmitAllowanceToTapToken() public {
        deal(address(aTapOFT), userA, 1e18);
        assertEq(aTapOFT.balanceOf(userA), 1e18);
        assertEq(aTapOFT.balanceOf(userB), 0);

        // userA allows TapToken to transfer TapTokens
        vm.startPrank(userA);
        pearlmit.approve(address(aTapOFT), 0, address(aTapOFT), 1e18, type(uint48).max);
        aTapOFT.approve(address(pearlmit), 1e18);
        vm.stopPrank();

        // userB sees this and uses that allowance to transfer to themselves
        vm.prank(userB);
        aTapOFT.transferFrom(userA,userB, 1e18);
        
        assertEq(aTapOFT.balanceOf(userA), 0);
        assertEq(aTapOFT.balanceOf(userB), 1e18);
    }
```

## Tools Used
Manual audit

## Recommended Mitigation Steps
Consider not doing the fallback to `Pearlmit` in `transferFrom`.


## Assessed type

Access Control
`MagnetarAction.TapToken` integration will leave tokens stuck in `Magnetar` contract
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/Magnetar.sol#L304-L305


# Vulnerability details

## Description
In `Magnetar` there is the functionality to interact with the `TapoiocaOptionBroker` and `TapiocaOptionLiquidityProvision` to manage your options using `MagnetarAction.TapToken`.

The issue is that if you do this, for the calls `ITapiocaOptionBroker.exerciseOption` and `ITapiocaOptionBroker.participate` the resulting tokens that are rewarded/minted will be stuck in the `Magnetar` contract.

This is because [`TapiocaOptionBroker.exerciseOption`](https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/options/TapiocaOptionBroker.sol#L570):
```solidity
File: tap-token/contracts/options/TapiocaOptionBroker.sol

570:        tapOFT.extractTAP(msg.sender, tapAmount);
```
and [`TapiocaOptionBroker::participate`](https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/options/TapiocaOptionBroker.sol#L301):
```solidity
File: tap-token/contracts/options/TapiocaOptionBroker.sol

301:        oTAPTokenID = oTAP.mint(msg.sender, lockExpiry, uint128(target), _tOLPTokenID);
```

mints to `msg.sender` which when called from `Magnetar` will be `Magnetar`.

## Impact
A user using `Magnetar` for participating or claiming rewards will have their tokens stuck in `Magnetar`.

## Proof of Concept
Test in `tap-token/test/Magnetar.t.sol`:
```solidityOptionTest
    function testMagnetarParticipateOTAPStuckInMagnetar() public {
        MagnetarCall memory permitYieldBox = MagnetarCall({
            id: MagnetarAction.Permit,
            target: address(pearlmit),
            value: 0,
            allowFailure: false,
            call: abi.encodeWithSelector(
                IPearlmit.approve.selector,
                address(yieldbox), 1, address(tOLP), type(uint200).max, type(uint48).max
            )
        });

        MagnetarCall memory lock = MagnetarCall({
            id: MagnetarAction.TapToken,
            target: address(tOLP),
            value: 0,
            allowFailure: false,
            call: abi.encodeWithSelector(
                TapiocaOptionLiquidityProvision.lock.selector,
                address(magnetar), singularity, 1 weeks, 1e18
            )
        });

        MagnetarCall memory permitTOLPToPearlmit = MagnetarCall({
            id: MagnetarAction.Permit,
            target: address(tOLP),
            value: 0,
            allowFailure: false,
            call: abi.encodeWithSelector(IERC721.setApprovalForAll.selector, address(pearlmit), true)
        });

        MagnetarCall memory permitPearlmitToTOB = MagnetarCall({
            id: MagnetarAction.Permit,
            target: address(pearlmit),
            value: 0,
            allowFailure: false,
            call: abi.encodeWithSelector(
                IPearlmit.approve.selector,
                address(tOLP), 1, addres