xanoutas/solidity-security-findings huggingface dataset:
Upgraded Q -> 2 from #28 [1702482522469]
Audit: 2023-10-zksync-findings
Severity: MEDIUM

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

 QA-01. L1ERC20Bridge._verifyDepositLimit doesn’t track bridged/failed amount when limitation is off for token.
Description
When user bridges erc20 token through the L1ERC20Bridge, then _verifyDepositLimit function is called, which should check if user doesn’t restrict limitations. https://github.com/code-423n4/2023-10-zksync/blob/main/code/contracts/ethereum/contracts/bridge/L1ERC20Bridge.sol#L340-L350

    function _verifyDepositLimit(address _l1Token, address _depositor, uint256 _amount, bool _claiming) internal {
        IAllowList.Deposit memory limitData = IAllowList(allowList).getTokenDepositLimitData(_l1Token);
        if (!limitData.depositLimitation) return; // no deposit limitation is placed for this token


        if (_claiming) {
            totalDepositedAmountPerUser[_l1Token][_depositor] -= _amount;
        } else {
            require(totalDepositedAmountPerUser[_l1Token][_depositor] + _amount <= limitData.depositCap, "d1");
            totalDepositedAmountPerUser[_l1Token][_depositor] += _amount;
        }
    }
As you can see, depositLimitation can be switched on and off for the token. In case if it’s on, then when user bridges, his totalDepositedAmountPerUser increases and in case if deposit has failed, then it’s decreased.

As you can see, limit can be set many times. So it’s possible that limit will be working, then will be switched off and then will be switched on again.

The problem is that when limitation is switched off, then totalDepositedAmountPerUser is not changed anymore and nothing is tracked. So when it will be switched on again, then user may already use all his limit, but totalDepositedAmountPerUser will not contain that information.

Recommendation
Increase and decrease totalDepositedAmountPerUser even if limitation is switched off. So when it will be switched on again, totalDepositedAmountPerUser variable will be up to date for user.
Upgraded Q -> 2 from #456 [1702482514265]
Audit: 2023-10-zksync-findings
Severity: MEDIUM

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

 [L-01] L1ERC20Bridge._verifyDepositLimit doesn’t handle user deposits correctly if the token depositCap is changed
Details
Let’s see the following scenario that illustrates the issue:

first the user deposits to bridge his ERC20 tokens from L1 to L2 and the value of totalDepositedAmountPerUser[_l1Token][_depositor] will be updated.
Then the owner of the AllowList removed the deposit cap of that token.
The user wants to claim his L2-L1 failed deposit; but this value will not be deducted from his totalDepositedAmountPerUser;
Then the admin adds a deposit cap for this token; but the totalDepositedAmountPerUser[_l1Token][_depositor] will not reflect the actual deposited amount of the user as the failed deposit hasn’t been deducted from his total amounts.
So this will result in limiting the user’s deposit limit as his failed deposit hasn’t been deducted when he claimed it when the token didn’t have a depositCap.
Upgraded Q -> 2 from #379 [1702482426013]
Audit: 2023-10-zksync-findings
Severity: MEDIUM

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

 Empty contracts hash doesn’t respect zkSync’s bytecode hash format
The code hash returned by the getCodeHash function does not comply with zkSync’s bytecode hash format when querying an empty account, a constructing contract or a precompiled contract.

The getCodeHash function is supposed to simulate the behavior of the EXTCODEHASH EVM opcode, as per the EIP-1052 specification. However, zkSync redefined how bytecode hashes are defined:

“”" On zkSync the bytecode hashes are stored in the following format:

The 0th byte denotes the version of the format. Currently the only version that is used is “1”.
The 1st byte is 0 for deployed contracts’ code and 1 for the contract code that is being constructed.
The 2nd and 3rd bytes denote the length of the contract in 32-byte words as big-endian 2-byte number.
The next 28 bytes are the last 28 bytes of the sha256 hash of the contract’s bytecode.
The bytes are ordered in little-endian order (i.e. the same way as for bytes32 ). “”"
getCodeHash returns the bytecode hash as stored in the AccountCodeStorage system contract (i.e. complying with zkSync’s definition of a bytecode hash), except for empty accounts, constructing contracts and precompiled contracts, for which it returns keccak256("") == 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470.

