Validium Docs
  • Overview
  • Connect to Validium
  • Start Coding 🚀
    • Quickstart
      • Overview
      • Deploy using Validium CLI
      • Deploy using Quickstart Repository
      • Deploy your first contract
      • Create an ERC20 token
  • Tooling
    • Block Explorers
    • Hardhat-Validium
      • Overview
      • Installation
      • Guides
        • Getting started
        • Migrating Hardhat project to Validium
        • Compiling non-inlinable libraries
      • Plugins
        • hardhat-zksync
        • hardhat-zksync-solc
        • hardhat-zksync-vyper
        • hardhat-zksync-deploy
        • hardhat-zksync-upgradable
        • hardhat-zksync-verify
        • hardhat-zksync-verify-vyper
        • hardhat-zksync-ethers
        • hardhat-zksync-node
        • Hardhat Community Plugins
    • Foundary
      • Overview
      • Installation
      • Getting Started
      • Migration Guide
        • Overview
        • Compilation
        • Deployment
        • Testing
  • Test and Debug
    • Getting Started
    • Docker L1 - L2 Nodes
    • In-Memory Node
    • Continuous Integration
    • Hardhat
    • Foundry
  • API Reference
    • Overview
    • Conventions
    • ZKs JSON-RPC API
    • Debug JSON-RPC API
    • Ethereum JSON-RPC API
    • PubSub JSON-RPC API
  • Concepts
    • Transaction Lifecycle
    • Blocks and Batches
    • Validium Network Fee Mechanism
    • Finality
    • System Upgrades
    • ZK Chains
    • Data Availability
      • Overview
      • Recreating L2 state from L1 pubdata
      • Validiums
    • Account Abstraction
    • L1 <-> L2 Communication
  • Components
    • Overview
    • Smart & System Contracts
      • Smart Contracts
      • System Contracts
    • Shared Bridges
    • Sequencer / Server
    • Validium Network EVM
      • Overview
      • Bootloader
      • Precompiles
      • Virtual Machine Specification
        • ZKsync Virtual Machine primer
        • VM Formal Specification
    • Prover
      • Overview
      • ZK Terminology
      • Running the Prover
      • Circuits
        • Overview
        • Circuit Testing
        • CodeDecommitter
        • DemuxLogQueue
        • ECRecover
        • KeccakRoundFunction
        • L1MessagesHasher
        • LogSorter
        • Main VM
        • RAMPermutation
        • Sha256RoundFunction
        • StorageApplication
        • Sorting and Deduplicating
          • Overview
          • SortDecommitments
          • StorageSorter
          • LogSorter
      • Boojum Gadgets
      • Boojum Function - `check_if_satisfied`
    • Compiler
      • Compiler Toolchain Overview
        • Compiler Toolchain Overview
        • Solidity Compiler
        • Vyper Compiler
        • LLVM Framework
      • Specification
        • Overview
        • Code Separation
        • System Contracts
        • Exception Handling
        • EVM Legacy Assembly Translator
        • Instructions
          • Instruction Reference
          • EVM
            • Native EVM Instructions
            • Arithmetic
            • Bitwise
            • Block
            • Call
            • Create
            • Environment
            • Logging
            • Logical
            • Memory
            • Return
            • Sha3
            • Stack
          • Extensions
            • Overview
            • Validium Network Extension Simulation (call)
            • Validium Network Extension Simulation (verbatim)
          • EVM Legacy Assembly
          • Yul
        • EraVM Binary Layout
    • Fee Withdrawer
    • Portal - Wallet + Bridge
    • Block Explorer
    • Transaction filtering
Powered by GitBook
On this page
  • Contract Level
  • Function Level
  1. Components
  2. Compiler
  3. Specification

Exception Handling


This document explains some peculiarities of the exception handling (EH) in zkEVM architecture.

In a nutshell, there are two EH mechanisms in zkEVM: contract-level and function-level. The former was inherited from the EVM architecture, and the latter is more common to general-purpose languages.

Contract Level
Function Level

Yul Example

revert(0, 0)

verbatim("throw")

Native to

EVM

General-purpose languages

Handled by

zkEVM

Compiler

Catchable by

Caller contract

Caller function

Efficient

Yes

Huge size impact due to numerous catch blocks. Extra cycles are needed for propagating the exception.

Contract Level

This type of exceptions is inherited from the EVM architecture. On EVM, such instructions as REVERT and INVALID, immediately terminate the contract execution and return the control to the callee. It is impossible to catch them within the contract, and it can be only done on the callee side with checking the call status code.

