xanoutas/solidity-security-findings huggingface dataset:
Error in `stateTransistionManager.unfreezeChain` would cause unfreezing of frozen Chains by Owner to always fail.
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
A typo in `stateTransistionManager.unfreezeChain` would cause a DOS when the stateTransitionManager attempts to unfreeze a previously frozen chain. It would revert due to the check in `Admin.freezeDiamond` which requires that the chain is not already frozen. This means the stateTransitionManager would not be able to unfreeze any chain that had already been frozen using the `stateTransitionManager.unfreezeChain`. This would cause any chain which has been frozen to remain frozen until the contract or facets are redeployed, effectively DOSsing that chain.

```
src: code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol

   function unfreezeChain(uint256 _chainId) external onlyOwner {
  @>      IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond();
    }
```


## Proof of Concept
The function `stateTransitionManager.unfreezeChain` which would be called by an admin/owner when attempting to unfreeze a previously frozen chain would always revert because this function instead would call the `Admin.freezeDiamond` instead of `Admin.unfreezeDiamond`. Due to checks in the freezeDiamond function, since this chain is already frozen, this would cause a revert.
```
  function freezeDiamond() external onlyAdminOrStateTransitionManager {
        Diamond.DiamondStorage storage diamondStorage = 
     Diamond.getDiamondStorage();

  @>    require(!diamondStorage.isFrozen, "a9"); // diamond proxy is frozen 
        already
        diamondStorage.isFrozen = true;

        emit Freeze();
    }
```

## Tools Used
Manual review

## Recommended Mitigation Steps
1. Change `freezeDiamond()` to `unfreezeDiamond` which is the expected logic.

```diff
   function unfreezeChain(uint256 _chainId) external onlyOwner {
 -       IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond();
 +       IZkSyncStateTransition(stateTransition[_chainId]).unfreezeDiamond();
    }
```








## Assessed type

DoS
State transition manager is unable to force upgrade a deployed ST,  which invalidates the designed safeguard for 'urgent high risk situation'
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/chain-deps/facets/Admin.sol#L103
https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol#L123
https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol#L226


# Vulnerability details

## Impact
State transition manager(STM) will be unable to force upgrade a deployed ST against intended design for 'urgent high risk situation'.

This invalidates the designed safeguard mechanism of an STM force upgrade an ST.

