msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: In case the portfolio makes a loss, the total reserves and reserve ratio will be inflated.
Severity: medium

Description:
The pool balance is transferred to the portfolio for investment, for example sending USDT to Curve/Aave/Balancer etc. to generate yield. However, there are risks associated with those protocols such as smart contract risks. In case a loss happens, it will not be reflected in the pool balance and the total reserve and reserve ratio will be inflated.\\nThe assets in the pool can be sent to the portfolio account to invest and earn yield. The amount of assets in the insurance pool and Unitas pool is tracked by the `_balance` variable. This amount is used to calculate the total reserve and total collateral, which then are used to calculate the reserve ratio.\\n```\\n        uint256 tokenReserve = _getBalance(token);\\n        uint256 tokenCollateral = IInsurancePool(insurancePool).getCollateral(token);\\n```\\n\\nWhen there is a loss to the portfolio, there is no way to write down the `_balance` variable. This leads to an overstatement of the total reserve and reserve ratio.

Impact:
Overstatement of the total reserve and reserve ratio can increase the risk for the protocol because of undercollateralization of assets.

Recommended mitigation:
Add function to allow admin to write off the `_balance` in case of investment lost. Example:\\n```\\nfunction writeOff(address token, uint256 amount) external onlyGuardian {\\n\\n    uint256 currentBalance = IERC20(token).balanceOf(address(this));\\n\\n    // Require that the amount to write off is less than or equal to the current balance\\n    require(amount <= currentBalance, "Amount exceeds balance");\\n    _balance[token] -= amount;\\n\\n    emit WriteOff(token, amount);\\n}\\n```\\n
Vulnerability: If any stable depegs, oracle will fail, disabling swaps
Severity: medium

Description:
If any stable depegs, oracle will fail, disabling swaps\\nWhen swapping, the price of the asset/stable is fetched from OracleX. After fetching the price, the deviation is checked in the `_checkPrice` function.\\nIf the price of an asset/stable depegs, the following require will fail:\\n```\\n _require(minPrice <= price && price <= maxPrice, Errors.PRICE_INVALID);\\n```\\n\\nDue to the fail in the deviation, any swapping activity will be disabled by default and transactions will not go through

Impact:
Core functionality of the protocol will fail to work if any token they fetch depegs and its price goes outside the bounds.

Recommended mitigation:
Use a secondary oracle when the first one fails and wrap the code in a try catch and store the last fetched price in a variable
Vulnerability: supplyNativeToken will strand ETH in contract if called after ACTION_DEFER_LIQUIDITY_CHECK
Severity: high

Description:
supplyNativeToken deposits msg.value to the WETH contract. This is very problematic if it is called after ACTION_DEFER_LIQUIDITY_CHECK. Since onDeferredLiqudityCheck creates a new context msg.value will be 0 and no ETH will actually be deposited for the user, causing funds to be stranded in the contract.\\nTxBuilderExtension.sol#L252-L256\\n```\\nfunction supplyNativeToken(address user) internal nonReentrant {\\n    WethInterface(weth).deposit{value: msg.value}();\\n    IERC20(weth).safeIncreaseAllowance(address(ironBank), msg.value);\\n    ironBank.supply(address(this), user, weth, msg.value);\\n}\\n```\\n\\nsupplyNativeToken uses the context sensitive msg.value to determine how much ETH to send to convert to WETH. After ACTION_DEFER_LIQUIDITY_CHECK is called, it enters a new context in which msg.value is always 0. We can outline the execution path to see where this happens:\\n`execute > executeInteral > deferLiquidityCheck > ironBank.deferLiquidityCheck > onDeferredLiquidityCheck (new context) > executeInternal > supplyNativeToken`\\nWhen IronBank makes it's callback to TxBuilderExtension it creates a new context. Since the ETH is not sent along to this new context, msg.value will always be 0. Which will result in no ETH being deposited and the sent ether is left in the contract.\\nAlthough these funds can be recovered by the admin, it may can easily cause the user to be unfairly liquidated in the meantime since a (potentially significant) portion of their collateral hasn't been deposited. Additionally in conjunction with my other submission on ownable not being initialized correctly, the funds would be completely unrecoverable due to lack of owner.

Impact:
User funds are indefinitely (potentially permanently) stuck in the contract. Users may be unfairly liquidated due to their collateral not depositing.

