msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Bridge Token Would Be Locked and Cannot Bridge to Native Token
Severity: high

Description:
If the bridge token B of a native token A is already deployed and `confirmDeployment` is called on the other layer and `setDeployed` sets A's `nativeToBridgedToken` value to `DEPLOYED_STATUS`. The bridge token B cannot bridge to native token A in `completeBridging` function, because A's `nativeToBridgedToken` value is not `NATIVE_STATUS`, as a result the native token won't be transferred to the receiver. User's bridge token will be locked in the original layer\\n```\\nif (nativeMappingValue == NATIVE\\_STATUS) {\\n // Token is native on the local chain\\n IERC20(\\_nativeToken).safeTransfer(\\_recipient, \\_amount);\\n} else {\\n bridgedToken = nativeMappingValue;\\n if (nativeMappingValue == EMPTY) {\\n // New token\\n bridgedToken = deployBridgedToken(\\_nativeToken, \\_tokenMetadata);\\n bridgedToNativeToken[bridgedToken] = \\_nativeToken;\\n nativeToBridgedToken[\\_nativeToken] = bridgedToken;\\n }\\n BridgedToken(bridgedToken).mint(\\_recipient, \\_amount);\\n}\\n```\\n\\n```\\nfunction setDeployed(address[] memory \\_nativeTokens) external onlyMessagingService fromRemoteTokenBridge {\\n address nativeToken;\\n for (uint256 i; i < \\_nativeTokens.length; i++) {\\n nativeToken = \\_nativeTokens[i];\\n nativeToBridgedToken[\\_nativeTokens[i]] = DEPLOYED\\_STATUS;\\n emit TokenDeployed(\\_nativeTokens[i]);\\n }\\n}\\n```\\n

Impact:

Recommended mitigation:
Add an condition `nativeMappingValue` = `DEPLOYED_STATUS` for native token transfer in `confirmDeployment`\\n```\\nif (nativeMappingValue == NATIVE_STATUS || nativeMappingValue == DEPLOYED_STATUS) {\\n   IERC20(_nativeToken).safeTransfer(_recipient, _amount);\\n```\\n
Vulnerability: User Cannot Withdraw Funds if Bridging Failed or Delayed  Won't Fix
Severity: high

Description:
If the bridging failed due to the single coordinator is down, censoring the message, or bridge token contract is set to a bad or wrong contract address by `setCustomContract`, user's funds will stuck in the `TokenBridge` contract until coordinator is online or stop censoring, there is no way to withdraw the deposited funds\\n```\\nfunction setCustomContract(\\n address \\_nativeToken,\\n address \\_targetContract\\n) external onlyOwner isNewToken(\\_nativeToken) {\\n nativeToBridgedToken[\\_nativeToken] = \\_targetContract;\\n bridgedToNativeToken[\\_targetContract] = \\_nativeToken;\\n emit CustomContractSet(\\_nativeToken, \\_targetContract);\\n}\\n```\\n

Impact:

Recommended mitigation:
Add withdraw functionality to let user withdraw the funds under above circumstances or at least add withdraw functionality for Admin (admin can send the funds to the user manually), ultimately decentralize coordinator and sequencer to reduce bridging failure risk.
Vulnerability: Bridges Don't Support Multiple Native Tokens, Which May Lead to Incorrect Bridging
Severity: high

Description:
Currently, the system design does not support the scenarios where native tokens with the same addresses (which is possible with the same deployer and nonce) on different layers can be bridged.\\nFor instance, Let's consider, there is a native token `A` on `L1` which has already been bridged on `L2`. If anyone tries to bridge native token `B` on `L2` with the same address as token `A` , instead of creating a new bridge on `L1` and minting new tokens, the token bridge will transfer native token `A` on `L1` to the `_recipient` which is incorrect.\\nThe reason is the mappings don't differentiate between the native tokens on two different Layers.\\n```\\n  mapping(address => address) public nativeToBridgedToken;\\n  mapping(address => address) public bridgedToNativeToken;\\n```\\n\\n```\\nfunction completeBridging(\\n address \\_nativeToken,\\n uint256 \\_amount,\\n address \\_recipient,\\n bytes calldata \\_tokenMetadata\\n) external onlyMessagingService fromRemoteTokenBridge {\\n address nativeMappingValue = nativeToBridgedToken[\\_nativeToken];\\n address bridgedToken;\\n\\n if (nativeMappingValue == NATIVE\\_STATUS) {\\n // Token is native on the local chain\\n IERC20(\\_nativeToken).safeTransfer(\\_recipient, \\_amount);\\n } else {\\n```\\n

