msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Tokens received from Curve's `remove_liquidity()` should be added to the assets list even if `_min_amounts` are set to `0`
Severity: high

Description:
Curve controller's `canRemoveLiquidity()` should return all the underlying tokens as `tokensIn` rather than only the tokens with `minAmount > 0`.\\n```\\nfunction canRemoveLiquidity(address target, bytes calldata data)\\n    internal\\n    view\\n    returns (bool, address[] memory, address[] memory)\\n{\\n    (,uint256[2] memory amounts) = abi.decode(\\n        data[4:],\\n        (uint256, uint256[2])\\n    );\\n\\n    address[] memory tokensOut = new address[](1);\\n    tokensOut[0] = target;\\n\\n    uint i; uint j;\\n    address[] memory tokensIn = new address[](2);\\n    while(i < 2) {\\n        if(amounts[i] > 0)\\n            tokensIn[j++] = IStableSwapPool(target).coins(i);\\n        unchecked { ++i; }\\n    }\\n    assembly { mstore(tokensIn, j) }\\n\\n    return (true, tokensIn, tokensOut);\\n}\\n```\\n\\nThe `amounts` in Curve controller's `canRemoveLiquidity()` represent the "Minimum `amounts` of underlying coins to receive", which is used for slippage control.\\nAt L144-149, only the tokens that specified a minAmount > 0 will be added to the `tokensIn` list, which will later be added to the account's assets list.\\nWe believe this is wrong as regardless of the minAmount `remove_liquidity()` will always receive all the underlying tokens.\\nTherefore, it should not check and only add the token when it's minAmount > 0.

Impact:
When the user set `_min_amounts` = `0` while removing liquidity from `Curve` and the withdrawn tokens are not in the account's assets list already, the user may get liquidated sooner than expected as `RiskEngine.sol#_getBalance()` only counts in the assets in the assets list.

Recommended mitigation:
`canRemoveLiquidity()` can be changed to:\\n```\\nfunction canRemoveLiquidity(address target, bytes calldata data)\\n    internal\\n    view\\n    returns (bool, address[] memory, address[] memory)\\n{\\n    address[] memory tokensOut = new address[](1);\\n    tokensOut[0] = target;\\n\\n    address[] memory tokensIn = new address[](2);\\n    tokensIn[0] = IStableSwapPool(target).coins(0);\\n    tokensIn[1] = IStableSwapPool(target).coins(1);\\n    return (true, tokensIn, tokensOut);\\n}\\n```\\n\\nSentiment Team\\nFixed as recommended. PR here.\\nLead Senior Watson\\nConfirmed fix.
Vulnerability: Missing revert keyword
Severity: medium

Description:
Missing `revert` keyword in `functionDelegateCall` bypasses an intended safety check, allowing the function to fail silently.\\nIn the helper function `functionDelegateCall`, there is a check to confirm that the target being called is a contract.\\n```\\nif (!isContract(target)) Errors.AddressNotContract;\\n```\\n\\nHowever, there is a typo in the check that is missing the `revert` keyword.\\nAs a result, non-contracts can be submitted as targets, which will cause the delegatecall below to return success (because EVM treats no code as STOP opcode), even though it doesn't do anything.\\n```\\n(bool success, ) = target.delegatecall(data);\\nrequire(success, "CALL_FAILED");\\n```\\n

Impact:
The code doesn't accomplish its intended goal of checking to confirm that only contracts are passed as targets, so delegatecalls can silently fail.

Recommended mitigation:
Issue Missing revert keyword\\nAdd missing `revert` keyword to L70 of Helpers.sol.\\n```\\nif (!isContract(target)) revert Errors.AddressNotContract;\\n```\\n\\nSentiment Team\\nFixed as recommended. PR here.\\nLead Senior Watson\\nConfirmed fix.
Vulnerability: No Limit for Minting Amount
Severity: high

Description:
In token contract `FiatTokenV1`, there is no limit set for amount of tokens can be minted, as a result, the minter can mint unlimited tokens, disrupting the token supply and value.\\n```\\nfunction mint(address to, uint256 amount) public onlyRole(MINTER\\_ROLE) {\\n \\_mint(to, amount);\\n}\\n```\\n

