msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: FulfillInterpreter - Wrong order of actions in fallback handling
Severity: low

Description:
When a transaction with a `callTo` that is not `address(0)` is fulfilled, the funds to be withdrawn on the user's behalf are first transferred to the `FulfillInterpreter` instance that is associated with this `TransactionManager` instance. After that, `execute` is called on that interpreter instance, which, in turn, tries to make a call to `callTo`. If that call reverts or isn't made in the first place because `callTo` is not a contract address, the funds are transferred directly to the `receivingAddress` in the transaction (which becomes `fallbackAddress` in execute); otherwise, it's the called contract's task to transfer the previously approved funds from the interpreter.\\n```\\nbool isNative = LibAsset.isNativeAsset(assetId);\\nif (!isNative) {\\n  LibAsset.increaseERC20Allowance(assetId, callTo, amount);\\n}\\n\\n// Check if the callTo is a contract\\nbool success;\\nbytes memory returnData;\\nif (Address.isContract(callTo)) {\\n  // Try to execute the callData\\n  // the low level call will return `false` if its execution reverts\\n  (success, returnData) = callTo.call{value: isNative ? amount : 0}(callData);\\n}\\n\\n// Handle failure cases\\nif (!success) {\\n  // If it fails, transfer to fallback\\n  LibAsset.transferAsset(assetId, fallbackAddress, amount);\\n  // Decrease allowance\\n  if (!isNative) {\\n    LibAsset.decreaseERC20Allowance(assetId, callTo, amount);\\n  }\\n}\\n```\\n\\nFor the fallback scenario, i.e., the call isn't executed or fails, the funds are first transferred to `fallbackAddress`, and the previously increased allowance is decreased after that. If the token supports it, the recipient of the direct transfer could try to exploit that the approval hasn't been revoked yet, so the logically correct order is to decrease the allowance first and transfer the funds later. However, it should be noted that the `FulfillInterpreter` should, at any point in time, only hold the funds that are supposed to be transferred as part of the current transaction; if there are any excess funds, these are leftovers from a previous failure to withdraw everything that could have been withdrawn, so these can be considered up for grabs. Hence, this is only a minor issue.

Impact:

Recommended mitigation:
We recommend reversing the order of actions for the fallback case: Decrease the allowance first, and transfer later. Moreover, it would be better to increase the allowance only in case a call will actually be made, i.e., if `Address.isContract(callTo)` is `true`.\\nRemark\\nThis issue was already present in the original version of the code but was missed initially and only found during the re-audit.
Vulnerability: FulfillInterpreter - Missing check whether callTo address contains code
Severity: low

Description:
Resolution\\nThis issue has been fixed.\\nThe receiver-side `prepare` checks whether the `callTo` address is either zero or a contract:\\n```\\n// Check that the callTo is a contract\\n// NOTE: This cannot happen on the sending chain (different chain\\n// contexts), so a user could mistakenly create a transfer that must be\\n// cancelled if this is incorrect\\nrequire(invariantData.callTo == address(0) || Address.isContract(invariantData.callTo), "#P:031");\\n```\\n\\nHowever, as a contract may `selfdestruct` and the check is not repeated later, there is no guarantee that `callTo` still contains code when the call to this address (assuming it is non-zero) is actually executed in FulfillInterpreter.execute:\\n```\\n// Try to execute the callData\\n// the low level call will return `false` if its execution reverts\\n(bool success, bytes memory returnData) = callTo.call{value: isEther ? amount : 0}(callData);\\n\\nif (!success) {\\n  // If it fails, transfer to fallback\\n  Asset.transferAsset(assetId, fallbackAddress, amount);\\n  // Decrease allowance\\n  if (!isEther) {\\n    Asset.decreaseERC20Allowance(assetId, callTo, amount);\\n  }\\n}\\n```\\n\\nAs a result, if the contract at `callTo` self-destructs between `prepare` and `fulfill` (both on the receiving chain), `success` will be `true`, and the funds will probably be lost to the user.\\nA user could currently try to avoid this by checking that the contract still exists before calling `fulfill` on the receiving chain, but even then, they might get front-run by `selfdestruct`, and the situation is even worse with a relayer, so this provides no reliable protection.

Impact:

Recommended mitigation:
Repeat the `Address.isContract` check on `callTo` before making the external call in `FulfillInterpreter.execute` and send the funds to the `fallbackAddress` if the result is `false`.\\nIt is, perhaps, debatable whether the check in `prepare` should be kept or removed. In principle, if the contract gets deployed between `prepare` and `fulfill`, that is still soon enough. However, if the `callTo` address doesn't have code at the time of `prepare`, this seems more likely to be a mistake than a “late deployment”. So unless there is a demonstrated use case for “late deployments”, failing in `prepare` (even though it's receiver-side) might still be the better choice.\\nRemark\\nIt should be noted that an unsuccessful call, i.e., a revert, is the only behavior that is recognized by `FulfillInterpreter.execute` as failure. While it is prevalent to indicate failure by reverting, this doesn't have to be the case; a well-known example is an ERC20 token that indicates a failing transfer by returning `false`.\\nA user who wants to utilize this feature has to make sure that the called contract behaves accordingly; if that is not the case, an intermediary contract may be employed, which, for example, reverts for return value `false`.
Vulnerability: TransactionManager - Adherence to EIP-712  Won't Fix
Severity: low

Description:
`fulfill` function requires the user signature on a `transactionId`. While currently, the user SDK code is using a cryptographically secured pseudo-random function to generate the `transactionId`, it should not be counted upon and measures should be placed on the smart-contract level to ensure replay-attack protection.\\n```\\nfunction recoverSignature(\\n  bytes32 transactionId,\\n  uint256 relayerFee,\\n  string memory functionIdentifier,\\n  bytes calldata signature\\n) internal pure returns (address) {\\n  // Create the signed payload\\n  SignedData memory payload = SignedData({\\n    transactionId: transactionId,\\n    relayerFee: relayerFee,\\n    functionIdentifier: functionIdentifier\\n  });\\n\\n  // Recover\\n  return ECDSA.recover(ECDSA.toEthSignedMessageHash(keccak256(abi.encode(payload))), signature);\\n}\\n```\\n

Impact:

Recommended mitigation:
Consider adhering to EIP-712, or at least including `address(this), block.chainId` as part of the data signed by the user.
Vulnerability: TransactionManager - Hard-coded chain ID might lead to problems after a chain split  Pending
Severity: low

Description:
The ID of the chain on which the contract is deployed is supplied as a constructor argument and stored as an `immutable` state variable:\\n```\\n/\\*\\*\\n \\* @dev The chain id of the contract, is passed in to avoid any evm issues\\n \\*/\\nuint256 public immutable chainId;\\n```\\n\\n```\\nconstructor(uint256 \\_chainId) {\\n  chainId = \\_chainId;\\n  interpreter = new FulfillInterpreter(address(this));\\n}\\n```\\n\\nHence, `chainId` can never change, and even after a chain split, both contracts would continue to use the same chain ID. That can have undesirable consequences. For example, a transaction that was prepared before the split could be fulfilled on both chains.

Impact:

Recommended mitigation:
It would be better to query the chain ID directly from the chain via `block.chainId`. However, the development team informed us that they had encountered problems with this approach as some chains apparently are not implementing this correctly. They resorted to the method described above, a constructor-supplied, hard-coded value. For chains that do indeed not inform correctly about their chain ID, this is a reasonable solution. However, for the reasons outlined above, we still recommend querying the chain ID via `block.chainId` for chains that do support that — which should be the vast majority — and using the fallback mechanism only when necessary.
Vulnerability: TribalChief - A wrong user.rewardDebt value is calculated during the withdrawFromDeposit function call
Severity: high

Description:
When withdrawing a single deposit, the reward debt is updated:\\n```\\nuint128 virtualAmountDelta = uint128( ( amount \\* poolDeposit.multiplier ) / SCALE\\_FACTOR );\\n\\n// Effects\\npoolDeposit.amount -= amount;\\nuser.rewardDebt = user.rewardDebt - toSigned128(user.virtualAmount \\* pool.accTribePerShare) / toSigned128(ACC\\_TRIBE\\_PRECISION);\\nuser.virtualAmount -= virtualAmountDelta;\\npool.virtualTotalSupply -= virtualAmountDelta;\\n```\\n\\nInstead of the `user.virtualAmount` in reward debt calculation, the `virtualAmountDelta` should be used. Because of that bug, the reward debt is much lower than it would be, which means that the reward itself will be much larger during the harvest. By making multiple deposit-withdraw actions, any user can steal all the Tribe tokens from the contract.

Impact:

Recommended mitigation:
Use the `virtualAmountDelta` instead of the `user.virtualAmount`.
Vulnerability: TribalChief - Unlocking users' funds in a pool where a multiplier has been increased is missing
Severity: medium

