xanoutas/solidity-security-findings huggingface dataset:
[Tomo-H1] All funds can drain if some conditions matched
Audit: 2022-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/bridge/L1ERC20Bridge.sol#L225-L257


# Vulnerability details

[https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/bridge/L1ERC20Bridge.sol#L225-L257](https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/bridge/L1ERC20Bridge.sol#L225-L257)

```solidity
function finalizeWithdrawal(
    uint256 _l2BlockNumber,
    uint256 _l2MessageIndex,
    uint16 _l2TxNumberInBlock,
    bytes calldata _message,
    bytes32[] calldata _merkleProof
) external nonReentrant senderCanCallFunction(allowList) {
    require(!isWithdrawalFinalized[_l2BlockNumber][_l2MessageIndex], "pw");

    L2Message memory l2ToL1Message = L2Message({
        txNumberInBlock: _l2TxNumberInBlock,
        sender: l2Bridge,
        data: _message
    });

    (address l1Receiver, address l1Token, uint256 amount) = _parseL2WithdrawalMessage(l2ToL1Message.data);
    // Preventing the stack too deep error
    {
        bool success = zkSyncMailbox.proveL2MessageInclusion(
            _l2BlockNumber,
            _l2MessageIndex,
            l2ToL1Message,
            _merkleProof
        );
        require(success, "nq");
    }

    isWithdrawalFinalized[_l2BlockNumber][_l2MessageIndex] = true;
    // Withdraw funds
    IERC20(l1Token).safeTransfer(l1Receiver, amount);

    emit WithdrawalFinalized(l1Receiver, l1Token, amount);
}
```

If some conditions match, an attacker can transfer all tokens to an arbitrary address

This is a bit lengthy, so I'll break it up as follows

You can see each source code on Proof of Concept

### 1: Possibility of conditions to occur

For this attack vector to be valid, the value of `acutualRootHash` would be `bytes(0)` as the default value.

As a result, the value of `s.l2LogsRootHashes` may be `bytes(0)`for the following reasons

- There is no checking `_storedBlock.l2LogsTreeRoot` is not `bytes(0)` before setting the `s.l2LogsRootHashes[currentBlockNumber]` by `[_executeOneBlock()](https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/zksync/facets/Executor.sol#L190-L196)`
- The possibility abuse of admin privileges by the `validator` to execute `executeBlocks()`

### 2: How to pass `proveL2MessageInclusion`

If the above case is met, the value `actualRootHash` would be `bytes(0)`

[https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/zksync/facets/Mailbox.sol#L69](https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/zksync/facets/Mailbox.sol#L69)

```solidity
bytes32 actualRootHash = s.l2LogsRootHashes[_blockNumber];
```

The value of `calculatedRootHash` is calculated by this function.

The `calculateRoot` allows empty proofs therefore users can get `bytes(0)` as a returned value of this function

This issue is similar to this situation

[https://github.com/code-423n4/2022-05-opensea-seaport-findings/issues/201](https://github.com/code-423n4/2022-05-opensea-seaport
Hacker can front-run the L2 ERC20 token deployment.
Audit: 2022-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/zksync/contracts/bridge/L2ERC20Bridge.sol#L50
https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/zksync/contracts/bridge/L2ERC20Bridge.sol#L61
https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/zksync/contracts/bridge/L2ERC20Bridge.sol#L73
https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/zksync/contracts/bridge/L2ERC20Bridge.sol#L77


# Vulnerability details

## Impact

hacker can front-run the L2 ERC20 token deployment to block L2 ERC20 token finalizeDeposit

## Proof of Concept


I intend to prove this piece of code is front-runnable by hacker:

```solidity
    /// @notice Finalize the deposit and mint funds
    /// @param _l1Sender The account address that initiate the deposit on L1
    /// @param _l2Receiver The account address that would receive minted ether
    /// @param _l1Token The address of the token that was locked on the L1. Always should be equal to zero (conventional value)
    /// @param _amount Total amount of tokens deposited from L1
    /// @param _data The additional data that user can pass with the deposit
    function finalizeDeposit(
        address _l1Sender,
        address _l2Receiver,
        address _l1Token,
        uint256 _amount,
        bytes calldata _data
    ) external override {
        // Only L1 bridge counterpart can initiate and finalize the deposit
        require(msg.sender == l1Bridge, "mq");

        address expectedL2Token = l2TokenAddress(_l1Token);
        if (l1TokenAddress[expectedL2Token] == address(0)) {
            address deployedToken = _deployL2Token(_l1Token, _data);
            require(deployedToken == expectedL2Token, "mt");
            l1TokenAddress[expectedL2Token] = _l1Token;
        }

        IL2StandardToken(expectedL2Token).bridgeMint(_l2Receiver, _amount);

        emit FinalizeDeposit(_l1Sender, _l2Receiver, expectedL2Token, _amount);
    }

    /// @dev Deploys and initialize the L2 token for the L1 counterpart
    function _deployL2Token(address _l1Token, bytes calldata _data) internal returns (address) {
        bytes32 salt = _getCreate2Salt(_l1Token);

        BeaconProxy l2Token = new BeaconProxy{salt: salt}(address(l2TokenFactory), "");
        L2StandardERC20(address(l2Token)).bridgeInitialize(_l1Token, _data);

        return address(l2Token);
    }
```


note the function: 

```solidity
    /// @dev Deploys and initialize the L2 token for the L1 counterpart
    function _deployL2Token(address _l1Token, bytes calldata _data) internal returns (address) {
        bytes32 salt = _getCreate2Salt(_l1Token);

        BeaconProxy l2Token = new BeaconProxy{salt: salt}(address(l2TokenFactory), "");
        L2StandardERC20(address(l2Token)).bridgeInitialize(_l1Token, _data);

        return address(l2Token);
    }
```

We know the address _l1token, it is
Double spending risk in L1 Bridge Contract
Audit: 2022-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/bridge/L1ERC20Bridge.sol#L206
https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Mailbox.sol#L40


# Vulnerability details

## Impact

There is double spending risk in L1 Bridge Contract. The user may call claimFailedDeposit to release their locked fund while they still have token balance in L2 network.

## Proof of Concept

Let us focus on the L1ERC20Bridge.sol

```solidity
    /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2
    /// @param _depositSender The address of the deposit initiator
    /// @param _l1Token The address of the deposited L1 ERC20 token
    /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization
    /// @param _l2BlockNumber The L2 block number where the deposit finalization was processed
    /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message
    /// @param _l2TxNumberInBlock The L2 transaction number in a block, in which the log was sent
    /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction with deposit finalization
    function claimFailedDeposit(
        address _depositSender,
        address _l1Token,
        bytes32 _l2TxHash,
        uint256 _l2BlockNumber,
        uint256 _l2MessageIndex,
        uint16 _l2TxNumberInBlock,
        bytes32[] calldata _merkleProof
    ) external nonReentrant senderCanCallFunction(allowList) {
        // Bootloader sends an L2 -> L1 log only after processing the L1 -> L2 transaction.
        // Thus, we can verify that the deposit (L1 -> L2 transaction) was included in the L2 block with failed status.
        //
        // The semantics of such L2 -> L1 log is always:
        // - sender = BOOTLOADER_ADDRESS
        // - key = hash(L1ToL2Transaction)
        // - value = status of the processing transaction (1 - success & 0 for fail)
        // - isService = true (just a conventional value)
        // - l2ShardId = 0 (means that L1 -> L2 transaction was processed in a rollup shard, other shards are not available yet anyway)
        // - txNumberInBlock = number of transaction in the block
        L2Log memory l2Log = L2Log({
            l2ShardId: 0,
            isService: true,
            txNumberInBlock: _l2TxNumberInBlock,
            sender: BOOTLOADER_ADDRESS,
            key: _l2TxHash,
            value: bytes32(0)
        });
        bool success = zkSyncMailbox.proveL2LogInclusion(_l2BlockNumber, _l2MessageIndex, l2Log, _merkleProof);
        require(success, "yn");

        uint256 amount = depositAmount[_depositSender][_l1Token][_l2TxHash];
        require(amount > 0, "y1");

        delete depositAmount[_depositSender][_l1Token][_l2TxHash];
        // Withdraw funds
        IERC20(_l1Token).safeTransfer(_depositSender, amount);
The governor can use the replay attack to manipuate the votes in his favor
Audit: 2022-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/4db6c596931a291b17a4e0e2929adf810a4a0eed/ethereum/contracts/zksync/facets/DiamondCut.sol#L105


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
Since the argument of function *approveEmergencyDiamondCutAsSecurityCouncilMember* does not have the proposalID, a governor can use replay attack to manipulate the votes in his favor, that is, even not enough security members approve it, he can still execute it. 


## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
The manipulation can be explained in the following way: 
Suppose the governor proposes a  proposal with proposalId = 1, and the *_diamondCutHash* is ABCD.
Suppose SECURITY_COUNCIL_APPROVALS_FOR_EMERGENCY_UPGRADE = 3 and there are 5 council members: A, B, C, D, E.
Let;s say in the first round only A, B approved, and C, D, and E didn't approve, so the proposal will not passed. 
Then there is a discussion, and A changed his mind to disapproval, and C changed his mind to approval. 
The proposal should not pass either for the second round. However, the governor will use replay attack to approve
A, and B continued to approve, and an additional C's approval, the governor will be able to get the proposal approved although this should not happen.

The problem is that function *approveEmergencyDiamondCutAsSecurityCouncilMember(bytes32 _diamondCutHash)* does not use propsalID, therefore, replay attack becomes possible. 


## Tools Used
Remix

## Recommended Mitigation Steps
Introduce proposalID in the argument of function *approveEmergencyDiamondCutAsSecurityCouncilMember(bytes32 _diamondCutHash)* so that the replay attack becomes impossible. One cannot vote for the same proposalID twice.
Unchecked array length of the `public_inputs[]` in `Verifier.sol`
Audit: 2022-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/4db6c596931a291b17a4e0e2929adf810a4a0eed/ethereum/contracts/zksync/Verifier.sol#L134


# Vulnerability details

## Impact
If an array enumerates all registered addresses, an adversary can register many addresses, causing the problem described above.

## Vulnerability Details
Ethereum is a very resource-constrained environment. Prices per computational step are orders of magnitude higher than with centralized providers. Moreover, Ethereum miners impose a limit on the total number of Gas consumed in a block. If array.length is large enough, the function exceeds the block gas limit, and transactions calling it will never be confirmed.
Example: `for (uint256 i = 0; i < array.length ; i++) { cosltyFunc(); }`

This becomes a security issue, if an external actor influences `array.length`.

The vulnerable piece of code:
```
for (uint256 i = 0; i < public_inputs.length; i = i.uncheckedInc()) {
            proof.input_values[i] = public_inputs[i];
        }
```

## Remediation
Either explicitly or just due to normal operation, the number of iterations in a loop can grow beyond the block gas limit, which can cause the complete contract to be stalled at a certain point. Therefore, loops with a bigger or unknown number of steps should always be avoided.
Unchecked array lenght
Audit: 2022-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/4db6c596931a291b17a4e0e2929adf810a4a0eed/ethereum/contracts/zksync/Verifier.sol#L134


# Vulnerability details

## Impact
If an array enumerates all registered addresses, an adversary can register many addresses, causing the problem described above.

## Description
Ethereum is a very resource-constrained environment. Prices per computational step are orders of magnitude higher than with centralized providers. Moreover, Ethereum miners impose a limit on the total number of Gas consumed in a block. If array.length is large enough, the function exceeds the block gas limit, and transactions calling it will never be confirmed.
`for (uint256 i = 0; i < array.length ; i++) { cosltyFunc(); }`

This becomes a security issue, if an external actor influences `array.length`.

## Proof of Concept
Vulnerable function is `deserialize_proof`:
```
for (uint256 i = 0; i < public_inputs.length; i = i.uncheckedInc()) {
```

## Remediation
Either explicitly or just due to normal operation, the number of iterations in a loop can grow beyond the block gas limit, which can cause the complete contract to be stalled at a certain point. Therefore, loops with a bigger or unknown number of steps should always be avoided.
`initializer`  modifier on `constructor` function 
Audit: 2022-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/main/zksync/contracts/bridge/L2StandardERC20.sol#L39
https://github.com/code-423n4/2022-10-zksync/blob/main/zksync/contracts/bridge/L2StandardERC20.sol#L43


# Vulnerability details

## Impact
In `L2StandardERC20.sol` the `initializer` modifier is placed both on the `constructor` function and on the `bridgeInitialize` function.  When the `initializer` modifier is used it cannot be called again on any function in the contract since it will revert with a "already initialized" error.  When this contract is deployed the `constructor` function will automatically be run,  then when attempting to call the `bridgeInitialize` function it will fail because the `initializer` modifier was already used on the `constructor` function.  This will prevent the `l1Address` from being set in storage and thus breaking the contract functionality. 

## Proof of Concept
https://github.com/code-423n4/2022-10-zksync/blob/main/zksync/contracts/bridge/L2StandardERC20.sol#L39

https://github.com/code-423n4/2022-10-zksync/blob/main/zksync/contracts/bridge/L2StandardERC20.sol#L43

## Tools Used
Manual code review 

## Recommended Mitigation Steps
The `initialize` modifier should be removed from the `constructor` function and only used on the `bridgeInitialize` function.
`reentrancyGuardInitializer` modifier used on `constructor` and `Initialize` functions
Audit: 2022-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/bridge/L1EthBridge.sol#L48
https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/bridge/L1EthBridge.sol#L56
https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/common/ReentrancyGuard.sol#L38
https://github.com/code-423n4/2022-10-zksync/blob/4db6c596931a291b17a4e0e2929adf810a4a0eed/ethereum/contracts/common/ReentrancyGuard.sol#L49-L55


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
In `L1EthBridge.sol` there are two places where the `reentrancyGuardInitializer` modifier is used. It's found on both the `constructor` function and also on the `initialize` function.  This is a problem because this modifier can only be successfully called once and will fail if called again after.  Since the `constructor` function is run automatically upon the deployment of the contract,  this means that it's impossible to call the `initialize` function thereafter. 

Inside the `reentrancyGuardInitializer` modifier it calls the `_initializeReentrancyGuard` function.  The `_NOT_ENTERED` value is then stored at the storage location specified by the `LOCK_FLAG_ADDRESS` variable. It then requires that the `LOCK_FLAG_ADDRESS` is zero for the first call to this function in order for the function not to fail `require(lockSlotOldValue == 0, "1B");`.

Upon the deployment of `L1EthBridge.sol` the `lockSlotOldValue` will be set due to the `reentrancyGuardInitializer` modifier being on its constructor.  This will make it impossible to call the `initialize` function which has the same modifier to deploy the L2 bridge counterpart during initialization making it impossible to initialize the contract bridge for later use or pass the `_l2BridgeBytecode` for use on L2. 

## Proof of Concept
https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/bridge/L1EthBridge.sol#L48

https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/bridge/L1EthBridge.sol#L56

https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/common/ReentrancyGuard.sol#L38

https://github.com/code-423n4/2022-10-zksync/blob/4db6c596931a291b17a4e0e2929adf810a4a0eed/ethereum/contracts/common/ReentrancyGuard.sol#L49-L55

## Tools Used
Manual code review

## Recommended Mitigation Steps
The `reentrancyGuardInitializer` modifier should be taken off the constructor function since it is already used on the `initialize` function.
`reentrancyGuardInitializer` modifier used on `constructor` and `Initialize` functions
Audit: 2022-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/bridge/L1ERC20Bridge.sol#L57
https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/bridge/L1ERC20Bridge.sol#L75
https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/common/ReentrancyGuard.sol#L38
https://github.com/code-423n4/2022-10-zksync/blob/4db6c596931a291b17a4e0e2929adf810a4a0eed/ethereum/contracts/common/ReentrancyGuard.sol#L49-L55


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
In `L1ERC20Bridge.sol` there are two places where the `reentrancyGuardInitializer` modifier is used. It's found on both the `constructor` function and also on the `initialize` function.  This is a problem because this modifier can only be successfully called once and will fail if called again after.  Since the `constructor` function is run automatically upon the deployment of the contract,  this means that it's impossible to call the `initialize` function thereafter. 

Inside the `reentrancyGuardInitializer` modifier it calls the `_initializeReentrancyGuard` function.  The `_NOT_ENTERED` value is then stored at the storage location specified by the `LOCK_FLAG_ADDRESS` variable. It then requires that the `LOCK_FLAG_ADDRESS` is zero for the first call to this function in order for the function not to fail `require(lockSlotOldValue == 0, "1B");`.

Upon the deployment of `L1ERC20Bridge.sol` the `lockSlotOldValue` will be set due to the `reentrancyGuardInitializer` modifier being on its constructor.  This will make it impossible to call the `initialize` function which has the same modifier rendering the `L1ERC20Bridge.sol` contract useless as you wont be able to initialize the needed state which includes setting the ` _l2TokenFactory` in storage. 

## Proof of Concept
https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/bridge/L1ERC20Bridge.sol#L57

https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/bridge/L1ERC20Bridge.sol#L75

https://github.com/code-423n4/2022-10-zksync/blob/main/ethereum/contracts/common/ReentrancyGuard.sol#L38

https://github.com/code-423n4/2022-10-zksync/blob/4db6c596931a291b17a4e0e2929adf810a4a0eed/ethereum/contracts/common/ReentrancyGuard.sol#L49-L55

## Tools Used
Manual code review 

## Recommended Mitigation Steps
The `reentrancyGuardInitializer` modifier should be taken off the constructor function since it is already used on the `initialize` function.
May introduce an invalid Facet into the system
Audit: 2022-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/4db6c596931a291b17a4e0e2929adf810a4a0eed/ethereum/contracts/zksync/libraries/Diamond.sol#L184-L193


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
The _saveFacetIfNew() has not checked if the newly introduced facet is valid or not (a valid contract), therefore, an invalid address might be added as a facet. according to Solidity documentation, "If the proxy delegates to an incorrect address, or implementation that has been destructed, the call to the implementation will return success even though no code was executed 
As a result, the caller will not notice the issue, and such behavior is likely to break third-party contract integration." As a result, when a user call a non-existent facet through the proxy, the call will be shown to be successful although no code was executed, the consequence is unpredictable.  



## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
https://github.com/code-423n4/2022-10-zksync/blob/4db6c596931a291b17a4e0e2929adf810a4a0eed/ethereum/contracts/zksync/libraries/Diamond.sol#L184-L193

## Tools Used
Remix

## Recommended Mitigation Steps
Reimplement the function _saveFAcetIfnew() and the proxy fallback function to ensure the facet is a valid contract. 

```
function _saveFacetIfNew(address _facet) private {
        DiamondStorage storage ds = getDiamondStorage();
        require(isContract(_facet), "_facet is not a contract.");  // check if it is a contract.

        uint256 selectorsLength = ds.facetToSelectors[_facet].selectors.length;
        // If there are no selectors associated with facet then save facet as new one
        if (selectorsLength == 0) {
            ds.facetToSelectors[_facet].facetPosition = uint16(ds.facets.length);
            ds.facets.push(_facet);
        }
    }

function isContract(address _addr) private returns (bool isContract){
  uint32 size;
  assembly {
    size := extcodesize(_addr)
  }
  return (size > 0);
}
```
Governor ownership can be lost because of not sanity check
Audit: 2022-10-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-10-zksync/blob/5a31c9db8ab32175dbd7264b05ce84931b6c0428/zksync/contracts/bridge/L2ERC20Bridge.sol#L31-L42


# Vulnerability details


# Governor ownership can be lost because of no checks
## Impact 
Sanity checks are important to not affect reputation / flows and users of the protocol when a mistake is done. 0 address should be checked for important address assignments (in this case, only done in the constructor) or function calls.

## PoC
`l2TokenFactory` calls transfer ownership to a `_governor` in 1 step without checks. This can force a redeployment and affect the protocol.
` l2TokenFactory.transferOwnership(_governor);`

Also there are no sanity checks in `L2ERC20Bridge.sol` constructor in:
       `l1Bridge = _l1Bridge;`
only assigned in the constructor and not checked for 0 address. 

If wrongly assigned to 0 `finalizeDeposit` would get blocked.

https://github.com/code-423n4/2022-10-zksync/blob/5a31c9db8ab32175dbd7264b05ce84931b6c0428/zksync/contracts/bridge/L2ERC20Bridge.sol#L31-L42

## Mitigation
- Check for 0 address
- Consider using `msg.sender` for `_governor` initial ownership.