xanoutas/solidity-security-findings huggingface dataset:
minLockAmount potential DoS
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/staking/LockingMultiRewards.sol#L102
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/staking/LockingMultiRewards.sol#L497-L499
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/staking/LockingMultiRewards.sol#L493-L495


# Vulnerability details

## Impact
The `createLock()` function's `minLockAmount` check doesn't consider token reward decimals. This means setting a reasonable minimum is impossible for tokens with decimals differing from the standard (e.g., USDC with 6 decimals). This exposes users to either very high minimums (low-decimal tokens) or very low minimums (high-decimal tokens), potentially enabling griefing attacks.

## Proof of Concept
In the case where a very high minimum is set for example, to suite a reasonable amount for a 18 decimal token, you might set 100e18 as the minimum which in the case of USDC would be an imposible minimum deposit to set a lock. Rendering the functionality unusable for all reward tokens with lower decimals.  

In the second case where the minimum is set to accomodate a token with lower decimals, the amount would be so low that will allow a malicious operator to front-run other users and create the maximum amount of locks using the `stakeFor()` priviliged function to stake on their behalf and reach the maximum locks. Esentially depriving users from the ability to create locks and benefit from the increased prices.

## Tools Used
Manual Analysis

## Recommended Mitigation Steps
To ensure adequate minimum lock amounts per reward token, add a `minLockAmount` field to the `Reward` struct within the existing `_rewardData` data map.

```solidity
   struct Reward {
        uint256 periodFinish;
        uint256 rewardRate;
        uint256 rewardPerTokenStored;
        bool exists;
        uint248 lastUpdateTime;
+       uint256 minLockAmount;
    }
```


## Assessed type

DoS
`_twapUpdate` should not take price after first transaction in a block.
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#L545-L558


# Vulnerability details

## Impact

The `_twapUpdate` takes pricedata after the first transaction in a block. This can be exploited by attackers who can manipulate the price and reset it back to the initial price at no external arbitrage risk unlike would have been incured if the pricedata was taken at the end of the block. 

## Proof of Concept

At the beginning of the block, when transactions are conducted, the TWAP is updated, subsequent transactions in the block will not be updated since `timeElapsed` will still be 0. 
Hence, an attacker can perform two transactions in one block which is first making a large swap through the reserves, which will change the TWAP price for the block since last update, then ending with a final swap back to the previous price, which will not reupdate the price as the elapsed time is still 0.
This will manipulate the price without inccuring any significant risks. 

```
    function _twapUpdate() internal {
        uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32);
        uint32 timeElapsed = blockTimestamp - _BLOCK_TIMESTAMP_LAST_;

        if (timeElapsed > 0 && _BASE_RESERVE_ != 0 && _QUOTE_RESERVE_ != 0) {
            /// @dev It is desired and expected for this value to
            /// overflow once it has hit the max of `type.uint256`.
            unchecked {
                _BASE_PRICE_CUMULATIVE_LAST_ += getMidPrice() * timeElapsed;
            }
        }

        _BLOCK_TIMESTAMP_LAST_ = blockTimestamp;
    }
```

## Tools Used
Manual code review

## Recommended Mitigation Steps
Take prices at the end of the block instead as an attacker that tries to manipulate the TWAP to an extreme value will have it there at the end of the block which opens them up to arbitrage losses.





## Assessed type

Other
Compounding rewards dilutes other staker's rewards gradually buying out their portion of rewards
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#L191-L194
https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/staking/LockingMultiRewards.sol#L597-L658


# Vulnerability details

## Impact
During the claim of rewards, pending rewards are locked to be released/available in the next epoch ~ 7 days. Large token holders or any holder can game the compounding feature to price other users out of their portion of the rewards.

## Proof of Concept
There still exists the issue of outbidding other stakers for the rewards. Although the Abracadabra team has implemented a locking system for the rewards and an epoch system of 7 days to release rewards from the previous epochs and lock new rewards for the current epoch, this supposed epoch and lock-up fix does not address the issue fully.