Impact:

Recommended mitigation:
Add a limit for the number of tokens the minter can mint.
Vulnerability: Private Key Is Exposed in the Deployment and Upgrade Script
Severity: high

Description:
In the contract deploying and upgrading script, private key is used to broadcast the transaction, this would expose private key of the deployer and upgrader account on the machine running the script, therefore compromising these accounts.\\n```\\nuint256 deployerPrivateKey = vm.envUint("PRIVATE\\_KEY");\\n```\\n\\n```\\nuint256 deployerPrivateKey = vm.envUint("PRIVATE\\_KEY");\\nvm.startBroadcast(deployerPrivateKey);\\n```\\n

Impact:

Recommended mitigation:
Have Forge sending a raw transaction to the cold wallet of the account, the wallet signs the transaction then return the signed transactions to Forge and broadcaster. Alternatively use different wallet for deployment and upgrade and stop using the wallet after the script is complete
Vulnerability: Critical Functions Are Public and Without Access Control
Severity: medium

Description:
Critical functions in RescuableV1(rescue) and `BlacklistableV1` `(blacklist,unblacklist)` are public and unauthenticated, any one can call these function to steal funds and blacklist other accounts. Although the child contract `FiatTokenV1` has authenticated the overridden functions and protected them from public access, other contracts inheriting `RescuableV1` and `BlacklistableV1` might have risks from the unauthenticated public functions\\n```\\nfunction rescue(IERC20 token, address to, uint256 amount) public virtual {\\n```\\n\\n```\\nfunction blacklist(address account) public virtual {\\n \\_blacklisted[account] = true;\\n emit Blacklisted(account);\\n}\\n\\n/\\*\\*\\n \\* @dev Removes account from blacklist\\n \\* @param account The address to remove from the blacklist\\n \\*/\\nfunction unBlacklist(address account) public virtual {\\n \\_blacklisted[account] = false;\\n emit UnBlacklisted(account);\\n}\\n```\\n

Impact:

Recommended mitigation:
Make these functions internal and in the child contract add correspondent public function with authentication to call the inherited functions
Vulnerability: Unecessary Parent Contracts
Severity: low

Description:
Contract `BlacklistableV1` and `RescuableV1` extends `ContextUpgradeable` and `ERC20Upgradeable,` which are not used in any of contract functions and are already inherited by the child contract `FiatTokenV1`.\\n```\\nabstract contract BlacklistableV1 is Initializable, ContextUpgradeable, ERC20Upgradeable {\\n```\\n\\n```\\nabstract contract RescuableV1 is Initializable, ContextUpgradeable, ERC20Upgradeable {\\n```\\n\\n```\\ncontract FiatTokenV1 is\\n Initializable,\\n ERC20Upgradeable,\\n ERC20PausableUpgradeable,\\n ERC20BurnableUpgradeable,\\n AccessControlUpgradeable,\\n ERC20PermitUpgradeable,\\n UUPSUpgradeable,\\n BlacklistableV1,\\n RescuableV1\\n{\\n```\\n

Impact:

Recommended mitigation:
Remove the unnecessary parent contracts
Vulnerability: Redundant _disableInitializers in Constructor
Severity: low

Description:
Contract `FiatTokenV1` inherits from contracts `BlacklistableV1` and `RescuableV1`, the two parent contracts both have `_disableInitializers` in their constructors to prevent uninitialized contract being initialized by the attackers, it's not necessary to have `_disableInitializers` in the FiatTokenV1's constructor, which is redundant and inefficient.\\n```\\nconstructor() {\\n \\_disableInitializers();\\n}\\n```\\n

Impact:

Recommended mitigation:
Remove constructor from `FiatTokenV1`
Vulnerability: Incorrect Final Block Number Can Be Finalized
Severity: high

