msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: If the recipient is added to the USDC blacklist, then cancel() does not work
Severity: medium

Description:
cancel() will send the vested USDC to the recipient, if the recipient is added to the USDC blacklist, then cancel() will not work\\nWhen cancel() is called, it sends the vested USDC to the recipient and cancels future payments. Consider a scenario where if the payer intends to call cancel() to cancel the payment stream, a malicious recipient can block the address from receiving USDC by adding it to the USDC blacklist (e.g. by doing something malicious with that address, etc.), which prevents the payer from canceling the payment stream and withdrawing future payments\\n```\\n    function cancel() external onlyPayerOrRecipient {\\n        address payer_ = payer();\\n        address recipient_ = recipient();\\n        IERC20 token_ = token();\\n\\n        uint256 recipientBalance = balanceOf(recipient_);\\n\\n        // This zeroing is important because without it, it's possible for recipient to obtain additional funds\\n        // from this contract if anyone (e.g. payer) sends it tokens after cancellation.\\n        // Thanks to this state update, `balanceOf(recipient_)` will only return zero in future calls.\\n        remainingBalance = 0;\\n\\n        if (recipientBalance > 0) token_.safeTransfer(recipient_, recipientBalance);\\n```\\n

Impact:
A malicious recipient may prevent the payer from canceling the payment stream and withdrawing future payments

Recommended mitigation:
Issue If the recipient is added to the USDC blacklist, then cancel() does not work\\nInstead of sending tokens directly to the payer or recipient in cancel(), consider storing the number of tokens in variables and having the payer or recipient claim it later
Vulnerability: Adverary can DOS contract by making a large number of deposits/withdraws then removing them all
Severity: high

Description:
When a user dequeues a withdraw or deposit it leaves a blank entry in the withdraw/deposit. This entry must be read from memory and skipped when processing the withdraws/deposits which uses gas for each blank entry. An adversary could exploit this to DOS the contract. By making a large number of these blank deposits they could make it impossible to process any auction.\\n```\\n    while (_quantity > 0) {\\n        Receipt memory deposit = deposits[i];\\n        if (deposit.amount == 0) {\\n            i++;\\n            continue;\\n        }\\n        if (deposit.amount <= _quantity) {\\n            // deposit amount is lesser than quantity use it fully\\n            _quantity = _quantity - deposit.amount;\\n            usdBalance[deposit.sender] -= deposit.amount;\\n            amountToSend = (deposit.amount * 1e18) / _price;\\n            IERC20(crab).transfer(deposit.sender, amountToSend);\\n            emit USDCDeposited(deposit.sender, deposit.amount, amountToSend, i, 0);\\n            delete deposits[i];\\n            i++;\\n        } else {\\n            // deposit amount is greater than quantity; use it partially\\n            deposits[i].amount = deposit.amount - _quantity;\\n            usdBalance[deposit.sender] -= _quantity;\\n            amountToSend = (_quantity * 1e18) / _price;\\n            IERC20(crab).transfer(deposit.sender, amountToSend);\\n            emit USDCDeposited(deposit.sender, _quantity, amountToSend, i, 0);\\n            _quantity = 0;\\n        }\\n    }\\n```\\n\\nThe code above processes deposits in the order they are submitted. An adversary can exploit this by withdrawing/depositing a large number of times then dequeuing them to create a larger number of blank deposits. Since these are all zero, it creates a fill or kill scenario. Either all of them are skipped or none. If the adversary makes the list long enough then it will be impossible to fill without going over block gas limit.

Impact:
Contract can be permanently DOS'd

Recommended mitigation:
Two potential solutions. The first would be to limit the number of deposits/withdraws that can be processed in a single netting. The second would be to allow the owner to manually skip withdraws/deposits by calling an function that increments depositsIndex and withdrawsIndex.
Vulnerability: resolveQueuedTrades() ERC777 re-enter to steal funds
Severity: medium

