msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: changeTreasury() Lack of check and remove old
Severity: medium

Description:
changeTreasury() Lack of check and remove old\\nchangeTreasury() used to set new treasury The code is as follows：\\n```\\n    function changeTreasury(uint256 _marketId, address _treasury)\\n        public\\n        onlyTimeLocker\\n    {\\n        if (_treasury == address(0)) revert AddressZero();\\n\\n        address[2] memory vaults = marketIdToVaults[_marketId];\\n\\n        if (vaults[0] == address(0) || vaults[1] == address(0)) {\\n            revert MarketDoesNotExist(_marketId);\\n        }\\n        IVaultV2(vaults[0]).whiteListAddress(_treasury);\\n        IVaultV2(vaults[1]).whiteListAddress(_treasury);\\n        IVaultV2(vaults[0]).setTreasury(treasury);\\n        IVaultV2(vaults[1]).setTreasury(treasury);\\n\\n        emit AddressWhitelisted(_treasury, _marketId);\\n    }\\n```\\n\\nThe above code has the following problem:\\nno check whether the new treasury same as the old. If it is the same, the whitelist will be canceled.\\nUse setTreasury(VaultFactoryV2.treasury), it should be setTreasury(_treasury)\\nnot cancel old treasury from the whitelist

Impact:
whiteListAddress abnormal

Recommended mitigation:
```\\n    function changeTreasury(uint256 _marketId, address _treasury)\\n        public\\n        onlyTimeLocker\\n    {\\n        if (_treasury == address(0)) revert AddressZero();\\n\\n        address[2] memory vaults = marketIdToVaults[_marketId];\\n\\n        if (vaults[0] == address(0) || vaults[1] == address(0)) {\\n            revert MarketDoesNotExist(_marketId);\\n        }\\n\\n+       require(vaults[0].treasury() !=_treasury,"same"); //check same\\n+       IVaultV2(vaults[0]).whiteListAddress(vaults[0].treasury()); //cancel old whitelist\\n+       IVaultV2(vaults[1]).whiteListAddress(vaults[1].treasury()); //cancel old whitelist\\n\\n        IVaultV2(vaults[0]).whiteListAddress(_treasury);\\n        IVaultV2(vaults[1]).whiteListAddress(_treasury);\\n+       IVaultV2(vaults[0]).setTreasury(_treasury);\\n+       IVaultV2(vaults[1]).setTreasury(_treasury);\\n-       IVaultV2(vaults[0]).setTreasury(treasury);\\n-       IVaultV2(vaults[1]).setTreasury(treasury);\\n\\n        emit AddressWhitelisted(_treasury, _marketId);\\n    }\\n```\\n
Vulnerability: mintRollovers should require entitledShares >= relayerFee
Severity: medium

Description:
mintRollovers should require entitledShares >= relayerFee\\nIn mintRollovers, the rollover is only not skipped if queue[index].assets >= relayerFee,\\n```\\n                if (entitledShares > queue[index].assets) {\\n                    // skip the rollover for the user if the assets cannot cover the relayer fee instead of revert.\\n                    if (queue[index].assets < relayerFee) {\\n                        index++;\\n                        continue;\\n                    }\\n```\\n\\nIn fact, since the user is already profitable, entitledShares is the number of assets of the user, which is greater than queue[index].assets, so it should check that entitledShares >= relayerFee, and use entitledShares instead of queue[index].assets to subtract relayerFee when calculating assetsToMint later.

Impact:
This will prevent rollover even if the user has more assets than relayerFee