Description:
In the data finalization function `finalizeCompressedBlocksWithProof`, `finalizationData.finalBlockNumber` is the final block number of the compressed block data to be finalized. However, there is no check in the contract or the prover to ensure `finalBlockNumber` is correct when there is no new data submitted in the finalization, i.e., `submissionDataLength == 0` . The prover can submit an incorrect final block number and, as a result, the finalized block number (currentL2BlockNumber) would be incorrect. Consequently, the prover can skip block data in the finalization.\\n```\\ncurrentL2BlockNumber = \\_finalizationData.finalBlockNumber;\\n```\\n\\n```\\nif (stateRootHashes[currentL2BlockNumber] != \\_finalizationData.parentStateRootHash) {\\n revert StartingRootHashDoesNotMatch();\\n}\\n```\\n

Impact:

Recommended mitigation:
Resolution\\nfixed by adding a recommended check of `finalBlockNumber` matching the last block number of the submitted data in `_finalizeCompressedBlocks` and a check in the prover and adding `finalBlockNumber` and `lastFinalizedBlockNumber` in the public input of the verifier in the finalization in PR-24\\nIn `_finalizeCompressedBlocks`, check if `finalBlockNumber` is equal to the last block number (finalBlockInData) of the last item of submitted block data. Another solution is to have the prover show that finalBlockNumberis correct in the proof by providing the last finalized block number (lastFinalizedBlockNumber) and verify it by adding `finalBlockNumber` and `lastFinalizedBlockNumber` in the public input of the verifier in the finalization.
Vulnerability: Finalization Fails for the First Batch of Data Submitted After Migration to the Updated Contract
Severity: high

Description:
When submitting the initial batch of compressed block data after the contract update, the finalization will fail.\\nIn function `_finalizeCompressedBlocks`, `startingDataParentHash = dataParents[_finalizationData.dataHashes[0]]` will be empty and, therefore, `startingParentFinalStateRootHash = dataFinalStateRootHashes[startingDataParentHash]` will be empty too. The check `_finalizationData.parentStateRootHash == stateRootHashes[currentL2BlockNumber]` requires `_finalizationData.parentStateRootHash == _initialStateRootHash`, which is not empty, so the condition `startingParentFinalStateRootHash != _finalizationData.parentStateRootHash` is true, and we revert with the error FinalStateRootHashDoesNotMatch:\\n```\\nif (stateRootHashes[currentL2BlockNumber] != \\_finalizationData.parentStateRootHash) {\\n revert StartingRootHashDoesNotMatch();\\n}\\n```\\n\\n```\\nif (finalizationDataDataHashesLength != 0) {\\n bytes32 startingDataParentHash = dataParents[\\_finalizationData.dataHashes[0]];\\n\\n if (startingDataParentHash != \\_finalizationData.dataParentHash) {\\n revert ParentHashesDoesNotMatch(startingDataParentHash, \\_finalizationData.dataParentHash);\\n }\\n\\n bytes32 startingParentFinalStateRootHash = dataFinalStateRootHashes[startingDataParentHash];\\n\\n if (startingParentFinalStateRootHash != \\_finalizationData.parentStateRootHash) {\\n revert FinalStateRootHashDoesNotMatch(startingParentFinalStateRootHash, \\_finalizationData.parentStateRootHash);\\n }\\n```\\n

Impact:

Recommended mitigation:
Set the correct initial value for `dataFinalStateRootHashes` for the initial batch of compressed block data.
Vulnerability: Prover Can Censor L2 → L1 Messages  Partially Addressed
Severity: high

Description:
In L2 → L1 messaging, messages are grouped and added to a Merkle tree by the prover. During finalization, the operator (coordinator) submits the Merkle root to L1, and the user SDK rebuilds the tree to which the message is added and generates a Merkle proof to claim against the root finalized on L1. However, the prover can skip messages when building the tree. Consequently, the user cannot claim the skipped message, which might result in frozen funds.\\nCurrently, the prover is a single entity owned by Linea. Hence, this would require malice or negligence on Linea's part.\\n```\\n\\_addL2MerkleRoots(\\_finalizationData.l2MerkleRoots, \\_finalizationData.l2MerkleTreesDepth);\\n\\_anchorL2MessagingBlocks(\\_finalizationData.l2MessagingBlocksOffsets, lastFinalizedBlock);\\n```\\n

Impact:

Recommended mitigation:
Decentralize the prover, so messages can be included by different provers.
Vulnerability: Malicious Operator Might Finalize Data From a Forked Linea Chain
Severity: high

