xanoutas/solidity-security-findings huggingface dataset:
max magnitude lock check will lead to a DoS and possible monopolization of gov/Option on twTAP/TapiocaOptionBroker
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/options/TapiocaOptionBroker.sol#L261


# Vulnerability details

There is a max magnitude lock check in the `twTAP.participate` function, it can't be greater than 4x the cumulative:
```
    if (magnitude >= pool.cumulative * 4) revert NotValid();
```

Because the param `_duration` can't be less than `EPOCH_DURATION = 7 days = 604800`, lets assume pool.cumulative is x, and solve for its minimum value without DoS:
```
(604800 * 604800 + x * x)**0.5 - x < 4 * x
```
Solution derived: the x, pool.cumulative, must be >= 123455 .

The main issue is that, when a user participates the lock with `divergenceForce = false`, his participation will decrease pool.cumulative. Meanwhile, if there are positions previously entered with `divergenceForce = true` that are now exiting, then pool.cumulative will be reduced to below 123455.

## Impact
TAP cannot be staked until pool.cumulative rises to 123455, and the system will remain under the DoS for even months until positions with divergenceForce = false can exit(arrive at the unlocking time). During this DoS period, the attacker has the potential to monopolize gov.

The same issue is also in the TapiocaOptionBroker. The following poc uses the twTap as the exploit case.

## Proof of Concept 
Flow of attack:
1. The user#1 locked some TAP(with hasVotingPower = true) for a duration = 1004800 (11.6 days)
2. After 11.6 days and before the user#1 unlocks, the user#2 lock some TAP(with hasVotingPower = true) for a duration = 1172776 (13.5 days)
3. The user#1 unlocks his twTap position.
4. After this unlocking, the `pool.cumulative` will be 77922, which is < 123455.
So the twTAP will be DoS until the user#2(attacker) unlocks, which has to wait about 13.5 days. During this DoS period, the attacker is the only voter of the gov, the attack could have complete control of gov.

```python
def computeMagnitude(_duration, cumulative):
    return int((_duration**2 + cumulative**2)**0.5) - cumulative

def assetValid4(_magnitude, _cumulative, tag):
    # print(_magnitude, _cumulative * 4)
    assert _magnitude < _cumulative * 4, f"Not Valid here #{tag}"

def update(_duration, _averageMagnitude, _cumulative, _NO):
    print(f"======{_NO}======")
    magnitude0 = computeMagnitude(_duration, _cumulative)
    print("duration, cumulative_before", _duration, _cumulative)
    assetValid4(magnitude0, _cumulative, _NO)
    _averageMagnitude = int((_averageMagnitude + magnitude0) / _NO)
    if _duration >= _cumulative:
        print("cumulative_delta", _averageMagnitude)
        _cumulative += _averageMagnitude
    else:
        print("cumulative_delta", -_averageMagnitude)
        _cumulative -= _averageMagnitude
    print("averageMagnitude,cumulative_after", _averageMagnitude, _cumulative)
    return _averageM
gov(twTAP) and Tapioca Option can be monopolized by an attacker
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/governance/twTAP.sol#L320-L337
https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/governance/twTAP.sol#L592-L594


# Vulnerability details

The function `twTAP._releaseTap` updates only `--twAML.totalParticipants` and neglected to update `twAML.averageMagnitude`. This will result in twAML.averageMagnitude accumulating every time a new position participates, without ever decreasing.
```solidity
            pool.averageMagnitude = (pool.averageMagnitude + magnitude) / pool.totalParticipants; // compute new average magnitude
