xanoutas/solidity-security-findings huggingface dataset:
Bad accounting on ERC4626MultiToken.sol leads to user funds stuck in the contract
Audit: 2023-05-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/erc-4626/ERC4626MultiToken.sol#L194
https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/erc-4626/ERC4626MultiToken.sol#L225
https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/erc-4626/ERC4626MultiToken.sol#L244


# Vulnerability details

## Summary
Bad accounting on `ERC4626MultiToken.sol` leads to user funds stuck in the contract on deposit and withdraw logic. 

## Vulnerability Detail
The `UlyssesToken` unified liquidity tokens are derived from `ERC4626Multitoken`. This is an standard tokenized vault with multiple underlying and an array of weights. The issue arises in the previewDeposit and previewWithdraw functions, which calculate corresponding shares based on an array of asset quantities.

The problem is that this array should adhere to the specified weights. For example, if there are three tokens in the vault with weights [10, 10, 30], when making a deposit, the user should send token amounts in that proportion, such as [5, 5, 15]. The same principle applies to withdrawals.
However, there is a bug. During a deposit, if the user sends tokens in a disproportionate manner, the vault mints shares only for the minimum amount and does not return the excess to the user.

```solidity
function convertToShares(uint256[] calldata assetsAmounts) public view virtual returns (uint256 shares) {
      uint256 _totalWeights = totalWeights;
      uint256 length = assetsAmounts.length;

      if (length != assets.length) revert InvalidLength();

      shares = type(uint256).max;
      for (uint256 i = 0; i < length;) {
          uint256 share = assetsAmounts[i].mulDiv(_totalWeights, weights[i]);
          if (share < shares) shares = share;
          unchecked {
              i++;
          }
    }
}
```
On the other hand, during a withdrawal, if the user mistakenly provides an array with different percentages, let's say 40 shares represent [5, 5, 10], but the user mistakenly enters [0, 0, 10], the vault only transfers the last ten tokens and burns the 40 shares.


## Impact
User founds get stuck in the protocol when using custom token quantities for deposits and withdraws.

## Proof of Concept

```solidity
function test_withdrawU() public virtual {
        //create a default Init struct
        Init memory init;
        //populate the struct with default values

        uint256[NUM_USERS] memory defaultArray = [uint256(50), uint256(50), uint256(100), uint256(25)];
        uint256[NUM_USERS][NUM_USERS] memory defaultArrayTwo = [defaultArray, defaultArray, defaultArray, defaultArray];
        

        init = Init({
            user: [address(5), address(1), address(2), address(3)],
            share: defaultArrayTwo,
            asset: defaultArrayTwo,
            yield: 10
        });

        setUpVault(init);
        address caller = init.user[0];
        address re
Liquidity providers may lose funds when initialising a strategy
Audit: 2023-05-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/talos/base/TalosBaseStrategy.sol#L135-L149
https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/talos/base/TalosBaseStrategy.sol#L102


# Vulnerability details

## Summary
Liquidity providers may lose funds when initialising a strategy

## Vulnerability Detail
Liquidity providers may lose a portion of provided liquidity in either of the pair tokens when creating a new position. 
The init function on TalosBaseStrategy.sol does not check for price volatility before providing / swapping liquidity. Unlike in the rest of interactions with Uniswap where the modifier checkDeviation checks for price volatility before providing / swapping liquidity. While amount0Desired & amount1Desired specify the amount of tokens the user wants to initialise the position with. amount0Min & amount1Min should not be set to 0 in order to protect from slippage when adding liquidity. 

```solidity
/// TalosBaseStrategy.sol
function init(uint256 amount0Desired, uint256 amount1Desired, address receiver)
        external
        virtual
        nonReentrant
        returns (uint256 shares, uint256 amount0, uint256 amount1)
    {
        ...
    (_tokenId, _liquidity, amount0, amount1) = _nonfungiblePositionManager.mint(
        INonfungiblePositionManager.MintParams({
           token0: address(_token0),
           token1: address(_token1),
           fee: poolFee,
           tickLower: tickLower,
           tickUpper: tickUpper,
           amount0Desired: amount0Desired,
           amount1Desired: amount1Desired,
           amount0Min: 0, /// @audit this could lead to loss of value
           amount1Min: 0,
           recipient: address(this),
           deadline: block.timestamp
        })
     );
   ...
 );