Recommended mitigation:
msg.value should be cached at the beginning of the function to preserve it across contexts
Vulnerability: PriceOracle.getPrice doesn't check for stale price
Severity: medium

Description:
PriceOracle.getPrice doesn't check for stale price. As result protocol can make decisions based on not up to date prices, which can cause loses.\\n```\\n    function getPriceFromChainlink(address base, address quote) internal view returns (uint256) {\\n        (, int256 price,,,) = registry.latestRoundData(base, quote);\\n        require(price > 0, "invalid price");\\n\\n        // Extend the decimals to 1e18.\\n        return uint256(price) * 10 ** (18 - uint256(registry.decimals(base, quote)));\\n    }\\n```\\n\\nThis function doesn't check that prices are up to date. Because of that it's possible that price is not outdated which can cause financial loses for protocol.

Impact:
Protocol can face bad debt.

Recommended mitigation:
You need to check that price is not outdated by checking round timestamp.
Vulnerability: PriceOracle will use the wrong price if the Chainlink registry returns price outside min/max range
Severity: medium

Description:
Chainlink aggregators have a built in circuit breaker if the price of an asset goes outside of a predetermined price band. The result is that if an asset experiences a huge drop in value (i.e. LUNA crash) the price of the oracle will continue to return the minPrice instead of the actual price of the asset. This would allow user to continue borrowing with the asset but at the wrong price. This is exactly what happened to Venus on BSC when LUNA imploded.\\nNote there is only a check for `price` to be non-negative, and not within an acceptable range.\\n```\\nfunction getPriceFromChainlink(address base, address quote) internal view returns (uint256) {\\n    (, int256 price,,,) = registry.latestRoundData(base, quote);\\n    require(price > 0, "invalid price");\\n\\n    // Extend the decimals to 1e18.\\n    return uint256(price) * 10 ** (18 - uint256(registry.decimals(base, quote)));\\n}\\n```\\n\\nA similar issue is seen here.

Impact:
The wrong price may be returned in the event of a market crash. An adversary will then be able to borrow against the wrong price and incur bad debt to the protocol.

Recommended mitigation:
Implement the proper check for each asset. It must revert in the case of bad price.\\n```\\nfunction getPriceFromChainlink(address base, address quote) internal view returns (uint256) {\\n    (, int256 price,,,) = registry.latestRoundData(base, quote);\\n    require(price >= minPrice && price <= maxPrice, "invalid price"); // @audit use the proper minPrice and maxPrice for each asset\\n\\n    // Extend the decimals to 1e18.\\n    return uint256(price) * 10 ** (18 - uint256(registry.decimals(base, quote)));\\n}\\n```\\n
Vulnerability: Wrong Price will be Returned When Asset is PToken for WstETH
Severity: medium

Description:
Iron Bank allows a PToken market to be created for an underlying asset in addition to a lending market. PTokens can be counted as user collaterals and their price is fetched based on their underlying tokens. However, wrong price will return when PToken's underlying asset is WstETH.\\nRetrieving price for WstETH is a 2 step process. WstETH needs to be converted to stETH first, then converted to ETH/USD. This is properly implemented when the market is WstETH through checking `if (asset==wsteth)`. But when PToken market is created for WstETH, this check will by bypassed because PToken contract address will be different from wsteth address.\\nPToken market price is set through `_setAggregators()` in PriceOracle.sol where base and quote token address are set and tested before writing into `aggregators` array. And note that quote token address can either be ETH or USD. When asset price is accessed through `getPrice()`, if the input asset is not `wsteth` address, `aggregators` is directly pulled to get chainlink price denominated in ETH or USD.\\n```\\n//PriceOracle.sol\\n//_setAggregators()\\n                require(\\n                    aggrs[i].quote == Denominations.ETH ||\\n                        aggrs[i].quote == Denominations.USD,\\n                    "unsupported quote"\\n                );\\n```\\n\\n```\\n//PriceOracle.sol\\n    function getPrice(address asset) external view returns (uint256) {\\n        if (asset == wsteth) {\\n            uint256 stEthPrice = getPriceFromChainlink(\\n                steth,\\n                Denominations.USD\\n            );\\n            uint256 stEthPerToken = WstEthInterface(wsteth).stEthPerToken();\\n            uint256 wstEthPrice = (stEthPrice * stEthPerToken) / 1e18;\\n            return getNormalizedPrice(wstEthPrice, asset);\\n        }\\n        AggregatorInfo memory aggregatorInfo = aggregators[asset];\\n        uint256 price = getPriceFromChainlink(\\n            aggregatorInfo.base,\\n            aggregatorInfo.quote\\n        );\\n       // rest of code\\n```\\n\\nThis creates a problem for PToken for WstETH, because `if (asset==wsteth)` will be bypassed and chainlink aggregator price will be returned. And chainlink doesn't have a direct price quote of WstETH/ETH or WstETH/USD, only WstETH/stETH or stETH/USD. This means most likely aggregator price for stETH/USD will be returned as price for WstETH.\\nSince stETH is a rebasing token, and WstETH:stETH is not 1 to 1, this will create a wrong valuation for users holding PToken for WstETH as collaterals.

