Farseen0/scar-pairs huggingface dataset:
LOW severity: [EIGEN2-8] Redundant double check of max operator count in register operator. **Severity:** Low

**Path:** SlashingRegistryCoordinator.sol:registerOperator#L151-L225

**Description:** The `registerOperator` function is called from the AllocationManager. The operator can specify a few parameters, such as the registration type. 

In the case of the `NORMAL` registration type,.....

Vulnerable code:
for (uint256 i = 0; i < quorumNumbers.length; i++) {
    uint8 quorumNumber = uint8(quorumNumbers[i]);

    require(
        numOperatorsPerQuorum[i] <= _quorumParams[quorumNumber].maxOperatorCount,
        MaxOperatorCountReached()
    );
}
LOW severity: [MNBD1-5] Early Pair Creation Breaks Fair Launch of New Moon Tokens And Gives An Attacker The Possibility To Steal All KAS from BondingCurvePool. **Severity:** Critical

**Path:** contracts/BondingCurvePool.sol#L234-L265 

**Description:** A bonding curve is designed to create fair and manipulation-resistant pricing for new tokens in their early stages. However, if someone can create a Zealous pair and control the reserves or pricing, they...

Vulnerable code:
kasAmount = (moonTokenAmount * kasReserves) / moonTokenReserves;

ERC20(token).approve(zealousSwapRouter, tokenForLiquidity);
IZealousSwapRouter02(zealousSwapRouter).addLiquidityKAS{ value: kasCollected }(
  token,
  tokenForLiquidity,
  0,
  0,
  address(this),
  block.timestamp + 15 minutes
);

function graduateToken() internal {
    // -- snip -- 
    
++  tokenManager.graduateToken(token);
    
    ERC20(token).approve(zealousSwapRouter, tokenForLiquidity);
    IZealousSwapRouter02(zealousSwapRouter).addLiquidityKAS{ value: kasCollected }(
      token,
      tokenForLiquidity,
      0,
      0,
      address(this),
      block.timestamp + 15 minutes
    );

    // Mark the token as graduated in the TokenManager
    // This will disable trading on the bonding curve
--  tokenManager.graduateToken(token);
    
    // -- snip -- 
}
LOW severity: [MNBD1-12] Insufficient Check of Approval Delay in BondingCurvePool. **Severity:** Medium

**Path:** contracts/BondingCurvePool.sol#L95-L100 

**Description:** In the `BondingCurvePool.buyTokens()` function, lines 95–100 attempt to enforce a delay between a user’s approval and their first token purchase:

However, this check can be bypassed if the user never calls...

Vulnerable code:
if (firstApprovalBlock[to] > 0 && lastBuyBlock[to] == 0) {
  require(
    block.number > firstApprovalBlock[to] + APPROVE_DELAY,
    'Please wait ~2.5s after approving before buying'
  );
}

++  require(firstApprovalBlock[to] != 0, 'User must be approved first'); 

    if (firstApprovalBlock[to] > 0 && lastBuyBlock[to] == 0) {
      require(
        block.number > firstApprovalBlock[to] + APPROVE_DELAY,
        'Please wait ~2.5s after approving before buying'
      );
    }
LOW severity: [MNBD1-4] Tokens cannot graduate if an attacker transfers KAS to the BondingCurvePool contract. **Severity:** Medium

**Path:** contracts/BondingCurvePool.sol#L247-L260 

**Description:** The `BondingCurvePool::graduateToken()` function is responsible for creating a new `ZealousSwapPair` between the MoonBound token and KAS. It does this by using the KAS collected during the bonding curve...

Vulnerable code:
function graduateToken() internal {
    ...
    // Add liquidity to DEX
    uint256 currentPrice = getPriceAtTokens(totalTokensSold);
    uint256 kasCollected = address(this).balance;

    uint256 tokenForLiquidity = (kasCollected * SCALING_FACTOR) / currentPrice;
    ...
}

(due to the deduction of `graduationFeeKAS`)

The issue arises when an attacker can artificially inflate `kasCollected` by forcefully transferring extra KAS to the contract. This causes `tokenForLiquidity` to exceed `reservedTokens`, which leads to a failure in the call to `IZealousSwapRouter02(zealousSwapRouter).addLiquidityKAS()` because of not enough token to transfer.