Impact:

Recommended mitigation:
Redesign the approach to handle the same native tokens on different layers. One possible approach could be to define the set of mappings for each layer.
Vulnerability: No Check for Initializing Parameters of TokenBridge
Severity: high

Description:
In `TokenBridge` contract's `initialize` function, there is no check for initializing parameters including `_securityCouncil`, `_messageService`, `_tokenBeacon` and `_reservedTokens`. If any of these address is set to 0 or other invalid value, `TokenBridge` would not work, user may lose funds.\\n```\\nfunction initialize(\\n address \\_securityCouncil,\\n address \\_messageService,\\n address \\_tokenBeacon,\\n address[] calldata \\_reservedTokens\\n) external initializer {\\n \\_\\_Pausable\\_init();\\n \\_\\_Ownable\\_init();\\n setMessageService(\\_messageService);\\n tokenBeacon = \\_tokenBeacon;\\n for (uint256 i = 0; i < \\_reservedTokens.length; i++) {\\n setReserved(\\_reservedTokens[i]);\\n }\\n \\_transferOwnership(\\_securityCouncil);\\n}\\n```\\n

Impact:

Recommended mitigation:
Add non-zero address check for `_securityCouncil`, `_messageService`, `_tokenBeacon` and `_reservedTokens`
Vulnerability: Owner Can Update Arbitrary Status for New Native Token Without Confirmation
Severity: high

Description:
The function `setCustomContract` allows the owner to update arbitrary status for new native tokens without confirmation, bypassing the bridge protocol.\\nIt can set `DEPLOYED_STATUS` for a new native token, even if there exists no bridged token for it.\\nIt can set `NATIVE_STATUS` for a new native token even if it's not.\\nIt can set `RESERVED_STATUS` disallowing any new native token to be bridged.\\n```\\nfunction setCustomContract(\\n address \\_nativeToken,\\n address \\_targetContract\\n) external onlyOwner isNewToken(\\_nativeToken) {\\n nativeToBridgedToken[\\_nativeToken] = \\_targetContract;\\n bridgedToNativeToken[\\_targetContract] = \\_nativeToken;\\n emit CustomContractSet(\\_nativeToken, \\_targetContract);\\n}\\n```\\n

Impact:

Recommended mitigation:
The function should not allow `_targetContract` to be any state code
Vulnerability: Owner May Exploit Bridged Tokens
Severity: high

Description:
The function `setCustomContract` allows the owner, to define a custom ERC20 contract for the native token. However, it doesn't check whether the target contract has already been defined as a bridge to a native token or not. As a result, the owner may take advantage of the design flaw and bridge another new native token that has not been bridged yet, to an already existing target(already a bridge for another native token). Now, if a user tries to bridge this native token, the token bridge on the source chain will take the user's tokens, and instead of deploying a new bridge on the destination chain, tokens will be minted to the `_recipient` on an existing bridge defined by the owner, or it can be any random EOA address to create a DoS.\\nThe owner can also try to front-run calls to `completeBridging` for new Native Tokens on the destination chain, by setting a different bridge via `setCustomContract`. Although, the team states that the role will be controlled by a multi-sig which makes frontrunning less likely to happen.\\n```\\nfunction setCustomContract(\\n address \\_nativeToken,\\n address \\_targetContract\\n) external onlyOwner isNewToken(\\_nativeToken) {\\n nativeToBridgedToken[\\_nativeToken] = \\_targetContract;\\n bridgedToNativeToken[\\_targetContract] = \\_nativeToken;\\n emit CustomContractSet(\\_nativeToken, \\_targetContract);\\n}\\n```\\n\\n```\\n} else {\\n bridgedToken = nativeMappingValue;\\n if (nativeMappingValue == EMPTY) {\\n // New token\\n bridgedToken = deployBridgedToken(\\_nativeToken, \\_tokenMetadata);\\n bridgedToNativeToken[bridgedToken] = \\_nativeToken;\\n nativeToBridgedToken[\\_nativeToken] = bridgedToken;\\n }\\n BridgedToken(bridgedToken).mint(\\_recipient, \\_amount);\\n}\\n```\\n