Description:
_openQueuedTrade() does not follow the “Checks Effects Interactions” principle and may lead to re-entry to steal the funds\\nThe prerequisite is that tokenX is ERC777 e.g. “sushi”\\nresolveQueuedTrades() call _openQueuedTrade()\\nin _openQueuedTrade() call "tokenX.transfer(queuedTrade.user)" if (revisedFee < queuedTrade.totalFee) before set queuedTrade.isQueued = false;\\n```\\n    function _openQueuedTrade(uint256 queueId, uint256 price) internal {\\n// rest of code\\n        if (revisedFee < queuedTrade.totalFee) {\\n            tokenX.transfer( //***@audit call transfer , if ERC777 , can re-enter ***/\\n                queuedTrade.user,\\n                queuedTrade.totalFee - revisedFee\\n            );\\n        }\\n\\n        queuedTrade.isQueued = false;  //****@audit  change state****/\\n    }\\n```\\n\\n3.if ERC777 re-enter to #cancelQueuedTrade() to get tokenX back,it can close, because queuedTrade.isQueued still equal true 4. back to _openQueuedTrade() set queuedTrade.isQueued = false 5.so steal tokenX

Impact:
if tokenX equal ERC777 can steal token

Recommended mitigation:
follow “Checks Effects Interactions”\\n```\\n    function _openQueuedTrade(uint256 queueId, uint256 price) internal {\\n// rest of code\\n+      queuedTrade.isQueued = false; \\n        // Transfer the fee to the target options contract\\n        IERC20 tokenX = IERC20(optionsContract.tokenX());\\n        tokenX.transfer(queuedTrade.targetContract, revisedFee);\\n\\n-       queuedTrade.isQueued = false; \\n        emit OpenTrade(queuedTrade.user, queueId, optionId);\\n    }\\n```\\n
Vulnerability: The `_fee()` function is wrongly implemented in the code
Severity: medium

Description:
_fee() function is wrongly implemented in the code so the protocol will get fewer fees and the trader will earn more\\n```\\n        (uint256 unitFee, , ) = _fees(10**decimals(), settlementFeePercentage);\\n        amount = (newFee * 10**decimals()) / unitFee;\\n```\\n\\nlet's say we have: `newFee` 100 USDC USDC Decimals is 6 `settlementFeePercentage` is 20% ==> 200\\nThe `unitFee` will be 520_000\\n`amount` = (100 * 1_000_000) / 520_000 `amount` = 192 USDC Which is supposed to be `amount` = 160 USDC

Impact:
The protocol will earn fees less than expected

Recommended mitigation:
The `_fee()` function needs to calculate the fees in this way\\n```\\ntotal_fee = (5000 * amount)/ (10000 - sf)\\n```\\n
Vulnerability: resolveQueuedTrades is intended to be non atomic but invalid signature can still cause entire transaction to revert
Severity: medium

Description:
BufferRouter#resolveQueuedTrades and unlockOptions attempt to be non atomic (i.e. doesn't revert the transaction if one fails) but an invalid signature can still cause the entire transaction to revert, because the ECDSA.recover sub call in _validateSigner can still revert.\\n```\\nfunction _validateSigner(\\n    uint256 timestamp,\\n    address asset,\\n    uint256 price,\\n    bytes memory signature\\n) internal view returns (bool) {\\n    bytes32 digest = ECDSA.toEthSignedMessageHash(\\n        keccak256(abi.encodePacked(timestamp, asset, price))\\n    );\\n    address recoveredSigner = ECDSA.recover(digest, signature);\\n    return recoveredSigner == publisher;\\n}\\n```\\n\\n_validateSigner can revert at the ECDSA.recover sub call breaking the intended non atomic nature of BufferRouter#resolveQueuedTrades and unlockOptions.

Impact:
BufferRouter#resolveQueuedTrades and unlockOptions don't function as intended if signature is malformed

Recommended mitigation:
Use a try statement inside _validateSigner to avoid any reverts:\\n```\\n    function _validateSigner(\\n        uint256 timestamp,\\n        address asset,\\n        uint256 price,\\n        bytes memory signature\\n    ) internal view returns (bool) {\\n        bytes32 digest = ECDSA.toEthSignedMessageHash(\\n            keccak256(abi.encodePacked(timestamp, asset, price))\\n        );\\n-       address recoveredSigner = ECDSA.recover(digest, signature);\\n\\n+       try ECDSA.recover(digest, signature) returns (address recoveredSigner) {\\n+           return recoveredSigner == publisher;\\n+       } else {\\n+           return false;\\n+       }\\n    }\\n```\\n
Vulnerability: Early depositors to BufferBinaryPool can manipulate exchange rates to steal funds from later depositors
Severity: high