Description:
When a user deposits funds to a pool, the current multiplier in use for this pool is being stored locally for this deposit. The value that is used later in a withdrawal operation is the local one, and not the one that is changing when a `governor` calls `governorAddPoolMultiplier`. It means that a decrease in the multiplier value for a given pool does not affect users that already deposited, but an increase does. Users that had already deposited should have the right to withdraw their funds when the multiplier for their pool increases by the `governor`.\\n```\\nfunction governorAddPoolMultiplier(\\n    uint256 \\_pid,\\n    uint64 lockLength,\\n    uint64 newRewardsMultiplier\\n) external onlyGovernor {\\n    PoolInfo storage pool = poolInfo[\\_pid];\\n    uint256 currentMultiplier = rewardMultipliers[\\_pid][lockLength];\\n    // if the new multplier is less than the current multiplier,\\n    // then, you need to unlock the pool to allow users to withdraw\\n    if (newRewardsMultiplier < currentMultiplier) {\\n        pool.unlocked = true;\\n    }\\n    rewardMultipliers[\\_pid][lockLength] = newRewardsMultiplier;\\n\\n    emit LogPoolMultiplier(\\_pid, lockLength, newRewardsMultiplier);\\n}\\n```\\n

Impact:

Recommended mitigation:
Replace the `<` operator with `>` in `TribalChief` line 152.
Vulnerability: TribalChief - Unsafe down-castings
Severity: medium

Description:
`TribalChief` consists of multiple unsafe down-casting operations. While the usage of types that can be packed into a single storage slot is more gas efficient, it may introduce hidden risks in some cases that can lead to loss of funds.\\nVarious instances in `TribalChief`, including (but not necessarily only) :\\n```\\nuser.rewardDebt = int128(user.virtualAmount \\* pool.accTribePerShare) / toSigned128(ACC\\_TRIBE\\_PRECISION);\\n```\\n\\n```\\npool.accTribePerShare = uint128(pool.accTribePerShare + ((tribeReward \\* ACC\\_TRIBE\\_PRECISION) / virtualSupply));\\n```\\n\\n```\\nuserPoolData.rewardDebt += int128(virtualAmountDelta \\* pool.accTribePerShare) / toSigned128(ACC\\_TRIBE\\_PRECISION);\\n```\\n

Impact:

Recommended mitigation:
Given the time constraints of this audit engagement, we could not verify the implications and provide mitigation actions for each of the unsafe down-castings operations. However, we do recommend to either use numeric types that use 256 bits, or to add proper validation checks and handle these scenarios to avoid silent over/under-flow errors. Keep in mind that reverting these scenarios can sometimes lead to a denial of service, which might be harmful in some cases.
Vulnerability: TribalChief - Governor decrease of pool's allocation point should unlock depositors' funds
Severity: low

Description:
When the `TribalChief` governor decreases the ratio between the allocation point (PoolInfo.allocPoint) and the total allocation point (totalAllocPoint) for a specific pool (either be directly decreasing `PoolInfo.allocPoint` of a given pool, or by increasing this value for other pools), the total reward for this pool is decreased as well. Depositors should be able to withdraw their funds immediately after this kind of change.\\n```\\nfunction set(uint256 \\_pid, uint128 \\_allocPoint, IRewarder \\_rewarder, bool overwrite) public onlyGovernor {\\n    totalAllocPoint = (totalAllocPoint - poolInfo[\\_pid].allocPoint) + \\_allocPoint;\\n    poolInfo[\\_pid].allocPoint = \\_allocPoint.toUint64();\\n\\n    if (overwrite) {\\n        rewarder[\\_pid] = \\_rewarder;\\n    }\\n\\n    emit LogSetPool(\\_pid, \\_allocPoint, overwrite ? \\_rewarder : rewarder[\\_pid], overwrite);\\n}\\n```\\n

Impact:

Recommended mitigation:
Make sure that depositors' funds are unlocked for pools that affected negatively by calling `TribalChief.set`.
Vulnerability: TribalChief - new block reward retrospectively takes effect on pools that have not been updated recently
Severity: low