The valid value would be either 0x0100000086f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 or 0x0101000086f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 depending on whether the contract is constructing or not.

The impact is that smart contracts can’t rely on bytecode hash to know the bytecode size of an account. As the size in the hash is messed up for impacted accounts, smart contract will not consider those accounts as zero bytecode size ones. The decoded size from the hash would 0x4601 = 17921 instead of 0x0000.

Note: impacted accounts are empty, constructing and precompiled contracts.

Classification
CWE-1068: Inconsistency Between Implementation and Documented Design
Upgraded Q -> 2 from #28 [1702239136163]
Audit: 2023-10-zksync-findings
Severity: MEDIUM

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

 QA-05. Inconsistency in different l1->l2 messages
Description
When user requests l1->l2 message, then he can call ContractDeployer in order to create new contract. Or he can do request without new contract creation.

When tx will be executed by bootloader on l2, then in case if it is not creating new contract, then user’s nonce will not be changed anyhow as there is no any communication with user’s account. But when new contract will be created, then user’s nonce will be increased.

As result in case if user didn’t do any tx on l2 yet, then his account is considered not active. Then if he triggers usual tx from l1, then his account is still not active. But if he deploys contract, then his account becomes active, so hashcode and code size is shown for it.
Upgraded Q -> 2 from #534 [1702238994709]
Audit: 2023-10-zksync-findings
Severity: MEDIUM

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

 L-2: CURRENT_MAX_PRECOMPILE_ADDRESS is wrong and causes inconsistent precompile codehashes
CURRENT_MAX_PRECOMPILE_ADDRESS is currently SHA256_SYSTEM_CONTRACT (0x02), but should be ECMUL_SYSTEM_CONTRACT (0x07).

As a consequence, AccountCodeStorage.getCodeHash(…) leads to inconsistent results:

getCodeHash(ECRECOVER_SYSTEM_CONTRACT) == EMPTY_STRING_KECCAK
getCodeHash(SHA256_SYSTEM_CONTRACT) == EMPTY_STRING_KECCAK
getCodeHash(ECADD_SYSTEM_CONTRACT) == 0x00
getCodeHash(ECMUL_SYSTEM_CONTRACT) == 0x00
Upgraded Q -> 2 from #1127 [1701027324065]
Audit: 2023-10-zksync-findings
Severity: MEDIUM

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

 LOW) Wrong enforcement on bytecode version
In the circuits, this enforcement is inverted

        state
            .state_get_from_queue
            .conditionally_enforce_true(cs, version_hash_matches);
It should be:

        version_hash_matches
            .conditionally_enforce_true(cs, state.state_get_from_queue);
In a future version, contracts with old version may be executed incorrectly. Line: https://github.com/code-423n4/2023-10-zksync/blob/main/code/era-zkevm_circuits/src/code_unpacker_sha256/mod.rs#L208-L210
Users can pay more and possibly  overpay than necessary for gas when deploying bytecode to Layer 1 (L1).
Audit: 2023-10-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/KnownCodesStorage.sol#L97


# Vulnerability details

## Impact
When publishing bytecode to L1, users could overpay for the transaction as gasToPay in _sendBytecodeToL1() can be higher than actual gas burned. The reason is that the size of the pubdata to be published to L1 will be in multiples of 32 due to ABI encoding, but the computation of gasToPay does not take that into account and uses a fixed overhead based on BYTECODE_PUBLISHING_OVERHEAD.

