xanoutas/solidity-security-findings huggingface dataset:
Potential unfair reward accumulation in between `processExpiredLocks()` calls
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#L397


# Vulnerability details

## Impact
Attacker can block stuff with dummy transactions in between `processExpiredLocks()` calls, to gain unfair reward calculation. 

## Proof of Concept
The `processExpiredLocks()` function is responsible for handling expired locks at the start of each epoch. Gelato manages this process per sponsor. The function conducts various checks before unlocking tokens.
```solidity
    function processExpiredLocks(address[] memory users, uint256[] calldata lockIndexes) external onlyOperators {
        if (users.length != lockIndexes.length) {
            revert ErrLengthMismatch();
        }

        _updateRewardsForUsers(users);

        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];

            if (index == lastLockIndex[user] && locks.length > 1) {
                revert ErrInvalidLockIndex();
            }

            if (locks[index].unlockTime > block.timestamp) {
                revert ErrLockNotExpired();
            }

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

            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;
            }
        }
    }
```
During execution, the function adjusts state variables such as `unlockedSupply`, `lockedSupply`, `bal.unlocked`, and `bal.locked`. These variables are crucial for determining `totalSupply()` and `balanceOf()`.

As this function may be called multiple times due to potential out-of-gas concerns, it creates opportunities for unfair staking reward accumulation between calls. An attacker whose `balanceOf()` to become a significant portion of the `totalSupply()` due to previous calls that release expired locks. Subsequently, the attacker could take advantage of reward calculations by strategically stuffing blocks with dummy transactions for profitability. This risk is particularly pronounced in layer 2 networks.


## Tools Used
Manual Review
## Recommended Mitigation Steps
Consider allowing users to unlock by themselves after certain `lockDuration`.


## Assessed type

Context
Users who deposited MIM and USDB tokens into BlastOnboarding may incur losses when the pool is created via bootstrap
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastOnboarding.sol#L104-L106
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastOnboardingBoot.sol#L101-L106
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/periphery/Router.sol#L68-L70
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L381-L383
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L381-L383
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/MagicLP.sol#L171
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/libraries/PMMPricing.sol#L194-L199
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/mimswap/libraries/PMMPricing.sol#L59-L63


# Vulnerability details

## Impact
Users can deposit `MIM` and `USDB` tokens into `BlastOnboarding`. 
Once `locked`, these tokens cannot be `unlocked`. 
The `locked` tokens will be utilized to establish a `MagicLP MIM/USDB pool` through `bootstrap`. 
Although the prices of `MIM` and `USDB` tokens are nearly identical, there is no assurance regarding the `locked` amounts of `MIM` and `USDB` tokens. 
Significant differences between the `locked` amounts may exist. 
Depending on this difference, the `K` value, and the chosen funds by the attacker, substantial `funds` can be stolen.

The vulnerability stems from the `createPool` function in the `Router`.
Consequently, any user who creates a `MagicLP pool` using this function is susceptible to `fund` loss.
## Proof of Concept
Users can deposit and `lock` `MIM` and `USDB` tokens into `BlastOnboarding`.
```
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;
    }
}
```
These tokens cannot be `unlocked`. 
They will be used to create a `MagicLP MIM/USDB pool` during `bootstrap`.
```
function bootstrap(uint256 minAmountOut) external onlyOwner onlyState(State.Closed) returns (address, address, uint256) {
    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);
}
```
All `locked` tokens are utilized; there is no way to use only a portion of them.
Additionally, there
Oracle price can be manipulated
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#L42-L45


# Vulnerability details

## Impact
Oracle price can be manipulated. 

## Proof of Concept
MagicLpAggregator uses pool reserves to calculate the price of the pair token,
```solidity
    function _getReserves() internal view virtual returns (uint256, uint256) {
        (uint256 baseReserve, uint256 quoteReserve) = pair.getReserves();
    }

    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());
    }
```
however reserve values can be manipulated. For example, an attacker can use a flash loan to inflate the pair price, see coded POC below

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "utils/BaseTest.sol";
import "oracles/aggregators/MagicLpAggregator.sol";

// import "forge-std/console2.sol";

interface IDodo {
    function getVaultReserve() external view returns (uint256 baseReserve, uint256 quoteReserve);
    function _QUOTE_TOKEN_() external view returns (address);
    function sellBase(address to) external returns (uint256);
    function sellQuote(address to) external returns (uint256);
}

interface IFlashMinter {
    function flashLoan(address, address, uint256, bytes memory) external;
}

contract MagicLpAggregatorExt is MagicLpAggregator {

    constructor(
        IMagicLP pair_,
        IAggregator baseOracle_,
        IAggregator quoteOracle_
    ) MagicLpAggregator(pair_, baseOracle_, quoteOracle_) {}

    function _getReserves() internal view override returns (uint256, uint256) {
        return IDodo(address(pair)).getVaultReserve();
    }

}