Impact:

Recommended mitigation:
Make sure, a native token should bridge to a single target contract. A possible approach could be to check whether the `bridgedToNativeToken` for a target is `EMPTY` or not. If it's not `EMPTY`, it means it's already a bridge for a native token and the function should revert. The same can be achieved by adding the modifier `isNewToken(_targetContract)`.\\nNote:- However, it doesn't resolve the issue of frontrunning, even if the likelihood is less.
Vulnerability: Updating Message Service Does Not Emit Event
Severity: medium

Description:
Resolution\\nThe recommendations are implemented by the Linea team in the pull request 69 with the final commit hash as `1fdd5cfc51c421ad9aaf8b2fd2b3e2ed86ffa898`\\nThe function `setMessageService` allows the owner to update the message service address. However, it does not emit any event reflecting the change. As a result, in case the owner gets compromised, it can silently add a malicious message service, exploiting users' funds. Since, there was no event emitted, off-chain monitoring tools wouldn't be able to trigger alarms and users would continue using rogue message service until and unless tracked manually.\\n```\\nfunction setMessageService(address \\_messageService) public onlyOwner {\\n messageService = IMessageService(\\_messageService);\\n}\\n```\\n

Impact:

Recommended mitigation:
Consider emitting an event reflecting the update from the old message service to the new one.
Vulnerability: Lock Solidity Version in pragma
Severity: low

Description:
Contracts should be deployed with the same compiler version they have been tested with. Locking the pragma helps ensure that contracts do not accidentally get deployed using, for example, the latest compiler which may have higher risks of undiscovered bugs. Contracts may also be deployed by others and the pragma indicates the compiler version intended by the original authors.\\nSee Locking Pragmas in Ethereum Smart Contract Best Practices.\\n```\\npragma solidity ^0.8.19;\\n```\\n\\n```\\npragma solidity ^0.8.19;\\n```\\n\\n```\\npragma solidity ^0.8.19;\\n```\\n\\n```\\npragma solidity ^0.8.19;\\n```\\n

Impact:

Recommended mitigation:
Lock the Solidity version to the latest version before deploying the contracts to production.\\n```\\npragma solidity 0.8.19;\\n```\\n
Vulnerability: TokenBridge Does Not Follow a 2-Step Approach for Ownership Transfers
Severity: low

Description:
Resolution\\nThe recommendations are implemented by the Linea team in the pull request 71 with the final commit hash as `8ebfd011675ea318b7067af52637192aa1126acd`\\n`TokenBridge` defines a privileged role Owner, however, it uses a single-step approach, which immediately transfers the ownership to the new address. If accidentally passed an incorrect address, the current owner will immediately lose control over the system as there is no fail-safe mechanism.\\nA safer approach would be to first propose the ownership to the new owner, and let the new owner accept the proposal to be the new owner. It will add a fail-safe mechanism for the current owner as in case it proposes ownership to an incorrect address, it will not immediately lose control, and may still propose again to a correct address.\\n```\\ncontract TokenBridge is ITokenBridge, PausableUpgradeable, OwnableUpgradeable {\\n```\\n

Impact:

Recommended mitigation:
Consider moving to a 2-step approach for the ownership transfers as recommended above. Note:- Openzeppelin provides another helper utility as Ownable2StepUpgradeable which follows the recommended approach
Vulnerability: Heavy Blocks May Affect Block Finalization, if the Gas Requirement Exceeds Block Gas Limit
Severity: high

