msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Teller Cannot Be Removed From Callback Contract
Severity: medium

Description:
If a vulnerable Teller is being exploited by an attacker, there is no way for the owner of the Callback Contract to remove the vulnerable Teller from their Callback Contract.\\nThe Callback Contract is missing the feature to remove a Teller. Once a Teller has been added to the whitelist (approvedMarkets mapping), it is not possible to remove the Teller from the whitelist.\\n```\\nFile: BondBaseCallback.sol\\n    /* ========== WHITELISTING ========== */\\n\\n    /// @inheritdoc IBondCallback\\n    function whitelist(address teller_, uint256 id_) external override onlyOwner {\\n        // Check that the market id is a valid, live market on the aggregator\\n        try _aggregator.isLive(id_) returns (bool live) {\\n            if (!live) revert Callback_MarketNotSupported(id_);\\n        } catch {\\n            revert Callback_MarketNotSupported(id_);\\n        }\\n\\n        // Check that the provided teller is the teller for the market ID on the stored aggregator\\n        // We could pull the teller from the aggregator, but requiring the teller to be passed in\\n        // is more explicit about which contract is being whitelisted\\n        if (teller_ != address(_aggregator.getTeller(id_))) revert Callback_TellerMismatch();\\n\\n        approvedMarkets[teller_][id_] = true;\\n    }\\n```\\n

Impact:
In the event that a whitelisted Teller is found to be vulnerable and has been actively exploited by an attacker in the wild, the owner of the Callback Contract needs to mitigate the issue swiftly by removing the vulnerable Teller from the Callback Contract to stop it from draining the asset within the Callback Contract. However, the mitigation effort will be hindered by the fact there is no way to remove a Teller within the Callback Contract once it has been whitelisted. Thus, it might not be possible to stop the attacker from exploiting the vulnerable Teller to drain assets within the Callback Contract. The Callback Contract owners would need to find a workaround to block the attack, which will introduce an unnecessary delay to the recovery process where every second counts.\\nAdditionally, if the owner accidentally whitelisted the wrong Teller, there is no way to remove it.

Recommended mitigation:
Consider implementing an additional function to allow the removal of a Teller from the whitelist (approvedMarkets mapping), so that a vulnerable Teller can be removed swiftly if needed.\\n```\\nfunction removeFromWhitelist(address teller_, uint256 id_) external override onlyOwner {\\n    approvedMarkets[teller_][id_] = false;\\n}\\n```\\n\\nNote: Although the owner of the Callback Contract can DOS its own market by abusing the `removeFromWhitelist` function, no sensible owner would do so.
Vulnerability: Debt Decay Faster Than Expected
Severity: medium

Description:
The debt decay at a rate faster than expected, causing market makers to sell bond tokens at a lower price than expected.\\nThe following definition of the debt decay reference time following any purchases at time `t` taken from the whitepaper. The second variable, which is the delay increment, is rounded up. Following is taken from Page 15 of the whitepaper - Definition 27\\n\\nHowever, the actual implementation in the codebase differs from the specification. At Line 514, the delay increment is rounded down instead.\\n```\\nFile: BondBaseSDA.sol\\n        // Set last decay timestamp based on size of purchase to linearize decay\\n        uint256 lastDecayIncrement = debtDecayInterval.mulDiv(payout_, lastTuneDebt);\\n        metadata[id_].lastDecay += uint48(lastDecayIncrement);\\n```\\n

Impact:
When the delay increment (TD) is rounded down, the debt decay reference time increment will be smaller than expected. The debt component will then decay at a faster rate. As a result, the market price will not be adjusted in an optimized manner, and the market price will fall faster than expected, causing market makers to sell bond tokens at a lower price than expected.\\nFollowing is taken from Page 8 of the whitepaper - Definition 8\\n

Recommended mitigation:
When computing the `lastDecayIncrement`, the result should be rounded up.\\n```\\n// Set last decay timestamp based on size of purchase to linearize decay\\n// Remove the line below\\n uint256 lastDecayIncrement = debtDecayInterval.mulDiv(payout_, lastTuneDebt);\\n// Add the line below\\n uint256 lastDecayIncrement = debtDecayInterval.mulDivUp(payout_, lastTuneDebt);\\nmetadata[id_].lastDecay // Add the line below\\n= uint48(lastDecayIncrement);\\n```\\n
Vulnerability: Fixed Term Bond tokens can be minted with non-rounded expiry
Severity: medium

