msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: RewardDistributor requirement prevents transition of Owner role to smart contract
Severity: medium

Description:
From dForce, we learned that the eventual plan for the system Owner role is to use a smart contract (a multisig or DAO). However, a requirement in `RewardDistributor` would prevent the `onlyOwner` method `_setDistributionFactors` from working in this case.\\n`_setDistributionFactors` calls `updateDistributionSpeed`, which requires that the caller is an EOA:\\n```\\n/\\*\\*\\n \\* @notice Update each iToken's distribution speed according to current global speed\\n \\* @dev Only EOA can call this function\\n \\*/\\nfunction updateDistributionSpeed() public override {\\n    require(msg.sender == tx.origin, "only EOA can update speeds");\\n    require(!paused, "Can not update speeds when paused");\\n\\n    // Do the actual update\\n    \\_updateDistributionSpeed();\\n}\\n```\\n\\nIn the event the Owner role is a smart contract, this statement would necessitate a complicated upgrade to restore full functionality.

Impact:

Recommended mitigation:
Rather than invoking `updateDistributionSpeed`, have `_setDistributionFactors` directly call the internal helper `_updateDistributionSpeed`, which does not require the caller is an EOA.
Vulnerability: MSDController._withdrawReserves does not update interest before withdrawal
Severity: medium

Description:
`MSDController._withdrawReserves` allows the Owner to mint the difference between an MSD asset's accumulated debt and earnings:\\n```\\nfunction \\_withdrawReserves(address \\_token, uint256 \\_amount)\\n    external\\n    onlyOwner\\n    onlyMSD(\\_token)\\n{\\n    (uint256 \\_equity, ) = calcEquity(\\_token);\\n\\n    require(\\_equity >= \\_amount, "Token do not have enough reserve");\\n\\n    // Increase the token debt\\n    msdTokenData[\\_token].debt = msdTokenData[\\_token].debt.add(\\_amount);\\n\\n    // Directly mint the token to owner\\n    MSD(\\_token).mint(owner, \\_amount);\\n```\\n\\nDebt and earnings are updated each time the asset's `iMSD` and `MSDS` contracts are used for the first time in a given block. Because `_withdrawReserves` does not force an update to these values, it is possible for the withdrawal amount to be calculated using stale values.

Impact:

Recommended mitigation:
Ensure `_withdrawReserves` invokes `iMSD.updateInterest()` and `MSDS.updateInterest()`.
Vulnerability: iETH.receive() does not support contracts executing during their constructor
Severity: low

Description:
`iETH.receive()` requires that the caller is a contract:\\n```\\n/\\*\\*\\n \\* @notice receive ETH, used for flashloan repay.\\n \\*/\\nreceive() external payable {\\n    require(\\n        msg.sender.isContract(),\\n        "receive: Only can call from a contract!"\\n    );\\n}\\n```\\n\\nThis method uses the `extcodesize` of an account to check that the account belongs to a contract. However, contracts currently executing their constructor will have an `extcodesize` of 0, and will not be able to use this method.\\nThis is unlikely to cause significant issues, but dForce may want to consider supporting this edge case.

Impact:

Recommended mitigation:
Use `msg.sender != tx.origin` as a more reliable method to detect use by a contract.
Vulnerability: Token approvals can be stolen in DAOfiV1Router01.addLiquidity()
Severity: high