## Proof of Concept
```
    function _sendBytecodeToL1(bytes32 _bytecodeHash, bytes32 _l1PreimageHash, uint256 _l1PreimageBytesLen) internal {
        // Burn gas to cover the cost of publishing pubdata on L1

        // Get the cost of 1 pubdata byte in gas
        uint256 meta = SystemContractHelper.getZkSyncMetaBytes();
        uint256 pricePerPubdataByteInGas = SystemContractHelper.getGasPerPubdataByteFromMeta(meta);

        uint256 gasToPay = (_l1PreimageBytesLen + BYTECODE_PUBLISHING_OVERHEAD) * pricePerPubdataByteInGas;
        _burnGas(Utils.safeCastToU32(gasToPay));

        // Send a log to L1 that bytecode should be known.
        // L1 smart contract will check the availability of bytecodeHash preimage.
        SystemContractHelper.toL1(true, _bytecodeHash, _l1PreimageHash);
    }
```
## Tools Used
Manual

## Recommended Mitigation Steps
Consider adjusting the gasToPay computation based on multiple of 32 instead of using a fixed overhead amount of BYTECODE_PUBLISHING_OVERHEAD.


## Assessed type

Other
Address collision can lead to unexpected consequences
Audit: 2023-10-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/matter-labs/era-system-contracts/blob/main/contracts/ContractDeployer.sol#L1


# Vulnerability details

## Impact
Address collision to 0x01 or 0x00 will lead to unexpected consequences.

## Proof of Concept
https://github.com/sherlock-audit/2023-07-kyber-swap-judging/issues/90
Given the discussion, collision possibility is 2^80

## Tools Used
https://github.com/matter-labs/era-system-contracts/issues/6

## Recommended Mitigation Steps



## Assessed type

Access Control
Use relayers instead of owner to avoid single point of failure
Audit: 2023-10-zksync-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
In the event that the owner's wallet is compromised the contracts like the Governor contracts and ValidTimeLock becomes compromised. It is advised to use relayers or role with option to remove or add roles given to the admins or security council in case of Governor

## Proof of Concept
If the address of the admin in the Governor contract and ValidTimeLock contract is compromised, all functions becomes compromised and in the case where onlyOwner functions exists like `scheduleTransparent` and `setValidator`, The Attacker can gain control of this contract and its derivative and manually add insidious validators or security council members to the contracts.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Use Roles and Relayers Instead


## Assessed type

Access Control
Risk of race condition in witness allocation may result in multiple threads eventually retrieving the same witness or overwriting each other's changes
Audit: 2023-10-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/matter-labs/era-zkevm_circuits/blob/main/src/storage_application/mod.rs#L128-L138


# Vulnerability details

## Summary

The methods in `ConditionalWitnessAllocator` such as `conditionally_allocate_with_default` and `conditionally_allocate_with_default_biased` use a shared `Arc<RwLock<VecDeque<EL::Witness>>>` to manage witnesses. However, the code seems to be vulnerable to race conditions that could lead to unexpected behavior if two or more threads attempt to allocate a witness at the same time.

## Vulnerability Detail

When a thread attempts to allocate a witness, it acquires a write lock on the `witness_source` and pops the front element. If two or more threads attempt to do this simultaneously, there's a chance that they may pop the same witness or encounter a race condition. Even though Rust's memory safety guarantees would prevent data races, logical race conditions are still possible.

The specific code in question is:

```rust
let witness = if should_allocate == true {
    let mut guard = witness.write().expect("not poisoned");
    let witness_element = guard.pop_front().expect("not empty witness");
    drop(guard);
    witness_element
} else {
    let witness_element = (default_values_closure)();
    witness_element
};
```

This snippet is problematic because, while acquiring the write lock guarantees that the actual mutation of the `witness_source` is thread-safe, the decision to pop the element from the queue (based on `should_allocate`) is not atomic with the actual popping.

## Impact

This issue can lead to unexpected behavior where multiple threads could eventually retrieve the same witness or overwrite each other's changes, leading to unpredictable outcomes in the application's state.

## Proof of Concept

Reproducing this vulnerability would require creating multiple threads that simultaneously attempt to allocate witnesses. In a real-world scenario, a high throughput of requests could trigger this race condition, thus leading to inconsistent state in the CA.

## Tools Used

Manual review + GPT for further text enhancements.

## Recommended Mitigation Steps

1. The simplest way to fix this would be to move the `should_allocate` check inside the write lock, ensuring atomicity:

