xanoutas/solidity-security-findings huggingface dataset:
Upgraded Q -> 2 from #122 [1714762888728]
Audit: 2024-03-zksync-findings
Severity: MEDIUM

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

 ## QA-01 A chain that gets frozen by the state transition manager can never be removed from the frozen state in `StateTransitionManager.sol`

### Proof of Concept

Take a look at https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol#L159-L163

```solidity
    function freezeChain(uint256 _chainId) external onlyOwner {
        IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond();
    }

```

This is a special function used by the admin to freeze a specific chain Id, this would come in handy in different context and is placed as a safe measure to help ensure the chain could be frozen to be safe.

Now protocol, logic also includes an unfreezing functionality, but this has been wrongly implemented, see https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol#L164-L168

```solidity
    function unfreezeChain(uint256 _chainId) external onlyOwner {
        IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond();
    }

```

It's evident that where as this function is meant to be used for "unfreezing" the specified chain, it insteads attempt to call `freezeDiamond()`, now this means that an attempt to call on `unfreezeChain()` by the owner of the `StateTransitionManager.sol` contract would always revert since while `freezeDiamond()` is being executed it checks if the chain Id is frozen already and if yes it reverts (`require(!diamondStorage.isFrozen, "a9");`), see https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol#L133-L150

```solidity
    function freezeDiamond() external onlyAdminOrStateTransitionManager {
        Diamond.DiamondStorage storage diamondStorage = Diamond.getDiamondStorage();

        require(!diamondStorage.isFrozen, "a9"); // diamond proxy is frozen already
        diamondStorage.isFrozen = true;

        emit Freeze();
    }

    /// @inheritdoc IAdmin
    function unfreezeDiamond() external onlyAdminOrStateTransitionManager {
        Diamond.DiamondStorage storage diamondStorage = Diamond.getDiamondStorage();

        require(diamondStorage.isFrozen, "a7"); // diamond proxy is not frozen
        diamondStorage.isFrozen = false;

        emit Unfreeze();
    }
```

Evidently, this bug is probably due to a copy paste error (i.e copying the implementation of `freezeChain()` for `unfreezeChain()` ), as we can see that the dev comment is the same in both instances of _freezing the chain_.

### Impact

Whereas the owner/admin can currently freeze a specified chain, the logic of unfreezing these chains is flawed and as such any chain that gets fr
Deviational use of `useNearCallPanic` while querying `mintEther()` in `ZKSYNC_NEAR_CALL_executeL1Tx()` could cost users all their gas for the execution
Audit: 2024-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/bootloader/bootloader.yul#L1792-L1847
https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/bootloader/bootloader.yul#L797-L822


# Vulnerability details


## Proof of Concept

Take a look at https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/bootloader/bootloader.yul#L797-L822

```yul
            function mintEther(to, amount, useNearCallPanic) {
                mstore(0, {{RIGHT_PADDED_MINT_ETHER_SELECTOR}})
                mstore(4, to)
                mstore(36, amount)
                let success := call(
                    gas(),
                    ETH_L2_TOKEN_ADDR(),
                    0,
                    0,
                    68,
                    0,
                    0
                )
                if iszero(success) {
                    switch useNearCallPanic
                    case 0 {
                        revertWithReason(
                            MINT_ETHER_FAILED_ERR_CODE(),
                            0
                        )
                    }
                    default {
                        nearCallPanic()
                    }
                }
            }
```

This function is used to mint ether to the recipient, as seen this function directly uses the `useNearCallPanic` to know if to revert with reason or panic call and burn all the gas attached to the parent frame

Now consider https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/bootloader/bootloader.yul#L1792-L1847

