msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Possible incorrect price for tokens in Balancer stable pool due to amplification parameter update
Severity: medium

Description:
Incorrect price calculation of tokens in StablePools if amplification factor is being updated\\nThe amplification parameter used to calculate the invariant can be in a state of update. In such a case, the current amplification parameter can differ from the amplificaiton parameter at the time of the last invariant calculation. The current implementaiton of `getTokenPriceFromStablePool` doesn't consider this and always uses the amplification factor obtained by calling `getLastInvariant`\\n```\\n            function getTokenPriceFromStablePool(\\n        address lookupToken_,\\n        uint8 outputDecimals_,\\n        bytes calldata params_\\n    ) external view returns (uint256) {\\n\\n                // rest of code..\\n\\n                try pool.getLastInvariant() returns (uint256, uint256 ampFactor) {\\n                   \\n                   // @audit the amplification factor as of the last invariant calculation is used\\n                    lookupTokensPerDestinationToken = StableMath._calcOutGivenIn(\\n                        ampFactor,\\n                        balances_,\\n                        destinationTokenIndex,\\n                        lookupTokenIndex,\\n                        1e18,\\n                        StableMath._calculateInvariant(ampFactor, balances_) // Sometimes the fetched invariant value does not work, so calculate it\\n                    );\\n```\\n\\n```\\n        // @audit the amplification parameter can be updated\\n        function startAmplificationParameterUpdate(uint256 rawEndValue, uint256 endTime) external authenticate {\\n\\n        // @audit for calculating the invariant the current amplification factor is obtained by calling _getAmplificationParameter()\\n        function _onSwapGivenIn(\\n        SwapRequest memory swapRequest,\\n        uint256[] memory balances,\\n        uint256 indexIn,\\n        uint256 indexOut\\n    ) internal virtual override whenNotPaused returns (uint256) {\\n        (uint256 currentAmp, ) = _getAmplificationParameter();\\n        uint256 amountOut = StableMath._calcOutGivenIn(currentAmp, balances, indexIn, indexOut, swapRequest.amount);\\n        return amountOut;\\n    }\\n```\\n

Impact:
In case the amplification parameter of a pool is being updated by the admin, wrong price will be calculated.

Recommended mitigation:
Use the latest amplification factor by callling the `getAmplificationParameter` function
Vulnerability: MerkleReserveMinter minting methodology is incompatible with current governance structure and can lead to migrated DAOs being hijacked immediately
Severity: medium

Description:
MerkleReserveMinter allows large number of tokens to be minted instantaneously which is incompatible with the current governance structure which relies on tokens being minted individually and time locked after minting by the auction. By minting and creating a proposal in the same block a user is able to create a proposal with significantly lower quorum than expected. This could easily be used to hijack the migrated DAO.\\nMerkleReserveMinter.sol#L154-L167\\n```\\nunchecked {\\n    for (uint256 i = 0; i < claimCount; ++i) {\\n        // Load claim in memory\\n        MerkleClaim memory claim = claims[I];\\n\\n        // Requires one proof per tokenId to handle cases where users want to partially claim\\n        if (!MerkleProof.verify(claim.merkleProof, settings.merkleRoot, keccak256(abi.encode(claim.mintTo, claim.tokenId)))) {\\n            revert INVALID_MERKLE_PROOF(claim.mintTo, claim.merkleProof, settings.merkleRoot);\\n        }\\n\\n        // Only allowing reserved tokens to be minted for this strategy\\n        IToken(tokenContract).mintFromReserveTo(claim.mintTo, claim.tokenId);\\n    }\\n}\\n```\\n\\nWhen minting from the claim merkle tree, a user is able to mint as many tokens as they want in a single transaction. This means in a single transaction, the supply of the token can increase very dramatically. Now we'll take a look at the governor contract as to why this is such an issue.\\nGovernor.sol#L184-L192\\n```\\n    // Store the proposal data\\n    proposal.voteStart = SafeCast.toUint32(snapshot);\\n    proposal.voteEnd = SafeCast.toUint32(deadline);\\n    proposal.proposalThreshold = SafeCast.toUint32(currentProposalThreshold);\\n    proposal.quorumVotes = SafeCast.toUint32(quorum());\\n    proposal.proposer = msg.sender;\\n    proposal.timeCreated = SafeCast.toUint32(block.timestamp);\\n\\n    emit ProposalCreated(proposalId, _targets, _values, _calldatas, _description, descriptionHash, proposal);\\n```\\n\\nGovernor.sol#L495-L499\\n```\\nfunction quorum() public view returns (uint256) {\\n    unchecked {\\n        return (settings.token.totalSupply() * settings.quorumThresholdBps) / BPS_PER_100_PERCENT;\\n    }\\n}\\n```\\n\\nWhen creating a proposal, we see that it uses a snapshot of the CURRENT total supply. This is what leads to the issue. The setup is fairly straightforward and occurs all in a single transaction:\\nCreate a malicious proposal (which snapshots current supply)\\nMint all the tokens\\nVote on malicious proposal with all minted tokens\\nThe reason this works is because the quorum is based on the supply before the mint while votes are considered after the mint, allowing significant manipulation of the quorum.

