msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Simplify fee calculation in WethAdapter
Severity: low

Description:
`WethAdapter` does some arithmetic to keep track of how much ether is being provided as a fee versus as funds that should be transferred into WETH:\\n```\\n// Some aggregators require ETH fees\\nuint256 fee = msg.value;\\n\\nif (address(tokenFrom) == Constants.ETH) {\\n    // If tokenFrom is ETH, msg.value = fee + amountFrom (total fee could be 0)\\n    require(amountFrom <= fee, "MSG\\_VAL\\_INSUFFICIENT");\\n    fee -= amountFrom;\\n    // Can't deal with ETH, convert to WETH\\n    IWETH weth = getWETH();\\n    weth.deposit{value: amountFrom}();\\n    \\_approveSpender(weth, spender, amountFrom);\\n} else {\\n    // Otherwise capture tokens from sender\\n    // tokenFrom.safeTransferFrom(recipient, address(this), amountFrom);\\n    \\_approveSpender(tokenFrom, spender, amountFrom);\\n}\\n\\n// Perform the swap\\naggregator.functionCallWithValue(abi.encodePacked(method, data), fee);\\n```\\n\\nThis code can be simplified by using `address(this).balance` instead.

Impact:

Recommended mitigation:
Resolution\\nConsenSys/[email protected]93bf5c6.\\nConsider something like the following code instead:\\n```\\nif (address(tokenFrom) == Constants.ETH) {\\n    getWETH().deposit{value: amountFrom}(); // will revert if the contract has an insufficient balance\\n    \\_approveSpender(weth, spender, amountFrom);\\n} else {\\n    tokenFrom.safeTransferFrom(recipient, address(this), amountFrom);\\n    \\_approveSpender(tokenFrom, spender, amountFrom);\\n}\\n\\n// Send the remaining balance as the fee.\\naggregator.functionCallWithValue(abi.encodePacked(method, data), address(this).balance);\\n```\\n\\nAside from being a little simpler, this way of writing the code makes it obvious that the full balance is being properly consumed. Part is traded, and the rest is sent as a fee.
Vulnerability: Consider checking adapter existence in MetaSwap
Severity: low

Description:
`MetaSwap` doesn't check that an adapter exists before calling into Spender:\\n```\\nfunction swap(\\n    string calldata aggregatorId,\\n    IERC20 tokenFrom,\\n    uint256 amount,\\n    bytes calldata data\\n) external payable whenNotPaused nonReentrant {\\n    Adapter storage adapter = adapters[aggregatorId];\\n\\n    if (address(tokenFrom) != Constants.ETH) {\\n        tokenFrom.safeTransferFrom(msg.sender, address(spender), amount);\\n    }\\n\\n    spender.swap{value: msg.value}(\\n        adapter.addr,\\n```\\n\\nThen `Spender` performs the check and reverts if it receives `address(0)`.\\n```\\nfunction swap(address adapter, bytes calldata data) external payable {\\n    require(adapter != address(0), "ADAPTER\\_NOT\\_SUPPORTED");\\n```\\n\\nIt can be difficult to decide where to put a check like this, especially when the operation spans multiple contracts. Arguments can be made for either choice (or even duplicating the check), but as a general rule it's a good idea to avoid passing invalid parameters internally. Checking for adapter existence in `MetaSwap.swap()` is a natural place to do input validation, and it means `Spender` can have a simpler model where it trusts its inputs (which always come from MetaSwap).

Impact:

Recommended mitigation:
Drop the check from `Spender.swap()` and perform the check instead in `MetaSwap.swap()`.
Vulnerability: Swap fees can be bypassed using redeemMasset
Severity: high

Description:
Part of the value proposition for liquidity providers is earning fees incurred for swapping between assets. However, traders can perform fee-less swaps by providing liquidity in one bAsset, followed by calling `redeemMasset()` to convert the resulting mAssets back into a proportional amount of bAssets. Since removing liquidity via `redeemMasset()` does not incur a fee this is equivalent to doing a swap with zero fees.\\nAs a very simple example, assuming a pool with 2 bAssets (say, DAI and USDT), it would be possible to swap 10 DAI to USDT as follows:\\nAdd 20 DAI to the pool, receive 20 mUSD\\ncall redeemMasset() to redeem 10 DAI and 10 USDT\\nThe boolean argument `applyFee` is set to `false` in _redeemMasset:\\n```\\n\\_settleRedemption(\\_recipient, \\_mAssetQuantity, props.bAssets, bAssetQuantities, props.indexes, props.integrators, false);\\n```\\n