Description:
`DAOfiV1Router01.addLiquidity()` creates the desired pair contract if it does not already exist, then transfers tokens into the pair and calls `DAOfiV1Pair.deposit()`. There is no validation of the address to transfer tokens from, so an attacker could pass in any address with nonzero token approvals to `DAOfiV1Router`. This could be used to add liquidity to a pair contract for which the attacker is the `pairOwner`, allowing the stolen funds to be retrieved using `DAOfiV1Pair.withdraw()`.\\n```\\nfunction addLiquidity(\\n    LiquidityParams calldata lp,\\n    uint deadline\\n) external override ensure(deadline) returns (uint256 amountBase) {\\n    if (IDAOfiV1Factory(factory).getPair(\\n        lp.tokenBase,\\n        lp.tokenQuote,\\n        lp.slopeNumerator,\\n        lp.n,\\n        lp.fee\\n    ) == address(0)) {\\n        IDAOfiV1Factory(factory).createPair(\\n            address(this),\\n            lp.tokenBase,\\n            lp.tokenQuote,\\n            msg.sender,\\n            lp.slopeNumerator,\\n            lp.n,\\n            lp.fee\\n        );\\n    }\\n    address pair = DAOfiV1Library.pairFor(\\n        factory, lp.tokenBase, lp.tokenQuote, lp.slopeNumerator, lp.n, lp.fee\\n    );\\n\\n    TransferHelper.safeTransferFrom(lp.tokenBase, lp.sender, pair, lp.amountBase);\\n    TransferHelper.safeTransferFrom(lp.tokenQuote, lp.sender, pair, lp.amountQuote);\\n    amountBase = IDAOfiV1Pair(pair).deposit(lp.to);\\n}\\n```\\n

Impact:

Recommended mitigation:
Transfer tokens from `msg.sender` instead of `lp.sender`.
Vulnerability: The deposit of a new pair can be stolen
Severity: high

Description:
To create a new pair, a user is expected to call the same `addLiquidity()` (or the addLiquidityETH()) function of the router contract seen above:\\n```\\nfunction addLiquidity(\\n    LiquidityParams calldata lp,\\n    uint deadline\\n) external override ensure(deadline) returns (uint256 amountBase) {\\n    if (IDAOfiV1Factory(factory).getPair(\\n        lp.tokenBase,\\n        lp.tokenQuote,\\n        lp.slopeNumerator,\\n        lp.n,\\n        lp.fee\\n    ) == address(0)) {\\n        IDAOfiV1Factory(factory).createPair(\\n            address(this),\\n            lp.tokenBase,\\n            lp.tokenQuote,\\n            msg.sender,\\n            lp.slopeNumerator,\\n            lp.n,\\n            lp.fee\\n        );\\n    }\\n    address pair = DAOfiV1Library.pairFor(\\n        factory, lp.tokenBase, lp.tokenQuote, lp.slopeNumerator, lp.n, lp.fee\\n    );\\n\\n    TransferHelper.safeTransferFrom(lp.tokenBase, lp.sender, pair, lp.amountBase);\\n    TransferHelper.safeTransferFrom(lp.tokenQuote, lp.sender, pair, lp.amountQuote);\\n    amountBase = IDAOfiV1Pair(pair).deposit(lp.to);\\n}\\n```\\n\\nThis function checks if the pair already exists and creates a new one if it does not. After that, the first and only deposit is made to that pair.\\nThe attacker can front-run that call and create a pair with the same parameters (thus, with the same address) by calling the `createPair` function of the `DAOfiV1Factory` contract. By calling that function directly, the attacker does not have to make the deposit when creating a new pair. The initial user will make this deposit, whose funds can now be withdrawn by the attacker.

Impact:

Recommended mitigation:
There are a few factors/bugs that allowed this attack. All or some of them should be fixed:\\nThe `createPair` function of the `DAOfiV1Factory` contract can be called directly by anyone without depositing with any `router` address as the parameter. The solution could be to allow only the `router` to create a pair.\\nThe `addLiquidity` function checks that the pair does not exist yet. If the pair exists already, a deposit should only be made by the owner of the pair. But in general, a new pair shouldn't be deployed without depositing in the same transaction.\\nThe pair's address does not depend on the owner/creator. It might make sense to add that information to the salt.
Vulnerability: Incorrect token decimal conversions can lead to loss of funds
Severity: high