Impact:
DOA can be completely hijacked

Recommended mitigation:
Token should be changed to use a checkpoint based total supply, similar to how balances are handled. Quorum should be based on that instead of the current supply.
Vulnerability: SdtRewardReceiver#_withdrawRewards has incorrect slippage protection and withdraws can be sandwiched
Severity: medium

Description:
The _min_dy parameter of poolCvgSDT.exchange is set via the poolCvgSDT.get_dy method. The problem with this is that get_dy is a relative output that is executed at runtime. This means that no matter the state of the pool, this slippage check will never work.\\nSdtRewardReceiver.sol#L229-L236\\n```\\n        if (isMint) {\\n            /// @dev Mint cvgSdt 1:1 via CvgToke contract\\n            cvgSdt.mint(receiver, rewardAmount);\\n        } else {\\n            ICrvPoolPlain _poolCvgSDT = poolCvgSDT;\\n            /// @dev Only swap if the returned amount in CvgSdt is gretear than the amount rewarded in SDT\\n            _poolCvgSDT.exchange(0, 1, rewardAmount, _poolCvgSDT.get_dy(0, 1, rewardAmount), receiver);\\n        }\\n```\\n\\nWhen swapping from SDT to cvgSDT, get_dy is used to set _min_dy inside exchange. The issue is that get_dy is the CURRENT amount that would be received when swapping as shown below:\\n```\\n@view\\n@external\\ndef get_dy(i: int128, j: int128, dx: uint256) -> uint256:\\n    """\\n    @notice Calculate the current output dy given input dx\\n    @dev Index values can be found via the `coins` public getter method\\n    @param i Index value for the coin to send\\n    @param j Index valie of the coin to recieve\\n    @param dx Amount of `i` being exchanged\\n    @return Amount of `j` predicted\\n    """\\n    rates: uint256[N_COINS] = self.rate_multipliers\\n    xp: uint256[N_COINS] = self._xp_mem(rates, self.balances)\\n\\n    x: uint256 = xp[i] + (dx * rates[i] / PRECISION)\\n    y: uint256 = self.get_y(i, j, x, xp, 0, 0)\\n    dy: uint256 = xp[j] - y - 1\\n    fee: uint256 = self.fee * dy / FEE_DENOMINATOR\\n    return (dy - fee) * PRECISION / rates[j]\\n```\\n\\nThe return value is EXACTLY the result of a regular swap, which is where the problem is. There is no way that the exchange call can ever revert. Assume the user is swapping because the current exchange ratio is 1:1.5. Now assume their withdraw is sandwich attacked. The ratio is change to 1:0.5 which is much lower than expected. When get_dy is called it will simulate the swap and return a ratio of 1:0.5. This in turn doesn't protect the user at all and their swap will execute at the poor price.

