Contract Interfaces

System contracts for interacting with the ZKsync Era network using ABI.

There is a set of system contract that helps with the interaction with ZKsync Era network. Most of them are called using abi.

Example

from web3 import Web3

from zksync2.manage_contracts.utils import zksync_abi_default
from zksync2.module.module_builder import ZkSyncBuilder

zksync = ZkSyncBuilder.build(self.env.zksync_server)
zksync_contract = self.eth_web3.eth.contract(Web3.to_checksum_address(zksync.zksync.main_contract_address), abi=zksync_abi_default())

ContractDeployer

ContractDeployer is a utility system contract, represented as a type to cover the following functionality:

  • encode binary contract representation by create method for further deploying.
  • encode binary contract representation by create2 method for further deploying.
  • Precompute contract address for create and create2 methods.

Construction: needs only web3 object with appended zksync module

Example:

from zksync2.manage_contracts.contract_deployer import ContractDeployer
from zksync2.module.module_builder import ZkSyncBuilder

zksync_web3 = ZkSyncBuilder.build("ZKSYNC_NETWORK_URL")
deployer = ContractDeployer(zksync_web3)

Parameter and Methods:

MethodParametersReturn valueDescription
encode_createbytecode, optional call_data & saltHexStrCreates binary representation of a contract in an internal deploying format.
bytecode - contract binary representation, call_data is used for ctor bytecode only, salt is used to generate unique identifier of deploying contract.
encode_create2bytecode, optional call_data & saltHexStrCreates binary representation of contract in an internal deploying format.
bytecode - contract binary representation, call_data is used for ctor bytecode only, salt is used to generate unique identifier of deploying contract.
compute_l2_create_addressAddress, NonceAddressAccepts address of deployer and current deployed nonce and returns address of contract that is going to be deployed by theencode_create method.
compute_l2_create2_addressAddress, bytecode, ctor bytecode, saltAddressAccepts address of the deployer, binary representation of contract, its constructor in binary format and salt. By default constructor can be b'0' value. It returns the address of the contract that is going to be deployed by anencode_create2 method.

Example: Deploying a contract using create

Here is an example of how to deploy a contract using the create method. This example assumes you have the necessary setup and environment variables configured.

import os
from pathlib import Path

from eth_account import Account
from eth_account.signers.local import LocalAccount
from eth_typing import HexAddress
from web3 import Web3

from zksync2.core.types import EthBlockParams
from zksync2.manage_contracts.contract_encoder_base import ContractEncoder
from zksync2.module.module_builder import ZkSyncBuilder
from zksync2.signer.eth_signer import PrivateKeyEthSigner
from zksync2.transaction.transaction_builders import TxCreateContract


def deploy_contract(
    zk_web3: Web3, account: LocalAccount, compiled_contract: Path
) -> HexAddress:
    """Deploy compiled contract on ZKsync network using create() opcode

    :param zk_web3:
        Instance of ZKsyncBuilder that interacts with ZKsync network

    :param account:
        From which account the deployment contract tx will be made

    :param compiled_contract:
        Compiled contract source.

    :return:
        Address of deployed contract.
    """
    # Get chain id of ZKsync network
    chain_id = zk_web3.zksync.chain_id

    # Signer is used to generate signature of provided transaction
    signer = PrivateKeyEthSigner(account, chain_id)

    # Get nonce of ETH address on ZKsync network
    nonce = zk_web3.zksync.get_transaction_count(
        account.address, EthBlockParams.PENDING.value
    )

    # Get contract ABI and bytecode information
    storage_contract = ContractEncoder.from_json(zk_web3, compiled_contract)[0]

    # Get current gas price in Wei
    gas_price = zk_web3.zksync.gas_price

    # Create deployment contract transaction
    create_contract = TxCreateContract(
        web3=zk_web3,
        chain_id=chain_id,
        nonce=nonce,
        from_=account.address,
        gas_limit=0,  # UNKNOWN AT THIS STATE
        gas_price=gas_price,
        bytecode=storage_contract.bytecode,
    )

    # ZKsync transaction gas estimation
    estimate_gas = zk_web3.zksync.eth_estimate_gas(create_contract.tx)
    print(f"Fee for transaction is: {Web3.from_wei(estimate_gas * gas_price, 'ether')} ETH")

    # Convert transaction to EIP-712 format
    tx_712 = create_contract.tx712(estimate_gas)

    # Sign message
    signed_message = signer.sign_typed_data(tx_712.to_eip712_struct())

    # Encode signed message
    msg = tx_712.encode(signed_message)

    # Deploy contract
    tx_hash = zk_web3.zksync.send_raw_transaction(msg)

    # Wait for deployment contract transaction to be included in a block
    tx_receipt = zk_web3.zksync.wait_for_transaction_receipt(
        tx_hash, timeout=240, poll_latency=0.5
    )

    print(f"Tx status: {tx_receipt['status']}")
    contract_address = tx_receipt["contractAddress"]

    print(f"Deployed contract address: {contract_address}")

    # Return the contract deployed address
    return contract_address


