Farseen0/scar-pairs huggingface dataset:
LOW severity: Missing zero address check in Governable can lead to loss of Ownership. **Severity**: Low

**Status**: Resolved

**Description**: 

In Governable.sol’s transferGovernor() function, there is missing zero address check. This can lead to accidentally losing ownership if a zero address is passed as a parameter to this function. This will lead to the admin losing access to.....

Vulnerable code:
function transferGovernor(address newGovernor) external virtual override onlyGovernor {
       // Effects: update the governor.
       governor = newGovernor;


       // Log the transfer of the governor.
       emit IGovernable.TransferGovernor({ oldGovernor: msg.sender, newGovernor: newGovernor });
   }
HIGH severity: Not enough Chainlink checks can make the protocol to behave incorrectly. **Severity**: Critical	

**Status**: Resolved

**Description**

The `TokenBridge.Base.sol` smart contract implements a `getChainlinkDataFeedLatestAnswer()` function which uses Chainlink Price Feeds to retrieve asset’s prices:

However, retrieving asset’s prices using Chainlink Price Feeds requires.....

Vulnerable code:
/**
    * Returns the latest answer
    * @param dataFeed Chainlink data feed
    * @return answer The latest answer
    */
   function getChainlinkDataFeedLatestAnswer( 
       AggregatorV3Interface dataFeed
   ) internal view returns (int) {
       // prettier-ignore
       (
           /* uint80 roundId */,
           int answer,
           /* uint startedAt */,
           /* uint updatedAt */,
           /* uint80 answeredInRound */
       ) = dataFeed.latestRoundData();
       return answer;
   }
HIGH severity: Tokens will get locked forever after pausing the contract.. **Severity**: Critical	

**Status**: Resolved

**Description**

The `PausableToken` smart contract is an ERC20 token defined as `Pausable`. The token implements `pause()` and `unpause()` functions to define if the contract is paused or not. The ERC20 token only allows `transfer()`,...

Vulnerable code:
function transfer(
       address to,
       uint256 amount
   ) public override whenNotPaused returns (bool) { 
       return super.transfer(to, amount);
   }

/**
    * @dev called by the owner to unpause, returns to normal state
    */
   function unpause() external onlyOwner {
       _pause(); 
   }
MEDIUM severity: Ether amount sent above fee or fee + amount will get locked in the contract. **Severity**: Medium	

**Status**: Resolved

**Description**

Every function within `WrappedTokenBridge.sol` and `OriginalTokenBridge.sol` are payable functions. This is because the user executing these functions needs to pay either fees or fees + amount if ether is wanted to be bridge.
All these...

Vulnerable code:
if (msg.value < totalFee) {
           revert InsufficientFee();
       }


if (msg.value < totalFee + amount) {
           revert InsufficientValueSent();
       }

if (msg.value < totalFee + amount) {
           revert InsufficientValueSent();
       }

       // Deposit ether to get wrapped ether, lock WETH in contract
       IWETH(WETH).deposit{value: amount}();

       _bridgeToErc20(
           WETH,
           remoteChainId,
           amount,
           to,
           nativeFee,
           referralCode,
           LzLib.CallParams(refundAddress, address(0)),
           adapterParams
       );
MEDIUM severity: It is not possible to unregister tokens if they are no longer wanted to be supported. **Severity**: Medium	

**Status**: Resolved

**Description**

The `registerToken()` function within the `WrappedTokenBridge.sol` smart contract is used to register a certain pair of tokens for bridging them:

localToRemote[localToken][remoteChainId] = remoteToken;       ...

Vulnerable code:
address remoteToken = localToRemote[localToken][remoteChainId];
       if (remoteToken == address(0)) {
           revert UnsupportedToken();
       }
MEDIUM severity: Fees can grow up to ‘infinite’. **Severity**: Medium	

**Status**: Acknowledged

**Description**