Impact:

Recommended mitigation:
Resolution\\nThis issue was reported independently via the bug bounty program and was fixed early during the audit. The fix has already been deployed on mainnet using the upgrade mechanism\\nCharge a small redemption fee in `redeemMasset()`.
Vulnerability: Users can collect interest from SavingsContract by only staking mTokens momentarily
Severity: high

Description:
The SAVE contract allows users to deposit mAssets in return for lending yield and swap fees. When depositing mAsset, users receive a “credit” tokens at the momentary credit/mAsset exchange rate which is updated at every deposit. However, the smart contract enforces a minimum timeframe of 30 minutes in which the interest rate will not be updated. A user who deposits shortly before the end of the timeframe will receive credits at the stale interest rate and can immediately trigger and update of the rate and withdraw at the updated (more favorable) rate after the 30 minutes window. As a result, it would be possible for users to benefit from interest payouts by only staking mAssets momentarily and using them for other purposes the rest of the time.\\n```\\n// 1. Only collect interest if it has been 30 mins\\nuint256 timeSinceLastCollection = now.sub(previousCollection);\\nif(timeSinceLastCollection > THIRTY\\_MINUTES) {\\n```\\n

Impact:

Recommended mitigation:
Remove the 30 minutes window such that every deposit also updates the exchange rate between credits and tokens. Note that this issue was reported independently during the bug bounty program and a fix is currently being worked on.
Vulnerability: Missing validation in Masset._redeemTo  Acknowledged
Severity: medium

Description:
In function `_redeemTo` the collateralisation ratio is not taken into account unlike in _redeemMasset:\\n```\\nuint256 colRatio = StableMath.min(props.colRatio, StableMath.getFullScale());\\n\\n// Ensure payout is related to the collateralised mAsset quantity\\nuint256 collateralisedMassetQuantity = \\_mAssetQuantity.mulTruncate(colRatio);\\n```\\n\\nIt seems like `_redeemTo` should not be executed if the collateralisation ratio is below 100%. However, the contracts (that is, `Masset` and ForgeValidator) themselves don't seem to enforce this explicitly. Instead, the governor needs to ensure that the collateralisation ratio is only set to a value below 100% when the basket is not “healthy” (for instance, if it is considered “failed”). Failing to ensure this may allow an attacker to redeem a disproportionate amount of assets. Note that the functionality for setting the collateralisation ratio is not currently implemented in the audited code.

Impact:

Recommended mitigation:
Consider enforcing the intended use of `_redeemTo` more explicitly. For instance, it might be possible to introduce additional input validation by requiring that the collateralisation ratio is not below 100%.
Vulnerability: Removing a bAsset might leave some tokens stuck in the vault  Acknowledged
Severity: low

Description:
In function `_removeBasset` there is existing validation to make sure only “empty” vaults are removed:\\n```\\nrequire(bAsset.vaultBalance == 0, "bAsset vault must be empty");\\n```\\n\\nHowever, this is not necessarily sufficient since the lending pool balance may be higher than the vault balance. The reason is that the vault balance is usually slightly out-of-date due to the 30 minutes time span between interest collections. Consider the scenario: (1) a user swaps out an asset 29 minutes after the last interest collection to reduce its vault balance from 100 USD to 0, and (2) the governor subsequently remove the asset. During those 29 minutes the asset was collecting interest (according to the lending pool the balance was higher than 100 USD at the time of the swap) that is now “stuck” in the vault.

Impact:

Recommended mitigation:
Consider adding additional input validation (for instance, by requiring that the lending pool balance to be 0) or triggering a swap directly when removing an asset from the basket.
Vulnerability: Unused parameter in BasketManager._addBasset  Won't Fix
Severity: low

