xanoutas/solidity-security-findings huggingface dataset:
`encodeToeComposeMsg` Encoding is incorrect and messages won't work
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/tapiocaOmnichainEngine/TapiocaOmnichainEngineCodec.sol#L85-L92
https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/tapiocaOmnichainEngine/TapiocaOmnichainEngineCodec.sol#L96-L109


# Vulnerability details

The function `encodeToeComposeMsg` is written as follows

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/tapiocaOmnichainEngine/TapiocaOmnichainEngineCodec.sol#L85-L92

```solidity

    function encodeToeComposeMsg(bytes memory _msg, uint16 _msgType, uint16 _msgIndex, bytes memory _tapComposedMsg)
        internal
        pure
        returns (bytes memory)
    {
        return abi.encodePacked(_tapComposedMsg, _msgType, uint16(_msg.length), _msgIndex, _msg);
    }
```

Decoding is written as follows:

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/tapiocaOmnichainEngine/TapiocaOmnichainEngineCodec.sol#L96-L109

```solidity
     *
     *           *    TapToken message packet   *
     * ------------------------------------------------------------- *
     * Name          | type      | start | end                       *
     * ------------------------------------------------------------- *
     * msgType       | uint16    | 0     | 2                         *
     * ------------------------------------------------------------- *
     * msgLength     | uint16    | 2     | 4                         *
     * ------------------------------------------------------------- *
     * msgIndex      | uint16    | 4     | 6                         *
     * ------------------------------------------------------------- *
     * tapComposeMsg | bytes     | 6     | msglength + 6             *
     * ------------------------------------------------------------- *
     *
```

Which means that the encoding is being done incorrectly

_msgType should be the first parameter

We can verify this (this is how I found it) via fuzz testing a random inputs and verifying that the decoded data matches


### POC

The POC uses a custom foundry repo, I simply copied the files and fixed imports

The library `TapiocaOmnichainEngineCodec` is changed to always use internal functions

The source code is here:
https://gist.github.com/GalloDaSballo/418d7c394de8bba4961332ce305d52aa

And the full repo is available here (invite only):
https://github.com/GalloDaSballo/omnichain-lib-fuzz


The test is as follows:

```solidity
        function encodeToeComposeMsg(bytes memory _msg, uint16 _msgType, uint16 _msgIndex, bytes memory _tapComposedMsg)
        public
    {
        bytes memory encoded =
            TapiocaOmnichainEngineCodec.encodeToeComposeMsg(_msg, _msgType, _msgIndex, _tapComposedMsg);
        (uint16 msgType_, uint16 msgLength_, uint16 msgIndex_, bytes memory tapComposeMsg_, bytes memory nextMsg_) =
All rewards stolen by claiming and then setting self as `sendsTo`
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tap-token/blob/050e666142e61018dbfcba64d295f9c458c69700/contracts/tokens/TapTokenReceiver.sol#L176-L177


# Vulnerability details

## Impact
`_claimTwpTapRewardsReceiver` calls `claimRewards`, which requires allowance from `twTap` to `Magnetar`

But it `sendsTo` 

https://github.com/Tapioca-DAO/tap-token/blob/050e666142e61018dbfcba64d295f9c458c69700/contracts/tokens/TapTokenReceiver.sol#L176-L177

```solidity
            address sendTo_ = OFTMsgCodec.bytes32ToAddress(claimTwTapRewardsMsg_.sendParam[sendParamIndex].sendParam.to);