Recommended mitigation:
Change to\\n```\\n                if (entitledShares > queue[index].assets) {\\n                    // skip the rollover for the user if the assets cannot cover the relayer fee instead of revert.\\n// Remove the line below\\n                   if (queue[index].assets < relayerFee) {\\n// Add the line below\\n                   if (entitledShares < relayerFee) {\\n                        index// Add the line below\\n// Add the line below\\n;\\n                        continue;\\n                    }\\n// rest of code\\n// Remove the line below\\n                   uint256 assetsToMint = queue[index].assets // Remove the line below\\n relayerFee;\\n// Add the line below\\n                   uint256 assetsToMint = entitledShares // Remove the line below\\n relayerFee;\\n```\\n
Vulnerability: Vault Factory ownership can be changed immediately and bypass timelock delay
Severity: medium

Description:
The VaultFactoryV2 contract is supposed to use a timelock contract with a delay period when changing its owner. However, there is a loophole that allows the owner to change the owner address instantly, without waiting for the delay period to expire. This defeats the purpose of the timelock contract and exposes the VaultFactoryV2 contract to potential abuse.\\nIn project description, timelock is required when making critical changes. Admin can only configure new markets and epochs on those markets.\\n```\\n    2) Admin can configure new markets and epochs on those markets, Timelock can make cirital changes like changing the oracle or whitelisitng controllers.\\n```\\n\\nThe VaultFactoryV2 contract has a `changeOwner` function that is supposed to be called only by the timelock contract with a delay period.\\n```\\nfunction changeOwner(address _owner) public onlyTimeLocker {\\n        if (_owner == address(0)) revert AddressZero();\\n        _transferOwnership(_owner);\\n    }\\n```\\n\\nThe VaultFactoryV2 contract inherits from the Openzeppelin Ownable contract, which has a `transferOwnership` function that allows the owner to change the owner address immediately. However, the `transferOwnership` function is not overridden by the `changeOwner` function, which creates a conflict and a vulnerability. The owner can bypass the timelock delay and use the `transferOwnership` function to change the owner address instantly.\\n```\\n    function transferOwnership(address newOwner) public virtual onlyOwner {\\n        require(newOwner != address(0), "Ownable: new owner is the zero address");\\n        _transferOwnership(newOwner);\\n    }\\n```\\n

Impact:
The transferOwnership is not worked as design (using timelock), the timelock delay become useless. This means that if the owner address is hacked or corrupted, the attacker can take over the contract immediately, leaving no time for the protocol and the users to respond or intervene.

Recommended mitigation:
Override the `transferOwnership` function and add modifier `onlyTimeLocker`.
Vulnerability: VaultFactoryV2#changeTreasury misconfigures the vault
Severity: medium

Description:
VaultFactoryV2#changeTreasury misconfigures the vault because the setTreasury subcall uses the wrong variable\\nVaultFactoryV2.sol#L228-L246\\n```\\nfunction changeTreasury(uint256 _marketId, address _treasury)\\n    public\\n    onlyTimeLocker\\n{\\n    if (_treasury == address(0)) revert AddressZero();\\n\\n    address[2] memory vaults = marketIdToVaults[_marketId];\\n\\n    if (vaults[0] == address(0) || vaults[1] == address(0)) {\\n        revert MarketDoesNotExist(_marketId);\\n    }\\n\\n    IVaultV2(vaults[0]).whiteListAddress(_treasury);\\n    IVaultV2(vaults[1]).whiteListAddress(_treasury);\\n    IVaultV2(vaults[0]).setTreasury(treasury);\\n    IVaultV2(vaults[1]).setTreasury(treasury);\\n\\n    emit AddressWhitelisted(_treasury, _marketId);\\n}\\n```\\n\\nWhen setting the treasury for the underlying vault pair it accidentally use the treasury variable instead of _treasury. This means it uses the local VaultFactoryV2 treasury rather than the function input.\\nControllerPeggedAssetV2.sol#L111-L123\\n```\\n    premiumVault.sendTokens(_epochId, premiumFee, treasury);\\n    premiumVault.sendTokens(\\n        _epochId,\\n        premiumTVL - premiumFee,\\n        address(collateralVault)\\n    );\\n    // strike price is reached so collateral is still entitled to premiumTVL - premiumFee but looses collateralTVL\\n    collateralVault.sendTokens(_epochId, collateralFee, treasury);\\n    collateralVault.sendTokens(\\n        _epochId,\\n        collateralTVL - collateralFee,\\n        address(premiumVault)\\n    );\\n```\\n\\nThis misconfiguration can be damaging as it may cause the triggerDepeg call in the controller to fail due to the sendToken subcall. Additionally the time lock is the one required to call it which has a minimum of 3 days wait period. The result is that valid depegs may not get paid out since they are time sensitive.