Impact:
SDT rewards will be sandwiched and can lose the entire balance

Recommended mitigation:
Allow the user to set _min_dy directly so they can guarantee they get the amount they want
Vulnerability: Division difference can result in a revert when claiming treasury yield and excess rewards to some users
Severity: medium

Description:
Different ordering of calculations are used to compute `ysTotal` in different situations. This causes the totalShares tracked to be less than the claimable amount of shares\\n`ysTotal` is calculated differently when adding to `totalSuppliesTracking` and when computing `balanceOfYsCvgAt`. When adding to `totalSuppliesTracking`, the calculation of `ysTotal` is as follows:\\n```\\n        uint256 cvgLockAmount = (amount * ysPercentage) / MAX_PERCENTAGE;\\n        uint256 ysTotal = (lockDuration * cvgLockAmount) / MAX_LOCK;\\n```\\n\\nIn `balanceOfYsCvgAt`, `ysTotal` is calculated as follows\\n```\\n        uint256 ysTotal = (((endCycle - startCycle) * amount * ysPercentage) / MAX_PERCENTAGE) / MAX_LOCK;\\n```\\n\\nThis difference allows the `balanceOfYsCvgAt` to be greater than what is added to `totalSuppliesTracking`\\nPOC\\n```\\n  startCycle 357\\n  endCycle 420\\n  lockDuration 63\\n  amount 2\\n  ysPercentage 80\\n```\\n\\nCalculation in `totalSuppliesTracking` gives:\\n```\\n        uint256 cvgLockAmount = (2 * 80) / 100; == 1\\n        uint256 ysTotal = (63 * 1) / 96; == 0\\n```\\n\\nCalculation in `balanceOfYsCvgAt` gives:\\n```\\n        uint256 ysTotal = ((63 * 2 * 80) / 100) / 96; == 10080 / 100 / 96 == 1\\n```\\n\\nExample Scenario\\nAlice, Bob and Jake locks cvg for 1 TDE and obtains rounded up `balanceOfYsCvgAt`. A user who is aware of this issue can exploit this issue further by using `increaseLockAmount` with small amount values by which the total difference difference b/w the user's calculated `balanceOfYsCvgAt` and the accounted amount in `totalSuppliesTracking` can be increased. Bob and Jake claims the reward at the end of reward cycle. When Alice attempts to claim rewards, it reverts since there is not enough reward to be sent.

Impact:
This breaks the shares accounting of the treasury rewards. Some user's will get more than the actual intended rewards while the last withdrawals will result in a revert

Recommended mitigation:
Perform the same calculation in both places\\n```\\n+++                     uint256 _ysTotal = (_extension.endCycle - _extension.cycleId)* ((_extension.cvgLocked * _lockingPosition.ysPercentage) / MAX_PERCENTAGE) / MAX_LOCK;\\n---     uint256 ysTotal = (((endCycle - startCycle) * amount * ysPercentage) / MAX_PERCENTAGE) / MAX_LOCK;\\n```\\n
Vulnerability: BPT LP Token could be sold off during re-investment
Severity: medium