Impact:
Since users holding PToken for WstETH will have wrong valuation, this potentially creates opportunities for malicious over-borrowing or unfair liquidations, putting the protocol at risk.

Recommended mitigation:
In `getPrice()`, consider adding another check whether the asset is PToken and its underlying asset is WstETH. If true, use the same bypass for pricing.
Vulnerability: Limit swap orders can be used to get a free look into the future
Severity: high

Description:
Users can cancel their limit swap orders to get a free look into prices in future blocks\\nThis is a part of the same issue that was described in the last contest. The sponsor fixed the bug for `LimitDecrease` and `StopLossDecrease`, but not for `LimitSwap`.\\nAny swap limit order submitted in block range N can't be executed until block range N+2, because the block range is forced to be after the submitted block range, and keepers can't execute until the price has been archived, which necessarily won't be until after block range N+1. Consider what happens when half of the oracle's block ranges are off from the other half, e.g.:\\n```\\n    1 2 3 4 5 6 7 8 9 < block number\\nO1: A B B B B C C C D\\nA A B B B B C C C\\n^^ grouped oracle block ranges\\n```\\n\\nAt block 1, oracles in both groups (O1 and O2) are in the same block range A, and someone submits a large swap limit order (N). At block 6, oracles in O1 are in N+2, but oracles in O2 are still in N+1. This means that the swap limit order will execute at the median price of block 5 (since the earliest group to have archive prices at block 6 for N+1 will be O1) and market swap order submitted at block 6 in the other direction will execute at the median price of block 6 since O2 will be the first group to archive a price range that will contain block 6. By the end of block 5, the price for O1 is known, and the price that O2 will get at block 6 can be predicted with high probability (e.g. if the price has just gapped a few cents), so a trader will know whether the two orders will create a profit or not. If a profit is expected, they'll submit the market order at block 6. If a loss is expected, they'll cancel the swap limit order from block 1, and only have to cover gas fees.\\nEssentially the logic is that limit swap orders will use earlier prices, and market orders (with swaps) will use later prices, and since oracle block ranges aren't fixed, an attacker is able to know both prices before having their orders executed, and use large order sizes to capitalize on small price differences.

Impact:
There is a lot of work involved in calculating statistics about block ranges for oracles and their processing time/queues, and ensuring one gets the prices essentially when the keepers do, but this is likely less work than co-located high frequency traders in traditional finance have to do, and if there's a risk free profit to be made, they'll put in the work to do it every single time, at the expense of all other traders.

Recommended mitigation:
Issue Limit swap orders can be used to get a free look into the future\\nAll orders should follow the same block range rules
Vulnerability: MarketUtils.getFundingAmountPerSizeDelta() has a rounding logical error.
Severity: medium