```rust
let mut guard = witness.write().expect("not poisoned");
let witness_element = if should_allocate {
    guard.pop_front().expect("not empty witness")
} else {
    (default_values_closure)()
};
drop(guard);
```

2. Consider using a thread-safe queue like `crossbeam::queue::ArrayQueue` or other concurrent data structures that handle concurrent pushes/pops safely without the need for manual locking.


## Assessed type

Context
Diamondcut should use delay.
Audit: 2023-10-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/libraries/Diamond.sol#L95


# Vulnerability details

## Impact
Users dont have enough time to decide if they are going to opt out of new changes.

## Proof of Concept

Diamouncut function does not have time delay check.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Implement delay for upgrade.


## Assessed type

Timing
The code in "UnsafeBytesCalldata" employs "magic numbers," which are hard-coded numerical values without clear explanation or context.
Audit: 2023-10-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/libraries/UnsafeBytesCalldata.sol#L17-L31


# Vulnerability details

## Impact
The "UnsafeBytesCalldata" library currently contains what might appear as unconventional numerical values, often referred to as "magic numbers." These values are used in functions like readUint16 and readUint64 for byte manipulation.

## Proof of Concept
In this particular case, while these numbers are unique to the context, it might not be essential to extract them as constants. However, it is recommended to add comments to the code to explain the reasoning behind these specific values and why they are correct for their purpose. These comments would provide valuable context to anyone reading or maintaining the code, making it more understandable and ensuring that the correctness of these values is clear.

```
function readUint16(bytes calldata _bytes, uint256 _start) internal pure returns (uint16 result) {
    assembly {
        let offset := sub(_bytes.offset, 30)
        result := calldataload(add(offset, _start))
    }
}

function readUint64(bytes calldata _bytes, uint256 _start) internal pure returns (uint64 result) {
    assembly {
        let offset := sub(_bytes.offset, 24)
        result := calldataload(add(offset, _start))
    }
}
```

## Tools Used
Manual / VsCode

## Recommended Mitigation Steps
Enhance the code by adding comments to clarify the rationale behind the seemingly unusual numerical values (often referred to as "magic numbers") in functions like readUint16 and readUint64. Explain why these specific values are chosen and how they are correct for their intended purpose.



## Assessed type

Decimal
BootloaderUtilities, TransactionHelper.sol : legacy transaction misses to check `maxFeePerErgs==getMaxPriorityFeePerErg` when creating the txHash
Audit: 2023-10-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/libraries/TransactionHelper.sol#L102
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/BootloaderUtilities.sol#L27


# Vulnerability details

## Impact

Legacy TxHash is built without valid check which could lead to failure of transaction exaction.

## Proof of Concept

`TransactionHelper` - library contract has set of function to build the transaction has depend of type of transaction.

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/libraries/TransactionHelper.sol#L100-L114

This library contract is used in `BootloaderUtilities` contract.

As per zkEVM documents, for legacy transaction, there should be following checks in place while constructing the txHash.

https://github.com/code-423n4/2023-10-zksync/blob/main/docs/Smart%20contract%20Section/System%20contracts%20bootloader%20description.md#:~:text=txType%3A%200.%20It,EIP1559%20tx%20type.

```solidity
  maxFeePerErgs=getMaxPriorityFeePerErg since it is pre-EIP1559 tx type.
  reserved1..reserved4 as well as paymaster are 0. paymasterInput is zero.
```

but, when we look at the txHash build process, there are no such check to validate.

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/system-contracts/contracts/BootloaderUtilities.sol#L44-L103

## Tools Used

Manual review

## Recommended Mitigation Steps

We suggest to update the codes by including the checks as mentioned in zkEVM docs.





## Assessed type

Invalid Validation
[Circuits] Transaction that reaches an Opcode outside bytecode can't be proven
Audit: 2023-10-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/main/code/era-zkevm_circuits/src/code_unpacker_sha256/mod.rs#L33
https://github.com/code-423n4/2023-10-zksync/blob/main/code/era-zkevm_circuits/src/main_vm/utils.rs#L129


