xanoutas/solidity-security-findings huggingface dataset:
Users can be griefed by staking unlocked dust amounts on their behalf
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/staking/LockingMultiRewards.sol#L458-L477


# Vulnerability details

## Impact

- In `LockingMultiRewards` contract: whenever a user stakes, withdraws or claims his rewards; his rewards are updated via `_updateRewardsForUser()` function, where it loops over reward tokens and updates the `rewards[user_][token_]` via `_udpateUserRewards()` function:

  ```javascript
      function _updateRewardsForUser(address user) internal {
          uint256 balance = balanceOf(user);
          uint256 totalSupply_ = totalSupply();

          for (uint256 i; i < rewardTokens.length; ) {
              address token = rewardTokens[i];
              _udpateUserRewards(user, balance, token, _updateRewardsGlobal(token, totalSupply_));

              unchecked {
                  ++i;
              }
          }
      }
  ```

  ```javascript
      function _udpateUserRewards(address user_, uint256 balance_, address token_, uint256 rewardPerToken_) internal {
          rewards[user_][token_] = _earned(user_, balance_, token_, rewardPerToken_);
          userRewardPerTokenPaid[user_][token_] = rewardPerToken_;
      }
  ```

  ```javascript
      function _earned(address user, uint256 balance_, address rewardToken, uint256 rewardPerToken_) internal view returns (uint256) {
          uint256 pendingUserRewardsPerToken = rewardPerToken_ - userRewardPerTokenPaid[user][rewardToken];

          return ((balance_ * pendingUserRewardsPerToken) / 1e18) + rewards[user][rewardToken];
      }
  ```

