xanoutas/solidity-security-findings huggingface dataset:
Unprovable batches will be generated upon doing a system upgrade via shadow proposals
Audit: 2024-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/governance/Governance.sol#L142-L145


# Vulnerability details

## Impact

If a system upgrade is ever done via a shadow proposal, unprovable batches will start to be submitted by the operator as the public input used in the proof generation by the EraVM and the ones used by the `Verifier` would differ.

## Proof of Concept

This is gonna take a while, as we have to go deep in how the node works, but I will try to make it as simple as possible. The contract responsible of updating zkSync Era (both on-chain and off-chain components) is the `Governance` contract, via two types of proposals:

- Transparent $\Rightarrow$ both the hash of the operation and the operation itself are publicly visible on-chain
- Shadow $\Rightarrow$ only the hash of the operation is public

which emits the following events, respectively

```solidity
    /// @notice Emitted when transparent operation is scheduled.
    event TransparentOperationScheduled(bytes32 indexed _id, uint256 delay, Operation _operation);

    /// @notice Emitted when shadow operation is scheduled.
    event ShadowOperationScheduled(bytes32 indexed _id, uint256 delay);
```

Now, when we do a system upgrade, for example, update the bytecode of the bootloader, the node must know we changed such a value and update itself accordingly, as it is used as a public input to both the verifier **AND** the circuits (see [here](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Executor.sol#L530)) otherwise

1. If the verifier has the wrong values, there is a completeness bug as it is rejecting **VALID** proofs
2. If the node has the wrong values, the proofs submitted to the verifier would be always rejected as invalid (this is a design decision to prevent a malicious operator from executing transactions with malicious bytecode)

Our situation is the number `2`, as the node *"thinks"* he is using the correct values and will start sending unprovable batches to the verifier. Let's go step by step.

The node listens to events emitted by core contracts by calling the `eth_getLogs` RPC method, which returns all the events emitted by an address in a specified block range (see [here](https://docs.alchemy.com/docs/deep-dive-into-eth_getlogs)). The code responsible for that is in [governance_upgrades.rs](https://github.com/matter-labs/zksync-era/blob/main/core/lib/zksync_core/src/eth_watch/event_processors/governance_upgrades.rs), deep inside the node. In a nutshell, a thread will be running on the background listening to events and updating the database as soon as a certain event appears. If we go to the main function

```rs
#[async_trait::async_trait]
impl EventProcessor for GovernanceUpgradesEventProcessor {
    async fn process_events(
        &mut self,
        storage: &
Attacker can run malicious code on contract through bad tokens on bridging
Audit: 2024-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/bridge/L1ERC20Bridge.sol#L162
https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/bridge/L1ERC20Bridge.sol#L135


# Vulnerability details

## Impact
- Bridge can be used to bridge spam tokens to users.
- Bridge can be exploited to call bad code leading to loss of funds of users and DOS.

## Proof of Concept
A Malicious User can inject bad code using an arbitrary token with bad code called in the _transfer function.

For a rough example a bad token like below could be used on the bridge successfully.

```solidity
contract MalToken is FeeOnTransferToken {
    address usdc; // Define the address of USDC token

    constructor(
        string memory name, 
        string memory symbol, 
        uint8 decimals,
        address _usdc // Pass the address of USDC token as an argument
    ) FeeOnTransferToken(name, symbol, decimals) {
        usdc = _usdc;
    }

    function _transfer(address from, address to, uint256 amount) internal override {        
        // Approve USDC token for transfer
        FeeOnTransferToken(usdc).approve(address(this), amount);
        
        // Transfer USDC token from sender to this contract
        FeeOnTransferToken(usdc).transferFrom(msg.sender, address(this), amount);
        
        // Perform the token transfer
        super._transfer(from, to, amount);
    }
}
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
- WhiteList tokens in bridge 
- Allow only verified clients to bridge allowing user bridge through verified client who are mandated to have whitelisted or supported tokens


## Assessed type

ERC20
paymaster will refund spentOnPubdata to user
Audit: 2024-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/system-contracts/bootloader/bootloader.yul#L1591


# Vulnerability details

## Vulnerability details

A very important modification of this update is that the GAS spent by `pubdata` is collected at the final step of the transaction.

But if there is a `paymaster`, when executing `paymaster.postTransaction(_maxRefundedGas)`
`_maxRefundedGas` does not subtract the `spentOnPubdata`

`bootloader.yul` the code is as follow：
```yul
            function refundCurrentL2Transaction(
                txDataOffset,
                transactionIndex,
                success, 
                gasLeft,
                gasPrice,
                reservedGas,
                basePubdataSpent,
                gasPerPubdata
            ) -> finalRefund {
                setTxOrigin(BOOTLOADER_FORMAL_ADDR())

                finalRefund := 0

                let innerTxDataOffset := add(txDataOffset, 32)

                let paymaster := getPaymaster(innerTxDataOffset)
                let refundRecipient := 0
                switch paymaster
                case 0 {
                    // No paymaster means that the sender should receive the refund
                    refundRecipient := getFrom(innerTxDataOffset)
                }
                default {
                    refundRecipient := paymaster
                    
                    if gt(gasLeft, 0) {
                        checkEnoughGas(gasLeft)
                        let nearCallAbi := getNearCallABI(gasLeft)
                        let gasBeforePostOp := gas()
                        pop(ZKSYNC_NEAR_CALL_callPostOp(
                            // Maximum number of gas that the postOp could spend
                            nearCallAbi,
                            paymaster,
                            txDataOffset,
                            success,
                            // Since the paymaster will be refunded with reservedGas,
                            // it should know about it
@>                          safeAdd(gasLeft, reservedGas, "jkl"),
                            basePubdataSpent,
                            reservedGas,
                            gasPerPubdata
                        ))
                        let gasSpentByPostOp := sub(gasBeforePostOp, gas())

                        gasLeft := saturatingSub(gasLeft, gasSpentByPostOp)
                    } 
                }

                // It was expected that before this point various `isNotEnoughGasForPubdata` methods would ensure that the user 
                // has enough funds for pubdata. Now, we just subtract the leftovers from the user.
@>              let spentOnPubdata := getErgsSpentForPubdata(
                    basePubdataSpent,
                    gasPerPubdata
                )

                let totalRefund := saturatingSub(add(reservedGas, gasLeft), spentOnPubdata)

                askOpera
incorrect Handling of Non-Standard ERC20 Token Transfers
Audit: 2024-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/bridge/L1ERC20Bridge.sol#L142-L144


# Vulnerability details

## description 
there is a vulenrbaility arise in the function deposit in this line
require(amount == _amount, "3T");,this line here where amount is the actual amount received by the shared bridge contract, and _amount is the amount the user intended to deposit. For tokens deducting fees on transfer, amount will be less than _amount, causing the transaction to revert, this This behavior can lead to failed deposit attempts for users attempting to bridge fee-on-transfer tokens, and resulting in a poor user experience and potential loss of funds due to transaction fees. Additionally, it restricts the usability of the bridge for a subset of tokens, reducing the contract's overall utility.
- users attempting to deposit non-standard tokens may face transaction failures without a clear understanding of the cause.
- The bridge's inability to handle non-standard tokens limits its utility and flexibility, potentially excluding a range of tokens from being bridged.
```solidity
 require(_amount != 0, "0T"); // empty deposit
        uint256 amount = _depositFundsToSharedBridge(msg.sender, IERC20(_l1Token), _amount);
        require(amount == _amount, "3T"); // The token has non-standard transfer logic

        l2TxHash = sharedBridge.depositLegacyErc20Bridge{value: msg.value}(
            msg.sender,
            _l2Receiver,
            _l1Token,
            _amount,
            _l2TxGasLimit,
            _l2TxGasPerPubdataByte,
            _refundRecipient
        );
        depositAmount[msg.sender][_l1Token][l2TxHash] = _amount;
        emit DepositInitiated(l2TxHash, msg.sender, _l2Receiver, _l1Token, _amount);
    }
```
the vulnerability is from that the contract fails to account for discrepancies between the intended deposit amount and the amount actually transferred when dealing with non-standard ERC20 tokens.
## impact

this discrepancy could lead to failed transactions or incorrect bridging amounts being recorded, especially in tokens deducting fees on transfers.
## Proof of Concept
 i fuzz with scenario that simulate token transfers with a 1% fee deduction on each transactionand and the contract's logic was expected to transfer a specified amount to the shared bridge, and the fuzz test checked if the actual transferred amount matched the specified amount and  get some result that the test   not pass because the actual transferred amount (436.59 tokens) was less than the specified amount due to the 1% fee deduction, revealing that the contract does not handle non-standard token logic as expected.: 
Specified Deposit Amount: 441 tokens
Simulated Transferred Amount (after 1% fee deduction): 436.59 tokens
Expected by Contract (for a successful deposit): 441 tokens
import random
```python
# Simulate the non-standard ERC20 token behavior
def simulate_toke
An attacker could potentially claim another person's refund or apply for an excessive refund through the `claimFailedDepositLegacyErc20Bridge`.
Audit: 2024-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/bridge/L1SharedBridge.sol#L329-L345
https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/bridge/L1SharedBridge.sol#L644-L666


# Vulnerability details


When initiating a refund request from the Legacy Bridge contract using `claimFailedDepositLegacyErc20Bridge`.

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/bridge/L1SharedBridge.sol#L644-L666

```solidity
    function claimFailedDepositLegacyErc20Bridge(
        address _depositSender,
        address _l1Token,
        uint256 _amount,
        bytes32 _l2TxHash,
        uint256 _l2BatchNumber,
        uint256 _l2MessageIndex,
        uint16 _l2TxNumberInBatch,
        bytes32[] calldata _merkleProof
    ) external override onlyLegacyBridge {
        _claimFailedDeposit(
            true,
            ERA_CHAIN_ID,
            _depositSender,
            _l1Token,
            _amount,
            _l2TxHash,
            _l2BatchNumber,
            _l2MessageIndex,
            _l2TxNumberInBatch,
            _merkleProof
        );
    }
```
It calls the internal function `_claimFailedDeposit`.
- `_isEraLegacyWithdrawal` as true, 
- `weCanCheckDepositHere` as false,
- `notCheckedInLegacyBridgeOrWeCanCheckDeposit` as false. 
According to the logic, this pathway does not verify the deposit details (who initiated the deposit, what token was deposited, and the amount of the deposit).

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/bridge/L1SharedBridge.sol#L329-L345

```solidity
        {
            bool notCheckedInLegacyBridgeOrWeCanCheckDeposit;
            {
                // Deposits that happened before the upgrade cannot be checked here, they have to be claimed and checked in the legacyBridge
                bool weCanCheckDepositHere = !_isEraLegacyWithdrawal(_chainId, _l2BatchNumber);
                // Double claims are not possible, as we this check except for legacy bridge withdrawals
                // Funds claimed before the update will still be recorded in the legacy bridge
                // Note we double check NEW deposits if they are called from the legacy bridge 
                notCheckedInLegacyBridgeOrWeCanCheckDeposit = (!_checkedInLegacyBridge) || weCanCheckDepositHere; 
            }
            if (notCheckedInLegacyBridgeOrWeCanCheckDeposit) {
                bytes32 dataHash = depositHappened[_chainId][_l2TxHash];
                bytes32 txDataHash = keccak256(abi.encode(_depositSender, _l1Token, _amount));
                require(dataHash == txDataHash, "ShB: d.it not hap");
                delete depositHappened[_chainId][_l2TxHash];
            }
        }
```

## Impact
- Impersonation: Attackers could c
An attacker can drain funds on L1 from L2
Audit: 2024-03-zksync-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
an attacker can drain fund without burn a token on L2
## Proof of Concept
The ``L1Messenger.sendToL1`` function always called when user to "withdraw" fund from L2 to L1, ``L1Messenger.sendToL1`` function is message to signal of bridge
```
## L2BaseToken

 function withdraw(address _l1Receiver) external payable override {
        uint256 amount = _burnMsgValue();

        // 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);
    }
```
Unfortunately when we look at the ``L1Messenger.sendToL1`` function, it has no access control. So, anyone can call ``L1Messenger.sendToL1`` function and send malicious messages to L1.
## POC
```
pragma solidity 0.8.20;

import {Test} from "forge-std/Test.sol";
import {L1Messenger} from "./L1Messenger.sol";

contract Attack is Test {

    L1Messenger public L1_MESSENGER_CONTRACT;

    constructor(address l1messager){
        L1_MESSENGER_CONTRACT = L1Messenger(l1messager);
    }

    function attack(uint256 amount) public {
     bytes memory message = _getL1WithdrawMessageMalicous(msg.sender, amount);
     L1_MESSENGER_CONTRACT.sendToL1(message);

    }

 function _getL1WithdrawMessageMalicous(address _to, uint256 _amount) internal pure returns (bytes memory) {
        return abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector, _to, _amount);
    }
}
```
## Tools Used
Manual
## Recommended Mitigation Steps
add access control e.g. only bridge or only systemContract


## Assessed type

Access Control
msg.sender has to be un-aliased in L1ERC20Bridge.tranferTokenToSharedBridge()
Audit: 2024-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/bridge/L1ERC20Bridge.sol#L64


# Vulnerability details

## Impact
Users who deposit tokens in L1ERC20Bridge will not receive them on the shared Bridge because the check require(msg.sender == address(sharedBridge) will always revert.

## Vulnerability details
L1ERC20Bridge.tranferTokenToSharedBridge() checks that msg.sender is sharedBridge: 
```
require(msg.sender == address(sharedBridge), "Not shared bridge"); 
```
However, since the function is called directly as a L1ERC20Bridge - sharedBridge transaction initiated by sharedBridge, which is a contract, msg.sender will actually be the aliased address of sharedBridge. sharedBridge is the un-aliased address of the bridge. As such, this check will always revert, thus users who deposit tokens in L1ERC20Bridge will not receive them on the shared Bridge.
[L1ERC20Bridge.sol#L64](https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/bridge/L1ERC20Bridge.sol#L64)
```
function tranferTokenToSharedBridge(address _token, uint256 _amount) external {
@>        require(msg.sender == address(sharedBridge), "Not shared bridge");
        uint256 amount = IERC20(_token).balanceOf(address(this));
        require(amount == _amount, "Incorrect amount");
        IERC20(_token).safeTransfer(address(sharedBridge), amount);
    }
```
The vulnerability is similar to [spearbit-blast-report-1](
https://github.com/spearbit/portfolio/blob/master/pdfs/report-blast-contracts-review-draft.pdf) where the check require(msg.sender == address(OTHER_BRIDGE),""); will always revert as msg.sender will be the aliased address of L1BlastBridge and OTHER_BRIDGE is the un-aliased address of L1BlastBridge.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Un-alias msg.sender in the check as such:
```
-- require(msg.sender == address(sharedBridge), "Not shared bridge");
++ require(AddressAliasHelper.undoL1ToL2Alias(msg.sender) == address(sharedBridge), "Not shared bridge");

```





## Assessed type

Error
Mathematical inaccuracy can lead to wrong prices
Audit: 2024-03-zksync-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Mailbox.sol#L167
https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Mailbox.sol#L171


# Vulnerability details

## Impact
The mathematical inaccuracy in the calculation involving   uint256 fullPubdataPriceBaseToken and l2GasPrice can result in precision loss

## Proof of Concept
In the principle of PEDMAS/BODMAS, division comes before addition. But in the case of uint256 fullPubdataPriceBaseToken, the case is reversed. This can cause the return of wrong results. For instance, let pubdataPriceBaseToken be 10 wei, batchOverheadBaseToken be 20 wei and uint256(feeParams.maxPubdataPerBatch) be 5 wei. 

Without the PEDMAS, fullPubdataPriceBaseToken will give 6 wei but result in 14 wei with PEDMAS application

## Tools Used
Manual Review
## Recommended Mitigation Steps
Division should come first before addition in calculations or the parameters involved in the division should be encapsulated properly:

uint256 fullPubdataPriceBaseToken = pubdataPriceBaseToken +
    (batchOverheadBaseToken / uint256(feeParams.maxPubdataPerBatch));

Or:
uint256 fullPubdataPriceBaseToken = (pubdataPriceBaseToken +
    batchOverheadBaseToken) / uint256(feeParams.maxPubdataPerBatch

whichever calculation is intended. Same should also apply to the l2gasprice calculation









## Assessed type

Error
Upgraded Q -> 2 from #60 [1714762986225]
Audit: 2024-03-zksync-findings
Severity: MEDIUM

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

 ### Low-05 StateTransitionManger cannot unfreeze a frozen chain to an error in StateTransitionManager.sol.
**Instances(1)**
A state transition (ST)'s `freezeDiamond()` or `unfreezeDiamond` is intended to be called by either StateTransitionManger or the admin of the state transition. However, there is an error in `unfreezeChain` on StateTransitionManager.sol that will prevent StateTransitionManger from unfreezing a chain when needed.

```solidity
//code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol
    function unfreezeChain(uint256 _chainId) external onlyOwner {
          //@audit this should be `unfreezeDiamond()`. This error prevents StateTransitionManager from freezing a chain(ST) when needed.
|>        IZkSyncStateTransition(stateTransition[_chainId]).freezeDiamond();
    }
```
(https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol#L166)

A state transition(ST) can be malicious. The hyperchain implementation is based on the assumption that StateTransitionManager(STM) is [the main point of trust](https://github.com/code-423n4/2024-03-zksync/blob/main/docs/Smart%20contract%20Section/L1%20ecosystem%20contracts.md#state-transition-manager-stm). This error prevents STM to exercise key control over ST to unfreeze an ST. If the ST admin is malicious and intends to freeze the chain for longer, STM won't be able to exercise intended control of the malicious ST.

```solidity
//code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol
      //@audit Due to error in StateTransitionManger, only ST admin can unfreeze the chain
|>    function unfreezeDiamond() external onlyAdminOrStateTransitionManager {
        Diamond.DiamondStorage storage diamondStorage = Diamond
            .getDiamondStorage();
        require(diamondStorage.isFrozen, "a7"); // diamond proxy is not frozen
        diamondStorage.isFrozen = false;
        emit Unfreeze();
    }
```
(https://github.com/code-423n4/2024-03-zksync/blob/4f0ba34f34a864c354c7e8c47643ed8f4a250e13/code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol#L143) 

Recommendations:
In `unfreezeChain`, change to call `unfreezeDiamond()`.