# Vulnerability details

## Impact
According to the VM docs, if PC (program counter) reaches a value outside the bytecode (so PC > bytecodeLength), we should read an INVALID Opcode.
The problem is that this feature is absent in the circuits code.

`code_unpacker_sha256` is the file that, given a bytecode hash, reads from the witness the list of code words, checking that their hash matches the given one. At the end it builds a memory queue with the opcode values.

During the `pre_state` function, the code reads this memory queue for the current opcode value (in [this function](https://github.com/code-423n4/2023-10-zksync/blob/main/code/era-zkevm_circuits/src/main_vm/utils.rs#L129)). However if the PC index is too high, it won't find a corresponding opcode value.

I believe in this situation the code will fail to execute, so it's impossible to generate a proof for this transaction.

## Proof of Concept
Someone deploys a contract with a JUMP opcode towards a point outside the contract. Then he makes a transaction calling this contract. If this transaction must be included (for example by posting it on L1), then the block can't be processed, DOS-ing the entire system.

## Recommended Mitigation Steps
Pre-state should check if PC is bigger then the bytecode size and create an exception with the Invalid Opcode. The bytecode size was already read during a previous far-call cycle, so it's a parameter that can be added easily in the VM state.


## Assessed type

DoS
TransactionValidator checks intrinsic costs against wrong value
Audit: 2023-10-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/zksync/libraries/TransactionValidator.sol#L41


# Vulnerability details

## Impact
An incorrect check allows an L1->L2 transaction to be sent that does not cover the sum of overhead and intrinsic costs for the operator.

## Proof of Concept

The gasLimit required for a transaction consists of (see [docs](https://github.com/code-423n4/2023-10-zksync/blob/main/docs/Smart%20contract%20Section/zkSync%20fee%20model.md#determining-base_fee)):

$$ \text{totalGasLimit} = \text{overhead + actualGasLimit} = \text{overhead + (intrinisicCosts + executionCosts)} $$

The function `TransactionValidator.getMinimalPriorityTransactionGasLimit` calculates the intrinsic costs that will be incurred for the processing of a transaction on L2. The calculated value is checked in `TransactionValidator.validateL1ToL2Transaction` like this:

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

The issue is that `_transaction.gasLimit` is the total gasLimit including the overhead for the operator. The overhead costs are already checked before that in `TransactionValidator.getTransactionBodyGasLimit`

```js
function getTransactionBodyGasLimit(
    uint256 _totalGasLimit,
    uint256 _gasPricePerPubdata,
    uint256 _encodingLength
) internal pure returns (uint256 txBodyGasLimit) {
    uint256 overhead = getOverheadForTransaction(_totalGasLimit, _gasPricePerPubdata, _encodingLength);

    require(_totalGasLimit >= overhead, "my"); // provided gas limit doesn't cover transaction overhead
    unchecked {
        txBodyGasLimit = _totalGasLimit - overhead;
    }
}
```

The value returned by this function is the `actualGasLimit` (see formula above) that will be available for the processing of the transaction and which should be used to check if the intrinsic costs are covered.

In the current implementation the `totalGasLimit` is checked twice if its greater than overhead and intrinsic costs (separately, not the sum). The bootloader does things correctly by subtracting the overhead from the totalGasLimit first and then checking if the rest covers the intrinsic costs (also called "preparation" costs):

```js
function processL1Tx(...){
    ...
    //gasLimitForTx is total - overhead (and some other intrinsic costs)
    let gasLimitForTx, reservedGas := getGasLimitForTx(...)
    ...
    canonicalL1TxHash, gasUsedOnPreparation := l1TxPreparation(txDataOffset)
    ...
}   if gt(gasLimitForTx, gasUsedOnPreparation) {
        ...
        potentialRefund, success := getExecuteL1TxAndGetRefund(txDataOffset, sub(gasLimitForTx, gasUsedOnPreparation))
```

That means users will not be able to execute transactions for cheap. However, since L1->L2 transaction have to be processed (due to the w