```

Which is a user provided arbitrary address, meaning any attacker can claim rewards to themselves for all locks

The code doesn't perform any check to ensure that the lock being claimed belongs to the original sender, meaning that any attacker can claim all rewards from any locker that approved TapToken to themselves


https://github.com/Tapioca-DAO/tap-token/blob/050e666142e61018dbfcba64d295f9c458c69700/contracts/tokens/TapTokenReceiver.sol#L152-L156

```solidity
    function _claimTwpTapRewardsReceiver(bytes memory _data) internal virtual twTapExists {
        ClaimTwTapRewardsMsg memory claimTwTapRewardsMsg_ = TapTokenCodec.decodeClaimTwTapRewardsMsg(_data);

        // Claim rewards, make sure to have approved this contract on TwTap. | /// @audit Stealing 100% of rewards?
        uint256[] memory claimedAmount_ = twTap.claimRewards(claimTwTapRewardsMsg_.tokenId, address(this));
```

### POC

- Claim rewards for any twTap approved lock
- Set `sendTo` to someone else
- All rewards are stolen

### Mitigation

There needs to be a check to ensure that the recipient is approved

Note that I sent another finding that shows how the logic for claiming seems incorrect

I would recommend either ensuring that the original sender is the owner of the lock

Or using ERC721 approved operators to prove it

Keep in mind the complications around addresses xChain when applying this change


## Assessed type

Invalid Validation
Funds can be stolen using reentrancy in Magnetar
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Proof of Concept
Magnetar.burst is entry point for user to execute actions. This [function doesn't have reentrancy protection](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/Magnetar.sol#L72).

Using this function it's possible to [execute market operations](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/Magnetar.sol#L101). `_processMarketOperation` function [calls `_checkSender(abi.decode(_actionCalldata[4:36], (address)))`](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/Magnetar.sol#L278) to make sure that caller is user itself or caller is whitelisted contract. This means that in case if I would like to execute any actions with other users, then the call will revert as i am not whitelisted contract.

Magnetar contract will be set as operator for users that would like to use it. In this case all market actions that are done through Magnetar will not fail. In case if attacker can make Magnetar call on behalf of other user, then he can steal user's funds.

This is indeed possible to do. What attacker needs is [to execute action with `MagnetarAction.OFT` action id](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/Magnetar.sol#L154) and provide Magnetar contract as target. In this case [whitelisted check will pass](https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/Magnetar.sol#L331) as Magnetar is whitelisted contract and then Magnetar.burst will be reentered, but with Magnetar itself as `msg.sender`, which then allows attacker to execute market operation on behalf of other user(or any other actions that he can find useful) as `_checkSender` check will now pass, because caller is Magnetar.

## Impact
Funds can be stolen

## Tools Used
VsCode
## Recommended Mitigation Steps
You can add reentrancy modifier if possible. Otherwise you need to implement better whitelist control, which will check whitelists for actions.


## Assessed type

Error
`twTap._requireClaimPermission` allows to claim to approved address and cause loss of Rewards and Principal
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tap-token/blob/050e666142e61018dbfcba64d295f9c458c69700/contracts/governance/twTAP.sol#L544-L551


# Vulnerability details

## Impact
`twTap` has introduced `_requireClaimPermission`, in other parts of the codebase we see that users are intended to grant allowance to `TapTokenReceiver` which is effectively a way to perform xChain operations

https://github.com/Tapioca-DAO/tap-token/blob/050e666142e61018dbfcba64d295f9c458c69700/contracts/tokens/TapTokenReceiver.sol#L148

```solidity
* @dev !!! The user needs to have given TwTAP allowance to this contract  !!!
```

Granting `twTAP` to any address allows anyone to claim on behalf of the original owner, losing all tokens and rewards, and in some cases (magnetar, routers) allowing to steal both as well.


This is because the code is checking the `to` then any non-atomic approval can be cause for griefing

-> Permit front-run -> Griefed
-> Approve -> Griefed

This will allow:
- Losing 100% of rewards
- Losing principal once unlocked

This will be skipped if `_isApprovedOrOwner(_to, _tokenId)`

But `_isApprovedOrOwner(_to, _tokenId)` may not be `msg.sender`

So we can claim to a random AMM or w/e approved addy

And cause total loss of funds

https://github.com/Tapioca-DAO/tap-token/blob/050e666142e61018dbfcba64d295f9c458c69700/contracts/governance/twTAP.sol#L544-L551

```solidity
    /**
     * @dev Use `_isApprovedOrOwner()` internally. /// @audit Could this allow stealing in some way?
     */
    function _requireClaimPermission(address _to, uint256 _tokenId) internal view { /// @audit This is actually wrong
        if (!_isApprovedOrOwner(_to, _tokenId) && !isERC721Approved(_ownerOf(_tokenId), _to, address(this), _tokenId)) {
            revert NotApproved(_tokenId, msg.sender);
        }
    }
```

https://github.com/Tapioca-DAO/tap-token/blob/050e666142e61018dbfcba64d295f9c458c69700/contracts/governance/twTAP.sol#L414-L427

```solidity
    function exitPosition(uint256 _tokenId, address _to)
        external
        nonReentrant
        whenNotPaused
        returns (uint256 tapAmount_)
    {
        {
            address owner_ = ownerOf(_tokenId);
            if (_to != owner_) {
                _requireClaimPermission(_to, _tokenId); /// @audit We can claim to Approved AMM or w/e so we can cause total loss
            } /// Or to L0
        }
        tapAmount_ = _releaseTap(_tokenId, _to);
    }
```

## POC

The POC is based on the Recon Template, happy to share the rest of the code privately.

### Output

Which demonstrates that the claim check allows us (random caller) to send tokens to any approved recipient

```python
Ran 1 test for test/recon/CryticToFoundry.sol:CryticToFoundry
[FAIL. Reason: assertion failed] testDemo() (gas: 303541)
Logs:
  Error: We can lose their tokens
  Error: Assertion Failed
```

### Code

In this case, I just deploy the real twTAP and other contracts via `setup`

```solidity
// SPDX-License-Identifier: MIT
pragma sol
Adversary can steal approved tOLPs to Magnetar via `_paricipateOnTOLP`
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
User can steal pre-approved tOLPs to Magnetar 

## Proof of Concept
Any user could steal any approved tOLP to Magnetar.
This is because within the Magnetar call, if the user has not minted a tOLP NFT, they can participate with any id they wish, by inputting it in `participateData`.

```solidity
    function _participateOnTOLP(
        IOptionsParticipateData memory participateData,
        address user,
        address lockDataTarget,
        uint256 tOLPTokenId
    ) internal {
        if (!cluster.isWhitelisted(0, participateData.target)) {
            revert Magnetar_TargetNotWhitelisted(participateData.target);
        }

        // Check tOLPTokenId
        if (participateData.tOLPTokenId != 0) {
            if (participateData.tOLPTokenId != tOLPTokenId && tOLPTokenId != 0) {
                revert Magnetar_tOLPTokenMismatch();
            }

            tOLPTokenId = participateData.tOLPTokenId;  // @audit - does not verify sender owns that token
        }
        if (tOLPTokenId == 0) revert Magnetar_ActionParamsMismatch();

        IERC721(lockDataTarget).approve(participateData.target, tOLPTokenId);
        uint256 oTAPTokenId = ITapiocaOptionBroker(participateData.target).participate(tOLPTokenId);

        address oTapAddress = ITapiocaOptionBroker(participateData.target).oTAP();
        IERC721(oTapAddress).safeTransferFrom(address(this), user, oTAPTokenId, "0x");
    }
```
The only thing to consider is that the following line, must not revert.

```solidity
IERC721(lockDataTarget).approve(participateData.target, tOLPTokenId);
```

Since the contract will not be an owner of `tOLPTokenId`, we'll need to input a custom malicious `lockDataTarget` address, for which the approve will not revert. The `lockDataTarget` is not used at any other place within that function, so there'll be no problem inputting a malicious address here.

After doing the described steps above, the attacker will lock the innocent user's tOLP and get the oTAP NFT minted to themselves, effectively stealing the innocent user's NFT. 

## Tools Used
Manual review

## Recommended Mitigation Steps
verify that the sender owns that tOLP id 


## Assessed type

ERC721
Attackers can construct compose calls to cross-chain steal assets from approved users
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph//blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/modules/MagnetarBaseModule.sol#L51-L84
https://github.com/Tapioca-DAO/tapioca-periph//blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/modules/MagnetarYieldBoxModule.sol#L81-L83


# Vulnerability details

## Impact
In the withdrawToChain function, the user can construct any composeMsg.(Additionally, since none of the functions change the nextMsg_ in composeMsg, an attacker can construct it in nextMsg_.)
```solidity
    function withdrawToChain(MagnetarWithdrawData memory data) public payable {
        _withdrawToChain(data);
    }
...
    function _withdrawToChain(MagnetarWithdrawData memory data) internal {
        if (!cluster.isWhitelisted(0, address(data.yieldBox))) {
            revert Magnetar_TargetNotWhitelisted(address(data.yieldBox));
        }
        IYieldBox _yieldBox = IYieldBox(data.yieldBox);

        // perform a same chain withdrawal
        if (data.lzSendParams.sendParam.dstEid == 0) {
            _withdrawHere(_yieldBox, data.assetId, data.lzSendParams.sendParam.to, data.lzSendParams.sendParam.amountLD);
            return;
        }

        // perform a cross chain withdrawal
        (, address asset,,) = _yieldBox.assets(data.assetId);
        if (!cluster.isWhitelisted(0, asset)) {
            revert Magnetar_TargetNotWhitelisted(asset);
        }

        _yieldBox.withdraw(data.assetId, address(this), address(this), data.lzSendParams.sendParam.amountLD, 0);
        // TODO: decide about try-catch here
        if (data.unwrap) {
            _lzCustomWithdraw(
                asset,
                data.lzSendParams,
                data.sendGas,
                data.sendVal,
                data.composeGas,
                data.composeVal,
                data.composeMsgType
            );
        } else {
            _lzWithdraw(asset, data.lzSendParams, data.sendGas, data.sendVal);
        }
    }
```
This allows an attacker to cross-chain call the depositYBLendSGLLockXchainTOLP function via withdrawToChain and specify any approved address as data.user.
```solidity
    function depositYBLendSGLLockXchainTOLP(DepositAndSendForLockingData memory data) public payable {
        // Check sender
        _checkSender(data.user);

        address yieldBox = IMarket(data.singularity).yieldBox();

        // if `depositData.deposit`:
        //      - deposit SGL asset to YB for `data.user`
        // if `lendAmount` > 0:
        //      - add asset to SGL
        uint256 fraction =
            _depositYBLendSGL(data.depositData, data.singularity, IYieldBox(yieldBox), data.user, data.lendAmount);
```
The cross-chain call is initiated by UsdoReceiver._toeComposeReceiver, and UsdoReceiver as msg.sender could pass the _checkSender check in depositYBLendSGLLockXchainTOLP.
```solidity
    function _toeComposeReceiver(uint16 _msgType, address _srcChainSender, bytes memory _toeComposeMsg)
Arbitrary call within Magnetar allows for theft of funds
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Theft of user funds

## Proof of Concept
The only restrictions within `_processOFTOperation` is that it should be a whitelisted address. Besides that, no other checks are made. This would allow for any user to utilise any approved funds towards Magnetar. This would mean, getting full access to user's approved `Singularity`, `BigBang`, `oTap`, `tOLP`, `YieldBox` funds and pretty much any approval which the user might've given to Magnetar. 


```solidity
    function _processOFTOperation(
        address _target,
        bytes calldata _actionCalldata,
        uint256 _actionValue,
        bool _allowFailure
    ) private {
        if (!cluster.isWhitelisted(0, _target)) revert Magnetar_NotAuthorized(_target, _target);
        _executeCall(_target, _actionCalldata, _actionValue, _allowFailure);
    }
```

If a user has for example given Magnetar allowance for their `tOLP` NFT, adversary can simply call `tOLP.transferFrom(victim, attacker, id)` and get the NFT.

## Tools Used
Manual review

## Recommended Mitigation Steps
Add proper checks to the function.


## Assessed type

call/delegatecall
Adversary can utilise approved to Magnetar oTAP and tOLP NFTs
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Adversary can utilise innocent user's oTAP and tOLP NFTs

## Proof of Concept
The idea of Magnetar is to allow users to batch transactions towards certain contract within the Tapioca contracts, include `TapiocaOptionBroker` and `TapiocaOptionLiquidityProvision`. In order to do so, users will have to give oTAP and tOLP allowance to the Magnetar contract.

The problem is that within the `_processTapTokenOperation` function any user could make a call for another's user's approved NFT, as there are no checks that the `msg.sender` is the owner of the NFT. 

```solidity
    function _processTapTokenOperation(
        address _target,
        bytes calldata _actionCalldata,
        uint256 _actionValue,
        bool _allowFailure
    ) private {
        if (!cluster.isWhitelisted(0, _target)) revert Magnetar_NotAuthorized(_target, _target);

        bytes4 funcSig = bytes4(_actionCalldata[:4]);
        if (
            funcSig == ITapiocaOptionBroker.exerciseOption.selector
                || funcSig == ITapiocaOptionBroker.participate.selector
                || funcSig == ITapiocaOptionBroker.exitPosition.selector
                || funcSig == ITapiocaOptionLiquidityProvision.lock.selector
                || funcSig == ITapiocaOptionLiquidityProvision.unlock.selector
        ) {
            _executeCall(_target, _actionCalldata, _actionValue, _allowFailure);
            return;
        }
        revert Magnetar_ActionNotValid(MagnetarAction.TapToken, _actionCalldata);
    }
```

Example: user can call `exerciseOption` for another person's approved oTAP and exercise their option.

```solidity
    function exerciseOption(uint256 _oTAPTokenID, ERC20 _paymentToken, uint256 _tapAmount) external whenNotPaused {
        // Load data
        (, TapOption memory oTAPPosition) = oTAP.attributes(_oTAPTokenID);
        LockPosition memory tOLPLockPosition = tOLP.getLock(oTAPPosition.tOLP);
        bool isPositionActive = _isPositionActive(tOLPLockPosition);
        if (!isPositionActive) revert OptionExpired();

        uint256 cachedEpoch = epoch;

        PaymentTokenOracle memory paymentTokenOracle = paymentTokens[_paymentToken];

        // Check requirements
        if (paymentTokenOracle.oracle == ITapiocaOracle(address(0))) {
            revert PaymentTokenNotSupported();
        }
        if (!oTAP.isApprovedOrOwner(msg.sender, _oTAPTokenID)) {
            revert NotAuthorized();
        }
        if (block.timestamp < oTAPPosition.entry + EPOCH_DURATION) {
            revert OneEpochCooldown();
        } // Can only exercise after 1 epoch duration

        // Get eligible OTC amount
        uint256 gaugeTotalForEpoch = singularityGauges[cachedEpoch][tOLPLockPosition.sglAssetID];
        uint256 netAmount = uint256(netDepositedForEpoch[cachedEpoch][tOLPLockPosition.sglAssetID
Adversary can steal user's NFT's if they have set Magnetar as `isApprovedForAll == true`.
Audit: 2024-02-tapioca-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Adversary can steal innocent user's pre-approved NFTs.

## Proof of Concept
Since Magnetar is supposed to be used as a router for multiple operations, it can be expected that user will have it pre-approved for their NFTs (such as `tOLP` as `oTAP` ones, as they'll be the ones primarily used).

The Magnetar contract allows for any user to make a `ERC721.approve`, via `_processPermitOperation`

```solidity
    function _processPermitOperation(address _target, bytes calldata _actionCalldata, bool _allowFailure) private {
        if (!cluster.isWhitelisted(0, _target)) revert Magnetar_NotAuthorized(_target, _target);

        /// @dev owner address should always be first param.
        // permitAction(bytes,uint16)
        // permit(address owner...)
        // revoke(address owner...)
        // permitAll(address from,..)
        // permit(address from,...)
        // setApprovalForAll(address from,...)
        // setApprovalForAsset(address from,...)
        bytes4 funcSig = bytes4(_actionCalldata[:4]);
        if (
            funcSig == IPermitAll.permitAll.selector || funcSig == IPermitAll.revokeAll.selector
                || funcSig == IPermit.permit.selector || funcSig == IPermit.revoke.selector
                || funcSig == IYieldBox.setApprovalForAll.selector || funcSig == IYieldBox.setApprovalForAsset.selector 
                || funcSig == IERC20.approve.selector || funcSig == IPearlmit.approve.selector
                || funcSig == IERC721.approve.selector 
        ) {
            /// @dev Owner param check. See Warning above.
            _checkSender(abi.decode(_actionCalldata[4:36], (address)));
            // No need to send value on permit
            _executeCall(_target, _actionCalldata, 0, _allowFailure);
            return;
        }
        revert Magnetar_ActionNotValid(MagnetarAction.Permit, _actionCalldata);
    }

```

The problem is that for OZ ERC721s (such as oTAP and tOLP), if an NFT owner has approved a spender as `isApprovedForAll`, the spender can call `approve` for any NFTs belonging to the owner.

In other words - if user A has set Magnetar as `approvedForAll`, user B can call `NFT.approve(userB, id)` and get access to user A's NFT

```solidity
    function approve(address to, uint256 tokenId) public virtual override {
        address owner = ERC721.ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");

        require(
            _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "ERC721: approve caller is not token owner or approved for all"
        );

        _approve(to, tokenId);
    }
```

## Tools Used
Manual review

## Recommended Mitigation Steps
Do not allow users to make a call with  `ERC721.approve` selector


## Assessed type

ERC721