// callee
revert(0, 0)

// caller
let success = call(...)
if iszero(success) {
    // option 1: rethrow on the contract level
    returndatacopy(...)
    revert(...)

    // option 2: rethrow on the function level
    verbatim("throw") // only available in the Yul mode and upcoming zkEVM solc
}

zkEVM behaves exactly the same. The VM automatically unwinds the call stack up to the uppermost function frame of the contract, leaving no possibility to catch and handle it on the way.

These types of exceptions are more efficient, as you can revert at any point of the execution without propagating the control flow all the way up to the uppermost function frame.

Implementation

Function Level

This type of exceptions is more common to general-purpose languages like C++. That is why it was easy to support within the LLVM framework, even though it is not supported by the smart contract languages we work with. That is also one of the reasons why the two EH mechanisms are handled separately and barely interact in the high-level code.

In general-purpose languages a set of EH tools is usually available, e.g. try , throw, and catch keywords that define which piece of code may throw and how the exception must be handled. However, these tools are not available in Solidity and its EVM Yul dialect, so some extensions have been added in the zkEVM Yul dialect compiled by zksolc, but there are limitations, some of which are dictated by the nature of smart contracts:

  1. Every function beginning with ZKSYNC_NEAR_CALL is implicitly wrapped with try. If there is an exception handler defined, the following will happen:

    • A panic will be caught by the caller of such function.

    • The control will be transferred to EH function. There can be only one EH function and it must be named ZKSYNC_CATCH_NEAR_CALL. It is not very efficient, because all functions must have an LLVM IR catch block that will catch and propagate the exception and call the EH function.

    • When the EH function has finished executing, the caller of ZKSYNC_NEAR_CALL receives the control back.

  2. Every operation is throw. Since any instruction can panic due to out-of-gas, all of them can throw. It is another thing reducing the potential for optimizations.

  3. The catch block is represented by the ZKSYNC_CATCH_NEAR_CALL function in Yul. A panic in ZKSYNC_NEAR_CALL will make their caller catch the exception and call the EH function. After the EH function is executed, the control is returned to the caller of ZKSYNC_NEAR_CALL.

// Follow the numbers for the order of execution. The call order is:
// caller -> ZKSYNC_NEAR_CALL_callee -> callee_even_deeper -> ZKSYNC_CATCH_NEAR_CALL -> caller

function ZKSYNC_NEAR_CALL_callee() -> value {    // 03
    value := callee_even_deeper()                // 04
}

function callee_even_deeper() -> value {         // 05
    verbatim("throw")                            // 06
}

// In every function an implicit 'catch' block in LLVM IR is created.
// This block will do the following:
//     1. Keep the return value ('zero') zero-initialized if one is expected
//     2. Call the EH function ('ZKSYNC_CATCH_NEAR_CALL')
//     3. Return the control flow to the next instruction ('value := 42')
function caller() -> value {                      // 01
    let zero := ZKSYNC_NEAR_CALL_callee()         // 02
    value := 42                                   // 09
}

// This handler could also be doing a revert.
// Reverts in EH functions work in the same way as without EH at all.
// They immediately terminate the execution and the control is given to the contract callee.
function ZKSYNC_CATCH_NEAR_CALL() {               // 07
    log0(...)                                     // 08
}

Having all the overhead above, the catch blocks are only generated if there is the EH function ZKSYNC_CATCH_NEAR_CALL defined in the contract. Otherwise there is no need to catch panics and they will be propagated to the callee contract automatically by the VM execution environment.

Implementation

In EraVM, there are two ways of implementing contract-local function calls:

Using jump is more lightweight and cheaper, but using call/ret is more feature-rich:

  1. In case of panic or revert, the storage effects and queues of this function are rolled back.

  2. It is possible to pass a portion of available gas; the unused gas will be returned to the caller, unless the function panicked.

  3. It is possible to set up a custom exception handler.

Prefixing Yul function name with ZKSYNC_NEAR_CALL_ allows to use this additional, platform-specific functionality, implemented by the call instruction. For other functions, the choice between call/ret or jump is up to the compiler.

PreviousSystem ContractsNextEVM Legacy Assembly Translator

Last updated 8 months ago

In EraVM, contracts call each other using . It as one of its arguments.

Saving the return address and using a instruction to call; using instruction with saved return address to return.

Using instruction to call; using one of ret instructions with modifiers , , or to return.

far_call instruction
accepts the address of the exception handler
jump
jump
call
ok
revert
panic