Description:
When the governor updates the block reward `tribalChiefTribePerBlock` the new reward is applied for the outstanding duration of blocks in `updatePool`. This means, if a pool hasn't updated in a while (unlikely) the new block reward is retrospectively applied to the pending duration instead of starting from when the block reward changed.\\nrewards calculation\\n```\\nif (virtualSupply > 0) {\\n    uint256 blocks = block.number - pool.lastRewardBlock;\\n    uint256 tribeReward = (blocks \\* tribePerBlock() \\* pool.allocPoint) / totalAllocPoint;\\n    pool.accTribePerShare = uint128(pool.accTribePerShare + ((tribeReward \\* ACC\\_TRIBE\\_PRECISION) / virtualSupply));\\n}\\n```\\n\\nupdating the block reward\\n```\\n/// @notice Allows governor to change the amount of tribe per block\\n/// @param newBlockReward The new amount of tribe per block to distribute\\nfunction updateBlockReward(uint256 newBlockReward) external onlyGovernor {\\n    tribalChiefTribePerBlock = newBlockReward;\\n    emit NewTribePerBlock(newBlockReward);\\n}\\n```\\n

Impact:

Recommended mitigation:
It is recommended to update pools before changing the block reward. Document and make users aware that the new reward is applied to the outstanding duration when calling `updatePool`.
Vulnerability: TribalChief - resetRewards should emit an event
Severity: low

Description:
The method `resetRewards` silently resets a pools tribe allocation.\\n```\\n/// @notice Reset the given pool's TRIBE allocation to 0 and unlock the pool. Can only be called by the governor or guardian.\\n/// @param \\_pid The index of the pool. See `poolInfo`. \\nfunction resetRewards(uint256 \\_pid) public onlyGuardianOrGovernor {\\n    // set the pool's allocation points to zero\\n    totalAllocPoint = (totalAllocPoint - poolInfo[\\_pid].allocPoint);\\n    poolInfo[\\_pid].allocPoint = 0;\\n   \\n    // unlock all staked tokens in the pool\\n    poolInfo[\\_pid].unlocked = true;\\n\\n    // erase any IRewarder mapping\\n    rewarder[\\_pid] = IRewarder(address(0));\\n}\\n```\\n

Impact:

Recommended mitigation:
For transparency and to create an easily accessible audit trail of events consider emitting an event when resetting a pools allocation.
Vulnerability: TribalChief - Unlocking users' funds in a pool where a multiplier has been increased is missing
Severity: medium

Description:
When a user deposits funds to a pool, the current multiplier in use for this pool is being stored locally for this deposit. The value that is used later in a withdrawal operation is the local one, and not the one that is changing when a `governor` calls `governorAddPoolMultiplier`. It means that a decrease in the multiplier value for a given pool does not affect users that already deposited, but an increase does. Users that had already deposited should have the right to withdraw their funds when the multiplier for their pool increases by the `governor`.\\n```\\nfunction governorAddPoolMultiplier(\\n uint256 \\_pid,\\n uint64 lockLength,\\n uint64 newRewardsMultiplier\\n) external onlyGovernor {\\n PoolInfo storage pool = poolInfo[\\_pid];\\n uint256 currentMultiplier = rewardMultipliers[\\_pid][lockLength];\\n // if the new multplier is less than the current multiplier,\\n // then, you need to unlock the pool to allow users to withdraw\\n if (newRewardsMultiplier < currentMultiplier) {\\n pool.unlocked = true;\\n }\\n rewardMultipliers[\\_pid][lockLength] = newRewardsMultiplier;\\n\\n emit LogPoolMultiplier(\\_pid, lockLength, newRewardsMultiplier);\\n}\\n```\\n

Impact:

Recommended mitigation:
Replace the `<` operator with `>` in `TribalChief` line 152.
Vulnerability: TribalChief - Governor decrease of pool's allocation point should unlock depositors' funds
Severity: low

Description:
When the `TribalChief` governor decreases the ratio between the allocation point (PoolInfo.allocPoint) and the total allocation point (totalAllocPoint) for a specific pool (either be directly decreasing `PoolInfo.allocPoint` of a given pool, or by increasing this value for other pools), the total reward for this pool is decreased as well. Depositors should be able to withdraw their funds immediately after this kind of change.\\n```\\nfunction set(uint256 \\_pid, uint128 \\_allocPoint, IRewarder \\_rewarder, bool overwrite) public onlyGovernor {\\n totalAllocPoint = (totalAllocPoint - poolInfo[\\_pid].allocPoint) + \\_allocPoint;\\n poolInfo[\\_pid].allocPoint = \\_allocPoint.toUint64();\\n\\n if (overwrite) {\\n rewarder[\\_pid] = \\_rewarder;\\n }\\n\\n emit LogSetPool(\\_pid, \\_allocPoint, overwrite ? \\_rewarder : rewarder[\\_pid], overwrite);\\n}\\n```\\n