```yul
            function ZKSYNC_NEAR_CALL_executeL1Tx(
                abi,
                txDataOffset,
                basePubdataSpent,
                gasPerPubdata,
            ) -> success {
                // Skipping the first word of the ABI encoding of the struct
                let innerTxDataOffset := add(txDataOffset, 32)
                let from := getFrom(innerTxDataOffset)
                let gasPrice := getMaxFeePerGas(innerTxDataOffset)

                debugLog("Executing L1 tx", 0)
                debugLog("from", from)
                debugLog("gasPrice", gasPrice)

                // We assume that addresses of smart contracts on zkSync and Ethereum
                // never overlap, so no need to check whether `from` is an EOA here.
                debugLog("setting tx origin", from)

                setTxOrigin(from)
                debugLog("setting gas price", gasPrice)

                setGasPrice(gasPrice)

                debugLog("execution itself", 0)

                let value := getValue(innerTxDataOffset)
                if value {
1820:                    mintEther(from, value, true)
                }

                success := executeL1Tx(innerTxDataOffset, from)
Centralization risks around `Governance.sol`
Audit: 2024-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/governance/Governance.sol#L167
https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/governance/Governance.sol#L154


# Vulnerability details

## Impact

The presence of modifiers and their implementations in the `Governance.sol` contract indicates that the Owner, the Security Council and the contract have distinct roles. As described in an [article by Matter Labs](https://blog.matter-labs.io/security-council-2-0-2337a555f17a), the purpose of the Security Council is to ensure that upgradability is not controlled by a single user or team. However, upon examining the implementation of the Governance contract, it does not fulfill this condition. There is no issue with only the Owner being able to open proposals through `scheduleTransparent()` or without exposing the operation on the chain through `scheduleShadow()`, however the fact that only the Owner can prevent the execution of proposals with `cancel()` appears to be a centralization issue.

With the current state of `Governance.sol`, the Owner has the ability to propose and `execute()` an operation granting unlimited authority to themselves using `scheduleShadow()`, because the `updateDelay` and `updateSecurityCouncil` functions, protected by onlySelf modifiers, can be successfully modified when called from within the `execute()` function since `msg.sender = Governance.sol`. In fact, even without changing the Security Council, by simply setting `minDelay = 0`, the Owner could make the existence of both `executeInstant()` and Security Council unnecessary, and instantly execute any operation they desire.

## Proof of Concept

It's not possible for me to show coded PoC in the current repository, but hypothetically;

1-) Owner `scheduleShadow()`s an operation which gives themselves full authority by changing the `securityCouncil` and `minDelay` and even upgrading all contracts as they wil.
2-) Waiting for `minDelay` to pass, operation is unknown on-chain.
3-) `execute()`s the scheduled operation.

The snippet below shows that canceling can only be done by the Owner role.
```
function cancel(bytes32 _id) external onlyOwner {
        require(isOperationPending(_id), "Operation must be pending");
        delete timestamps[_id];
        emit OperationCancelled(_id);
    }
```

## Tools Used

Manual Reveiw

## Recommended Mitigation Steps

Although I do not assume that the owner could be malicious and I do not like issues related to centralization, it is stated on the contest page that "Matter Labs and is also partially trusted, it should never be able to directly steal users' funds or conduct malicious upgrades." I think the current implementation of `Governance` contradicts that condition, Matter Labs' philosophy, and abolishes the mission of the Security Council.

The problem can be solved in the most elegant and simple way by adding the `onlyOwnerOrSecurityCounc
`L2StandardERC20.sol` has a flaw in token's reinitialization logic cause after a while the governor would not be able to update the token's metadata 
Audit: 2024-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/zksync/contracts/bridge/L2StandardERC20.sol#L111-L128
https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/zksync/contracts/bridge/L2StandardERC20.sol#L134-L139


# Vulnerability details



## Proof of Concept

Take a look at https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/zksync/contracts/bridge/L2StandardERC20.sol#L111-L128

```solidity
    function reinitializeToken(
        ERC20Getters calldata _availableGetters,
        string memory _newName,
        string memory _newSymbol,
        uint8 _version
    ) external onlyNextVersion(_version) reinitializer(_version) {
        // It is expected that this token is deployed as a beacon proxy, so we'll
        // allow the governor of the beacon to reinitialize the token.
        address beaconAddress = _getBeacon();
        require(msg.sender == UpgradeableBeacon(beaconAddress).owner(), "tt");

        __ERC20_init_unchained(_newName, _newSymbol);
        __ERC20Permit_init(_newName);
        availableGetters = _availableGetters;

        emit BridgeInitialize(l1Address, _newName, _newSymbol, decimals_);
    }

```

This function is used to reinitialize or update a token's metadata.

Firstly would be key to note that it passes the `version` to the `onlyNextVersion()` modifier to ensure that the governor does not accidentally disable future reinitialization of the token, https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/zksync/contracts/bridge/L2StandardERC20.sol#L134-L139

```solidity
    modifier onlyNextVersion(uint8 _version) {
        // The version should be incremented by 1. Otherwise, the governor risks disabling
        // future reinitialization of the token by providing too large a version.
        require(_version == _getInitializedVersion() + 1, "v");
        _;
    }
```

Case is with how this whole logic is implemented, would be key to note that the function `_getInitializedVersion()` is inherited from Openzeppelin's implementation, i.e https://github.com/OpenZeppelin/openzeppelin-contracts/blob/0a5fba7a7ed726698477c7772be5d17afe69f118/contracts/proxy/utils/Initializable.sol#L208C1-L227C6

```solidity
    function _getInitializedVersion() internal view returns (uint64) {
        return _getInitializableStorage()._initialized;
    }

    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        assembly {
            $.slot := INITIALIZABLE_STORAGE
        }
    }
```

