xanoutas/solidity-security-findings huggingface dataset:
No access control on init() in BlastCauldronV4 contract
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The `BlastCauldronV4.init()` function initializes the contract after deployment, However there is no access control for the public function, making any user to be able to initialize the contract with arbitrary input to set the state variables for `collateral, oracle, oracleData, accrueInfo.INTEREST_PER_SECOND, LIQUIDATION_MULTIPLIER, COLLATERIZATION_RATE, BORROW_OPENING_FEE`

Additionally, the function can be front-runned since there is no access control.


## Proof of Concept
A direct or frontrunned call to 
https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/blast/BlastWrappers.sol#L59-L72
```
function init(bytes calldata data) public payable override {
        if (_governor == address(this)) {
            revert ErrInvalidGovernorAddress();
        }

        _setupBlacklist();

        super.init(data);
        BlastYields.configureDefaultClaimables(_governor);
    }

    function _setupBlacklist() private {
        blacklistedCallees[address(BlastYields.BLAST_YIELD_PRECOMPILE)] = true;
    }
```
`BlastCauldronV4.init()` would set the `collateral, oracle, oracleData, accrueInfo.INTEREST_PER_SECOND, LIQUIDATION_MULTIPLIER, COLLATERIZATION_RATE, BORROW_OPENING_FEE` state variables in

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/cauldrons/CauldronV4.sol#L137-L152

```
 require(address(collateral) == address(0), "Cauldron: already initialized");
        (collateral, oracle, oracleData, accrueInfo.INTEREST_PER_SECOND, LIQUIDATION_MULTIPLIER, COLLATERIZATION_RATE, BORROW_OPENING_FEE) = abi.decode(data, (IERC20, IOracle, bytes, uint64, uint256, uint256, uint256));
        borrowLimit = BorrowCap(type(uint128).max, type(uint128).max);
        require(address(collateral) != address(0), "Cauldron: bad pair");

        magicInternetMoney.approve(address(bentoBox), type(uint256).max);

        blacklistedCallees[address(bentoBox)] = true;
        blacklistedCallees[address(this)] = true;
        blacklistedCallees[Owned(address(bentoBox)).owner()] = true;

        (, exchangeRate) = oracle.get(oracleData);
```


## Tools Used
Manual Review

## Recommended Mitigation Steps
1. Apply access controls to functions that should be called by authorized entities.
2. Contract can also be initialized during deployment if access control is not considered.


## Assessed type

Access Control
`LockingMultiRewards::_userRewardLock` doesn't update when token is locked cause wrong reward calculation
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/staking/LockingMultiRewards.sol#L479-L514


# Vulnerability details

## Impact
Users may receive fewer rewards than they should.