```

## Impact
An attacker can monopolize the gov by precisely controlling the number of tokens entering and exiting to ensure that only their own position remains in the current twTap.

On the other hand, there is another exploit way here. If pool.cumulative < pool.averageMagnitude, then pool.cumulative will be set to 0. But the greater `position.averageMagnitude` will be added to pool.cumulative when the position exits. This will cause pool.cumulative to continuously increase,  which will result in the efficiency of twTAP/TapiocaOptionBroker becoming increasingly lower. Because the `multiplier` of the twTAP or `target` of the TapiocaOptionBroker will always be `dMIN`.

The same issue is also in the TapiocaOptionBroker. The only difference is that the `pool.cumulative` can't be decreased to zero, because it will be reset to `EPOCH_DURATION` if its zero in https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/options/TapiocaOptionBroker.sol#L242-L244 . The following poc uses the twTap as the exploit case.

## Proof of Concept 
We describe this attack using the simplest scenario, assuming that initially, the pool contains only the attacker's own position, but the `pool.averageMagnitude` is not zero(which means there were some participators before).
1. The attacker first needs to create a positions with `divergenceForce = true`, and `magnitude = M1 = current pool.averageMagnitude`. After the participation, the pool.averageMagnitude will be pool.averageMagnitude +M1.
2. Waiting for the unlock time and exits the above position. After the exit, pool.averageMagnitude remains unchanged(pool.averageMagnitude +M1), but the pool.totalParticipants is back to 0.
3. The attacker repeats the above process, which results in pool.averageMagnitude increasing by M1 each time.
4. Before the last unlock, the `pool.cumulative` and `pool.averageMagnitude` now are very large values, the attacker participates  with `divergenceForce = false` for a couple of months.
5. Then the attacker exits his last position with `divergenceForce = true`. It will make `pool.cumulative = 0;`, which breaks the developers' assumptions about this branch:
https://github.com/Tapioca-DAO/tap-token/blob/20a83b1d2d5577653610a6c3879dff9df4968345/contracts/governance/twTAP.sol#L33
`MagnetarHelper.getFractionForAmount` uses the wrong rounding, and will yield the wrong result
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/MagnetarHelper.sol#L208-L219


# Vulnerability details

### Impact

`MagnetarHelper.getFractionForAmount` is used to determine the `fraction` (shares) that will be received when an `amount` is deposited.

The code needs to compute the totalShares and then determine what the fraction is going to be.

The logic is used in `Singularity._removeAsset` and since it's tied to a withdrwal, the rounding will be `down`

However, in `MagnetarHelper` the round is `up`

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/MagnetarHelper.sol#L208-L219

```solidity
    function getFractionForAmount(ISingularity singularity, uint256 amount) external view returns (uint256 fraction) {
        (uint128 totalAssetShare, uint128 totalAssetBase) = singularity.totalAsset();
        (uint128 totalBorrowElastic,) = singularity.totalBorrow();
        uint256 assetId = singularity.assetId();

        IYieldBox yieldBox = IYieldBox(singularity.yieldBox());

        uint256 share = yieldBox.toShare(assetId, amount, false);
        uint256 allShare = totalAssetShare + yieldBox.toShare(assetId, totalBorrowElastic, true); /// @audit Round UP for Debt

        fraction = allShare == 0 ? share : (share * totalAssetBase) / allShare;
    }
```

See the actual implementation in `Singularity`

https://github.com/Tapioca-DAO/Tapioca-bar/blob/c2031ac2e2667ac8f9ac48eaedae3dd52abef559/contracts/markets/singularity/SGLCommon.sol#L199-L216

```solidity
    function _removeAsset(address from, address to, uint256 fraction) internal returns (uint256 share) {
        if (totalAsset.base == 0) {
            return 0;
        }
        Rebase memory _totalAsset = totalAsset;
        uint256 allShare = _totalAsset.elastic + yieldBox.toShare(assetId, totalBorrow.elastic, false);
        share = (fraction * allShare) / _totalAsset.base;
    }
```

This will cause issues when calculating how much fraction to withdraw based on the amount required by the withdrawer


### Mitigation 

Change the rounding direction 

```solidity
        uint256 allShare = totalAssetShare + yieldBox.toShare(assetId, totalBorrowElastic, down);
