msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Uniswap v3 callbacks access control should be hardened
Severity: low

Description:
Resolution\\nFixed in GammaStrategies/[email protected]9a7a3dd by implementing the auditor's recommendation for `uniswapV3MintCallback`, and deleting `uniswapV3SwapCallback` and the call to `pool.swap`.\\nUniswap v3 uses a callback pattern to pull funds from the caller. The caller, (in this case Hypervisor) has to implement a callback function which will be called by the Uniswap's `pool`. Both `uniswapV3MintCallback` and `uniswapV3SwapCallback` restrict the access to the callback functions only for the `pool`. However, this alone will not block a random call from the `pool` contract in case the latter was hacked, which will result in stealing all the funds held in `Hypervisor` or of any user that approved the `Hypervisor` contract to transfer tokens on his behalf.\\n```\\nfunction uniswapV3MintCallback(\\n    uint256 amount0,\\n    uint256 amount1,\\n    bytes calldata data\\n) external override {\\n    require(msg.sender == address(pool));\\n    address payer = abi.decode(data, (address));\\n\\n    if (payer == address(this)) {\\n        if (amount0 > 0) token0.safeTransfer(msg.sender, amount0);\\n        if (amount1 > 0) token1.safeTransfer(msg.sender, amount1);\\n    } else {\\n        if (amount0 > 0) token0.safeTransferFrom(payer, msg.sender, amount0);\\n        if (amount1 > 0) token1.safeTransferFrom(payer, msg.sender, amount1);\\n    }\\n}\\n\\nfunction uniswapV3SwapCallback(\\n    int256 amount0Delta,\\n    int256 amount1Delta,\\n    bytes calldata data\\n) external override {\\n    require(msg.sender == address(pool));\\n    address payer = abi.decode(data, (address));\\n\\n    if (amount0Delta > 0) {\\n        if (payer == address(this)) {\\n            token0.safeTransfer(msg.sender, uint256(amount0Delta));\\n        } else {\\n            token0.safeTransferFrom(payer, msg.sender, uint256(amount0Delta));\\n        }\\n    } else if (amount1Delta > 0) {\\n        if (payer == address(this)) {\\n            token1.safeTransfer(msg.sender, uint256(amount1Delta));\\n        } else {\\n            token1.safeTransferFrom(payer, msg.sender, uint256(amount1Delta));\\n        }\\n    }\\n}\\n```\\n

Impact:

Recommended mitigation:
Consider adding (boolean) storage variables that will help to track whether a call to `uniswapV3MintCallback | uniswapV3SwapCallback` was preceded by a call to `_mintLiquidity | rebalance` respectively. An example for the `rebalance` function would be bool `rebalanceCalled`, this variable will be assigned a `true` value in `rebalance` before the external call of `pool.swap`, then `uniswapV3SwapCallback` will require that `rebalanceCalled` == `true`, and then right after `rebalanceCalled` will be assigned a `false` value.
Vulnerability: UniProxy.depositSwap doesn't deposit all the users' funds
Severity: medium

Description:
Resolution\\nFixed in GammaStrategies/[email protected]9a7a3dd by deleting the `depositSwap` function.\\nWhen executing the swap, the minimal amount out is passed to the router (deposit1 in this example), but the actual swap amount will be `amountOut`. But after the trade, instead of depositing `amountOut`, the contract tries to deposit `deposit1`, which is lower. This may result in some users' funds staying in the `UniProxy` contract.\\n```\\nelse{\\n //swap token1 for token0\\n swap = uint256(swapAmount);\\n IHypervisor(pos).token0().transferFrom(msg.sender, address(this), deposit0+swap);\\n\\n amountOut = router.exactInput(\\n ISwapRouter.ExactInputParams(\\n path,\\n address(this),\\n block.timestamp + swapLife,\\n swap,\\n deposit1\\n )\\n ); \\n}\\n\\nrequire(amountOut > 0, "Swap failed");\\n\\nif (positions[pos].version < 2) {\\n // requires lp token transfer from proxy to msg.sender\\n shares = IHypervisor(pos).deposit(deposit0, deposit1, address(this));\\n IHypervisor(pos).transfer(to, shares);\\n}\\n```\\n

