xanoutas/solidity-security-findings huggingface dataset:
The call to MsgValueSimulator with non zero msg.value will call to sender itself which will bypass the onlySelf check
Audit: 2023-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/MsgValueSimulator.sol#L22-L63


# Vulnerability details

## Impact
First, I need to clarify, there may be more serious ways to exploit this issue. Due to the lack of time and documents, I cannot complete further exploit. The current exploit has only achieved the impact in the title. I will expand the possibility of further exploit in the poc chapter.

The call to MsgValueSimulator with non zero msg.value will call to sender itself with the msg.data. It means that if you can make a contract or a custom account call to specified address with non zero msg.value (that's very common in withdrawal functions and smart contract wallets), you can make the contract/account call itself. And if you can also control the calldata, you can make the contract/account call its functions by itself.

It will bypass some security check with the msg.sender, or break the accounting logic of some contracts which use the msg.sender as account name.

For example the `onlySelf` modifier in the ContractDepolyer contract:
```
modifier onlySelf() {
    require(msg.sender == address(this), "Callable only by self");
    _;
}
```

## Proof of Concept
The `MsgValueSimulator` use the `mimicCall` to forward the original call.
```
return EfficientCall.mimicCall(gasleft(), to, _data, msg.sender, false, isSystemCall);
```

And if the `to` address is the `MsgValueSimulator` address, it will go back to the `MsgValueSimulator.fallback` function again. 

The fallback function will  Extract the `value` to send, isSystemCall flag and the `to` address from the extraAbi params(r3,r4,r5) in the `_getAbiParams` function. But it's different from the first call to the `MsgValueSimulator`. The account uses `EfficientCall.rawCall` function to call the `MsgValueSimulator.fallback` in the first call. For example, code in `DefaultAccount._execute`:
```
bool success = EfficientCall.rawCall(gas, to, value, data);
```

The rawCall will simulate `system_call_byref` opcode to call the `MsgValueSimulator`. And the `system_call_byref` will write the r3-r5 registers which are read as the above extraAbi params. 

But the second call is sent by `EfficientCall.mimicCall`, as the return value explained in the document https://github.com/code-423n4/2023-03-zksync/blob/main/docs/VM-specific_v1.3.0_opcodes_simulation.pdf, `mimicCall` will mess up the registers and will use r1-r4 for standard ABI convention and r5 for the extra who_to_mimic arg. So extraAbi params(r3-r5) read by `_getAbiParams` will be messy data. It can lead to very serious consequences, because the r3 will be used as the msg.value, and the r4 will be used as the `to` address in the final `mimicCall`. It means that the contract will send a different(greater) value to a different address, which is unexpected in the original call.

I really don't know how to write a complex test to verify register changes in the era-compiler-tester. So to find out how to control the regi
[bootloader] A bytecode hash without the bytecode (preimage) can be marked as known, breaking the prover
Audit: 2023-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/bootloader/bootloader.yul#L1324-L1345


# Vulnerability details

**This is a report of a finding in `bootloader.yul`. While the file is out of scope of the contest, the sponsor stated that they would still accept findings in the file and would judge them separately from the contest.**

## Impact
A bytecode hash for which the bytecode (preimage) wasn't published to the L1 can be marked as known on the L2. This will break mining and proving of blocks because calls to contracts with invalid or missing bytecode cannot be proved. The issue cannot be resolved after deployment: the preimage (i.e. bytecode) of a butecode hash that has been marked as known cannot be re-published to the L1 since the bootloader doesn't allow publishing known bytecodes to avoid overpaying.
## Proof of Concept
As per the description of the project:
> On zkSync, the L2 stores the contract's code hashes and not the codes themselves. Therefore, it must be part of the protocol to ensure that no contract with unknown bytecode (i.e. hash with an unknown preimage) is ever deployed.

Also:
> If the transaction comes from L2, i.e. (the factory dependencies are yet to publish on L1), the operator prepares the compress the bytecode offchain and then verifies that the bytecode was compressed correctly. 

And also:
> A call to a contract with invalid bytecode can not be proven. That is why it is essential that no contract with invalid bytecode is ever deployed on zkSync.

In accordance with this description, in the beginning of transaction execution, the bootloader marks bytecode hashes provided by the operator as known. It also validates and sends compressed bytecode to L1. This is done in the [ZKSYNC_NEAR_CALL_markFactoryDepsL2](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/bootloader/bootloader.yul#L1308) function:
1. It [loads bytecodes provided by the operator and the bytecode hashes provided in the transaction](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/bootloader/bootloader.yul#L1317-L1319).
1. It then [iterates the hashes provided by the transaction](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/bootloader/bootloader.yul#L1324-L1340) and, for those hashes that match the ones provided by the operator, calls `sendCompressedBytecode`.
1. The [sendCompressedBytecode](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/bootloader/bootloader.yul#L1500) takes the bytecodes (bytecode hashes, original bytecodes, and their compressed versions) provided by the operator and passes them to the [BytecodeCompressor.publishCompressedBytecode](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/BytecodeCompressor.sol#L35).
1. `publishCompressedBytecode` verifies that th
Wrong block number calculation
Audit: 2023-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/SystemContext.sol#L85-L86


# Vulnerability details

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

The calculation for the block info is (currentBlockNumber + 1) * BLOCK_INFO_BLOCK_NUMBER_PART + newTimestamp.
getBlockNumberAndTimestamp(), getBlockTimestamp(), getBlockNumber() all incorrectly calculate the current block number.
The calculation:

function getBlockNumberAndTimestamp() public view returns (uint256 blockNumber, uint256 blockTimestamp) {
        uint256 blockInfo = currentBlockInfo;
        blockNumber = blockInfo / BLOCK_INFO_BLOCK_NUMBER_PART;  //Warden: this is wrong
        blockTimestamp = blockInfo % BLOCK_INFO_BLOCK_NUMBER_PART;
    }
The blockTimestamp calculation is correct, it returns the right timestamp. But the block number isn't. The value that is currently calculated is bigger, because instead of the [block info - timestamp value] (which is correct, because our block info calculation above is the timestamp + block number, so we need to remove it first before calculating the block number), we take the [block info + timestamp] value, which inflates the block number the block number.
A validator can set the timestamp to a really big value, which will heavily inflate the block number.
The correct calculation should be: 
blockNumber = (blockInfo - blockTimestamp) / BLOCK_INFO_BLOCK_NUMBER_PART;

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

https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/SystemContext.sol#L85-L86

## Tools Used
manual review
## Recommended Mitigation Steps
Correct the calculation blockNumber = (blockInfo - blockTimestamp) / BLOCK_INFO_BLOCK_NUMBER_PART;
I believe the above calculation does the trick. 
Maybe this calculation is intended, but it's still unsafe, because block timestamp can be inflated which in turn will heavily increase the block number, which can break the system and other contracts.
NonceHolder.setValueUnderNonce lack of validation
Audit: 2023-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/NonceHolder.sol#L81-L96


# Vulnerability details

## Impact
NonceHolder.setValueUnderNonce can set the nonce value for key has already being used.

## Proof of Concept
Quoting to the document __NonceHolder__ part:
  It provides a function validateNonceUsage which the bootloader uses to check whether the nonce has been used for a certain account or not. Bootloader enforces that the nonce is marked as non-used before the validation step of the transaction and marked as used afterward. The contract ensures that once marked as used, the nonce can not be set back to the "unused" state.

And also the comments [L87-L89](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/NonceHolder.sol#L85-L89) conform to the above document.

But setValueUnderNonce didn't validate if __key__ nonce has been used.

## Tools Used
VS

## Recommended Mitigation Steps
uisng **validateNonceUsage(msg.sender, _key, false);** to validae before setting

        function setValueUnderNonce(uint256 _key, uint256 _value) public onlySystemCall {
            IContractDeployer.AccountInfo memory accountInfo = DEPLOYER_SYSTEM_CONTRACT.getAccountInfo(msg.sender);
    
            require(_value != 0, "Nonce value can not be set to 0");
            // If an account has sequential nonce ordering, we enforce that the previous
            // nonce has already been used.
            if (accountInfo.nonceOrdering == IContractDeployer.AccountNonceOrdering.Sequential && _key != 0) {
                require(isNonceUsed(msg.sender, _key - 1), "Previous nonce has not been used");
            }
    
    +        validateNonceUsage(msg.sender, _key, false);
            uint256 addressAsKey = uint256(uint160(msg.sender));
    
            nonceValues[addressAsKey][_key] = _value;
    
            emit ValueSetUnderNonce(msg.sender, _key, _value);
        }
L2EthToken.withdraw can be underflow with little cost
Audit: 2023-03-zksync-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
calculating [balance[address(this)]](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/L2EthToken.sol#L85) within __unchecked__ will cause underflow.

Say if **balance[address(this) == 1** and **msg.value == 2**, after calling __withdraw__, balance[address(this)] will be __type(uint256).max__

## Proof of Concept

    function underFlow(uint a, uint b) public pure returns(uint) {
        uint c;
        unchecked {
            c = a - b;
        }
        return c;
    }

## Tools Used
VS
## Recommended Mitigation Steps
moving __balance[address(this)] -= amount;__ out of __unchecked__

    +        balance[address(this)] -= amount;
             unchecked {
    -            balance[address(this)] -= amount;
                 totalSupply -= amount;
             }
User may lose ETH
Audit: 2023-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/MsgValueSimulator.sol#L32-L63


# Vulnerability details

## Impact
User may lose ETH

## Proof of Concept
If a non-existent function is called , the protocol may enter ```MsgValueSimulator.fallback()```.Inside the ```fallback()``` function ,the protocol will transfer ETH from one address to another. If user calls a function that doesn't exist and there happens to be value and address in the calldata, user may lose ETH.
```
fallback(bytes calldata _data) external payable onlySystemCall returns (bytes memory) {
        (uint256 value, bool isSystemCall, address to) = _getAbiParams();

        if (value != 0) {
            (bool success, ) = address(ETH_TOKEN_SYSTEM_CONTRACT).call(
                abi.encodeCall(ETH_TOKEN_SYSTEM_CONTRACT.transferFromTo, (msg.sender, to, value))
            );

            // If the transfer of ETH fails, we do the most Ethereum-like behaviour in such situation: revert(0,0)
            if (!success) {
                assembly {
                    revert(0, 0)
                }
            }
        }

        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)
            }
        }

        // For the next call this `msg.value` will be used.
        SystemContractHelper.setValueForNextFarCall(uint128(value));

        return EfficientCall.mimicCall(gasleft(), to, _data, msg.sender, false, isSystemCall);
    }

```

## Tools Used
Vscode
## Recommended Mitigation Steps
The Risk of Adding an Incorrect Previous Block Hash Value in a Blockchain Network
Audit: 2023-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/SystemContext.sol#L109-L128


# Vulnerability details

## Impact
There is a risk of adding an incorrect previous block hash value to a block, either intentionally or unintentionally. If the previous block hash value is incorrect, it means that the data in the previous block has been tampered with, and the current block is not linked to the correct previous block. This can cause a break in the chain, making the blockchain network vulnerable to attacks such as double-spending and other forms of fraud.

## Proof of Concept

       function setNewBlock(
        bytes32 _prevBlockHash,
        uint256 _newTimestamp,
        uint256 _expectedNewNumber,
        uint256 _baseFee
       ) external onlyBootloader {
        (uint256 currentBlockNumber, uint256 currentBlockTimestamp) = getBlockNumberAndTimestamp();
        require(_newTimestamp >= currentBlockTimestamp, "Timestamps should be incremental");
        require(currentBlockNumber + 1 == _expectedNewNumber, "The provided block number is not correct");

        blockHash[currentBlockNumber] = _prevBlockHash;

        // Setting new block number and timestamp
        currentBlockInfo = (currentBlockNumber + 1) * BLOCK_INFO_BLOCK_NUMBER_PART + _newTimestamp;

        baseFee = _baseFee;

        // The correctness of this block hash and the timestamp will be checked on L1:
        SystemContractHelper.toL1(false, bytes32(_newTimestamp), _prevBlockHash);
    }

(https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/SystemContext.sol#L109-L128)

_prevBlockHash value is not checked any where in the function. We assigning the value directly to blockHash[currentBlockNumber] without ensuring the previous block value is genuine.

There is the possibility of calling setNewBlock() with wrong _prevBlockHash value either intentionally or unintentionally.

Ensuring the accuracy and security of the previous block hash value is critical for the proper functioning and security of a blockchain network. Any compromise in the previous block hash value can compromise the entire blockchain, making it unreliable for storing and transferring valuable assets.

## Tools Used
Manual Audit 

## Recommended Mitigation Steps
Ensure the value of _prevBlockHash is correct before assigning to blockHash[currentBlockNumber]
Adding Multiple Blocks with the Same Timestamp Can Create Ambiguity in the Order of Blocks in the Blockchain Network
Audit: 2023-03-zksync-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Multiple blocks at the same timestamp creates ambiguity about the order in which these blocks should be added to the chain. This can cause inconsistencies in the state of the network and make it vulnerable to attacks such as double-spending.

## Proof of Concept

 require(_newTimestamp >= currentBlockTimestamp, "Timestamps should be incremental");
[SystemContext.sol#L116](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/SystemContext.sol#L116)

1) So as per _newTimestamp >= currentBlockTimestamp condition check its possible to add multiple blocks with same timestamp.

2) As per documentation the Timestamps should be incremental. 

3) But the condition check is wrong. This will accept even the same timestamps because of >= condition check 

Each block in a blockchain network contains a timestamp and a unique block hash, and the chronological order of the blocks is critical for the integrity and security of the network. 

This can result in the creation of competing chains, known as forks, where different nodes on the network have different versions of the blockchain that they believe to be valid. This can cause inconsistencies in the state of the network and make it vulnerable to attacks such as double-spending

## Tools Used
Manual Audit 

## Recommended Mitigation Steps

Modify the condition check like bellow 

require(_newTimestamp > currentBlockTimestamp, "Timestamps should be incremental");

This ensures that _newTimestamp is greater than currentBlockTimestamp
_l1Receiver may lose the token amount 
Audit: 2023-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/L2EthToken.sol#L80-L94


# Vulnerability details

## Impact
_l1Receiver lose the token amount 

## Proof of Concept

function withdraw(address _l1Receiver) external payable override {
        uint256 amount = msg.value;

        // Silent burning of the ether
        unchecked {
            balance[address(this)] -= amount;
            totalSupply -= amount;
        }

        // Send the L2 log, a user could use it as proof of the withdrawal
        bytes memory message = _getL1WithdrawMessage(_l1Receiver, amount);
        L1_MESSENGER_CONTRACT.sendToL1(message);

        emit Withdrawal(msg.sender, _l1Receiver, amount);
    }

Inside the withdraw function the balance[_l1Receiver] value is not increased. only the amount is burned from balance[address(this)] and totalSupply . The amount is not added to any receiver address or msg.sender 

## Tools Used
Manual audit

## Recommended Mitigation Steps

Add the token to balance of _l1Receiver 

balance[_l1Receiver]+=amount;
Operator can cause funds to be stolen by manipulating gas fee refund
Audit: 2023-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/main/bootloader/bootloader.yul#L1097
https://github.com/code-423n4/2023-03-zksync/blob/main/bootloader/bootloader.yul#L1421-L1423
https://github.com/code-423n4/2023-03-zksync/blob/main/bootloader/bootloader.yul#L1427


# Vulnerability details

## Impact
An operator can manipulate the refund of gas fee mechanism to steal from the bootloader balance. 
Inside `refundCurrentL2Transaction` function in the bootloader where the refund is happening for the refund recipient at [#L1097](https://github.com/code-423n4/2023-03-zksync/blob/main/bootloader/bootloader.yul#L1097), the operator provides a value for the refund. If the provided value is lower than the one calculated for the bootloader, then the one calculated by the bootloader will be selected. Check line [#L1427](https://github.com/code-423n4/2023-03-zksync/blob/main/bootloader/bootloader.yul#L1427).
```
let refundInGas := max(operatorProvidedRefund, gasLeft)
```

However, the operator can suggest a very high value of refund for the  recipient (a user or paymaster), therefore the recipient receives a value way higher than the gas left provided in the first place as there is no limit check for it.

We have two cases:
1. If the bootloader has a balance higher than the transaction's upfront payment, then all the balance could be stolen.
2. If the bootloader has only the transaction's upfront payment and the refund recipient is a paymaster, then the paymaster can always charge the user for the whole payment. In other words, the user will never receive a refund in case there is one.


I believe the issue is indeed in scope as mentioned in the scoping details of [the contest](https://code4rena.com/contests/2023-03-zksync-era-system-contracts-contest) 
> - Describe any specific areas you would like addressed. E.g. Please try to break XYZ.: The focus is on the system contracts, but the bootloader will also be shared and any problems in it are generally in scope 

<br/>

## Proof of Concept
As a part of the bootloader concept, the bootloader asks the operator to decide the gas fee refund as we see here 
[#L1423](https://github.com/code-423n4/2023-03-zksync/blob/main/bootloader/bootloader.yul#L1421-L1423)
   ```
 askOperatorForRefund(gasLeft)

 let operatorProvidedRefund := getOperatorRefundForTx(transactionIndex)
```

  
The `refundInGas` value is the max between `operatorProvidedRefund` and `gasLeft` which is calculated by the bootloader at line [#L1427](https://github.com/code-423n4/2023-03-zksync/blob/main/bootloader/bootloader.yul#L1427)

 ```
let refundInGas := max(operatorProvidedRefund, gasLeft)
``` 

Now the issue there is no limit check for the `operatorProvidedRefund` value. This means the operator can suggest up to 2^32-1 as a refund which is the max allowed by the bootloader, and as we see here, the transfer happens directly after that.

*Note: it shouldn't exceed bootloader's balance. Otherwise, it fails.*
```
	if iszero(validat
Attacker could potentially burn the token balance of totalSupply and  L2EthContract 
Audit: 2023-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/L2EthToken.sol#L80-L94


# Vulnerability details

## Impact
Attacker potentially can burn all L2EthContract and totalSupply tokens

## Proof of Concept

function withdraw(address _l1Receiver) external payable override {
        uint256 amount = msg.value;

        // Silent burning of the ether
        unchecked {
            balance[address(this)] -= amount;
            totalSupply -= amount;
        }

        // Send the L2 log, a user could use it as proof of the withdrawal
        bytes memory message = _getL1WithdrawMessage(_l1Receiver, amount);
        L1_MESSENGER_CONTRACT.sendToL1(message);

        emit Withdrawal(msg.sender, _l1Receiver, amount);
    }

Lets assume contract has 1000 tokens and totalsupply is 1000 tokens

POC:

1) Bob initiate the transaction with msg.value amount 1000 to withdraw

2) Here there is no withdrawal limit and balances checks inside the function

3) The amount is directly burned form balance[address(this)] and totalSupply 

Attacker can potentially can burn the whole amount from balance[address(this)] and totalSupply 

So the contract and totalSupply is drained to zero amount 

## Tools Used
Manual Audit 

## Recommended Mitigation Steps
To ensure that a L2EthContract only allows burning of tokens that were originally minted by the sender