```


## Assessed type

Math
`MagnetarAssetModule` `_extractTokens` uses `msg.sender` instead of `data.user`
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/modules/MagnetarAssetModule.sol#L91-L92


# Vulnerability details

### Impact

All calls to `_extractTokens` in the codebase use `data.user` which is sanizitized via `_checkSender`

This instance uses `msg.sender` meaning that some functionality will not be available, for example this won't work via `composeCall`

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/modules/MagnetarAssetModule.sol#L91-L92

```solidity
        if (data.depositAmount > 0) { /// @audit Prob invalid if caller is not data.user
            data.depositAmount = _extractTokens(msg.sender, assetAddress, data.depositAmount);
```


### Mitigation

Change
```solidity
_extractTokens(msg.sender, assetAddress, data.depositAmount);
```

to

```solidity
_extractTokens(data.user, assetAddress, data.depositAmount);
```


## Assessed type

Invalid Validation
Incorrect Rounding will cause `data.removeAndRepayData.removeAssetFromSGL` to send back less than intended
Audit: 2024-02-tapioca-findings
Severity: MEDIUM

# Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/modules/MagnetarOptionModule.sol#L151-L167


# Vulnerability details

When withdrawing from Yieldbox, the `shares` to burn are rounded up.

https://github.com/boringcrypto/YieldBox/blob/cd62ffc4b003e4de428b45169010e94b523caef4/contracts/YieldBox.sol#L212-L216

```solidity
    function withdraw(
        uint256 assetId,
        address from,
        address to,
        uint256 amount,
        uint256 share
    ) public allowed(from) returns (uint256 amountOut, uint256 shareOut) {
        // Checks
        Asset storage asset = assets[assetId];
        require(asset.tokenType != TokenType.Native, "YieldBox: can't withdraw Native");

        // Effects
        uint256 totalAmount = _tokenBalanceOf(asset);
        if (share == 0) {
            // value of the share paid could be lower than the amount paid due to rounding, in that case, add a share (Always round up)
            share = amount._toShares(totalSupply[assetId], totalAmount, true);
        } else {
```

In the case of `data.removeAndRepayData.removeAssetFromSGL`, the rounding is done in the wrong direction:

https://github.com/Tapioca-DAO/tapioca-periph/blob/2ddbcb1cde03b548e13421b2dba66435d2ac8eb5/contracts/Magnetar/modules/MagnetarOptionModule.sol#L151-L167

```solidity
if (data.removeAndRepayData.removeAssetFromSGL) { ///???
            uint256 _assetId = singularity_.assetId();
            uint256 share = yieldBox_.toShare(_assetId, _removeAmount, false); /// @audit Incorrect Rounding

            address removeAssetTo = data.removeAndRepayData.assetWithdrawData.withdraw
                || data.removeAndRepayData.repayAssetOnBB ? address(this) : data.user;

            singularity_.removeAsset(data.user, removeAssetTo, share); /// @audit Less tokens will be sent

            //withdraw
            if (data.removeAndRepayData.assetWithdrawData.withdraw) {
                uint256 computedAmount = yieldBox_.toAmount(_assetId, share, false);
                data.removeAndRepayData.assetWithdrawData.lzSendParams.sendParam.amountLD = computedAmount;
                data.removeAndRepayData.assetWithdrawData.lzSendParams.sendParam.minAmountLD = computedAmount;
                _withdrawToChain(data.removeAndRepayData.assetWithdrawData);
            }
        }
```

This will cause withdrawals to withdraw a smaller amount than intended

### Mitigation 

Change
```solidity
uint256 share = yieldBox_.toShare(_assetId, _removeAmount, false); /// @audit Incorrect Rounding
```

To

```solidity
uint256 share = yieldBox_.toShare(_assetId, _removeAmount, true);
```



## Assessed type

Error
Upgraded Q -> 3 from #146 [1706014097180]
Audit: 2023-12-autonolas-findings
Severity: HIGH

Judge has assessed an item in Issue #146 as 3 risk. The relevant finding follows:

 L-6: OLAS minting via treasury is not guaranteed
The OLAS.mint(…) method does not revert if the requested amount cannot be limited due to the inflation limit:
    function mint(address account, uint256 amount) external {
        // Access control
        if (msg.sender != minter) {
            revert ManagerOnly(msg.sender, minter);
        }

        // Check the inflation schedule and mint
        if (inflationControl(amount)) {
            _mint(account, amount);
        }
    }
The treasury itself correctly accounts for the OLAS inflation limit and thereby guarantees the minting of the the requested OLAS amount here and here.
However, the minter of OLAS can be changed via changeMinter(address newMinter) by the owner.
Then, the new minter can mint OLAS up to the inflation limit.
Next, the minter is changed back to the treasury contract.
Now, the minting via treasury (see references in step 2) will fail to mint enough OLAS without any error.
Functions that send Ether to arbitrary destinations
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/registries/contracts/multisigs/GnosisSafeMultisig.sol#L106C7-L106C101?plain=1#L1


# Vulnerability details

## Impact
Unprotected call to a function sending Ether to an arbitrary address.

## Dangerous calls:
GnosisSafe.handlePayment(uint256,uint256,uint256,address,address) 

## Tools Used
Manual Auditing line by line

## Recommended Mitigation Steps
Ensure that an arbitrary user cannot withdraw unauthorized funds.


## Assessed type

call/delegatecall
claimOwnerIncentives x depositServiceDonationsETH x checkpoint   x-entrancy Attack to get instant topups
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Treasury.sol#L406https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Treasury.sol#L257
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Dispenser.sol#L104
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Treasury.sol#L298
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L1067


# Vulnerability details

## Impact
Double Topup via `claimOwnerIncentives`-> `depositServiceDonationsETH` -> `checkpoint` -> `claimOwnerIncentives` loop


## Proof of Concept
-> Deploys Attacking Smart Contract
-> `Executes 1st transaction` via `Attacking Contract` to trigger `depositServiceDonationsETH`
-> `Execute 2nd transaction` via `Attacking Contract` to trigger:
- `claimOwnerIncentives`     //  for 1st topup [ 1 ]
- `depositServiceDonationsETH` on fallback     // to update service lastespoch & pendingTopup [ 2 ] 
- `checkpoint` on fallback     //to update epoch [ 3 ] 
- `1st` `claimOwnerIncentives` completes
- Call `claimOwnerIncentives` again     //for 2nd topup [ 4 ] 

## Tools Used
Manual review

## Recommended Mitigation Steps
Add x entrancy checks 





## Assessed type

Reentrancy
Silent failure in user reward transfer in `Treasury.withdrawToAccount()` can lead to loss of rewards
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Dispenser.sol#L99-L106
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L1146
https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Treasury.sol#L402-L410


# Vulnerability details

## Impact
The [`withdrawToAccount()`](https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Treasury.sol#L387) function of the `Treasury` contract is designed to send ETH rewards and OLAS top-ups to a specified account. However, there is a potential issue where a user's reward transfer could silently fail. This occurs when the `amountETHFromServices` variable is less than `accountRewards`. In such a case, the function will still succeed if `accountTopUps` is greater than 0, but the user's reward will not be transferred. 

This function is only called through [`Dispenser.claimOwnerIncentives()`](https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Dispenser.sol#L89), which first calls [`Tokenomics.accountOwnerIncentives()`](https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L1085) to obtain the reward and topUp amounts to pass on to `Treasury.withdrawToAccount()`. The `accountOwnerIncentives()` function always zeroes both values for the caller. The vulnerability arises because the reward is then zeroed even if the transfer silently fails in `withdrawToAccount()`.

Since there is no limit on the amount of rewards a user may be claiming in one call, this can be considered loss of matured yield and is hence classified as high severity.

## Proof of Concept
Consider a scenario where Alice is a user who is supposed to receive rewards. She call the `Dispenser.claimOwnerIncentives()` function, which calls `Tokenomics.accountOwnerIncentives()` to obtain Alice's reward and top-up amounts and then calls `Treasury.withdrawToAccount()` with these amounts. If the `amountETHFromServices` is less than Alice's reward amount, but her top-up amount is greater than 0, the `withdrawToAccount` function will still succeed. However, Alice's reward will not be transferred, and her reward is zeroed in `Tokenomics.accountOwnerIncentives()`. As a result, Alice loses her expected reward.

## Tools Used
Manual review

## Recommended Mitigation Steps
Modify the `withdrawToAccount` function to revert or return false in the case where `amountETHFromServices` is less than `accountRewards`. A possible implementation may look as follows:

```diff
@@ -397,20 +397,25 @@ contract Treasury is IErrorsTokenomics {
             revert ManagerOnly(msg.sender, dispenser);
         }
 
+        success = true;
         uint256 amountETHFromServic
Service owners can accrue OLAS top-ups even when donating to their own services
Audit: 2023-12-autonolas-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L687


# Vulnerability details

## Impact

The basis of this exploit is the ability for a `topUpEligible` service owner — any registrant of a service who also stakes enough OLAS in the voting escrow contract — to accrue OLAS top-ups even when they donate to services that they own themselves. As long as an attacker also owns all the dependencies of their service, they will reclaim the entirety of any donations sent through the protocol: contributing nothing to the protocol, but extracting OLAS from it anyways.

At worst, this allows any attacker with a sufficiently high ether balance to claim the entire pool of OLAS allotted for the epoch's top-ups, since users receive a share of that pool proportional to their total pending ether rewards. The attacker could simply wait until the end of an epoch to see how much ether they would need to donate to themselves to marginalize the other pending donations. The `lastDonationBlockNumber` validation in [`Tokenomics.checkpoint:L893`](https://github.com/code-423n4/2023-12-autonolas/blob/2a095eb1f8359be349d23af67089795fb0be4ed1/tokenomics/contracts/Tokenomics.sol#L893) means the attacker cannot flash loan WETH to accomplish this, but anyone with enough ether to make the donation outright can delay their attack until the block preceeding the `checkpoint` call.

This is still a high-impact vulnerability without such a high-impact attack, though. Even if an attacker does not have enough ether to claim the entire pool of OLAS, they can always extract some amount of OLAS from the protocol that would otherwise be distributed to valid service owners.

## Proof of concept

[This test](https://gist.github.com/ethanbennett/932b66123db8c515e647f8f4d4a7ed56) illustrates a `topUpEligible` service owner donating to services they own themselves. After a new epoch begins and the pending amounts become final, the attacker claims their rewards. The test confirms that their ether balance is the same as it was before executing the malicious donation, and that they have received a large amount of OLAS in the process.

## Tools used

Foundry

## Recommended mitigation steps

The simplest way to prevent the scenario demonstrated in the proof of concept would be to block top-ups from accruing if the `msg.sender` is the owner of any recipient services. This is superior to disallowing the donations in the first place: it is possible, for instance, that donations like these might be valid if the donation is intended for a service the donor does not own but lists a dependency that *does* belong to the donor.

However, this does not prevent an attacker from donating from a secondary account that also belongs to them. This is a more complex problem, and the mitigation may require larger changes with more significant trade-offs. For example, a donation fee could make this attack unprofitable, but