xanoutas/solidity-security-findings huggingface dataset:
`YieldBoxPermit#permit`, `YieldBoxPermit#permitAll` are vulnerable to cross chain replay attacks
Audit: 2023-07-tapioca-findings
Severity: HIGH

# Lines of code

 https://github.com/Tapioca-DAO/YieldBox/blob/f5ad271b2dcab8b643b7cf622c2d6a128e109999/contracts/YieldBoxPermit.sol#L53
 https://github.com/Tapioca-DAO/YieldBox/blob/f5ad271b2dcab8b643b7cf622c2d6a128e109999/contracts/YieldBoxPermit.sol#L82


# Vulnerability details

## Impact

The `YieldBox` contract is going to be deployed on multiple chains ([confirmed in contest discord channel](https://discordapp.com/channels/810916927919620096/1125494082375000244/1131670307279745125)). Since `block.chainid` is not used anywhere in the signature, the functions `permit` and `permitAll` are vulnerable to cross chain replay attacks.

## Proof of Concept

A replay attack occurs when an attacker is able to copy a valid transaction involving another users signature, and send it to the network again. It is for this reason that nonces are used. However when the same contract exists on multiple chains, since the nonces are handled separately it is possible for a transaction on one chain to be replayed by attacker on another chain. For more info on cross chain replay attacks, see [this medium article](https://medium.com/coinmonks/what-the-heck-is-replay-protection-aae910f2a3cb). The fact that the `YieldBox` contract is intended to be deployed to potentially ["dozens of networks"](https://docs.tapioca.xyz/tapioca/) significantly increases the attack surface.

Consider the following example:
- Alice calls `permit` where owner=Alice and spender=Bob on mainnet, using the first nonce
- Bob calls `permit` where owner=Alice and spender=Bob and the same assetId and deadline as Alice's initial mainnet call, but on Arbitrum L2 instead
- The call will succeed, as the signatures will be identical (assuming Alice has not yet increased her nonce on Arbitrum)
- Bob has successfully allowed himself to spend Alice's tokens on Arbitrum, despite Alice only intending for Bob to spend her tokens on mainnet

## Tools Used

Manual review

## Recommended Mitigation Steps

Implement `block.chainid` in the calculation of `structHash`.

```diff
    function permit(
        address owner,
        address spender,
        uint256 assetId,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(block.timestamp <= deadline, "YieldBoxPermit: expired deadline");

-       bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, assetId, _useNonce(owner), deadline));
+       bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, assetId, _useNonce(owner), deadline, block.chainid));

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSA.recover(hash, v, r, s);
        require(signer == owner, "YieldBoxPermit: invalid signature");

        _setApprovalForAsset(owner, spender, assetId, true);
    }
```

```diff
    function permitAll(
        address owner,
        address spender,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32
`LidoEthStrategy#emergencyWithdraw`: hardcoded `minAmount` calculation allows theft of user funds through MEV
Audit: 2023-07-tapioca-findings
Severity: HIGH

# Lines of code

 https://github.com/Tapioca-DAO/tapioca-yieldbox-strategies-audit/blob/05ba7108a83c66dada98bc5bc75cf18004f2a49b/contracts/lido/LidoEthStrategy.sol#L108


# Vulnerability details

## Impact

The `emergencyWithdraw` function in `LidoEthStrategy` uses a hardcoded `minAmount` value to define slippage tolerance when swapping using Curve's ETH/stETH pool. This value is intended to be 0.5% (inline comment), however it is written `(toWithdraw * 50) / 10_000` instead of `toWithdraw - (toWithdraw * 50) / 10_000`, which instead allows for 99.5% slippage. This provides essentially zero slippage protection and puts practically all of the funds being withdrawn at risk of MEV sandwich attacks. Since `emergencyWithdraw` always withdraws the strategy's entire balance, this puts all users funds at risk.

## Proof of Concept

```solidity
File: tapioca-yieldbox-strategies-audit\contracts\lido\LidoEthStrategy.sol

104:     function emergencyWithdraw() external onlyOwner returns (uint256 result) {
105:         compound("");
106: 
107:         uint256 toWithdraw = stEth.balanceOf(address(this));
108:         uint256 minAmount = (toWithdraw * 50) / 10_000; //0.5%  @audit incorrect calculation
109:         result = curveStEthPool.exchange(1, 0, toWithdraw, minAmount);
110: 
111:         INative(address(wrappedNative)).deposit{value: result}();
112:     }
```

Example: if the contract holds 1000 stETH, then `toWithdraw = 1000e18` and

    minAmount = (1000e18 * 50) / 10_000
              = 5e18
              = 5 ETH

The transaction would succeed even if the swap results in the contract receiving as little as 5 ETH from 1000 stETH input.

## Tools Used

Manual review

## Recommended Mitigation Steps

Correct the calculation on line 108:

```diff
    function emergencyWithdraw() external onlyOwner returns (uint256 result) {
        compound("");

        uint256 toWithdraw = stEth.balanceOf(address(this));
-       uint256 minAmount = (toWithdraw * 50) / 10_000; //0.5%
+       uint256 minAmount = toWithdraw - (toWithdraw * 50) / 10_000; //0.5%
        result = curveStEthPool.exchange(1, 0, toWithdraw, minAmount); // @audit 0 native, 1 stETH

        INative(address(wrappedNative)).deposit{value: result}();
    }
```


## Assessed type

MEV
EXCESSIVE VESTED TOKENS CAN GET LOCKED IN THE CONTRACT
Audit: 2023-07-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tap-token-audit/blob/main/contracts/Vesting.sol#L153-L158
https://github.com/Tapioca-DAO/tap-token-audit/blob/main/contracts/Vesting.sol#L112-L117


# Vulnerability details

## Impact

The `Vesting.registerUser` function is used to register a new user to the `Vesting` contract. Each of the vested amount for each new user is accumulated and cached into the `_totalAmount` state variable. After all the users are registered the `Vesting.init` function is called to initialize the contract. The `seeded` variable is set to the `_seededAmount` input parameter after the following two conditional checks have passed.

        if (_seededAmount == 0) revert NoTokens();
        if (_totalAmount > _seededAmount) revert NotEnough();

The issue here is that `_seededAmount > _totalAmount` scenario can occur thus passing the above `require` check.

Now let's consider the following scenario:

1. There are 3 users (A, B, C) who are registered in the vested contract.
2. Following are vested amounts for each of the users.
    A = 50
    B = 30
    C = 20
3. Let's assume the Cliff is 3 days and the duration is 5 days.    
4. Now the `_totalAmount = 100` (cumulative sum of the three users)
5. The `onlyOwner` calls the `Vesting.init` function and sets the `_seededAmount = 150`. Now both `_seededAmount == 0` and `_totalAmount > _seededAmount` will be false. 
6. And let's assume that `availableToken < _seededAmount` check will also be false since the `_token.balanceOf(address(this)) = 160`.
7. And the `init` function execution will proceed without revert.
6. Let's assume all the users claim after the `duration` has passed.
7. They call the `Vesting.claim` function and receive their vested amounts (50, 30, 20) respectively.
8. Hence the `_totalClaimed` will be `100` now.
9. Once these respective users have claimed there is still a balance left in the contract as shown below:
    _token.balanceOf(address(this)) - _totalClaimed = 160 - 100 = 60
10. These `60` tokens will be locked in the contract since none of the registered users can claim no more.
11. And there is no functionality with in the contract scope that can retrieve these locked funds out of the contract balance.

## Proof of Concept

```solidity
        if (_seededAmount == 0) revert NoTokens();
        if (_totalAmount > _seededAmount) revert NotEnough();

        token = _token;
        uint256 availableToken = _token.balanceOf(address(this));
        if (availableToken < _seededAmount) revert BalanceTooLow();
```

https://github.com/Tapioca-DAO/tap-token-audit/blob/main/contracts/Vesting.sol#L153-L158

```solidity
        uint256 _claimable = claimable(msg.sender);
        if (_claimable == 0) revert NothingToClaim();

        _totalClaimed += _claimable;
        users[msg.sender].claimed += _claimable;
        users[msg.sender].latestClaimTimestamp = block.timestamp;
```

https://github.com/Tapioca-DAO/tap-token-audit/blob/main/contracts/Vesting.sol#L112-L117

##
TapiocaOptionBroker: An attacker can make epochs to distribute 0 TAP
Audit: 2023-07-tapioca-findings
Severity: HIGH

# Lines of code

https://github.com/Tapioca-DAO/tap-token/blob/964a1990adf6ea12e6b14ad0813964bd83c2439d/contracts/options/TapiocaOptionBroker.sol#L443


# Vulnerability details

## Impact

The `newEpoch` function in the TapiocaOptionBroker is used to advance epoch and to set the total tap tokens the current epoch will distribute. To compute the tap for the current epoch, the function calls the `emitForWeek` of the `tapOFT` contract:

```solidity
// Extract TAP
uint256 epochTAP = tapOFT.emitForWeek();
_emitToGauges(epochTAP);
```

Whatever the `emitForWeek` function returns it is set as the gauge amount to distribute in the current epoch. The issue is that an attacker can make the `emitForWeek` function to return zero, avoiding the `TapiocaOptionBroker` to set the correct emission amount.

## Proof of Concept

The root cause is that the function `emitForWeek` is open to be called by anyone and when the emission for a week is already set, then the function returns zero:

```solidity
uint256 week = _timestampToWeek(block.timestamp);
if (emissionForWeek[week] > 0) return 0;
```

So, a malicious user will call `emitForWeek` at the beginning of every block when `_timestampToWeek(block.timestamp)` returns a different week and set the emission for that week before the `TapiocaOptionBroker` contract.

## Tools Used

Manual Review

## Recommended Mitigation Steps

Two options:

- 1. Do not make the function `emitForWeek` open to anyone, apply a check that only the `TapiocaOptionBroker` can call this function in the `TapOFT` contract. 

- 2. Change the `emitForWeek` function to:

```solidity
function emitForWeek() external notPaused returns (uint256) {
        require(_getChainId() == governanceChainIdentifier, "chain not valid");

        uint256 week = _timestampToWeek(block.timestamp);
        // if (emissionForWeek[week] > 0) return 0;
        // @audit FIX: return the current emission for the current week.
        if (emissionForWeek[week] > 0) return emissionForWeek[week];

        // Update DSO supply from last minted emissions
        dso_supply -= mintedInWeek[week - 1];

        // Compute unclaimed emission from last week and add it to the current week emission
        // @audit It is possible for mintedInWeek to be greater than emissionForWeek?.
        uint256 unclaimed = emissionForWeek[week - 1] - mintedInWeek[week - 1];
        uint256 emission = uint256(_computeEmission());
        emission += unclaimed;
        emissionForWeek[week] = emission;

        emit Emitted(week, emission);

        return emission;
    }
```





## Assessed type

Access Control
USDT approval racing can lead to DoS
Audit: 2023-07-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph-audit/blob/023751a4e987cf7c203ab25d3abba58f7344f213/contracts/Magnetar/modules/MagnetarMarketModule.sol#L157


# Vulnerability details

## Impact
USDT approval racing can lead to DoS

## Proof of Concept
2023-07-tapioca/tapioca-periph-audit/contracts/Magnetar/modules/MagnetarMarketModule.sol#L157

## Tools Used

## Recommended Mitigation Steps
Use safeApprove


## Assessed type

call/delegatecall
Balancer swap fee is not set and quote properly when Rebalancing
Audit: 2023-07-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapiocaz-audit/blob/bcf61f79464cfdc0484aa272f9f6e28d5de36a8f/contracts/Balancer.sol#L322


# Vulnerability details

## Impact

Rebalance may revert

## Proof of Concept

The rebalance functon tightly integrate with stargate and layezero

https://stargateprotocol.gitbook.io/stargate/developers/cross-chain-swap-fee

however, the swap fee is not quoted and calculated 

the swap 

https://github.com/Tapioca-DAO/tapiocaz-audit/blob/bcf61f79464cfdc0484aa272f9f6e28d5de36a8f/contracts/Balancer.sol#L321

```solidity
    erc20.approve(address(router), _amount);
        router.swap(
            _dstChainId,
            _srcPoolId,
            _dstPoolId,
            _oft, //refund,
            _amount,
            _computeMinAmount(_amount, _slippage),
            _lzTxParams,
            _lzTxParams.dstNativeAddr,
            "0x"
        );
```

the user can easily either underpay the fee and swap does not go through or overpay the swap fee because lack of swap fee estimation

## Tools Used

Manual Review

## Recommended Mitigation Steps


## Assessed type

Token-Transfer
Direct claim of convex rewards causes rewards to get stuck
Audit: 2023-07-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-yieldbox-strategies-audit/blob/05ba7108a83c66dada98bc5bc75cf18004f2a49b/contracts/convex/ConvexTricryptoStrategy.sol#L272-L284


# Vulnerability details

## Impact
`ConvexTriCryptoStrategy` does not take into account that rewards from Convex can be claimed directly on behalf of any address. All rewards that get into the strategy contract this way will get stuck and compounding of yield will be denied.

## Proof of Concept
The `ConvexTriCryptoStrategy` allows the claim and conversion of Convex rewards in its `compound` function:

```js
    function compound(bytes memory data) public {
        if (data.length == 0) return;
        (uint256[] memory rewards, address[] memory tokens) = _executeClaim(
            data
        );

        //iterate through rewards and swap to wETH ...
```

The `_executeClaim` function performs the claim and then checks the balance difference of the contract before and after to calculate the rewards:

```js

//parse input and check balances before
...

zap.claimRewards(
    tempData.rewardContracts,
    tempData.extraRewardContracts,
    tempData.tokenRewardContracts,
    tempData.tokenRewardTokens,
    extrasTempData.depositCrvMaxAmount,
    extrasTempData.minAmountOut,
    extrasTempData.depositCvxMaxAmount,
    extrasTempData.spendCvxAmount,
    extrasTempData.options
);

uint256[] memory balancesAfter = new uint256[](tempData.tokens.length);
for (uint256 i = 0; i < tempData.tokens.length; i++) {
    balancesAfter[i] = IERC20(tempData.tokens[i]).balanceOf(
        address(this)
    );
}

uint256[] memory finalBalances = new uint256[](tempData.tokens.length);
for (uint256 i = 0; i < tempData.tokens.length; i++) {
    finalBalances[i] = balancesAfter[i] - balancesBefore[i];
}

return (finalBalances, tempData.tokens);

```

The `ClaimZap` contract ([Link](https://github.com/convex-eth/platform/blob/main/contracts/contracts/ClaimZap.sol)) called here is from Convex and allows a batch claim of rewards from `rewardContracts`, `extraRewardContracts` and `tokenRewardContracts`:

```js
for(uint256 i = 0; i < rewardContracts.length; i++){
    IBasicRewards(rewardContracts[i]).getReward(msg.sender,true);
}
//claim from extra rewards
for(uint256 i = 0; i < extraRewardContracts.length; i++){
    IBasicRewards(extraRewardContracts[i]).getReward(msg.sender);
}
```

Looking at the `getReward` functions ([here](https://github.com/convex-eth/platform/blob/main/contracts/contracts/BaseRewardPool.sol) and [here](https://github.com/convex-eth/platform/blob/main/contracts/contracts/VirtualBalanceRewardPool.sol)), it can be seen that anyone is allowed to claim for any address:

```js
//BaseRewardPool (rewardContract)
function getReward(address _account, bool _claimExtras) public updateReward(_account) returns(bool){
    uint256 reward = earned(_account);
    if (reward > 0) {
        rewards[_account] = 0;
        rewardToken.safeTransfer(_account, reward);
        IDeposit(operator).
Add access control to inti constructor like function
Audit: 2023-07-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/bigBang/BigBang.sol#L101


# Vulnerability details

## Impact
Not adding access control to init function would allow for frontrunning and injection of malicious code

## Proof of Concept
 event MinDebtRateUpdated(uint256 oldVal, uint256 newVal);
  /// @notice event emitted when the maximum debt rate is updated
  event MaxDebtRateUpdated(uint256 oldVal, uint256 newVal);
  /// @notice event emitted when the debt rate against the main market is updated
  event DebtRateAgainstEthUpdated(uint256 oldVal, uint256 newVal);

  constructor() MarketERC20("Tapioca BigBang") {}

  /// @notice The init function that acts as a constructor
  // @audit-issue M-01 Use this in constuctor or add access control to avoid front running and adding malicious contracts
  function init(bytes calldata data) external onlyOnce {
    (
      IPenrose tapiocaBar_,
      IERC20 _collateral,
      uint256 _collateralId,
      IOracle _oracle,
      uint256 _exchangeRatePrecision,
      uint256 _debtRateAgainstEth,
      uint256 _debtRateMin,
      uint256 _debtRateMax,
      uint256 _debtStartPoint


## Tools Used
Remix, Hardhat
## Recommended Mitigation Steps
Add proper access control


## Assessed type

Access Control
Loss of Funds when user wants to repay debt and underflow in _repay () function
Audit: 2023-07-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/singularity/SGLLendingCommon.sol#L91
https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/Market.sol#L56-L57
https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/singularity/SGLBorrow.sol#L43


# Vulnerability details

## Impact



There is no check for if userBorrowPart[to] is greater than or equal to part before subtraction which can lead to loss of funds for user or underflow, incase a user inputs more amount than the user is in debt for.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
 /// @param part The amount to repay. See `userBorrowPart`.


 /// @notice borrow amount per user
    mapping(address => uint256) public userBorrowPart;



   function _repay(
        address from,
        address to,
        bool skim,
        uint256 part
    ) internal returns (uint256 amount) {
        (totalBorrow, amount) = totalBorrow.sub(part, true);

        userBorrowPart[to] -= part;

        uint256 share = yieldBox.toShare(assetId, amount, true);
        uint128 totalShare = totalAsset.elastic;
        _addTokens(from, to, assetId, share, uint256(totalShare), skim);

if a user wants to repay a loan there's no check for if the amount the user inputs is not greater than the amount the user borrowed, which can lead to an underflow or loss of asset for user.
## Tools Used
Manual review
## Recommended Mitigation Steps
Add a check to make sure userBorrowPart[to] is greater than or equal to part before the subtraction operation happens. 


## Assessed type

Under/Overflow
Yearn Stragety tolerant 0 loss, which is too strict and can block withdraw
Audit: 2023-07-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-yieldbox-strategies-audit/blob/05ba7108a83c66dada98bc5bc75cf18004f2a49b/contracts/yearn/YearnStrategy.sol#L105


# Vulnerability details

## Impact

Yearn Stragety tolerant 0 loss, which is too strict

## Proof of Concept

When withdraw from [Yearn Stragety](https://github.com/Tapioca-DAO/tapioca-yieldbox-strategies-audit/blob/05ba7108a83c66dada98bc5bc75cf18004f2a49b/contracts/yearn/YearnStrategy.sol#L105)

```
result = vault.withdraw(toWithdraw, address(this), 0);
```

https://github.com/yearn/yearn-vaults/blob/97ca1b2e4fcf20f4be0ff456dabd020bfeb6697b/contracts/Vault.vy#L1074

```solidity
  @param maxLoss
        The maximum acceptable loss to sustain on withdrawal. Defaults to 0.01%.
        If a loss is specified, up to that amount of shares may be burnt to cover losses on withdrawal.
```

Yearn finance may implement yield farming stragety (such as lending / borrow in external platform) and subject to loss 

settting 0 acceptable loss is too strict and can block withdraw if the yearn vault has loss

## Tools Used

Manual Review

## Recommended Mitigation Steps

do not hardcode 0 accetpable loss and make this parameter passable as function parameter


## Assessed type

Token-Transfer
Incorrect parameter for allowedBorrow when repaying
Audit: 2023-07-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-bar-audit/blob/2286f80f928f41c8bc189d0657d74ba83286c668/contracts/markets/bigBang/BigBang.sol#L268


# Vulnerability details

## Impact

Incorrect parameter for `allowedBorrow` check during repayment in `BigBang` requires an approval that is orders of magnitudes higher than the intended amount (if Alice wants to allow Bob to use their funds). This can be abused by Bob to take more collateral or borrow more USDO on Alices behalf than what she intended.

## Proof of Concept

The function `_allowedBorrow` is used to check if an address has given another address the allowance to spend its tokens.It expects a share value as second argument:

```js
function _allowedBorrow(address from, uint share) internal {
    if (from != msg.sender) {
        if (allowanceBorrow[from][msg.sender] < share) {
            revert NotApproved(from, msg.sender);
        }
        allowanceBorrow[from][msg.sender] -= share;
```

Looking at the different usages in `BigBang` it is evident that this is meant to refer to a value expressed in `userCollateralShare`, for example in `addCollateral`:

```js
function addCollateral(
    ...
    uint256 share
) public allowedBorrow(from, share) notPaused {
    _addCollateral(from, to, skim, amount, share);
}

function _addCollateral(
    ...
    uint256 share
) internal {
    if (share == 0) {
        share = yieldBox.toShare(collateralId, amount, false);
    }
    userCollateralShare[to] += share;
```

The usage of `_allowedBorrow` is consistent throughout the `BigBang` contract, with the exception of `repay`:

```js
/// @param part The amount to repay. See `userBorrowPart`
function repay(
    ...
    uint256 part
) public notPaused allowedBorrow(from, part) returns (uint256 amount) {
```

Here the second argument `part` refers to a users debt expressed in `userBorrowPart`. Since user `userCollateralShare` and `userBorrowPart` have different underlying assets, they will also differ in their order of magnitude. For example Alice wants to allow Bob to repay 100 "parts" of his debt via her assets. This would be roughly 100 USDO (depending on accrued interest). She now gives an allowance of 100e18 to Bob. But this would also allow Bob to use 100e18 worth of Alices `userCollateralShare` in other actions, which could for example be ETH.

## Tools Used

Manual Review

## Recommended Mitigation Steps

The `borrow` function converts the USDO amount into an amount expressed in `userCollateralShare`:

```js
uint256 allowanceShare = _computeAllowanceAmountInAsset(
    from,
    exchangeRate,
    amount,
    asset.safeDecimals()
);
_allowedBorrow(from, allowanceShare);
```

This logic should be used here as well.


## Assessed type

Other