Impact:

Recommended mitigation:
Deposit all the user's funds to the Hypervisor.
Vulnerability: Initialization flaws
Severity: low

Description:
For non-upgradeable contracts, the Solidity compiler takes care of chaining the constructor calls of an inheritance hierarchy in the right order; for upgradeable contracts, taking care of initialization is a manual task - and with extensive use of inheritance, it is tedious and error-prone. The convention in OpenZeppelin Contracts Upgradeable is to have a `__C_init_unchained` function that contains the actual initialization logic for contract `C` and a `__C_init` function that calls the `*_init_unchained` function for every super-contract - direct and indirect - in the inheritance hierarchy (including C) in the C3-linearized order from most basic to most derived. This pattern imitates what the compiler does for constructors.\\nAll `*_init` functions in the contracts (__ERC20WrapperGluwacoin_init, `__ERC20Reservable_init`, `__ERC20ETHless_init`, and __ERC20Wrapper_init) are missing some `_init_unchained` calls, and sometimes the existing calls are not in the correct order.\\nThe `__ERC20WrapperGluwacoin_init` function is implemented as follows:\\n```\\nfunction \\_\\_ERC20WrapperGluwacoin\\_init(\\n    string memory name,\\n    string memory symbol,\\n    IERC20 token\\n) internal initializer {\\n    \\_\\_Context\\_init\\_unchained();\\n    \\_\\_ERC20\\_init\\_unchained(name, symbol);\\n    \\_\\_ERC20ETHless\\_init\\_unchained();\\n    \\_\\_ERC20Reservable\\_init\\_unchained();\\n    \\_\\_AccessControlEnumerable\\_init\\_unchained();\\n    \\_\\_ERC20Wrapper\\_init\\_unchained(token);\\n    \\_\\_ERC20WrapperGluwacoin\\_init\\_unchained();\\n}\\n```\\n\\nAnd the C3 linearization is:\\n```\\nERC20WrapperGluwacoin\\n  ↖ ERC20Reservable\\n  ↖ ERC20ETHless\\n  ↖ ERC20Wrapper\\n  ↖ ERC20Upgradeable\\n  ↖ IERC20MetadataUpgradeable\\n  ↖ IERC20Upgradeable\\n  ↖ AccessControlEnumerableUpgradeable\\n  ↖ AccessControlUpgradeable\\n  ↖ ERC165Upgradeable\\n  ↖ IERC165Upgradeable\\n  ↖ IAccessControlEnumerableUpgradeable\\n  ↖ IAccessControlUpgradeable\\n  ↖ ContextUpgradeable\\n  ↖ Initializable\\n```\\n\\nThe calls `__ERC165_init_unchained();` and `__AccessControl_init_unchained();` are missing, and `__ERC20Wrapper_init_unchained(token);` should move between `__ERC20_init_unchained(name, symbol);` and `__ERC20ETHless_init_unchained();`.

Impact:

Recommended mitigation:
Review all `*_init` functions, add the missing `*_init_unchained` calls, and fix the order of these calls.
Vulnerability: Flaw in _beforeTokenTransfer call chain and missing tests
Severity: low

