xanoutas/solidity-security-findings huggingface dataset:
Loss of funds when msg.value > 2**128
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/MsgValueSimulator.sol#L48-L57


# Vulnerability details

## Impact
When a user try to transfer an amount of ether > 2**128 an invariant is broken where instead of reverting the transaction the affected code just return.

## Proof of Concept
The following is the affected code where it returns instead of reverting.
```solidity
        if (value != 0) {
            (bool success, ) = address(ETH_TOKEN_SYSTEM_CONTRACT).call(
                abi.encodeCall(ETH_TOKEN_SYSTEM_CONTRACT.transferFromTo, (msg.sender, to, value))
            );
        ...
        if (value > MAX_MSG_VALUE) {
            // The if above should never be true, since noone should be able to have
            // MAX_MSG_VALUE wei of ether. However, if it does happen for some reason,
            // we will revert(0,0).
            // Note, that we use raw revert here instead of `panic` to emulate behaviour close to
            // the EVM's one, i.e. returndata should be empty.
            assembly {
                return(0, 0)
            }
        }
```
Note that the amount of ether would be locked in the MsgValueSimulator contract because by returning the state changes are not reverted.

The above issue affects everywhere in the codebase where a transfer of ether happens. For example when calling a constructor and passing ether there is a cast which would be safe due to the above check if correct. As a consequence an amount of ether equal to 2**128 could be lost.
```solidity
    function _constructContract(address _sender, address _newAddress, bytes calldata _input, bool _isSystem) internal {
        // Transfer the balance to the new address on the constructor call.
        uint256 value = msg.value;
        if (value > 0) {
            ETH_TOKEN_SYSTEM_CONTRACT.transferFromTo(address(this), _newAddress, value);
            // Safe to cast value, because `msg.value` <= `uint128.max` due to `MessageValueSimulator` invariant
            SystemContractHelper.setValueForNextFarCall(uint128(value));
        }
```
Other places affected are the [rawCall](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/libraries/EfficientCall.sol#L133-L146) and the [systemCall](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/libraries/SystemContractsCaller.sol#L102-L111) functions.
## Recommended Mitigation Steps
Revert instad of returning.
Incorrect calculation of gasToPay due to dividing before multiplying, rounding error.
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/L1Messenger.sol#L37


# Vulnerability details

## Impact
In Solidity, it is an error to divide before multiplying because of lots of rounding errors that can come from that. In this case:


        uint256 pubdataLen;
        unchecked {
            pubdataLen = ((_message.length + 31) / 32) * 32 + 64;
        }
        uint256 gasToPay = pubdataLen * gasPerPubdataBytes;

The calculation of pubdataLen will be wrong for certain numbers that round down: (_message.length + 31) / 32. Therefore the calculation of the gas to Pay will be wrong, possibly causing other undetected issue apart from the wrong calculation.

## Proof of Concept
Let's take the example of sending a message to L1 of 5 bytes:

Current case:

            //5 + 31 = 36 
            //36 / 32 = 1  * 32 = 32 + 64 = 96
The result is 96 due to a rounding down error between 36/32


Optimal case:
            //5 + 31 = 36 
            //36 * 32 = 1152 / 32 = 36 + 64 = 100
The result of a right done arithmetic operation in this case would be 100


As you can see there is an arithmetic error due to division before multiplication




## Tools Used
Manual

## Recommended Mitigation Steps
You always have to multiply before divide in solidity:


 pubdataLen = ((_message.length + 31) * 32) / 32  + 64;
Unsafe safeTransfer function
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync//blob/main/contracts/openzeppelin/utils/Address.sol#L266
https://github.com/code-423n4/2023-03-zksync//blob/main/contracts/openzeppelin/token/ERC20/utils/SafeERC20.sol#L22


# Vulnerability details

## Impact
The safeTransfer function of the SafeERC20.sol contract check that the target is actually a contract before calling it, this is to avoid calls to address with no code that will always return succes = true. For exemple if you use token.safeTransfer() and token = address(0),transfer will fail and the transaction will revert thanks to the isContract check.

The isContract use address.code.length > 0 to check if its a contract or not.

zkSync have the concept of Empty contracts, defined like this : 

"Some of the contracts are relied upon to have EOA-like behavior, i.e. they can be always called and get the success value in return. An example of such an address is a 0 address. We also require the bootloader to be callable so that the users could transfer ETH to it.

For these contracts, we insert the EmptyContract code upon genesis. It is basically a noop code, which does nothing and returns success=1."

So in zkSync era, the safeTransfer and safeTransferFrom function will work with address that actually contains the EmptyContract code,notably the 0 address.This can lead to unintended behavior for protocols that trust the SafeERC20 contract.

## Proof of Concept
As a proof of Concept, i will give an exemple of a known protocol that can get exploited with the 0 address returning true for the isContract check.

The celer bridge :
https://polygonscan.com/address/0x88dcdc47d2f83a99cf0000fdf667a468bb958a78#code 

There is two bridge function, one to bridge ERC20 and on to bridge Native currency but they use the same event, if you  use the 0 address on the sendERC20 function, the transaction will fail. But on zkSync era the transaction will go trough since the 0 address does contain code and you will get native token on the other side of the bridge since the 0 address represents native token.

In the past, qubit finance got exploited because they didnt use the safeERC20 contract and the attacker stole 80 millions dollar with the exact method described above.

https://www.halborn.com/blog/post/explained-the-qubit-hack-january-2022

Their might be other ways to exploit this, but basically every bridge using the same events to bridge ERC20 tokens and Native currency could be exploited because of this.


## Tools Used
None
## Recommended Mitigation Steps
Change the isContract function of the SafeERC20 contract to block calls to those empty contract address, or at least to the 0 address.
Anyone can steal funds in the Contract Deployer
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/ContractDeployer.sol#L318


# Vulnerability details

## Impact
If `ContractDeployer.sol` ever holds funds, it could potentially be drained  via the chained creation of new contracts.

## Proof of Concept
When creating a contract the create/create2 functions will be called inside the contract deployer here: https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/ContractDeployer.sol#L128-L150, which after doing validation on the codehash and created account, will call the `_constructContract` method here: https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/ContractDeployer.sol#L308-L328. 

This call sets the `msg.value`, and calls the L2EthToken contract to transfer funds from the bootloader to the constructed contract, then sets the `msg.value` for the next call. It will then use `mimicCall` to call the contract's constructor. However, if the created contract's constructor creates another contract, passing the `msg.value` to the new contract, the same `create`/`create2` function will be invoked, and the `_constructContract` function will transfer ether from itself to the new contract, setting the `msg.value` again. An attacker can repeat this process and drain the `ContractDeployer` of any funds, since every time a new contract is created ether is transferred to the new contract from the `ContractDeployer` and the system will set the same `msg.value` to propogate it. This is similar to a reentrancy attack but a new contract being created is valid and will bypass the checks in the `_nonSystemDeployOnAddress()` function here: https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/ContractDeployer.sol#L249-L267

## Tools Used
Manual Review

## Recommended Mitigation Steps
Transfer from `msg.sender` instead of the `ContractDeployer` contract.
Malicious or hacked admin can steal all ETH
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync//blob/main/contracts/L2EthToken.sol#L40-L58


# Vulnerability details

## Impact

In `L2EthToken.sol` we have [transferFromTo()](https://github.com/code-423n4/2023-03-zksync//blob/main/contracts/L2EthToken.sol#L40-L58) It is possible malicious or hacked admin to steal the ETH.

## Proof of Concept

As can be seen from the code snippet below, nothing can stop malicious or hacked admin to steal all ETH. He can use `address _from` and send the ETH to `address _to`. 
I see from the NatSpec that function can be called only by trusted system contracts, but for greater safety it is good to add timelock mechanism.

```solidity
function transferFromTo(address _from, address _to, uint256 _amount) external override {
        require(
            msg.sender == MSG_VALUE_SYSTEM_CONTRACT ||
                msg.sender == address(DEPLOYER_SYSTEM_CONTRACT) ||
                msg.sender == BOOTLOADER_FORMAL_ADDRESS,
            "Only system contracts with special access can call this method"
        );

        uint256 fromBalance = balance[_from];
        require(fromBalance >= _amount, "Transfer amount exceeds balance");
        unchecked {
            balance[_from] = fromBalance - _amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            balance[_to] += _amount;
        }

        emit Transfer(_from, _to, _amount);
    }
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

For more certain it is good to add timelock mechanism.
***
Unsafe cast
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync//blob/main/contracts/AccountCodeStorage.sol#L74-L123


# Vulnerability details

## Impact
In `AccountCodeStorage.sol` we have function [getCodeHash()](https://github.com/code-423n4/2023-03-zksync//blob/main/contracts/AccountCodeStorage.sol#L74) and [getCodeSize](https://github.com/code-423n4/2023-03-zksync//blob/main/contracts/AccountCodeStorage.sol#L102-L122) Due to an insecure cast, it is possible to get an integer overflow.
Solidity version >0.8.0 provide SafeMath, but casting operations are not safe and can overflow.

## Proof of Concept
As can be seen from the code snippet below, `getCodeHash` intake `_input` which is uint256.
This input is used on line 77 and 78 and as seen below we have unsafe cast from uint160 to uint256.

```solidity
    /// @notice Simulate the behavior of the `extcodehash` EVM opcode.
    /// @param _input The 256-bit account address.
    /// @return codeHash - hash of the bytecode according to the EIP-1052 specification.
    function getCodeHash(uint256 _input) external view override returns (bytes32) {
        // We consider the account bytecode hash of the last 20 bytes of the input, because
        // according to the spec "If EXTCODEHASH of A is X, then EXTCODEHASH of A + 2**160 is X".

77:        address account = address(uint160(_input));
78:        if (uint160(account) <= CURRENT_MAX_PRECOMPILE_ADDRESS) {
            return EMPTY_STRING_KECCAK;
        }

        bytes32 codeHash = getRawCodeHash(account);

        // The code hash is equal to the `keccak256("")` if the account is an EOA with at least one transaction.
        // Otherwise, the account is either deployed smart contract or an empty account,
        // for both cases the code hash is equal to the raw code hash.

        if (codeHash == 0x00 && NONCE_HOLDER_SYSTEM_CONTRACT.getRawNonce(account) > 0) {
            codeHash = EMPTY_STRING_KECCAK;
        }

        // The contract is still on the constructor, which means it is not deployed yet,
        // so set `keccak256("")` as a code hash. The EVM has the same behavior.
        else if (Utils.isContractConstructing(codeHash)) {
            codeHash = EMPTY_STRING_KECCAK;
        }

        return codeHash;

    }
```

Absolutely the same applies to the function `getCodeSize()` in the same contract.
You need to make sure that the values fit in the variables you are trying to assign them to when casting variables.

## Tools Used

Manual Review

## Recommended Mitigation Steps

Consider using OpenZeppelin's SafeCast library to prevent unexpected overflows when casting.
Gas check inaccuracy
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/L1Messenger.sol#L43-L46
https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/libraries/SystemContractHelper.sol#L152


# Vulnerability details

## Impact

Since the gas forwarded will be limited to 63/64 of the total `gasleft`, L1 transactions will be vulnerable of being reverted. To achieve 1:1 partity with the EVM, the ZKEVM should account for 1/64 rule. Please refer to the 1/64 rule [here](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md). The actual amount of gas left during the call will be less than expected.

## Proof of Concept

By running `L1Messenger.sendToL1()` in a sandbox environment (regular EVM), we can get gasleft ~14766 before the call.

If we make the following calculation:

```
gasleft = 14766
gasleft * 63/64 = 14535 (floored)
```

We can see that if the amount of gas to pay is smaller than ~14766 and bigger than ~14535, the require check will pass, but the transaction will fail.

https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/L1Messenger.sol#L43-L46

https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/libraries/SystemContractHelper.sol#L152

## Recommended Mitigation Steps

There should be a margin between `gasleft` and the gas to pay. Consider multiplying `_gasToBurn` by 64/63 and also adding a buffer for safety.

```
constant uint256 BUFFER = 12_000;
require(gasleft() >= (_gasToBurn * 64) / 63 + BUFFER);
```
[Medium - 2] A force deployed contract may be stuck in the constructor forever
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/ContractDeployer.sol#L212
https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/ContractDeployer.sol#L196-L207
https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/ContractDeployer.sol#L326
https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/ContractDeployer.sol#L214


# Vulnerability details

## Impact

The [`forceDeployOnAddress()`](https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/ContractDeployer.sol#L212) function in the `ContractDeployer` contract may be used to redeploy contracts at a specified address. Very useful in the case of precompiles or system contracts upgrades for instance.
In the [deployment parameters](https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/ContractDeployer.sol#L196-L207), multiple values can be set by the force deployer. Among those is the `callConstructor` which is responsible for calling the `_constructContract()`, which calls the constructor, sets the immutable values for the contract, and finally [mark the contract as constructed](https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/ContractDeployer.sol#L326).

The issue is that if the force deployer does not want to call the constructor, then the contract will be in this "constructing" state forever because the flag was [turned on](https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/ContractDeployer.sol#L214) but not turned off in this condition.

## Tools Used

Manual inspection

## Recommended Mitigation Steps

Turn the flag back off by marking the contract as "constructed" in an `else` block:

```solidity
function forceDeployOnAddress(ForceDeployment calldata _deployment, address _sender) external payable onlySelf {
	_ensureBytecodeIsKnown(_deployment.bytecodeHash);
  _storeConstructingByteCodeHashOnAddress(_deployment.newAddress, _deployment.bytecodeHash);

  AccountInfo memory newAccountInfo;
  newAccountInfo.supportedAAVersion = AccountAbstractionVersion.None;
  // Accounts have sequential nonces by default.
  newAccountInfo.nonceOrdering = AccountNonceOrdering.Sequential;
  _storeAccountInfo(_deployment.newAddress, newAccountInfo);

	if (_deployment.callConstructor) {
		_constructContract(_sender, _deployment.newAddress, _deployment.input, false);
	} else {
		ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT.markAccountCodeHashAsConstructed(_deployment.newAddress);
	}

	emit ContractDeployed(_sender, _deployment.bytecodeHash, _deployment.newAddress);
}
```
[Medium - 1] Ecrecover precompile doesn't behave the same as the one from Ethereum
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/precompiles/Ecrecover.yul#L85
https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/precompiles/Ecrecover.yul#L86
https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/precompiles/Ecrecover.yul#L88
https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/precompiles/Ecrecover.yul#L71


# Vulnerability details

## Impact

According to the [Ethereum yellow paper](https://ethereum.github.io/yellowpaper/paper.pdf) and in the specifications of the `ecrecover` precompile, it is stated that if the `ecrecover` doesn't return anything (denoted by `∅`), then the return should be 0 as well.

![Precompile specs](https://user-images.githubusercontent.com/51274081/226169367-2b481b01-d2a4-4da6-a120-8a8379bc05b1.png)

If we take a look at the current `ecrecover` implementation of the precompile, it does a [`precompileCall`](https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/precompiles/Ecrecover.yul#L85) with the signature whose parameters (r, s, v) were checked before. Then [loads the memory at slot 0](https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/precompiles/Ecrecover.yul#L86) and we can see just after that in order to return some data, [those two must be `true`](https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/precompiles/Ecrecover.yul#L88).

The issue is that compared to the yellow paper, there is no handling of the case when the precompile doesn't return anything, but yields a success boolean.

As the client is not in scope, it is not clear how it would result in if there was a bug there as well.

Then, it may return the data that is at the location `32` or `0x20` in hex for a length of `32`, most likely the [`v` parameter](https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/precompiles/Ecrecover.yul#L71) which was substracted by 1. This is not what we would expect from the result of a `ecrecover` call, that should normally return the address of the signer of the message.

This is where it is handled in go-ethereum, the Ethereum client: https://github.com/ethereum/go-ethereum/blob/master/crypto/secp256k1/secp256.go#L118

```go
// RecoverPubkey returns the public key of the signer.
// msg must be the 32-byte hash of the message to be signed.
// sig must be a 65-byte compact ECDSA signature containing the
// recovery id as the last element.
func RecoverPubkey(msg []byte, sig []byte) ([]byte, error) {
	if len(msg) != 32 {
		return nil, ErrInvalidMsgLen
	}
	if err := checkSignature(sig); err != nil {
		return nil, err
	}

	var (
		pubkey  = make([]byte, 65)
		sigdata = (*C.uchar)(unsafe.Pointer(&sig[0]))
		msgdata = (*C.uchar)(unsafe.Pointer(&msg[0]))
	)
  // ZkSync-era audit: if the recover return is nothing, return an error
	if C.secp256k1_ext_ecdsa_recover(context, (*C.uchar)(unsafe.Pointer(&pubkey[0])), sigdata, msgdata) == 0 {
		return nil, ErrRecoverFailed
	}
	return pubkey, nil
}
```
deploying contracts with forceDeployOnAddress will break contracts when callConstructor is false
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/ContractDeployer.sol#L212-L227
https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/ContractDeployer.sol#L302-L306


# Vulnerability details

## Impact
when function `forceDeployOnAddress()` used for deploying contract and `callConstructor` is false, then contract's bytecodehash would stay in constructing state and calling the contract won't be possible. it can cause protocol and other contracts that are using it to break and if they call that address and sends some funds, then those funds would be lost. the issue is critical because the updated contract(which is updated by calling `forceDeployOnAddress()`) can be part of important process like bridging or sending messages or withdrawing funds.

## Proof of Concept
This is `forceDeployOnAddress()` code in ContractDeployer:
```solidity
    /// @notice The method that can be used to forcefully deploy a contract.
    /// @param _deployment Information about the forced deployment.
    /// @param _sender The `msg.sender` inside the constructor call.
    function forceDeployOnAddress(ForceDeployment calldata _deployment, address _sender) external payable onlySelf {
        _ensureBytecodeIsKnown(_deployment.bytecodeHash);
        _storeConstructingByteCodeHashOnAddress(_deployment.newAddress, _deployment.bytecodeHash);

        AccountInfo memory newAccountInfo;
        newAccountInfo.supportedAAVersion = AccountAbstractionVersion.None;
        // Accounts have sequential nonces by default.
        newAccountInfo.nonceOrdering = AccountNonceOrdering.Sequential;
        _storeAccountInfo(_deployment.newAddress, newAccountInfo);

        if (_deployment.callConstructor) {
            _constructContract(_sender, _deployment.newAddress, _deployment.input, false);
        }

        emit ContractDeployed(_sender, _deployment.bytecodeHash, _deployment.newAddress);
    }
```
As you can see in the second line code calls `_storeConstructingByteCodeHashOnAddress()` and it would set constructing bytecode hash the address(the second byte is 1), and when `_deployment.callConstructor` is false, code won't call `_constructContract()` (which sets the address's bytecode hash as constructed after calling constructor) and contract bytecode hash would stay in constructing state after the deployement.
the constructing bytecode state is there to prevent other contracts to call this contract when this contract's constructor is called. Constructing state means that "if anyone else call the contract, it will behaves like a contract being constructing (EmptyContract/EOA)". so contract won't be callable if it stays in the Constructing state. This can cause serious issues, like this scenario:
If important contracts like L1Messenger, L2EthToken, MsgValueSimulator, ... needed upgrade and their code upgraded with this deployer function(and admin didn't want to call cons