Let's take a look at the `_getRewards()` function implementation below:

```js
function _getRewards(address user) internal {
        RewardLock storage _rewardLock = _userRewardLock[user]; // @note gets the locked rewards for the caller

        // first ever lock is always expired because `unlockTime` is 0
        // unlock time is aligned to epoch
        bool expired = _rewardLock.unlockTime <= block.timestamp;

        // cache the length here since the loop will be modifying the array
        uint256 rewardItemLength = _rewardLock.items.length;

        // expired lock
        // existing lock items will be reused
        if (expired) {
            _rewardLock.unlockTime = nextEpoch(); // @note creates new unlock time for the new pending rewards in this epoch
            emit LogRewardLockCreated(user, _rewardLock.unlockTime);
        }

        for (uint256 i; i < rewardTokens.length; ) {
            address rewardToken = rewardTokens[i];
            uint256 rewardAmount = rewards[user][rewardToken];

            // in all scenario, reset the reward amount immediately
            rewards[user][rewardToken] = 0; // @note resets the user rewards to avoid double accruals

            // don't assume the rewardTokens array is always the same length as the items array
            // as new reward tokens can be added by the owner
            if (i < rewardItemLength) {
                RewardLockItem storage item = _rewardLock.items[i];

                // expired lock, claim existing unlocked rewards if any
                if (expired) {
                    uint256 amount = item.amount;

                    // since this current lock is expired and that item index
                    // matches the reward index, override the current amount
                    // with the new locked amount.
                    item.amount = rewardAmount;

                    // use cached amount
                    if (amount > 0) {
                        rewardToken.safeTransfer(user, amount); // @note releases rewards from previous epochs to now be available in this epoch
                        emit Lo
Reentrancy in swap function's  loop 
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#L541-L570
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/periphery/Router.sol#L365
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/periphery/Router.sol#L336
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/periphery/Router.sol#L314


# Vulnerability details

## Impact
Draining of reserve token A or Token B with flashloan when an arbitrage opportunity arise through the Loop in swap function .

## Proof of Concept 



this could be achieved by using the flashloan function  in ''MagicLP.sol'' - which allows for borrowing of assets without collateral .
arbitrage opportunity : when there is a price discrepancy between two tokens in the protocol (due to liquidity imbalances / oracle price feeds / sudden market moves and etc ) .

 the malicious actor sets up a malicious contract to abuse the loop in fuction swap sets the direction of the loop to ensure that the protocol sells Token A for Token B or the other way around  in a loop abusing the arbitrage opportunity (Token A has bigger reserve than Token B and etc. ) abusing the loop till reserve's are drained  or hits build in revert in the malicious contract when the desired amount of funds is drained ,after this he returns the flashloan and ends up with the profit made from depleting the Chosen Token reserve or taking the desired amount / abusing the arbitrage opportunity  .


## Tools Used
Manual review

## Recommended Mitigation Steps

1. Carefully implement the checks->effects->interactions pattern through Router.sol
2 Or Use reentrancy guard, the best known of which is OpenZeppelin’s ReentrancyGuard .
Avoid doing loops with external calls/buying and selling operations .



















## Assessed type

Reentrancy
[H-01] Initializing function is not working as intended in `MagicLP.sol`
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#L88
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L99


# Vulnerability details

## Impact

In `MagicLP.sol` , there is a [init](https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L91) function which initializes the contract and there is also a [bool internal `_INITIALIZED_`](https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L68) state variable which keep a check if contract is initialized or not and by default it has false value. But when the contract gets deployed the constructor and sets `_INITIALIZED_` variable true. After that when [init](https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L91) function get called for initialization , it get reverted throwing [ErrInitialized()](https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L100C20-L100C36).It would leave magicLP pools uninitialized thus breaking the core functioning of protocol. 

## Proof of Concept

1. MagicLP Pool will be created via `Factory.sol`.
2. The moment it get created, the [constructor](https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L84) will get executed which would set `_INITIALIZED_` variable true.
3. Immediately after which Factory would initialize the magicLP pool via `init` function but when it would enter the function , there it will check if  `_INITIALIZED_` variable is true or not .
4. Since constructor had set `_INITIALIZED_` variable to true, function will execute the code inside of `if` block , which would throw `ErrInitialized()` and will revert.

```solidity
function init(
        address baseTokenAddress,
        address quoteTokenAddress,
        uint256 lpFeeRate,
        address mtFeeRateModel,
        uint256 i,
        uint256 k
    ) external {
        if (_INITIALIZED_) {
            revert ErrInitialized();
        }
...

```  
* The below foundry test file shows how when `init` function called for magicLP pool initialization, will revert, leaving the contract uninitialized forever. 

```solidity

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity 0.8.20;

import {Test,console} from "forge-std/Test.sol";
import {MagicLP} from "../src/mimswap/MagicLP.sol";

contract MagicLPTest is Test{
    MagicLP magicLp;
    function setUp() public{
        // Owner is address(this)
        magicLp=new MagicLP(address(this));
    }
    function test_Init() public{
        address baseTokenAddress=makeAddr("bt");
        address quoteTokenAddress=makeAddr("qt");
`pool` can never be initialized in `BlastOnboardingBoot` and therefore `bootstrap` can never be called
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
In `BlastOnboardingBoot`, `bootstrap` relies on `pool` variable being set

[BlastOnboardingBoot.sol#L96-L127](https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/blast/BlastOnboardingBoot.sol#L96-L127)
```solidity
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, address(this), baseAmount, quoteAmount);

        if (totalPoolShares < minAmountOut) {
            revert ErrInsufficientAmountOut();
        }

        // Create staking contract
        // 3x boosting for locker, 7 days reward duration, 13 weeks lp locking
        // make this contract temporary the owner the set it as an operator
        // for permissionned `stakeFor` during the claiming process and then
        // transfer the ownership to the onboarding owner.
        staking = new LockingMultiRewards(pool, 30_000, 7 days, 13 weeks, address(this));
        staking.setOperator(address(this), true);
        staking.transferOwnership(owner);

        // Approve staking contract
        pool.safeApprove(address(staking), totalPoolShares);

        emit LogLiquidityBootstrapped(pool, address(staking), totalPoolShares);

        return (pool, address(staking), totalPoolShares);
    }
```

A big problem is that it can never be set in the `BlastOnboardingBoot` contract so it will always be `address(0)`. Therefore `bootstrap` always fails.

## Tools Used

Manual Review

## Recommended Mitigation Steps

Initialize `pool` variable in `initialize` or create a `setPool` function to set the `pool` variable.








## Assessed type

Other
Gas and token yield from `BlastOnboardingBoot` proxy will be sent to address(0) because of unitialized `feeTo`
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

`feeTo` is not immutable so the value in the implementation will not be shared with its proxy.

[BlastOnboarding.sol#L32](https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/blast/BlastOnboarding.sol#L32)
```solidity
    address public feeTo;
```

In `initialize()`, we also don't initialize `feeTo` in the proxy

[BlastOnboardingBoot.sol#L129-L133](https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/blast/BlastOnboardingBoot.sol#L129-L133)
```solidity
    function initialize(Router _router) external onlyOwner {
        router = Router(payable(_router));
        factory = IFactory(router.factory());
        emit LogInitialized(_router);
    }
```

This means when calling `claimGasYields` or `claimTokenYields` in the proxy, gas yield will be sent to the zero address since its uninitialized in the proxy.

[BlastOnboarding.sol#L160-L173](https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/blast/BlastOnboarding.sol#L160-L173)
```solidity
    function claimGasYields() external onlyOwner returns (uint256) {
        return BlastYields.claimMaxGasYields(feeTo);
    }

    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
 
Manual Review

## Recommended Mitigation Steps

Either make `feeTo` immutable or initialize it in `initialize()`











## Assessed type

Other
`BlastOnboardingBoot` proxy does not configure Blast gas yield mode
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The `BlastOnboardingBoot` proxy does not configure their yield mode using `BlastYields.configureDefaultClaimables` in their `initialize()` function

[BlastOnboardingBoot.sol#L129-L133](https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/blast/BlastOnboardingBoot.sol#L129-L133)
```solidity
    function initialize(Router _router) external onlyOwner {
        router = Router(payable(_router));
        factory = IFactory(router.factory());
        emit LogInitialized(_router);
    }
```

Although an admin can still call `callBlastPrecompile` on the proxy after realising it. The gas yield accrued from when the proxy is deployed to when  `callBlastPrecompile` is called is lost forever and can never be claimed.

[BlastOnboarding.sol#L160-L162](https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/blast/BlastOnboarding.sol#L160-L162)
```solidity
    function claimGasYields() external onlyOwner returns (uint256) {
        return BlastYields.claimMaxGasYields(feeTo);
    }
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

Since it's a proxy, the `BlastYields.configureDefaultClaimables` also needs to be called in `initialize` to configure a proxy's gas yield mode. You may also want to call `BlastPoints.configure` in `initialize` as well.





















## Assessed type

Other
Ability to adjust _I_ creates sandwich opportunity 
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#L470-L502


# Vulnerability details

## Impact
Adjusting the value of "I" directly influences the price. This can be exploited by a MEV bot, simply by trading just before the "adjustPrice" 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.

## Proof of Concept
In a PMM, _I_ is directly linked to pricing, changing it changes the price. Bots in mem pool can monitor it and front run and sell the base token and get the quote token and immediately after price change sell the quote tokens to get more of base token.

This problem arise in the following code snippet. 

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

Exact similar issue was reported in the dodo v3 sherlock competition.

https://solodit.xyz/issues/m-1-adjusting-_i_-will-create-a-sandwich-opportunity-because-of-price-changes-sherlock-dodo-gsp-git

## Tools Used
Solodit

## Recommended Mitigation Steps
This function is originally not present in the original implementation of DODO V2 and PMM don't need it to, let the free market decide the pricing of asset instead of controlling it via admin. So removing this function and still all the functionality will work fine. 


## Assessed type

Other
Denial of Service
Audit: 2024-03-abracadabra-money-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

 Denial of Service: processExpiredLocks could be blocked by a single user with an expired lock, preventing others from processing.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

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; ) {
            address user = users[i];
            Balances storage bal = _balances[user];
            LockedBalance[] storage locks = _userLocks[user];

            if (locks.length == 0) {
                revert ErrNoLocks();
            }

            uint256 index = lockIndexes[i];

            // Prevents processing `lastLockIndex` out of order
            if (index == lastLockIndex[user] && locks.length > 1) {
                revert ErrInvalidLockIndex();
            }

            // prohibit releasing non-expired locks
      @>      if (locks[index].unlockTime > block.timestamp) {
                revert ErrLockNotExpired();
            }

            uint256 amount = locks[index].amount;
            uint256 lastIndex = locks.length - 1;

            /// Last lock index changed place with the one we just swapped.
            if (lastLockIndex[user] == lastIndex) {
                lastLockIndex[user] = index;
            }

            if (index != lastIndex) {
                locks[index] = locks[lastIndex];
                emit LogLockIndexChanged(user, lastIndex, index);
            }

            locks.pop();

            unlockedSupply += amount;
            lockedSupply -= amount;

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

            emit LogUnlocked(user, amount, index);

            unchecked {
                ++i;
            }
        }
    }


## Tools Used

## Recommended Mitigation Steps

we should call each function individual so that revert cannot cause DOS.


## Assessed type

Context