Description:
It seems like the `_measurementMultiple` parameter is always `StableMath.getRatioScale()` (1e8). There is also some range validation code that seems unnecessary if the parameter is always 1e8.\\n```\\nrequire(\\_measurementMultiple >= 1e6 && \\_measurementMultiple <= 1e10, "MM out of range");\\n```\\n

Impact:

Recommended mitigation:
Consider removing the parameter and the input validation to improve the readability of the code.
Vulnerability: Assumptions are made about interest distribution  Won't Fix
Severity: low

Description:
There is a mechanism that prevents interest collection if the extrapolated APY exceeds a threshold (MAX_APY).\\n```\\nrequire(extrapolatedAPY < MAX\\_APY, "Interest protected from inflating past maxAPY");\\n```\\n\\nThe extrapolation seems to assume that the interest is payed out frequently and continuously. It seems like a less frequent payout (for instance, once a month/year) could be rejected since the extrapolation considers the interest since the last time that `collectAndDistributeInterest` was called (potentially without interest being collected).

Impact:

Recommended mitigation:
Consider revisiting or documenting this assumption. For instance, one could consider extrapolating between the current time and the last time that (non-zero) interest was actually collected.
Vulnerability: Assumptions are made about Aave and Compound integrations  Acknowledged
Severity: low

Description:
The code makes several assumptions about the Aave and Compound integrations. A malicious or malfunctioning integration (or lending pool) might violate those assumptions. This might lead to unintended behavior in the system. Below are three such assumptions:\\nfunction `checkBalance` reverts if the token hasn't been added:\\n```\\nIPlatformIntegration(\\_integration).checkBalance(\\_bAsset);\\n```\\n\\nfunction `withdraw` is trusted to not fail when it shouldn't:\\n```\\nIPlatformIntegration(\\_integrators[i]).withdraw(\\_recipient, bAsset, q, \\_bAssets[i].isTransferFeeCharged);\\n```\\n\\nthe mapping from mAssets to pTokens is fixed:\\n```\\nrequire(bAssetToPToken[\\_bAsset] == address(0), "pToken already set");\\n```\\n\\nThe first assumption could be avoided by adding a designated function to check if the token was added.\\nThe second assumption is more difficult to avoid, but should be considered when adding new integrations. The system needs to trust the lending pools to work properly; for instance, if the lending pool would blacklist the integration contract the system may behave in unintended ways.\\nThe third assumption could be avoided, but it comes at a cost.

Impact:

Recommended mitigation:
Consider revisiting or avoiding these assumptions. For any assumptions that are there by design it would be good to document them to facilitate future changes. One should also be careful to avoid coupling between external systems. For instance, if withdrawing from Aave fails this should not prevent withdrawing from Compound.
Vulnerability: Assumptions are made about bAssets  Acknowledged
Severity: low

Description:
The code makes several assumptions about the bAssets that can be used. A malicious or malfunctioning asset contract might violate those assumptions. This might lead to unintended behavior in the system. Below there are several such assumptions:\\nDecimals of a bAsset are constant where the decimals are used to derive the asset's ratio:\\n```\\nuint256 bAsset\\_decimals = CommonHelpers.getDecimals(\\_bAsset);\\n```\\n\\nDecimals must be in a range from 4 to 18:\\n```\\nrequire(decimals >= 4 && decimals <= 18, "Token must have sufficient decimal places");\\n```\\n\\nThe governor is able to foresee when transfer fees are charged (which needs to be called if anything changes); in theory, assets could be much more flexible in when transfer fees are charged (for instance, during certain periods or for certain users)\\n```\\nfunction setTransferFeesFlag(address \\_bAsset, bool \\_flag)\\n```\\n\\nIt seems like some of these assumptions could be avoided, but there might be a cost. For instance, one could retrieve the decimals directly instead of “caching” them and one could always enable the setting where transfer fees may be charged.

Impact:

Recommended mitigation:
Consider revisiting or avoiding these assumptions. For any assumptions that are there by design it would be good to document them to facilitate future changes.
Vulnerability: Unused field in ForgePropsMulti struct  Won't Fix
Severity: low