Description:
A malicious operator (prover) can add and finalize block data from a forked Linea chain, so transactions on the forked chain can be finalized, causing a loss of funds from the L1.\\nFor example, a malicious operator forks the canonical chain, then the attacker sends the forked chain Ether to L1 with `sendMessage` from the forked L2. The operator then submits the block data to L1 and finalizes it with `finalizeCompressedBlocksWithProof`, using the finalization data and proof from the forked chain. (Note that the malicious prover sets the forked chain `chainId` in its circuit as a constant.) The L1 contract (LineaRollup) doesn't know whether the data and the proof are from the canonical L2 or the forked one. The finalization succeeds, and the attacker can claim the bridged forked chain Ether and steal funds from L1.\\nAs there is currently only one operator and it is owned by the Linea team, this kind of attack is unlikely to happen. However, when the operator and the coordinator are decentralized, the likelihood of this attack increases.\\n```\\nuint256 publicInput = uint256(\\n keccak256(\\n abi.encode(\\n shnarf,\\n \\_finalizationData.parentStateRootHash,\\n \\_finalizationData.lastFinalizedTimestamp,\\n \\_finalizationData.finalBlockNumber,\\n \\_finalizationData.finalTimestamp,\\n \\_finalizationData.l1RollingHash,\\n \\_finalizationData.l1RollingHashMessageNumber,\\n keccak256(abi.encodePacked(\\_finalizationData.l2MerkleRoots))\\n )\\n```\\n\\n```\\n\\_addL2MerkleRoots(\\_finalizationData.l2MerkleRoots, \\_finalizationData.l2MerkleTreesDepth);\\n```\\n

Impact:

Recommended mitigation:
Add `chainId` in the `FinalizationData` as a public input of the verifier function `_verifyProof`, so the proof from the forked Linea chain will not pass the verification because the `chainId` won't match.
Vulnerability: Empty Compressed Data Allowed in Data Submission
Severity: medium

Description:
In `submitData`, the coordinator can submit data with empty `compressedData` in `_submissionData`, which is not a desired purpose of this function and may cause undefined system behavior.\\n```\\nfunction submitData(\\n SubmissionData calldata \\_submissionData\\n)\\n external\\n whenTypeNotPaused(PROVING\\_SYSTEM\\_PAUSE\\_TYPE)\\n whenTypeNotPaused(GENERAL\\_PAUSE\\_TYPE)\\n onlyRole(OPERATOR\\_ROLE)\\n{\\n \\_submitData(\\_submissionData);\\n}\\n```\\n

Impact:

Recommended mitigation:
Add a check to disallow data submission with empty `compressedData`.
Vulnerability: Limiting the Price in the buy and onTokenTransfer Functions
Severity: medium

Description:
When an investor tries to `buy` the tokens in the `Crowdinvesting` contract, the `buy` function does not allow to limit the amount of tokens that can be spent during this particular transaction:\\n```\\nfunction buy(uint256 \\_amount, address \\_tokenReceiver) public whenNotPaused nonReentrant {\\n // rounding up to the next whole number. Investor is charged up to one currency bit more in case of a fractional currency bit.\\n uint256 currencyAmount = Math.ceilDiv(\\_amount \\* getPrice(), 10 \\*\\* token.decimals());\\n```\\n\\nThe owner of the price oracle can front-run the transaction and twist the price.\\nOf course, the buyer can try to regulate that limit with the token allowance, but there may be some exceptions. Sometimes, users want to give more allowance and buy in multiple transactions over time. Or even give an infinite allowance (not recommended) out of convenience.\\nThe same issue can be found in the `onTokenTransfer` function. This function works differently because the amount of currency is fixed, and the amount of tokens minted is undefined. Because of that, limiting the allowance won't help, so the user doesn't know how many tokens can be bought.

Impact:

Recommended mitigation:
It's recommended to explicitly limit the amount of tokens that can be transferred from the buyer for the `buy` function. And allow users to define a minimal amount of tokens bought in the `onTokenTransfer` function.
Vulnerability: Potential Re-Entrancy Attack in the Crowdinvesting Contract
Severity: low

