Compilation

Understand the key differences when compiling contracts with foundry-zksync.


zksolc is the compiler used by Validium to convert solidity code to zkEVM-compatible bytecode. It uses the same input format as solc but the output bytecodes and their respective hashes. Internally it uses a custom-compiled solc.

Below are the common compilation differences development teams will need to address when making use of Foundry ZKsync.

Contract Bytecode Access

Contract bytecode cannot be accessed on the Validium architecture due to the way zkEVM handles certain instructions like EXTCODECOPY. As a result, using address(..).code in a Solidity contract will produce a compile-time error with zksolc.

Issue

The EXTCODECOPY instruction is not supported in Validium's architecture. Any attempt to access a contract's bytecode using address(..).code will fail to compile.

Problematic Code

This code will fail during compilation because address(..).code is unsupported in Validium:

contract FooBar {
    function number() return (uint8) {
        return 10;
    }
}

contract FooTest is Test {
    function testFoo() public {
        FooBar target = new FooBar();
        address(target).code;   // will fail at compile-time
    }
}

Compilation Error

When you attempt to compile this code, you will see the following error message:

Solution

To work around this limitation, you can use Foundry's FFI (Foreign Function Interface) functionality to access contract bytecode and hash it externally, and then pass it back into the contract. This approach leverages cheatcodes to read the bytecode from a file.

Fixed Code Example

Here's how you can modify the code to avoid the use of address(..).code:

Additional Configuration

This solution requires enabling read permissions in the foundry.toml file for the file access during testing:

Contract Size Limit

The zksolc compiler enforces a limit on the number of instructions a contract can have, capped at 2^16 instructions. If a contract exceeds this limit, the compilation will fail.

Issue

Contracts with a large number of instructions will not compile on ZKsync due to the 65535 addressable space limitation imposed by zksolc.

Problematic Scenario

If you attempt to compile a large contract that exceeds this instruction limit, the compilation will fail with an error.

Example of Compilation Error

This error indicates that the contract's instruction count has exceeded the maximum allowed limit.

Solution

There are three possible solutions to address this issue:

  1. Attempt Compilation with --zk-force-evmla=true: You can attempt to compile the contract using ZKsync's EVM legacy architecture by adding the --zk-force-evmla=true flag. This can sometimes bypass the contract size limit by compiling in a different mode. Example command:

  2. Use the --zk-fallback-oz=true flag: If the contract size still exceeds the limit, try compiling with optimization level -Oz by using the --zk-fallback-oz=true flag. This tells the compiler to fall back to -Oz optimization when the bytecode is too large, potentially reducing the contract size further. Example command:

  3. Split the Contract into Smaller Units: If neither of the above flags resolves the issue, the contract must be refactored into smaller, modular contracts. This involves separating your logic into different contracts and using contract inheritance or external contract calls to maintain functionality.

Example of Refactoring

Here’s an example of splitting a large contract into smaller units:

Before (single large contract):

After (split into smaller contracts):

Non-inlineable Libraries

Libraries that contain public or external methods cannot be inlined when compiling to ZKsync VM bytecode. Although Solidity can inline libraries during EVM compilation, this inlining is not supported in the Yul intermediate representation, which is used by zksolc to compile for ZKsync.

Issue

On ZKsync, if your project contains non-inlineable libraries, the compilation will fail because these libraries cannot be inlined during the Yul to ZKsync bytecode compilation step.

Problematic Scenario

Consider the following library that calculates the square of a number:

Now, assume you have a smart contract that uses this library:

When you try to compile this project, the compiler will fail because the MiniMath library is not inlined due to the public method.

Example of Compilation Error

This error indicates that the MiniMath library is missing because it cannot be inlined and must be deployed separately.

Solution

Libraries with public or external methods must be deployed separately, and their addresses must be passed to the compiler. The library methods will be called through their deployed address, replacing inlining.

Steps to Fix

  1. Deploy the Missing Libraries: Run the following command to deploy the missing library:

    This command will deploy the library (MiniMath in this case) to the specified network.

  2. Compile the Main Contract: After deploying the library, proceed with compiling the main contract by specifying the deployed library addresses during compilation:

    The deployed address will be linked to the main contract, and library methods will be invoked via external calls rather than inline.

Last updated