Description:
The `sequencer` takes care of finalizing blocks by submitting proof, blocks' data, proof type, and parent state root hash. The team mentions that the blocks are finalized every 12s, and under general scenarios, the system will work fine. However, in cases where there are blocks containing lots of transactions and event logs, the function may require gas more than the block gas limit. As a consequence, it may affect block finalization or lead to a potential DoS.\\n```\\nfunction finalizeBlocks(\\n BlockData[] calldata \\_blocksData,\\n bytes calldata \\_proof,\\n uint256 \\_proofType,\\n bytes32 \\_parentStateRootHash\\n)\\n```\\n

Impact:

Recommended mitigation:
We advise the team to benchmark the cost associated per block for the finalization and how many blocks can be finalized in one rollup and add the limits accordingly for the prover/sequencer.
Vulnerability: Postman Can Incorrectly Deliver a Message While Still Collecting the Fees
Severity: high

Description:
The message service allows cross chain message delivery, where the user can define the parameters of the message as:\\nfrom: Sender of the message _to: Receiver of the message _fee: The fees, the sender wants to pay to the postman to deliver the message valueSent: The value in the native currency of the chain to be sent with the message messageNumber: Nonce value which increments for every message _calldata: Calldata for the message to be executed on the destination chain\\nThe postman estimates the gas before claiming/delivering the message on the destination chain, thus avoiding scenarios where the fees sent are less than the cost of claiming the message.\\nHowever, there is nothing that restricts the postman from sending the gas equal to the fees paid by the user. Although it contributes to the MEV, where the postman can select the messages with higher fees first and deliver them prior to others, it also opens up an opportunity where the postman can deliver a message incorrectly while still claiming the fees.\\nOne such scenario is, where the low-level call to target `_to` makes another sub-call to another address, let's say `x`. Let's assume, the `_to` address doesn't check, whether the call to address `x` was successful or not. Now, if the postman supplies a gas, which makes the top-level call succeed, but the low-level call to `x` fails silently, the postman will still be retrieving the fees of claiming the message, even though the message was not correctly delivered.\\n```\\n(bool success, bytes memory returnData) = \\_to.call{ value: \\_value }(\\_calldata);\\nif (!success) {\\n if (returnData.length > 0) {\\n assembly {\\n let data\\_size := mload(returnData)\\n revert(add(32, returnData), data\\_size)\\n }\\n } else {\\n revert MessageSendingFailed(\\_to);\\n }\\n}\\n```\\n\\n```\\n(bool success, bytes memory returnData) = \\_to.call{ value: \\_value }(\\_calldata);\\nif (!success) {\\n if (returnData.length > 0) {\\n assembly {\\n let data\\_size := mload(returnData)\\n revert(add(32, returnData), data\\_size)\\n }\\n } else {\\n revert MessageSendingFailed(\\_to);\\n }\\n}\\n```\\n

Impact:

Recommended mitigation:
Another parameter can be added to the message construct giving the user the option to define the amount of gas required to complete a transaction entirely. Also, a check can be added while claiming the message, to make sure the gas supplied by the postman is sufficient enough compared to the gas defined/demanded by the user. The cases, where the user can demand a huge amount of gas, can be simply avoided by doing the gas estimation, and if the demanded gas is more than the supplied fees, the postman will simply opt not to deliver the message
Vulnerability: User's Funds Would Stuck if the Message Claim Failed on the Destination Layer
Severity: high

Description:
When claiming the message on the destination layer, if the message failed to execute with various reasons (e.g. wrong target contract address, wrong contract logic, out of gas, malicious contract), the Ether sent with `sendMessage` on the original layer will be stuck, although the message can be retried later by the Postman or the user (could fail again)\\n```\\nuint256 messageNumber = nextMessageNumber;\\nuint256 valueSent = msg.value - \\_fee;\\n\\nbytes32 messageHash = keccak256(abi.encode(msg.sender, \\_to, \\_fee, valueSent, messageNumber, \\_calldata));\\n```\\n\\n```\\n(bool success, bytes memory returnData) = \\_to.call{ value: \\_value }(\\_calldata);\\nif (!success) {\\n if (returnData.length > 0) {\\n assembly {\\n let data\\_size := mload(returnData)\\n revert(add(32, returnData), data\\_size)\\n }\\n } else {\\n revert MessageSendingFailed(\\_to);\\n }\\n}\\n```\\n\\n```\\n(bool success, bytes memory returnData) = \\_to.call{ value: \\_value }(\\_calldata);\\nif (!success) {\\n if (returnData.length > 0) {\\n assembly {\\n let data\\_size := mload(returnData)\\n revert(add(32, returnData), data\\_size)\\n }\\n } else {\\n revert MessageSendingFailed(\\_to);\\n }\\n}\\n```\\n