Description:
BPT LP Token could be sold off during the re-investment process. BPT LP Tokens must not be sold to external DEXs under any circumstance because:\\nThey are used to redeem the underlying assets from the pool when someone exits the vault\\nThe BPTs represent the total value of the vault\\nWithin the `ConvexStakingMixin._isInvalidRewardToken` function, the implementation ensures that the LP Token (CURVE_POOL_TOKEN) is not intentionally or accidentally sold during the reinvestment process.\\n```\\nFile: ConvexStakingMixin.sol\\n    function _isInvalidRewardToken(address token) internal override view returns (bool) {\\n        return (\\n            token == TOKEN_1 ||\\n            token == TOKEN_2 ||\\n            token == address(CURVE_POOL_TOKEN) ||\\n            token == address(CONVEX_REWARD_POOL) ||\\n            token == address(CONVEX_BOOSTER) ||\\n            token == Deployments.ALT_ETH_ADDRESS\\n        );\\n    }\\n```\\n\\nHowever, the same control was not implemented for the Balancer/Aura code. As a result, it is possible for LP Token (BPT) to be sold during reinvestment. Note that for non-composable Balancer pools, the pool tokens does not consists of the BPT token. Thus, it needs to be explicitly defined within the `_isInvalidRewardToken` function.\\n```\\nFile: AuraStakingMixin.sol\\n    function _isInvalidRewardToken(address token) internal override view returns (bool) {\\n        return (\\n            token == TOKEN_1 ||\\n            token == TOKEN_2 ||\\n            token == TOKEN_3 ||\\n            token == TOKEN_4 ||\\n            token == TOKEN_5 ||\\n            token == address(AURA_BOOSTER) ||\\n            token == address(AURA_REWARD_POOL) ||\\n            token == address(Deployments.WETH)\\n        );\\n    }\\n```\\n\\nPer the sponsor's clarification below, the contracts should protect against the bot doing unintended things (including acting maliciously) due to coding errors, which is one of the main reasons for having the `_isInvalidRewardToken` function. Thus, this issue is a valid bug in the context of this audit contest.\\n

Impact:
LP tokens (BPT) might be accidentally or maliciously sold off by the bots during the re-investment process. BPT LP Tokens must not be sold to external DEXs under any circumstance because:\\nThey are used to redeem the underlying assets from the pool when someone exits the vault\\nThe BPTs represent the total value of the vault

Recommended mitigation:
Ensure that the LP tokens cannot be sold off during re-investment.\\n```\\nfunction _isInvalidRewardToken(address token) internal override view returns (bool) {\\n    return (\\n        token == TOKEN_1 ||\\n        token == TOKEN_2 ||\\n        token == TOKEN_3 ||\\n        token == TOKEN_4 ||\\n        token == TOKEN_5 ||\\n// Add the line below\\n       token == BALANCER_POOL_TOKEN ||\\n        token == address(AURA_BOOSTER) ||\\n        token == address(AURA_REWARD_POOL) ||\\n        token == address(Deployments.WETH)\\n    );\\n}\\n```\\n
Vulnerability: MultiInvoker doesn't pay keepers refund for l1 calldata
Severity: medium

Description:
MultiInvoker doesn't pay keepers refund for l1 calldata, as result keepers can be not incentivized to execute orders.\\nMultiInvoker contract allows users to create orders, which then can be executed by keepers. For his job, keeper receives fee from order's creator. This fee payment is handled by `_handleKeep` function.\\nThe function will call `keep` modifier and will craft `KeepConfig` which contains `keepBufferCalldata`, which is flat fee for l1 calldata of this call.\\n```\\n    modifier keep(\\n        KeepConfig memory config,\\n        bytes calldata applicableCalldata,\\n        uint256 applicableValue,\\n        bytes memory data\\n    ) {\\n        uint256 startGas = gasleft();\\n\\n\\n        _;\\n\\n\\n        uint256 applicableGas = startGas - gasleft();\\n        (UFixed18 baseFee, UFixed18 calldataFee) = (\\n            _baseFee(applicableGas, config.multiplierBase, config.bufferBase),\\n            _calldataFee(applicableCalldata, config.multiplierCalldata, config.bufferCalldata)\\n        );\\n\\n\\n        UFixed18 keeperFee = UFixed18.wrap(applicableValue).add(baseFee).add(calldataFee).mul(_etherPrice());\\n        _raiseKeeperFee(keeperFee, data);\\n        keeperToken().push(msg.sender, keeperFee);\\n\\n\\n        emit KeeperCall(msg.sender, applicableGas, applicableValue, baseFee, calldataFee, keeperFee);\\n    }\\n```\\n\\nThis modifier should calculate amount of tokens that should be refunded to user and then raise it. We are interested not in whole modifier, but in calldata handling. To do that we call `_calldataFee` function. This function does nothing in the `Kept` contract and is overrided in the `Kept_Arbitrum` and `Kept_Optimism`.\\nThe problem is that MultiInvoker is only one and it just extends `Keept`. As result his `_calldataFee` function will always return 0, which means that calldata fee will not be added to the refund of keeper.