Description:
In OpenZeppelin's ERC-20 implementation, the virtual `_beforeTokenTransfer` function provides a hook that is called before tokens are transferred, minted, or burned. In the Gluwacoin codebase, it is used to check whether the unreserved balance (as opposed to the regular balance, which is checked by the ERC-20 implementation) of the sender is sufficient to allow this transfer or burning.\\nIn `ERC20WrapperGluwacoin`, `ERC20Reservable`, and `ERC20Wrapper`, the `_beforeTokenTransfer` function is implemented in the following way:\\n```\\nfunction \\_beforeTokenTransfer(\\n    address from,\\n    address to,\\n    uint256 amount\\n) internal override(ERC20Upgradeable, ERC20Wrapper, ERC20Reservable) {\\n    ERC20Wrapper.\\_beforeTokenTransfer(from, to, amount);\\n    ERC20Reservable.\\_beforeTokenTransfer(from, to, amount);\\n}\\n```\\n\\n```\\nfunction \\_beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override (ERC20Upgradeable) {\\n    if (from != address(0)) {\\n        require(\\_unreservedBalance(from) >= amount, "ERC20Reservable: transfer amount exceeds unreserved balance");\\n    }\\n\\n    super.\\_beforeTokenTransfer(from, to, amount);\\n}\\n```\\n\\n```\\nfunction \\_beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override (ERC20Upgradeable) {\\n    super.\\_beforeTokenTransfer(from, to, amount);\\n}\\n```\\n\\nFinally, the C3-linearization of the contracts is:\\n```\\nERC20WrapperGluwacoin\\n  ↖ ERC20Reservable\\n  ↖ ERC20ETHless\\n  ↖ ERC20Wrapper\\n  ↖ ERC20Upgradeable\\n  ↖ IERC20MetadataUpgradeable\\n  ↖ IERC20Upgradeable\\n  ↖ AccessControlEnumerableUpgradeable\\n  ↖ AccessControlUpgradeable\\n  ↖ ERC165Upgradeable\\n  ↖ IERC165Upgradeable\\n  ↖ IAccessControlEnumerableUpgradeable\\n  ↖ IAccessControlUpgradeable\\n  ↖ ContextUpgradeable\\n  ↖ Initializable\\n```\\n\\nThis means `ERC20Wrapper._beforeTokenTransfer` is ultimately called twice - once directly in `ERC20WrapperGluwacoin._beforeTokenTransfer` and then a second time because the `super._beforeTokenTransfer` call in `ERC20Reservable._beforeTokenTransfer` resolves to `ERC20Wrapper._beforeTokenTransfer`. (ERC20ETHless doesn't override _beforeTokenTransfer.)\\nMoreover, while reviewing the correctness and coverage of the tests is not in scope for this engagement, we happened to notice that there are no tests that check whether the unreserved balance is sufficient for transferring or burning tokens.

Impact:

Recommended mitigation:
`ERC20WrapperGluwacoin._beforeTokenTransfer` should just call `super._beforeTokenTransfer`. Moreover, the `_beforeTokenTransfer` implementation can be removed from `ERC20Wrapper`.\\nWe would like to stress the importance of careful and comprehensive testing in general and of this functionality in particular, as it is crucial for the system's integrity. We also encourage investigating whether there are more such omissions and an evaluation of the test quality and coverage in general.
Vulnerability: Hard-coded decimals
Severity: low

Description:
The Gluwacoin wrapper token should have the same number of decimals as the wrapped ERC-20. Currently, the number of decimals is hard-coded to 6. This limits flexibility or requires source code changes and recompilation if a token with a different number of decimals is to be wrapped.\\n```\\nfunction decimals() public pure override returns (uint8) {\\n    return 6;\\n}\\n```\\n

Impact:

Recommended mitigation:
We recommend supplying the number of `decimals` as an initialization parameter and storing it in a state variable. That increases gas consumption of the `decimals` function, but we doubt this view function will be frequently called from a contract, and even if it was, we think the benefits far outweigh the costs.\\nMoreover, we believe the `decimals` logic (i.e., function `decimals` and the new state variable) should be implemented in the `ERC20Wrapper` contract - which holds the basic ERC-20 functionality of the wrapper token - and not in `ERC20WrapperGluwacoin`, which is the base contract of the entire system.
Vulnerability: Re-initialization of the Balancer pool is potentially possible
Severity: low