The `TokenBridgeBase.sol` smart contract implement some functions to define certain protocol fees:

However, there is no upper limit for the new fees to be set so they can be set up to any number, including really high...

Vulnerable code:
/**
    * @notice Set transfer fee for BRC-20
    * @param _brc20TransferFee Transfer fee for BRC-20 in satoshi
    */
   function setBrc20TransferFee(uint256 _brc20TransferFee) external onlyOwner {
       brc20TransferFee = _brc20TransferFee; 
       emit Brc20TransferFeeSet(_brc20TransferFee);
   }

   /**
    * @notice Set transfer fee for Runes
    * @param _runesTransferFee Transfer fee for Runes in satoshi
    */
   function setRunesTransferFee(uint256 _runesTransferFee) external onlyOwner {
       runesTransferFee = _runesTransferFee;  
       emit RunesTransferFeeSet(_runesTransferFee);
   }

   /**
    * @notice Set transfer fee for BTC
    * @param _btcTransferFee Transfer fee for BTC in satoshi
    */
   function setBtcTransferFee(uint256 _btcTransferFee) external onlyOwner {
       btcTransferFee = _btcTransferFee;  
       emit BtcTransferFeeSet(_btcTransferFee);
   }
LOW severity: Fees are set to 0 at the first instance. **Severity**: Low	

**Status**: Resolved

**Description**

When the `TokenBridgeBase.sol` smart contract is deployed, the `brc20TransferFee`, `runesTransferFee` and `btcTransferFee` are not being set, therefore they are 0.

If the corresponding functions to modify these fees are not executed, then.....

Vulnerable code:
constructor(
       address _endpoint,
       address _btcBridge,
       address _superAdmin,
       address _btcDataFeed,
       address _nativeDataFeed,
       uint8 _nativeDecimals
   ) BridgeRoles(_superAdmin, _btcBridge) NonblockingLzApp(_endpoint) {
       btcDataFeed = AggregatorV3Interface(_btcDataFeed);
       nativeDataFeed = AggregatorV3Interface(_nativeDataFeed);
       nativeDecimals = _nativeDecimals;
   }
LOW severity: Bridge address can not be changed if needed. **Severity**: Low	

**Status**: Resolved

**Description**

The `bridge` address used within the `WrappedERC20.sol` smart contract is set via constructor when the contract is the deployed. The address is set to an `immutable` variable and is used to ensure that certain functions are only called by...

Vulnerable code:
constructor(
       address _bridge,
       string memory _name,
       string memory _symbol,
       uint8 _decimals
   ) ERC20(_name, _symbol) {
       if (_bridge == address(0)) {
           revert InvalidBridge();
       }

       BRIDGE = _bridge; 
       TOKEN_DECIMALS = _decimals;
   }

function setBridge(address _bridge) external onlyOwner {
       if (_bridge == address(0)) {
           revert InvalidBridge();
       }
       bridge = _bridge;
       emit BridgeSet(_bridge);
   }
LOW severity: Insufficient HEU Tokens for Claims. **Severity** : Low

**Status**: Acknowledged

**Description**: 

The claim() function does not validate whether the contract has enough HEU tokens to fulfill a user's claim. If the balance of HEU is insufficient (e.g., due to previous withdrawals or unexpected transfers), the claim could fail.
...

Vulnerable code:
require(heu.balanceOf(address(this)) >= heuAmount, "Insufficient HEU balance in contract to fulfill claim");
HIGH severity: Centralization Risk Due to Whole Total Supply Minted to One Wallet. **Description**

On deployment, the entire total supply of the token is minted to a single wallet. This poses a
centralization risk as it gives one wallet complete control over the entire asset. This
concentration of power can lead to trust issues and potential misuse of...

Vulnerable code:
constructor(
uint256 initialSupply
) BurnMintERC677("XYRO", "XYRO", 18, 0) EIP712("XYRO", "1") {
_mint(msg.sender, initialSupply);
}
HIGH severity: Improper Use of approve Instead of safeApprove from SafeER. **Severity**: Medium

