xanoutas/solidity-security-findings huggingface dataset:
[bootloader] Paymaster context can override a transaction hash in memory
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/bootloader/bootloader.yul#L883
https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/bootloader/bootloader.yul#L188-L194


# 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 will still accept findings in the file and will judge them separately from the contest.**

## Impact
The context returned by a paymaster can escape the limit of 33 slots and override the transaction hash stored in memory after the context memory segment. This can cause the consecutive call to `DefaultAccount.executeTransaction` to fail, since the function receives transaction hashes as arguments. In the worst case scenario, a malicious paymaster can overwrite a transaction hash to impact the logic of `DefaultAccount.executeTransaction` in a custom Account Abstraction implementation contract.
## Proof of Concept
zkSync Era implements the Account Abstraction protocol and uses [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) as a reference specification. The protocol defines paymasters as actors who are trusted by accounts to pay transaction fees on their behalf. To process a payment, the bootloader [calls `IPaymaster.validateAndPayForPaymasterTransaction`](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/bootloader/bootloader.yul#L754) on the paymaster specified in a transaction. [validateAndPayForPaymasterTransaction](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/interfaces/IPaymaster.sol#L28) should return two values:
1. the magic, which signifies that tha callee implements the `IPaymaster` protocol;
1. the context, which is a sequence of arbitrary bytes that's passed to the [IPaymaster.postTransaction](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/interfaces/IPaymaster.sol#L43) function at a later step of the transaction verification process.

The bootloader stores the context returned by a paymaster in memory. As per the specification, the context can take no more than 33 slots, or 1056 bytes (33 * 32 = 1056):
> [40..72] words — 33 slots for holding the paymaster context data for the current transaction.

However, when the length of context is validated, there's an off-by-one error that allows to store a 34-byte context. To demonstrate this, let's take a context that breaks the 33 slot requirement:
```
1111111100000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000420
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
................................................................
```

This is an example of a return data from `IPaymaster.
Unchecked low-level calls
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/libraries/SystemContractHelper.sol#L177-L182
https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/libraries/SystemContractHelper.sol#L187-L192
https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/libraries/SystemContractHelper.sol#L164-L172


# Vulnerability details

## Impact
Unchecked low-level calls

## Proof of Concept

The returned value of [L177-L182](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/libraries/SystemContractHelper.sol#L177-L182) and [L187-L192](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/libraries/SystemContractHelper.sol#L187-L192) is discarded
Although function __setValueForNextFarCall__ has return value, none of the callers check if the return value is success [MsgValueSimulator.fallback](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/MsgValueSimulator.sol#L60), [ContractDeployer._constructContract](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/ContractDeployer.sol#L320)

## Tools Used
vs

## Recommended Mitigation Steps
Address.sendValue() might fail silently
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#L60


# Vulnerability details

## Impact
Address.sendValue() is using call() function inside and call() function might fail silently.

## Proof of Concept
[contracts/openzeppelin/utils/Address.sol#L60](https://github.com/code-423n4/2023-03-zksync//blob/main/contracts/openzeppelin/utils/Address.sol#L60) 
Per the Solidity docs:
"The low-level functions call, delegatecall and staticcall return true as their first return value if the account called is non-existent, as part of the design of the EVM. Account existence must be checked prior to calling if needed."

There are the same issues for past contests.

* [Low-level transfer via call() can fail silently 2022-03-rolla-findings#51](https://github.com/code-423n4/2022-03-rolla-findings/issues/51)
* [Failed transfer with low level call won't revert 2022-03-lifinance-findings#101](https://github.com/code-423n4/2022-03-lifinance-findings/issues/101)
## Recommended Mitigation Steps

Check for the account's existence before transferring.
DefaultAccount._validateTransaction cast uint256 to uint32
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
according to solidity document(https://docs.soliditylang.org/en/v0.8.19/units-and-global-variables.html), **gasleft() returns (uint256)**, in [DefaultAccount._validateTransaction](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/DefaultAccount.sol#L82-L87), gasleft's return value is casted into uint32. In such case, if a transaction's gas is more than type(uint32).max, the higher part data will be lost

## Proof of Concept
https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/DefaultAccount.sol#L82-L87

## Tools Used
VS

## Recommended Mitigation Steps
using __Utils.safeCastToU32__
No way to withdraw ETH sent to DefaultAccount.sol via receive
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Users may lose assets

## Proof of Concept
The contract DefaultAccount.sol contains a receive function which allows the contract to receive native token. If a user mistankingly sends over some native tokens to the contract, their ether will be stuck.
```
    receive() external payable {
        // If the contract is called directly, behave like an EOA
    }
```

## Tools Used
Vscode

## Recommended Mitigation Steps
Implement the ```rescuetoken()``` function
Use safeTransfer()/ safeTransferFrom() instead of transfer()/ transferFrom()
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/01379615bc20c2926d81c0a182f1abb6e922b93a/contracts/MsgValueSimulator.sol#L37
https://github.com/code-423n4/2023-03-zksync/blob/01379615bc20c2926d81c0a182f1abb6e922b93a/contracts/interfaces/IEthToken.sol#L8
https://github.com/code-423n4/2023-03-zksync/blob/01379615bc20c2926d81c0a182f1abb6e922b93a/contracts/L2EthToken.sol#L40
https://github.com/code-423n4/2023-03-zksync/blob/01379615bc20c2926d81c0a182f1abb6e922b93a/contracts/ContractDeployer.sol#L318


# Vulnerability details

## Impact
It is a good idea to add a `require()` statement that checks the return value of ERC20 token transfers or to use something like OpenZeppelin's `safeTransfer()` / `safetransferFrom()` unless one is sure the given token reverts in case of a failure. Failure to do so will cause silent failures of transfers and affect token accounting in contract.
However, using `require()` to check transfer return values could lead to issues with non-compliant ERC20 tokens which do not return a boolean value. Therefore, it's highly advised to use OpenZeppelin's `safeTransfer()` / `safetransferFrom()`.

## Proof of Concept
https://github.com/code-423n4/2023-03-zksync/blob/01379615bc20c2926d81c0a182f1abb6e922b93a/contracts/MsgValueSimulator.sol
https://github.com/code-423n4/2023-03-zksync/blob/01379615bc20c2926d81c0a182f1abb6e922b93a/contracts/interfaces/IEthToken.sol
https://github.com/code-423n4/2023-03-zksync/blob/01379615bc20c2926d81c0a182f1abb6e922b93a/contracts/L2EthToken.sol
https://github.com/code-423n4/2023-03-zksync/blob/01379615bc20c2926d81c0a182f1abb6e922b93a/contracts/ContractDeployer.sol

## Tools Used
Manual.

## Recommended Mitigation Steps
Consider using `safeTransfer()` / `safetransferFrom()` instead of `transfer()` / `transferFrom()`.
Breaking accounting on `L2EthToken`
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

This can break accounting as well as create opportunities for abuse

## Proof of Concept

1) `L2EthToken.mint()` has the `onlyBootloader` modifier and can mint any amount for anyone:

```solidity
File: L2EthToken.sol
72:     function mint(address _account, uint256 _amount) external override onlyBootloader {
73:         totalSupply += _amount;
74:         balance[_account] += _amount;
75:         emit Mint(_account, _amount);
76:     }
```

2) `L2EthToken.mint()` is totally unprotected and can be called by anyone, where a [subtraction](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/L2EthToken.sol#L85) that is undocumented and `unchecked` with the `L2EthToken` contract's balance is happening:

```solidity
File: L2EthToken.sol
80:     function withdraw(address _l1Receiver) external payable override {
81:         uint256 amount = msg.value;
82: 
83:         // Silent burning of the ether
84:         unchecked {
85:             balance[address(this)] -= amount;
86:             totalSupply -= amount;
87:         }
88: 
89:         // Send the L2 log, a user could use it as proof of the withdrawal
90:         bytes memory message = _getL1WithdrawMessage(_l1Receiver, amount);
91:         L1_MESSENGER_CONTRACT.sendToL1(message);
92: 
93:         emit Withdrawal(msg.sender, _l1Receiver, amount);
94:     }
```

3) `L1_MESSENGER_CONTRACT.sendToL1(message)` is an external call to [L1Messenger.sendToL1()](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/L1Messenger.sol#L22-L52) which [isn't a payable function](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/L1Messenger.sol#L22). Inside `sendToL1()`, [msg.sender](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/L1Messenger.sol#L49-L51) will be the `L2EthToken` contract. This call should always work and will therefore be commented in the coded POC for the code to compile with Foundry.

4) Use the following to setup a Foundry POC:

[Setting up a hybrid project](https://hardhat.org/hardhat-runner/docs/advanced/hardhat-and-foundry#setting-up-a-hybrid-project)

```bash
yarn add --dev hardhat @nomicfoundation/hardhat-foundry
```

```js
File: hardhat.config.ts
5: import "@nomicfoundation/hardhat-foundry";
```

```bash
npx hardhat init-foundry
forge remappings &> remappings.txt
Missing verifyingContract at TYPEHASH for EIP-712 
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/libraries/TransactionHelper.sol#L81
https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/libraries/TransactionHelper.sol#L138


# Vulnerability details

## Impact
EIP 712 DOMAIN TYPEHASH is broke and it might lead to unexpected results. In some cases, this possibly could lead to have the same signature valid on a different contract on the same chain. 

## Proof of Concept
In the build of the DOMAIN TYPEHASH, the string verifyingContract field is forgotten: 
```
bytes32 constant EIP712_DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId)");
```

According to https://eips.ethereum.org/EIPS/eip-712 
> address verifyingContract the address of the contract that will verify the signature. The user-agent may do contract specific phishing prevention.

also mentioned:
>*The EIP712Domain fields should be the order as above, skipping any absent fields. Future field additions must be in alphabetical order and come after the above fields. User-agents should accept fields in any order as specified by the EIPT712Domain type.*

<br/>

## Tools Used
Manual Review
<br/>

## Recommended Mitigation Steps
add **address verifyingContract to ** to EIP712_DOMAIN_TYPEHASH 
https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/libraries/TransactionHelper.sol#L81
```
bytes32 constant EIP712_DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId, address verifyingContract)");
```
<br/>

add _addr as verifyingContract to domainSeparator
```address _addr = address(this);```
https://github.com/code-423n4/2023-03-zksync/blob/main/contracts/libraries/TransactionHelper.sol#L138

```
bytes32 domainSeparator = keccak256(
            abi.encode(EIP712_DOMAIN_TYPEHASH, keccak256("zkSync"), keccak256("2"), block.chainid, _addr)
        );
```
readUint16() and readUint64() fail to clean up the returned results.
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
``readUint16()`` and ``readUint64()`` fail to clean up the returned results.


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


``readUint16()`` and ``readUint64()`` fail to clean up the returned results, this is because in Assembly, the cleanup is not automatic:

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

We need to make sure both of them are cleaned by using masking. 

## Tools Used
VSCode


## Recommended Mitigation Steps
We introduce two masks to clean up the results before we return them. 
```diff
        
+ uint256 constant MASK16 = 0xffff;
+ uint256 constant MASK64 = 0xffffffffffffffff;


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

    function readUint64(bytes calldata _bytes, uint256 _start) internal pure returns (uint64 result) {
        assembly {
            let offset := sub(_bytes.offset, 24)
            result := calldataload(add(offset, _start))
+            result := and(result, MASK64)           
        }
    }
```
Dirty bytes in dictionary during publishing the compressed bytecode
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The provided dictionary during publishing the compressed bytecode can have extra useless bytes, that can lead to extra gas charging when being sent to L1.

## Proof of Concept

During publishing the compressed bytecode, it is not checked that the `_rawCompressedData` is efficiently compact or not.
https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/BytecodeCompressor.sol#L35

For a better understanding, let's say the bytecode that is going to be compressed and published is `ABCB` (each character denotes 8 bytes). Please note that the second and fourth characters are similar to each other.

The correct way of compressing is that the `_rawCompressedData` be equal to: `0x0003ABC0000000100020001`. In other words, the `dictionary = ABC` and `encodedData = 0x0000000100020001`. So, by using the `encodedData`, the chunks `0, 1, 2, 1` in the `dictionary` will be pointed respectively.

But, a malicious sequencer can charge the user more by adding dirty bits to the `dictionary`. In the example of above, the `_rawCompressedData` could be equal to: `0x0004ABCX0000000100020001`. In other words, the `dictionary = ABCX` and `encodedData = 0x0000000100020001`. It means an extra 8-byte `X` is added to the dictionary, while it is not used at all. That is, the `encodedData` only refers to the chunks `0, 1, 2, 1`, respectively, without referring to the chunk 3, i.e. `X`.  

By doing so, the malicious sequencer can make the `_rawCompressedData` large, just to increase `gasToPay` when sending to L1. While, the encoded chunk matches to the original bytecode

## Tools Used

## Recommended Mitigation Steps
It is recommended to check that the number of 2-byte chunk of `encodedData` be larger than or equal to the number of 8-byte chunk of `dictionary`. By doing so, we will ensure that all the dictionary chunks are referred, and none of them are useless.
```
require((encodedData.length / 2) >= (dictionary.length / 8), "dictionary has dirty bits");
```
``constructedBytecodeHash()`` has the side effects of setting other flags to false, not just the ``isConstructor`` bit.
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/libraries/Utils.sol#L65-L67


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
``constructedBytecodeHash()`` has the side effects of setting other flags to false, not just the ``isConstructor`` bit.

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

``constructedBytecodeHash()`` is supposed to set only the ``isConstructor`` bit to false, but it sets the whole byte to false instead. Therefore, it has side-effect. 

[https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/libraries/Utils.sol#L65-L67](https://github.com/code-423n4/2023-03-zksync/blob/21d9a364a4a75adfa6f1e038232d8c0f39858a64/contracts/libraries/Utils.sol#L65-L67)

The main problem is that it uses ``IS_CONSTRUCTOR_BYTECODE_HASH_BIT_MASK``, actually, ``SET_IS_CONSTRUCTOR_MARKER_BIT_MASK`` should be used instead. In this way, we will not set other bits false, only set the  ``isConstructor`` bit to false.

Impacts: if other bits will be used to encode other flags, then this function has the side effect of setting other flags to false as well. It is not a clean implementation. 

## Tools Used
VScode

## Recommended Mitigation Steps
We will use ``SET_IS_CONSTRUCTOR_MARKER_BIT_MASK`` to set the ``isConstructor`` bit to false only.

```diff
function constructedBytecodeHash(bytes32 _bytecodeHash) internal pure returns (bytes32) {
-        return _bytecodeHash & ~IS_CONSTRUCTOR_BYTECODE_HASH_BIT_MASK;
+        return _bytecodeHash & ~SET_IS_CONSTRUCTOR_MARKER_BIT_MASK;
}
```
setValueUnderNonce () missing increase minNonce
Audit: 2023-03-zksync-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
setValueUnderNonce () missing increase minNonce， execute `incrementMinNonceIfEquals()` later, will get the used `Nonce`.

## Proof of Concept
setValueUnderNonce () can set `Nonce` to used
The code is as follows:
```solidity
    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");
            //<-----not increase minNonce
        }

        uint256 addressAsKey = uint256(uint160(msg.sender));

        nonceValues[addressAsKey][_key] = _value;

        emit ValueSetUnderNonce(msg.sender, _key, _value);
    }
```

If nonceOrdering == Sequential enforces restriction "Previous nonce has not been used"
But did not increase `minNonce`
In this way, if execute `incrementMinNonceIfEquals()` later, will get the used `Nonce`.Or the incoming `_expectedNonce` not satisfied


## Tools Used

## Recommended Mitigation Steps

If nonceOrdering == Sequential,  increases minNonce at the same time

```solidity
    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");
+           increaseMinNonce(1);
        }

        uint256 addressAsKey = uint256(uint160(msg.sender));

        nonceValues[addressAsKey][_key] = _value;

        emit ValueSetUnderNonce(msg.sender, _key, _value);
    }
```