Impact:
Keeper will not be incentivized to execute orders.

Recommended mitigation:
You need to implement 2 versions of MultiInvoker: for optimism(Kept_Optimism) and arbitrum(Kept_Arbitrum).
Vulnerability: `KeeperOracle.request` adds only the first pair of market+account addresses per oracle version to callback list, ignoring all the subsequent ones
Severity: medium

Description:
The new feature introduced in 2.1 is the callback called for all markets and market+account pairs which requested the oracle version. These callbacks are called once the corresponding oracle settles. For this reason, `KeeperOracle` keeps a list of markets and market+account pairs per oracle version to call market.update on them:\\n```\\n/// @dev Mapping from version to a set of registered markets for settlement callback\\nmapping(uint256 => EnumerableSet.AddressSet) private _globalCallbacks;\\n\\n/// @dev Mapping from version and market to a set of registered accounts for settlement callback\\nmapping(uint256 => mapping(IMarket => EnumerableSet.AddressSet)) private _localCallbacks;\\n```\\n\\nHowever, currently `KeeperOracle` stores only the market+account from the first request call per oracle version, because if the request was already made, it returns from the function before adding to the list:\\n```\\nfunction request(IMarket market, address account) external onlyAuthorized {\\n    uint256 currentTimestamp = current();\\n@@@ if (versions[_global.currentIndex] == currentTimestamp) return;\\n\\n    versions[++_global.currentIndex] = currentTimestamp;\\n    emit OracleProviderVersionRequested(currentTimestamp);\\n\\n    // @audit only the first request per version reaches these lines to add market+account to callback list\\n    _globalCallbacks[currentTimestamp].add(address(market));\\n    _localCallbacks[currentTimestamp][market].add(account);\\n    emit CallbackRequested(SettlementCallback(market, account, currentTimestamp));\\n}\\n```\\n\\nAccording to docs, the same `KeeperOracle` can be used by multiple markets. And every account requesting in the same oracle version is supposed to be called back (settled) once the oracle version settles.

Impact:
The new core function of the protocol doesn't work as expected and `KeeperOracle` will fail to call back markets and accounts if there is more than 1 request in the same oracle version (which is very likely).

Recommended mitigation:
Move addition to callback list to just before the condition to exit function early:\\n```\\nfunction request(IMarket market, address account) external onlyAuthorized {\\n    uint256 currentTimestamp = current();\\n    _globalCallbacks[currentTimestamp].add(address(market));\\n    _localCallbacks[currentTimestamp][market].add(account);\\n    emit CallbackRequested(SettlementCallback(market, account, currentTimestamp));\\n    if (versions[_global.currentIndex] == currentTimestamp) return;\\n\\n    versions[++_global.currentIndex] = currentTimestamp;\\n    emit OracleProviderVersionRequested(currentTimestamp);\\n}\\n```\\n
Vulnerability: Wound agent can't invoke heal in the next round
Severity: medium

