xanoutas/solidity-security-findings huggingface dataset:
`TwTAP` participation can be bricked stopping user to participate
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/governance/twTAP.sol#L315
https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/governance/twTAP.sol#L601-L605


# Vulnerability details

## Description
`TwTAP` uses the `twAML` algorithm to determine the amount of rewards a user gets for locking their `TAP`. There, after you've achieved a certain threshold your lock duration will affect the reward calculation using a state called `cumulative`. Which is tracked per pool.

When you join the pool total average magnitude will be added or subtracted from the pool `cumulative` depending on if you lock longer or shorter than the average duration.

[`TwTAP::participate`](https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/governance/twTAP.sol#L328-L337):
```solidity
File: tap-token/contracts/governance/twTAP.sol

328:            if (divergenceForce) {
329:                pool.cumulative += pool.averageMagnitude;
330:            } else {
331:                // TODO: Strongly suspect this is never less. Prove it.
332:                if (pool.cumulative > pool.averageMagnitude) {
333:                    pool.cumulative -= pool.averageMagnitude;
334:                } else {
335:                    pool.cumulative = 0;
336:                }
337:            }
```

When you exit your position, your contribution will be undone in [`TwTAP::_releaseTap`](https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/governance/twTAP.sol#L600-L608):
```solidity
File: tap-token/contracts/governance/twTAP.sol

600:            if (position.divergenceForce) {
601:                if (pool.cumulative > position.averageMagnitude) {
602:                    pool.cumulative -= position.averageMagnitude;
603:                } else {
604:                    pool.cumulative = 0;
605:                }
606:            } else {
607:                pool.cumulative += position.averageMagnitude;
608:            }
```

Both of these have an issue, on lines `L335` and `L604` the `pool.cumulative` can be set to `0`. If this happens any participations would be blocked due to this row in [`TwTAP::participate`](https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/governance/twTAP.sol#L314-L315):
```solidity
File: tap-token/contracts/governance/twTAP.sol

314:        // Revert if the lock 4x the cumulative
315:        if (magnitude >= pool.cumulative * 4) revert NotValid();
```

Since you must pass in at least a week as duration, `magnitude` will always be `>0`.

A malicious user could use this to prevent other users from participating thus increasing their own share of the total rewards. If they at start, lock for maximum time, `4 weeks`, to create a lock with a high `averageMagnitude`. Then also lock for a very short period of time, to bring `cumulative` low agai
Missing slippage check when depositing to YieldBox
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/modules/MagnetarMintCommonModule.sol#L150
https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/modules/MagnetarAssetModule.sol#L95
 https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/modules/MagnetarCollateralModule.sol#L83


# Vulnerability details

## Description
There are many functions of Magnetar's modules which attempt to deposit assets into YieldBox. For example, in the `_depositYBLendSGL` function of MagnetarMintCommonModule
```solidity=
function _depositYBLendSGL(
    IDepositData memory depositData,
    address singularityAddress,
    IYieldBox yieldBox_,
    address user,
    uint256 lendAmount
) internal returns (uint256 fraction) {
    if (singularityAddress != address(0)) {
        ...
        if (depositData.deposit) {
            depositData.amount = _extractTokens(user, sglAssetAddress, depositData.amount);

            sglAssetAddress.safeApprove(address(yieldBox_), depositData.amount);
            yieldBox_.depositAsset(sglAssetId, address(this), user, depositData.amount, 0);
        }

        // if `lendAmount` > 0:
        //      - add asset to SGL
        fraction = 0;
        if (lendAmount == 0 && depositData.deposit) {
            lendAmount = depositData.amount;
        }
        if (lendAmount > 0) {
            uint256 lendShare = yieldBox_.toShare(sglAssetId, lendAmount, false);
            fraction = ISingularity(singularityAddress).addAsset(user, user, false, lendShare);
        }

        _revertYieldBoxApproval(singularityAddress, yieldBox_);
    }
}
```
However, there is no slippage check for depositing assets into YieldBox, while the ratio between shares and the amount in YieldBox always fluctuates (see [code snippet](https://github.com/Tapioca-DAO/tap-yieldbox/blob/8de8ad15fe80c4e00843475d7d20fae4d0397003/contracts/YieldBox.sol#L138-L162)). It may result in the user receiving lower shares than expected from depositing.
## Impact
When using Magnetar's functions, users will face risks from the fluctuation of YieldBox's shares and amounts, which may lead to unexpected losses when depositing to YieldBox.

## Tools Used
Manual review
## Recommended Mitigation Steps
Slippage checks should be added whenever depositing to YieldBox


## Assessed type

Other
The order of composed messages is reversed during the `depositYBLendSGLLockXchainTOLP()` function of MagnetarAssetXChainModule
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/modules/MagnetarAssetXChainModule.sol#L90-L97
https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/tapiocaOmnichainEngine/TapiocaOmnichainEngineCodec.sol#L91


# Vulnerability details


## Description
In the `depositYBLendSGLLockXchainTOLP()` function, after lending to Singularity, it attempts to send SGL tokens cross-chain and execute the lock action upon receiving by using a composed message. Therefore, this function decodes the passed compose message to change the `lockData.fraction` to the received token amount, and then encodes that message again afterward
```solidity=
(uint16 msgType_,, uint16 msgIndex_, bytes memory tapComposeMsg_, bytes memory nextMsg_) =
        TapiocaOmnichainEngineCodec.decodeToeComposeMsg(data.lockAndParticipateSendParams.lzParams.sendParam.composeMsg);

        LockAndParticipateData memory lockData = abi.decode(tapComposeMsg_, (LockAndParticipateData));
        lockData.fraction = toftAmount;

        data.lockAndParticipateSendParams.lzParams.sendParam.composeMsg =
            TapiocaOmnichainEngineCodec.encodeToeComposeMsg(abi.encode(lockData), msgType_, msgIndex_, nextMsg_);
```

The `decodeToeComposeMsg()` function decodes with the order from left to right, which means `tapComposeMsg_` precedes `nextMsg_` in the whole encoded messages.
```solidity=
function decodeToeComposeMsg(bytes memory _msg)
    internal
    pure
    returns (
        uint16 msgType_,
        uint16 msgLength_,
        uint16 msgIndex_,
        bytes memory tapComposeMsg_,
        bytes memory nextMsg_
    )
{
    // TODO use bitwise operators?
    msgType_ = BytesLib.toUint16(BytesLib.slice(_msg, 0, 2), 0);
    msgLength_ = BytesLib.toUint16(BytesLib.slice(_msg, MSG_TYPE_OFFSET, 2), 0);

    msgIndex_ = BytesLib.toUint16(BytesLib.slice(_msg, MSG_LENGTH_OFFSET, 2), 0);
    tapComposeMsg_ = BytesLib.slice(_msg, MSG_INDEX_OFFSET, msgLength_);

    uint256 tapComposeOffset_ = MSG_INDEX_OFFSET + msgLength_;
    nextMsg_ = BytesLib.slice(_msg, tapComposeOffset_, _msg.length - (tapComposeOffset_));
}
```
However, `encodeToeComposeMsg()` function will move the next message to the beginning of the total composed message.
```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);
}
```
Therefore, the order of child messages will be changed in `depositYBLendSGLLockXchainTOLP` function.
## Impact
Cross-chain execution of Layerzero v2 will compose with the wrong order of actions, leading to unexpected results of DOS.
## Tools Used
Manual review
## Recommended Mitigation Steps
Move `_tapComposedMsg` to the end of the encoded message in `encodeToeComposeMsg` as following:
`depositRepayAndRemoveCollateralFromMarket` function of MagnetarAssetModule can't be used on behalf of user
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Description
Functions of Magnetar are intended to be callable from whitelisted addresses on behalf of users. This serves purposes such as allowing TOFT or USDT contracts to execute Magnetar functions during lzReceive (receiving tokens cross-chain).

All Magnetar functions use `_checkSender` to allow whitelisted sender.
```solidity=
function _checkSender(address _from) internal view {
    if (_from != msg.sender && !cluster.isWhitelisted(0, msg.sender)) {
        revert Magnetar_NotAuthorized(msg.sender, _from);
    }
}
```
However, in the `depositRepayAndRemoveCollateralFromMarket` function of MagnetarAssetModule, it calls `_extractTokens` with the `msg.sender` address to pull tokens from the sender. Therefore, if `msg.sender` is different from data.user (the sender calling on behalf of the user), this function still pulls tokens from the sender. This behavior is incorrect, resulting in pulling tokens from the wrong address or reverting due to insufficient balance and allowance of `msg.sender`

```solidity=
function depositRepayAndRemoveCollateralFromMarket(DepositRepayAndRemoveCollateralFromMarketData memory data)
    public
    payable
{
    // Check sender
    _checkSender(data.user);

    ...

    // @dev deposit to YieldBox
    if (data.depositAmount > 0) {
        data.depositAmount = _extractTokens(msg.sender, assetAddress, data.depositAmount);
        IERC20(assetAddress).approve(address(_yieldBox), 0);
        IERC20(assetAddress).approve(address(_yieldBox), data.depositAmount);
        _yieldBox.depositAsset(assetId, address(this), address(this), data.depositAmount, 0);
    }

    ...
```
## Impact
Senders will be at risk of losses due to mistakenly pulling tokens or facing a DOS attack when calling `depositRepayAndRemoveCollateralFromMarket` on behalf of the user. This intended functionality will be broken.

## Tools Used
Manual review
## Recommended Mitigation Steps
Should `_extractTokens` from `data.user` instead of `msg.sender`
```solidity=
data.depositAmount = _extractTokens(msg.sender, assetAddress, data.depositAmount);
```


## Assessed type

Other
`_lockOnTOB` function of MagnetarMintCommonModule will not work due to the missing approved asset for YieldBox before depositing
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Description
In MagnetarMintCommonModule, the `_lockOnTOB` function is used to pull the singularity tokens from the user and lock them into the TapiocaOptionBroker contract.
```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);
        ...
    }
}
```
In the above code snippet, `_extractTokens` is used to pull singularity tokens from the user to this contract. Afterward, it will deposit these tokens into YieldBox to get YieldBox shares and then lock them in the TOB contract.

However, it misses approving Singularity tokens before depositing them into YieldBox. YieldBox will attempt to pull tokens from this contract (from == address(this)), so it will revert as YieldBox can't transfer tokens due to insufficient allowance during `yieldBox_.depositAsset()`.
## Impact
The functions of Magnetar which call `_lockOnTOB` will be broken, including the `mintBBLendSGLLockTOLP` function of MagnetarMintModule and the `lockAndParticipate` function of MagnetarMintXChainModule.
## Tools Used
Manual review
## Recommended Mitigation Steps
Should approve Singularity tokens before depositing them into YieldBox:
```solidity=
//deposit to YieldBox
_extractTokens(user, singularityAddress, fraction);
singularityAddress.safeApprove(address(yieldBox_), fraction);
yieldBox_.depositAsset(tOLPSglAssetId, address(this), address(this), fraction, 0);
```


## Assessed type

Other
`_lzCustomWithdraw` function of MagnetarBaseModule  will not send any tokens cross-chain
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/modules/MagnetarBaseModule.sol#L131-L171


# Vulnerability details

## Description
The `_withdrawToChain` function of MagnetarBaseModule is used to withdraw tokens from YieldBox shares existing in the contract and send them cross-chain to the user's recipient. This function has an `unwrap` option to unwrap tokens after sending cross-chain, using the `_lzCustomWithdraw` function.

However, the `_lzCustomWithdraw` function only executes the lz calldata with the token amount to send being zero.
```solidity=
function _lzCustomWithdraw(
    address _asset,
    LZSendParam memory _lzSendParam,
    uint128 _lzSendGas,
    uint128 _lzSendVal,
    uint128 _lzComposeGas,
    uint128 _lzComposeVal,
    uint16 _lzComposeMsgType
) private {
    PrepareLzCallReturn memory prepareLzCallReturn = _prepareLzSend(_asset, _lzSendParam, _lzSendGas, _lzSendVal);

    TapiocaOmnichainEngineHelper _toeHelper = new TapiocaOmnichainEngineHelper();
    PrepareLzCallReturn memory prepareLzCallReturn2 = _toeHelper.prepareLzCall(
        ITapiocaOmnichainEngine(_asset),
        PrepareLzCallData({
            dstEid: _lzSendParam.sendParam.dstEid,
            recipient: _lzSendParam.sendParam.to,
            amountToSendLD: 0,
            minAmountToCreditLD: 0,
            msgType: _lzComposeMsgType,
            composeMsgData: ComposeMsgData({
                index: 0,
                gas: _lzComposeGas,
                value: prepareLzCallReturn.msgFee.nativeFee.toUint128(),
                data: _lzSendParam.sendParam.composeMsg,
                prevData: bytes(""),
                prevOptionsData: bytes("")
            }),
            lzReceiveGas: _lzSendGas + _lzComposeGas,
            lzReceiveValue: _lzComposeVal
        })
    );

    if (msg.value < prepareLzCallReturn2.msgFee.nativeFee) {
        revert Magnetar_GasMismatch(prepareLzCallReturn2.msgFee.nativeFee, msg.value);
    }

    IOftSender(_asset).sendPacket{value: prepareLzCallReturn2.msgFee.nativeFee}(
        prepareLzCallReturn2.lzSendParam, prepareLzCallReturn2.composeMsg
    );
}
```

As you can see in the above code snippet, `prepareLzCallReturn` was prepared as calldata to send tokens without composing a message but has not been executed. It only executes `prepareLzCallReturn2`, which represents the lz calldata for a composed message. It is used for custom options after receiving tokens. 
However, the `amountToSendLD` of `prepareLzCallReturn2` is 0,  resulting in this call not sending any tokens cross-chain.

## Impact
Users will lose funds when they use a Magnetar function which triggers `_lzCustomWithdraw`. For example, using `withdrawToChain()` of MagnetarYieldBoxModule with the unwrap option (`data.unwrap` == true).
## Tools Used
Manual review
## Recommended Mitigation Steps
Should pass variable from data to `amountToSendLD` and `minAmountToCreditLD` to prepare calldata
Incorrect return value of function `BaseTapiocaOmnichainEngine._payNative()` 
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Description
According to the function `_payNative(_nativeFee)` described in the [LayerZero codebase](https://github.com/LayerZero-Labs/LayerZero-v2/blob/142846c3d6d51e3c2a0852c41b4c2b63fcda5a0a/oapp/contracts/oapp/OAppSender.sol#L93-L103), it is designed to return the native fee associated with the message. However, when a contract intends to initiate multiple LayerZero messages within a single transaction, more than just `_nativeFee` may be required from the sender to execute such messages.

The contract `BaseTapiocaOmnichainEngine()` facilitates multiple LayerZero messages within the Magnetar contract and the Tap token contract. Therefore, the function `_payNativeFee()` needs to be overridden to return an amount of native tokens greater than just `_nativeFee`. However, in the current implementation of the function `BaseTapiocaOmnichainEngine._payNative()`, it still returns the value of the input `_nativeFee`.

```solidity
/**
 * @inheritdoc OAppSender
 * @dev Overwrite to check for < values.
 */
function _payNative(uint256 _nativeFee) internal override returns (uint256 nativeFee) {
    if (msg.value < _nativeFee) revert NotEnoughNative(msg.value);
    return _nativeFee;
}
```

## Impact
As only `_nativeFee` will be sent along with the cross-chain message, the remaining amount `msg.value - _nativeFee` will become trapped in the `BaseTapiocaOmnichainEngine` contract. This amount can be larger than just the fee to execute the transaction since the `Magnetar` also supports the `LzComposeOption`, which defines the `msg.value` used to execute the compose option.

Due to the insufficient native tokens provided for the multiple LayerZero messages, certain functions cannot be executed (e.g., `MagnetarBaseModule._lzCustomWithdraw()`, `TapTokenReceiver._claimTwpTapRewardsReceiver()`, ...).

## Tools Used
Manual review

## Recommended Mitigation Steps
Consider modifying function `BaseTapiocaOmnichainEngine._payNative()` as follows: 
```solidity=
function _payNative(uint256 _nativeFee) internal override returns (uint256 nativeFee) {
    if (msg.value < _nativeFee) revert NotEnoughNative(msg.value);
    return msg.value;
}
```


## Assessed type

Context
Attacker can drain all tap token from victim's wallet using function `BaseTapiocaOmnichainEngine::transferFrom()`
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Description
The function `BaseTapiocaOmnichainEngine::transferFrom()` is a modified version of the well-known function `ERC20::transferFrom()` with slight alterations to accommodate the `pearlmit` contract.

```solidity
function transferFrom(address from, address to, uint256 value) public virtual override returns (bool) {
    address spender = _msgSender();
    // If the allowance on this contract is not met, attempt a transferFrom via Pearlmit.
    if (allowance(from, spender) < value) {
        // _transfer(from, to, value);
        bool isErr = pearlmit.transferFromERC20(from, to, address(this), value);
        if (isErr) revert BaseTapiocaOmnichainEngine_NotValid();
    } else {
        // If the allowance on this contract is met, carry out a normal transferFrom.
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
    }

    return true;
}
```

In the scenario where the allowance of the `from` address with respect to the `msg.sender` is less than the transfer amount `value`, the function invokes `pearlmit.transferFromERC20(from, to, address(this), value)` to execute the token transfer. However, a vulnerability arises as there is no check for permission between the `from` address and the `msg.sender`.

The attacker can arbitrarily set the `from` address to one that has:
- [Approved](https://github.com/limitbreakinc/PermitC/blob/d659082456d8152931c1dc677eaac8ea6ecb8f07/src/PermitC.sol#L135-L142) the contract `BaseTapiocaOmnichainEngine` in the pearlmit contract, and
- `BaseTapiocaOmnichainEngine.allowance(from, pearlmit) > 0`.

Additionally, the attacker can set the `to` address to their own address. By doing so, all tokens from the `from` address will be stolen by the attacker.

## Impact
Attacker can steal Tap tokens from other users 

## Tools Used
Manual review

## Recommended Mitigation Steps
Consider removing the logic with the pearlmit contract in the function `transferFrom()` 


## Assessed type

Access Control