Description:
The `_convert()` function in `DAOfiV1Pair` is used to accommodate tokens with varying `decimals()` values. There are three cases in which it implicitly returns 0 for any `amount`, the most notable of which is when `token.decimals() == resolution`.\\nAs a result of this, `getQuoteOut()` reverts any time either `baseToken` or `quoteToken` have `decimals == INTERNAL_DECIMALS` (currently hardcoded to 8).\\n`getBaseOut()` also reverts in most cases when either `baseToken` or `quoteToken` have `decimals() == INTERNAL_DECIMALS`. The exception is when `getBaseOut()` is called while `supply` is 0, as is the case in `deposit()`. This causes `getBaseOut()` to succeed, returning an incorrect value.\\nThe result of this is that no swaps can be performed in one of these pools, and the `deposit()` function will return an incorrect `amountBaseOut` of `baseToken` to the depositor, the balance of which can then be withdrawn by the `pairOwner`.\\n```\\nfunction \\_convert(address token, uint256 amount, uint8 resolution, bool to) private view returns (uint256 converted) {\\n    uint8 decimals = IERC20(token).decimals();\\n    uint256 diff = 0;\\n    uint256 factor = 0;\\n    converted = 0;\\n    if (decimals > resolution) {\\n        diff = uint256(decimals.sub(resolution));\\n        factor = 10 \\*\\* diff;\\n        if (to && amount >= factor) {\\n            converted = amount.div(factor);\\n        } else if (!to) {\\n            converted = amount.mul(factor);\\n        }\\n    } else if (decimals < resolution) {\\n        diff = uint256(resolution.sub(decimals));\\n        factor = 10 \\*\\* diff;\\n        if (to) {\\n            converted = amount.mul(factor);\\n        } else if (!to && amount >= factor) {\\n            converted = amount.div(factor);\\n        }\\n    }\\n}\\n```\\n

Impact:

Recommended mitigation:
The `_convert()` function should return `amount` when `token.decimals() == resolution`. Additionally, implicit return values should be avoided whenever possible, especially in functions that implement complex mathematical operations.\\n`BancorFormula.power(baseN, baseD, _, _)` does not support `baseN < baseD`, and checks should be added to ensure that any call to the `BancorFormula` conforms to the expected input ranges.
Vulnerability: The swapExactTokensForETH checks the wrong return value
Severity: high

Description:
The following lines are intended to check that the amount of tokens received from a swap is greater than the minimum amount expected from this swap (sp.amountOut):\\n```\\nuint amountOut = IWETH10(WETH).balanceOf(address(this));\\nrequire(\\n    IWETH10(sp.tokenOut).balanceOf(address(this)).sub(balanceBefore) >= sp.amountOut,\\n    'DAOfiV1Router: INSUFFICIENT\\_OUTPUT\\_AMOUNT'\\n);\\n```\\n\\nInstead, it calculates the difference between the initial receiver's balance and the balance of the router.

Impact:

Recommended mitigation:
Check the intended value.
Vulnerability: DAOfiV1Pair.deposit() accepts deposits of zero, blocking the pool
Severity: medium

Description:
`DAOfiV1Pair.deposit()` is used to deposit liquidity into the pool. Only a single deposit can be made, so no liquidity can ever be added to a pool where `deposited == true`. The `deposit()` function does not check for a nonzero deposit amount in either token, so a malicious user that does not hold any of the `baseToken` or `quoteToken` can lock the pool by calling `deposit()` without first transferring any funds to the pool.\\n```\\nfunction deposit(address to) external override lock returns (uint256 amountBaseOut) {\\n    require(msg.sender == router, 'DAOfiV1: FORBIDDEN\\_DEPOSIT');\\n    require(deposited == false, 'DAOfiV1: DOUBLE\\_DEPOSIT');\\n    reserveBase = IERC20(baseToken).balanceOf(address(this));\\n    reserveQuote = IERC20(quoteToken).balanceOf(address(this));\\n    // this function is locked and the contract can not reset reserves\\n    deposited = true;\\n    if (reserveQuote > 0) {\\n        // set initial supply from reserveQuote\\n        supply = amountBaseOut = getBaseOut(reserveQuote);\\n        if (amountBaseOut > 0) {\\n            \\_safeTransfer(baseToken, to, amountBaseOut);\\n            reserveBase = reserveBase.sub(amountBaseOut);\\n        }\\n    }\\n    emit Deposit(msg.sender, reserveBase, reserveQuote, amountBaseOut, to);\\n}\\n```\\n

Impact:

Recommended mitigation:
Require a minimum deposit amount in both `baseToken` and `quoteToken`, and do not rely on any assumptions about the distribution of `baseToken` as part of the security model.
Vulnerability: Restricting DAOfiV1Pair functions to calls from router makes DAOfiV1Router01 security critical
Severity: medium

