xanoutas/solidity-security-findings huggingface dataset:
Factory::create() is vulnerable to reorg attacks
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/periphery/Factory.sol#L81-L90


# Vulnerability details


## Proof of Concept

Take a look at https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/periphery/Factory.sol#L81-L90

```solidity
    function create(address baseToken_, address quoteToken_, uint256 lpFeeRate_, uint256 i_, uint256 k_) external returns (address clone) {
        address creator = tx.origin;

        bytes32 salt = _computeSalt(creator, baseToken_, quoteToken_, lpFeeRate_, i_, k_);
        clone = LibClone.cloneDeterministic(address(implementation), salt);
        IMagicLP(clone).init(address(baseToken_), address(quoteToken_), lpFeeRate_, address(maintainerFeeRateModel), i_, k_);

        emit LogCreated(clone, baseToken_, quoteToken_, creator, lpFeeRate_, maintainerFeeRateModel, i_, k_);
        _addPool(creator, baseToken_, quoteToken_, clone);
    }
```

We can see that this function uses the `create()` logic and depends on the `tx.origin`, now Blast is suspicious of a reorg attack and protocol would be deployed on here.

Where as one can assume that the tx.origin should be different for different calls this is not really the case as going to the [Blast Explorer for transactions that are currently enqueued](https://blastexplorer.io/txsEnqueued) we can see that only two addresses are rampant as the tx.origin, i.e `0x4b16E5d33D7ab3864d53aAec93c8301C1FA4a226` and `0x6e5572f31bd9385709ec61305Afc749F0fa8fae1` what this leads is the fact that another user can just wait and due to a re-org take control of the magic lp deployment, since the tx.origin in this case would be the same with original deployer's own.

## Impact

Now, if users rely on the address derivation in advance or try to deploy the magiclp with the same address, any funds/tokens sent to it could potentially be withdrawn by anyone else. All in all, it could lead to the theft of user funds.

## Recommended Mitigation Steps

Try the deployment using create2 with salt that includes real `msg.sender`.




## Assessed type

Context
no user withdrawal mechanism for locked token in BlastOnboarding contract
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/blast/BlastOnboarding.sol#L101-L142


# Vulnerability details

## Impact
In the BlastOnboarding contract, users are able to deposit supported tokens, lock tokens and withdraw unlocked tokens. However there is no method/function for users to unlock their locked tokens voluntarily for withdrawal. Hence, when a user chooses to lock a supported token amount, they are unable to withdaw that locked amount.


## Proof of Concept
A user is able to deposit tokens by either having it locked or unlocked.
```
function deposit(address token, uint256 amount, bool lock_) external whenNotPaused onlyState(State.Opened) onlySupportedTokens(token) {
        token.safeTransferFrom(msg.sender, address(this), amount);

        if (lock_) {
            totals[token].locked += amount;
            balances[msg.sender][token].locked += amount;
        } else {
            totals[token].unlocked += amount;
            balances[msg.sender][token].unlocked += amount;
        }
        ...
```

Assume user initially deposited without locking and then decides to lock by calling lock().

```
function lock(address token, uint256 amount) external whenNotPaused onlyState(State.Opened) onlySupportedTokens(token) {
        balances[msg.sender][token].unlocked -= amount;
        balances[msg.sender][token].locked += amount;
        totals[token].unlocked -= amount;
        totals[token].locked += amount;

        emit LogLock(msg.sender, token, amount);
    }
```

After a while the user decides to withdraw their locked tokens. However, is unable to do so since the withdraw() function only caters for unlocked tokens

```
function withdraw(address token, uint256 amount) external whenNotPaused onlySupportedTokens(token) {
        balances[msg.sender][token].unlocked -= amount;
        balances[msg.sender][token].total -= amount;
        totals[token].unlocked -= amount;
        totals[token].total -= amount;
      ...
```

From above;
1. Alice deposits 1000000 USDC tokens without locking.
2. hence `balances[Alice][USDC].unlocked = 1000000000000` // 1000000 * 1e6
`balances[Alice][USDC].total = 1000000000000`
3. Alice then calls lock() to lock her deposited tokens.
  -> `balances[Alice][USDC].unlocked = 0`
     `balances[Alice][USDC].locked = 1000000000000`
     `balances[Alice][USDC].total = 1000000000000`
4. Alice is unable to withdraw her 1000000 USDC tokens since witdrawal is only possible on unlocked tokens.


## Tools Used
Manual

## Recommended Mitigation Steps
Add in a functionality for unlocking tokens for users to be able to withdraw their tokens voluntarily.


## Assessed type

ERC20
Changing Parameters using `setParameters` will create a sandwich opportunity
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


# Vulnerability details

## Impact

An attacker can steal some funds from the LPs when owner updates parameter like `_I_`.

## Proof of Concept

`_I_` is basically the market price provided by an oracle. So updating the value of `_I_` directly influences the price. 

```solidity

      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);
    }

```

Now consider the following scenario:

1. Consider the pool for Quote `Q` and Base `B` with a initial ideal price `I_Old`.
2. Assume `_K_` negligible for easier calculation, Price of `B` wrt `Q` now equals `I * Q`.
3. Let's assume owner wants to update `I_Old` to `I_New` value where `I_New` > `I_Old`.
4. Malicious user Alice observes the transaction in the mempool and exploits the parameter change.
5. Alice executes a front-running transaction, purchasing all available supply of the base asset (B).
6. Subsequently, Alice executes a back-run transaction, selling all previously acquired base assets at the higher price set by the parameter update.

In case, the owner's transaction is to decrease `_I_`, Alice can do the opposite and still end up getting the profit at the expense of Liquidity providers.

## Tools Used

VS Code

## Recommended Mitigation Steps

Implement a Mechanism such that calls to `sellBaseToken` and `sellQuoteToken` should revert whenever pool parameters are changed in that block.


## Assessed type

MEV
Protocol queries chainlink's deprecated pricing function which is not sufficiently validated and could return stale prices
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/oracles/aggregators/MagicLpAggregator.sol#L49-L51


# Vulnerability details


## Proof of Concept

Take a look at https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/oracles/aggregators/MagicLpAggregator.sol#L38-L51

```solidity
    function latestAnswer() public view override returns (int256) {
        uint256 baseAnswerNomalized = uint256(baseOracle.latestAnswer()) * (10 ** (WAD - baseOracle.decimals()));
        uint256 quoteAnswerNormalized = uint256(quoteOracle.latestAnswer()) * (10 ** (WAD - quoteOracle.decimals()));
        uint256 minAnswer = baseAnswerNomalized < quoteAnswerNormalized ? baseAnswerNomalized : quoteAnswerNormalized;

        (uint256 baseReserve, uint256 quoteReserve) = _getReserves();
        baseReserve = baseReserve * (10 ** (WAD - baseDecimals));
        quoteReserve = quoteReserve * (10 ** (WAD - quoteDecimals));
        return int256(minAnswer * (baseReserve + quoteReserve) / pair.totalSupply());
    }

    function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80) {
        return (0, latestAnswer(), 0, 0, 0);
    }
```

Evidently we can see that this is the logic for getting prices that's implemented in the protocol, note that a call to `latestRoundData()` actually [just queries latestAnswer() ](https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/oracles/aggregators/MagicLpAggregator.sol#L49-L51)

Now the price actually returned could be stale, note that this function also does not guarantee that the price returned by the Chainlink price feed is not stale and there is no additional checks to ensure that the return values are valid.

So If chainlink stops support, the function would stop working, causing querying prices to always revert.

## Impact

As explained under _Proof Of Concept_, if chainlink were to stop supporting their deprecated API functions as expected, protocol's method of querying prices would now **always revert.**

## Recommended Mitigation Steps

The `latestRoundData` function should be used instead of the deprecated `latestAnswer` function and add sufficient checks to ensure that the pricefeed is not stale.






## Assessed type

Oracle
MissingwhenNotpaused modifer 
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastOnboarding.sol#L164


# Vulnerability details

## Impact
Misiing whennotpaused modifier in function claimTokenYields , users still claim even protocol is paused 

 ## Proof of Concept

    function claimTokenYields(address[] memory tokens) external onlyOwner {
        for (uint256 i = 0; i < tokens.length; i++) {
            if (!supportedTokens[tokens[i]]) {
                revert ErrUnsupportedToken();
            }
            if (registry.nativeYieldTokens(tokens[i])) {
                BlastYields.claimAllTokenYields(tokens[i], feeTo);
            }
        }
    }


## Tools Used
## Recommended Mitigation Steps
    function claimTokenYields(address[] memory tokens) external Whennotpaused onlyOwner {
        for (uint256 i = 0; i < tokens.length; i++) {
            if (!supportedTokens[tokens[i]]) {
                revert ErrUnsupportedToken();
            }
            if (registry.nativeYieldTokens(tokens[i])) {
                BlastYields.claimAllTokenYields(tokens[i], feeTo);
            }
        }
    }



## Assessed type

Invalid Validation
No Slippage Protection in `buyShares` can lead to Losses for User
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


# Vulnerability details

## Impact

Loss of funds for users due to slippage during the execution of the `buyShares` function.

## Proof of Concept

The `buyShares`, is a `external` accessed function, serves as the entry point for liquidity providers to contribute liquidity and receive LP shares in return.

Here's it's implementation:

```solidity

      function buyShares(address to) external nonReentrant returns (uint256 shares, uint256 baseInput, uint256 quoteInput) {
        uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this));
        uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));
        uint256 baseReserve = _BASE_RESERVE_;
        uint256 quoteReserve = _QUOTE_RESERVE_;

        baseInput = baseBalance - baseReserve;
        quoteInput = quoteBalance - quoteReserve;

        // SNIP
        if (totalSupply() == 0) {
            // SNIP
        } else if (baseReserve > 0 && quoteReserve > 0) {
            // case 2. normal case
            uint256 baseInputRatio = DecimalMath.divFloor(baseInput, baseReserve);
            uint256 quoteInputRatio = DecimalMath.divFloor(quoteInput, quoteReserve);
            uint256 mintRatio = quoteInputRatio < baseInputRatio ? quoteInputRatio : baseInputRatio; // min of both
@->         shares = DecimalMath.mulFloor(totalSupply(), mintRatio);

            // SNIP
        }

        _mint(to, shares);
        _setReserve(baseBalance, quoteBalance);

        emit BuyShares(to, shares, balanceOf(to));
    }

```

Now consider the following scenario:

1. `Initial State:`

    * Alice wants to provide liquidity to the pool by buying LP shares.
    * The pool currently has a certain reserve of base and quote tokens.

2. `Change of Reserve:`

    * Prior to Alice's transaction, anyone calls `sellBase` or `sellQuote` which can change the reserve ratio of the pool.

3. `Alice's Transaction:`

    * Alice's transaction executes to buy LP shares using the `buyShares` function.
    * However, due to the changed reserve ratio, the amount of shares Alice receives for providing for liquidity differs significantly because of change in current reserve ratio.

Because of scenarios like this, user will receive fewer shares than anticipated, resulting in losses due to slippage. Therefore it's important to protect users by reverting in case of higher slippage.

## Tools Used

VS Code

## Recommended Mitigation Steps

Add `minShares` parameter and validate it in `buyShares` function itself just like how it is done in Router.


## Assessed type

MEV
`BlastOnboarding` contract: users deposits will be stuck if they have locked deposits after claiming
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastOnboardingBoot.sol#L155-L164
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastOnboarding.sol#L123-L130


# Vulnerability details

## Impact

- `BlastOnboardingBoot` contract enables users to deposit their tokens, and then stake their acquired LP share in the staking `LockingMultiRewards` contract to receive rewards for their stakes.

- When a user wants to stake his LP shares in the staking contract; he calls `BlastOnboardingBoot.claim()` fuction, where it first checks that the user hasn't staked his shares before in the staking multiRewards contract, then his shares are calculated via `_claimable()` function:

  ```javascript
      function claim(bool lock) external returns (uint256 shares) {
          if (!ready) {
              revert ErrNotReady();
          }
          if (claimed[msg.sender]) {
              revert ErrAlreadyClaimed();
          }

          shares = _claimable(msg.sender);
          if (shares == 0) {
              revert ErrNothingToClaim();
          }

          claimed[msg.sender] = true;
          staking.stakeFor(msg.sender, shares, lock);

          emit LogClaimed(msg.sender, shares, lock);
      }
  ```

- As can be noticed; the user can claim **only once** , and his claimable shares are calculated based on `his locked balance * total pool shares / total locked balance`:

  ```javascript
  function _claimable(address user) internal view returns (uint256 shares) {
          uint256 totalLocked = totals[MIM].locked + totals[USDB].locked;

          if (totalLocked == 0) {
              return 0;
          }

          uint256 userLocked = balances[user][MIM].locked + balances[user][USDB].locked;
          return (userLocked * totalPoolShares) / totalLocked;
      }
  ```

  where the user must first deposit a locked amount, or transfer his unlocked balance to a locked one via `BlastOnboarding.lock()` function:

  ```javascript
      function lock(address token, uint256 amount) external whenNotPaused onlyState(State.Opened) onlySupportedTokens(token) {
          balances[msg.sender][token].unlocked -= amount;
          balances[msg.sender][token].locked += amount;
          totals[token].unlocked -= amount;
          totals[token].locked += amount;

          emit LogLock(msg.sender, token, amount);
      }
  ```

- **But** if the user deposits a locked balance or transfer his unlocked balance to a locked one **after he claims**; then his deposited locked balance will be stuck and never be withdrawn by the user as there's no function that enables users from converting their locked balance to unlocked one to be withdrawn.

## Proof of Concept

[BlastOnboardingBoot.\_claimable function](https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastOnboardingBoot.sol#
`MagicLpAggregator.latestAnswer()` doesn't return the price in 18 decimals
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/oracles/aggregators/MagicLpAggregator.sol#L37-L46
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/oracles/aggregators/MagicLpAggregator.sol#L29-L31
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L163-L165


# Vulnerability details

## Impact

- `MagicLpAggregator.latestAnswer()` function is designed to return the price of base/quote token in 18 decimals (as the `MagicLpAggregator.decimals()` function will always return 18) regardless of the quote or base decimals:

```javascript
    function latestAnswer() public view override returns (int256) {
        uint256 baseAnswerNomalized = uint256(baseOracle.latestAnswer()) * (10 ** (WAD - baseOracle.decimals()));

        uint256 quoteAnswerNormalized = uint256(quoteOracle.latestAnswer()) * (10 ** (WAD - quoteOracle.decimals()));

        uint256 minAnswer = baseAnswerNomalized < quoteAnswerNormalized ? baseAnswerNomalized : quoteAnswerNormalized;

        (uint256 baseReserve, uint256 quoteReserve) = _getReserves();
        baseReserve = baseReserve * (10 ** (WAD - baseDecimals));
        quoteReserve = quoteReserve * (10 ** (WAD - quoteDecimals));
        return int256((minAnswer * (baseReserve + quoteReserve)) / pair.totalSupply());
    }
```

- Let's see the following scenario where the returned price is not correctly scaled to 18 decimals:

1.  `base` token is of 9 decimals, while `quote` token is of 6 decimals.

```javascript
    function latestAnswer() public view override returns (int256) {

        uint256 baseAnswerNomalized = uint256(baseOracle.latestAnswer()) * (10 ** (WAD - baseOracle.decimals()));

        uint256 quoteAnswerNormalized = uint256(quoteOracle.latestAnswer()) * (10 ** (WAD - quoteOracle.decimals()));

        uint256 minAnswer = baseAnswerNomalized < quoteAnswerNormalized ? baseAnswerNomalized : quoteAnswerNormalized;

        (uint256 baseReserve, uint256 quoteReserve) = _getReserves();
        baseReserve = baseReserve * (10 ** (WAD - baseDecimals));
        quoteReserve = quoteReserve * (10 ** (WAD - quoteDecimals));
        return int256((minAnswer * (baseReserve + quoteReserve)) / pair.totalSupply());
    }
```

2. Knowing that the oracles will return the price in 18 decimals, then:  
   `baseAnswerNomalized = 1e18 * 1e(18-18) = 1e18` ,
   `quoteAnswerNormalized = 1e18 * 1e(18-18) = 1e18`,  
    and the `minAnswer` is taken as the minimum between the two normalized answers: `quoteAnswerNormalized` and `baseAnswerNomalized` in `1e18` decimals

3. `baseReserve = 1e9 * 1e(18-9) = 1e9 * 1e9 = 1e18`,
   `quoteReserve = 1e6 * 1e(18-6) = 1e6 * 1e12 = 1e18`,

4. Knowing that `pair.totalSupply()` will return the shares total supply in the `base` token decimals (as per [`MagicLP.decimals()` function](https://github.com/code-
`LockingMultiRewards` contract: rewards calculation assumes that staking token is always of 18 decimals
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-L286
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/staking/LockingMultiRewards.sol#L292-L295


# Vulnerability details

## Impact

- `notifyRewardAmount()` function enables operator to add rewards for whitelisted tokens by the owner (the owner sets the whitelisted rewards tokens via `addReward()` function):

```javascript
    function notifyRewardAmount(address rewardToken, uint256 amount, uint minRemainingTime) public onlyOperators {
        if (!_rewardData[rewardToken].exists) {
            revert ErrInvalidTokenAddress();
        }

        _updateRewards();
        rewardToken.safeTransferFrom(msg.sender, address(this), amount);

        Reward storage reward = _rewardData[rewardToken];

        uint256 _nextEpoch = nextEpoch();
        uint256 _remainingRewardTime = _nextEpoch - block.timestamp;

        if (_remainingRewardTime < minRemainingTime) {
            revert ErrInsufficientRemainingTime();
        }

        // Take the remainder of the current rewards and add it to the amount for the next period
        if (block.timestamp < reward.periodFinish) {
            amount += _remainingRewardTime * reward.rewardRate;
        }

        // avoid `rewardRate` being 0
        if (amount < _remainingRewardTime) {
            revert ErrNotEnoughReward();
        }

        reward.rewardRate = amount / _remainingRewardTime;
        reward.lastUpdateTime = uint248(block.timestamp);
        reward.periodFinish = _nextEpoch;

        emit LogRewardAdded(amount);
    }
```

as can be noticed; the `reward.rewardRate` will have the same decimals of the rewards token.

- Whenever the global rewards are updated via `_updateRewardsGlobal()`; the `_rewardData[token_].rewardPerTokenStored` is calculated by `_rewardPerToken()` funtion:

  ```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
      }
  ```

  ```javascript
      function _rewardPerToken(address rewardToken, uint256 lastTimeRewardApplicable_, uint256 totalSupply_) public view returns (uint256) {
          if (totalSupply_ == 0) {
              return _rewardData[rewardToken].rewardPerTokenStored;
          }

          uint256 timeElapsed = lastTimeRewardApplicable_ - _rewardData[rewardToken].lastUpdateTime;
          uint256 pendingRewardsPerToken = (timeElapsed * _rewardData[rewardToken].rewardRate * 1e18) / totalSupply_;