xanoutas/solidity-security-findings huggingface dataset:
L1WethBridge.sol#L233 : `finalizeWithdrawal` transfers the funds two times - First - ETH , Second - WETH
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/bridge/L1WethBridge.sol#L233


# Vulnerability details

## Impact

Funds are transferred two times which is loss of funds. So, flagging this issue as High.

## Proof of Concept

L2 ETH withdrawal can be finalized by calling the [finalizeWithdrawal](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/bridge/L1WethBridge.sol#L233) function.

Inside the function, following checks are done.

1. Check whether the transaction is already finalized or not.

2. If alreadyFinalised, checks for already included log or not by calling the function `proveL2MessageInclusion`

3. if not finalized, calls the [finalizeEthWithdrawal](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/bridge/L1WethBridge.sol#L257C20-L257C41) function  and finalize it.

Lets look at the `finalizeEthWithdrawal` function
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L192-L216

```solidity
    function finalizeEthWithdrawal(
        uint256 _l2BatchNumber,
        uint256 _l2MessageIndex,
        uint16 _l2TxNumberInBatch,
        bytes calldata _message,
        bytes32[] calldata _merkleProof
    ) external override nonReentrant senderCanCallFunction(s.allowList) {
        require(!s.isEthWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex], "jj");


        L2Message memory l2ToL1Message = L2Message({
            txNumberInBatch: _l2TxNumberInBatch,
            sender: L2_ETH_TOKEN_SYSTEM_CONTRACT_ADDR,
            data: _message
        });


        (address _l1WithdrawReceiver, uint256 _amount) = _parseL2WithdrawalMessage(_message);


        bool proofValid = proveL2MessageInclusion(_l2BatchNumber, _l2MessageIndex, l2ToL1Message, _merkleProof);
        require(proofValid, "pi"); // Failed to verify that withdrawal was actually initialized on L2


        s.isEthWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex] = true;
        _withdrawFunds(_l1WithdrawReceiver, _amount); ---------------------------->>> audit fine. ETH is transfered.


        emit EthWithdrawalFinalized(_l1WithdrawReceiver, _amount);
    }
```

after completing the `finalizeEthWithdrawal` call, again the WETH is transferred inside the function `finalizeWithdrawal`

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/bridge/L1WethBridge.sol#L261-L267
```solidity
        IWETH9(l1WethAddress).deposit{value: amount}();
        // Transfer WETH tokens from the smart contract address to the withdrawal receiver
        IERC20(l1WethAddress).safeTransfer(l1WethWithdrawReceiver, amount); --------------->> transfers second time


        isWithdrawalFinalized[_
L1WethBridge.sol#L233 : possibility of reentrancy attack due to the state update after external call
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/bridge/L1WethBridge.sol#L233


# Vulnerability details

## Impact
Funds can be drained by re-entering the transaction call again and again.

## Proof of Concept

when the weth withdrawal is initiated in L2 chain, the transaction will be finalized on L1 by calling the function
[finalizeWithdrawal](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/bridge/L1WethBridge.sol#L233) .

This function will takes the following parameters,

    /// @notice Finalize the withdrawal and release funds
    /// @param _l2BatchNumber The L2 batch number where the ETH (WETH) withdrawal was processed
    /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the ETH
    /// withdrawal message containing additional data about WETH withdrawal
    /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the ETH withdrawal log was sent
    /// @param _message The L2 withdraw data, stored in an L2 -> L1 message
    /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization

and it checks whether the transaction is already finalized or not.

If not finalized yet --> will finalize and process.

If already finalized --> checks whether the message is included or not.

if at these checks, the ETH is converted into WETH and sent to the `l1WethWithdrawReceiver`

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/bridge/L1WethBridge.sol#L233-L268

```solidity
    function finalizeWithdrawal(
        uint256 _l2BatchNumber,
        uint256 _l2MessageIndex,
        uint16 _l2TxNumberInBatch,
        bytes calldata _message,
        bytes32[] calldata _merkleProof
    ) external nonReentrant senderCanCallFunction(allowList) {
        require(!isWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex], "Withdrawal is already finalized");


        (address l1WethWithdrawReceiver, uint256 amount) = _parseL2EthWithdrawalMessage(_message);


        // Check if the withdrawal has already been finalized on L2.
        bool alreadyFinalised = zkSync.isEthWithdrawalFinalized(_l2BatchNumber, _l2MessageIndex);
        if (alreadyFinalised) {
            // Check that the specified message was actually sent while withdrawing eth from L2.
            L2Message memory l2ToL1Message = L2Message({
                txNumberInBatch: _l2TxNumberInBatch,
                sender: L2_ETH_TOKEN_SYSTEM_CONTRACT_ADDR,
                data: _message
            });
            bool success = zkSync.proveL2MessageInclusion(_l2BatchNumber, _l2MessageIndex, l2ToL1Message, _merkleProof);
            require(success, "vq");
        } else {
            // Finalize the withdrawal if it is not yet done.
            zkSync
ERC20 Token Balance Manipulation and Potential DoS Vulnerability
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The vulnerability allows an attacker to manipulate the balance of an ERC20 token in the L1ERC20Bridge contract. This manipulation can lead to insufficient funds during the `withdraw()` function, potentially causing a Denial of Service (DoS) attack. If the balance becomes insufficient, users' funds could be locked in the contract. To unlock the funds, users would need to send more tokens into the contract, resulting in a loss of funds for the protocol.

The vulnerability arises from the use of `_token.balanceOf(address(this))` in the `_depositFunds` function. The balance of the ERC20 token is read before and after the transfer operation, allowing an attacker to manipulate the balance between these two points. By manipulating the balance, an attacker can cause the contract to believe it received more tokens than it actually did, potentially leading to a DoS condition if the contract allows withdrawal based on these manipulated balances.

## Proof of Concept
```solidity
File: contracts/ethereum/contracts/bridge/L1ERC20Bridge.sol

217:         uint256 balanceBefore = _token.balanceOf(address(this));

219:         uint256 balanceAfter = _token.balanceOf(address(this));

```
```solidity
// Before the transfer operation
uint256 balanceBefore = _token.balanceOf(address(this));

// Attacker manipulates the balance here

// After the transfer operation
uint256 balanceAfter = _token.balanceOf(address(this));
```

Impact Scenario:

- Attacker manipulates the balance after `balanceBefore` and before `balanceAfter`.
- The contract believes it received more tokens than it actually did.
- Users initiate withdrawals based on the manipulated balances.
- Withdrawal fails due to insufficient funds, locking users' funds and potentially causing a DoS condition.

## Tools Used
VS Code, Manual review

## Recommended Mitigation Steps
Disallow such kind of variable balance token.





## Assessed type

Reentrancy
Potential Reentrancy Vulnerability in Governance Contract
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/main/code/contracts/ethereum/contracts/governance/Governance.sol#L226


# Vulnerability details

## Impact
The impact of this finding is that the governance contract is susceptible to reentrancy attacks, where an attacker could potentially manipulate the contract's state and funds in unexpected ways. This can lead to unauthorized fund transfers or other malicious actions.

## Proof of Concept

The vulnerable line of code in the contract is as follows:

```solidity
File: contracts/ethereum/contracts/governance/Governance.sol

226:             (bool success, bytes memory returnData) = _calls[i].target.call{value: _calls[i].value}(_calls[i].data);

```

 The contract does not implement reentrancy protection mechanisms, which means that an external malicious contract could potentially call back into the governance contract before the state is updated, leading to unexpected outcomes.

A potential attack scenario could involve an external malicious contract that, upon receiving Ether from the governance contract, immediately calls back into the governance contract before the state is fully updated. This could result in the attacker repeatedly manipulating the contract's state and funds within the same transaction, leading to unauthorized transfers or other malicious actions.

## Tools Used
VS Code, Manual review

## Recommended Mitigation Steps
To mitigate this vulnerability, consider implementing the ReentrancyGuard pattern or using the reentrancy protection mechanisms available in OpenZeppelin contracts. This pattern ensures that state-changing functions in the contract are designed to be reentrancy-safe.

Additionally, it's crucial to carefully review the logic of the contract and analyze potential reentrancy scenarios. Use the reentrancyGuard modifier or follow best practices outlined in OpenZeppelin's security guide to prevent reentrancy attacks.

Ensure that all state changes are done before any external calls and that funds are handled in a secure manner. Thoroughly test the contract using different attack scenarios to ensure its security.


## Assessed type

Reentrancy
Operator can mint unlimited ETH when processing L1->L2 transactions
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/c857609bfdc41a0ee2c1b245217a785f66b42a56/code/system-contracts/bootloader/bootloader.yul#L913-L918
https://github.com/code-423n4/2023-10-zksync/blob/c857609bfdc41a0ee2c1b245217a785f66b42a56/code/system-contracts/bootloader/bootloader.yul#L937-L959


# Vulnerability details

## Impact
Operator can modify the refund address and the refund amount when processing L1->L2 transactions to mint any amount of L2 ETH to their address.
## Proof of Concept
Processing L1->L2 transactions can result in unspent gas, e.g. when the gas limit of a transaction was set too high ([bootloader.yul#L993-L996](https://github.com/code-423n4/2023-10-zksync/blob/c857609bfdc41a0ee2c1b245217a785f66b42a56/code/system-contracts/bootloader/bootloader.yul#L993-L996)). Unspent gas is refunded to the transaction sender ([bootloader.yul#L937-L959](https://github.com/code-423n4/2023-10-zksync/blob/c857609bfdc41a0ee2c1b245217a785f66b42a56/code/system-contracts/bootloader/bootloader.yul#L937-L959)).

The operator is allowed to increase the amount of unspent gas that'll be refunded. This is implemented via VM hooks:
1. First, the operator is asked to set the refund amount ([bootloader.yul#L913-L914](https://github.com/code-423n4/2023-10-zksync/blob/c857609bfdc41a0ee2c1b245217a785f66b42a56/code/system-contracts/bootloader/bootloader.yul#L913-L914)). This function calls a VM hook that's handled by the operator:
    ```yul
    function askOperatorForRefund(gasLeft) {
        storeVmHookParam(0, nonOptimized(gasLeft))
        setHook(VM_HOOK_ASK_OPERATOR_FOR_REFUND())
    }
    ```
    While handling the hook, the operator is expected to put the refund amount into bootloader's memory ([bootloader.yul#L3309-L3318](https://github.com/code-423n4/2023-10-zksync/blob/c857609bfdc41a0ee2c1b245217a785f66b42a56/code/system-contracts/bootloader/bootloader.yul#L3309-L3318)):
    ```
    /// @dev Asks operator for the refund for the transaction. The function provides
    /// the operator with the leftover gas found by the bootloader. 
    ...
    /// After it is run, the operator should put the expected refund
    /// into the memory slot (in the out of circuit execution).
    ```
1. After `askOperatorForRefund()` was called, the bootloader reads the refund amount from the memory ([bootloader.yul#L918](https://github.com/code-423n4/2023-10-zksync/blob/c857609bfdc41a0ee2c1b245217a785f66b42a56/code/system-contracts/bootloader/bootloader.yul#L918)):
    ```yul
    /// @dev Return the operator suggested transaction refund.
    function getOperatorRefundForTx(transactionIndex) -> ret {
        let refundPtr := add(TX_OPERATOR_REFUND_BEGIN_BYTE(), mul(transactionIndex, 32))
        ret := mload(refundPtr)
    }
    ```

However, since the operator fully controls bootloader's memory ([a message from the sponsor that proves that](https://discord.com/channels/810916927919620096/1163388898303561830/1163438804896591962)), it can write to any memor
Attacker can manipulate the sorted queue in log sorter to emit reverted logs and events
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/era-zkevm_circuits/src/log_sorter/mod.rs#L331-L456


# Vulnerability details

## Impact
Attacker can manipulate the sorted queue in log sorter, constraints are not strong enough and reverted l1 logs and events can still be emitted.

## Proof of Concept
Let's see what we have enforced in this circuit. For a unique timestamp, either there is only a write log, we should add it to the queue; or there is a write log and a rollback log, which means revert took place, we should ignore it.

            // We compare timestamps, and then resolve logic over rollbacks, so the only way when
            // keys are equal can be when we do rollback
            let sorting_key = sorted_item.timestamp;

            // ensure sorting for uniqueness timestamp and rollback flag
            // We know that timestamps are unique accross logs, and are also the same between write and rollback
            let (keys_are_equal, new_key_is_smaller) =
                unpacked_long_comparison(cs, &[previous_key], &[sorting_key]);

            // keys are always ordered no matter what, and are never equal unless it's padding
            new_key_is_smaller.conditionally_enforce_false(cs, should_pop);
At first, we enforce the timestamps in the sorted queue are in ascending orders, which means write log and rollback log of the same timestamp should be adjacent.

            // there are only two cases when keys are equal:
            // - it's a padding element
            // - it's a rollback

            // it's enough to compare timestamps as VM circuit guarantees uniqueness of the if it's not a padding
            let previous_is_not_rollback = previous_item.rollback.negated(cs);
            let enforce_sequential_rollback = Boolean::multi_and(
                cs,
                &[previous_is_not_rollback, sorted_item.rollback, should_pop],
            );
            keys_are_equal.conditionally_enforce_true(cs, enforce_sequential_rollback);
Here, for two consecutive element A, B in the queue, if A is not rollback and B is rollback, we enforce that A, B shares the same timestamp.

            let same_log = UInt32::equals(cs, &sorted_item.timestamp, &previous_item.timestamp);
            let values_are_equal =
                UInt256::equals(cs, &sorted_item.written_value, &previous_item.written_value);
            let negate_previous_is_trivial = previous_is_trivial.negated(cs);
            let should_enforce = Boolean::multi_and(cs, &[same_log, negate_previous_is_trivial]);
            values_are_equal.conditionally_enforce_true(cs, should_enforce);
Here, for two consecutive element A, B in the queue, if they share the same timestamp, we enforce that they have the same written value. (This is already guaranteed by the earlier circuits)

            let this_item_is_non_trivial_rollback =
                Boolean::multi_and(cs, &[sorted_item.rollback, should_pop]
DOS in finalizeEthWithdrawal() and requestL2Transaction() of MailboxFacet Contract due to missing modifier initialization
Audit: 2023-10-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L192-L198
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/facets/Mailbox.sol#L236-L244


# Vulnerability details

## Impact
DOS of these functions impacts the ability of users to finalize Ether withdrawals and request L2 transactions from L1. This could potentially halt the operation of the zkSync Layer 2 scaling solution for Ethereum, affecting the ability of users to interact with the contract and perform transactions.

## Proof of Concept
In order to understand this bug we need to first understand the relation between nonReentrant modifier and
reentrancyGuardInitializer modifier in zkSync's custom [reentrancy guard](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/common/ReentrancyGuard.sol) implementation contract

Let's Start with [nonReentrant()](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/common/ReentrancyGuard.sol#L68-L89) modifier

```
modifier nonReentrant() {
        uint256 _status;
        assembly {
            _status := sload(LOCK_FLAG_ADDRESS)
        }

        // On the first call to nonReentrant, _notEntered will be true
        require(_status == _NOT_ENTERED, "r1");
```
First it stores LOCK_FLAG_ADDRESS (which right now is 0) value in _status variable
Then it checks whether _status == _NOT_ENTERED (which is always 1)

As we know the _status is equal to 0 because LOCK_FLAG_ADDRESS was 0, therefore it will revert with "r1" error.

That is why in order to pass this check we first need to call [_initializeReentrancyGuard()](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/common/ReentrancyGuard.sol#L46-L59) which is possible by having [reentrancyGuardInitializer](https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/common/ReentrancyGuard.sol#L41C14-L44) modifier in our contract's constructor.

The reentrancyGuardInitializer modifier calls the _initializeReentrancyGuard() function

```solidity
 modifier reentrancyGuardInitializer() {
        _initializeReentrancyGuard();
        _;
    }
```


```solidity
function _initializeReentrancyGuard() private {
        uint256 lockSlotOldValue;

        // Storing an initial non-zero value makes deployment a bit more
        // expensive but in exchange every call to nonReentrant
        // will be cheaper.
        assembly {
            lockSlotOldValue := sload(LOCK_FLAG_ADDRESS)
            sstore(LOCK_FLAG_ADDRESS, _NOT_ENTERED)
        }

        // Check that storage slot for reentrancy guard is empty to rule out possibility of slot con
Upgraded Q -> 2 from #314 [1702561397269]
Audit: 2023-10-zksync-findings
Severity: MEDIUM

Judge has assessed an item in Issue #314 as 2 risk. The relevant finding follows:

 [NC‑05]: Calling precompile contracts with delegatecall has inconsistent results
It is possible to call the precompile contracts with delegatecall. The results for the user vary depending on if an inner precompileCall() is made.

The opcode used precompileCall() will revert if the current address is not a system contract. Since msg.sender in a delegatecall will be the user contract it should be that the call to precompileCall() reverts.

However, the exact specifics of when precompileCall() will revert are not located on the contracts in scope and exist inside the VM. This assumes the desired functionality that precompileCall() will check the msg.sender rather than the address which contains the bytecode.

So, if we call EcAdd or EcMul with valid parameters it will succeed since precompileCall() is not triggered. However, if we call EcAdd or EcMul with invalid parameters then the burnGas() function is called and the delegatecall will revert. All other precompiles will fail since they make use of precompileCall().
Upgraded Q -> 2 from #314 [1702561391414]
Audit: 2023-10-zksync-findings
Severity: MEDIUM

Judge has assessed an item in Issue #314 as 2 risk. The relevant finding follows:

 [NC‑05]: Calling precompile contracts with delegatecall has inconsistent results
It is possible to call the precompile contracts with delegatecall. The results for the user vary depending on if an inner precompileCall() is made.

The opcode used precompileCall() will revert if the current address is not a system contract. Since msg.sender in a delegatecall will be the user contract it should be that the call to precompileCall() reverts.

However, the exact specifics of when precompileCall() will revert are not located on the contracts in scope and exist inside the VM. This assumes the desired functionality that precompileCall() will check the msg.sender rather than the address which contains the bytecode.

So, if we call EcAdd or EcMul with valid parameters it will succeed since precompileCall() is not triggered. However, if we call EcAdd or EcMul with invalid parameters then the burnGas() function is called and the delegatecall will revert. All other precompiles will fail since they make use of precompileCall().