Description:
The `ForgePropsMulti` struct defines the field `isValid` which always seems to be true:\\n```\\n/\\*\\* @dev All details needed to Forge with multiple bAssets \\*/\\nstruct ForgePropsMulti {\\n    bool isValid; // Flag to signify that forge bAssets have passed validity check\\n    Basset[] bAssets;\\n    address[] integrators;\\n    uint8[] indexes;\\n}\\n```\\n\\nIf it is indeed always true, one could remove the following line:\\n```\\nif(!props.isValid) return 0;\\n```\\n

Impact:

Recommended mitigation:
If the field is indeed always true please consider removing it to simplify the code.
Vulnerability: BassetStatus enum defines multiple unused states  Won't Fix
Severity: low

Description:
The `BassetStatus` enum defines several values that do not seem to be assigned in the code:\\nDefault (different from “Normal”?)\\nBlacklisted\\nLiquidating\\nLiquidated\\nFailed\\n```\\n/\\*\\* @dev Status of the Basset - has it broken its peg? \\*/\\nenum BassetStatus {\\n    Default,\\n    Normal,\\n    BrokenBelowPeg,\\n    BrokenAbovePeg,\\n    Blacklisted,\\n    Liquidating,\\n    Liquidated,\\n    Failed\\n}\\n```\\n\\nSince some of these are used in the code there might be some dead code that can be removed as a result. For example:\\n```\\n\\_bAsset.status == BassetStatus.Liquidating ||\\n\\_bAsset.status == BassetStatus.Blacklisted\\n```\\n

Impact:

Recommended mitigation:
If those values are indeed never used please consider removing them to simplify the code.
Vulnerability: Potential gas savings by terminating early  Acknowledged
Severity: low

Description:
If a function invocation is bound to revert, one should try to revert as soon as possible to save gas. In `ForgeValidator.validateRedemption` it is possible to terminate more early:\\n```\\nif(atLeastOneBecameOverweight) return (false, "bAssets must remain below max weight", false);\\n```\\n

Impact:

Recommended mitigation:
Consider moving the require-statement a few lines up (for instance, after assigning to atLeastOneBecameOverweight).
Vulnerability: Discrepancy between code and comments
Severity: low

Description:
There is a discrepancy between the code at:\\n```\\nrequire(weightSum >= 1e18 && weightSum <= 4e18, "Basket weight must be >= 100 && <= 400%");\\n```\\n\\nAnd the comment at:\\n```\\n\\* @dev Throws if the total Basket weight does not sum to 100\\n```\\n

Impact:

Recommended mitigation:
Update the code or the comment to be consistent.
Vulnerability: Loss of the liquidity pool is not equally distributed
Severity: high

