Deployment

Understand the differences in deploying smart contracts on ZKSync using foundry-zksync.

Address Derivation

Issue

ZKsync uses a different strategy for deriving addresses in CREATE and CREATE2 operations compared to Ethereum. This can cause discrepancies if your project relies on hardcoded Ethereum-style addresses for contract deployment.

Problematic Code

In Ethereum, you might have code that calculates the address of a contract deployed using CREATE2 based on a salt and bytecode hash:

  function computeCreate2Address(
      address deployer,
      bytes32 salt,
      bytes memory bytecode
  ) public pure returns (address) {
      return address(uint160(uint(keccak256(abi.encodePacked(
          bytes1(0xff),
          deployer,
          salt,
          keccak256(bytecode)
      )))));
  }

This method won’t work in ZKsync because the address derivation logic is different. To learn more about these differences refer to the documentation on Differences with address derivation.

Error

When deploying contracts using CREATE2, the actual address of the deployed contract may differ from the expected address derived using Ethereum’s method. This can cause failures when interacting with contracts by their derived address.

Solution

To derive ZKsync-compatible CREATE2 addresses, you need to use ZKsync's specific hashing mechanism that includes a zksyncCreate2 prefix.

Fixed Code Example

The following is an example of how you can compute a valid CREATE2 address for ZKSync. This function uses ZKsync’s specific address derivation logic and ensures the contract addresses are correctly computed.

    function computeCreate2Address(
        address _sender,
        bytes32 _salt,
        bytes32 _bytecodeHash,
        bytes32 _constructorInputHash
    ) external pure returns (address) {
        bytes32 senderBytes = bytes32(uint256(uint160(_sender)));
        bytes32 data = keccak256(
            bytes.concat(keccak256("zksyncCreate2"), senderBytes, _salt, _bytecodeHash, _constructorInputHash)
        );

        return address(uint160(uint256(data)));
    }

Batch Transactions

Issue

In Foundry ZKsync, batched transactions may be executed out of order, which can cause failures if subsequent transactions depend on the execution of prior transactions.

Problematic Code

In Ethereum, you might use batched transactions within your scripts like this:

contract Calculator {
    function add(uint8 a, uint8 b) public pure returns (uint8) {
        return a + b;
    }
}

contract FooScript is Script {
    function run() public {
        vm.startBroadcast();
        Calculator calc = new Calculator();     // tx1
        uint8 sum = calc.add(1, 2);             // tx2
        vm.stopBroadcast();
    }
}

In this example, the deployment of the Calculator contract (tx1) and the invocation of add (tx2) are batched together. However, on ZKsync, tx2 might execute before tx1, causing the script to fail.

Error

The error occurs when the second transaction (tx2) is executed before the first transaction (tx1), leading to a failure since the contract isn't deployed yet:

Error: Transaction reverted: contract not deployed

Solution

To ensure transactions are executed correctly on ZKsync using Foundry ZKsync, use the --slow flag, which sends transactions one at a time and keeps them in order.

Fixed Code Example

Here’s how you can run the script with proper transaction execution:

forge script script/FooTest.s.sol:FooScript --zksync --rpc-url https://sepolia.era.zksync.dev --broadcast --slow

The --slow flag forces transactions to be sent individually to the RPC endpoint, ensuring they are executed in the correct order.

Bytecode Hash

Bytecode hashes output by zksolc are fundamentally different from the hash obtained via solc. The most glaring difference is the first (most-significant) byte denotes the version of the format, which at present is 1. This leads to all zksolc bytecode hashes to begin with 1, whereas solc bytecodes are merely the keccak hash of the bytecode.

Any code making assumptions about bytecode hashes around EVM-scope will need to be migrated to accommodate for ZKsync's bytecode hashes.


Made with ❤️ by the ZKsync Community