Description:
Fixed Term Tellers intend to mint tokens that expire once per day, to consolidate liquidity and create a uniform experience. However, this rounding is not enforced on the external `deploy()` function, which allows for tokens expiring at unexpected times.\\nIn `BondFixedTermTeller.sol`, new tokenIds are deployed through the `_handlePayout()` function. The function calculates the expiry (rounded down to the nearest day), uses this expiry to create a tokenId, and — if that tokenId doesn't yet exist — deploys it.\\n```\\n// rest of code\\nexpiry = ((vesting_ + uint48(block.timestamp)) / uint48(1 days)) * uint48(1 days);\\n\\n// Fixed-term user payout information is handled in BondTeller.\\n// Teller mints ERC-1155 bond tokens for user.\\nuint256 tokenId = getTokenId(payoutToken_, expiry);\\n\\n// Create new bond token if it doesn't exist yet\\nif (!tokenMetadata[tokenId].active) {\\n    _deploy(tokenId, payoutToken_, expiry);\\n}\\n// rest of code\\n```\\n\\nThis successfully consolidates all liquidity into one daily tokenId, which expires (as expected) at the time included in the tokenId.\\nHowever, if the `deploy()` function is called directly, no such rounding occurs:\\n```\\nfunction deploy(ERC20 underlying_, uint48 expiry_)\\n    external\\n    override\\n    nonReentrant\\n    returns (uint256)\\n{\\n    uint256 tokenId = getTokenId(underlying_, expiry_);\\n    // Only creates token if it does not exist\\n    if (!tokenMetadata[tokenId].active) {\\n        _deploy(tokenId, underlying_, expiry_);\\n    }\\n    return tokenId;\\n}\\n```\\n\\nThis creates a mismatch between the tokenId time and the real expiry time, as tokenId is calculated by rounding the expiry down to the nearest day:\\n```\\nuint256 tokenId = uint256(\\n    keccak256(abi.encodePacked(underlying_, expiry_ / uint48(1 days)))\\n);\\n```\\n\\n... while the `_deploy()` function saves the original expiry:\\n```\\ntokenMetadata[tokenId_] = TokenMetadata(\\n    true,\\n    underlying_,\\n    uint8(underlying_.decimals()),\\n    expiry_,\\n    0\\n);\\n```\\n

Impact:
The `deploy()` function causes a number of issues:\\nTokens can be deployed that don't expire at the expected daily time, which may cause issues with your front end or break user's expectations\\nTokens can expire at times that don't align with the time included in the tokenId\\nMalicious users can pre-deploy tokens at future timestamps to "take over" the token for a given day and lock it at a later time stamp, which then "locks in" that expiry time and can't be changed by the protocol

Recommended mitigation:
Include the same rounding process in `deploy()` as is included in _handlePayout():\\n```\\nfunction deploy(ERC20 underlying_, uint48 expiry_)\\n        external\\n        override\\n        nonReentrant\\n        returns (uint256)\\n    {\\n        expiry = ((vesting_ + uint48(block.timestamp)) / uint48(1 days)) * uint48(1 days);\\n        uint256 tokenId = getTokenId(underlying_, expiry_);\\n        // rest of code\\n```\\n
Vulnerability: Fixed Term Teller tokens can be created with an expiry in the past
Severity: high

Description:
The Fixed Term Teller does not allow tokens to be created with a timestamp in the past. This is a fact that protocols using this feature will expect to hold and build their systems around. However, users can submit expiry timestamps slightly in the future, which correlate to tokenIds in the past, which allows them to bypass this check.\\nIn `BondFixedTermTeller.sol`, the `create()` function allows protocols to trade their payout tokens directly for bond tokens. The expectation is that protocols will build their own mechanisms around this. It is explicitly required that they cannot do this for bond tokens that expire in the past, only those that have yet to expire:\\n```\\nif (expiry_ < block.timestamp) revert Teller_InvalidParams();\\n```\\n\\nHowever, because tokenIds round timestamps down to the latest day, protocols are able to get around this check.\\nHere's an example:\\nThe most recently expired token has an expiration time of 1668524400 (correlates to 9am this morning)\\nIt is currently 1668546000 (3pm this afternoon)\\nA protocol calls create() with an expiry of 1668546000 + 1\\nThis passes the check that `expiry_ >= block.timestamp`\\nWhen the expiry is passed to `getTokenId()` it rounds the time down to the latest day, which is the day corresponding with 9am this morning\\nThis expiry associated with this tokenId is 9am this morning, so they are able to redeem their tokens instantly

