Installing solx

You can start using solx in the following ways:

  1. Download stable releases. See Static Executables.
  2. Build solx from sources. See Building from Source.

System Requirements

It is recommended to have at least 4 GB of RAM to compile large projects. The compilation process is parallelized by default, so the number of threads used is equal to the number of CPU cores.

Large projects can consume a lot of RAM during compilation on machines with a high number of cores. If you encounter memory issues, consider reducing the number of threads using the --threads option.

The table below outlines the supported platforms and architectures:

CPU/OSMacOSLinuxWindows
x86_64
arm64

Please avoid using outdated distributions of operating systems, as they may lack the necessary dependencies or include outdated versions of them. solx is only tested on recent versions of popular distributions, such as MacOS 11.0 and Windows 10.

Versioning

The solx version consists of three parts:

  1. solx version itself.
  2. Version of solc libraries solx is statically linked with.
  3. Revision of the LLVM-friendly fork of solc maintained by the solx team.

For instance, the latest revision of the latest version of solc is 0.8.30-1.0.2. Here are the solc revisions released by now:

RevisionFixes
v1.0.0Compatibility between EVM assembly and LLVM IR
v1.0.1A compiler crash with nested try-catch patterns
v1.0.2Metadata of recursive calls across inheritance

We recommend always using the latest version of solx to benefit from the latest features and bug fixes.

Ethereum Development Toolkits

For large codebases, it is more convenient to use solx via toolkits such as Foundry and Hardhat. These tools manage compiler input and output on a higher level, and provide additional features like incremental compilation and caching.

Static Executables

We ship solx binaries on the releases page of the eponymous repository. This repository maintains intuitive and stable naming for the executables and provides a changelog for each release. Tools using solx must download the binaries from this repository and cache them locally.

All executables are statically linked and must work on all recent platforms without issues.

Building from Source

Please consider using the pre-built executables before building from source. Building from source is only necessary for development, research, and debugging purposes. Deployment and production use cases should rely only on the officially released executables.

  1. Install the necessary system-wide dependencies.

    • For Linux (Debian):
    apt install cmake ninja-build curl git libssl-dev pkg-config clang lld
    
    • For Linux (Arch):
    pacman -Syu which cmake ninja curl git pkg-config clang lld
    
    • For MacOS:

      1. Install the Homebrew package manager by following the instructions at brew.sh.

      2. Install the necessary system-wide dependencies:

        brew install cmake ninja coreutils
        
      3. Install a recent build of the LLVM/Clang compiler using one of the following tools:

  2. Install Rust.

    The easiest way to do it is following the latest official instructions.

The Rust version used for building is pinned in the rust-toolchain.toml file at the repository root. cargo will automatically download the pinned version of rustc when you start building the project.

  1. Clone and checkout this repository with submodules.

    git clone https://github.com/matter-labs/solx --recursive
    

    By default, submodules checkout is disabled to prevent cloning large repositories via cargo. If you're building locally, ensure all submodules are checked out with:

    git submodule update --recursive --checkout
    
  2. Install the Matter Labs LLVM framework builder. This tool clones the repository of Matter Labs LLVM Framework and runs a sequence of build commands tuned for the needs of solx.

    cargo install compiler-llvm-builder
    

    To fine-tune your build of Matter Labs LLVM framework, refer to the section on tuning the Matter Labs LLVM build.

Always use the latest version of the builder to benefit from the latest features and bug fixes. To check for new versions and update the builder, simply run cargo install compiler-llvm-builder again, even if you have already installed the builder. The builder is not the LLVM framework itself, but only a tool to build it. By default, it is installed in ~/.cargo/bin/, which is usually added to your PATH during the Rust installation process.

  1. Build the LLVM framework using the zksync-llvm tool.

    # Navigate to the root of your local copy of this repository.
    cd solx
    # Build the LLVM framework.
    zksync-llvm build
    

    For more information and available build options, run zksync-llvm build --help.

  2. Build the solc libraries.

    cd era-solidity
    mkdir -pv build
    cd build
    cmake .. \
       -DPEDANTIC='OFF' \
       -DTESTS='OFF' \
       -DCMAKE_BUILD_TYPE='Release' \
       -DSOL_VERSION_ZKSYNC='0.8.30-1.0.2'
    cmake --build . --config Release --parallel ${YOUR_CPU_COUNT}
    cd ../..
    

    The sequence above may fail with clang v20 and/or Boost 1.88. We are currently looking for a solution and will document it when we find one. If you encounter compilation errors, try specifying the aforementioned versions to replace your system default:

    # MacOS example
    
    # Install Boost v1.85
    brew install boost@1.85
    
    # Make Boost v1.85 default
    brew unlink boost
    brew link boost@1.85
    
    # Install LLVM with clang v19
    brew install llvm@19
    
    # Re-run the build command
    cmake .. \
       -DCMAKE_C_COMPILER=/opt/homebrew/opt/llvm@19/bin/clang \
       -DCMAKE_CXX_COMPILER=/opt/homebrew/opt/llvm@19/bin/clang++ \
       -DPEDANTIC=OFF \
       -DTESTS=OFF \
       -DCMAKE_BUILD_TYPE="Release" \
       -DSOL_VERSION_ZKSYNC="0.8.30-1.0.2"
    cmake --build . --config Release --parallel ${YOUR_CPU_COUNT}
    

    The -DSOL_VERSION_ZKSYNC flag is used to specify the version-revision of the solc that is reported by solx. By default, we recommend keeping the revision at 1.0.2 to follow our versioning. Otherwise, feel free to change it according to your needs.

  3. Build the solx executable.

    cargo build --release
    

    The solx executable will appear as ./target/release/solx, where you can run it directly or move it to another location.

    If cargo cannot find the LLVM build artifacts, ensure that the LLVM_SYS_191_PREFIX environment variable is not set in your system, as it may be pointing to a location different from the one expected by solx.