This can be exploited via a self-destruct technique, allowing the attacker to send `graduationFeeKAS + ε` KAS to the `BondingCurvePool` even though the contract lacks a `receive()` function.

**Remediation:**  If `tokenForLiquidity` exceeds `reservedTokens`, cap `tokenForLiquidity` to `reservedTokens`.
LOW severity: [MNBD1-2] Lack of Slippage Protection During Graduation in BondingCurvePool. **Severity:** Medium

**Path:** contracts/BondingCurvePool.sol#L253-L260 

**Description:** When the `BondingCurvePool` has sold all its tokens, it calls `graduateToken` and sends all collected Ether to the Zealous Swap Router to convert it into liquidity. However, it lacks any checks to ensure a...

Vulnerable code:
IZealousSwapRouter02(zealousSwapRouter).addLiquidityKAS{ value: kasCollected }(
  token,
  tokenForLiquidity,
  0,
  0,
  address(this),
  block.timestamp + 15 minutes
);
LOW severity: [MNBD1-6] An attacker can prevent the graduation of the BondingCurvePool by front-running to sell a very small amount of curve tokens. **Severity:** Low

**Description:** In the BondingCurvePool contract, `graduateToken()` is only triggered when all curve tokens have been sold, as indicated by a condition in the `buyTokens()` function:

`totalTokensSold` increases by the amount of curve tokens bought by users in the `buyTokens()`.....

Vulnerable code:
// Graduate if all tokens have been sold
if (totalTokensSold == curveTokens) {
  graduateToken();
}

function sellTokens(
  uint256 amount,
  address to
) external override nonReentrant onlyMoonBound returns (uint256) {
  bool graduated = tokenManager.getTokenGraduationStatus(token);
  require(!graduated, 'Token has already graduated.'); // Check graduation status
  require(amount > 0, 'Amount must be greater than 0');
  require(ERC20(token).balanceOf(to) >= amount, 'Not enough tokens to sell');
  require(ERC20(token).allowance(to, address(this)) >= amount, 'Insufficient allowance');

  uint256 refundAmount = calculateCost(totalTokensSold - amount, totalTokensSold);
  require(address(this).balance >= refundAmount, 'Not enough KAS in the pool.');

  ERC20(token).transferFrom(to, address(this), amount); // Transfer tokens to the contract

  // Update the total tokens sold
  totalTokensSold -= amount;

  uint256 tax = (refundAmount * TAX_BPS) / 10000; // 1% tax
  if (tax > 0) {
    refundAmount -= tax; // Deduct tax from refund

    // Transfer tax to moonbound vault
    (bool taxSuccess, ) = moonBound.call{ value: tax }('');
    require(taxSuccess, 'Failed to send tax to moonBound vault');
  }

  // Transfer KAS to the seller
  (bool success, ) = to.call{ value: refundAmount }('');
  require(success, 'Failed to send refund to the sender');

  emit TokensSold(to, amount, refundAmount);

  return refundAmount;
}
LOW severity: [MNBD1-14] 10 KAS Permanently Locked in MoonBound Contract. **Severity:** Low

**Path:** contracts/MoonBound.sol#L186-L189 

**Description:** The `distributeRevenue()` function handles the distribution of revenue from MoonBound tokens to relevant parties (e.g., projects, stakers, etc.). It distributes an amount of KAS `kasToDistribute` equal to the...

Vulnerable code:
uint256 totalKasBalance = address(this).balance;
require(totalKasBalance > 10e18, 'Insufficient balance for distribution');

uint256 kasToDistribute = totalKasBalance - 10e18;
LOW severity: [MNBD1-10] Incorrect Assumption on totalVolume in distributeToProjects() May Lead to Misallocation or Revert. **Severity:** Low

**Path:** contracts/MoonBound.sol#L224-L250 

**Description:** The `distributeToProjects()` function calculates each creator’s share using `totalVolume` as the denominator:

However, it assumes that `totalVolume == sum(tokenVolumes)`, without enforcing this constraint. If the...

Vulnerable code:
uint256 creatorShare = (kasToProjects * tokenVolumes[i]) / totalVolume;