Impact:
Valid depegs may be missed due to misconfiguration

Recommended mitigation:
Set to _treasury rather than treasury.
Vulnerability: Null epochs will freeze rollovers
Severity: medium

Description:
When rolling a position it is required that the user didn't payout on the last epoch. The issue with the check is that if a null epoch is triggered then rollovers will break even though the vault didn't make a payout\\nCarousel.sol#L401-L406\\n```\\n            uint256 entitledShares = previewWithdraw(\\n                queue[index].epochId,\\n                queue[index].assets\\n            );\\n            // mint only if user won epoch he is rolling over\\n            if (entitledShares > queue[index].assets) {\\n```\\n\\nWhen minting rollovers the following check is made so that the user won't automatically roll over if they made a payout last epoch. This check however will fail if there is ever a null epoch. Since no payout is made for a null epoch it should continue to rollover but doesn't.

Impact:
Rollover will halt after null epoch

Recommended mitigation:
Change to less than or equal to:\\n```\\n-           if (entitledShares > queue[index].assets) {\\n+           if (entitledShares >= queue[index].assets) {\\n```\\n
Vulnerability: Inconsistent use of epochBegin could lock user funds
Severity: medium

Description:
The epochBegin timestamp is used inconsistently and could lead to user funds being locked.\\nThe function `ControllerPeggedAssetV2.triggerNullEpoch` checks for timestamp like this:\\n```\\nif (block.timestamp < uint256(epochStart)) revert EpochNotStarted();\\n```\\n\\nThe modifier `epochHasNotStarted` (used by Carousel.deposit) checks it like this:\\n```\\nif (block.timestamp > epochConfig[_id].epochBegin)\\n    revert EpochAlreadyStarted();\\n```\\n\\nBoth functions can be called when `block.timestamp == epochBegin`. This could lead to a scenario where a deposit happens after `triggerNullEpoch` is called (both in the same block). Because `triggerNullEpoch` sets the value for `finalTVL`, the TVL that comes from the deposit is not accounted for. If emissions have been distributed this epoch, this will lead to the incorrect distribution of emissions and once all emissions have been claimed the remaining assets will not be claimable, due to reversion in `withdraw` when trying to send emissions:\\n```\\nfunction previewEmissionsWithdraw(uint256 _id, uint256 _assets)\\n    public\\n    view\\n    returns (uint256 entitledAmount)\\n{\\n    entitledAmount = _assets.mulDivDown(emissions[_id], finalTVL[_id]);\\n}\\n// rest of code\\n//in withdraw:\\nuint256 entitledEmissions = previewEmissionsWithdraw(_id, _assets);\\nif (epochNull[_id] == false) {\\n    entitledShares = previewWithdraw(_id, _assets);\\n} else {\\n    entitledShares = _assets;\\n}\\nif (entitledShares > 0) {\\n    SemiFungibleVault.asset.safeTransfer(_receiver, entitledShares);\\n}\\nif (entitledEmissions > 0) {\\n    emissionsToken.safeTransfer(_receiver, entitledEmissions);\\n}\\n```\\n\\nThe above could also lead to revert through division by 0 if `finalTVL` is set to 0, even though the deposit after was successful.

Impact:
incorrect distribution, Loss of deposited funds

