xanoutas/solidity-security-findings huggingface dataset:
A malicious operator could set the timestamp to a wrong value and directly have an impact on users
Audit: 2024-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/common/Config.sol#L40-L46


# Vulnerability details


## Proof of Concept

First, would be key to note that, an operator is not fully trusted but rather they are "partially trusted" and this finding depends on them being a tad malicious which makes the expectation/invariant of them not being able to break the system not hold true.

Take a look at https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/common/Config.sol#L40-L46

```solidity
/// @dev Timestamp - seconds since unix epoch.
uint256 constant COMMIT_TIMESTAMP_NOT_OLDER = 3 days;

/// @dev Maximum available error between real commit batch timestamp and analog used in the verifier (in seconds)
uint256 constant COMMIT_TIMESTAMP_APPROXIMATION_DELTA = 1 hours;

```

One can see that the value for `COMMIT_TIMESTAMP_APPROXIMATION_DELTA` is set to 1 hour, but as [these comments suggest](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/common/Config.sol#L43), this value should instead be as low as even `12` seconds, however it's been set to a whooping `3600` seconds. _(300 times the real value it should be set as)_.

Now consider https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Executor.sol#L103-L124

```solidity
    function _verifyBatchTimestamp(
        uint256 _packedBatchAndL2BlockTimestamp,
        uint256 _expectedBatchTimestamp,
        uint256 _previousBatchTimestamp
    ) internal view {
        // Check that the timestamp that came from the system context is expected
        uint256 batchTimestamp = _packedBatchAndL2BlockTimestamp >> 128;
        require(batchTimestamp == _expectedBatchTimestamp, "tb");

        // While the fact that _previousBatchTimestamp < batchTimestamp is already checked on L2,
        // we double check it here for clarity
        require(_previousBatchTimestamp < batchTimestamp, "h3");

        uint256 lastL2BlockTimestamp = _packedBatchAndL2BlockTimestamp & PACKED_L2_BLOCK_TIMESTAMP_MASK;

        // All L2 blocks have timestamps within the range of [batchTimestamp, lastL2BlockTimestamp].
        // So here we need to only double check that:
        //@audit
        // - The timestamp of the batch is not too small.
        // - The timestamp of the last L2 block is not too big.
        require(block.timestamp - COMMIT_TIMESTAMP_NOT_OLDER <= batchTimestamp, "h1"); // New batch timestamp is too small
        require(lastL2BlockTimestamp <= block.timestamp + COMMIT_TIMESTAMP_APPROXIMATION_DELTA, "h2"); // The last L2 block timestamp is too big
    }
```

We can see that this function is used to ensure that the timestamps of the new L2 blocks are correct, now if a batch's
Frontrunning of legacy funds transfer may cause permanent DOS of Transfer of Legacy Funds to SharedBridge
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#L132


# Vulnerability details

### Detail
During the migration process, the funds from the legacy bridge is expected to be transferred to the shared bridge by calling `L1SharedBridge.transferFundsFromLegacy`. This function checks if the token to be  transferred is ETH or a Non-ETH token, if it is not ETH, the token balance of the sharedBridge is cached as `balanceBefore` and the entire token balance of the legacy bridge cached as the amount to be transferred. After this check, the amount is then transferred directly to the sharedBridge by an external call to `L1ERC20LegacyBridge.tranferTokenToSharedBridge`. Afterward, the balance of the sharedBridge is cached, and the difference between balanceAfter and balanceBefore is required to be equal to the amount.
```
src: code/contracts/ethereum/contracts/bridge/L1SharedBridge.sol;

function transferFundsFromLegacy(address _token, address _target, uint256 _targetChainId) external onlyOwner {
        if (_token == ETH_TOKEN_ADDRESS) {
           ....................................
        } else {
            uint256 balanceBefore = IERC20(_token).balanceOf(address(this));
            uint256 amount = IERC20(_token).balanceOf(address(legacyBridge));
            require(amount > 0, "ShB: 0 amount to transfer");
@>          IL1ERC20Bridge(_target).tranferTokenToSharedBridge(_token, amount);
            uint256 balanceAfter = IERC20(_token).balanceOf(address(this));
@>          require(balanceAfter - balanceBefore == amount, "ShB: wrong amount transferred");
           ........................
    }
```
### Impact
The transfer of legacy funds from `L1ERC20LegacyBridge` to `L1SharedBridge` during the migration process can be denied potentially permanently by a malicious actor, causing the migration process to fail or not complete as expected.

### Proof of Concept
This vulnerability comes in because the external transfer call to transfer this token balance to the shared bridge contract can be front run due to a race condition, since this migration would occur on ETHEREUM, and a malicious user can directly deposit tokens into the shared bridge before this transfer is completed. When the balanceAfter is called and cached, this balance would be different from what is expected, causing the require check that the difference between balanceAfter and balanceBefore to fail. 
Consider the following scenario
1. During migration process, `L1SharedBridge.transferFundsFromLegacy` is called on a token by the owner contract, within the function, after balanceBefore is cached into memory.
2. When `tranferTokenToSharedBridge` external call is made, a malicious user makes a direct transfer of the tokens to the L1SharedBridge contract, causing the  balance of the contract to be changed. When balanceAfter is cached and the difference between balanceAfter and bal
Inconsistent behavior in forking
Audit: 2024-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/main/code/system-contracts/bootloader/bootloader.yul#L1


# Vulnerability details

## Impact
Hey there, I was testing the functionalities of L2 smart contract using `yarn test-node`, and forking with foundry. And apparently there is some inconsistency in reading the code length of some addresses.

## Proof of Concept
ethereum:
1. get the url https://rpc.ankr.com/eth
2. run `cast code 0x0000000000000000000000000000000000000000 --rpc-url https://rpc.ankr.com/eth` This will give us `0x`.
3. run `cast codesize 0x0000000000000000000000000000000000000000 --rpc-url https://rpc.ankr.com/eth` This will give us 0


zksync:
1. run `yarn test-node` This will give us the RPC URL (In this case 127.0.0.1:8011)
2. run `cast code 0x0000000000000000000000000000000000000000 --rpc-url http://127.0.0.1:8011` This will give us '0x0000000101200190000000040000c13d0000000001000019000000110001042e0000008001000039000000400010043f0000000001000416000000000101004b0000000e0000c13d0000002001000039000001000010044300000120000004430000000501000041000000110001042e000000000100001900000012000104300000001000000432000000110001042e0000001200010430000000000000000000000002000000000000000000000000000000400000010000000000000000004d1f25f776f5b2892f4915d389c969ce225b4c569dea7e9e04c0df9bf1530f2a'
3. run `cast codesize 0x0000000000000000000000000000000000000000 --rpc-url http://127.0.0.1:8011` This will give us 224

Because fork testing in foundry relies on `eth_getCode` the code will read the underlying bytecode of that address, without going through the bootloader which handle the odd behavior that zksync has.

This also applies for other address, such ass precompile 0x01 ecrecover, and the other.

## Tools Used
Foundry



## Assessed type

Other
L2SharedBridge l1LegacyBridge is not set
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/L2SharedBridge.sol#L68


# Vulnerability details

## Vulnerability details

The migration steps for `L1ERC20Bridge/L2ERC20Bridge` are as follows:

https://github.com/code-423n4/2024-03-zksync/blob/main/docs/Protocol%20Section/Migration%20process.md

> II. Upgrade L1ERC20Bridge contract
> 
> 1. Upgrade L2 bridge
> 
> The new L2ERC20Bridge will upgraded to become the L2SharedBridge, and it will be backwards compatible with all messages from the old L1ERC20Bridge, so we upgrade that first as L1->L2 messages are much faster, and in the meantime we can upgrade the L1ERC20Bridge. The new L2SharedBridge can receive deposits from both the old L1ERC20Bridge and the new L1SharedBridge.
> 
> 2. Upgrade L1ERC20Bridge
> 
> We upgrade the L1ERC20Bridge, and move all ERC20 tokens to the L1SharedBridge.

Since `L2ERC20Bridge` will be updated first, and then `L1ERC20Bridge` will be updated, `L2SharedBridge` needs to be compatible with the old `L1ERC20Bridge` before `L1ERC20Bridge` is updated.

So in `L2SharedBridge.initialize()` we need to set `l1LegacyBridge = L1ERC20Bridge` and `finalizeDeposit()` to allow `l1LegacyBridge` to execute.

But the current implementation doesn't set `l1LegacyBridge`, it's always `address(0)`. 

```solidity
    function initialize(
        address _l1Bridge,
        address _l1LegecyBridge,
        bytes32 _l2TokenProxyBytecodeHash,
        address _aliasedOwner
    ) external reinitializer(2) {
        require(_l1Bridge != address(0), "bf");
        require(_l2TokenProxyBytecodeHash != bytes32(0), "df");
        require(_aliasedOwner != address(0), "sf");
        require(_l2TokenProxyBytecodeHash != bytes32(0), "df");

        l1Bridge = _l1Bridge;
        l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash;

        if (block.chainid != ERA_CHAIN_ID) {
            address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}());
            l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken);
            l2TokenBeacon.transferOwnership(_aliasedOwner);
        } else {
            require(_l1LegecyBridge != address(0), "bf2");
@>          // Missing set l1LegecyBridge？
            // l2StandardToken and l2TokenBeacon are already deployed on ERA, and stored in the proxy
        }
    }
```

The above method just checks `_l1LegecyBridge ! = address(0)`, and does not assign a value, `l1LegacyBridge` is always `adddress(0)`.

This way any messages sent by the user before update `L1ERC20Bridge` will fail because `finalizeDeposit()` will not pass the validation

## Impact

Until `L1ERC20Bridge` is updated, messages sent by the user will fail.

## Recommended Mitigation

```diff
    function initialize(
        address _l1Bridge,
        address _l1LegecyBridge,
        bytes32 _l2TokenProxyBytecodeHash,
        address _aliasedOwner
    ) external reinitializer(2
StateTransitionManager.unfreezeChain() can't execute
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

## Vulnerability details
`StateTransitionManager.unfreezeChain() ` the code is as follows:

```solidity
contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Ownable2Step {
...
    function unfreezeChain(uint256 _chainId) external onlyOwner {
        IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond();
    }
```

This method is used for `unfreeze`, but it calls `freezeDiamond()`, which makes it impossible to `unfreeze` properly.

## Impact

StateTransitionManager cannot unfreeze zkSync.

Although `admin` of `zkSync` can do `unfreeze` directly by `zkSync.unfreezeDiamond()`

However, `admin` is usually a multi-sign account, and to execute it would require a new wait for more time
during which time `zkSync` would be fully `freeze`

## Recommended Mitigation

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


## Assessed type

Context
Governance Deadlock Risk from Compromised Actors has not been fully fixed
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#L1


# Vulnerability details


## Proof of Concept

In the current [governance setup](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/governance/Governance.sol), specific actions are designated to governance actors, including the owner and the security council. These actions encompass a range of capabilities such as `cancel`, `execute`, `executeInstant`, `scheduleTransparent`, and `scheduleShadow`. Notably, the governance contract's design allows for self-calling through `execute` and `executeInstant`, extending additional privileges to the owner for functions like `updateSecurityCouncil` and `updateDelay`.

Now as explained by this [bug report](https://github.com/code-423n4/2023-10-zksync-findings/issues?q=is%3Aissue+is%3Aopen+Governance+logic+may+enter+a+deadlock+), the implications of this setup leads to two deadlock scenarios:

1. **Owner Compromise**: If the owner's security is breached, the ability to submit upgrades via `scheduleTransparent` or `scheduleShadow` is jeopardized, effectively halting future upgrades since the owner is the sole proposer.
2. **Security Council Compromise**: A compromised security council can exploit the `cancel` function to obstruct all owner-initiated upgrades. This blockade extends to critical governance changes such as the security council's update, given the owner's inability to utilize `executeInstant` and the expected nonzero proposal delay.

These vulnerabilities indicate that a single compromise can disrupt the entire governance process.

Now it's been stated that `unfixed` issues from the past contest are OOS for this contest, but that's not the case for this report since we are proving how the fix applied by protocol does not actually mitgates the issue, this is cause protocol have applied the "fixes" , as confirmed by the commit with the current `Governance.sol`, but erroneously does not fix the whole thing [and only removed the security council's access to `cancel()`](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/governance/Governance.sol#L154-L159).

## Impact

The governance framework remains susceptible to deadlock scenarios following a compromise of key actors. In this case if the owner's keys get compromised then it's automatically impossible for anyone to schedule any upgrades since they are the only ones that have access to this function.

## Recommended Mitigation Steps

As previously suggested, enforce a minimum delay for upgrades, that way there is an insurance of sufficient time for response actions by stakeholders, before `updateDelay()` goes through.

 



## Assessed type

Access Control
Users are overcharged due to how gas to pay is calculated when publishing data and clearing the state
Audit: 2024-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/contracts/L1Messenger.sol#L44-L64


# Vulnerability details


## Proof of Concept

First take a look at https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/contracts/L1Messenger.sol#L44-L64

```solidity
    uint256 internal constant KECCAK_ROUND_GAS_COST = 40;

    uint256 internal constant KECCAK_ROUND_NUMBER_OF_BYTES = 136;

    function keccakGasCost(uint256 _length) internal pure returns (uint256) {
        return KECCAK_ROUND_GAS_COST * (_length / KECCAK_ROUND_NUMBER_OF_BYTES + 1);
    }

    uint256 internal constant SHA256_ROUND_GAS_COST = 7;

    uint256 internal constant SHA256_ROUND_NUMBER_OF_BYTES = 64;

    function sha256GasCost(uint256 _length) internal pure returns (uint256) {
        return SHA256_ROUND_GAS_COST * ((_length + 8) / SHA256_ROUND_NUMBER_OF_BYTES + 1);
    }

```

Evidently there are constants placed and protocol have used the logic of adding one after the division so as to sort out issues where the numerator is "<" than the denominator causing the equation to return 0, case is that this logic is somewhat wrong and could be very harmful to some users.

Now consider the three instances where it's calculated, i.e in [sendL2ToL1Log()](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/contracts/L1Messenger.sol#L89-L90)

```solidity
        uint256 gasToPay = keccakGasCost(L2_TO_L1_LOG_SERIALIZE_SIZE) + 2 * keccakGasCost(64);
        SystemContractHelper.burnGas(Utils.safeCastToU32(gasToPay), 0);
```

In [sendToL1()](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/contracts/L1Messenger.sol#L148-L154)

```solidity
        uint256 gasToPay = keccakGasCost(L2_TO_L1_LOG_SERIALIZE_SIZE) +
            3 *
            keccakGasCost(64) +
            gasSpentOnMessageHashing +
            COMPUTATIONAL_PRICE_FOR_PUBDATA *
            pubdataLen;
        SystemContractHelper.burnGas(Utils.safeCastToU32(gasToPay), uint32(pubdataLen));
```

And lastly in [requestBytecodeL1Publication()](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/contracts/L1Messenger.sol#L175-L179)

```solidity
        uint256 gasToPay = sha256GasCost(bytecodeLen) +
            keccakGasCost(64) +
            COMPUTATIONAL_PRICE_FOR_PUBDATA *
            pubdataLen;
        SystemContractHelper.burnGas(Utils.safeCastToU32(gasToPay), uint32(pubdataLen));
```

Evidently, in all three instances, we can see that there is a constant query on `keccakGasCost(64)` which would always return `40` [due to the rounding down in the calculation](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/contracts/L1Messenger.sol#L51), this is cau
If an L1 Receiver gets or is blacklisted by an L1 Token, funds will be permanently locked in L1SharedBridge
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/L2SharedBridge.sol#L123
https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/bridge/L1SharedBridge.sol#L452


# Vulnerability details

## Impact
The inability to update the `_l1Receiver` address upon blacklisting could lead to permanent locking of funds within the `L1SharedBridge`.

## Proof of Concept
In `L2SharedBridge.sol::withdraw()`, users can initiate a withdrawal by burning funds on the contract and sending a message to L1, where tokens would be unlocked.
Note that funds will be sent to the `_l1Receiver`, and there is no way to change this address after calling `withdraw()` since the funds are burned on L2 and the message has been sent to L1.
[Link to code](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/zksync/contracts/bridge/L2SharedBridge.sol#L123)
```
    function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external override {
        require(_amount > 0, "Amount cannot be zero");

        IL2StandardToken(_l2Token).bridgeBurn(msg.sender, _amount);

        address l1Token = l1TokenAddress[_l2Token];
        require(l1Token != address(0), "yh");

        bytes memory message = _getL1WithdrawMessage(_l1Receiver, l1Token, _amount);
        L2ContractHelper.sendMessageToL1(message);

        emit WithdrawalInitiated(msg.sender, _l1Receiver, _l2Token, _amount);
    }
```

Then, the user can unlock the funds by calling `finalizeWithdrawal` on `L1SharedBridge`, which sends the tokens to the `_l1Receiver` specified on L2:
[Link to code](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/bridge/L1SharedBridge.sol#L452)
```
            // Withdraw funds
            IERC20(l1Token).safeTransfer(l1Receiver, amount);
        }
```

Given that some widely used tokens like USDC, Tether, etc., have blacklisting mechanisms, in the case when `_l1Receiver` gets or is blacklisted in an L1 token like USDC, Tether, etc., funds will be permanently locked within `L1SharedBridge`.

## Tools Used
Foundry
VSCode

## Recommended Mitigation Steps
Empower users with the ability to update the `_l1Receiver` address in case of blacklisting or other exceptional circumstances.

One of the possible solutions is to implement an ERC20 token, let's call it `ZToken`, which is pegged to the L1 token. In this proposed approach, during the withdrawal process, the `_l1Receiver` would receive `ZToken` instead of the L1 token directly. Subsequently, the `_l1Receiver` can redeem the `ZToken` for the corresponding L1 token. This mechanism provides flexibility in scenarios where the redemption fails due to the address being blacklisted. If the redemption transaction fails because the `_l1Receiver` is blacklisted on t
Insufficient randomness in create2 computation would allow prediction of l2 token addresses and potential DOS attacks.
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/L2SharedBridge.sol#L158
https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/zksync/contracts/bridge/L2SharedBridge.sol#L153


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

## Tools Used
Manual Review

## Recommended Mitigation Steps
It is recommended to use the user's input to generate the salt when using create2 to deploy the contract of the l2token, this is in order to allow sufficient randomness and potential issues.
```diff

```


## Assessed type

Invalid Validation