Description:
To calculate the exchange rate for shares in BufferBinaryPool it divides the total supply of shares by the totalTokenXBalance of the vault. The first deposit can mint a very small number of shares then donate tokenX to the vault to grossly manipulate the share price. When later depositor deposit into the vault they will lose value due to precision loss and the adversary will profit.\\n```\\nfunction totalTokenXBalance()\\n    public\\n    view\\n    override\\n    returns (uint256 balance)\\n{\\n    return tokenX.balanceOf(address(this)) - lockedPremium;\\n}\\n```\\n\\nShare exchange rate is calculated using the total supply of shares and the totalTokenXBalance, which leaves it vulnerable to exchange rate manipulation. As an example, assume tokenX == USDC. An adversary can mint a single share, then donate 1e8 USDC. Minting the first share established a 1:1 ratio but then donating 1e8 changed the ratio to 1:1e8. Now any deposit lower than 1e8 (100 USDC) will suffer from precision loss and the attackers share will benefit from it.

Impact:
Adversary can effectively steal funds from later users through precision loss

Recommended mitigation:
Require a small minimum deposit (i.e. 1e6)
Vulnerability: When tokenX is an ERC777 token, users can bypass maxLiquidity
Severity: medium

Description:
When tokenX is an ERC777 token, users can use callbacks to provide liquidity exceeding maxLiquidity\\nIn BufferBinaryPool._provide, when tokenX is an ERC777 token, the tokensToSend function of account will be called in tokenX.transferFrom before sending tokens. When the user calls provide again in tokensToSend, since BufferBinaryPool has not received tokens at this time, totalTokenXBalance() has not increased, and the following checks can be bypassed, so that users can provide liquidity exceeding maxLiquidity.\\n```\\n         require(\\n             balance + tokenXAmount <= maxLiquidity,\\n             "Pool has already reached it's max limit"\\n         );\\n```\\n

Impact:
users can provide liquidity exceeding maxLiquidity.

Recommended mitigation:
Change to\\n```\\n    function _provide(\\n        uint256 tokenXAmount,\\n        uint256 minMint,\\n        address account\\n    ) internal returns (uint256 mint) {\\n// Add the line below\\n        bool success = tokenX.transferFrom(\\n// Add the line below\\n            account,\\n// Add the line below\\n            address(this),\\n// Add the line below\\n            tokenXAmount\\n// Add the line below\\n        );\\n        uint256 supply = totalSupply();\\n        uint256 balance = totalTokenXBalance();\\n\\n        require(\\n            balance // Add the line below\\n tokenXAmount <= maxLiquidity,\\n            "Pool has already reached it's max limit"\\n        );\\n\\n        if (supply > 0 && balance > 0)\\n            mint = (tokenXAmount * supply) / (balance);\\n        else mint = tokenXAmount * INITIAL_RATE;\\n\\n        require(mint >= minMint, "Pool: Mint limit is too large");\\n        require(mint > 0, "Pool: Amount is too small");\\n\\n// Remove the line below\\n        bool success = tokenX.transferFrom(\\n// Remove the line below\\n            account,\\n// Remove the line below\\n            address(this),\\n// Remove the line below\\n            tokenXAmount\\n// Remove the line below\\n        );\\n```\\n
Vulnerability: Bulls that are unable to receive NFTs will not be able to claim them later
Severity: medium