Description:
The attack requires a set of pre-requisites:\\nThe currency token should have a re-entrancy opportunity inside the token transfer.\\nThe re-entrancy can be done on a token transfer from the `_msgSender()` to the `feeCollector`, so there are not a lot of attackers who can potentially execute it.\\nThe owner should be involved in the attack, so it's most likely an attack by the owner.\\n```\\nfunction buy(uint256 \\_amount, address \\_tokenReceiver) public whenNotPaused nonReentrant {\\n // rounding up to the next whole number. Investor is charged up to one currency bit more in case of a fractional currency bit.\\n uint256 currencyAmount = Math.ceilDiv(\\_amount \\* getPrice(), 10 \\*\\* token.decimals());\\n\\n (uint256 fee, address feeCollector) = \\_getFeeAndFeeReceiver(currencyAmount);\\n if (fee != 0) {\\n currency.safeTransferFrom(\\_msgSender(), feeCollector, fee);\\n }\\n\\n currency.safeTransferFrom(\\_msgSender(), currencyReceiver, currencyAmount - fee);\\n \\_checkAndDeliver(\\_amount, \\_tokenReceiver);\\n\\n emit TokensBought(\\_msgSender(), \\_amount, currencyAmount);\\n}\\n```\\n\\nSo on the token transfer to the `feeCollector` above, the `currency` parameter can be changed by the `owner`. And the following token transfer (currency.safeTransferFrom(_msgSender(), currencyReceiver, currencyAmount - fee);) will be made in a different `currency`.\\nA possible scenario of the attack could look as follows:\\nMalicious owner sells tokens for a valuable currency. People are placing allowance for the tokens.\\nThe owner changes the currency to a new one with a much lower price and re-entrancy during transfer.\\nWhen a victim wants to buy tokens, the owner reenters on fee transfer and returns the old currency.\\nThe victim transfers the updated currency that is more expensive.

Impact:

Recommended mitigation:
Save the currency in memory at the beginning of the function and use it further.
Vulnerability: Lack of Validation of PrivateOffer Initialization Parameters
Severity: low

Description:
The `PrivateOffer` contract allows to create a customised deal for a specific investor. The `initialize()` function receives parameters to set up the `PrivateOffer` accordingly.\\nThe following parameters lack of validation during initialization:\\n`tokenAmount`\\n`token`\\n`currency`\\n`tokenAmount`\\n```\\nuint256 currencyAmount = Math.ceilDiv(\\n \\_arguments.tokenAmount \\* \\_arguments.tokenPrice,\\n 10 \\*\\* \\_arguments.token.decimals()\\n);\\n```\\n\\n`tokenAmount` is not validated at all. It should be verified to be greater than zero.\\n`token`\\n`token` is not validated at all. It should be verified to be different than zero address.\\n`currency`\\n`currency` is not validated at all. The documentation mentions a restricted list of supported currencies. It should be enforced by checking this parameter against a whitelist of `currency` addresses.

Impact:

Recommended mitigation:
Enhance the validation of the following parameters: `tokenAmount`, `token`, `currency`.
Vulnerability: Lack of Validation of Crowdinvesting Initialization Parameters
Severity: low