Description:
The `DAOfiV1Pair` functions `deposit()`, `withdraw()`, and `swap()` are all restricted to calls from the router in order to avoid losses from user error. However, this means that any unidentified issue in the Router could render all pair contracts unusable, potentially locking the pair owner's funds.\\nAdditionally, `DAOfiV1Factory.createPair()` allows any nonzero address to be provided as the `router`, so pairs can be initialized with a malicious `router` that users would be forced to interact with to utilize the pair contract.\\n```\\nfunction deposit(address to) external override lock returns (uint256 amountBaseOut) {\\n    require(msg.sender == router, 'DAOfiV1: FORBIDDEN\\_DEPOSIT');\\n```\\n\\n```\\nfunction withdraw(address to) external override lock returns (uint256 amountBase, uint256 amountQuote) {\\n    require(msg.sender == router, 'DAOfiV1: FORBIDDEN\\_WITHDRAW');\\n```\\n\\n```\\nfunction swap(address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut, address to) external override lock {\\n    require(msg.sender == router, 'DAOfiV1: FORBIDDEN\\_SWAP');\\n```\\n

Impact:

Recommended mitigation:
Do not restrict `DAOfiV1Pair` functions to calls from `router`, but encourage users to use a trusted `router` to avoid losses from user error. If this restriction is kept, consider including the `router` address in the deployment salt for the pair or hardcoding the address of a trusted `router` in `DAOfiV1Factory` instead of taking the `router` as a parameter to `createPair()`.
Vulnerability: Pair contracts can be easily blocked
Severity: low

Description:
The parameters used to define a unique pair are the `baseToken`, `quoteToken`, `slopeNumerator`, `n`, and `fee`. There is only one accepted value for `n`, and there are eleven accepted values for `fee`. This makes the number of possible “interesting” pools for each token pair somewhat limited, and pools can be easily blocked by front-running deployments and depositing zero liquidity or immediately withdrawing deposited liquidity. Because liquidity can only be added once, these pools are permanently blocked.\\nThe existing mitigation for this issue is to create a new pool with slightly different parameters. This creates significant cost for the creator of a pair, forces them to deploy a pair with sub-optimal parameters, and could potentially block all interesting pools for a token pair.\\nThe salt used to determine unique pair contracts in DAOfiV1Factory.createPair():\\n```\\nrequire(getPair(baseToken, quoteToken, slopeNumerator, n, fee) == address(0), 'DAOfiV1: PAIR\\_EXISTS'); // single check is sufficient\\nbytes memory bytecode = type(DAOfiV1Pair).creationCode;\\nbytes32 salt = keccak256(abi.encodePacked(baseToken, quoteToken, slopeNumerator, n, fee));\\nassembly {\\n    pair := create2(0, add(bytecode, 32), mload(bytecode), salt)\\n}\\nIDAOfiV1Pair(pair).initialize(router, baseToken, quoteToken, pairOwner, slopeNumerator, n, fee);\\npairs[salt] = pair;\\n```\\n

Impact:

Recommended mitigation:
Consider adding additional parameters to the salt that defines a unique pair, such as the `pairOwner`. Modifying the parameters included in the salt can also be used to partially mitigate other security concerns raised in this report.
Vulnerability: DAOfiV1Router01.removeLiquidityETH() does not support tokens with no return value
Severity: low

Description:
While the rest of the system uses the `safeTransfer*` pattern, allowing tokens that do not return a boolean value on `transfer()` or `transferFrom()`, `DAOfiV1Router01.removeLiquidityETH()` throws and consumes all remaining gas if the base token does not return `true`.\\nNote that the deposit in this case can still be withdrawn without unwrapping the Eth using `removeLiquidity()`.\\n```\\nfunction removeLiquidityETH(\\n    LiquidityParams calldata lp,\\n    uint deadline\\n) external override ensure(deadline) returns (uint amountToken, uint amountETH) {\\n    IDAOfiV1Pair pair = IDAOfiV1Pair(DAOfiV1Library.pairFor(factory, lp.tokenBase, WETH, lp.slopeNumerator, lp.n, lp.fee));\\n    require(msg.sender == pair.pairOwner(), 'DAOfiV1Router: FORBIDDEN');\\n    (amountToken, amountETH) = pair.withdraw(address(this));\\n    assert(IERC20(lp.tokenBase).transfer(lp.to, amountToken));\\n    IWETH10(WETH).withdraw(amountETH);\\n    TransferHelper.safeTransferETH(lp.to, amountETH);\\n}\\n```\\n