```

## Impact
High, a strategy may be initialised with wrong liquidity amounts, initialiser may loss funds due to providing liquidity in the wrong ratio.


## Tools Used
Manual review, foundry

## Recommended Mitigation Steps
Add the modifier checkDeviation to the init function on TalosBaseStrategy.sol to prevent price manipulation, or use the pool twap oracle to get accurate minimum amounts for the swap.


## Assessed type

Uniswap
User can manipulate totalRewardUnclaimed and steal pool incentives
Audit: 2023-05-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/uni-v3-staker/UniswapV3Staker.sol#L483
https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/uni-v3-staker/UniswapV3Staker.sol#L438-L451
https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/uni-v3-staker/UniswapV3Staker.sol#L429-L451


# Vulnerability details

## Impact
In the ```UniswapV3Staker.sol``` contract, a user can drain the incentives by repeatedly staking and unstaking.

## Proof of Concept
During staking, the ```_stakeToken(...)``` function checks that incentives is not zero (this would later become insufficient) but does not in itself create incentive
```
        if (incentives[incentiveId].totalRewardUnclaimed == 0) revert NonExistentIncentiveError();
```

When the user tries to restake by calling the ```_unstakeToken(...)``` through the ```restakeToken(...)``` function, 
```


    function restakeToken(uint256 tokenId) external {
        ...
        if (incentiveId.startTime != 0) _unstakeToken(incentiveId, tokenId, true);
        ...
        _stakeToken(tokenId, pool, tickLower, tickUpper, liquidity);
    }
```

the ```incentives[incentiveId].totalRewardUnclaimed``` is decreased by the ```reward``` in an ```unchecked``` block. When ```_stakeToken(...)``` is called to complete ```restakeToken(...)``` function, it checks again to ensure that ```incentives[incentiveId].totalRewardUnclaimed``` is not zero as shown above.


```
        unchecked {
            ...
            // reward is never greater than total reward unclaimed
            incentive.totalRewardUnclaimed -= reward;
            ...
            tokenIdRewards[tokenId] += reward;
        }