contract Borrower {
    IFlashMinter private immutable minter;
    IDodo private immutable dodoPool;
    MagicLpAggregator private immutable oracle;

    constructor(address _minter, address _dodoPool, address _oracle) {
        minter = IFlashMinter(_minter);
        dodoPool = IDodo(_dodoPool);
        oracle = MagicLpAggregator(_oracle); 
    }

    /// Initiate a flash loan
    function flashBorrow(address token, uint256 amount) public {
        IERC20Metadata(token).approve(address(minter), ~uint256(0));
        minter.flashLoan(address(this), token, amount, "");
    }
    /// ERC-3156 Flash loan callback
    function onFlashLoan(
        address initiator,
        address tok
_remainingRewardTime calculation is wrong in the function notifyRewardAmount.
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#L372


# Vulnerability details

## Impact
reward.rewardRate will unfairly increase which leads some users getting more rewards and some users getting no or less rewards.


## Proof of Concept
1. Let assume, reward duration = 7days, the function notifyRewardAmount is called on january 1, so  reward.periodFinish is set to january 7.

2. On January 4,  function notifyRewardAmount is called again with some reward amount, so here _remainingRewardTime  should be = (january 7 - january 4) = 3 days.but _remainingRewardTime  gives 7 days which is wrong.

3. Let's see how? See function notifyRewardAmount, now  _nextEpoch = january 11(january 4+7 days), so _remainingRewardTime = january 11 - january 4 = 7 days.

## Tools Used
manual review
## Recommended Mitigation Steps
Implement _remainingRewardTime properly. 




## Assessed type

Context
Attackers steal/drain rewards from LockingMultiRewards contract.
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#L293


# Vulnerability details

## Impact

Attackers steal/drain rewards from LockingMultiRewards contract.

## Proof of Concept
1. Let's assume, Alice has staked 100 amount, Bob has staked 100 amount and currently totalsupply = 200 and there is only one reward token.

2.  function notifyRewardAmount is called with 200 reward and  let assume _rewardData[token_].rewardPerTokenStored i.e rewardPerToken becomes 1e18 .

3. function _udpateUserRewards is called for alice and bob,as userRewardPerTokenPaid[user][rewardToken] = 0 for alice and bob, so pendingUserRewardsPerToken = 1e18 for both.now alice’s earned token = 100, bob’s earned token = 100.userRewardPerTokenPaid[user][rewardToken]  is set to 1e18 for both.

4. function notifyRewardAmount is again called with 200 reward and  let assume now_rewardData[token_].rewardPerTokenStored i.e rewardPerToken becomes 2e18 

5. Now john stakes 100 tokens , function _udpateUserRewards is called for john,as userRewardPerTokenPaid[user][rewardToken] = 0 for john(this is attack vector),so pendingUserRewardsPerToken = 2e18 for john.now john’s earned token = 200, 

6. After that , function _udpateUserRewards is called for alice and bob,as userRewardPerTokenPaid[user][rewardToken] = 1e18 for alice and bob, so pendingUserRewardsPerToken = 1e18 for both.now alice’s earned token = 100, bob’s earned token = 100. So Alice, Bob,John have earned 400 reward tokens but there are only 200 reward tokens in the contract.John can claim those reward tokens before Alice and bob. Now alice and have less reward tokens.

7. John can transfer this 100 amount of tokens to different accounts  and can always steal rewards from the contract. 

## Tools Used
manual review
## Recommended Mitigation Steps
If userRewardPerTokenPaid[user][rewardToken] = 0 for a user, update userRewardPerTokenPaid[user][rewardToken] to  _rewardData[rewardToken].rewardPerTokenStored.



## Assessed type

Context
ETH sent to contract `Router` 'receive()' function from non-WETH addresses will be locked and lost
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The contract 'Router' has a payable empty 'receive()' function, but the funds received this way from non-WETH addresses are NOT used by the contract at all and can NEVER be transferred out of the contract. Thus those funds are locked and lost forever.

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

## Tools Used

## Recommended Mitigation Steps
Recommend adding a check in the function 'receive()' to make sure only WETH can send ethers to the contract. See <https://github.com/Uniswap/v2-periphery/blob/master/contracts/UniswapV2Router02.sol#L29>





## Assessed type

ETH-Transfer
BlastOnboarding::deposit() can result in permanent locking tokens 
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastOnboarding.sol#L101-L121
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastOnboarding.sol#L123-L130
https://github.com/code-423n4/2024-03-abracadabra-money/blob/1f4693fdbf33e9ad28132643e2d6f7635834c6c6/src/blast/BlastOnboarding.sol#L132-L141


# Vulnerability details

## Impact
BlastOnboarding contract allows the users to deposit tokens into the contract with option to deposit or to deposit and lock using the deposit() function.

Tokens deposited and not locked can be locked by calling lock() function.

But, there is no provision to unlock the locked tokens. As such, the tokens that were locked cannot be locked and would be stuck as there is no way to withdraw the locked tokens.


## Proof of Concept
In the deposit function, the caller can direct to lock the tokens by passing lock_ flag as true. If true, the deposited tokens are locked in the contract.

The lock() and withdraw() functions work on the unlocked tokens only. So, the tokens that were locked will be permanently locked in the contract.

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

        totals[token].total += amount;

        if (caps[token] > 0 && totals[token].total > caps[token]) {
            revert ErrCapReached();
        }

        balances[msg.sender][token].total += amount;

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

## Tools Used
Manual review.

## Recommended Mitigation Steps
Implement a provision for the locked tokens to be unlocked.


## Assessed type

Other
BlastCauldronV4::init() can be called by anyone to initialize/Reinitialize CauldronV4 contract
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Using init, the caller can reinitialise the CauldronV4() with the data passed by the caller. As there is no restriction on who can call, any one should be able to call and reinitialise the CauldronV4() contract.

## Proof of Concept
The caller can pass the configuration information for CauldronV4() via the init function and set value prescribed by the caller.

```solidity
   function init(bytes calldata data) public payable override {
        if (_governor == address(this)) {
            revert ErrInvalidGovernorAddress();
        }

        _setupBlacklist();

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

    CauldronV4 contract:: init()
    ========================================================================

    function init(bytes calldata data) public virtual payable override {
        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);

        accrue();
    }

```

## Tools Used
Manual review

## Recommended Mitigation Steps
Apply access control so that only qualified users can call the init() function.





## Assessed type

Access Control
`BlastOnboardingBoot.sol` has no way to claim accrued yield
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Loss of USDB yield before protocol bootstraps, and additional lost on yield from unlocked funds .

## Proof of Concept
USDB yield is set to `CLAIMABLE` in `BlastOnboarding.sol`. Protocol can call `claimTokenYields()` to claim the yield from USDB rebasing up. After upgrading to the `BlastOnboardingBoot.sol` implementation, protocol will no longer be able to claim these yields.

USDB is only transferred to the MagicLP pool after `bootstrap()` (which does have the ability to claim yields). USDB will reside in this contract before `bootstrap()` is called.

1. If protocol upgrades before claiming yield and bootstraps, yields will be lost
2. If protocol claims yield before upgrade, yield lost will be the amount from after upgrade to before bootstrap

Furthermore, I believe user's unlocked funds are meant to be withdrawable from `BlastOnboardingBoot.sol`. These funds will continue to collect yield as well.

As of writing this, about 6m USDB has been deposited into the BlastOnboarding contract. Impact due to loss of yield is huge.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Add the implementation to claim yields to the `BlastOnboardingBoot.sol` contract.






## Assessed type

Context
Users lose the ability to withdraw their unlocked token after upgrading to BlastOnboardingBoot
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Loss of all unlocked principal for users

## Proof of Concept
`BlastOnboarding.sol` is currently deployed as an LLE where users can deposit MIM/USDB. Users have the options to lock their MIM/USDB. Once they have opted in to be locked, their funds can no longer be unlocked until the upgrade to `BlastOnboardingBoot.sol`. If they do not wish to lock their tokens, they are free to `withdraw()` anytime.

However, after upgrading the implementation to `BlastOnboardingBoot.sol` contract, the contract is missing a way for users to withdraw their unlocked funds. The amount users can claim is only based on their `locked` token amount as seen in the `_claimable()` implementation.

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

Funds deposited but not locked are all lost.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Implement a withdraw function for users to withdraw unlocked funds in `BlastOnboardingBoot.sol`



## Assessed type

Context
BlastOnboarding.sol makes all the locked tokens deposited there unusable
Audit: 2024-03-abracadabra-money-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
BlastOnboarding.sol contract allows its users to withdraw their tokens only if they are marked as unlocked. However once marked as locked, the tokens can never be unlocked, which makes the locked tokens really locked forever inside it. 

## Proof of Concept

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

        totals[token].total += amount;

        if (caps[token] > 0 && totals[token].total > caps[token]) {
            revert ErrCapReached();
        }

        balances[msg.sender][token].total += amount;

        emit LogDeposit(msg.sender, token, amount, 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);
    }

    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;

        token.safeTransfer(msg.sender, amount);

        emit LogWithdraw(msg.sender, token, amount);
    }
You can see that when a user makes a depost, he can choose how to initiate the tokens. If the tokens are set to unlocked, they can later be set to locked using lock function. However there is no function that can unlocks them. 

If we see the withdraw function, only tokens that are unlocked can be withdrawn, which makes the locked token unusable and blocked forever in the BlastOnboarding contract.

## Tools Used

Manual review

## Recommended Mitigation Steps
If you really want to lock the tokens, consider using a timelock where users will not be able to unlock them for several time but once the time passes, they will be able to unlock it.


## Assessed type

Token-Transfer