Impact:

Recommended mitigation:
Be consistent with the use of `safeTransfer*`, and do not use `assert()` in cases where the condition can be false.
Vulnerability: Users can withdraw their funds immediately when they are over-leveraged
Severity: high

Description:
`Accounts.withdraw` makes two checks before processing a withdrawal.\\nFirst, the method checks that the amount requested for withdrawal is not larger than the user's balance for the asset in question:\\n```\\nfunction withdraw(address \\_accountAddr, address \\_token, uint256 \\_amount) external onlyAuthorized returns(uint256) {\\n\\n    // Check if withdraw amount is less than user's balance\\n    require(\\_amount <= getDepositBalanceCurrent(\\_token, \\_accountAddr), "Insufficient balance.");\\n    uint256 borrowLTV = globalConfig.tokenInfoRegistry().getBorrowLTV(\\_token);\\n```\\n\\nSecond, the method checks that the withdrawal will not over-leverage the user. The amount to be withdrawn is subtracted from the user's current “borrow power” at the current price. If the user's total value borrowed exceeds this new borrow power, the method fails, as the user no longer has sufficient collateral to support their borrow positions. However, this `require` is only checked if a user is not already over-leveraged:\\n```\\n// This if condition is to deal with the withdraw of collateral token in liquidation.\\n// As the amount if borrowed asset is already large than the borrow power, we don't\\n// have to check the condition here.\\nif(getBorrowETH(\\_accountAddr) <= getBorrowPower(\\_accountAddr))\\n    require(\\n        getBorrowETH(\\_accountAddr) <= getBorrowPower(\\_accountAddr).sub(\\n            \\_amount.mul(globalConfig.tokenInfoRegistry().priceFromAddress(\\_token))\\n            .mul(borrowLTV).div(Utils.getDivisor(address(globalConfig), \\_token)).div(100)\\n        ), "Insufficient collateral when withdraw.");\\n```\\n\\nIf the user has already borrowed more than their “borrow power” allows, they are allowed to withdraw regardless. This case may arise in several circumstances; the most common being price fluctuation.

Impact:

Recommended mitigation:
Disallow withdrawals if the user is already over-leveraged.\\nFrom the comment included in the code sample above, this condition is included to support the `liquidate` method, but its inclusion creates an attack vector that may allow users to withdraw when they should not be able to do so. Consider adding an additional method to support `liquidate`, so that users may not exit without repaying debts.
Vulnerability: Stale Oracle prices might affect the rates
Severity: high

Description:
It's possible that due to network congestion or other reasons, the price that the ChainLink oracle returns is old and not up to date. This is more extreme in lesser known tokens that have fewer ChainLink Price feeds to update the price frequently. The codebase as is, relies on `chainLink().getLatestAnswer()` and does not check the timestamp of the price.\\n```\\n    function priceFromAddress(address tokenAddress) public view returns(uint256) {\\n        if(Utils.\\_isETH(address(globalConfig), tokenAddress)) {\\n            return 1e18;\\n        }\\n        return uint256(globalConfig.chainLink().getLatestAnswer(tokenAddress));\\n    }\\n```\\n

Impact:

Recommended mitigation:
Do a sanity check on the price returned from the oracle. If the price is older than a threshold, revert or handle in other means.
Vulnerability: Overcomplicated unit conversions
Severity: medium