Description:
`MarketUtils.getFundingAmountPerSizeDelta()` has a rounding logical error. The main problem is the divisor always use a roundupDivision regardless of the input `roundUp` rounding mode. Actually the correct use should be: the divisor should use the opposite of `roundup` to achieve the same logic of rounding.\\n`MarketUtils.getFundingAmountPerSizeDelta()` is used to calculate the `FundingAmountPerSizeDelta` with a roundup input mode parameter.\\nThis function is used for example by the IncreaseLimit order via flow `OrderHandler.executeOrder() -> _executeOrder() -> OrderUtils.executeOrder() -> processOrder() -> IncreaseOrderUtils.processOrder() -> IncreasePositionUtils.increasePosition() -> PositionUtils.updateFundingAndBorrowingState() -> MarketUtils.updateFundingAmoutPerSize() -> getFundingAmountPerSizeDelta()`.\\nHowever, the main problem is the divisor always use a roundupDivision regardless of the input `roundUp` rounding mode. Actually the correct use should be: the divisor should use the opposite of `roundup` to achieve the same logic of rounding.\\nMy POC code confirms my finding: given fundingAmount = 2e15, openInterest = 1e15+1, and roundup = true, the correct answer should be: 1999999999999998000000000000001999999999999999. However, the implementation returns the wrong solution of : 1000000000000000000000000000000000000000000000. The reason is that the divisor uses a roundup and gets a divisor of 2, as a result, the final result is actually rounded down rather than rounding up!\\n```\\nfunction testGetFundingAmountPerSizeDelta() public{\\n        uint result = MarketUtils.getFundingAmountPerSizeDelta(2e15, 1e15+1, true);\\n        console2.log("result: %d", result);\\n        uint256 correctResult = 2e15 * 1e15 * 1e30 + 1e15; // this is a real round up\\n        correctResult = correctResult/(1e15+1);\\n        console2.log("correctResult: %d", correctResult);\\n        assertTrue(result  == 1e15 * 1e30);\\n    }\\n```\\n

Impact:
MarketUtils.getFundingAmountPerSizeDelta() has a rounding logical error, sometimes, when roundup = true, the result, instead of rounding up, it becomes a rounding down!

Recommended mitigation:
Change the rounding mode of the divisor to the opposite of the input `roundup` mode. Or, the solution can be just as follows:\\n```\\nfunction getFundingAmountPerSizeDelta(\\n        uint256 fundingAmount,\\n        uint256 openInterest,\\n        bool roundUp\\n    ) internal pure returns (uint256) {\\n        if (fundingAmount == 0 || openInterest == 0) { return 0; }\\n     \\n       \\n\\n        // how many units in openInterest\\n// Remove the line below\\n        uint256 divisor = Calc.roundUpDivision(openInterest, Precision.FLOAT_PRECISION_SQRT);\\n\\n// Remove the line below\\n        return Precision.toFactor(fundingAmount, divisor, roundUp);\\n// Add the line below\\n       return Precision.toFactor(fundingAmount*Precision.FLOAT_PRECISION_SQRT, openInterest, roundUp\\n    }\\n```\\n
Vulnerability: short side of getReservedUsd does not work for market that has the same collateral token
Severity: medium

Description:
short side of getReservedUsd does not work for market that has the same collateral token\\nConsider the case of ETH / USD market with both long and short collateral token as ETH.\\nthe available amount to be reserved (ETH) would CHANGE with the price of ETH.\\n```\\n    function getReservedUsd(\\n        DataStore dataStore,\\n        Market.Props memory market,\\n        MarketPrices memory prices,\\n        bool isLong\\n    ) internal view returns (uint256) {\\n        uint256 reservedUsd;\\n        if (isLong) {\\n            // for longs calculate the reserved USD based on the open interest and current indexTokenPrice\\n            // this works well for e.g. an ETH / USD market with long collateral token as WETH\\n            // the available amount to be reserved would scale with the price of ETH\\n            // this also works for e.g. a SOL / USD market with long collateral token as WETH\\n            // if the price of SOL increases more than the price of ETH, additional amounts would be\\n            // automatically reserved\\n            uint256 openInterestInTokens = getOpenInterestInTokens(dataStore, market, isLong);\\n            reservedUsd = openInterestInTokens * prices.indexTokenPrice.max;\\n        } else {\\n            // for shorts use the open interest as the reserved USD value\\n            // this works well for e.g. an ETH / USD market with short collateral token as USDC\\n            // the available amount to be reserved would not change with the price of ETH\\n            reservedUsd = getOpenInterest(dataStore, market, isLong);\\n        }\\n\\n        return reservedUsd;\\n    }\\n```\\n

Impact:
reservedUsd does not work when long and short collateral tokens are the same.

Recommended mitigation:
Consider apply both long and short calculations of reserveUsd with relation to the indexTokenPrice.
Vulnerability: An Oracle Signer can never be removed even if he becomes malicious
Severity: medium