Impact:

Recommended mitigation:
Add refund mechanism to refund users funds if the message failed to deliver on the destination layer
Vulnerability: Front Running finalizeBlocks When Sequencers Are Decentralized
Severity: high

Description:
When sequencer is decentralized in the future, one sequencer could front run another sequencer's `finalizeBlocks` transaction, without doing the actual proving and sequencing, and steal the reward for sequencing if there is one. Once the frontrunner's `finalizeBlocks` is executed, the original sequencer's transaction would fail as `currentL2BlockNumber` would increment by one and state root hash won't match, as a result the original sequencer's sequencing and proving work will be wasted.\\n```\\nfunction finalizeBlocks(\\n BlockData[] calldata \\_blocksData,\\n bytes calldata \\_proof,\\n uint256 \\_proofType,\\n bytes32 \\_parentStateRootHash\\n)\\n external\\n whenTypeNotPaused(PROVING\\_SYSTEM\\_PAUSE\\_TYPE)\\n whenTypeNotPaused(GENERAL\\_PAUSE\\_TYPE)\\n onlyRole(OPERATOR\\_ROLE)\\n{\\n if (stateRootHashes[currentL2BlockNumber] != \\_parentStateRootHash) {\\n revert StartingRootHashDoesNotMatch();\\n }\\n\\n \\_finalizeBlocks(\\_blocksData, \\_proof, \\_proofType, \\_parentStateRootHash, true);\\n}\\n```\\n

Impact:

Recommended mitigation:
Add the sequencer's address as one parameters in `_finalizeBlocks` function, and include the sequencer's address in the public input hash of the proof in verification function `_verifyProof`.\\n```\\nfunction _finalizeBlocks(\\n   BlockData[] calldata _blocksData,\\n   bytes memory _proof,\\n   uint256 _proofType,\\n   bytes32 _parentStateRootHash,\\n   bool _shouldProve,\\n   address _sequencer\\n )\\n```\\n\\n```\\n_verifyProof(\\n        uint256(\\n          keccak256(\\n            abi.encode(\\n              keccak256(abi.encodePacked(blockHashes)),\\n              firstBlockNumber,\\n              keccak256(abi.encodePacked(timestampHashes)),\\n              keccak256(abi.encodePacked(hashOfRootHashes)),\\n              keccak256(abi.encodePacked(_sequencer)\\n            )\\n          )\\n        ) % MODULO_R,\\n        _proofType,\\n        _proof,\\n        _parentStateRootHash\\n      );\\n```\\n
Vulnerability: User Funds Would Stuck if the Single Coordinator Is Offline or Censoring Messages
Severity: high

Description:
When user sends message from L1 to L2, the coordinator needs to post the messages to L2, this happens in the anchoring message(addL1L2MessageHashes) on L2, then the user or Postman can claim the message on L2. since there is only a single coordinator, if the coordinator is down or censoring messages sent from L1 to L2, users funds can stuck in L1, until the coordinator come back online or stops censoring the message, as there is no message cancel feature or message expire feature. Although the operator can pause message sending on L1 once the coordinator is down, but if the message is sent and not posted to L2 before the pause it will still stuck.\\n```\\nuint256 messageNumber = nextMessageNumber;\\nuint256 valueSent = msg.value - \\_fee;\\n\\nbytes32 messageHash = keccak256(abi.encode(msg.sender, \\_to, \\_fee, valueSent, messageNumber, \\_calldata));\\n```\\n\\n```\\nfunction addL1L2MessageHashes(bytes32[] calldata \\_messageHashes) external onlyRole(L1\\_L2\\_MESSAGE\\_SETTER\\_ROLE) {\\n uint256 messageHashesLength = \\_messageHashes.length;\\n\\n if (messageHashesLength > 100) {\\n revert MessageHashesListLengthHigherThanOneHundred(messageHashesLength);\\n }\\n\\n for (uint256 i; i < messageHashesLength; ) {\\n bytes32 messageHash = \\_messageHashes[i];\\n if (inboxL1L2MessageStatus[messageHash] == INBOX\\_STATUS\\_UNKNOWN) {\\n inboxL1L2MessageStatus[messageHash] = INBOX\\_STATUS\\_RECEIVED;\\n }\\n unchecked {\\n i++;\\n }\\n }\\n\\n emit L1L2MessageHashesAddedToInbox(\\_messageHashes);\\n}\\n```\\n