Description:
There are many instances of unit conversion in the system that are implemented in a confusing way. This could result in mistakes in the conversion and possibly failure in correct accounting. It's been seen in the ecosystem that these type of complicated unit conversions could result in calculation mistake and loss of funds.\\nHere are a few examples:\\n```\\n    function getBorrowRatePerBlock(address \\_token) public view returns(uint) {\\n        if(!globalConfig.tokenInfoRegistry().isSupportedOnCompound(\\_token))\\n        // If the token is NOT supported by the third party, borrowing rate = 3% + U \\* 15%.\\n            return getCapitalUtilizationRatio(\\_token).mul(globalConfig.rateCurveSlope()).div(INT\\_UNIT).add(globalConfig.rateCurveConstant()).div(BLOCKS\\_PER\\_YEAR);\\n\\n        // if the token is suppored in third party, borrowing rate = Compound Supply Rate \\* 0.4 + Compound Borrow Rate \\* 0.6\\n        return (compoundPool[\\_token].depositRatePerBlock).mul(globalConfig.compoundSupplyRateWeights()).\\n            add((compoundPool[\\_token].borrowRatePerBlock).mul(globalConfig.compoundBorrowRateWeights())).div(10);\\n    }\\n```\\n\\n```\\n                compoundPool[\\_token].depositRatePerBlock = cTokenExchangeRate.mul(UNIT).div(lastCTokenExchangeRate[cToken])\\n                    .sub(UNIT).div(blockNumber.sub(lastCheckpoint[\\_token]));\\n```\\n\\n```\\n        return lastDepositeRateIndex.mul(getBlockNumber().sub(lcp).mul(depositRatePerBlock).add(INT\\_UNIT)).div(INT\\_UNIT);\\n```\\n

Impact:

Recommended mitigation:
Simplify the unit conversions in the system. This can be done either by using a function wrapper for units to convert all values to the same unit before including them in any calculation or by better documenting every line of unit conversion
Vulnerability: Commented out code in the codebase
Severity: medium

Description:
There are many instances of code lines (and functions) that are commented out in the code base. Having commented out code increases the cognitive load on an already complex system. Also, it hides the important parts of the system that should get the proper attention, but that attention gets to be diluted.\\nThe main problem is that commented code adds confusion with no real benefit. Code should be code, and comments should be comments.\\nHere's a few examples of such lines of code, note that there are more.\\n```\\n    struct LiquidationVars {\\n        // address token;\\n        // uint256 tokenPrice;\\n        // uint256 coinValue;\\n        uint256 borrowerCollateralValue;\\n        // uint256 tokenAmount;\\n        // uint256 tokenDivisor;\\n        uint256 msgTotalBorrow;\\n```\\n\\n```\\n                if(token != ETH\\_ADDR) {\\n                    divisor = 10\\*\\*uint256(globalConfig.tokenInfoRegistry().getTokenDecimals(token));\\n                }\\n                // globalConfig.bank().newRateIndexCheckpoint(token);\\n                power = power.add(getDepositBalanceCurrent(token, \\_borrower)\\n```\\n\\nMany usage of `console.log()` and also the commented import on most of the contracts\\n```\\n        // require(\\n        // totalBorrow.mul(100) <= totalCollateral.mul(liquidationDiscountRatio),\\n        // "Collateral is not sufficient to be liquidated."\\n        // );\\n```\\n\\n```\\n    // function \\_isETH(address \\_token) public view returns (bool) {\\n    // return globalConfig.constants().ETH\\_ADDR() == \\_token;\\n    // }\\n\\n    // function getDivisor(address \\_token) public view returns (uint256) {\\n    // if(\\_isETH(\\_token)) return INT\\_UNIT;\\n    // return 10 \\*\\* uint256(getTokenDecimals(\\_token));\\n    // }\\n```\\n\\n```\\n        // require(\\_borrowLTV != 0, "Borrow LTV is zero");\\n        require(\\_borrowLTV < SCALE, "Borrow LTV must be less than Scale");\\n        // require(liquidationThreshold > \\_borrowLTV, "Liquidation threshold must be greater than Borrow LTV");\\n```\\n

Impact:

Recommended mitigation:
In many of the above examples, it's not clear if the commented code is for testing or obsolete code (e.g. in the last example, can _borrowLTV ==0?) . All these instances should be reviewed and the system should be fully tested for all edge cases after the code changes.