Impact:
Protocols can bypass the check that all created tokens must have an expiry in the future, and mint tokens with a past expiry that can be redeemed immediately.\\nThis may not cause a major problem for Bond Protocol itself, but protocols will be building on top of this feature without expecting this behavior.\\nLet's consider, for example, a protocol that builds a mechanism where users can stake some asset, and the protocol will trade payout tokens to create bond tokens for them at a discount, with the assumption that they will expire in the future. This issue could create an opening for a savvy user to stake, mint bond tokens, redeem and dump them immediately, buy more assets to stake, and continue this cycle to earn arbitrage returns and tank the protocol's token.\\nBecause there are a number of situations like the one above where this issue could lead to a major loss of funds for a protocol building on top of Bond, I consider this a high severity.

Recommended mitigation:
Before checking whether `expiry_ < block.timestamp`, expiry should be rounded to the nearest day:\\n```\\nexpiry = ((vesting_ + uint48(block.timestamp)) / uint48(1 days)) * uint48(1 days);\\n```\\n
Vulnerability: findMarketFor() missing check minAmountOut_
Severity: medium

Description:
BondAggregator#findMarketFor() minAmountOut_ does not actually take effect，may return a market's "payout" smaller than minAmountOut_ , Causes users to waste gas calls to purchase\\nBondAggregator#findMarketFor() has check minAmountOut_ <= maxPayout but the actual "payout" by "amountIn_" no check greater than minAmountOut_\\n```\\n    function findMarketFor(\\n        address payout_,\\n        address quote_,\\n        uint256 amountIn_,\\n        uint256 minAmountOut_,\\n        uint256 maxExpiry_\\n    ) external view returns (uint256) {\\n// rest of code\\n            if (expiry <= maxExpiry_) {\\n                payouts[i] = minAmountOut_ <= maxPayout\\n                    ? payoutFor(amountIn_, ids[i], address(0))\\n                    : 0;\\n\\n                if (payouts[i] > highestOut) {//****@audit not check payouts[i] >= minAmountOut_******//\\n                    highestOut = payouts[i];\\n                    id = ids[i];\\n                }\\n            }\\n```\\n

Impact:
The user gets the optimal market through BondAggregator#findMarketFor(), but incorrectly returns a market smaller than minAmountOut_, and the call to purchase must fail, resulting in wasted gas

Recommended mitigation:
```\\n    function findMarketFor(\\n        address payout_,\\n        address quote_,\\n        uint256 amountIn_,\\n        uint256 minAmountOut_,\\n        uint256 maxExpiry_\\n    ) external view returns (uint256) {\\n// rest of code\\n            if (expiry <= maxExpiry_) {\\n                payouts[i] = minAmountOut_ <= maxPayout\\n                    ? payoutFor(amountIn_, ids[i], address(0))\\n                    : 0;\\n\\n-               if (payouts[i] > highestOut) {\\n+               if (payouts[i] >= minAmountOut_ && payouts[i] > highestOut) {\\n                    highestOut = payouts[i];\\n                    id = ids[i];\\n                }\\n            }\\n```\\n
Vulnerability: Create Fee Discount Feature Is Broken
Severity: medium