function distributeToProjects(
  uint256 kasToDistribute,
  address[] calldata tokenCreators,
  uint256[] calldata tokenVolumes,
  uint256 totalVolume
) internal {
  uint256 kasToProjects = (kasToDistribute * 10) / 100;
  for (uint256 i = 0; i < tokenCreators.length; i++) {
    uint256 creatorShare = (kasToProjects * tokenVolumes[i]) / totalVolume;
    (bool projectSuccess, ) = tokenCreators[i].call{ value: creatorShare }('');
    require(projectSuccess, 'Failed to transfer to token creator');
  }
}

function distributeToNFTStakers(
  uint256 kasToDistribute,
  address[] calldata nftStakers,
  uint256[] calldata stakerPowers,
  uint256 totalPowerStaked
) internal {
  uint256 kasToNFTStakers = (kasToDistribute * 10) / 100;
  for (uint256 i = 0; i < nftStakers.length; i++) {
    uint256 stakerShare = (kasToNFTStakers * stakerPowers[i]) / totalPowerStaked;
    (bool stakerSuccess, ) = nftStakers[i].call{ value: stakerShare }('');
    require(stakerSuccess, 'Failed to transfer to NFT staker');
  }
}
LOW severity: [ZLS1-2] First Depositor Can Front-Run and Steal the Next User's Stake. **Severity:** High

**Path:** contracts/ZEALInfinityPool.sol#L196-L197
contracts/ZEALInfinityPool.sol#L274-L287  

**Description:** The `ZEALInfinityPool.stake()` function mints xZeal shares in exchange for Zeal tokens. The number of shares minted is calculated as follows:

Since the calculation...

Vulnerable code:
1. currentRate = (totalStaked + totalRewards).mulDiv(PRECISION_FACTOR, xSupply);
2. xMint = _amount.mulDiv(PRECISION_FACTOR, currentRate);

(X + 1 + X / 2) / 2 
  = (X / 2 + 1) + (X / 4 - 0.5)

uint256 xMint = _amount.mulDiv(PRECISION_FACTOR, currentRate);
require(xMint > 0, "Stake amount too small");
LOW severity: [ZLS1-1] All users can benefit from a discounted fee instead of the regular fee when performing a swap. **Severity:** Medium

**Path:** contracts/ZealousSwapPair.sol#L285-L311 

**Description:** The function `ZealousSwapPair.swap()` facilitates token swaps and takes an input parameter `actualUser`, representing the true initiator of the swap (typically the caller of the router contract). This...

