xanoutas/solidity-security-findings huggingface dataset:
Add storage gasp to avoid overwriting storage slots
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/ZkSyncStateTransitionBase.sol#L11
https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/upgrades/BaseZkSyncUpgrade.sol#L44
https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/upgrades/BaseZkSyncUpgradeGenesis.sol#L17


# Vulnerability details

## Impact
For the Upgradeable contracts, the inherited contract `ZkSyncStateTransitionBase` should contain storage gaps to avoid issues. If the Contracts inheriting `BaseZkSyncUpgrade` are upgraded with additional variables, it should not conflict with storage slots previously written on the proxy

## Proof of Concept
See https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps

## Tools Used
Manual Review

## Recommended Mitigation Steps

add storage gaps like below

```solidity
uint256[49] __gap;
```


## Assessed type

Upgradable
Permanent loss of the L1 -> L2 refund gas when creating a new chain
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#L194


# Vulnerability details

## Impact

Setting the `refundRecipient` of a L1 -> L2 transaction to `0` will send the leftover gas of such a transaction to the `address(0)`. As the hyperchain ecosystem evolves, more and more hyperchains will be created, so the amount lost over time can be considerable.

## Proof of Concept

When the `bridgeHub` creates a new hyperchain, it will eventually call

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

```solidity
    /// @notice called by Bridgehub when a chain registers
    function createNewChain(
        uint256 _chainId,
        address _baseToken,
        address _sharedBridge,
        address _admin,
        bytes calldata _diamondCut
    ) external onlyBridgehub { ... }
```

If we follow the execution path, we see that it builds from scratch the L2 transaction to be submitted in

```solidity
    /// @dev we have to set the chainId at genesis, as blockhashzero is the same for all chains with the same chainId
    function _setChainIdUpgrade(uint256 _chainId, address _chainContract) internal {
        bytes memory systemContextCalldata = abi.encodeCall(ISystemContext.setChainId, (_chainId));
        uint256[] memory uintEmptyArray;
        bytes[] memory bytesEmptyArray;

        L2CanonicalTransaction memory l2ProtocolUpgradeTx = L2CanonicalTransaction({
            txType: SYSTEM_UPGRADE_L2_TX_TYPE,
            from: uint256(uint160(L2_FORCE_DEPLOYER_ADDR)),
            to: uint256(uint160(L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR)),
            gasLimit: $(PRIORITY_TX_MAX_GAS_LIMIT),
            gasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA,
            maxFeePerGas: uint256(0),
            maxPriorityFeePerGas: uint256(0),
            paymaster: uint256(0),
            // Note, that the priority operation id is used as "nonce" for L1->L2 transactions
            nonce: protocolVersion,
            value: 0,
            reserved: [uint256(0), 0, 0, 0],
            data: systemContextCalldata,
            signature: new bytes(0),
            factoryDeps: uintEmptyArray,
            paymasterInput: new bytes(0),
            reservedDynamic: new bytes(0)
        });

        ...
    }
```

and it sets all the values inside the `reserved` array field to `0`. This is a bad decission as it is very difficult to estimate the exact amount of gas that a tx will need, so there will be some dust that must be returned to the caller. From the previous contest, the `reserved[1]` field denotes the `refundAddress`, so if it is `0`, the bootloader will naively send the refund gas to the `address(0)`, which means those funds will be permanently l
Freezed Chain will never be unfreeze since `StateTransitionManager::unfreezeChain` is calling `freezeDiamond` instead of `unfreezeDiamond`.
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#L165-L167


# Vulnerability details



## Vulnerability Details & POC

`StateTransitionManager::unfreezeChain` function is meant for unfreeze the freezed chain of passed `_chainId` param. While `freezeChain` function is meant for freeze the chain according to passed \_chainId. But `freezeChain` and `unfreezeChain` both functions are calling same function `freezeDiamond` by same line `IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond()` by mistake. So both these function will only freeze the chain.

Also there is no other function inside `StateTransitionManager.sol` contract which is calling `unfreezeDiamond`. `unfreezeDiamond` is function defined in `Admin.sol` where the call is going since `IZkSyncStateTransition` also inherits IAdmin which have freezeDiamond and unfreezeDiamond both functions. But `unfreezeDiamond` is not called from `unfreezeChain` function. So freezed chain will never be unfreeze.