Description:
The create fee discount feature is found to be broken within the protocol.\\nThe create fee discount feature relies on the `createFeeDiscount` state variable to determine the fee to be discounted from the protocol fee. However, it was observed that there is no way to initialize the `createFeeDiscount` state variable. As a result, the `createFeeDiscount` state variable will always be zero.\\n```\\nFile: BondFixedExpiryTeller.sol\\n        // If fee is greater than the create discount, then calculate the fee and store it\\n        // Otherwise, fee is zero.\\n        if (protocolFee > createFeeDiscount) {\\n            // Calculate fee amount\\n            uint256 feeAmount = amount_.mulDiv(protocolFee - createFeeDiscount, FEE_DECIMALS);\\n            rewards[_protocol][underlying_] += feeAmount;\\n\\n            // Mint new bond tokens\\n            bondToken.mint(msg.sender, amount_ - feeAmount);\\n\\n            return (bondToken, amount_ - feeAmount);\\n        } else {\\n            // Mint new bond tokens\\n            bondToken.mint(msg.sender, amount_);\\n\\n            return (bondToken, amount_);\\n        }\\n```\\n\\n```\\nFile: BondFixedTermTeller.sol\\n        // If fee is greater than the create discount, then calculate the fee and store it\\n        // Otherwise, fee is zero.\\n        if (protocolFee > createFeeDiscount) {\\n            // Calculate fee amount\\n            uint256 feeAmount = amount_.mulDiv(protocolFee - createFeeDiscount, FEE_DECIMALS);\\n            rewards[_protocol][underlying_] += feeAmount;\\n\\n            // Mint new bond tokens\\n            _mintToken(msg.sender, tokenId, amount_ - feeAmount);\\n\\n            return (tokenId, amount_ - feeAmount);\\n        } else {\\n            // Mint new bond tokens\\n            _mintToken(msg.sender, tokenId, amount_);\\n\\n            return (tokenId, amount_);\\n        }\\n```\\n

Impact:
The create fee discount feature is broken within the protocol. There is no way for the protocol team to configure a discount for the users of the `BondFixedExpiryTeller.create` and `BondFixedTermTeller.create` functions. As such, the users will not obtain any discount from the protocol when using the create function.

Recommended mitigation:
Implement a setter method for the `createFeeDiscount` state variable and the necessary verification checks.\\n```\\nfunction setCreateFeeDiscount(uint48 createFeeDiscount_) external requiresAuth {\\n    if (createFeeDiscount_ > protocolFee)  revert Teller_InvalidParams();\\n    if (createFeeDiscount_ > 5e3) revert Teller_InvalidParams();\\n    createFeeDiscount = createFeeDiscount_;\\n}\\n```\\n
Vulnerability: Auctioneer Cannot Be Removed From The Protocol
Severity: medium

Description:
If a vulnerable Auctioneer is being exploited by an attacker, there is no way to remove the vulnerable Auctioneer from the protocol.\\nThe protocol is missing the feature to remove an auctioneer. Once an auctioneer has been added to the whitelist, it is not possible to remove the auctioneer from the whitelist.\\n```\\nFile: BondAggregator.sol\\n    function registerAuctioneer(IBondAuctioneer auctioneer_) external requiresAuth {\\n        // Restricted to authorized addresses\\n\\n        // Check that the auctioneer is not already registered\\n        if (_whitelist[address(auctioneer_)])\\n            revert Aggregator_AlreadyRegistered(address(auctioneer_));\\n\\n        // Add the auctioneer to the whitelist\\n        auctioneers.push(auctioneer_);\\n        _whitelist[address(auctioneer_)] = true;\\n    }\\n```\\n

Impact:
In the event that a whitelisted Auctioneer is found to be vulnerable and has been actively exploited by an attacker in the wild, the protocol needs to mitigate the issue swiftly by removing the vulnerable Auctioneer from the protocol. However, the mitigation effort will be hindered by the fact there is no way to remove an Auctioneer within the protocol once it has been whitelisted. Thus, it might not be possible to stop the attacker from exploiting the vulnerable Auctioneer. The protocol team would need to find a workaround to block the attack, which will introduce an unnecessary delay to the recovery process where every second counts.\\nAdditionally, if the admin accidentally whitelisted the wrong Auctioneer, there is no way to remove it.

Recommended mitigation:
Consider implementing an additional function to allow the removal of an Auctioneer from the whitelist, so that vulnerable Auctioneer can be removed swiftly if needed.\\n```\\nfunction deregisterAuctioneer(IBondAuctioneer auctioneer_) external requiresAuth {\\n    // Remove the auctioneer from the whitelist\\n    _whitelist[address(auctioneer_)] = false;\\n}\\n```\\n
Vulnerability: BondBaseSDA.setDefaults doesn't validate inputs
Severity: medium

Description:
BondBaseSDA.setDefaults doesn't validate inputs which can lead to initializing new markets incorrectly\\n```\\n    function setDefaults(uint32[6] memory defaults_) external override requiresAuth {\\n        // Restricted to authorized addresses\\n        defaultTuneInterval = defaults_[0];\\n        defaultTuneAdjustment = defaults_[1];\\n        minDebtDecayInterval = defaults_[2];\\n        minDepositInterval = defaults_[3];\\n        minMarketDuration = defaults_[4];\\n        minDebtBuffer = defaults_[5];\\n    }\\n```\\n\\nFunction BondBaseSDA.setDefaults doesn't do any checkings, as you can see. Because of that it's possible to provide values that will break market functionality.\\nFor example you can set `minDepositInterval` to be bigger than `minMarketDuration` and it will be not possible to create new market.\\nOr you can provide `minDebtBuffer` to be 100% ot 0% that will break logic of market closing.