Recommended mitigation:
The modifier `epochHasNotStarted` should use `>=` as comparator
Vulnerability: Denial-of-Service in the liquidation flow results in the collateral NTF will be stuck in the contract.
Severity: medium

Description:
If the `loanTovalue` value of the offer is extremely high, the liquidation flow will be reverted, causing the collateral NTF to persist in the contract forever.\\nThe platform allows users to sign offers and provide funds to those who need to borrow assets.\\nIn the first scenario, the lender provided an offer that the `loanTovalue` as high as the result of the `shareMatched` is `0`. For example, if the borrowed amount was `1e40` and the offer had a `loanTovalue` equal to `1e68`, the share would be `0`.\\nAs a result, an arithmetic error (Division or modulo by 0) will occur in the `price()` function at line 50 during the liquidation process.\\nIn the second scenario, if the lender's share exceeds `0`, but the offer's `loanToValue` is extremely high, the `price()` function at line 54 may encounter an arithmetic error(Arithmetic over/underflow) during the `estimatedValue` calculation.\\nPoof of Concept\\nkairos-contracts/test/BorrowBorrow.t.sol\\n```\\nfunction testBorrowOverflow() public {\\n    uint256 borrowAmount = 1e40;\\n    BorrowArg[] memory borrowArgs = new BorrowArg[](1);\\n    (, ,uint256 loanId , ) = kairos.getParameters();\\n    loanId += 1;\\n\\n    Offer memory offer = Offer({\\n            assetToLend: money,\\n            loanToValue: 1e61,\\n            duration: 1,\\n            expirationDate: block.timestamp + 2 hours,\\n            tranche: 0,\\n            collateral: getNft()\\n        });\\n    uint256 currentTokenId;\\n\\n    getFlooz(signer, money, getOfferArg(offer).amount);\\n\\n    {\\n        OfferArg[] memory offerArgs = new OfferArg[](1);\\n        currentTokenId = getJpeg(BORROWER, nft);\\n        offer.collateral.id = currentTokenId;\\n        offerArgs[0] = OfferArg({\\n            signature: getSignature(offer),\\n            amount: borrowAmount,\\n            offer: offer\\n        });\\n        borrowArgs[0] = BorrowArg({nft: NFToken({id: currentTokenId, implem: nft}), args: offerArgs});\\n    }\\n\\n    vm.prank(BORROWER);\\n    kairos.borrow(borrowArgs);\\n\\n    assertEq(nft.balanceOf(BORROWER), 0);\\n    assertEq(money.balanceOf(BORROWER), borrowAmount);\\n    assertEq(nft.balanceOf(address(kairos)), 1);\\n\\n    vm.warp(block.timestamp + 1);\\n    Loan memory loan = kairos.getLoan(loanId);\\n    console.log("price of loanId", kairos.price(loanId));\\n}\\n```\\n

Impact:
The loan position will not be liquidated, which will result in the collateral NTF being permanently frozen in the contract.

Recommended mitigation:
We recommend adding the mechanism during the borrowing process to restrict the maximum `loanToValue` limit and ensure that the lender's share is always greater than zero. This will prevent arithmetic errors.
Vulnerability: Adversary can utilize a large number of their own loans to cheat other lenders out of interest
Severity: medium