**Status**: Resolved

**Description**

The contract directly calls the approve function of ERC20 tokens without using the safeApprove method provided by SafeERC20. This can be unsafe with tokens that do not fully conform to the ERC20 standard or have non-standard...

Vulnerable code:
function addLiquidityToPool(address stable, uint256 amount, address receiver) private {
    IDebtToken(debtToken).mintFromWhitelistedContract(amount);
    address vault = pools[stable].vault;
    IERC20(stable).approve(vault, amount); // Direct approve
    IERC20(debtToken).approve(vault, amount); // Direct approve
    IArrakisV2(vault).mint(amount, receiver);
}
LOW severity: Inadequate Pool Validity Check in suspendDeposit Function. **Severity**: Low

**Status**: Resolved

**Description**: 

The suspendDeposit function allows anyone with ownership control to suspend deposits for a non-existent pool, as there is no check to verify if the given stablecoin address (stable) is valid. This could lead to unexpected behavior or...

Vulnerable code:
if (!validStables[stable]) revert PoolDoesNotExist();
HIGH severity: User Controlled Options Price. **Severity** - Critical

**Severity** - Resolved

**Description**

When joining a duel the amount put up as wager is compared against an options price and can’t be lower than that price. But since the options price is controlled by the user →

The user can provide a very low value as the option...

Vulnerable code:
function joinDuel(
        string memory _duelId,
        string memory _option,
        uint256 _optionsIndex,
        uint256 _optionPrice,
        uint256 _amount
    )

uint256 amountTokenToMint = (_amount * 1e18) / _optionPrice;
HIGH severity: Users Can Lose All Their Rewards Even If Their Option Won The Duel. **Severity** - Critical

**Status** - Resolved

**Description**

Consider the following →

UserA joins a duel (say duelId = 1)  , provides an option and an option index , and as amount provides 1000 USDC
This would deploy a new options token , like this →
OptionToken newOptionToken = new...

Vulnerable code:
function _getUserDuelOptionShare(
        string memory _duelId,
        uint256 _optionIndex,
        address _user
    ) internal view returns (uint256 optionShare) {
        address optionToken = optionIndexToOptionToken[_duelId][_optionIndex];
        uint256 optionTokenBalance = IERC20(optionToken).balanceOf(_user);
        uint256 totalOptionTokenSupply = IERC20(optionToken).totalSupply();
        optionShare = (optionTokenBalance * 1e18) / totalOptionTokenSupply;
    }
HIGH severity: Rounding While Calculating Option Shares Might Lead To Winner Receiving 0 Rewards. **Severity** - High

**Status** - Acknowledged

The winning option shares is calculated as follows →

If the user has 1e18 option token and the totalOptionTokenSupply is > 1e36 , then user’s optionShare would come out to be 0 and even though he won the duel (one of the winners) he received 0...

Vulnerable code:
function _getUserDuelOptionShare(
        string memory _duelId,
        uint256 _optionIndex,
        address _user
    ) internal view returns (uint256 optionShare) {
        address optionToken = optionIndexToOptionToken[_duelId][_optionIndex];
        uint256 optionTokenBalance = IERC20(optionToken).balanceOf(_user);
        uint256 totalOptionTokenSupply = IERC20(optionToken).totalSupply();
        optionShare = (optionTokenBalance * 1e18) / totalOptionTokenSupply;
    }