if __name__ == "__main__":
    # Set a provider
    PROVIDER = "https://sepolia.era.zksync.dev"

    # Get the private key from OS environment variables
    PRIVATE_KEY = bytes.fromhex(os.environ.get("PRIVATE_KEY"))

    # Connect to ZKsync network
    zk_web3 = ZkSyncBuilder.build(PROVIDER)

    # Get account object by providing from private key
    account: LocalAccount = Account.from_key(PRIVATE_KEY)

    # Provide a compiled JSON source contract
    contract_path = Path("../solidity/storage/build/combined.json")

    # Perform contract deployment
    deploy_contract(zk_web3, account, contract_path)

Example: Deploying a contract using create2

Here is an example of how to deploy a contract using the create2 method.

import os
from pathlib import Path

from eth_account import Account
from eth_account.signers.local import LocalAccount
from eth_typing import HexAddress
from web3 import Web3

from zksync2.core.types import EthBlockParams
from zksync2.manage_contracts.contract_encoder_base import ContractEncoder
from zksync2.manage_contracts.precompute_contract_deployer import PrecomputeContractDeployer
from zksync2.module.module_builder import ZkSyncBuilder
from zksync2.signer.eth_signer import PrivateKeyEthSigner
from zksync2.transaction.transaction_builders import TxCreate2Contract


def generate_random_salt() -> bytes:
    return os.urandom(32)


def deploy_contract(
    zk_web3: Web3, account: LocalAccount, compiled_contract: Path
) -> HexAddress:
    """Deploy compiled contract on ZKsync network using create2() opcode

    :param zk_web3:
        Instance of ZKsyncBuilder that interacts with ZKsync network

    :param account:
        From which account the deployment contract tx will be made

    :param compiled_contract:
        Compiled contract source.

    :return:
        Address of deployed contract.
    """
    # Get chain id of ZKsync network
    chain_id = zk_web3.zksync.chain_id

    # Signer is used to generate signature of provided transaction
    signer = PrivateKeyEthSigner(account, chain_id)

    # Get nonce of ETH address on ZKsync network
    nonce = zk_web3.zksync.get_transaction_count(
        account.address, EthBlockParams.PENDING.value
    )

    # Deployment of same smart contract (same bytecode) without salt cannot be done twice
    # Remove salt if you want to deploy contract only once
    random_salt = generate_random_salt()

    # Precompute the address of smart contract
    # Use this if there is a case where contract address should be known before deployment
    deployer = PrecomputeContractDeployer(zk_web3)

    # Get contract ABI and bytecode information
    storage_contract = ContractEncoder.from_json(zk_web3, compiled_contract)[0]

    # Get precomputed contract address
    precomputed_address = deployer.compute_l2_create2_address(sender=account.address,
                                                              bytecode=storage_contract.bytecode,
                                                              constructor=b'',
                                                              salt=random_salt)

    # Get current gas price in Wei
    gas_price = zk

_web3.zksync.gas_price

    # Create2 deployment contract transaction
    create2_contract = TxCreate2Contract(web3=zk_web3,
                                         chain_id=chain_id,
                                         nonce=nonce,
                                         from_=account.address,
                                         gas_limit=0,
                                         gas_price=gas_price,
                                         bytecode=storage_contract.bytecode,
                                         salt=random_salt)

    # ZKsync transaction gas estimation
    estimate_gas = zk_web3.zksync.eth_estimate_gas(create2_contract.tx)
    print(f"Fee for transaction is: {Web3.from_wei(estimate_gas * gas_price, 'ether')} ETH")

    # Convert transaction to EIP-712 format
    tx_712 = create2_contract.tx712(estimate_gas)

    # Sign message
    signed_message = signer.sign_typed_data(tx_712.to_eip712_struct())

    # Encode signed message
    msg = tx_712.encode(signed_message)

    # Deploy contract
    tx_hash = zk_web3.zksync.send_raw_transaction(msg)

    # Wait for deployment contract transaction to be included in a block
    tx_receipt = zk_web3.zksync.wait_for_transaction_receipt(
        tx_hash, timeout=240, poll_latency=0.5
    )

    print(f"Tx status: {tx_receipt['status']}")
    contract_address = tx_receipt["contractAddress"]
    print(f"Contract address: {contract_address}")

    # Check does precompute address match with deployed address
    if precomputed_address.lower() != contract_address.lower():
        raise RuntimeError("Precomputed contract address does now match with deployed contract address")

    return contract_address


if __name__ == "__main__":
    # Set a provider
    PROVIDER = "https://sepolia.era.zksync.dev"

    # Get the private key from OS environment variables
    PRIVATE_KEY = bytes.fromhex(os.environ.get("PRIVATE_KEY"))

    # Connect to ZKsync network
    zk_web3 = ZkSyncBuilder.build(PROVIDER)

    # Get account object by providing from private key
    account: LocalAccount = Account.from_key(PRIVATE_KEY)

    # Provide a compiled JSON source contract
    contract_path = Path("../solidity/storage/build/combined.json")

    # Perform contract deployment
    deploy_contract(zk_web3, account, contract_path)
For development and testing, it is recommended to use burner wallets. Avoid using real private keys to prevent security risks.

Made with ❤️ by the ZKsync Community