Impact:
Can't create new market or market logic will be not working as designed.

Recommended mitigation:
Add input validation.
Vulnerability: BondAggregator.liveMarketsBy eventually will revert because of block gas limit
Severity: medium

Description:
BondAggregator.liveMarketsBy eventually will revert because of block gas limit\\n```\\n    function liveMarketsBy(address owner_) external view returns (uint256[] memory) {\\n        uint256 count;\\n        IBondAuctioneer auctioneer;\\n        for (uint256 i; i < marketCounter; ++i) {\\n            auctioneer = marketsToAuctioneers[i];\\n            if (auctioneer.isLive(i) && auctioneer.ownerOf(i) == owner_) {\\n                ++count;\\n            }\\n        }\\n\\n\\n        uint256[] memory ids = new uint256[](count);\\n        count = 0;\\n        for (uint256 i; i < marketCounter; ++i) {\\n            auctioneer = marketsToAuctioneers[i];\\n            if (auctioneer.isLive(i) && auctioneer.ownerOf(i) == owner_) {\\n                ids[count] = i;\\n                ++count;\\n            }\\n        }\\n\\n\\n        return ids;\\n    }\\n```\\n\\nBondAggregator.liveMarketsBy function is looping through all markets and does at least `marketCounter` amount of external calls(when all markets are not live) and at most 4 * `marketCounter` external calls(when all markets are live and owner matches. This all consumes a lot of gas, even that is called from view function. And each new market increases loop size.\\nThat means that after some time `marketsToAuctioneers` mapping will be big enough that the gas amount sent for view/pure function will be not enough to retrieve all data(50 million gas according to this). So the function will revert.\\nAlso similar problem is with `findMarketFor`, `marketsFor` and `liveMarketsFor` functions.

Impact:
Functions will always revert and whoever depends on it will not be able to get information.

Recommended mitigation:
Remove not active markets or some start and end indices to functions.
Vulnerability: `Staking.unstake()` doesn't decrease the original voting power that was used in `Staking.stake()`.
Severity: high

Description:
`Staking.unstake()` doesn't decrease the original voting power that was used in `Staking.stake()`.\\nWhen users stake/unstake the underlying NFTs, it calculates the token voting power using getTokenVotingPower() and increases/decreases their voting power accordingly.\\n```\\n    function getTokenVotingPower(uint _tokenId) public override view returns (uint) {\\n      if (ownerOf(_tokenId) == address(0)) revert NonExistentToken();\\n\\n      // If tokenId < 10000, it's a FrankenPunk, so 100/100 = a multiplier of 1\\n      uint multiplier = _tokenId < 10_000 ? PERCENT : monsterMultiplier;\\n      \\n      // evilBonus will return 0 for all FrankenMonsters, as they are not eligible for the evil bonus\\n      return ((baseVotes * multiplier) / PERCENT) + stakedTimeBonus[_tokenId] + evilBonus(_tokenId);\\n    }\\n```\\n\\nBut `getTokenVotingPower()` uses some parameters like `monsterMultiplier` and `baseVotes` and the output would be changed for the same `tokenId` after the admin changed these settings.\\nCurrently, `_stake()` and `_unstake()` calculates the token voting power independently and the below scenario would be possible.\\nAt the first time, `baseVotes = 20, monsterMultiplier = 50`.\\nA user staked a `FrankenMonsters` and his voting power = 10 here.\\nAfter that, the admin changed `monsterMultiplier = 60`.\\nWhen a user tries to unstake the NFT, the token voting power will be `20 * 60 / 100 = 12` here.\\nSo it will revert with uint underflow here.\\nAfter all, he can't unstake the NFT.

Impact:
`votesFromOwnedTokens` might be updated wrongly or users can't unstake for the worst case because it doesn't decrease the same token voting power while unstaking.

Recommended mitigation:
I think we should add a mapping like `tokenVotingPower` to save an original token voting power when users stake the token and decrease the same amount when they unstake.