## Proof of Concept
According to [doc](https://github.com/code-423n4/2024-03-zksync/blob/main/docs/Smart%20contract%20Section/L1%20ecosystem%20contracts.md#configurability-in-the-first-release), `in case of urgent high risk situation, STM might force upgrade the contract`(ST). 

However, the above safeguard of force upgrading ST is not possible due to StateTransitionManger.sol's incomplete implementation.

(1) A deployed chain(ST) can choose to perform a mandatory upgrade at its own convenience. It's also possible for an ST to postpone an upgrade set by the state transition manager. An ST will perform an upgrade through `upgradeChainFromVersion()` on the Admin facet. 
```solidity
//code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol
    function upgradeChainFromVersion(
        uint256 _oldProtocolVersion,
        Diamond.DiamondCutData calldata _diamondCut
|>    ) external onlyAdminOrStateTransitionManager {
...
```
(https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol#L103)
Note that although `upgradeChainFromVersion()` is access-controlled by both chain admin and StateTransitionManager contract. StateTransitionManager will not be able to call it, because no methods or flows on StateTransitionManger.sol will invoke the call.

(2) Another method on Admin facet for upgrade is `executeUpgrade()` which is access-controlled by only StateTransitionManager contract. However, `executeUpgrade()` can only be invoked inside `_setChainIdUpgrade()`. And `_setChainIdUpgrade()` can only be invoked inside `createNewChain()`. This means, after a chain(ST)'s genesis, `executeUpgrade()` cannot be invoked by stateTransitionManger again to perform further upgrades.
```solidity
//code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol
    function executeUpgrade(
        Diamond.DiamondCutData calldata _diamondCut
|>    ) external onlyStateTransitionManager {
        Diamond.diamondCut(_diamondCut);
        emit ExecuteUpgrade(_diamondCut);
    }
```
(https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8
Key Admin facet functions are invalidated, due to vulnerable access-control implementations
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/chain-deps/facets/Admin.sol#L51
https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol#L58


# Vulnerability details

## Impact
Key Admin facet functions(`setPorterAvailability`, `setPriorityTxMaxGasLimit`) are invalidated, due to vulnerable access-control implementations

## Proof of Concept
Key admin facet functions such as `setPorterAvailability` and  `setPriorityTxMaxGasLimit` are now `onlyStateTransitionManager` controlled. However, (1) StateTransitionManger.sol is unable to call `setPorterAvailability` in any flows. (2) StateTransitionManger.sol is unable to change `PriorityTxMaxGasLimit` if needed.

(1) `s.zkPorterIsAvailable` on Admin facet is not initializable in DiamondInit.sol. Neither is it callable by designated StateTransitionManger.sol because ST manager contract does not have any functions or flows that will invoke `setPorterAvailability`. The result is `s.zkPorterIsAvailable` will always be default false value and cannot be set or reset if needed.
```solidity
//code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol
    //@audit s.zkPorterIsAvailable will not be initialized by DiamondInit.sol, neither can it be set by StateTransitionManager.sol, due to the contract has not methods or flows to set it.
    function setPorterAvailability(
        bool _zkPorterIsAvailable
|>    ) external onlyStateTransitionManager {
        // Change the porter availability
        s.zkPorterIsAvailable = _zkPorterIsAvailable;
        emit IsPorterAvailableStatusUpdate(_zkPorterIsAvailable);
    }
```
(https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol#L51)
(2) Although `s.priorityTxMaxGasLimit` is initializalbe by DiamondInit.sol, it is not callable by StateTransitionManager.sol due to the contract doesn't have methods or flows to call it. As a result, `s.priorityTxMaxGasLimit` cannot be changed if needed.
```solidity
//code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol
    //@audit s.priorityTxMaxGasLimit will only be set during initialization by DiamondInit.sol. But setPriorityTxMaxGasLimit cannot be called by StateTransitionManger.sol because the contract doesn't have methods or flows to call it. s.priorityTxMaxGasLimit cannot be updated.
    function setPriorityTxMaxGasLimit(
        uint256 _newPriorityTxMaxGasLimit
|>    ) external onlyStateTransitionManager {
        require(_newPriorityTxMaxGasLimit <= MAX_GAS_PER_TRANSACTION, "n5");
        uint256 oldPriorityTxMaxGasLimit = s.priorityTxMaxGasLimit;
        s.priorityTxMaxGasLimit = _newPriorityTxMaxGasLimit;
        emit NewPriorityTxMaxGasLimit(
L1SharedBridge's restriction on Weth deposit can be bypassed
Audit: 2024-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/bridge/L1SharedBridge.sol#L194
https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Mailbox.sol#L206


# Vulnerability details

## Impact
L1SharedBridge's restriction on Weth deposit can be bypassed. Deprecated L1WethBridge.sol can still interact with Mailbox to deposit WETH on L2. 

## Proof of Concept
At current hyperchain upgrade, L1WethBridge and L2WethBrdige will be [deprecated](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/docs/Protocol%20Section/Changelog.md?plain=1#L35). 

L1SharedBridge.sol has checks to ensure that weth deposit is not allowed in `bridgehubDeposit()` ([_l1Token != l1WethAddress](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/bridge/L1SharedBridge.sol#L194)). 

This check can be bypassed. Notably the new Mailbox.sol still maintains legacy `requestL2Transaction()` for ERA chain, but it doesn't check whether the `sender` is the L1Wethbridge address. As a result, users can still call [L1Wethbridge `deposit()`](https://github.com/code-423n4/2023-10-zksync/blob/26547a90d569559ef5c80ff8c523010134e84a71/code/contracts/ethereum/contracts/bridge/L1WethBridge.sol#L185) which calls the new Mailbox's `requestL2Transaction` to deposit Weth. And tx will go through because the lack of checks on Weth bridge addresses on Mailbox. 
```solidity
//code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Mailbox.sol
    function requestL2Transaction(
        address _contractL2,
        uint256 _l2Value,
        bytes calldata _calldata,
        uint256 _l2GasLimit,
        uint256 _l2GasPerPubdataByteLimit,
        bytes[] calldata _factoryDeps,
        address _refundRecipient
    ) external payable returns (bytes32 canonicalTxHash) {
        //@audit-info note: this only ensure chain is ERA, but doesn't check whether msg.sender is L1Wethbridge address. L1Wethbridge can still requestL2Transaction through Mailbox
|>      require(
            s.chainId == ERA_CHAIN_ID,
            "legacy interface only available for era token"
        );
        canonicalTxHash = _requestL2TransactionSender(
            BridgehubL2TransactionRequest({
                sender: msg.sender,
                contractL2: _contractL2,
                mintValue: msg.value,
                l2Value: _l2Value,
                l2GasLimit: _l2GasLimit,
                l2Calldata: _calldata,
                l2GasPerPubdataByteLimit: _l2GasPerPubdataByteLimit,
                factoryDeps: _factoryDeps,
                refundRecipient: _refundRecipient
            })
        );
        IL1SharedBridge(s.baseTokenBridge).bridgehubDepositBaseToken{
            value: msg.value
        }(s.chainId, ms
L2SharedBridge.finalizeDeposit will not work for legacy bridge
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/L2SharedBridge.sol#L87-L91


# Vulnerability details

## Proof of Concept
L2SharedBridge.finalizeDeposit function is allowed to be called [by l1 shared bridge or legacy bridge](https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/zksync/contracts/bridge/L2SharedBridge.sol#L87-L91).

The problem is that legacy bridge [is not stored during initialization](https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/zksync/contracts/bridge/L2SharedBridge.sol#L87-L91) and thus finalization will not work.

## Impact
Finalization of legacy deposit will not work.

## Tools Used
VsCode
## Recommended Mitigation Steps
Save `l1LegacyBridge` variable.


## Assessed type

Error
StateTransitionManager doesn't have ability to set some settings for chain
Audit: 2024-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol#L45-L64


# Vulnerability details

## Proof of Concept
There are several settings that can be set by StateTransitionManager [in the admin facet for the chain](https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol#L45-L64). All those function use `onlyStateTransitionManager` modifier.

But StateTransitionManager itself doesn't have any means to call that functions and thus changed the configuration. While all settings are set during initial upgrade of proxy, StateTransitionManager can't change those values for existing chains and invariant is broken.

## Impact
StateTransitionManager can't change some settings for the chain.

## Tools Used
VsCode
## Recommended Mitigation Steps
Implement those functions.


## Assessed type

Error
StateTransitionManager.unfreezeChain calls wrong function
Audit: 2024-03-zksync-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Proof of Concept
Using StateTransitionManager.freezeChain function admin [can freeze specific chain](https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol#L161). This means that all non freezable facets [will not be callable anymore](https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/state-transition/chain-deps/DiamondProxy.sol#L31).

StateTransitionManager.unfreezeChain function should do the opposite action, however it by mistake [calls freeze as well](https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol#L166), which means that it will be not possible to unfreeze proxy.

## Impact
Not possible to unfreeze proxy. Need to do upgrade.

## Tools Used
VsCode
## Recommended Mitigation Steps
Use correct function to unfreeze.





## Assessed type

Error
Users might be charged unfair amount of baseToken for L2Gas due to vulnerable baseToken price conversion implementation
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/chain-deps/facets/Mailbox.sol#L159-L160
https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol#L79


# Vulnerability details

## Impact
Users might be charged unfair amount of baseToken for L2Gas due to vulnerable baseToken price conversion implementation.

## Proof of Concept
Hyperchain allows non-eth baseToken chains. When user request L1->L2 transactions to a non-eth chain, users will pay L2 gas in baseToken instead of ETH.

The problem is baseToken l2 gas conversion might be using an outdated ratio of baseToken/ETH due to a vulnerable baseToken price update implementation `setTokenMultiplier`. 

When a user request a L1->L2 transaction, Mailbox checks if user sent enough baseToken(`mintValue`) to cover gas(`baseCost`) in `_requestL2Transaction()`. `uint256 baseCost = _params.l2GasPrice * _params.l2GasLimit;` `l2GasPrice` is calculated in `_deriveL2GasPrice()` based on baseToken multipliers. 
```solidity
//code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Mailbox.sol
    function _deriveL2GasPrice(
        uint256 _l1GasPrice,
        uint256 _gasPerPubdata
    ) internal view returns (uint256) {
...
 |>       uint256 l1GasPriceConverted = (_l1GasPrice *
            s.baseTokenGasPriceMultiplierNominator) /
            s.baseTokenGasPriceMultiplierDenominator;
        uint256 pubdataPriceBaseToken;
...
```
(https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Mailbox.sol#L159-L160)

However, `s.baseTokenGasPriceMultiplierNominator` and `s.baseTokenGasPriceMultiplierDenominator` can only be updated in `setTokenMultiplier()` by the chain admin, which is most likely a multi-sig contract. Since base token is not updated dynamically, this will result in an old or assumed baseToken/ETH ratio being used for `baseCost` check. User is charged a baseCost based on and outdated baseToken/ETH ratio. The user can be overcharged. 

```solidity
//code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol
//@audit setTokenMultiplier can only called by chain Admin after chain genesis, the chain Admin is most likely a multi-sig contract, which will not update baseToken/ETH ratio in time, resulting in stale price.
    function setTokenMultiplier(
        uint128 _nominator,
        uint128 _denominator
|>    ) external onlyAdminOrStateTransitionManager {
...
        s.baseTokenGasPriceMultiplierNominator = _nominator;
        s.baseTokenGasPriceMultiplierDenominator = _denominator;
...
``` 
(https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Ad
It's possible to replay some upgrade transactions
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#L55
https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/contracts/libraries/TransactionHelper.sol#L118-L136


# Vulnerability details

## Impact

Field `reserved` is not being taken into consideration when encoding hash of the zkSync native transaction type (`TransactionHelper._encodeHashEIP712Transaction()`).

Moreover, according to `TransactionValidator.validateUpgradeTransaction()`, upgrade transaction is valid when `reserved[1] <= type(uint160).max`. This means that it's possible to replay upgrade transaction by providing different values to `reserved[1]` field.


## Proof of Concept

[File: TransactionValidator.sol](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/libraries/TransactionValidator.sol#L54-L57)
```
        require(_transaction.reserved[0] == 0, "ue");
        require(_transaction.reserved[1] <= type(uint160).max, "uf");
        require(_transaction.reserved[2] == 0, "ug");
        require(_transaction.reserved[3] == 0, "uo");
```

While `reserved[0]`, `reserved[2]`, `reserved[3]` must equal to `0` - the `reserved[1]` just needs to be lower or equal `type(uint160).max`.

[File: TransactionHelper.sol](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/contracts/libraries/TransactionHelper.sol#L118-L136)
```
function _encodeHashEIP712Transaction(Transaction calldata _transaction) private view returns (bytes32) {
        bytes32 structHash = keccak256(
            abi.encode(
                EIP712_TRANSACTION_TYPE_HASH,
                _transaction.txType,
                _transaction.from,
                _transaction.to,
                _transaction.gasLimit,
                _transaction.gasPerPubdataByteLimit,
                _transaction.maxFeePerGas,
                _transaction.maxPriorityFeePerGas,
                _transaction.paymaster,
                _transaction.nonce,
                _transaction.value,
                EfficientCall.keccak(_transaction.data),
                keccak256(abi.encodePacked(_transaction.factoryDeps)),
                EfficientCall.keccak(_transaction.paymasterInput)
            )
        );
```

Since `reserved` field is not being taken into consideration in `_encodeHashEIP712Transaction()`, this basically means, that it would be possible to replay some transactions with different `_transaction.reserved[1]`.

1. Let's create a valid upgrade transaction and send it. Let's assume that its `reserved` is set to `"0100"`
2. Let's take that transaction and modify `reserved[1]`: now `reserve` is `"0200"`.
3. Since `require(_transaction.reserved[1] <= type(uint160).max, "uf");` is valid (line 55 in
User will pay more gas than defined in Ethereum Yellow Paper
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#L101-L107


# Vulnerability details

## Impact

The Ethereum Yellow Paper defines two types of `G_txdata`:

```
G_txdatazero - 4 Paid for every zero byte of data or code for a transaction.
G_txdatanonzero - 16 Paid for every non-zero byte of data or code for a transaction.
```

As we see, non-zero byte of data costs more (16) than a zero byte (4).

However, `L1_GAS_PER_PUBDATA_BYTE()` implemented in the `bootloader.yul` does not distinguish between zero byte of data or code for a transaction and non-zero byte of data or code for a transaction. Every operation costs 17. This behavior implies user loss - because the user will always pay the constant amount of gas, even for non-zero byte of data or code for a transaction (which should costs less than a non-zero byte).

## Proof of Concept

[File: bootloader.yul](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/bootloader/bootloader.yul#L101-L107)
```
 /// @dev The number of L1 gas needed to be spent for
            /// L1 byte. While a single pubdata byte costs `16` gas, 
            /// we demand at least 17 to cover up for the costs of additional
            /// hashing of it, etc.
            function L1_GAS_PER_PUBDATA_BYTE() -> ret {
                ret := 17
            }
```

As demonstrated above, the number of L1 gas needed to be spend for L1 byte is always constant and it's hardcoded to 17.
User will pay 17 gas no matter if it's the zero or non-zero byte of data or code for a transaction.

According to Ethereum Yellow Paper, zero bytes costs less (4) than non-zero ones (16). This requirement is, however, not fulfilled in the `bootloader.yul`, because both zero and non-zero L1 byte costs 17.

## Tools Used

Manual code review


## Recommended Mitigation Steps

Separate costs for zero and non-zero byte of data or code for a transaction. The zero byte of data or code for a transaction should cost less.



## Assessed type

Other