Description:
The minimal interest paid by a loan is scaled by the number of provisions that make up the loan. By inflating the number of provisions with their own provisions then can cause legitimate lenders to receive a much lower interest rate than intended.\\nClaimFacet.sol#L94-L106\\n```\\nfunction sendInterests(Loan storage loan, Provision storage provision) internal returns (uint256 sent) {\\n    uint256 interests = loan.payment.paid - loan.lent;\\n    if (interests == loan.payment.minInterestsToRepay) {\\n        // this is the case if the loan is repaid shortly after issuance\\n        // each lender gets its minimal interest, as an anti ddos measure to spam offer\\n        sent = provision.amount + (interests / loan.nbOfPositions);\\n    } else {\\n        /* provision.amount / lent = share of the interests belonging to the lender. The parenthesis make the\\n        calculus in the order that maximizes precison */\\n        sent = provision.amount + (interests * (provision.amount)) / loan.lent;\\n    }\\n    loan.assetLent.checkedTransfer(msg.sender, sent);\\n}\\n```\\n\\nIf a loan is paid back before the minimal interest rate has been reached then each provision will receive the unweighted minimal interest amount. This can be abused to take loans that pay legitimate lenders a lower APR than expected, cheating them of their yield.\\nExample: A user wishes to borrow 1000 USDC at 10% APR. Assume the minimal interest per provision is 10 USDC and minimum borrow amount is 20 USDC. After 1 year the user would owe 100 USDC in interest. A user can abuse the minimum to pay legitimate lenders much lower than 10% APR. The attacker will find a legitimate offer to claim 820 USDC. This will create an offer for themselves and borrow 20 USDC from it 9 times. This creates a total of 10 provisions each owed a minimum of 10 USDC or 100 USDC total. Now after 1 year they owe 100 USDC on their loan and the repay the loan. Since 100 USDC is the minimum, each of the 10 provisions will get their minimal interest. 90 USDC will go to their provisions and 10 will go to the legitimate user who loaned them a majority of the USDC. Their APR is ~1.2% which is ~1/9th of what they specified.

Impact:
Legitimate users can be cheated out of interest owed

Recommended mitigation:
The relative size of the provisions should be enforced so that one is not much larger than any other one
Vulnerability: minOfferCost can be bypassed in certain scenarios
Severity: medium

Description:
minOfferCost is designed to prevent spam loan requests that can cause the lender to have positions that cost more gas to claim than interest. Due to how interest is calculated right after this minimum is passed it is still possible for the lender to receive less than the minimum.\\nClaimFacet.sol#L94-L106\\n```\\nfunction sendInterests(Loan storage loan, Provision storage provision) internal returns (uint256 sent) {\\n    uint256 interests = loan.payment.paid - loan.lent;\\n    if (interests == loan.payment.minInterestsToRepay) {\\n        // this is the case if the loan is repaid shortly after issuance\\n        // each lender gets its minimal interest, as an anti ddos measure to spam offer\\n        sent = provision.amount + (interests / loan.nbOfPositions);\\n    } else {\\n        /* provision.amount / lent = share of the interests belonging to the lender. The parenthesis make the\\n        calculus in the order that maximizes precison */\\n        sent = provision.amount + (interests * (provision.amount)) / loan.lent; <- audit-issue minimal interest isn't guaranteed\\n    }\\n    loan.assetLent.checkedTransfer(msg.sender, sent);\\n}\\n```\\n\\nWhen a loan has generated more than the minimum interest amount the method for calculating the interest paid is different and depending on the size of the provisions it may lead to provisions that are under the guaranteed minimum.\\nExample: Assume the minimum interest is 1e18. A loan is filled with 2 provisions. The first provision is 25% and the second is 75%. Since there are two loans the total minimum interest for the loan is 2e18. After some time the paid interest reaches 2.001e18 and the loan is paid back. Since it is above the minimum interest rate, it is paid out proportionally. This gives 0.5e18 to the first provision and 1.5e18 to the second provision. This violates the minimum guaranteed interest amount.

Impact:
Minimum interest guarantee can be violated

Recommended mitigation:
Minimum interest should be set based on the percentage of the lowest provision and provision shouldn't be allowed to be lower than some amount. Since this problem occurs when the percentage is less than 1/n (where n is the number of provisions), any single provision should be allowed to be lower than 1/(2n).
Vulnerability: Keeper can make deposits/orders/withdrawals fail and receive fee+rewards
Severity: medium