Impact:

Recommended mitigation:
Make sure that depositors' funds are unlocked for pools that affected negatively by calling `TribalChief.set`.
Vulnerability: IdleCDO._deposit() allows re-entrancy from hookable tokens.
Severity: medium

Description:
The function `IdleCDO._deposit()` updates the system's internal accounting and mints shares to the caller, then transfers the deposited funds from the user. Some token standards, such as ERC777, allow a callback to the source of the funds before the balances are updated in `transferFrom()`. This callback could be used to re-enter the protocol while already holding the minted tranche tokens and at a point where the system accounting reflects a receipt of funds that has not yet occurred.\\nWhile an attacker could not interact with `IdleCDO.withdraw()` within this callback because of the `_checkSameTx()` restriction, they would be able to interact with the rest of the protocol.\\n```\\nfunction \\_deposit(uint256 \\_amount, address \\_tranche) internal returns (uint256 \\_minted) {\\n  // check that we are not depositing more than the contract available limit\\n  \\_guarded(\\_amount);\\n  // set \\_lastCallerBlock hash\\n  \\_updateCallerBlock();\\n  // check if strategyPrice decreased\\n  \\_checkDefault();\\n  // interest accrued since last depositXX/withdrawXX/harvest is splitted between AA and BB\\n  // according to trancheAPRSplitRatio. NAVs of AA and BB are updated and tranche\\n  // prices adjusted accordingly\\n  \\_updateAccounting();\\n  // mint tranche tokens according to the current tranche price\\n  \\_minted = \\_mintShares(\\_amount, msg.sender, \\_tranche);\\n  // get underlyings from sender\\n  IERC20Detailed(token).safeTransferFrom(msg.sender, address(this), \\_amount);\\n}\\n```\\n

Impact:

Recommended mitigation:
Move the `transferFrom()` action in `_deposit()` to immediately after `_updateCallerBlock()`.
Vulnerability: IdleCDO.virtualPrice() and _updatePrices() yield different prices in a number of cases
Severity: medium

Description:
The function `IdleCDO.virtualPrice()` is used to determine the current price of a tranche. Similarly, `IdleCDO._updatePrices()` is used to store the latest price of a tranche, as well as update other parts of the system accounting. There are a number of cases where the prices yielded by these two functions differ. While these are primarily corner cases that are not obviously exploitable in practice, potential violations of key accounting invariants should always be considered serious.\\nAdditionally, the use of two separate implementations of the same calculation suggest the potential for more undiscovered discrepancies, possibly of higher consequence.\\nAs an example, in `_updatePrices()` the precision loss from splitting the strategy returns favors BB tranche holders. In `virtualPrice()` both branches of the price calculation incur precision loss, favoring the `IdleCDO` contract itself.\\n`_updatePrices()`\\n```\\nif (BBTotSupply == 0) {\\n  // if there are no BB holders, all gain to AA\\n  AAGain = gain;\\n} else if (AATotSupply == 0) {\\n  // if there are no AA holders, all gain to BB\\n  BBGain = gain;\\n} else {\\n  // split the gain between AA and BB holders according to trancheAPRSplitRatio\\n  AAGain = gain \\* trancheAPRSplitRatio / FULL\\_ALLOC;\\n  BBGain = gain - AAGain;\\n}\\n```\\n\\n`virtualPrice()`\\n```\\nif (\\_tranche == AATranche) {\\n  // calculate gain for AA tranche\\n  // trancheGain (AAGain) = gain \\* trancheAPRSplitRatio / FULL\\_ALLOC;\\n  trancheNAV = lastNAVAA + (gain \\* \\_trancheAPRSplitRatio / FULL\\_ALLOC);\\n} else {\\n  // calculate gain for BB tranche\\n  // trancheGain (BBGain) = gain \\* (FULL\\_ALLOC - trancheAPRSplitRatio) / FULL\\_ALLOC;\\n  trancheNAV = lastNAVBB + (gain \\* (FULL\\_ALLOC - \\_trancheAPRSplitRatio) / FULL\\_ALLOC);\\n}\\n```\\n

Impact:

Recommended mitigation:
Implement a single method that determines the current price for a tranche, and use this same implementation anywhere the price is needed.