- As can be noticed, user's `rewards[user_][token_]` is updated based on `rewardPerToken_ - userRewardPerTokenPaid * user's balance`, so that user's rewards balance can be updated only once per block as long as there's no update on `rewardPerToken_` (via `notifyRewardAmount()`).

- This can be exploited by any malicious user to grief other users by staking dust **unlocked** amount (1 wei) on their behalf via `stakeFor()` function:

  ```javascript
  function _stakeFor(address account, uint256 amount, bool lock_) internal {
          if (amount == 0) {
              revert ErrZeroAmount();
          }

          // This staking contract isn't using balanceOf, so it's safe to transfer immediately
          stakingToken.safeTransferFrom(msg.sender, address(this), amount);
          stakingTokenBalance += amount;

          _updateRewardsForUser(account);

          if (lock_) {
              _createLock(account, amount);
          } else {
              _balances[account].unlocked += amount;
              unlockedSupply += amount;

              emit LogStaked(account, amount);
          }
      }
  ```

and when the griefed user rewards are updated via `_updateRewardsForUser()`, the `userRewardPerTokenPaid[user]` will be set to the latest `rewardPerToken_` , so when the griefed user calls any of the stake,withdraw or getRewards functions; the updated rewards will be
First deposit can unintentionally lock the quote target value to zero
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/mimswap/MagicLP.sol#L360-#L410


# Vulnerability details

## Vulnerability details
When the first deposit happens, `_QUOTE_TARGET_` is set as follows:

        if (totalSupply() == 0) {
            // case 1. initial supply
            if (quoteBalance == 0) {
                revert ErrZeroQuoteAmount();
            }

            shares = quoteBalance < DecimalMath.mulFloor(baseBalance, _I_) ? DecimalMath.divFloor(quoteBalance, _I_) : baseBalance;
            _BASE_TARGET_ = shares.toUint112();
            _QUOTE_TARGET_ = DecimalMath.mulFloor(shares, _I_).toUint112();
In this scenario, the 'shares' value can be a minimum of 1e3, as indicated here: [link](https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/mimswap/MagicLP.sol#L593-#L596)

This implies that if someone deposits minuscule amounts of quote token and base token, they can set the `_QUOTE_TARGET_` to zero because the mulFloor operation uses a scaling factor of 1e18:

    uint256 internal constant ONE = 10 ** 18;
    function mulFloor(uint256 target, uint256 d) internal pure returns (uint256) {
        return (target * d) / ONE;
    }
Should the quote target become 0, subsequent deposits will not increase due to the multiplication with "0" on the quote target. This situation is highly problematic because the swaps depend on the value of the quote target:

    // @review 0 + (0 * something) = 0
    _QUOTE_TARGET_ = (uint256(_QUOTE_TARGET_) + DecimalMath.mulFloor(uint256(_QUOTE_TARGET_), mintRatio)).toUint112();

Although when creating pool, it will always buy shares first, but there is no checking condition to make sure above scenario do not happen:

    function createPool(
        address baseToken,
        address quoteToken,
        uint256 lpFeeRate,
        uint256 i,
        uint256 k,
        address to,
        uint256 baseInAmount,
        uint256 quoteInAmount
    ) external returns (address clone, uint256 shares) {
        _validateDecimals(IERC20Metadata(baseToken).decimals(), IERC20Metadata(quoteToken).decimals());

        clone = IFactory(factory).create(baseToken, quoteToken, lpFeeRate, i, k);

        baseToken.safeTransferFrom(msg.sender, clone, baseInAmount);
        quoteToken.safeTransferFrom(msg.sender, clone, quoteInAmount);
        (shares, , ) = IMagicLP(clone).buyShares(to);    //<----
    }
 `bootstrap()` function that call `createPool()` function:

    function bootstrap(uint256 minAmountOut) external onlyOwner onlyState(State.Closed) returns (address, address, uint256) {
        if (pool != address(0)) {
            revert ErrAlreadyBootstrapped();
        }

        uint256 baseAmount = totals[MIM].locked;
        uint256 quoteAmount = totals[USDB].locked;
        MIM.safeApprove(address(router), type(uint256).max);
        USDB.safeApprove(address(router), type(uint256).max);

        (pool, totalPoolShares) = router.createPool(MIM, USDB, FEE_RATE, I, K
Incorrect rewards calculation with low balances (rounding down to zero)
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/staking/LockingMultiRewards.sol#L292-L295


# Vulnerability details

## [M-08] Incorrect rewards calculation with low balances (rounding down to zero)

## Impact

- In `LockingMultiRewards` contract: whenever a user stakes, withdraws or claims his rewards; his rewards are updated via `_updateRewardsForUser()` function, where it loops over reward tokens and updates the `rewards[user_][token_]` via `_udpateUserRewards()` function:

  ```javascript
      function _updateRewardsForUser(address user) internal {
          uint256 balance = balanceOf(user);
          uint256 totalSupply_ = totalSupply();

          for (uint256 i; i < rewardTokens.length; ) {
              address token = rewardTokens[i];
              _udpateUserRewards(user, balance, token, _updateRewardsGlobal(token, totalSupply_));

              unchecked {
                  ++i;
              }
          }
      }
  ```

  ```javascript
      function _udpateUserRewards(address user_, uint256 balance_, address token_, uint256 rewardPerToken_) internal {
          rewards[user_][token_] = _earned(user_, balance_, token_, rewardPerToken_);
          userRewardPerTokenPaid[user_][token_] = rewardPerToken_;
      }
  ```

  ```javascript
      function _earned(address user, uint256 balance_, address rewardToken, uint256 rewardPerToken_) internal view returns (uint256) {
          uint256 pendingUserRewardsPerToken = rewardPerToken_ - userRewardPerTokenPaid[user][rewardToken];

          return ((balance_ * pendingUserRewardsPerToken) / 1e18) + rewards[user][rewardToken];
      }
  ```

- But as can be noticed from `_earned()` function; if the `balance_ * pendingUserRewardsPerToken` is **less than 1e18**, it will be rounded down to zero and the `_earned()` function will return the same old value without update.

## Proof of Concept

[LockingMultiRewards.\_earned function](https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/staking/LockingMultiRewards.sol#L292-L295)

```javascript
    function _earned(address user, uint256 balance_, address rewardToken, uint256 rewardPerToken_) internal view returns (uint256) {
        uint256 pendingUserRewardsPerToken = rewardPerToken_ - userRewardPerTokenPaid[user][rewardToken];
        return ((balance_ * pendingUserRewardsPerToken) / 1e18) + rewards[user][rewardToken];
    }