Vulnerable code:
function swap(
    uint amount0Out,
    uint amount1Out,
    address to,
    bytes calldata data,
    address actualUser /// @audit There is no guarantee that actualUser is the actual caller
) external lock {
    ... 
    
    {
        // Scope for reserve{0,1}Adjusted to avoid stack-too-deep errors
        if (discountManagerContract != address(0)) {
            isDiscountEligible = IZealousSwapDiscountManager(
                discountManagerContract
            ).isDiscountEligible(actualUser);
        }
        uint balance0Adjusted = balance0.mul(10000).sub(
            (
                balance0 > _reserve0 - amount0Out
                    ? balance0 - (_reserve0 - amount0Out)
                    : 0
            ).mul(isDiscountEligible ? discountFee : regularFee)
        );
        uint balance1Adjusted = balance1.mul(10000).sub(
            (
                balance1 > _reserve1 - amount1Out
                    ? balance1 - (_reserve1 - amount1Out)
                    : 0
            ).mul(isDiscountEligible ? discountFee : regularFee)
        );
        require(
            balance0Adjusted.mul(balance1Adjusted) >=
                uint(_reserve0).mul(_reserve1).mul(10000 ** 2),
            "ZealousSwap: K"
        );
    }
    
    ...

++ mapping(address => bool) public isWhitelistedRouter;

function swap(
    uint amount0Out,
    uint amount1Out,
    address to,
    bytes calldata data,
    address actualUser
) external lock {
++  if (!isWhitelistedRouter[msg.sender]) {
++      require(
++          msg.sender == actualUser, 
++          "Incorrect actual user"
++      );
++  }

    ...
}
LOW severity: [ZLS1-3] An attacker can set the exchange rate of ZEALInfinityPool to a very large or unlimited value. **Severity:** Medium

**Path:** contracts/ZEALInfinityPool.sol#L192-L194
contracts/ZEALInfinityPool.sol#L85-L99

**Description:** In the ZEALInfinityPool contract, the exchange rate between staked Zeal tokens and minted xZeal tokens always increases, using the `highestExchangeRate` variable as a...

Vulnerable code:
function getExchangeRate() public view returns (uint256) {
    uint256 xSupply = xZealToken.totalSupply();
    if (xSupply == 0) {
        return highestExchangeRate; // Return highest rate if no supply
    }
    uint256 currentRate = (totalStaked + totalRewards).mulDiv(
        PRECISION_FACTOR,
        xSupply
    );

    return
        currentRate > highestExchangeRate
            ? currentRate
            : highestExchangeRate;
}

function stake(uint256 _amount) external nonReentrant {
    ...
    if (xSupply == 0) {
        currentRate = PRECISION_FACTOR;
    } else {
        currentRate = (totalStaked + totalRewards).mulDiv(
            PRECISION_FACTOR,
            xSupply
        );
    }

    currentRate = currentRate > highestExchangeRate
        ? currentRate
        : highestExchangeRate;
    ...
LOW severity: [ZLS1-10] lastRewardBlock Should Be Updated to Current Block When Resuming Emissions. **Severity:** Medium

**Path:** contracts/ZEALInfinityPool.sol#L300-L304 

**Description:** When emissions are paused and later resumed via the `setEmissionsPaused(false) `function in the `ZEALInfinityPool` contract, the `lastRewardBlock` should be updated to the current block number at the time...

Vulnerable code:
function setEmissionsPaused(bool _paused) external onlyOwner {
    _updateEmissions();
    emissionsPaused = _paused;
    emit EmissionsPaused(_paused);
}
LOW severity: [ZLS1-4] User Can Bypass Locking Period Without Penalty Using emergencyWithdraw. **Severity:** Medium

**Path:** contracts/ZealousSwapFarms.sol#L424-L440 

**Description:** When a user deposits tokens, they can’t withdraw them until the locking period has passed. This is enforced in the `withdraw` function with the following check:

However, the `emergencyWithdraw` function...

Vulnerable code:
function withdraw(uint256 _pid, uint256 _amount) public nonReentrant {
  -- snip -- 
  require(
      block.number >= user.lastInteraction + lockingPeriod,
      "ZealousSwapFarms: Tokens are still locked"
  );

function emergencyWithdraw(uint256 _pid) public nonReentrant {
    require(_pid < poolInfo.length, "ZealousSwapFarms: Invalid pool ID");

    PoolInfo storage pool = poolInfo[_pid];
    UserInfo storage user = userInfo[_pid][msg.sender];

    uint256 amount = user.amount;
    require(amount > 0, "ZealousSwapFarms: No LP tokens staked");

    user.amount = 0;
    user.rewardDebt = 0;

    pool.totalDeposited = pool.totalDeposited - amount;
    pool.lpToken.safeTransfer(address(msg.sender), amount);

    emit EmergencyWithdraw(msg.sender, _pid, amount);
}
LOW severity: [ZLS1-9] Calling ZealousSwapFarms.setRewardToken() May Result in Inflated or Incorrect Staker Payouts. **Severity:** Low

**Path:** contracts/ZealousSwapFarms.sol#L442-L452 

**Description:** The `setRewardToken() `function in `ZealousSwapFarms` allows the farm owner to update the reward token. However, this change can be made without first distributing all pending rewards in the previous token. As.....

Vulnerable code:
function setRewardToken(IERC20 _newRewardToken) public onlyOwner {
    require(
        address(_newRewardToken) != address(0),
        "ZealousSwapFarms: Invalid token address"
    );

    massUpdatePools();

    rewardToken = _newRewardToken;
    emit RewardTokenUpdated(address(_newRewardToken));
}
HIGH severity: [ZLS1-6] Missing validation of lastRewardBlock when adding a new pool in ZealousSwapFarms. **Severity:** Low

**Path:** contracts/ZealousSwapFarms.sol#L143-L148

**Description:** In the `ZealousSwapFarms::add()` function, if it reactivates an old pool, the `lastRewardBlock` of that pool is set to the maximum of `block.number` and the global `startBlock`. However, if it adds a new pool,...

Vulnerable code:
function add(
    uint256 _allocPoint,
    IERC20 _lpToken,
    bool _withUpdate,
    uint256 _lastRewardBlock
) public onlyOwner {
  ...
  
  uint256 lastRewardBlock = _lastRewardBlock;
  if (_lastRewardBlock == 0) {
    lastRewardBlock = block.number > startBlock
        ? block.number
        : startBlock;
  }
  
  ...

uint256 lastRewardBlock = _lastRewardBlock;
  if (_lastRewardBlock == 0) {
      lastRewardBlock = block.number > startBlock
          ? block.number
          : startBlock;
  }
+ require(
+     lastRewardBlock >= block.number && 
+     lastRewardBlock >= startBlock, 
+     "ZealousSwapFarms: Too small lastRewardBlock"
+ );
LOW severity: [ZLS1-8] Redundant Boundary Checks in ZealousSwapFactory.setFeesForAllPairs(). **Severity:** Informational

**Path:** contracts/ZealousSwapFactory.sol#L49-L53
contracts/ZealousSwapFactory.sol#L66-L70  
contracts/ZealousSwapPair.sol#L46-L50 

**Description:** Lines 49–53 of the `setFeesForAllPairs()` function in the `ZealousSwapFactory` contract perform boundary validation on.....

Vulnerable code:
require(_regularFee <= 100, "ZealousSwap: FEE_TOO_HIGH");
require(
    _discountFee <= _regularFee,
    "ZealousSwap: DISCOUNT_HIGHER_THAN_REGULAR"
);

function setFeesForAllPairs(uint _regularFee, uint _discountFee) external {
        require(msg.sender == feeToSetter, "ZealousSwap: FORBIDDEN");
--      require(_regularFee <= 100, "ZealousSwap: FEE_TOO_HIGH");
--      require(
--          _discountFee <= _regularFee,
--          "ZealousSwap: DISCOUNT_HIGHER_THAN_REGULAR"
--      );

        for (uint i = 0; i < allPairs.length; i++) {
            IZealousSwapPair(allPairs[i]).setFees(_regularFee, _discountFee);
        }
    }

    function setFeesForSpecificPairs(
        uint _regularFee,
        uint _discountFee,
        address[] calldata _pairsToUpdate
    ) external {
        require(msg.sender == feeToSetter, "ZealousSwap: FORBIDDEN");
--      require(_regularFee <= 100, "ZealousSwap: FEE_TOO_HIGH");
--      require(
--          _discountFee <= _regularFee,
--          "ZealousSwap: DISCOUNT_HIGHER_THAN_REGULAR"
--      );


        for (uint i = 0; i < _pairsToUpdate.length; i++) {
            IZealousSwapPair(_pairsToUpdate[i]).setFees(
                _regularFee,
                _discountFee
            );
        }
    }
HIGH severity: [ZLS1-7] Redundant check of the NFT owner in the stakeNFT() function. **Severity:** Informational

**Path:** contracts/ZealousSwapNFTStaking.sol#L310-L313

**Description:** In the `ZealousSwapNFTStaking` contract, this owner check of the NFT from line 310 to 313 in the `stakeNFT()` function is redundant:

This is because the function already checks the NFT's owner...

Vulnerable code:
require(
    IERC721(nftContract).ownerOf(nftID) == msg.sender,
    "ZealousSwapNFTStaking: Not token owner"
);

address beforeOwner = IERC721(nftContract).ownerOf(nftID);
IERC721(nftContract).transferFrom(msg.sender, address(this), nftID);
address afterOwner = IERC721(nftContract).ownerOf(nftID);
require(
    afterOwner == address(this) && beforeOwner == msg.sender,
    "ZealousSwapNFTStaking: NFT transfer failed"
);
MEDIUM severity: [UFAR2] Quex callback can be permanently DoSed by congesting queues. **Severity:** Critical

**Path:** main/contracts/pool/UFarmPool.sol:quexCallback (L881-1007)

**Description:** The UFarmPool contract uses the Quex Oracle to query off-chain information, namely the total value of the pool (`_totalCost`). 

Any deposit or withdrawal action will add this action to...

Vulnerable code:
requestsLength = depositQueue.length;
while (requestsLength > 0) {
    [..]
}

function quexCallback(uint256 receivedRequestId, DataItem memory response) external {
    [..]
    {
        uint256 sharesToMint;
        uint256 amountToInvest;
        uint256 totalDeposit;
        bytes32 depositRequestHash;
        QueueItem memory depositItem;

        requestsLength = depositQueue.length;
        while (requestsLength > 0) {
            // Validate each deposit request
            depositItem = depositQueue[requestsLength - 1];
            amountToInvest = depositItem.amount;
            investor = depositItem.investor;
            depositRequestHash = depositItem.requestHash;

            // Process the deposit
            try this.safeTransferToPool(investor, amountToInvest, depositItem.bearerToken) {
                sharesToMint = _mintSharesByQuote(investor, amountToInvest, _totalCost);

                // Adjust the total cost and total deposit
                _totalCost += amountToInvest;
                totalDeposit += amountToInvest;

                emit Deposit(investor, depositItem.bearerToken, amountToInvest, sharesToMint);

                if (depositRequestHash != bytes32(0)) {
                    __usedDepositsRequests[depositRequestHash] = true;
                    emit DepositRequestExecuted(investor, depositRequestHash);
                }
            } catch {
                depositQueue.pop();
                requestsLength = depositQueue.length;
                continue;
            }

            depositQueue.pop();
            requestsLength = depositQueue.length;
        }

        highWaterMark += totalDeposit;
    }
    [..]
}
MEDIUM severity: [UFAR1] The approve top-up role can replay the same deposit/withdrawal request. **Severity:** High

**Path:** contracts/main/contracts/pool/UFarmPool.sol#L404-L415
contracts/main/contracts/pool/UFarmPool.sol#L451-L461

**Description:** The `UFarmPool::approveDeposits()` function allows the approve top-up role (`Permissions.Pool.ApprovePoolTopup` &...

Vulnerable code:
for (uint256 i; i < requestsLength; ++i) {
    try this.validateDepositRequest(_depositRequests[i]) returns (
        address investor,
        uint256 amountToInvest,
        bytes32 depositRequestHash,
        address bearerToken
    ) {
        depositQueue.push(QueueItem(amountToInvest, depositRequestHash, investor, bearerToken));
    } catch {
        continue;
    }
}
sendQuexRequest();

if (__usedDepositsRequests[depositRequestHash]) revert UFarmErrors.ActionAlreadyDone();

while (requestsLength > 0) {
    // Validate each deposit request
    depositItem = depositQueue[requestsLength - 1];
    amountToInvest = depositItem.amount;
    investor = depositItem.investor;
    depositRequestHash = depositItem.requestHash;

    // Process the deposit
    try this.safeTransferToPool(investor, amountToInvest, depositItem.bearerToken) {
        sharesToMint = _mintSharesByQuote(investor, amountToInvest, _totalCost);

        // Adjust the total cost and total deposit
        _totalCost += amountToInvest;
        totalDeposit += amountToInvest;

        emit Deposit(investor, depositItem.bearerToken, amountToInvest, sharesToMint);

        if (depositRequestHash != bytes32(0)) {
            __usedDepositsRequests[depositRequestHash] = true;
            emit DepositRequestExecuted(investor, depositRequestHash);
        }
    } catch {
        depositQueue.pop();
        requestsLength = depositQueue.length;
        continue;
    }

    depositQueue.pop();
    requestsLength = depositQueue.length;
}
MEDIUM severity: [UFAR4] Malicious User Could Deactivate Pool. **Severity:** High

**Description:** In the `UFarmPool:withdraw` function, the contract records a withdrawal request by storing the current block timestamp:

Later, when the callback is executed, the contract  removes the request hash in two scenarios:

1. User has insufficient shares:

2. User...

Vulnerable code:
if (pendingWithdrawalsRequests[withdrawalRequestHash] == 0) {
    // Set the withdrawal request timestamp
    pendingWithdrawalsRequests[withdrawalRequestHash] = block.timestamp;
    emit WithdrawRequestReceived(investor, withdrawalRequestHash, block.timestamp);
} else {

if (sharesToBurn > availableToWithdraw) {
    delete pendingWithdrawalsRequests[withdrawalRequestHash];

    withdrawQueue.pop();
    requestsLength = withdrawQueue.length;
    continue;
}

if (investor != ufarmFund && amountToWithdraw != 0) {
    // Mark the request as used
    __usedWithdrawalsRequests[withdrawalRequestHash] = true;

    // Delete the request from the pending withdrawals
    delete pendingWithdrawalsRequests[withdrawalRequestHash];
}