Description:
All stakeholders in the liquidity pool should be able to withdraw the same amount as they staked plus a share of fees that the converter earned during their staking period.\\n```\\n        IPoolTokensContainer(anchor).burn(\\_poolToken, msg.sender, \\_amount);\\n\\n        // calculate how much liquidity to remove\\n        // if the entire supply is liquidated, the entire staked amount should be sent, otherwise\\n        // the price is based on the ratio between the pool token supply and the staked balance\\n        uint256 reserveAmount = 0;\\n        if (\\_amount == initialPoolSupply)\\n            reserveAmount = balance;\\n        else\\n            reserveAmount = \\_amount.mul(balance).div(initialPoolSupply);\\n\\n        // sync the reserve balance / staked balance\\n        reserves[reserveToken].balance = reserves[reserveToken].balance.sub(reserveAmount);\\n        uint256 newStakedBalance = stakedBalances[reserveToken].sub(reserveAmount);\\n        stakedBalances[reserveToken] = newStakedBalance;\\n```\\n\\nThe problem is that sometimes there might not be enough funds in reserve (for example, due to this issue https://github.com/ConsenSys/bancor-audit-2020-06/issues/4). So the first ones who withdraw their stakes receive all the tokens they own. But the last stakeholders might not be able to get their funds back because the pool is empty already.\\nSo under some circumstances, there is a chance that users can lose all of their staked funds.\\nThis issue also has the opposite side: if the liquidity pool makes an extra profit, the stakers do not owe this profit and cannot withdraw it.

Impact:

Recommended mitigation:
Resolution\\nThe issue was addressed by adding a new fee mechanism called ‘adjusted fees'. This mechanism aims to decrease the deficit of the reserves over time. If there is a deficit of reserves, it is usually present on the secondary token side, because there is a strong incentive to bring the primary token to the balanced state. Roughly speaking, the idea is that if the secondary token has a deficit in reserves, there are additional fees for trading that token. These fees are not distributed across the liquidity providers like the regular fees. Instead, they are just populating the reserve, decreasing the existing deficit.\\nLoss is still not distributed across the liquidity providers, and there is a possibility that there are not enough funds for everyone to withdraw them. In the case of a run on reserves, LPs will be able to withdraw funds on a first-come-first-serve basis.\\nDistribute losses evenly across the liquidity providers.
Vulnerability: Use of external calls with a fixed amount of gas  Won't Fix
Severity: medium

Description:
The converter smart contract uses the Solidity transfer() function to transfer Ether.\\n.transfer() and .send() forward exactly 2,300 gas to the recipient. The goal of this hardcoded gas stipend was to prevent reentrancy vulnerabilities, but this only makes sense under the assumption that gas costs are constant. Recently EIP 1884 was included in the Istanbul hard fork. One of the changes included in EIP 1884 is an increase to the gas cost of the SLOAD operation, causing a contract's fallback function to cost more than 2300 gas.\\n```\\n\\_to.transfer(address(this).balance);\\n```\\n\\n```\\nif (\\_targetToken == ETH\\_RESERVE\\_ADDRESS)\\n```\\n\\n```\\nmsg.sender.transfer(reserveAmount);\\n```\\n

Impact:

Recommended mitigation:
Resolution\\nIt was decided to accept this minor risk as the usage of .call() might introduce other unexpected behavior.\\nIt's recommended to stop using .transfer() and .send() and instead use .call(). Note that .call() does nothing to mitigate reentrancy attacks, so other precautions must be taken. To prevent reentrancy attacks, it is recommended that you use the checks-effects-interactions pattern.
Vulnerability: Use of assert statement for input validation
Severity: low

Description:
Solidity assertion should only be used to assert invariants, i.e. statements that are expected to always hold if the code behaves correctly. Note that all available gas is consumed when an assert-style exception occurs.\\nIt appears that assert() is used in one location within the test scope to catch invalid user inputs:\\n```\\nassert(amount < targetReserveBalance);\\n```\\n

Impact:

Recommended mitigation:
Using `require()` instead of `assert()`.
Vulnerability: Remove Loihi methods that can be used as backdoors by the administrator
Severity: high

Description:
There are several functions in `Loihi` that give extreme powers to the shell administrator. The most dangerous set of those is the ones granting the capability to add assimilators.\\nSince assimilators are essentially a proxy architecture to delegate code to several different implementations of the same interface, the administrator could, intentionally or unintentionally, deploy malicious or faulty code in the implementation of an assimilator. This means that the administrator is essentially totally trusted to not run code that, for example, drains the whole pool or locks up the users' and LPs' tokens.\\nIn addition to these, the function `safeApprove` allows the administrator to move any of the tokens the contract holds to any address regardless of the balances any of the users have.\\nThis can also be used by the owner as a backdoor to completely drain the contract.\\n```\\nfunction safeApprove(address \\_token, address \\_spender, uint256 \\_value) public onlyOwner {\\n\\n    (bool success, bytes memory returndata) = \\_token.call(abi.encodeWithSignature("approve(address,uint256)", \\_spender, \\_value));\\n\\n    require(success, "SafeERC20: low-level call failed");\\n\\n}\\n```\\n

Impact:

Recommended mitigation:
Remove the `safeApprove` function and, instead, use a trustless escape-hatch mechanism like the one suggested in issue 6.1.\\nFor the assimilator addition functions, our recommendation is that they are made completely internal, only callable in the constructor, at deploy time.\\nEven though this is not a big structural change (in fact, it reduces the attack surface), it is, indeed, a feature loss. However, this is the only way to make each shell a time-invariant system.\\nThis would not only increase Shell's security but also would greatly improve the trust the users have in the protocol since, after deployment, the code is now static and auditable.