```
The malicious user repeats the cycle until the ```incentives[incentiveId].totalRewardUnclaimed``` becomes small but is not zero since it is continuously reduced by the ```reward``` and this is done within an unchecked block. At this point, the developer's assumption that **reward is never greater than total reward unclaimed** becomes dangerous because he does not add a check to ensure his assumption holds true since it is not guarantee that the ```reward``` will decreasing for every epoch.


At some point during the user's staking and unstaking iteration (via the ```restakeToken(...)``` function), the ```incentives[incentiveId].totalRewardUnclaimed``` will overflow since the accounting is done in an ```unchecked``` block leading to an inflated ```totalRewardUnclaimed```. 

For the next ```restake(...)``` done by the malicious user, the ```reward``` will be calculated using the inflated ```incentive.totalRewardUnclaimed``` value leading to the user accruing a larger than normal reward. 

```
    function _unstakeToken(IncentiveKey memory key, uint256 tokenId, bool isNotRestake) private {
        ...
        uint256 reward = RewardMath.computeBoostedRewardAmount(
            incentive.totalRewardUncl
DENIAL OF SERVICE (`DoS`) WHEN DECREASING THE `weight` DUE TO UNDERFLOW IN THE `UlyssesPool.setWeight` FUNCTION
Audit: 2023-05-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-amm/UlyssesPool.sol#L252-L270


# Vulnerability details

## Impact
The `UlyssesPool.setWeight` function is used to update the `weight` of a particular `poolId` in the current `UlyssesPool`. But when the `weight` of the respective `poolId` is decreased from the current value, the calculation underflows and thus `DoS` the reducing of `weight` for any `poolId` in the `Ulysses AMM`.

This could lead to `owner` not being able to reduce the `weight` of a respective `poolId` based on the required allcoation of current pool's assets for the other pools. 

Consider the following scenario:

1. There are three pools in the Ulysses AMM.
2. Out of the three pools, pool1 has two `BandwidthStates` created by pool2 and pool3 respectively.
3. pool2 has a weight of 30 in the pool1.
4. pool3 has a weight of 70 in the pool1.
5. The owner of the `UlyssesPool` decides to change the `weight` of the pool2 to `20` thus reducing the `newTotalWeights`.
6. Since now the `oldTotalWeights > newTotalWeights` is true, function will attempt to recalculate the new bandwidths of the respective `UlyssesPools` as follows:

        if (oldTotalWeights > newTotalWeights) {
            for (uint256 i = 1; i < bandwidthStateList.length;) {
                if (i != poolIndex) {
                    uint256 oldBandwidth = bandwidthStateList[i].bandwidth;
                    if (oldBandwidth > 0) {
                        bandwidthStateList[i].bandwidth =
                            oldBandwidth.mulDivUp(oldTotalWeights, newTotalWeights).toUint248();

                        leftOverBandwidth += oldBandwidth - bandwidthStateList[i].bandwidth;
                    }
                }

                ...

        }

7. But now the `leftOverBandwidth += oldBandwidth - bandwidthStateList[i].bandwidth;` operation will `underflow` since the calculated `bandwidthStateList[i].bandwidth` > `oldBandwidth`, since `oldTotalWeights` > `newTotalWeights`.

8. As a result this will DoS a key component (reducing the `weight` of a respective `PoolId` by using the `UlyssesPool.setWeight` function) of the Ulysses AMM.

## Proof of Concept

Add the following `testFail_SetWeight` test to the `UlyssesPoolTest.t.sol` test file.

```solidity
    function testFail_SetWeight() public {
        setUpHandler();

        vm.startPrank(handler);

        UlyssesPool[] memory pools = createPools(3);
        UlyssesPool pool1 = UlyssesPool(pools[0]);
        UlyssesPool pool2 = UlyssesPool(pools[1]);
        UlyssesPool pool3 = UlyssesPool(pools[2]);

        pool1.addNewBandwidth(2, 20); //@audit-info - 
        pool1.addNewBandwidth(3, 80);

        pool2.addNewBandwidth(1, 100);

        pool3.addNewBandwidth(1, 100);

        vm.stopPrank();    

        MockERC20(pool1.asset()).mint(address(this), type(uint256).max);
        MockERC20(pool1.asset()).approve(address(pool1), type(uint256).max);

        MockERC20(pool2.asset()).mint(addres
FlywheelAcummulatedRewards/FlywheelBribeRewards gains are instantaneous and can be frontrun
Audit: 2023-05-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/rewards/rewards/FlywheelAcummulatedRewards.sol#L46-L54


# Vulnerability details

## Impact

FlywheelAcummulatedRewards/FlywheelBribeRewards gains are instantaneous and can be frontrun.  
The user only needs to frontrun the delegate before each incentive is distributed to get the incentive, and there is no way to prevent the user from undelegating after receiving the reward.  
The protocal is to mint votes using staking bHermes. For example, the user can staking bHermes to get votes, delegate to receive the incentive, undelegate, and then take out bHermes to do other earnings, and wait until a week later to re-delegate.

## Proof of Concept

```solidity
function testUndelegateAfterAccrue() external {
        MockERC20 token = new MockERC20("test token", "TKN", 18);
        FlywheelCore flywheel = createFlywheel(token);
        gaugeToken.setMaxDelegates(2);

        hermes.mint(address(this), 100e18);
        hermes.approve(address(gaugeToken), 100e18);
        gaugeToken.mint(address(this), 100e18);
        gaugeToken.delegate(address(this));
        gaugeToken.incrementGauge(address(gauge), 100e18);

        address other = address(0xdead);
        hevm.startPrank(other);
        hermes.mint(other, 100e18);
        hermes.approve(address(gaugeToken), 100e18);
        gaugeToken.mint(other, 100e18);
        gaugeToken.delegate(other);
        gaugeToken.incrementGauge(address(gauge), 100e18);
        hevm.stopPrank();

        // addBribeFlywheel
        gauge.addBribeFlywheel(flywheel);
        token.mint(address(depot), 100 ether);

        // claimRewards
        gauge.accrueBribes(address(this));
        flywheel.claimRewards(address(this));
        gauge.accrueBribes(other);
        flywheel.claimRewards(other);
        assert(token.balanceOf(address(this)) == token.balanceOf(other));
        assert(token.balanceOf(address(this)) == 50 ether);

        // Undo delegate
        gaugeToken.decrementGauge(address(gauge), 100e18);
        gaugeToken.undelegate(address(this), 100e18);
        gaugeToken.burn(address(this), 100e18);

        hevm.warp(WEEK - 3 days);

        // frontrun to delegate
        gaugeToken.mint(address(this), 100e18);
        gaugeToken.delegate(address(this));
        gaugeToken.incrementGauge(address(gauge), 100e18);

        // update bribeFlywheel
        token.mint(address(depot), 100 ether);

        // Cycle 1
        hevm.warp(WEEK);

        // claimRewards
        gauge.accrueBribes(address(this));
        flywheel.claimRewards(address(this));
        gauge.accrueBribes(other);
        flywheel.claimRewards(other);
        assert(token.balanceOf(address(this)) == token.balanceOf(other));
        assert(token.balanceOf(address(this)) == 100 ether);

        // Undo delegate
        gaugeToken.decrementGauge(address(gauge), 100e18);
        gaugeToken.undelegate(address(this), 100e18);
        gaugeTok
addLiquidity() unable to work
Audit: 2023-05-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-amm/UlyssesRouter.sol#L49-L56


# Vulnerability details

## Impact
missing the first transfer of the asset to `router`, `addLiquidity()` unable to work

## Proof of Concept
`UlyssesRouter.addLiquidity`use for mint LP
The code is as follows:

```solidity
    function addLiquidity(uint256 amount, uint256 minOutput, uint256 poolId) external returns (uint256) {

        UlyssesPool ulysses = getUlyssesLP(poolId);

        amount = ulysses.deposit(amount, msg.sender);

        if (amount < minOutput) revert OutputTooLow();
        return amount;
    }
```

The above code, missing the first transfer of the asset to `router`
e.g.: `ulysses.asset().safeTransferFrom(msg.sender, address(this), amount);`

Cannot work

## Tools Used

## Recommended Mitigation Steps

```solidity
    function addLiquidity(uint256 amount, uint256 minOutput, uint256 poolId) external returns (uint256) {

        UlyssesPool ulysses = getUlyssesLP(poolId);

+       ulysses.asset().safeTransferFrom(msg.sender, address(this), amount);

        amount = ulysses.deposit(amount, msg.sender);

        if (amount < minOutput) revert OutputTooLow();
        return amount;
    }
```


## Assessed type

Context
_payFallbackGas is not being paid in case selector is 0x07 or 0x08
Audit: 2023-05-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L1227-L1307


# Vulnerability details

## Impact
_payFallbackGas gas is not being paid for selectors 0x07 and 0x08 which causes a loss for protocol's execution gas budget. In case Execution budget is not enough then anyFallback will fail.

## Proof of Concept
In `_payFallbackGas()` gas should always be paid in case selector (flag) is sent as 0x07 or 0x08 by calldata in `anyFallback()` [L1227-L1307](https://github.com/code-423n4/2023-05-maia/blob/main/src/ulysses-omnichain/BranchBridgeAgent.sol#L1227-L1307)

```solidity
    function anyFallback(bytes calldata data)
        external
        virtual
        requiresExecutor
        returns (bool success, bytes memory result)
    {
        //Get Initial Gas Checkpoint
        uint256 initialGas = gasleft();


        //Save Flag
        bytes1 flag = data[0];


        //Save memory for Deposit Nonce
        uint32 _depositNonce;


        /// DEPOSIT FLAG: 0, 1, 2
        if ((flag == 0x00) || (flag == 0x01) || (flag == 0x02)) {
            //Check nonce calldata slice.
            _depositNonce = uint32(bytes4(data[PARAMS_START:PARAMS_TKN_START]));


            //Make tokens available to depositor.
            _clearDeposit(_depositNonce);


            emit LogCalloutFail(flag, data, rootChainId);


            //Deduct gas costs from deposit and replenish this bridge agent's execution budget.
            _payFallbackGas(_depositNonce, initialGas);


            return (true, "");


            /// DEPOSIT FLAG: 3
        } else if (flag == 0x03) {
            _depositNonce = uint32(bytes4(data[PARAMS_START + PARAMS_START:PARAMS_TKN_START + PARAMS_START]));


            //Make tokens available to depositor.
            _clearDeposit(_depositNonce);


            emit LogCalloutFail(flag, data, rootChainId);


            //Deduct gas costs from deposit and replenish this bridge agent's execution budget.
            _payFallbackGas(_depositNonce, initialGas);


            return (true, "");


            /// DEPOSIT FLAG: 4, 5
        } else if ((flag == 0x04) || (flag == 0x05)) {
            //Save nonce
            _depositNonce = uint32(bytes4(data[PARAMS_START_SIGNED:PARAMS_START_SIGNED + PARAMS_TKN_START]));


            //Make tokens available to depositor.
            _clearDeposit(_depositNonce);


            emit LogCalloutFail(flag, data, rootChainId);


            //Deduct gas costs from deposit and replenish this bridge agent's execution budget.
            _payFallbackGas for (_depositNonce, initialGas);


            return (true, "");


            /// DEPOSIT FLAG: 6
        } else if (flag == 0x06) {
            //Save nonce
            _depositNonce = uint32(
                bytes4(data[PARAMS_START_SIGNED + PARAMS_START:PARAMS_START_SIGNED + PARAMS_TKN_START + PARAMS_START])
            );


            //Make tokens available to depositor.
            _clearDep
Incorrect usage of Comparison Operator(==) instead of Asignment Operator(=) in PartnerManagerFactory
Audit: 2023-05-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-05-maia/blob/main/src/maia/factories/PartnerManagerFactory.sol#L61


# Vulnerability details

##Impact

The incorrect usage of comparison operator instead of an assignment operator in addPartner and addVault functions could lead to undesirable behaviour. While the mapping partnerIds and vaultIds are supposed to keep track of the IDs, due to the error, these mappings aren't updated as intended. This could potentially cause problems in the system where these mappings are referenced, leading to faulty business logic.

##Proof of Concept

https://github.com/code-423n4/2023-05-maia/blob/main/src/maia/factories/PartnerManagerFactory.sol#L61

In the addPartner and addVault functions, the lines partnerIds[newPartnerManager] == id; and vaultIds[newVault] == id; respectively are performing a comparison operation which will do nothing.

The correct lines of code should be:

partnerIds[newPartnerManager] = id;
vaultIds[newVault] = id;

This will ensure that the new IDs are correctly assigned in the mappings.

## Tools Used

Manual Review

##Recommended Mitigation Steps

The code should be updated to use assignment operators instead of comparison operators.

partnerIds[newPartnerManager] = id;
vaultIds[newVault] = id;


## Assessed type

Other
setWeight() Logic error
Audit: 2023-05-maia-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-05-maia/blob/54a45beb1428d85999da3f721f923cbf36ee3d35/src/ulysses-amm/UlyssesPool.sol#L223


# Vulnerability details

## Impact
Logic error

## Proof of Concept
`setWeight()`Used to set the new weight
The code is as follows:
```solidity
    function setWeight(uint256 poolId, uint8 weight) external nonReentrant onlyOwner {
        if (weight == 0) revert InvalidWeight();

        uint256 poolIndex = destinations[poolId];

        if (poolIndex == 0) revert NotUlyssesLP();

        uint256 oldRebalancingFee;

        for (uint256 i = 1; i < bandwidthStateList.length; i++) {
            uint256 targetBandwidth = totalSupply.mulDiv(bandwidthStateList[i].weight, totalWeights);

            oldRebalancingFee += _calculateRebalancingFee(bandwidthStateList[i].bandwidth, targetBandwidth, false);
        }

        uint256 oldTotalWeights = totalWeights;
        uint256 weightsWithoutPool = oldTotalWeights - bandwidthStateList[poolIndex].weight;
        uint256 newTotalWeights = weightsWithoutPool + weight;
        totalWeights = newTotalWeights;

        if (totalWeights > MAX_TOTAL_WEIGHT || oldTotalWeights == newTotalWeights) {
            revert InvalidWeight();
        }

        uint256 leftOverBandwidth;

        BandwidthState storage poolState = bandwidthStateList[poolIndex];
        poolState.weight = weight;
@>      if (oldTotalWeights > newTotalWeights) {
            for (uint256 i = 1; i < bandwidthStateList.length;) {
                if (i != poolIndex) {
                    uint256 oldBandwidth = bandwidthStateList[i].bandwidth;
                    if (oldBandwidth > 0) {
                        bandwidthStateList[i].bandwidth =
                            oldBandwidth.mulDivUp(oldTotalWeights, newTotalWeights).toUint248();
                        leftOverBandwidth += oldBandwidth - bandwidthStateList[i].bandwidth;
                    }
                }

                unchecked {
                    ++i;
                }
            }
            poolState.bandwidth += leftOverBandwidth.toUint248();
        } else {
            uint256 oldBandwidth = poolState.bandwidth;
            if (oldBandwidth > 0) {
@>              poolState.bandwidth = oldBandwidth.mulDivUp(oldTotalWeights, newTotalWeights).toUint248();

                leftOverBandwidth += oldBandwidth - poolState.bandwidth;
            }

            for (uint256 i = 1; i < bandwidthStateList.length;) {
              

                if (i != poolIndex) {
                     if (i == bandwidthStateList.length - 1) {
@>                       bandwidthStateList[i].bandwidth += leftOverBandwidth.toUint248();
                     } else if (leftOverBandwidth > 0) {
@>                       bandwidthStateList[i].bandwidth +=
@>                          leftOverBandwidth.mulDiv(bandwidthStateList[i].weight, weightsWithoutPool).toUint248();
                     }
                }

                unchecked {
                    ++