Description:
Instead of creating a new Balancer pool for an auction every time, the same pool is getting re-used repeatedly. When this happens, the old liquidity is withdrawn, and if there is enough FEI in the contract, the weights are shifted pool is filled with new tokens. If there is not enough FEI, the pool is left empty, and users can still interact with it. When there's enough FEI again, it's re-initialized again, which is not the intention:\\n```\\nuint256 bptTotal = pool.totalSupply();\\nuint256 bptBalance = pool.balanceOf(address(this));\\n\\n// Balancer locks a small amount of bptTotal after init, so 0 bpt means pool needs initializing\\nif (bptTotal == 0) {\\n    \\_initializePool();\\n    return;\\n}\\n```\\n\\nTheoretically, this will never happen because there should be minimal leftover liquidity tokens after the withdrawal. But we couldn't strictly verify that fact because it requires looking into balancer code much deeper.

Impact:

Recommended mitigation:
One of the options would be only to allow re-using the pool in atomic transactions. So if there are not enough FEI tokens for the next auction, the `swap` transaction reverts. That will help with another issue (issue 3.2) too.
Vulnerability: The BalancerLBPSwapper may not have enough Tribe tokens
Severity: low

Description:
Whenever the `swap` function is called, it should re-initialize the Balancer pool that requires adding liquidity: 99% Fei and 1% Tribe. So the Tribe should initially be in the contract.\\n```\\nfunction \\_getTokensIn(uint256 spentTokenBalance) internal view returns(uint256[] memory amountsIn) {\\n    amountsIn = new uint256[](2);\\n\\n    uint256 receivedTokenBalance = readOracle().mul(spentTokenBalance).mul(ONE\\_PERCENT).div(NINETY\\_NINE\\_PERCENT).asUint256();\\n\\n    if (address(assets[0]) == tokenSpent) {\\n        amountsIn[0] = spentTokenBalance;\\n        amountsIn[1] = receivedTokenBalance;\\n    } else {\\n        amountsIn[0] = receivedTokenBalance;\\n        amountsIn[1] = spentTokenBalance;\\n    }\\n}\\n```\\n\\nAdditionally, when the `swap` is called, and there is not enough FEI to re-initiate the Balancer auction, all the Tribe gets withdrawn. So the next time the `swap` is called, there is no Tribe in the contract again.\\n```\\n// 5. Send remaining tokenReceived to target\\nIERC20(tokenReceived).transfer(tokenReceivingAddress, IERC20(tokenReceived).balanceOf(address(this)));\\n```\\n

Impact:

Recommended mitigation:
Create an automated mechanism that mints/transfers Tribe when it is needed in the swapper contract.
Vulnerability: StableSwapOperatorV1 - resistantFei value is not correct in the resistantBalanceAndFei function
Severity: high