```

## Tools Used

Manual Review.

## Recommended Mitigation Steps

Implement a mechanism that prevents updating the `rewards[user_][token_]` if `(balance_ * pendingUserRewardsPerToken)` is < 1e18 .


## Assessed type

Math
The staker will lose from his accumulated rewards if his locks are expired and processed by the operator
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/staking/LockingMultiRewards.sol#L402-L445


# Vulnerability details

## Impact

- Users can create locked stakes to receive more rewards, where their locked staked balance will be trippled when calculating their rewards (from the `LockingMultiRewardsScript.s.sol` deployment script , the `lockingBoostMultiplerInBips` value that is used to increase the locked staked balance is set to 30_000- which means 3\*balance).

- `LockingMultiRewards` contract operator can remove the expired locks for a specific user via `processExpiredLocks()`, where it first updates rewards of that user based on his locked and unlocked balance, then the expired locks are removed and the user unlocked balance is **increased** by the amount staked in the expired lock, and the user's locked balance is **decreased** by the amount staked in the expired lock:

  ```javascript
      function processExpiredLocks(address[] memory users, uint256[] calldata lockIndexes) external onlyOperators {
          if (users.length != lockIndexes.length) {
              revert ErrLengthMismatch();
          }

          _updateRewardsForUsers(users);

          // Release all expired users' locks
          for (uint256 i; i < users.length; ) {
              //some code...

              uint256 amount = locks[index].amount;
              //some code...

              unlockedSupply += amount;
              lockedSupply -= amount;

              bal.unlocked += amount;
              bal.locked -= amount;

              //some code...

              unchecked {
                  ++i;
              }
          }
      }
  ```

- Then when this user later calls `getRewards()` to claim his rewards, the function will first update his rewards based on his current balance (increased unlocked and decreased locked), so his rewards will be decreased as the locked balance that rewards the user more (3 times at least) is decreased, resulting in claiming less rewards than intended:

```javascript
    function getRewards() public virtual {
        _updateRewardsForUser(msg.sender);
        _getRewards(msg.sender);
    }
```

```javascript
    function _updateRewardsForUser(address user) internal {
        uint256 balance = balanceOf(user); //! unlocked + locked*3
        uint256 totalSupply_ = totalSupply(); //! unlocked + locked*3

        for (uint256 i; i < rewardTokens.length; ) {
            address token = rewardTokens[i];

            _udpateUserRewards(user, balance, token, _updateRewardsGlobal(token, totalSupply_));

            unchecked {
                ++i;
            }
        }
    }
```

where `balanceOf(user)` will be equal to the unlocked balance + `lockingBoostMultiplerInBips`\* locked balance, where the `lockingBoostMultiplerInBips` minimum value is 10_001 (from the deployment script `LockingMultiRewardsScript.s.sol`: this value is set to 30_000); the locked balance co
`LockingMultiRewards` contract: the first staker can inflate `_rewardData[token_].rewardPerTokenStored`
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/staking/LockingMultiRewards.sol#L277-L287


# Vulnerability details

## Impact

The global reward data of each rewards token is updated via `_updateRewardsGlobal()` function whenever `notifyRewardAmount` function is invokend, and when the users stake,lock, withdraw and claim their rewards:

```javascript
        function _updateRewardsGlobal(address token_, uint256 totalSupply_) internal returns (uint256 rewardPerToken_) {
            uint256 lastTimeRewardApplicable_ = lastTimeRewardApplicable(token_);
            rewardPerToken_ = _rewardPerToken(token_, lastTimeRewardApplicable_, totalSupply_);

            _rewardData[token_].rewardPerTokenStored = rewardPerToken_;
            _rewardData[token_].lastUpdateTime = uint248(lastTimeRewardApplicable_); // safe to cast as this will never overflow
        }
```

as can be noticed, the `_rewardData[token_].rewardPerTokenStored` is calculated by the `_rewardPerToken()`, where this value is increased by `pendingRewardsPerToken` that's calculated based on `the time elapsed from the last update * current rewards rate / totalSupply`:

```javascript
    function _rewardPerToken(address rewardToken, uint256 lastTimeRewardApplicable_, uint256 totalSupply_) public view returns (uint256) {
        if (totalSupply_ == 0) {
            return _rewardData[rewardToken].rewardPerTokenStored;
        }
        //! @audit-issue :  inflating rewardPerToken by staking very small amount by the first staker (where it's much less than 1e18):
        uint256 timeElapsed = lastTimeRewardApplicable_ - _rewardData[rewardToken].lastUpdateTime;
        uint256 pendingRewardsPerToken = (timeElapsed * _rewardData[rewardToken].rewardRate * 1e18) / totalSupply_;

        return _rewardData[rewardToken].rewardPerTokenStored + pendingRewardsPerToken;
    }