Now, evidently this function returns `uint64` value, meaning that protocol instead limits it self to `255` versions instead of `18,446,744,073,709,551,615` which is over a `> 99,9999%` decrease on the amount of generations it should support.

## Impact

Medium, this is high impact and somewhat
Large transfers may not work with some ERC20 tokens
Audit: 2024-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/zksync/contracts/bridge/interfaces/IL1ERC20Bridge.sol#L67


# Vulnerability details



Some  IERC20 implementations (e.g UNI, COMP) may fail if the valued transferred is larger than uint96. [Source](https://github.com/d-xo/weird-erc20#revert-on-large-approvals--transfers)


```solidity
File: contracts/ethereum/contracts/bridge/L1ERC20Bridge.sol
67              IERC20(_token).safeTransfer(address(sharedBridge), amount);
```
https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/zksync/contracts/bridge/interfaces/IL1ERC20Bridge.sol#L67



## Assessed type

ERC20
No way to retrieve ETH from the contract:
Audit: 2024-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/main/code/system-contracts/contracts/GasBoundCaller.sol#L15
https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/state-transition/chain-deps/DiamondProxy.sol#L10
https://github.com/code-423n4/2024-03-zksync/blob/main/code/system-contracts/contracts/Compressor.sol#L22
https://github.com/code-423n4/2024-03-zksync/blob/main/code/system-contracts/contracts/EmptyContract.sol#L10
https://github.com/code-423n4/2024-03-zksync/blob/main/code/system-contracts/contracts/L2BaseToken.sol#L18


# Vulnerability details



The following contracts contain at least one payable function, yet the function does not utilise forwarded ETH, and the contract is missing functionality to withdraw ETH from the contract. This means that funds may become trapped in the contract indefinitely.  Consider adding a withdraw/sweep function to contracts that are capable of receiving ether.


```solidity
File: contracts/GasBoundCaller.sol
15      contract GasBoundCaller {
```
https://github.com/code-423n4/2024-03-zksync/blob/main/code/system-contracts/contracts/GasBoundCaller.sol#L15

```solidity
File: contracts/ethereum/contracts/state-transition/chain-deps/DiamondProxy.sol
10      contract DiamondProxy {
```
https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/state-transition/chain-deps/DiamondProxy.sol#L10

```solidity
File: system-contracts/contracts/Compressor.sol
22      contract Compressor is ICompressor, ISystemContract {
```
https://github.com/code-423n4/2024-03-zksync/blob/main/code/system-contracts/contracts/Compressor.sol#L22

```solidity
File: system-contracts/contracts/EmptyContract.sol
10      contract EmptyContract {
```
https://github.com/code-423n4/2024-03-zksync/blob/main/code/system-contracts/contracts/EmptyContract.sol#L10

```solidity
File: system-contracts/contracts/L2BaseToken.sol
18      contract L2BaseToken is IBaseToken, ISystemContract {
```
https://github.com/code-423n4/2024-03-zksync/blob/main/code/system-contracts/contracts/L2BaseToken.sol#L18


## Assessed type

Other
Permanent freezing of an hyperchain if it is ever freezed by the `StateTransitionManager`
Audit: 2024-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol#L166


# Vulnerability details

## Impact
There is a typo when unfreezing an hyperchain, which makes it impossible to unfreeze a previously freezed one.

## Proof of Concept
Pretty visual

[**StateTransitionManager, function unfreezeChain**](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol#L166)

```solidity
    /// @dev freezes the specified chain
    function unfreezeChain(uint256 _chainId) external onlyOwner {
        IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond(); // @audit must be unfreezeDiamond(), typo
    }
```

Calling `unfreezeChain` on a freezed one will always revert due to the following `require` in

[**Admin, function freezeDiamond**](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol#L136)

```solidity
    function freezeDiamond() external onlyAdminOrStateTransitionManager {
        ...

        require(!diamondStorage.isFrozen, "a9"); // diamond proxy is frozen already
        
        ...
    }
```

Runnable POC (for completeness)

```solidity
    function test_freezeX2() public {
        address admin = utilsFacet.util_getAdmin();

        vm.expectRevert(bytes.concat("a9"));

        vm.startPrank(admin);
        adminFacet.freezeDiamond();
        adminFacet.freezeDiamond();
        vm.stopPrank();
    }
    
```

## Recommended Mitigation Steps

Trivial

[**StateTransitionManager, function unfreezeChain**](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol#L166)

```diff
    /// @dev freezes the specified chain
    function unfreezeChain(uint256 _chainId) external onlyOwner {
-       IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond();
+       IZkSyncStateTransition(stateTransition[_chainId]).unfreezeDiamond();
    }
```


## Assessed type

Other
Incorrect Check for Intrinsic Costs in L1 to L2 Transaction Validation
Audit: 2024-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/libraries/TransactionValidator.sol#L35-L41


# Vulnerability details



## Impact
The current implementation of the `validateL1ToL2Transaction` function allows an L1 to L2 transaction to be sent that does not cover the sum of overhead and intrinsic costs for the operator. This could potentially enable griefing attacks by submitting transactions that only cover the maximum of the overhead or intrinsic costs, but not the total sum of both.


## Proof of Concept

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/libraries/TransactionValidator.sol#L35-L41

The `validateL1ToL2Transaction` function checks the minimal priority transaction gas limit calculated by `getMinimalPriorityTransactionGasLimit` against the `_transaction.gasLimit`. However, `_transaction.gasLimit` represents the total gas limit, including the overhead. The correct check should be against the actual gas limit available for execution, which is calculated by subtracting the overhead from the total gas limit.


## Tools Used
God & Manual Review


## Recommended Mitigation Steps
1. In the `validateL1ToL2Transaction` function, I'd suggest replacing the check:

```solidity
require(
    getMinimalPriorityTransactionGasLimit(
        _encoded.length,
        _transaction.factoryDeps.length,
        _transaction.gasPerPubdataByteLimit
    ) <= _transaction.gasLimit,
    "up"
);
```

with:

```solidity
uint256 txBodyGasLimit = getTransactionBodyGasLimit(_transaction.gasLimit, _encoded.length);
require(
    getMinimalPriorityTransactionGasLimit(
        _encoded.length,
        _transaction.factoryDeps.length,
        _transaction.gasPerPubdataByteLimit
    ) <= txBodyGasLimit,
    "up"
);
```

2. This way, the minimal priority transaction gas limit (which represents the sum of intrinsic costs and execution costs) is correctly checked against the actual gas limit available for execution (`txBodyGasLimit`), ensuring that the transaction covers both the overhead and the intrinsic costs.



## Assessed type

Other
Insufficient Minimum Delay Enforcement in `updateDelay` Function
Audit: 2024-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/governance/Governance.sol#L249-L252


# Vulnerability details

## Impact

No enforcement of a minimum delay in the `updateDelay` function could undermine the entire governance mechanism of the contract and lead to the following consequences:

1. Bypassing the intended timelock mechanism, allowing immediate execution of potentially malicious operations without oversight or review.
2. Disrupting contract upgrades by executing unauthorized or malicious upgrades to the governed contracts.
3. Exploiting the `execute` and `executeInstant` functions to perform unauthorized actions, such as draining funds, modifying contract state, or executing arbitrary code.
4. Compromising the governance process by nullifying the intended oversight and security measures provided by the timelock.

## Proof of Concept 

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/governance/Governance.sol#L249-L252

The `updateDelay` function in the contract lacks enforcement of a minimum delay value when updating the `minDelay` parameter. This could allow an attacker who gains control of the contract by hook or crook to set the `minDelay` to zero or an extremely low value, effectively bypassing the intended timelock mechanism for governance operations.

```solidity
function updateDelay(uint256 _newDelay) external onlySelf {
    emit ChangeMinDelay(minDelay, _newDelay);
    minDelay = _newDelay; // No minimum delay check
}
```

With a minimal or no delay, an attacker could then schedule and execute malicious operations almost immediately, exploiting functions like `scheduleTransparent`, `scheduleShadow`, `execute`, and `executeInstant`.

```solidity
function scheduleTransparent(Operation calldata _operation, uint256 _delay) external onlyOwner {
    bytes32 id = hashOperation(_operation);
    _schedule(id, _delay); // _delay could be set to zero
    emit TransparentOperationScheduled(id, _delay, _operation);
}

function scheduleShadow(bytes32 _id, uint256 _delay) external onlyOwner {
    _schedule(_id, _delay); // _delay could be set to zero
    emit ShadowOperationScheduled(_id, _delay);
}

function execute(Operation calldata _operation) external payable onlyOwnerOrSecurityCouncil {
    // ...
    // With minDelay set to zero, this function could execute immediately
}

function executeInstant(Operation calldata _operation) external payable onlySecurityCouncil {
    // ...
    // With minDelay set to zero, this function could execute immediately
}
```

## Tools Used
God & Manual Review

## Recommended Mitigation Steps

I'd recommend enforcing a reasonable minimum delay in the `updateDelay` function. This can be achieved by adding a check to ensure that the new delay value is greater than or equal to a predefined minimum delay constant.

```solidity
uint256 public const