Description:
The `resistantBalanceAndFei` function of a `PCVDeposit` contract is supposed to return the amount of funds that the contract controls; it is then used to evaluate the total value of PCV (collateral in the protocol). Additionally, this function returns the number of FEI tokens that are protocol-controlled. These FEI tokens are “temporarily minted”; they are not backed up by the collateral and shouldn't be used in calculations that determine the collateralization of the protocol.\\nIdeally, the amount of these FEI tokens should be the same during the deposit, withdrawal, and the `resistantBalanceAndFei` function call. In the `StableSwapOperatorV1` contract, all these values are totally different:\\nduring the deposit, the amount of required FEI tokens is calculated. It's done in a way so the values of FEI and 3pool tokens in the metapool should be equal after the deposit. So if there is the initial imbalance of FEI and 3pool tokens, the deposit value of these tokens will be different:\\n```\\n// get the amount of tokens in the pool\\n(uint256 \\_3crvAmount, uint256 \\_feiAmount) = (\\n    IStableSwap2(pool).balances(\\_3crvIndex),\\n    IStableSwap2(pool).balances(\\_feiIndex)\\n);\\n// // rest of code and the expected amount of 3crv in it after deposit\\nuint256 \\_3crvAmountAfter = \\_3crvAmount + \\_3crvBalanceAfter;\\n \\n// get the usd value of 3crv in the pool\\nuint256 \\_3crvUsdValue = \\_3crvAmountAfter \\* IStableSwap3(\\_3pool).get\\_virtual\\_price() / 1e18;\\n \\n// compute the number of FEI to deposit\\nuint256 \\_feiToDeposit = 0;\\nif (\\_3crvUsdValue > \\_feiAmount) {\\n    \\_feiToDeposit = \\_3crvUsdValue - \\_feiAmount;\\n}\\n```\\n\\nduring the withdrawal, the FEI and 3pool tokens are withdrawn in the same proportion as they are present in the metapool:\\n```\\nuint256[2] memory \\_minAmounts; // [0, 0]\\nIERC20(pool).approve(pool, \\_lpToWithdraw);\\nuint256 \\_3crvBalanceBefore = IERC20(\\_3crv).balanceOf(address(this));\\nIStableSwap2(pool).remove\\_liquidity(\\_lpToWithdraw, \\_minAmounts);\\n```\\n\\nin the `resistantBalanceAndFei` function, the value of protocol-controlled FEI tokens and the value of 3pool tokens deposited are considered equal:\\n```\\nresistantBalance = \\_lpPriceUSD / 2;\\nresistantFei = resistantBalance;\\n```\\n\\nSome of these values may be equal under some circumstances, but that is not enforced. After one of the steps (deposit or withdrawal), the total PCV value and collateralization may be changed significantly.

Impact:

Recommended mitigation:
Make sure that deposit, withdrawal, and the `resistantBalanceAndFei` are consistent and won't instantly change the PCV value significantly.
Vulnerability: BalancerLBPSwapper - init() can be front-run to potentially steal tokens
Severity: medium

Description:
The deployment process for `BalancerLBPSwapper` appears to be the following:\\ndeploy `BalancerLBPSwapper`.\\nrun `ILiquidityBootstrappingPoolFactory.create()` proving the newly deployed swapper address as the owner of the pool.\\ninitialize `BalancerLBPSwapper.init()` with the address of the newly created pool.\\nThis process may be split across multiple transactions as in the `v2Phase1.js` deployment scenario.\\nBetween step (1) and (3) there is a window of opportunity for someone to maliciously initialize contract. This should be easily detectable because calling `init()` twice should revert the second transaction. If this is not caught in the deployment script this may have more severe security implications. Otherwise, this window can be used to grief the deployment initializing it before the original initializer does forcing them to redeploy the contract or to steal any tokenSpent/tokenReceived that are owned by the contract at this time.\\nNote: It is assumed that the contract will not own a lot of tokens right after deployment rendering the scenario of stealing tokens more unlikely. However, that highly depends on the deployment script for the contract system.\\n```\\nfunction init(IWeightedPool \\_pool) external {\\n    require(address(pool) == address(0), "BalancerLBPSwapper: initialized");\\n\\n    pool = \\_pool;\\n    IVault \\_vault = \\_pool.getVault();\\n\\n    vault = \\_vault;\\n\\n    // Check ownership\\n    require(\\_pool.getOwner() == address(this), "BalancerLBPSwapper: contract not pool owner");\\n```\\n\\n```\\nIERC20(tokenSpent).approve(address(\\_vault), type(uint256).max);\\nIERC20(tokenReceived).approve(address(\\_vault), type(uint256).max);\\n```\\n

Impact:

Recommended mitigation:
protect `BalancerLBPSwapper.init()` and only allow a trusted entity (e.g. the initial deployer) to call this method.
Vulnerability: PCVEquityMinter and BalancerLBPSwapper - desynchronisation race
Severity: medium