```

where the totalSupply is:

```javascript
        function totalSupply() public view returns (uint256) {
            return unlockedSupply + ((lockedSupply * lockingBoostMultiplerInBips) / BIPS);
        }
```

**So how could the `rewardPerTokenStored` be inflated by the first staker?**

1. assume that the current reward rate of a specific rewards token is > 0, and there's no stakes made in the contract, so the `totalSupply` is zero.
2. now the first staker stakes a very small **unlocked** amount of 1 wei (**dust, less than 1e18**).
3. then the next staker stakes; now the `_rewardPerToken` function will calculate the `_rewardData[rewardToken].rewardPerTokenStored` as follows:

```javascript
    function _rewardPerToken(address rewardToken, uint256 lastTimeRewardApplicable_, uint256 totalSupply_) public view returns (uint256) {
        //some code..

        uint256 timeElapsed = lastTimeRewardApplicable_ - _rewardData[rewardToken].lastUpdateTime;

        // @audit-issue : which is:
        // timeElapsed * rewardRate * 1e18 / 1 = timeElapsed * rewardRate
`LockingMultiRewards` contract: users will be able to claim more rewards than they are entitled to by calling `withdrawWithRewards()`
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/staking/LockingMultiRewards.sol#L186-L189


# Vulnerability details

## Impact

- `LockingMultiRewards.withdrawWithRewards` function enables users to withdraw an amount of their **unlocked** staked deposits and then claim their accumulated rewards in the same function:

```javascript
    function withdrawWithRewards(uint256 amount) public virtual {
            withdraw(amount);
            _getRewards(msg.sender);
    }
```

where first the withdrawal of the **unlocked** staked amount is executed, but before that the accumulated rewards of the user are updated via `_updateRewardsForUser()`, then his unlocked balance is **decreased** by the withdrawn amount:

```javascript
          function withdraw(uint256 amount) public virtual {
                  if (amount == 0) {
                      revert ErrZeroAmount();
                  }

                  _updateRewardsForUser(msg.sender);

                  _balances[msg.sender].unlocked -= amount;
                  unlockedSupply -= amount;

                  stakingToken.safeTransfer(msg.sender, amount);
                  stakingTokenBalance -= amount;

                  emit LogWithdrawn(msg.sender, amount);
              }
```

```javascript
    function _updateRewardsForUser(address user) internal {
        uint256 balance = balanceOf(user); // << unlocked + locked*3
        uint256 totalSupply_ = totalSupply(); //<< unlocked + locked*3

        for (uint256 i; i < rewardTokens.length; ) {
            address token = rewardTokens[i];
            _udpateUserRewards(user, balance, token, _updateRewardsGlobal(token, totalSupply_));

            unchecked {
                ++i;
            }
        }
    }
```

then after the withdrawal is executed, the `_getRewards()` function is invoked to transfer the rewards for the user.

- But as can be noticed, the `_getRewards()` function is invoked directly after the `withdraw()` function without updating the user rewards based on his new balance after the withdrawal, and this will result in the user claiming more rewards than he is entitled to, because his old balance before withdrawal is the one used to calculate his accumulated rewards.

- To assure that this is a vulnerability/missing update: the `getRewards()` function that is intended to send the accumulated rewards to the user **updates** the rewards of the user based on his current balance (locked and unlocked) before sending them.

## Proof of Concept

[LockingMultiRewards.withdrawWithRewards function](https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/staking/LockingMultiRewards.sol#L186-L189)

```javascript
    function withdrawWithRewards(uint256 amount) public virtual {
        withdraw(amount);
        _getRewards(msg.sender);
    }
```

## Tools Used

Manual Review.

## Recommended Mitigation Steps

Update `withdraw
`MagicLp` contract: updating base/quote tokens price (`_I_`) can be sandwiched by arbitrageurs
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L494-L495


# Vulnerability details

## Impact

- `MagicLp` contract uses `_I_` variable as a replacemnt of oracles to get the price of base/quote tokens, and the variable `_K_` is used to limit the price fluctuation of the pair tokens, and these values are used to price the tokens when users selling their base or quote tokens (pricing is done based on the state of the contract's reserves and balances extracted from `getPMMState()`) , where the owner of the contract can set these values via `MagicLp.setParameters()`.

- Knowing that `_I_` is in the range of `0 < _I_ < 10**36`; updating `_I_` will directly influence the price, and this can be exploited by any arbitrager by frontrunning the transaction and trading (selling base or quote tokens) just before this variable is adjusted via `setParameters()` function and trading again (selling again base or quote tokens but with a higher return) right after the price change (backrunning) , which would result in a loss for the share holders (LP providers) where their shares would hold a lesser value.

## Proof of Concept

[MagicLP.setParameters function](https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L494-L495)

```javascript
    function setParameters(
        address assetTo,
        uint256 newLpFeeRate,
        uint256 newI,
        uint256 newK,
        uint256 baseOutAmount,
        uint256 quoteOutAmount,
        uint256 minBaseReserve,
        uint256 minQuoteReserve
    ) public nonReentrant onlyImplementationOwner {
        if (_BASE_RESERVE_ < minBaseReserve || _QUOTE_RESERVE_ < minQuoteReserve) {
            revert ErrReserveAmountNotEnough();
        }
        if (newI == 0 || newI > MAX_I) {
            revert ErrInvalidI();
        }
        if (newK > MAX_K) {
            revert ErrInvalidK();
        }
        if (newLpFeeRate < MIN_LP_FEE_RATE || newLpFeeRate > MAX_LP_FEE_RATE) {
            revert ErrInvalidLPFeeRate();
        }

        _LP_FEE_RATE_ = uint64(newLpFeeRate);
        _K_ = uint64(newK);
        _I_ = uint128(newI);

        _transferBaseOut(assetTo, baseOutAmount);
        _transferQuoteOut(assetTo, quoteOutAmount);
        (uint256 newBaseBalance, uint256 newQuoteBalance) = _resetTargetAndReserve();

        emit ParametersChanged(newLpFeeRate, newI, newK, newBaseBalance, newQuoteBalance);
    }
```

## Tools Used

Manual Review.

## Recommended Mitigation Steps

Update `_I_` variable in multiple transactions over time so that it wouldn't create an opportunity for arbitrageurs to exploit the price change.



## Assessed type

MEV
Adjusting "_I_" will create a sandwich opportunity because of price changes
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/mimswap/MagicLP.sol#L470-#L510
https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/mimswap/MagicLP.sol#L244-#L265


# Vulnerability details

## Vulnerability details
Because `MagicLP` is forked from DODO, we will check DODO documentation to understand this contract. From [DODO documentation](https://docs.dodoex.io/en/developer/contracts/dodo-v1-v2/dodo-v1-v2-integration-guide), the "_I_" is the "i" value in here and it is directly related with the output amount a trader will receive when selling a quote/base token:

![img](https://i.imgur.com/UpeLbGg.png)

Adjusting the value of "_I_" directly by calling `setParameters()` function will influence the price. This can be exploited by a MEV bot, simply by trading just before the `setParameters()` function and exiting right after the price change. The profit gained from this operation essentially represents potential losses for the liquidity providers who supplied liquidity to the pool.

## Impact
Since the price will change, the MEV bot can simply sandwich the tx and get profit.

Another note on this is that even though the adjustPrice called by `onlyImplementationOwner` without getting frontrunned, it still creates a big price difference which requires immediate arbitrages. Usually these type of parameter changes that impacts the trades are setted by time via ramping to mitigate the unfair advantages that it can occur during the price update.

## Tools Used
Manual review

## Recommended Mitigation Steps
Acknowledge the issue and use private RPC's to eliminate front-running or slowly ramp up the "_I_" so that the arbitrage opportunity is fair





## Assessed type

MEV
Automative characteristic influence mimswap
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/mimswap/MagicLP.sol#L366


# Vulnerability details

## Impact
BlastTokenRegistry only supports weth and usdb, for other tokens that are automative, the mimswap functionality will be affected

## Proof of Concept
https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/mimswap/MagicLP.sol#L366

baseBalance can be bigger than real transfers, which will make the share bigger than should be

## Tools Used
Manual Audit

## Recommended Mitigation Steps
in _afterInitialized function of BlastMagicLp, if registry not support this token, then just revert


## Assessed type

Context