Description:
Assume players being marked as wounded in the round `12` , players cannot invoke `heal` in the next round 13\\n```\\n    function test_heal_in_next_round_v1() public {\\n        _startGameAndDrawOneRound();\\n\\n        _drawXRounds(11);\\n\\n\\n        (uint256[] memory woundedAgentIds, ) = infiltration.getRoundInfo({roundId: 12});\\n\\n        address agentOwner = _ownerOf(woundedAgentIds[0]);\\n        looks.mint(agentOwner, HEAL_BASE_COST);\\n\\n        vm.startPrank(agentOwner);\\n        _grantLooksApprovals();\\n        looks.approve(TRANSFER_MANAGER, HEAL_BASE_COST);\\n\\n        uint256[] memory agentIds = new uint256[](1);\\n        agentIds[0] = woundedAgentIds[0];\\n\\n        uint256[] memory costs = new uint256[](1);\\n        costs[0] = HEAL_BASE_COST;\\n\\n        //get gameInfo\\n        (,,,,,uint40 currentRoundId,,,,,) = infiltration.gameInfo();\\n        assert(currentRoundId == 13);\\n\\n        //get agent Info\\n        IInfiltration.Agent memory agentInfo = infiltration.getAgent(woundedAgentIds[0]);\\n        assert(agentInfo.woundedAt == 12);\\n\\n        //agent can't invoke heal in the next round.\\n        vm.expectRevert(IInfiltration.HealingMustWaitAtLeastOneRound.selector);\\n        infiltration.heal(agentIds);\\n    }\\n```\\n

Impact:
User have to wait for 1 more round which led to the odds for an Agent to heal successfully start at 99% at Round 1 reduce to 98% at Round 2.

Recommended mitigation:
```\\n    // No need to check if the heal deadline has passed as the agent would be killed\\n    unchecked {\\n-       if (currentRoundId - woundedAt < 2) {\\n-       if (currentRoundId - woundedAt < 1) {\\n            revert HealingMustWaitAtLeastOneRound();\\n        }\\n    }\\n```\\n
Vulnerability: fulfillRandomWords() could revert under certain circumstances
Severity: medium

Description:
Crucial part of my POC is the variable AGENTS_TO_WOUND_PER_ROUND_IN_BASIS_POINTS. I communicated with the protocol's team that they plan to set it to 20 initially but it is possible to have a different value for it in future. For the POC i used 30.\\n```\\nfunction test_fulfillRandomWords_revert() public {\\n        _startGameAndDrawOneRound();\\n\\n        _drawXRounds(48);\\n        \\n        uint256 counter = 0;\\n        uint256[] memory wa = new uint256[](30);\\n        uint256 totalCost = 0;\\n\\n        for (uint256 j=2; j <= 6; j++) \\n        {\\n            (uint256[] memory woundedAgentIds, ) = infiltration.getRoundInfo({roundId: j});\\n\\n            uint256[] memory costs = new uint256[](woundedAgentIds.length);\\n            for (uint256 i; i < woundedAgentIds.length; i++) {\\n                costs[i] = HEAL_BASE_COST;\\n                wa[counter] = woundedAgentIds[i];\\n                counter++;\\n                if(counter > 29) break;\\n            }\\n\\n            if(counter > 29) break;\\n        }\\n        \\n        \\n        totalCost = HEAL_BASE_COST * wa.length;\\n        looks.mint(user1, totalCost);\\n\\n        vm.startPrank(user1);\\n        _grantLooksApprovals();\\n        looks.approve(TRANSFER_MANAGER, totalCost);\\n\\n\\n        infiltration.heal(wa);\\n        vm.stopPrank();\\n\\n        _drawXRounds(1);\\n    }\\n```\\n\\nI put this test into Infiltration.startNewRound.t.sol and used --gas-report to see that the gas used for fulfillRandomWords exceeds 2 500 000.

Impact:
DOS of the protocol and inability to continue the game.

Recommended mitigation:
A couple of ideas :\\nYou can limit the value of AGENTS_TO_WOUND_PER_ROUND_IN_BASIS_POINTS to a small enough number so that it is 100% sure it will not reach the gas limit.\\nConsider simply storing the randomness and taking more complex follow-on actions in separate contract calls as stated in the "Security Considerations" section of the VRF's docs.