Description:
A lot of care has been taken to ensure that, if a bull has a contract address that doesn't accept ERC721s, the NFT is saved to `withdrawableCollectionTokenId` for later withdrawal. However, because there is no way to withdraw this token to a different address (and the original address doesn't accept NFTs), it will never be able to be claimed.\\nTo settle a contract, the bear calls `settleContract()`, which sends their NFT to the bull, and withdraws the collateral and premium to the bear.\\n```\\ntry IERC721(order.collection).safeTransferFrom(bear, bull, tokenId) {}\\ncatch (bytes memory) {\\n    // Transfer NFT to BvbProtocol\\n    IERC721(order.collection).safeTransferFrom(bear, address(this), tokenId);\\n    // Store that the bull has to retrieve it\\n    withdrawableCollectionTokenId[order.collection][tokenId] = bull;\\n}\\n\\nuint bearAssetAmount = order.premium + order.collateral;\\nif (bearAssetAmount > 0) {\\n    // Transfer payment tokens to the Bear\\n    IERC20(order.asset).safeTransfer(bear, bearAssetAmount);\\n}\\n```\\n\\nIn order to address the case that the bull is a contract that can't accept NFTs, the protocol uses a try-catch setup. If the transfer doesn't succeed, it transfers the NFT into the contract, and sets `withdrawableCollectionTokenId` so that the specific NFT is attributed to the bull for later withdrawal.\\nHowever, assuming the bull isn't an upgradeable contract, this withdrawal will never be possible, because their only option is to call the same function `safeTransferFrom` to the same contract address, which will fail in the same way.\\n```\\nfunction withdrawToken(bytes32 orderHash, uint tokenId) public {\\n    address collection = matchedOrders[uint(orderHash)].collection;\\n\\n    address recipient = withdrawableCollectionTokenId[collection][tokenId];\\n\\n    // Transfer NFT to recipient\\n    IERC721(collection).safeTransferFrom(address(this), recipient, tokenId);\\n\\n    // This token is not withdrawable anymore\\n    withdrawableCollectionTokenId[collection][tokenId] = address(0);\\n\\n    emit WithdrawnToken(orderHash, tokenId, recipient);\\n}\\n```\\n

Impact:
If a bull is a contract that can't receive NFTs, their orders will be matched, the bear will be able to withdraw their assets, but the bull's NFT will remain stuck in the BVB protocol contract.

Recommended mitigation:
There are a few possible solutions:\\nAdd a `to` field in the `withdrawToken` function, which allows the bull `to` withdraw the NFT `to` another address\\nCreate a function similar `to` `transferPosition` that can be used `to` transfer owners of a withdrawable NFT\\nDecide that you want `to` punish bulls who aren't able `to` receive NFTs, in which case there is no need `to` save their address or implement a `withdrawToken` function
Vulnerability: Attackers can use `reclaimContract()` to transfer assets in protocol to address(0)
Severity: high

Description:
`reclaimContract()` would transfer payment tokens to `bulls[contractId]`. An attacker can make `reclaimContract()` transfer assets to address(0).\\nAn attacker can use a fake order to trick `reclaimContract()`. The fake order needs to meet the following requirements:\\n`block.timestamp > order.expiry`.\\n`!settledContracts[contractId]`.\\n`!reclaimedContracts[contractId],`.\\nThe first one is easy to fulfilled, an attacker can decide the content of the fake order. And the others are all satisfied since the fake order couldn't be settled or reclaimed before.\\n```\\n    function reclaimContract(Order calldata order) public nonReentrant {\\n        bytes32 orderHash = hashOrder(order);\\n\\n        // ContractId\\n        uint contractId = uint(orderHash);\\n\\n        address bull = bulls[contractId];\\n\\n        // Check that the contract is expired\\n        require(block.timestamp > order.expiry, "NOT_EXPIRED_CONTRACT");\\n\\n        // Check that the contract is not settled\\n        require(!settledContracts[contractId], "SETTLED_CONTRACT");\\n\\n        // Check that the contract is not reclaimed\\n        require(!reclaimedContracts[contractId], "RECLAIMED_CONTRACT");\\n\\n        uint bullAssetAmount = order.premium + order.collateral;\\n        if (bullAssetAmount > 0) {\\n            // Transfer payment tokens to the Bull\\n            IERC20(order.asset).safeTransfer(bull, bullAssetAmount);\\n        }\\n\\n        reclaimedContracts[contractId] = true;\\n\\n        emit ReclaimedContract(orderHash, order);\\n    }\\n```\\n

Impact:
An attacker can use this vulnerability to transfer assets from BvB to address(0). It results in serious loss of funds.

Recommended mitigation:
There are multiple solutions for this problem.\\ncheck `bulls[contractId] != address(0)`\\ncheck the order is matched `matchedOrders[contractId].maker != address(0)`
Vulnerability: Transferring Ownership Might Break The Market
Severity: medium