Description:
Malicious keeper can make execution of deposits/orders/withdrawals fail by providing limited gas to the execution.\\nIf enough gas is sent for the cancellation to succeed but for the execution to fail the keeper is able to receive the execution fee + incentive rewards and cancel all deposits/orders/withdrawals.\\n```\\nfunction executeDeposit(\\n        bytes32 key,\\n        OracleUtils.SetPricesParams calldata oracleParams\\n    ) external\\n        globalNonReentrant\\n        onlyOrderKeeper\\n        withOraclePrices(oracle, dataStore, eventEmitter, oracleParams)\\n    {\\n        uint256 startingGas = gasleft();\\n\\n        try this._executeDeposit(\\n            key,\\n            oracleParams,\\n            msg.sender,\\n            startingGas\\n        ) {\\n        } catch (bytes memory reasonBytes) {\\n            _handleDepositError(\\n                key,\\n                startingGas,\\n                reasonBytes\\n            );\\n        }\\n    }\\n```\\n\\nFor the attack to succeed, the keeper needs to make `this._executeDeposit` revert. Due to the 64/63 rule the attack will succeed if both of the following conditions meet:\\n63/64 of the supplied gas will cause an out of gas in the `try` statement\\n1/64 of the supplied gas is enough to execute the `catch` statement.\\nConsidering `2000000` is the max callback limit and native token transfer gas limit is large enough to support contracts the above conditions can be met.

Impact:
Keeper can remove all deposits/withdrawals/orders from the protocol.\\nEssentially stealing all execution fees paid\\nKeeper can create deposits and by leveraging the bug can cancel them when executing while receiving rewards.\\nVaults will be drained

Recommended mitigation:
Add a buffer of gas that needs to be supplied to the execute function to make sure the `try` statement will not revert because of out of gas.
Vulnerability: Incorrect function call leads to stale borrowing fees
Severity: high

Description:
Due to an incorrect function call while getting the total borrow fees, the returned fees will be an inaccurate and stale amount. Which will have an impact on liquidity providers\\nAs said the function getTotalBorrowingFees:\\n```\\nfunction getTotalBorrowingFees(DataStore dataStore, address market, address longToken, address shortToken, bool isLong) internal view returns (uint256) {\\n    uint256 openInterest = getOpenInterest(dataStore, market, longToken, shortToken, isLong);\\n    uint256 cumulativeBorrowingFactor = getCumulativeBorrowingFactor(dataStore, market, isLong);\\n    uint256 totalBorrowing = getTotalBorrowing(dataStore, market, isLong);\\n    return openInterest * cumulativeBorrowingFactor - totalBorrowing;\\n}\\n```\\n\\ncalculates the fess by calling getCumulativeBorrowingFactor(...):\\nwhich is the wrong function to call because it returns a stale borrowing factor. To get the actual borrowing factor and calculate correctly the borrowing fees, GMX should call the `getNextCumulativeBorrowingFactor` function:\\nWhich makes the right calculation, taking into account the stale fees also:\\n```\\n    uint256 durationInSeconds = getSecondsSinceCumulativeBorrowingFactorUpdated(dataStore, market.marketToken, isLong);\\n    uint256 borrowingFactorPerSecond = getBorrowingFactorPerSecond(\\n        dataStore,\\n        market,\\n        prices,\\n        isLong\\n    );\\n\\n    uint256 cumulativeBorrowingFactor = getCumulativeBorrowingFactor(dataStore, market.marketToken, isLong);\\n\\n    uint256 delta = durationInSeconds * borrowingFactorPerSecond;\\n    uint256 nextCumulativeBorrowingFactor = cumulativeBorrowingFactor + delta;\\n    return (nextCumulativeBorrowingFactor, delta);\\n```\\n

Impact:
Ass fee calculation will not be accurate, liquidity providers will be have a less-worth token because pending fees are not accounted in the pool's value

Recommended mitigation:
In order to mitigate the issue, call the function `getNextCumulativeBorrowingFactor` instead of the function `getCumulativeBorrowingFactor()` for a correct accounting and not getting stale fees