## Proof of Concept
`LockingMultiRewards::_userRewardLock` modified just in `_getRewards` function. This function called in `withdrawWithRewards` and `getRewards` functions. If a user locks tokens and doesn't call either of these two functions (which don't make sense to call after depositing), the calculation will be incorrect. When time passes and the user wants to claim their rewards, `_userRewardLock` will have just saved this value in [this line](https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/staking/LockingMultiRewards.sol#L648). 

## Tools Used
Manual Review
## Recommended Mitigation Steps
`LockingMultiRewards::_userRewardLock` should be added in function `_createLock`. 
Or per user's locked amount should stored just in one storage variable. Now there are 3 mappings (`_balances`, `_userLocks` and `_userRewardLock`) that stores per user's locked amount. 


## Assessed type

Context
Attacker can call `removeLiquidity` on the router contract with `sharesIn` greater than their actual balance. 
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/periphery/Router.sol#L271
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/periphery/Router.sol#L287
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L424


# Vulnerability details

## Impact
This vulnerability in the router contract enables users to withdraw more liquidity than their own total actual share, potentially leading to financial losses and compromising the system's integrity. Malicious actors will exploit this loophole to manipulate liquidity pools and disrupt market stability as they will get more 'baseAmount' and 'quoteAmount' than they would have gotten with their actual `shareAmount` balance.     

## Proof of Concept
Inside `removeLiquidity` function of Router contract, the `router.sol` contract gets set as `msg.sender` when router contract calls `IMagicLP(lp).sellShares(sharesIn, to, minimumBaseAmount, minimumQuoteAmount, "", deadline);` 

So in contract `MagicLP.sol` in function ` sellShares` when it checks the condition `if (shareAmount > balanceOf(msg.sender)) {
            revert ErrNotEnough();
        }` at that time it checks if `shareAmount` passed is bigger than the balance of `msg.sender` ie in this situation balance of `router contract`. 

But here protocol needs to check if the original `msg.sender` of `removeLiquidity` function in `Router.sol` contract is having the actual share amount that he is passing in function `shareAmount`. If not then it will revert!

Proof Of Concept - Here's an exploit scenario involving two users, Alice and Bob  

```
Setup:
Alice has 100 LP tokens in MagicLP.
Bob has 50 LP tokens in MagicLP.
The router contract currently holds 200 LP tokens (from previous transactions).

Exploit:
Alice calls removeLiquidity on the router contract, requesting to remove 150 LP tokens, which is more than her balance but less than the router's balance.

The router contract calls safeTransferFrom, transferring 150 LP tokens from Alice to itself. This succeeds because the router has enough LP tokens.

The router contract then calls sellShares on the MagicLP contract with 150 LP tokens as sharesIn.

The sellShares function checks the router's balance, which is sufficient, and proceeds with the transaction.

Alice receives assets for 150 LP tokens, despite only owning 100 LP tokens.

Outcome:
Alice has exploited the system to withdraw more assets than her share of the liquidity pool.

Bob and other liquidity providers are affected because the pool's asset balance is reduced by more than Alice's actual share, potentially diluting their remaining shares.
``` 
## Tools Used
Manual Review
## Recommended Mitigation Steps
To prevent this exploit, the router contract's `removeLiquidity` function must ensure that the sharesIn amount does not exceed the
Implementation in  FlashLoan can lead to  manipulation of reserves and drain funds in pool 
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L290-L353
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L567-L579
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L134-L136


# Vulnerability details

## Impact
The implementation in `MagicLP::flashLoan()` function can lead to manipulation of `_BASE_RESERVE_` and `_QUOTE_RESERVE_` , bypass all `if statement checks` and drain all funds in pool

## Proof of Concept
When calls `MagicLP::flashLoan()`, 
`_BASE_TOKEN_` and `_QUOTE_TOKEN_` transfer to the contract owner,
```
 _transferBaseOut(assetTo, baseAmount);
 _transferQuoteOut(assetTo, quoteAmount);
```
in callback : if we call function sync( ), it will update both `_BASE_RESERVE_` to `_BASE_TOKEN_.balanceOf(address(this)` and `_QUOTE_RESERVE_` to `_QUOTE_TOKEN_.balanceOf(address(this))`
```
 if (data.length > 0) {
            ICallee(assetTo).FlashLoanCall(msg.sender, baseAmount, quoteAmount, data);
        }
```
```
 function _sync() internal {
        uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this));
        uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));

        if (baseBalance != _BASE_RESERVE_) {
            _BASE_RESERVE_ = baseBalance.toUint112();
        }
        if (quoteBalance != _QUOTE_RESERVE_) {
            _QUOTE_RESERVE_ = quoteBalance.toUint112();
        }

        _twapUpdate();
    }
```

following with getting baseBalance and quoteBalance
```
 uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this));
 uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));
```

and bypass all if statement checks 
```

        // no input -> pure loss
        if (baseBalance < _BASE_RESERVE_ && quoteBalance < _QUOTE_RESERVE_) {
            revert ErrFlashLoanFailed();
        }

        // sell quote case
        // quote input + base output
        if (baseBalance < _BASE_RESERVE_) {
            uint256 quoteInput = quoteBalance - uint256(_QUOTE_RESERVE_);
            (uint256 receiveBaseAmount, uint256 mtFee, PMMPricing.RState newRState, uint256 newQuoteTarget) =
                querySellQuote(tx.origin, quoteInput);

            if (uint256(_BASE_RESERVE_) - baseBalance > receiveBaseAmount) {
                revert ErrFlashLoanFailed();
            }

            _transferBaseOut(_MT_FEE_RATE_MODEL_.maintainer(), mtFee);
            if (_RState_ != uint32(newRState)) {
                _QUOTE_TARGET_ = newQuoteTarget.toUint112();
                _RState_ = uint32(newRState);
                emit RChange(newRState);
            }
            emit Swap(address(_QUOTE_TOKEN_), address(_BASE_TOKEN_), quoteInput, receiveBaseAmount, msg.sender, assetTo);
        }

        // sell base case
        // base input + quote output
        if (quoteBalance < _QUOTE_RESE
Incorrect Method of Price calculation in `MagicLpAggregator` Allows for LP Price Manipulation
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/oracles/aggregators/MagicLpAggregator.sol#L45


# Vulnerability details

## Impact

A malicious actor can manipulate LP pricing, similar to the exploit witnessed in the [`Warp Finance Hack`](https://rekt.news/warp-finance-rekt/).

## Proof of Concept

The `latestAnswer` function in `MagicLpAggregator` is used to calculate the current price of the LP token for a given pool.

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

```

Calculation Process:

1. Obtain the current price of the base token and normalize it to 18 decimals.
2. Obtain the current price of the quote token and normalize it to 18 decimals.
3. Retrieve the current reserves of base and quote tokens of the pool and normalize them to 18 decimals.
4. Calculate the price using the formula:

> minAnswer * (baseReserve + quoteReserve) / pair.totalSupply()

Here, The issue lies in the 3rd point: return value from the `_getReserves()` function, which is susceptible to manipulation. This function retrieves the spot reserves of the pool. So When a malicious user dumps large amount of any token into the pool, `baseReserve + quoteReserve` will be significantly increased. Thus, this value should not be utilized for pricing LP tokens due to its volatility caused by trades executed within the pool.

`Warp Finance` used the formula: `r0 * p0 + r1 * p1` to calculate their TVL similar to how `MagicLpAggregator` contract is using: `min(p0, p1). r0 + min(p0, p1). r1`, a method that has proven susceptible to exploitation.

Given the ability to alter spot reserves, this vulnerability enables anyone to artificially inflate the price within a single block without incurring any risk, utilizing flashloans. Thus it is very important to mitigate this risk.

## Tools Used

Manual Analysis

## Recommended Mitigation Steps

Use different approach to price the LP in better way as shown [here](https://cmichel.io/pricing-lp-tokens/)


## Assessed type

Context
`MagicLp` contract: pool can be drained if any of the reserves are empty
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/libraries/PMMPricing.sol#L92-L95
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/libraries/PMMPricing.sol#L59-L63


# Vulnerability details

## Impact

- `MagicLp` contract has `sellBase` function that enables users to sell their base tokens for a quote token in return, and it has `sellQuote` function that enables users to sell their quote tokens and get base tokens in return (from the contract reserves), where users trade their tokens with the contract tokens reserves.

- But these functions (`sellBase` & `sellQuote`) allow sellers to purchase the contract reserves without a limit, where any user can empty the base or quote reserves as there's no limit on the amount of tokens being purchased from the contract.

- So for example:
  - if the quote token reserve is emptied, then `_QUOTE_TARGET_` & `_QUOTE_RESERVE_` will be zeros (`state.Q0` & `state.Q` in `getPMMState()`),
  - then any malicious user can sell 0 quote token amount to withdraw the whole base token balance minus `_BASE_TARGET_`, by calling `MagicLP.querySellQuote(trader,0)`.
  - this is caused as the edge case when the target is zero is not checked by the `PMMPricing.sellQuoteToken`:

```javascript
function sellQuoteToken(PMMState memory state, uint256 payQuoteAmount) internal pure returns (uint256 receiveBaseAmount, RState newR) {
        if (state.R == RState.ONE) {
           //some code...
        } else if (state.R == RState.ABOVE_ONE) {
            //some code...
        } else {
            uint256 backToOnePayQuote = state.Q0 - state.Q; // << @audit-issue : when the quote reserve is emptied; then the reserve and target will be zero, thus backToOnePayQuote == 0
            uint256 backToOneReceiveBase = state.B - state.B0;
            if (payQuoteAmount < backToOnePayQuote) {

            //some code...

            } else if (payQuoteAmount == backToOnePayQuote) {// << @audit-issue : doesn't check if the payQuoteAmount || backToOnePayQuote == 0
                receiveBaseAmount = backToOneReceiveBase;
                newR = RState.ONE;
            } else {
               //some code...
            }
        }
    }

```

- This can be achieved by a flash-loan attack as well (via `flashLoan()` function).

## Proof of Concept

[PMMPricing.sellQuoteToken function/ L92-L95](https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/libraries/PMMPricing.sol#L92-L95)

```javascript
 } else if (payQuoteAmount == backToOnePayQuote) {
                receiveBaseAmount = backToOneReceiveBase;
                newR = RState.ONE;
            } else {
```

[PMMPricing.sellBaseToken function/L59-L63](https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/libraries/PMMPricing.sol#L59-L63
`buyShares` function will never work and always DoS when deployed
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Permanent DoS because of revert in the function.

## Proof of Concept

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

Here's its implementation:

```solidity

    function buyShares(address to) external nonReentrant returns (uint256 shares, uint256 baseInput, uint256 quoteInput) {
      
      // SNIP //

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

        if (_QUOTE_TARGET_ == 0) {
            revert ErrZeroQuoteTarget();
        }

        if (shares <= 2001) {
            revert ErrMintAmountNotEnough();
        }

@->     _mint(address(0), 1001);
        shares -= 1001;
      }

      // SNIP

```

This contract, derived from `DODOStablePool`, shares the same code for the `buyShares` function. However, there's a crucial difference in the implementation approach between `MagicLP` and `DODOStablePool`.

`DODOStablePool` manually adds all required functions to make the contract `ERC20` compliant, while `MagicLP` opts to inherit OpenZeppelin's ERC20 implementation to ensure adherence to standards and mitigate potential errors.

While this change seems innocuous, there's a significant discrepancy in how the `_mint` function is handled.

In `DODOStablePool` (`DSPVault`), `_mint` is defined as follows:

```solidity
File: DSPVault.sol

    function _mint(address user, uint256 value) internal {
        require(value > 1000, "MINT_AMOUNT_NOT_ENOUGH");
        _SHARES_[user] = _SHARES_[user].add(value);
        totalSupply = totalSupply.add(value);
        emit Mint(user, value);
        emit Transfer(address(0), user, value);
    }

```

However, in OpenZeppelin's implementation, `_mint` is guarded against minting to the zero address:

```solidity
File: ERC20.sol

    function _mint(address account, uint256 value) internal {
@->     if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

```

As evident, OpenZeppelin's version prohibits minting to the zero address. This discrepancy causes the `buyShares` function to revert consistently in a production.

## Tools Used

VS Code

## Recommended Mitigation Steps

Instead of minting to the zero address, consider minting to an external contract that cannot transfer those tokens.





## Assessed type

DoS
Incorrect deployment of `MagicLP` will make zero address as owner and DoS every `onlyImplementationOwner` modifier functions
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Incorrect deployment leading to Permanent DoS in all `onlyImplementationOwner` modifier functions of `MagicLP`.

## Proof of Concept

The `create` function within the Factory contract is responsible for deploying a new clone of the `MagicLP` contract implementation:

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

```

However, the `MagicLP` implementation contract utilizes a constructor with an `owner_` parameter:

```solidity

    constructor(address owner_) Owned(owner_) {
        implementation = this;

        // prevents the implementation contract initialization
        _INITIALIZED_ = true;
    }

```

In Solidity, constructor code is executed only once during contract deployment, and it does not exist in the runtime bytecode. Consequently, the constructor code within the `MagicLP` implementation will never execute in the context of a clone's state.

This results in the following issue:

The `owner` is never initialized in the `MagicLP` contract, defaulting to `address(0)`.

```solidity
File: Owned.sol

  function transferOwnership(address newOwner) public virtual onlyOwner {
        owner = newOwner;

        emit OwnershipTransferred(msg.sender, newOwner);
    }

``` 
As the `owner` is `address(0)`, any attempt to execute functions requiring `onlyImplementationOwner` modifiers will fail, causing a DoS scenario. Functions such as `rescue`, `setParameters`, and `ratioSync` will be inaccessible due to the inability to transfer ownership from `address(0)`.

## Tools Used

VS Code

## Recommended Mitigation Steps

1. Remove the constructor from the `MagicLP` contract.

2. Update the `create` method to pass the creator's address during initialization:

```diff

    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_);
+       IMagicLP(clone).init(address(baseToken_), address(quoteToken_