Description:
After the transfer of the market ownership, the market might stop working, and no one could purchase any bond token from the market leading to a loss of sale for the market makers.\\nThe `callbackAuthorized` mapping contains a list of whitelisted market owners authorized to use the callback. When the users call the `purchaseBond` function, it will check at Line 390 if the current market owner is still authorized to use a callback. Otherwise, the function will revert.\\n```\\nFile: BondBaseSDA.sol\\n    function purchaseBond(\\n        uint256 id_,\\n        uint256 amount_,\\n        uint256 minAmountOut_\\n    ) external override returns (uint256 payout) {\\n        if (msg.sender != address(_teller)) revert Auctioneer_NotAuthorized();\\n\\n        BondMarket storage market = markets[id_];\\n        BondTerms memory term = terms[id_];\\n\\n        // If market uses a callback, check that owner is still callback authorized\\n        if (market.callbackAddr != address(0) && !callbackAuthorized[market.owner])\\n            revert Auctioneer_NotAuthorized();\\n```\\n\\nHowever, if the market owner transfers the market ownership to someone else. The market will stop working because the new market owner might not be on the list of whitelisted market owners (callbackAuthorized mapping). As such, no one can purchase any bond token.\\n```\\nFile: BondBaseSDA.sol\\n    function pushOwnership(uint256 id_, address newOwner_) external override {\\n        if (msg.sender != markets[id_].owner) revert Auctioneer_OnlyMarketOwner();\\n        newOwners[id_] = newOwner_;\\n    }\\n```\\n

Impact:
After the transfer of the market ownership, the market might stop working, and no one could purchase any bond token from the market leading to a loss of sale for the market makers.

Recommended mitigation:
Before pushing the ownership, if the market uses a callback, implement an additional validation check to ensure that the new market owner has been whitelisted to use the callback. This will ensure that transferring the market ownership will not break the market due to the new market owner not being whitelisted.\\n```\\nfunction pushOwnership(uint256 id_, address newOwner_) external override {\\n    if (msg.sender != markets[id_].owner) revert Auctioneer_OnlyMarketOwner();\\n// Add the line below\\n   if (markets[id_].callbackAddr != address(0) && !callbackAuthorized[newOwner_])\\n// Add the line below\\n    revert newOwnerNotAuthorizedToUseCallback();\\n    newOwners[id_] = newOwner_;\\n}\\n```\\n
Vulnerability: Market Price Lower Than Expected
Severity: medium

Description:
The market price does not conform to the specification documented within the whitepaper. As a result, the computed market price is lower than expected.\\nThe following definition of the market price is taken from the whitepaper. Taken from Page 13 of the whitepaper - Definition 25\\n\\nThe integer implementation of the market price must be rounded up per the whitepaper. This ensures that the integer implementation of the market price is greater than or equal to the real value of the market price so as to protect makers from selling tokens at a lower price than expected.\\nWithin the `BondBaseSDA.marketPrice` function, the computation of the market price is rounded up in Line 688, which conforms to the specification.\\n```\\nFile: BondBaseSDA.sol\\n    function marketPrice(uint256 id_) public view override returns (uint256) {\\n        uint256 price = currentControlVariable(id_).mulDivUp(currentDebt(id_), markets[id_].scale);\\n\\n        return (price > markets[id_].minPrice) ? price : markets[id_].minPrice;\\n    }\\n```\\n\\nHowever, within the `BondBaseSDA._currentMarketPrice` function, the market price is rounded down, resulting in the makers selling tokens at a lower price than expected.\\n```\\nFile: BondBaseSDA.sol\\n    function _currentMarketPrice(uint256 id_) internal view returns (uint256) {\\n        BondMarket memory market = markets[id_];\\n        return terms[id_].controlVariable.mulDiv(market.totalDebt, market.scale);\\n    }\\n```\\n

Impact:
Loss for the makers as their tokens are sold at a lower price than expected.\\nAdditionally, the affected `BondBaseSDA._currentMarketPrice` function is used within the `BondBaseSDA._decayAndGetPrice` function to derive the market price. Since a lower market price will be returned, this will lead to a higher amount of payout tokens. Subsequently, the `lastDecayIncrement` will be higher than expected, which will lead to a lower `totalDebt`. Lower debt means a lower market price will be computed later.

Recommended mitigation:
Ensure the market price is rounded up so that the desired property can be achieved and the makers will not be selling tokens at a lower price than expected.\\n```\\nfunction _currentMarketPrice(uint256 id_) internal view returns (uint256) {\\n    BondMarket memory market = markets[id_];\\n// Remove the line below\\n   return terms[id_].controlVariable.mulDiv(market.totalDebt, market.scale);\\n// Add the line below\\n   return terms[id_].controlVariable.mulDivUp(market.totalDebt, market.scale);\\n}\\n```\\n