uint256 totalWinningWagers = totalWagerForOption[_duelId][_winningOption];
        uint256 winningOptionPoolBalance = totalWinningWagers + _payout;


        for (uint256 i = 0; i < winners.length; i++) {
            address winner = winners[i];
            uint256 winnerShare = _getUserDuelOptionShare(_duelId, _optionIndex, winner);
            uint256 winnerWinningTokenAmount = (winnerShare * winningOptionPoolBalance) / 1e18;
LOW severity: Lack of Seller Balance Verification in sell Function. **Severity** - Low

**Status** - Resolved

**Description**

The sell function checks the seller's token allowance but not their actual token balance. Sellers without sufficient tokens can create sales that cannot be fulfilled, leading to failed transactions for buyers.

**Scenario**:

A seller...

Vulnerable code:
require(erc20.balanceOf(msg.sender) >= quantity, "Insufficient token balance");
LOW severity: Missing Event Emissions in Setter Functions. **Severity**: Low

**Status**: Resolved

**Description**

Several setter functions in the contract update critical state variables without emitting corresponding events. Event emissions in setter functions provide an essential audit trail on the blockchain, allowing users, auditors, and developers.....

Vulnerable code:
// Define events
event CreateDuelFeeUpdated(uint256 newFee);
event MinimumWagerThresholdUpdated(uint256 newThreshold);
event BotAddressUpdated(address newBotAddress);

// Update setter functions
function setCreateDuelFee(uint256 _fee) external onlyOwner {
    createDuelFee = _fee;
    emit CreateDuelFeeUpdated(_fee);
}

function setMinimumWagerThreshold(uint256 _minThreshold) external onlyOwner {
    minThreshold = _minThreshold;
    emit MinimumWagerThresholdUpdated(_minThreshold);
}

function setBotAddress(address _bot) external onlyOwner {
    require(_bot != address(0), "Invalid bot address");
    bot = _bot;
    emit BotAddressUpdated(_bot);
}
HIGH severity: Lack of an implementation for withdrawing beneficiaries balance. **Severity**: Critical

**Status**: Resolved

**Location**: BandoFulfillmentManagerV1.sol

**Description**

The beneficiaryWithdraw() function in both BandoERC20FulfillableV1 and BandoFulfillableV1 contracts are only designed to be called by the manager contract but the BandoFulfillmentManagerV1...

Vulnerable code:
function beneficiaryWithdraw(uint256 serviceID, address token) public virtual nonReentrant {
        require(_manager == msg.sender, "Caller is not the manager");
        ...
}
HIGH severity: Fund locked due to lack of an implementation for withdrawing service fees. **Severity**: Critical

**Status**: Resolved

**Location**: BandoRouterV1.sol

**Description**

In the BandoRouterV1 contract, the requestService() function requires the caller to pay the sum of request.weiAmount and service.feeAmount.

But the function calls the _escrow.deposit() function with...

Vulnerable code:
(bool success, uint256 total_amount) = request.weiAmount.tryAdd(service.feeAmount);
if (!success) {
    revert OverflowError();
}
       
if (msg.value != total_amount) {
    revert AmountMismatch();
}

IBandoFulfillable(_escrow).deposit{value: request.weiAmount}(serviceID, request);
HIGH severity: Loss of fulfiller funds and fees due to wrong comparison between depositsAmount and total_amount. **Severity**: Critical

**Status**: Resolved

**Location**: BandoERC20FulfillableV1.sol

**Walkthrough**:
Say a user will like to submit a request for a ERC20Service.

**Initial states:**

The initial deposit for the user is 0.

So when the user calls the router contract:

 
The user transfers in...

Vulnerable code:
function getERC20DepositsFor(address token, address payer, uint256 serviceID) public view returns (uint256 amount) {
    amount = _erc20_deposits[serviceID][token][payer];
}

function requestERC20Service(
    uint256 serviceID, 
    ERC20FulFillmentRequest memory request
) public payable whenNotPaused nonReentrant returns (bool)

IERC20(request.token).safeTransferFrom(
     msg.sender,
    _erc20Escrow,
    request.tokenAmount
);
require(
    IERC20(request.token).balanceOf(msg.sender) <= pre_balance - request.tokenAmount,
    "BandoRouterV1: ERC20 invalid transfer return"
);
IBandoERC20Fulfillable(_erc20Escrow).depositERC20(serviceID, request);
MEDIUM severity: Fulfilments are based on variable service fee. **Severity**: Medium

**Status**: Resolved

**Description**

In the current implementation of the protocol, when registering a fulfillment, the service fee is fetched from the registry contract.
Service memory service = _registryContract.getService(serviceID);

The problem is that during any time...

Vulnerable code:
function updateServiceFeeAmount(uint256 serviceId, uint256 newFeeAmount) external onlyOwner {
    require(_serviceRegistry[serviceId].fulfiller != address(0), 'FulfillableRegistry: Service does not exist');
    _serviceRegistry[serviceId].feeAmount = newFeeAmount;
}
MEDIUM severity: Users can't withdraw their own refunds themselves. **Severity**: Medium

**Status**: Resolved

**Location**: BandoERC20FulfillableV1.sol, BandoFulfillableV1.sol

**Description**

In the current implementation, if a user has a pending refund, they have to wait for the service fulfiler or the owner to process the refund.

This is an unusual pattern...

Vulnerable code:
function withdrawERC20Refund(uint256 serviceID, address token, address refundee) public virtual nonReentrant returns (bool) {
    require(_manager == msg.sender, "Caller is not the manager");
    ...
}
MEDIUM severity: The removeServiceAddress() function fails with Overflow error. **Severity**: Medium	

**Status**: Resolved

**Description**

In FulfillableRegistry.sol,the removeServiceAddress() function  fails with an Underflow error. This is because _serviceCount is never incremented when adding the service. 

**Recommendation**:

Please consider incrementing the...

Vulnerable code:
function addService(uint256 serviceId, Service memory service) external onlyManager returns (bool) {
       require(
           _serviceRegistry[serviceId].fulfiller == address(0),
           'FulfillableRegistry: Service already exists'
       );
       _serviceRegistry[serviceId] = service;
       // @audit Line below Added by auditor to test 
       _serviceCount++;
       emit ServiceAdded(serviceId, service.fulfiller);
       return true;
   }
LOW severity: Incorrect Comparison for Offset Validation. **Severity**: Low

**Status**: Resolved

**Description**

In the airdrop function of ERC20Vesting, the check if(offset > _vestingCount) is used to ensure the offset is within bounds. However, it should be if(offset >= _vestingCount) because if the offset is equal to _vestingCount, the index will...

Vulnerable code:
492:     function airdrop(uint256 batchSize, uint256 offset) external onlyPlatformAdmin {
493:         if(offset > _vestingCount)
494:             revert OutOfBounds();
495: 
496:         uint256 index = _vestingCount - offset;
HIGH severity: Attacker Can Grief The Management Fee To Be Lesser Than Expected. **Severity**: Critical	

**Status**: Resolved

**Description**

Fee is accrued when the share value increases / accrues value , then anyone can call the takeFees function which will accrue performance and management fee and send it to the fee recipient. 

But an attacker can do the following...

Vulnerable code:
if (shareValue > fees_.highWaterMark) fees.highWaterMark = shareValue;


        if (fee > 0) _mint(fees_.feeRecipient, convertToShares(fee));


        fees.feesUpdatedAt = uint64(block.timestamp);

return
            managementFee > 0
                ? managementFee.mulDivDown(
                    totalAssets() * (block.timestamp - fees_.feesUpdatedAt),
                    365.25 days // seconds per year
                ) / 1e18
                : 0;
HIGH severity: convertToAssets is receiving asset’s decimals instead of share’s decimals, possibly leading to a miscalculation. **Severity**: Critical	

**Status**: Acknowledged

**Description**

When calculating how much worth a share is in the protocol (for example when calculating fees) we perform the following →

But the function convertToAssets is being passed one asset instead of one share , although this might not...

Vulnerable code:
function _accruedPerformanceFee(
        Fees memory fees_
    ) internal view returns (uint256) {
        uint256 shareValue = convertToAssets(10 ** asset.decimals());
HIGH severity: A vault can be added to OracleVaultController after receiving deposits leading to incorrect price. **Severity**: High	

**Status**: Acknowledged

**Description**

The function `addVault()` within the `OracleVaultController.sol` smart contract is used to add a vault to the controller to be able to update its price. The function always initialize the price to 1e18 (1:1) -- This is to prevent...

Vulnerable code:
function addVault(address vault) external onlyOwner { 
       highWaterMarks[vault] = 1e18;


       oracle.setPrice(vault, address(ERC4626(vault).asset()), 1e18, 1e18);


       emit VaultAdded(vault);
   }
MEDIUM severity: No slippage protection can lead to loss of funds. **Severity**: Medium	

**Status**: Acknowledged

**Description**

The `deposit()` function within the `BaseControlledAsyncRedeem.sol` smart contract is used for users to deposit assets in exchange for shares. The amount of shares minted to the users are calculated in the following way: 

It can be.....

Vulnerable code:
function convertToShares(uint256 assets) public view virtual returns (uint256) {
       uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.


       return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
   }
MEDIUM severity: The Redeem Function In BaseControlledAsyncRedeem Does Not Check That msg.sender Can Spend Owner Funds Using Allowance. **Severity**: Medium

**Status**: Acknowledged

**Description**

The redeem function in the BaseControlledAsyncRedeem function allows users to claim their tokens by redeeming vault shares. The EIP-4626 (derived) standard enforces that when msg.sender creates a redeem transaction, there should be a.....

Vulnerable code:
function redeem(
       uint256 shares,
       address receiver,
       address controller
   ) public virtual override returns (uint256 assets) {
       require(
           controller == msg.sender || isOperator[controller][msg.sender],
           "ERC7540Vault/invalid-caller"
       );
       // ============= SNIP ============
MEDIUM severity: User’s funds can get locked due to ‘limits’ having no maximum limit.. **Severity**: Medium	

**Status**: Resolved

**Description**

The ´Limits´ struct within the `AsyncVault.sol` contract contains 2 variables:
`depositLimit`: Maximum amount of assets that can be deposited into the vault
`minAmount`: Minimum amount of shares that can be minted / redeemed from the...

Vulnerable code:
function setLimits(Limits memory limits_) external onlyOwner {
       _setLimits(limits_);
   }


   /// @dev Internal function to set the limits
   function _setLimits(Limits memory limits_) internal { 
       emit LimitsUpdated(limits, limits_);


       limits = limits_;
   }
MEDIUM severity: Missing valid range check for signature. **Severity**: Medium	

**Status**: Resolved

**Description**

There is a missing check for a valid range of s in the authorizeOperator() function of the BaseERC7540 contract

The valid range for s is in 0 < s < secp256k1n ÷ 2 + 1
This means that the valid range for s must be in the "lower half" of.....

Vulnerable code:
bytes32 r;
       bytes32 s;
       uint8 v;
       assembly {
           r := mload(add(signature, 0x20))
           s := mload(add(signature, 0x40))
           v := byte(0, mload(add(signature, 0x60)))
       }

if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) 
   revert("Invalid s value");
}
MEDIUM severity: Incorrect calculation of managementFee. **Severity**: Medium	

**Status**: Resolved

**Description**
 
The `_accruedManagementFee()` function within the `AsyncVault.sol` smart contract is used to calculate the managementFee. One of the parameters used for this calculator is the total amount of seconds per year:

It can be observed that...

Vulnerable code:
function _accruedManagementFee(
       Fees memory fees_
   ) internal view returns (uint256) {
       uint256 managementFee = uint256(fees_.managementFee);


       return
           managementFee > 0
               ? managementFee.mulDivDown(
                   totalAssets() * (block.timestamp - fees_.feesUpdatedAt),
                   365.25 days // seconds per year
               ) / 1e18
               : 0;
   }