Impact:

Recommended mitigation:
Decentralize coordinator and sequencer or enable user cancel or drop the message if message deadline has expired.
Vulnerability: Changing Verifier Address Doesn't Emit Event
Severity: high

Description:
In function `setVerifierAddress`, after the verifier address is changed, there is no event emitted, which means if the operator (security council) changes the verifier to a buggy verifier, or if the security council is compromised, the attacker can change the verifier to a malicious one, the unsuspecting user would still use the service, potentially lose funds due to the fraud transactions would be verified.\\n```\\nfunction setVerifierAddress(address \\_newVerifierAddress, uint256 \\_proofType) external onlyRole(DEFAULT\\_ADMIN\\_ROLE) {\\n if (\\_newVerifierAddress == address(0)) {\\n revert ZeroAddressNotAllowed();\\n }\\n verifiers[\\_proofType] = \\_newVerifierAddress;\\n}\\n```\\n

Impact:

Recommended mitigation:
Emits event after changing verifier address including old verifier address, new verifier address and the caller account
Vulnerability: L2 Blocks With Incorrect Timestamp Could Be Finalized
Severity: medium

Description:
In `_finalizeBlocks` of `ZkEvmV2`, the current block timestamp `blockInfo.l2BlockTimestamp` should be greater or equal than the last L2 block timestamp and less or equal than the L1 block timestamp when `_finalizeBlocks` is executed. However the first check is missing, blocks with incorrect timestamp could be finalized, causing unintended system behavior\\n```\\nif (blockInfo.l2BlockTimestamp >= block.timestamp) {\\n revert BlockTimestampError();\\n}\\n```\\n

Impact:

Recommended mitigation:
Add the missing timestamp check
Vulnerability: Rate Limiting Affecting the Usability and User's Funds Safety
Severity: medium

Description:
In `claimMessage` of `L1MessageService` and `sendMessage` function of `L1MessageService` contract, function `_addUsedAmount` is used to rate limit the Ether amount (1000 Eth) sent from L2 to L1 in a time period (24 hours), this is problematic, usually user sends the funds to L1 when they need to exit from L2 to L1 especially when some security issues happened affecting their funds safety on L2, if there is a limit, the limit can be reached quickly by some whale sending large amount of Ether to L1, while other users cannot withdraw their funds to L1, putting their funds at risk. In addition, the limit can only be set and changed by the security council and security council can also pause message service at any time, blocking user withdraw funds from L2, this makes the L2->L1 message service more centralized.\\n```\\n\\_addUsedAmount(\\_fee + \\_value);\\n```\\n\\n```\\n\\_addUsedAmount(msg.value);\\n```\\n\\n```\\nfunction \\_addUsedAmount(uint256 \\_usedAmount) internal {\\n uint256 currentPeriodAmountTemp;\\n\\n if (currentPeriodEnd < block.timestamp) {\\n // Update period before proceeding\\n currentPeriodEnd = block.timestamp + periodInSeconds;\\n currentPeriodAmountTemp = \\_usedAmount;\\n } else {\\n currentPeriodAmountTemp = currentPeriodAmountInWei + \\_usedAmount;\\n }\\n\\n if (currentPeriodAmountTemp > limitInWei) {\\n revert RateLimitExceeded();\\n }\\n\\n currentPeriodAmountInWei = currentPeriodAmountTemp;\\n}\\n```\\n

Impact:

Recommended mitigation:
Remove rate limiting for L2->L1 message service