Description:
The call flow of removeOracleSIgner incorrectly compares the hash of ("removeOracleSigner", account) with the hash of ("addOracleSigner", account) for validating that an action is actually initiated. This validation always fails because the hashes can never match.\\nThe process of removing oracle signers is 2 stage. First function `signalRemoveOracleSigner` is called by the TimelockAdmin which stores a time-delayed timestamp corresponding to the keccak256 hash of ("removeOracleSigner", account) - a bytes32 value called actionKey in the pendingActions mapping.\\nThen the Admin needs to call function `removeOracleSignerAfterSignal` but this function calls `_addOracleSignerActionKey` instead of `_removeOracleSignerActionKey` for calculating the bytes32 action key value. Now the actionKey is calculated as keccak256 hash of ("addOracleSigner", account) and this hash is used for checking if this action is actually pending by ensuring its timestamp is not zero inside the `_validateAction` function called via `_validateAndClearAction` function at Line 122. The hash of ("removeOracleSigner", account) can never match hash of ("addOracleSigner", account) and thus this validation will fail.\\n```\\n function removeOracleSignerAfterSignal(address account) external onlyTimelockAdmin nonReentrant {\\n        bytes32 actionKey = _addOracleSignerActionKey(account);\\n        _validateAndClearAction(actionKey, "removeOracleSigner");\\n\\n        oracleStore.removeSigner(account);\\n\\n        EventUtils.EventLogData memory eventData;\\n        eventData.addressItems.initItems(1);\\n        eventData.addressItems.setItem(0, "account", account);\\n        eventEmitter.emitEventLog1(\\n            "RemoveOracleSigner",\\n            actionKey,\\n            eventData\\n        );\\n    }\\n```\\n

Impact:
The process of removing an Oracle Signer will always revert and this breaks an important safety measure if a certain oracle signer becomes malicious the TimelockAdmin could do nothing(these functions are meant for this). Hence, important functionality is permanently broken.

Recommended mitigation:
Replace the call to _addOracleSignerActionKey at Line 118 by call to _removeOracleSignerActionKey
Vulnerability: Stale inflationMultiplier in L1ECOBridge
Severity: high

Description:
`L1ECOBridge::inflationMultiplier` is updated through `L1ECOBridge::rebase` on Ethereum, and it is used in `_initiateERC20Deposit` and `finalizeERC20Withdrawal` to convert between token amount and `_gonsAmount`. However, if `rebase` is not called in a timely manner, the `inflationMultiplier` value can be stale and inconsistent with the value of L1 ECO token during transfer, leading to incorrect token amounts in deposit and withdraw.\\nThe `inflationMultiplier` value is updated in `rebase` with an independent transaction on L1 as shown below:\\n```\\n    function rebase(uint32 _l2Gas) external {\\n        inflationMultiplier = IECO(l1Eco).getPastLinearInflation(block.number);\\n```\\n\\nHowever, in both `_initiateERC20Deposit`, `transferFrom` is called before the `inflationMultiplier` is used, which can lead to inconsistent results if `rebase` is not called on time for the `inflationMultiplier` to be updated. The code snippet for `_initiateERC20Deposit` is as follows:\\n```\\n        IECO(_l1Token).transferFrom(_from, address(this), _amount);\\n        _amount = _amount * inflationMultiplier;\\n```\\n\\n`finalizeERC20Withdrawal` has the same problem.\\n```\\n        uint256 _amount = _gonsAmount / inflationMultiplier;\\n        bytes memory _ecoTransferMessage = abi.encodeWithSelector(IERC20.transfer.selector,_to,_amount);\\n```\\n\\nThe same problem does not exist in L2ECOBridge. Because the L2 rebase function updates inflationMultiplier and rebase l2Eco token synchronously.\\n```\\n    function rebase(uint256 _inflationMultiplier)\\n        external\\n        virtual\\n        onlyFromCrossDomainAccount(l1TokenBridge)\\n        validRebaseMultiplier(_inflationMultiplier)\\n    {\\n        inflationMultiplier = _inflationMultiplier;\\n        l2Eco.rebase(_inflationMultiplier);\\n        emit RebaseInitiated(_inflationMultiplier);\\n    }\\n```\\n

Impact:
The attacker can steal tokens with this.\\nHe can deposit to L1 bridge when he observes a stale larger value and he will receive more tokens on L2.

Recommended mitigation:
Calling `IECO(l1Eco).getPastLinearInflation(block.number)` instead of using `inflationMultiplier`.