Description:
There is nothing that prevents other actors from calling `BalancerLBPSwapper.swap()` `afterTime` but right before `PCVEquityMinter.mint()` would as long as the `minAmount` required for the call to pass is deposited to `BalancerLBPSwapper`.\\nBoth the `PCVEquityMinter.mint()` and `BalancerLBPSwapper.swap()` are timed (via the `afterTime` modifier) and are ideally in sync. In an ideal world the incentive to call `mint()` would be enough to ensure that both contracts are always in sync, however, a malicious actor might interfere by calling `.swap()` directly, providing the `minAmount` required for the call to pass. This will have two effects:\\ninstead of taking the newly minted FEI from `PCVEquityMinter`, existing FEI from the malicious user will be used with the pool. (instead of inflating the token the malicious actor basically pays for it)\\nthe `Timed` modifiers of both contracts will be out of sync with `BalancerLBPSwapper.swap()` being reset (and failing until it becomes available again) and `PCVEquityMinter.mint()` still being available. Furthermore, keeper-scripts (or actors that want to get the incentive) might continue to attempt to `mint()` while the call will ultimately fail in `.swap()` due to the resynchronization of `timed` (unless they simulate the calls first).\\nNote: There are not a lot of incentives to actually exploit this other than preventing protocol inflation (mint) and potentially griefing users. A malicious user will lose out on the incentivized call and has to ensure that the `minAmount` required for `.swap()` to work is available. It is, however, in the best interest of security to defuse the unpredictable racy character of the contract interaction.\\n```\\nfunction \\_afterMint() internal override {\\n    IPCVSwapper(target).swap();\\n}\\n```\\n\\n```\\nfunction swap() external override afterTime whenNotPaused {\\n    (\\n        uint256 spentReserves,\\n        uint256 receivedReserves, \\n        uint256 lastChangeBlock\\n    ) = getReserves();\\n\\n    // Ensures no actor can change the pool contents earlier in the block\\n    require(lastChangeBlock < block.number, "BalancerLBPSwapper: pool changed this block");\\n```\\n

Impact:

Recommended mitigation:
If `BalancerLBPSwapper.swap()` is only to be called within the flows of action from a `PCVEquityMinter.mint()` it is suggested to authenticate the call and only let `PCVEquityMinter` call `.swap()`
Vulnerability: CollateralizationOracleWrapper - the deviation threshold check in update() always returns false
Severity: medium

Description:
A call to `update()` returns a boolean flag indicating whether the update was performed on outdated data. This flag is being checked in `updateIfOutdated()` which is typically called by an incentivized keeper function.\\nThe `_isExceededDeviationThreshold` calls at the end of the `_update()` function always return `false` as they are comparing the same values (cachedProtocolControlledValue to the `_protocolControlledValue` value and `cachedProtocolControlledValue` has just been set to `_protocolControlledValue` a couple of lines before). `_isExceededDeviationThreshold` will, therefore, never detect a deviation and return `false´.\\nThere may currently be no incentive (e.g. from the keeper side) to call `update()` if the values are not outdated but they deviated too much from the target. However, anyone can force an update by calling the non-incentivized public `update()` method instead.\\n```\\n    require(\\_validityStatus, "CollateralizationOracleWrapper: CollateralizationOracle is invalid");\\n\\n    // set cache variables\\n    cachedProtocolControlledValue = \\_protocolControlledValue;\\n    cachedUserCirculatingFei = \\_userCirculatingFei;\\n    cachedProtocolEquity = \\_protocolEquity;\\n\\n    // reset time\\n    \\_initTimed();\\n\\n    // emit event\\n    emit CachedValueUpdate(\\n        msg.sender,\\n        cachedProtocolControlledValue,\\n        cachedUserCirculatingFei,\\n        cachedProtocolEquity\\n    );\\n\\n    return outdated\\n        || \\_isExceededDeviationThreshold(cachedProtocolControlledValue, \\_protocolControlledValue)\\n        || \\_isExceededDeviationThreshold(cachedUserCirculatingFei, \\_userCirculatingFei);\\n}\\n```\\n

Impact:

Recommended mitigation:
Add unit tests to check for all three return conditions (timed, deviationA, deviationB)\\nMake sure to compare the current to the stored value before updating the cached values when calling `_isExceededDeviationThreshold`.