Tuning the LLVM build

  • For more information and available build options, run zksync-llvm build --help.

  • Use the --use-ccache option to speed up the build process if you have ccache installed.

  • To build the Matter Labs LLVM framework using specific C and C++ compilers, pass additional arguments to CMake using the --extra-args option:

    # Pay special attention to character escaping.
    
    zksync-llvm build \
      --use-ccache \
      --extra-args \
        '\-DCMAKE_C_COMPILER=/opt/homebrew/Cellar/llvm@18/18.1.8/bin/clang' \
        '\-DCMAKE_BUILD_TYPE=Release' \
        '\-DCMAKE_CXX_COMPILER=/opt/homebrew/Cellar/llvm@18/18.1.8/bin/clang++' 
    

Building LLVM manually

  • If you prefer building your LLVM framework manually, include the following flags in your CMake command:

    # We recommended using the latest version of CMake.
    
    -DLLVM_TARGETS_TO_BUILD='EVM'
    -DLLVM_ENABLE_PROJECTS='lld'
    -DBUILD_SHARED_LIBS='Off'
    

For most users, the Matter Labs LLVM builder is the recommended way to build the framework. This section was added for compiler toolchain developers and researchers with specific requirements and experience with the LLVM framework. We are going to present a more detailed guide for LLVM contributors in the future.

Command Line Interface (CLI)

The CLI of solx is designed to mimic that of solc. There are several main input/output (I/O) modes in the solx interface:

The basic CLI is simpler and suitable for using from the shell. The standard JSON mode is similar to client-server interaction, thus more suitable for using from other applications.

All toolkits using solx must be operating in standard JSON mode and follow its specification. It will make the toolkits more robust and future-proof, as the standard JSON mode is the most versatile and used for the majority of popular projects.

This page focuses on the basic CLI mode. For more information on the standard JSON mode, see this page.

Basic CLI

Basic CLI mode is the simplest way to compile a file with the source code.

To compile a basic Solidity contract, run the simple example from the --bin section.

The rest of this section describes the available CLI options and their usage. You may also check out solx --help for a quick reference.

--bin

Emits the full bytecode.

solx 'Simple.sol' --bin

Output:

======= Simple.sol:Simple =======
Binary:
5b60806040525f341415601c5763...

--bin

Emits the runtime part of the bytecode.

solx 'Simple.sol' --bin-runtime

Output:

======= Simple.sol:Simple =======
Binary of the runtime part:
5b60806040525f34141560145760...

--asm

Emits the text assembly produced by LLVM.

solx 'Simple.sol' --asm

Output:

======= Simple.sol:Simple =======
Deploy LLVM EVM assembly:
        .text
        .file   "Simple.sol:Simple"
main:
.func_begin0:
        JUMPDEST
        PUSH1 128
        PUSH1 64
...

Runtime LLVM EVM assembly:
        .text
        .file   "Simple.sol:Simple.runtime"
main:
.func_begin0:
        JUMPDEST
        PUSH1 128
        PUSH1 64
...

--metadata

Emits the contract metadata. The metadata is a JSON object that contains information about the contract, such as its name, source code hash, the list of dependencies, compiler versions, and so on.

The solx metadata format is compatible with the Solidity metadata format. This means that the metadata output can be used with other tools that support Solidity metadata. Extra solx data is inserted into solc metadata with this JSON object:

{
  "solx": {
    "llvm_options": [],
    "optimizer_settings": {
      "is_debug_logging_enabled": false,
      "is_fallback_to_size_enabled": false,
      "is_verify_each_enabled": false,
      "level_back_end": "Aggressive",
      "level_middle_end": "Aggressive",
      "level_middle_end_size": "Zero"
    },
    "solc_llvm_revision": "1.0.2",
    "solc_version": "0.8.30",
    "solx_version": "0.1.0"
  }
}

Usage:

solx 'Simple.sol' --metadata

Output:

======= Simple.sol:Simple =======
Metadata:
{"compiler":{"version":"0.8.30+commit.89ae86f4"},"language":"Solidity","output":{"abi":[{"inputs":[],"name":"first","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"second","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"compilationTarget":{"Simple.sol":"Simple"},"evmVersion":"cancun","libraries":{},"metadata":{"bytecodeHash":"ipfs"},"optimizer":{"enabled":true,"runs":200},"remappings":[]},"solx":{"llvm_options":[],"optimizer_settings":{"is_debug_logging_enabled":false,"is_fallback_to_size_enabled":false,"is_verify_each_enabled":false,"level_back_end":"Aggressive","level_middle_end":"Aggressive","level_middle_end_size":"Zero"},"solc_llvm_revision":"1.0.2","solc_version":"0.8.30","solx_version":"0.1.0"},"sources":{"Simple.sol":{"keccak256":"0x1145e81d58e9fd0859036aac4ba16cfcfbe11045e3dfd5105a2dca469f31db89","license":"MIT","urls":["bzz-raw://9d97789b5c14a95fac1e7586de6712119f4606f79d6771324c9d24417ebab0db","dweb:/ipfs/QmSZ3HNGZom6N6eb8d74Y7UQAKAGRkXgbinwVVLaiuGb3S"]}},"version":1}

--ast-json

Emits the AST of each Solidity file.

solx 'Simple.sol' --ast-json

Output:

======= Simple.sol:Simple =======
JSON AST:
{"absolutePath":".../Simple.sol","exportedSymbols":{"Test":[26]},"id":27,"license":"MIT","nodeType":"SourceUnit","nodes":[ ... ],"src":"506:265:0"}

Since solx communicates with solc only via standard JSON under the hood, full JSON AST instead is emitted instead of the compact one.

--abi

Emits the contract ABI specification.

solx 'Simple.sol' --abi

Output:

======= Simple.sol:Simple =======
Contract JSON ABI:
[{"inputs":[],"name":"first","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"second","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"}]

--hashes

Emits the contract function signatures.

solx 'Simple.sol' --hashes

Output:

======= Simple.sol:Simple =======
Function signatures:
3df4ddf4: first()
5a8ac02d: second()

--storage-layout

Emits the contract storage layout.

solx 'Simple.sol' --storage-layout

Output:

======= Simple.sol:Simple =======
Contract Storage Layout:
{"storage":[{"astId":3,"contract":"Simple.sol:Simple","label":"field_1","offset":0,"slot":"0","type":"t_uint256"},{"astId":5,"contract":"Simple.sol:Simple","label":"field_2","offset":0,"slot":"1","type":"t_uint256"},{"astId":7,"contract":"Simple.sol:Simple","label":"field_3","offset":0,"slot":"2","type":"t_uint256"}],"types":{"t_uint256":{"encoding":"inplace","label":"uint256","numberOfBytes":"32"}}}

--transient-storage-layout

Emits the contract transient storage layout.

solx 'Simple.sol' --transient-storage-layout

Output:

======= Simple.sol:Simple =======
Contract Transient Storage Layout:
{"storage":[{"astId":3,"contract":"Simple.sol:Simple","label":"field_1","offset":0,"slot":"0","type":"t_uint256"},{"astId":5,"contract":"Simple.sol:Simple","label":"field_2","offset":0,"slot":"1","type":"t_uint256"},{"astId":7,"contract":"Simple.sol:Simple","label":"field_3","offset":0,"slot":"2","type":"t_uint256"}],"types":{"t_uint256":{"encoding":"inplace","label":"uint256","numberOfBytes":"32"}}}

--userdoc

Emits the contract user documentation.

solx 'Simple.sol' --userdoc

Output:

======= Simple.sol:Simple =======
User Documentation:
{"kind":"user","methods":{ ... },"version":1}

--devdoc

Emits the contract developer documentation.

solx 'Simple.sol' --devdoc

Output:

======= Simple.sol:Simple =======
Developer Documentation:
{"kind":"dev","methods":{ ... },"version":1}

--asm-solc-json

Emits the solc EVM assembly in JSON format.

solx 'Simple.sol' --asm-solc-json

Output:

======= Simple.sol:Simple =======
EVM assembly:
{".auxdata":null,".code":[ ... ],".data":{"0":{".auxdata":"a26469706673582212202644e52ba9ffa2e1d55713f314f19bc59467d1342b170ca4ce0e2d6d0e7afda664736f6c634300081e0033",".code":[ ... ],".data":null}},"full_path":"Simple.sol:Simple"}

This is the solc EVM assembly output that is translated to LLVM IR by solx. For solx's own EVM assembly output emitted by LLVM, use the --asm option instead.

--ir-optimized

Emits the optimized solc Yul IR.

solx 'Simple.sol' --ir-optimized

Output:

======= Simple.sol:Simple =======
Optimized IR:
/// @use-src 0:"Simple.sol:Simple"
object "Test_26" {
    code {
        {
            ...
        }
    }
    /// @use-src 0:"Simple.sol:Simple"
    object "Test_26_deployed" {
        code {
            {
                ...
            }
        }
        data ".metadata" hex"a2646970667358221220ccbacddd0734a08eedf3a12f841c7e8c7127e2457a91f6e68f5fac6df7b9c88a64736f6c634300081e0033"
    }
}

solx always requests optimized Yul IR from solc to ensure that all inlineable libraries are always inlined. For the sake of consistency, solx does not support the unoptimized Yul IR output, as it is not used in practice.

Input Files

solx supports multiple input files. The following command compiles two Solidity files and prints the bytecode:

solx 'Simple.sol' 'Complex.sol' --bin

Solidity import remappings are passed in the way as input files, but they are distinguished by a = symbol between source and destination. The following command compiles a Solidity file with a remapping and prints the bytecode:

solx 'Simple.sol' 'github.com/ethereum/dapp-bin/=/usr/local/lib/dapp-bin/' --bin

solx does not handle remappings itself, but only passes them through to solc. Visit the solc documentation to learn more about the processing of remappings.

--libraries

Specifies the libraries to link with compiled contracts. The option accepts multiple string arguments. The safest way is to wrap each argument in single quotes, and separate them with a space.

The specifier has the following format: <ContractPath>:<ContractName>=<LibraryAddress>.

Usage:

solx 'Simple.sol' --bin --libraries 'Simple.sol:Test=0x1234567890abcdef1234567890abcdef12345678'

--base-path, --include-path, --allow-paths

These options are used to specify Solidity import resolution settings. They are not used by solx and only passed through to solc like import remappings.

Visit the solc documentation to learn more about the processing of these options.

--output-dir

Specifies the output directory for build artifacts. Can only be used in basic CLI mode.

Usage in basic CLI mode:

solx 'Simple.sol' --bin --asm --metadata --output-dir './build/'
ls './build/Simple.sol'

Output:

Compiler run successful. Artifact(s) can be found in directory "build".
...
Test.zasm       Test.zbin       Test_meta.json

--overwrite

Overwrites the output files if they already exist in the output directory. By default, solx does not overwrite existing files.

Can only be used in combination with the --output-dir option.

Usage:

solx 'Simple.sol' --bin --output-dir './build/' --overwrite

If the --overwrite option is not specified and the output files already exist, solx will print an error message and exit:

Error: Refusing to overwrite an existing file "./build/Simple.sol/Test.bin" (use --overwrite to force).

--version

Prints the version of solx and the hash of the LLVM commit it was built with.

Usage:

solx --version

--help

Prints the help message.

Usage:

solx --help

Other I/O Modes

The mode-altering CLI options are mutually exclusive. This means that only one of the options below can be enabled at a time:

--standard-json

For the standard JSON mode usage, see the Standard JSON page.

solx Compilation Settings

The options in this section are only configuring the solx compiler and do not affect the underlying solc compiler.

--optimization / -O

Sets the optimization level of the LLVM optimizer. Available values are:

LevelMeaningHints
0No optimizationFor fast compilation during development
1Performance: basicFor optimization research
2Performance: defaultFor optimization research
3Performance: aggressiveBest performance for production
sSize: defaultFor optimization research
zSize: aggressiveBest size for contracts with size constraints

For most cases, it is fine to keep the default value of 3. You should only use the level z if you are ready to deliberately sacrifice performance and optimize for size.

Large contracts may hit the EVM bytecode size limit. In this case, it is recommended using the --optimization-size-fallback option rather than setting the level to z.

--optimization-size-fallback

Sets the optimization level to z for contracts that failed to compile due to overrunning the bytecode size constraints.

Under the hood, this option automatically triggers recompilation of contracts with level z. Contracts that were successfully compiled with the original --optimization setting are not recompiled.

For deployment, it is recommended to have this option always enabled to reduce issues with bytecode size constraints. If your environment does not have bytecode size limitations, it is better to keep this option disabled to prevent unnecessary recompilations.

--metadata-hash

Specifies the hash format used for contract metadata.

Usage with ipfs:

solx 'Simple.sol' --bin --metadata-hash 'ipfs'

Output with ipfs:

======= Simple.sol:Simple =======
Binary:
5b60806040525f341415601c5763000000488063000000245f395ff35b5f80...
a2646970667358221220ba14ea4e52366f139a845913d41e98933393bd1c1126331611687003d4aa92de64736f6c6378247a6b736f6c633a312e352e31333b736f6c633a302e382e32393b6c6c766d3a312e302e310055

The byte array starting with a2 at the end of the bytecode is a CBOR-encoded compiler version data and an optional metadata hash.

The last two bytes of the metadata (0x0055) are not a part of the CBOR payload, but the length of it, which must be known to correctly decode the payload.

JSON representation of the CBOR payload:

{
    // Optional: included if `--metadata-hash` is set to `ipfs`.
    "ipfs": "1220ba14ea4e52366f139a845913d41e98933393bd1c1126331611687003d4aa92de",

    // Required: consists of semicolon-separated pairs of colon-separated compiler names and versions.
    // `solx:<version>` is always included.
    // `solc:<version>;llvm:<version>` is only included for Solidity and Yul contracts, but not included for LLVM IR ones.
    // `llvm` stands for the revision of Matter Labs fork of solc, that solx is statically linked with.
    "solc": "solx:0.1.0;solc:0.8.30;llvm:1.0.2"
}

For more information on these formats, see the CBOR and IPFS documentation.

--no-cbor-metadata

Disables the CBOR metadata that is appended at the end of bytecode. This option is useful for debugging and research purposes.

It is not recommended to use this option in production, as it is not possible to verify contracts deployed without metadata.

Usage:

solx 'Simple.sol' --no-cbor-metadata

--llvm-options

Specifies additional options for the LLVM framework. The argument must be a single quoted string following a = separator.

Usage:

solx 'Simple.sol' --bin --llvm-options='-key=value'

The --llvm-options option is experimental and must only be used by experienced users. All supported options will be documented in the future.

solc Compilation Settings

The options in this section are only configuring solc, so they are passed directly to its child process, and do not affect the solx compiler.

--via-ir

Switches the solc codegen to Yul a.k.a. IR.

Usage:

solx 'Simple.sol' --bin --via-ir

--evm-version

Specifies the EVM version solc will produce artifacts for. Only artifacts such as Yul and EVM assembly are known to be affected by this option. For instance, if the EVM version is set to cancun, then Yul and EVM assembly may contain MCOPY instructions, so no calls to the Identity precompile (address 0x04) will be made.

EVM version only affects IR artifacts produced by solc and only indirectly affects EVM bytecode produced by solx.

The default value is chosen by solc. For instance, solc v0.8.24 and older use shanghai by default, whereas newer ones use cancun.

The following values are allowed, however have in mind that newer EVM versions are only supported by newer versions of solc:

  • homestead
  • tangerineWhistle
  • spuriousDragon
  • byzantium
  • constantinople
  • petersburg
  • istanbul
  • berlin
  • london
  • paris
  • shanghai
  • cancun
  • prague

Usage:

solx 'Simple.sol' --bin --evm-version 'cancun'

For more information on how solc handles EVM versions, see its EVM version documentation.

--metadata-literal

Tells solc to store referenced sources as literal data in the metadata output.

This option only affects the contract metadata output produced by solc, and does not affect artifacts produced by solx.

Usage:

solx 'Simple.sol' --bin --metadata --metadata-literal

--no-import-callback

Disables the default import resolution callback in solc.

This parameter is used by some tooling that resolves all imports by itself, such as Hardhat.

Usage:

solx 'Simple.sol' --no-import-callback

Multi-Language Support

solx supports input in multiple programming languages:

The following sections outline how to use solx with these languages.

--yul (or --strict-assembly)

Enables the Yul mode. In this mode, input is expected to be in the Yul language. The output works the same way as with Solidity input.

Usage:

solx --yul 'Simple.yul' --bin

Output:

======= Simple.yul =======
Binary:
5b60806040525f341415601c5763...

--llvm-ir

Enables the LLVM IR mode. In this mode, input is expected to be in the LLVM IR language. The output works the same way as with Solidity input.

In this mode, every input file is treated as runtime code, while deploy code will be generated automatically by solx. It is not possible to write deploy code manually yet, but it will be supported in the future.

Unlike solc, solx is an LLVM-based compiler toolchain, so it uses LLVM IR as an intermediate representation. It is not recommended to write LLVM IR manually, but it can be useful for debugging and optimization purposes. LLVM IR is more low-level than Yul and EVM assembly in the solx IR hierarchy.

Usage:

solx --llvm-ir 'Simple.ll' --bin

Output:

======= Simple.ll =======
Binary:
5b60806040525f341415601c5763...

Debugging

--debug-output-dir

Specifies the directory to store intermediate build artifacts. The artifacts can be useful for debugging and research.

The directory is created if it does not exist. If artifacts are already present in the directory, they are overwritten.

The intermediate build artifacts can be:

NameVia IRExtension
EVM Assemblynoevmla
EthIRnoethir
Yulyesyul
LLVM IRanyll

Usage:

solx 'Simple.sol' --bin --debug-output-dir './debug/'
ls './debug/'

Output:

Compiler run successful. No output generated.
...
Simple.sol_Test.evmla
Simple.sol_Test.ethir
Simple.sol_Test.unoptimized.ll
Simple.sol_Test.optimized.ll
Simple.sol_Test.runtime.evmla
Simple.sol_Test.runtime.ethir
Simple.sol_Test.runtime.unoptimized.ll
Simple.sol_Test.runtime.optimized.ll

The output file name is constructed as follows: <ContractPath>_<ContractName>.<Modifiers>.<Extension>.

--llvm-verify-each

Enables the verification of the LLVM IR after each optimization pass. This option is useful for debugging and research purposes.

Usage:

solx 'Simple.sol' --bin --llvm-verify-each

--llvm-debug-logging

Enables the debug logging of the LLVM IR optimization passes. This option is useful for debugging and research purposes.

Usage:

solx 'Simple.sol' --bin --llvm-debug-logging

Standard JSON

Standard JSON is a protocol for interaction with the solx and solc compilers. This protocol must be implemented by toolkits such as Hardhat and Foundry.

The protocol uses two data formats for communication: input JSON and output JSON.

Usage

Input JSON can be provided by-value via the --standard-json option:

solx --standard-json './input.json'

Alternatively, the input JSON can be fed to solx via stdin:

cat './input.json' | solx --standard-json

For the sake of interface unification, solx will always return with exit code 0 and have its standard JSON output printed to stdout. It differs from solc that may return with exit code 1 and a free-formed error in some cases, such as when the standard JSON input file is missing, even though the solc documentation claims otherwise.

Input JSON

The input JSON provides the compiler with the source code and settings for the compilation. The example below serves as the specification of the input JSON format.

This format introduces several solx-specific parameters such as settings.optimizer.sizeFallback. These parameters are marked as solx-only.

On the other hand, parameters that are not mentioned here but are parts of solc standard JSON protocol have no effect in solx.

{
  // Required: Source code language.
  // Currently supported: "Solidity", "Yul", "LLVM IR".
  "language": "Solidity",
  // Required: Source code files to compile.
  // The keys here are the "global" names of the source files. Imports can be using other file paths via remappings.
  "sources": {
    // In source file entry, either but not both "urls" and "content" must be specified.
    "myFile.sol": {
      // Required (unless "content" is used): URL(s) to the source file.
      "urls": [
        // In Solidity mode, directories must be added to the command-line via "--allow-paths <path>" for imports to work.
        // It is possible to specify multiple URLs for a single source file. In this case the first successfully resolved URL will be used.
        "/tmp/path/to/file.sol"
      ],
      // Required (unless "urls" is used): Literal contents of the source file.
      "content": "contract settable is owned { uint256 private x = 0; function set(uint256 _x) public { if (msg.sender == owner) x = _x; } }"
    }
  },

  // Required: Compilation settings.
  "settings": {
    // Optional: Optimizer settings.
    "optimizer": {
      // Optional, solx-only: Set the LLVM optimizer level.
      // Available options:
      // -0: do not optimize, currently unsupported
      // -1: basic optimizations for gas usage
      // -2: advanced optimizations for gas usage
      // -3: all optimizations for gas usage
      // -s: basic optimizations for bytecode size
      // -z: all optimizations for bytecode size
      // Default: 3.
      "mode": "3",
      // Optional, solx-only: Re-run the compilation with "mode": "z" if the initial compilation exceeds the EVM bytecode size limit.
      // Used on a per-contract basis and applied automatically, so some contracts will end up compiled in the initial mode, and others with "mode": "z".
      // Default: false.
      "sizeFallback": false
    },

    // Optional: Sorted list of remappings.
    // Important: Only used with Solidity input.
    "remappings": [ ":g=/dir" ],
    // Optional: Addresses of the libraries.
    // If not all library addresses are provided here, it will result in unlinked bytecode files that will require post-compile-time linking before deployment.
    // Important: Only used with Solidity, Yul, and LLVM IR input.
    "libraries": {
      // The top level key is the name of the source file where the library is used.
      // If remappings are used, this source file should match the global path after remappings were applied.
      "myFile.sol": {
        // Source code library name and address where it is deployed.
        "MyLib": "0x123123..."
      }
    },

    // Optional: Version of EVM solc will produce IR for.
    // Affects type checking and code generation.
    // Can be "homestead", "tangerineWhistle", "spuriousDragon", "byzantium", "constantinople", "petersburg", "istanbul", "berlin", "london", "paris", "shanghai", "cancun" or "prague".
    // Only used with Solidity, and only affects Yul and EVM assembly codegen. For instance, with version "cancun", solc will produce `MCOPY` instructions, whereas with older EVM versions it will not.
    // Default: "cancun".
    "evmVersion": "cancun",
    // Optional: Select the desired output.
    // Default: no flags are selected, and no output is generated.
    "outputSelection": {
      "<path>": {
        // Available file-level options, must be listed under "<path>"."":
        "": [
          // AST of all source files.
          "ast"
        ],
        // Available contract-level options, must be listed under "<path>"."<name>":
        "<name>": [
          // Solidity ABI.
          "abi",
          // Metadata.
          "metadata",
          // Developer documentation (natspec).
          "devdoc",
          // User documentation (natspec).
          "userdoc",
          // Slots, offsets and types of the contract's state variables in storage.
          "storageLayout",
          // Slots, offsets and types of the contract's state variables in transient storage.
          "transientStorageLayout",
          // Yul produced by solc.
          "irOptimized",
          // Everything of the below.
          "evm",
          // Solidity function hashes.
          "evm.methodIdentifiers",
          // EVM assembly produced by solc.
          "evm.legacyAssembly",
          // Everything that starts with "evm.bytecode".
          "evm.bytecode",
          // Deploy bytecode produced by solx/LLVM.
          // As long as the solx bytecode linker is in experimental stage, all contracts will be compiled if this key is enabled for at least one contract.
          "evm.bytecode.object",
          // Deploy code assembly produced by solx/LLVM.
          "evm.bytecode.llvmAssembly",
          // Unsupported, but emitted as an empty object to preserve compatibility with some toolkits.
          "evm.bytecode.opcodes",
          // Unsupported, but emitted as an empty object to preserve compatibility with some toolkits.
          "evm.bytecode.sourceMap",
          // Unsupported, but emitted as an empty object to preserve compatibility with some toolkits.
          "evm.bytecode.functionDebugData",
          // Unsupported, but emitted as an empty object to preserve compatibility with some toolkits.
          "evm.bytecode.generatedSources",
          // Everything that starts with "evm.deployedBytecode".
          "evm.deployedBytecode",
          // Runtime bytecode produced by solx/LLVM.
          // As long as the solx bytecode linker is in experimental stage, all contracts will be compiled if this key is enabled for at least one contract.
          "evm.deployedBytecode.object",
          // Runtime code assembly produced by solx/LLVM.
          "evm.deployedBytecode.llvmAssembly",
          // Link references for linkers that are to resolve library addresses at deploy time.
          "evm.deployedBytecode.linkReferences",
          // Resolved automatically by solx/LLVM, but emitted as an empty object to preserve compatibility with some toolkits.
          "evm.deployedBytecode.immutableReferences",
          // Unsupported, but emitted as an empty object to preserve compatibility with some toolkits.
          "evm.deployedBytecode.opcodes",
          // Unsupported, but emitted as an empty object to preserve compatibility with some toolkits.
          "evm.deployedBytecode.sourceMap",
          // Unsupported, but emitted as an empty object to preserve compatibility with some toolkits.
          "evm.deployedBytecode.functionDebugData",
          // Unsupported, but emitted as an empty object to preserve compatibility with some toolkits.
          "evm.deployedBytecode.generatedSources"
        ]
      }
    },
    // Optional: Metadata settings.
    "metadata": {
      // Optional: Use the given hash method for the metadata hash that is appended to the bytecode.
      // Available options: "none", "ipfs".
      // Default: "ipfs".
      "bytecodeHash": "ipfs",
      // Optional: Use only literal content and not URLs.
      // Default: false.
      "useLiteralContent": true,
      // Optional: Whether to include CBOR-encoded metadata at the end of bytecode.
      // Default: true.
      "appendCBOR": true
    },
    // Optional: Enables the IR codegen in solc.
    "viaIR": true,

    // Optional, solx: Extra LLVM settings.
    "llvmOptions": [
      "-key", "value"
    ]
  }
}

Output JSON

The output JSON contains all artifacts produced by solx and solc together. The example below serves as the specification of the output JSON format.

{
  // Required: File-level outputs.
  "sources": {
    "sourceFile.sol": {
      // Required: Identifier of the source.
      "id": 1,
      // Optional: The AST object.
      // Corresponds to "ast" in the outputSelection settings.
      "ast": {/* ... */}
    }
  },

  // Required: Contract-level outputs.
  "contracts": {
    // The source name.
    "sourceFile.sol": {
      // The contract name.
      // If the language only supports one contract per file, this field equals to the source name.
      "ContractName": {
        // Optional: The Ethereum Contract ABI (object).
        // See https://docs.soliditylang.org/en/develop/abi-spec.html.
        // Corresponds to "abi" in the outputSelection settings.
        "abi": [/* ... */],
        // Optional: Storage layout (object).
        // Corresponds to "storageLayout" in the outputSelection settings.
        "storageLayout": {/* ... */},
        // Optional: Transient storage layout (object).
        // Corresponds to "transientStorageLayout" in the outputSelection settings.
        "transientStorageLayout": {/* ... */},
        // Optional: Contract metadata (string).
        // Corresponds to "metadata" in the outputSelection settings.
        "metadata": "/* ... */",
        // Optional: Developer documentation (natspec object).
        // Corresponds to "devdoc" in the outputSelection settings.
        "devdoc": {/* ... */},
        // Optional: User documentation (natspec object).
        // Corresponds to "userdoc" in the outputSelection settings.
        "userdoc": {/* ... */},
        // Optional: Yul produced by solc (string).
        // Corresponds to "irOptimized" in the outputSelection settings.
        "irOptimized": "/* ... */",
        // Optional: EVM target outputs.
        // Corresponds to "evm" in the outputSelection settings.
        "evm": {
          // Optional: EVM assembly produced by solc (object).
          // Corresponds to "evm.legacyAssembly" in the outputSelection settings.
          "legacyAssembly": {/* ... */},
          // Optional: List of function hashes (object).
          // Corresponds to "evm.methodIdentifiers" in the outputSelection settings.
          "methodIdentifiers": {
            // Mapping between the function signature and its hash.
            "delegate(address)": "5c19a95c"
          },
          // Optional: Deploy EVM bytecode.
          // Corresponds to "evm.bytecode" in the outputSelection settings.
          "bytecode": {
            // Optional: Bytecode (string).
            // Corresponds to "evm.bytecode.object" in the outputSelection settings.
            "object": "5b60806040525f341415601c5763...",
            // Optional: LLVM text assembly (string).
            // Corresponds to "evm.bytecode.llvmAssembly" in the outputSelection settings.
            "llvmAssembly": "/* ... */",
            // Optional: Link references for linkers that are to resolve library addresses at deploy time (object).
            // Corresponds to "evm.bytecode.linkReferences" in the outputSelection settings.
            "linkReferences": {/* ... */},
            // Optional: Always empty, included only to preserve compatibility with some toolkits (string).
            // Corresponds to "evm.bytecode.immutableReferences" in the outputSelection settings.
            "opcodes": {},
            // Optional: Always empty, Included only to preserve compatibility with some toolkits (string).
            // Corresponds to "evm.bytecode.sourceMap" in the outputSelection settings.
            "sourceMap": {},
            // Optional: Always empty, Included only to preserve compatibility with some toolkits (array).
            // Corresponds to "evm.bytecode.functionDebugData" in the outputSelection settings.
            "functionDebugData": {},
            // Optional: Always empty, Included only to preserve compatibility with some toolkits (object).
            // Corresponds to "evm.bytecode.generatedSources" in the outputSelection settings.
            "generatedSources": {}
          },
          // Optional: Runtime EVM bytecode.
          // Corresponds to "evm.deployedBytecode" in the outputSelection settings.
          "deployedBytecode": {
            // Optional: Bytecode (string).
            // Corresponds to "evm.deployedBytecode.object" in the outputSelection settings.
            "object": "5b60806040525f34141560145760...",
            // Optional: LLVM text assembly (string).
            // Corresponds to "evm.deployedBytecode.llvmAssembly" in the outputSelection settings.
            "llvmAssembly": "/* ... */",
            // Optional: Link references for linkers that are to resolve library addresses at deploy time (object).
            // Corresponds to "evm.deployedBytecode.linkReferences" in the outputSelection settings.
            "linkReferences": {/* ... */},
            // Optional: Resolved by LLVM automatically, so always returned as an empty object (object).
            // Included only to preserve compatibility with some toolkits.
            // Corresponds to "evm.deployedBytecode.immutableReferences" in the outputSelection settings.
            "immutableReferences": {},
            // Optional: Always empty, included only to preserve compatibility with some toolkits (string).
            // Corresponds to "evm.deployedBytecode.opcodes" in the outputSelection settings.
            "opcodes": {},
            // Optional: Always empty, Included only to preserve compatibility with some toolkits (string).
            // Corresponds to "evm.deployedBytecode.sourceMap" in the outputSelection settings.
            "sourceMap": {},
            // Optional: Always empty, Included only to preserve compatibility with some toolkits (array).
            // Corresponds to "evm.deployedBytecode.functionDebugData" in the outputSelection settings.
            "functionDebugData": {},
            // Optional: Always empty, Included only to preserve compatibility with some toolkits (object).
            // Corresponds to "evm.deployedBytecode.generatedSources" in the outputSelection settings.
            "generatedSources": {}
          }
        }
      }
    }
  },

  // Optional: Unset if no messages were emitted.
  "errors": [
    {
      // Optional: Location within the source file.
      // Unset if the error is unrelated to input sources.
      "sourceLocation": {
        /// Required: The source path.
        "file": "sourceFile.sol",
        /// Required: The source location start. Equals -1 if unknown.
        "start": 0,
        /// Required: The source location end. Equals -1 if unknown.
        "end": 100
      },
      // Required: Message type.
      // solc errors are listed at https://docs.soliditylang.org/en/latest/using-the-compiler.html#error-types.
      "type": "Error",
      // Required: Component the error originates from.
      "component": "general",
      // Required: Message severity.
      // Possible values: "error", "warning", "info".
      "severity": "error",
      // Optional: Unique code for the cause of the error.
      // Only solc produces error codes for now.
      // solx currently emits errors without codes, but they will be introduced soon.
      "errorCode": "3141",
      // Required: Message.
      "message": "Invalid keyword",
      // Required: Message formatted using the source location.
      "formattedMessage": "sourceFile.sol:100: Invalid keyword"
    }
  ]
}

Building with Sanitizers

This is the guide on building solx with sanitizers enabled.

Introduction

Sanitizers are tools that help find bugs in code. They are used to detect memory corruption, leaks, and undefined behavior. The most common sanitizers are AddressSanitizer, MemorySanitizer, and ThreadSanitizer.

If you are not familiar with sanitizers, see the official documentation.

Who is this guide for?

This guide is for developers who want to debug issues with solx.

Prerequisites

For sanitizers build to work, the host LLVM compiler version that is used to build LLVM MUST have the same version as the LLVM compiler that is used internally by `rustc` to build **solx**.

You can check the LLVM version used by rustc by running the following command rustc --version --verbose.

Build steps

The general steps to have a sanitizer enabled build include:

  1. Build the LLVM framework with the required sanitizer enabled.
  2. Build solx with the LLVM build from the previous step.

Please, follow the common installation instructions until the zksync-llvm build step.

This guide assumes the build with AddressSanitizer enabled.

Build LLVM with sanitizer enabled

When building LLVM, use --sanitizer <sanitizer> option and set build type to RelWithDebInfo:

zksync-llvm build --sanitizer=Address --build-type=RelWithDebInfo
Please note that the default Apple Clang compiler is not compatible with Rust. You need to install LLVM using Homebrew and specify the path to the LLVM compiler in the `--extra-args` option. For example:
zksync-llvm build --sanitizer=Address \
  --extra-args '\-DCMAKE_C_COMPILER=/opt/homebrew/opt/llvm/bin/clang' \
               '\-DCMAKE_CXX_COMPILER=/opt/homebrew/opt/llvm/bin/clang++'

Build solx with the sanitizer enabled

To build solx with the sanitizer enabled, you need to set the RUSTFLAGS environment variable to -Z sanitizer=address and run the cargo build command. Sanitizers build is a feature that is available only for the nightly Rust compiler, it is recommended to set RUSTC_BOOTSTRAP=1 environment variable before the build.

It is also mandatory to use --target option to specify the target architecture. Otherwise, the build will fail. Please, check the table below to find the correct target for your platform.

PlatformLLVM Target Triple
MacOS-arm64aarch64-apple-darwin
MacOS-x86x86_64-apple-darwin
Linux-arm64aarch64-unknown-linux-gnu
Linux-x86x86_64-unknown-linux-gnu

Additionally, for proper reports symbolization it is recommended to set the ASAN_SYMBOLIZER_PATH environment variable. For more info, see symbolizing reports section of LLVM documentation.

For example, to build solx for MacOS-arm64 with AddressSanitizer enabled, run the following command:

export RUSTC_BOOTSTRAP=1
export ASAN_SYMBOLIZER_PATH=$(which llvm-symbolizer) # check the path to llvm-symbolizer
TARGET=aarch64-apple-darwin # Change to your target
RUSTFLAGS="-Z sanitizer=address" cargo test --target=${TARGET}

Congratulations! You have successfully built solx with the sanitizers enabled.

Please, refer to the official documentation for more information on how to use sanitizers and their types.