Description:
The `Crowdinvesting` contract allows everyone who meets the requirements to buy tokens at a fixed price. The `initialize()` function receives parameters to set up the `Crowdinvesting` accordingly.\\nThe following parameters lack of validation during initialization:\\n`tokenPrice`\\n`minAmountPerBuyer`\\n`lastBuyDate`\\n`currency`\\n`tokenPrice`\\n```\\nrequire(\\_arguments.tokenPrice != 0, "\\_tokenPrice needs to be a non-zero amount");\\n```\\n\\n`tokenPrice` is checked to be different to zero. It should be verified to be in between `priceMin` and `priceMax` when these parameters are provided.\\n`minAmountPerBuyer`\\n```\\nrequire(\\n \\_arguments.minAmountPerBuyer <= \\_arguments.maxAmountPerBuyer,\\n "\\_minAmountPerBuyer needs to be smaller or equal to \\_maxAmountPerBuyer"\\n);\\n```\\n\\n`minAmountPerBuyer` is checked to be below or equal to `maxAmountPerBuyer`. It should be verified to not be zero.\\n`lastBuyDate`\\n```\\nlastBuyDate = \\_arguments.lastBuyDate;\\n```\\n\\n`lastBuyDate` is not validated at all. It should be verified to be greater than the current `block.timestamp`. Currently, a `Crowdinvesting` contract with `lastBuyDate` parameter set to a value (different than zero) below `block.timestamp` will not be able to sell any token.\\n```\\nfunction \\_checkAndDeliver(uint256 \\_amount, address \\_tokenReceiver) internal {\\n require(tokensSold + \\_amount <= maxAmountOfTokenToBeSold, "Not enough tokens to sell left");\\n require(tokensBought[\\_tokenReceiver] + \\_amount >= minAmountPerBuyer, "Buyer needs to buy at least minAmount");\\n require(\\n tokensBought[\\_tokenReceiver] + \\_amount <= maxAmountPerBuyer,\\n "Total amount of bought tokens needs to be lower than or equal to maxAmount"\\n );\\n\\n if (lastBuyDate != 0 && block.timestamp > lastBuyDate) {\\n revert("Last buy date has passed: not selling tokens anymore.");\\n }\\n\\n tokensSold += \\_amount;\\n tokensBought[\\_tokenReceiver] += \\_amount;\\n\\n token.mint(\\_tokenReceiver, \\_amount);\\n}\\n```\\n\\n`currency`\\n```\\nrequire(address(\\_arguments.currency) != address(0), "currency can not be zero address");\\n```\\n\\n`currency` is checked to be different than zero. The documentation mentions a restricted list of supported currencies. It should be enforced by checking this parameter against a whitelist of `currency` addresses.

Impact:

Recommended mitigation:
Enhance the validation of the following parameters: `tokenPrice`, `tokenPrice`, `lastBuyDate`, `currency`.
Vulnerability: RocketDAOProtocolProposal._propose() Should Revert if _blockNumber > block.number
Severity: medium

Description:
Currently, the `RocketDAOProtocolProposal._propose()` function does not account for scenarios where `_blockNumber` is greater than `block.number`. This is a critical oversight, as voting power cannot be determined for future block numbers.\\n```\\nfunction _propose(string memory _proposalMessage, uint256 _blockNumber, uint256 _totalVotingPower, bytes calldata _payload) internal returns (uint256) {\\n```\\n

Impact:

Recommended mitigation:
We recommend updating the function to revert on transactions where `_blockNumber` exceeds `block.number`. This will prevent the creation of proposals with undefined voting power and maintain the integrity of the voting process.
Vulnerability: Unused Parameter and Improper Parameter Sanitization in RocketNetworkVoting.calculateVotingPower()
Severity: low

Description:
The `matchedETH` parameter in `RocketNetworkVoting.calculateVotingPower()` is unused.\\n```\\n// Get contracts\\nRocketDAOProtocolSettingsNodeInterface rocketDAOProtocolSettingsNode = RocketDAOProtocolSettingsNodeInterface(getContractAddress("rocketDAOProtocolSettingsNode"));\\n```\\n\\nAdditionally, the `_block` parameter is not sanitized. Thus, if calling the function with a block number `_block` where `_block >= block.number`, the call will revert because of a division-by-zero error. Indeed, `rocketNetworkSnapshots.lookupRecent` will return a `rplPrice` of zero since the checkpoint does not exist. Consequently, the function `calculateVotingPower` will revert when computing the `maximumStake`.\\n```\\nkey = keccak256(abi.encodePacked("rpl.staked.node.amount", _nodeAddress));\\nuint256 rplStake = uint256(rocketNetworkSnapshots.lookupRecent(key, uint32(_block), 5));\\n\\nreturn calculateVotingPower(rplStake, ethMatched, ethProvided, rplPrice);\\n```\\n\\n```\\nuint256 maximumStake = providedETH * maximumStakePercent / rplPrice;\\n```\\n

Impact:

Recommended mitigation:
We recommend removing the unused parameter to enhance code clarity. The presence of unused parameters can lead to potential confusion for future developers. Additionally, we recommend ensuring that the snapshotted `rplPrice` value exists before it is used to compute the `maximumStake` value.