`unfreezeChain` also have wrong comment instead of writing unfreezes it writes freezes.
It seems like dev just copy pasted without doing required changes.

### Vulnerable Code

[code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol#L165-L167](https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol#L165C4-L167C6)

```solidity

159:    /// @dev freezes the specified chain
160:    function freezeChain(uint256 _chainId) external onlyOwner {
161:        IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond();
     }

164:    /// @dev freezes the specified chain
165:    function unfreezeChain(uint256 _chainId) external onlyOwner {
166:        IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond();//@audit `freezeDiamond` called instead of `unfreezeDiamond`

    }

```

(https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/state-transition/chain-interfaces/IZkSyncStateTransition.sol#L15)

[IZkSyncStateTransition is inheriting IAdmin](https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/state-transition/chain-interfaces/IZkSyncStateTransition.sol#L15)
and by IZkSyncStateTransition wrapping instance is prepared to call freezeDiamond.

```solidity
15: interface IZkSyncStateTransition is IAdmin, IExecutor, IGetters, IMailbox {
```

[IAdmin interfaces have both functions](https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/state-transition/chain-interfaces/IAdmin.sol#L52C5-L58C41)

```solidity
13:  interface IAdmin is IZkSyncStateTransitionBase {

    ....

52:  /// @notice Instantly pause the functionality of all freezable facets & their selectors
     /// @dev Only the governance mechanism may freeze Diamond Proxy
54:    function freezeDiamond() external;

     /// @noti
Irreversible Chain Freeze due to Identical Freeze/Unfreeze 
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#L159-L167


# Vulnerability details

## vulnerability details 
There is a  flaw in the contract with chain management logic. Specifically, in the functions freezeChain and unfreezeChain  those functions  are implemented with identical logic, both invoking the freezeDiamond() method on the IZkSyncStateTransition interface. and This results in an inability to unfreeze a chain once it has been frozen, as the intended mechanism to reverse a freeze operation unfreezeDiamond or equivalent is not called—indeed, it appears absent.
the contract fails to provide a mechanism to unfreeze a chain once frozen. This misalignment between the function's name/intent and its actual behavior can lead to irreversible operational consequences.

## Impact

this vulnerability could lead to the permanent cessation of operations on any chain managed by the contract. and This would disrupt all users and transactions associated with the frozen chain, potentially leading to financial losses, 

Attack Scenario:

An attacker or even an admin with malicious intent, or an administrator making an operational mistake, could invoke the freezeChain function to halt all activities on a targeted chain. Due to the flawed implementation, legitimate users or administrators would find themselves unable to revert this state change, effectively locking the chain in a frozen state indefinitely.

## Proof of Concept
i fuzz with scenario and a get this as result
```python 
class MockStateTransitionManager:
    def __init__(self):
        # Assuming chain states are either 'active' or 'frozen'
        self.chain_states = {}

    def freezeChain(self, chain_id):
        """Freezes the specified chain"""
        self.chain_states[chain_id] = 'frozen'
    
    def unfreezeChain(self, chain_id):
        """Unfreezes the specified chain - Incorrectly calls freezeChain's logic"""
        self.chain_states[chain_id] = 'frozen'  # This should be 'active' to unfreeze

    def getChainState(self, chain_id):
        """Returns the state of the specified chain"""
        return self.chain_states.get(chain_id, 'active')  # Default state is 'active'

# Instantiate the mock contract
mock_contract = MockStateTransitionManager()

# Test the freeze and unfreeze functionality with a fuzzing approach
# We'll vary the chain_id over a range to simulate different scenarios
# The goal is to confirm if unfreezeChain effectively unfreezes a chain or not

chain_states_before = {}
chain_states_after_freeze = {}
chain_states_after_unfreeze = {}

for chain_id in range(1, 11):  # Fuzzing with 10 different chain IDs
    # Initially record the chain state
    chain_states_before[chain_id] = mock_contract.getChainState(chain_id)
    
    # Freeze the chain
    mock_contract.freezeChain(chain_id)
    chain_states_after_freeze[chain_id] = mock_con
Gas refund to paymaster may silently fail
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#L1583-L1595


# Vulnerability details


zkSync supports account abstraction natively. `bootloader.yul`, which is entrypoint to EraVM, will execute multiple transactions, taking some parts of the EVM on its shoulders, like refunding gas unspent on transaction to user.

When a refund for transaction is processed, function `refundCurrentL2Transaction` is called, which in turn uses `ZKSYNC_NEAR_CALL_callPostOp()` -  account abstraction defined `postOp` (reference implementation of [ERC-4337 postOp](https://github.com/eth-infinitism/account-abstraction/blob/75f02457e71bcb4a63e5347589b75fa4da5c9964/contracts/samples/TokenPaymaster.sol#L105-L112)).

However, low level call result of `postOp` is popped, and it's the result of calling paymaster to retrieve the refund. If it fails for whatever reason, it won't be terated as a failure, and the paymaster will loose refund. 

## Impact
Paymaster gas refund loss

## Proof of Concept
The issue starts with `processL2Tx`:
```javascript
            function processL2Tx(
                txDataOffset,
                resultPtr,
                transactionIndex,
                gasPerPubdata
            ) {
// [...]

                let gasSpentOnExecute := 0
                let success := 0
                success, gasSpentOnExecute := l2TxExecution(txDataOffset, gasLeft, basePubdataSpent, reservedGas, gasPerPubdata)

                let refund := 0
                let gasToRefund := sub(gasLeft, gasSpentOnExecute)
                if lt(gasLeft, gasSpentOnExecute){
                    gasToRefund := 0
                }

                // Note, that we pass reservedGas from the refundGas separately as it should not be used
                // during the postOp execution.
                refund := refundCurrentL2Transaction(
                    txDataOffset,
                    transactionIndex,
                    success,
                    gasToRefund,
                    gasPrice,
                    reservedGas,
                    basePubdataSpent,
                    gasPerPubdata
                )

                notifyAboutRefund(refund)
```

After transaction is executed in `l2TxExecution()` function, `gasToRefund` is taken from `gasLeft - gasSpentOnExecute`. This value is then passed to `refundCurrentL2Transaction()` function.

```javascript
            function refundCurrentL2Transaction(
                txDataOffset,
                transactionIndex,
                success, 
                gasLeft,
                gasPrice,
                reservedGas,
                basePubdataSpent,
                gasPerPubdata
            ) -> finalRefund {
                setTxOrigin(BOOTLOADER_FORMAL_ADDR())

                finalRefund := 0

                let innerTxDataOffset := add(txDataOffset, 32)

                let paymaster := getPaymaster(innerTx
No ability to unfreeze the chain
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#L165-L167


# Vulnerability details

The `StateTransitionManager` contract in the zkSync system contains two functions, `freezeChain` and `unfreezeChain`, intended to manage the operational state of a specified blockchain by freezing and unfreezing it, respectively. Both functions are designed to either halt or resume operations, particularly in response to detected vulnerabilities or the need for maintenance. However, `unfreezeChain()`  function incorrectly calls `freezeDiamond()` instead of an expected counterpart method `unfreezeDiamond()` that would revert the chain's state. This means that chain that is once frozen, is blocked forever.

## Impact
Permament DoS of a chain after first freeze.

## Proof of Concept

Please add following test to `FreezeChain.t.sol` and execute it:

```javascript
    function test_FreezingUnfreezingChain() public {
        createNewChain(getDiamondCutData(diamondInit));

        address newChainAddress = chainContractAddress.stateTransition(chainId);
        GettersFacet gettersFacet = GettersFacet(newChainAddress);
        bool isChainFrozen = gettersFacet.isDiamondStorageFrozen();
        assertEq(isChainFrozen, false);

        vm.stopPrank();
        vm.startPrank(governor);

        chainContractAddress.freezeChain(block.chainid);
        vm.expectRevert(bytes.concat("q1")); // storage frozen, cannot unfreeze
        chainContractAddress.unfreezeChain(block.chainid);
    }
```

The tests runs successfully, meaning that it reverts with `q1` error - storage frozen:
```
Running 1 test for test/foundry/unit/concrete/state-transition/StateTransitionManager/FreezeChain.t.sol:freezeChainTest
[PASS] test_FreezingUnfreezingChain() (gas: 3397331)
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.46ms

Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
```
## Tools Used

Manual Review

## Recommended Mitigation Steps
Change code to unfreeze diamond:

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


## Assessed type

DoS
Users are charged too much as the memory overhead gas
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#L128-L137
https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/bootloader/bootloader.yul#L37-L53
https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/bootloader/bootloader.yul#L2010-L2017


# Vulnerability details


## Proof of Concept

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

```solidity
    function getOverheadForTransaction(
        uint256 _encodingLength
    ) internal pure returns (uint256 batchOverheadForTransaction) {
        // The overhead from taking up the transaction's slot
        batchOverheadForTransaction = TX_SLOT_OVERHEAD_L2_GAS;

        // The overhead for occupying the bootloader memory can be derived from encoded_len
        uint256 overheadForLength = MEMORY_OVERHEAD_GAS * _encodingLength;
        batchOverheadForTransaction = Math.max(batchOverheadForTransaction, overheadForLength);
    }
```

This function is used to get the overhead that a user is to pay upfront for a tx, now both `TX_SLOT_OVERHEAD_L2_GAS` and `MEMORY_OVERHEAD_GAS` are `10000` and `10` respectively.

Navigating to the bootloader we can see that the logic/values are as expected the same, i.e https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/bootloader/bootloader.yul#L37-L53

```yul

            function TX_SLOT_OVERHEAD_GAS() -> ret {
                ret := 10000
            }


            function MEMORY_OVERHEAD_GAS() -> ret {
                ret := 10
            }
```

And https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/bootloader/bootloader.yul#L2010-L2017

```yul
            function getTransactionUpfrontOverhead(
                txEncodeLen
            ) -> ret {
                ret := max(
                    safeMul(txEncodeLen, MEMORY_OVERHEAD_GAS(), "iot"),
                    TX_SLOT_OVERHEAD_GAS()
                )
            }
```

Evidently, the `MEMORY_OVERHEAD_GAS` value has been set to 10, issue with this is that, according to [the docs](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/docs/Smart%20contract%20Section/zkSync%20fee%20model.md) we can see that to get this value this is the formula used `TX_MEMORY_OVERHEAD_GAS = MAX_GAS_PER_BATCH / MAX_MEMORY_FOR_BATCH`.

Now would be key to note that, for L1->L2 transactions the `MAX_GAS_PER_BATCH` is considered to be `80kk` and `MAX_MEMORY_FOR_BATCH` is `32 * 600_000`, i.e `19.2kk`.

Causing the `TX_MEMORY_OVERHEAD_GAS = 80kk/19.2kk` ~= `4,17`, now protocol d
StateTransitionManager Won't Be Able to Unfreeze a Chain After Freezing It
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 
This bug hampers the `StateTransitionManager`'s ability to manage chain freezing.

## Proof of Concept
In `StateTransitionManager.sol` there are two functions available to freeze and unfreeze a chain:
1. `freezeChain`
```
    /// @dev freezes the specified chain
    function freezeChain(uint256 _chainId) external onlyOwner {
        IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond();
    }
```
2. `unfreezeChain`
```
    /// @dev freezes the specified chain
    function unfreezeChain(uint256 _chainId) external onlyOwner {
        IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond();
    }
```
The problem is that both functions are calling `freezeDiamond()` which will freeze the chain.
`freezeDiamond()`:
```
    function freezeDiamond() external onlyAdminOrStateTransitionManager {
        Diamond.DiamondStorage storage diamondStorage = Diamond.getDiamondStorage();

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

        emit Freeze();
    }
```
It means that if a chain gets freezed by `StateTransitionManager` or the Admin of a chain, `StateTransitionManager` won't be able to unfreeze the chain again.

Note that the admin of a chainId is able to call `unfreezeDiamond` directly from the `Admin.sol` facet, but this does not mitigate the bug in the `unfreezeChain` function. Despite the admin's capability to unfreeze the chain, the bug persists as the `StateTransitionManager` should adhere to the invariants by being able to both freeze and unfreeze a chain.

## Tools Used
VSCode 

## Recommended Mitigation Steps
```diff
diff --git a/StateTransitionManager.sol.orig b/StateTransitionManager.sol
index 0c27439..c74df7a 100644
--- a/StateTransitionManager.sol.orig
+++ b/StateTransitionManager.sol
@@ -161,9 +161,9 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own
         IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond();
     }
 
-    /// @dev freezes the specified chain
+    /// @dev unfreezes the specified chain
     function unfreezeChain(uint256 _chainId) external onlyOwner {
-        IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond();
+        IZkSyncStateTransition(stateTransition[_chainId]).unfreezeDiamond();
     }
 
     /// @dev reverts batches on the specified chain
```


## Assessed type

Error