ZKsync contracts
Now that we know how to bridge tokens back and forth, let’s talk about running things on ZKsync.
We have a bunch of great tutorials (like this one https://docs.zksync.io/build/tooling/hardhat/getting-started) that you can follow to get the exact code & command line calls to create the contracts - so in this article, let’s focus on how things differ between ZKsync and Ethereum.
Note Before reading this article, I’d recommend doing the hardhat tutorial above.
Ethereum flow
In case of Ethereum, you start by writing a contract code in solidity, then you compile it with solc
, and you get the
EVM bytecode, deployment bytecode (which is a function that should return the bytecode itself) and ABI (interface).
Afterwards, you send the deployment bytecode to the 0x000 address on Ethereum, which does some magic (executes the deployment bytecode, that should contain the constructor etc) and puts the contract under the address that is generated based on your account id and a nonce.
From this moment on, you can send the transactions to this new address (and most of the tools would ask you to provide the ABI, so that they can set the proper function arguments).
All the bytecode will be run on the EVM (Ethereum Virtual Machine) - that has a stack, access to memory and storage, and a bunch of opcodes.
ZKsync flow
The main part (and the main cost) of the ZKsync is the proving system. In order to make proof as fast as possible, we’re running a little bit different virtual machine (zkEVM) - that has a slightly different set of opcodes, and also contains a bunch of registers. More details on this will be written in the future articles.
Having a different VM means that we must have a separate compiler zk-solc - as the bytecode that is produced by this compiler has to use the zkEVM specific opcodes.
While having a separate compiler introduces a bunch of challenges (for example, we need a custom hardhat plugins ), it brings a bunch of benefits too: for example it allows us to move some of the VM logic (like new contract deployment) into System contracts - which allows faster & cheaper modifications and increased flexibility.
ZKsync system contracts
Small note on system contracts: as mentioned above, we moved some of the VM logic into system contracts, which allows us to keep VM simpler (and with this - keep the proving system simpler).
You can see the full list (and codes) of the system contracts here: https://github.com/matter-labs/era-system-contracts.
While some of them are not really visible to the contract developer (like the fact that we’re running a special
Bootleader
to package a bunch of transactions together - more info in a future article) - some others are very
visible - like our ContractDeployer
ContractDeployer
Deploying a new contract differs on Ethereum and ZKsync.
While on Ethereum - you send the transaction to 0x00 address - on ZKsync you have to call the special ContractDeployer
system contract.
If you look on your hardhat example, you’ll notice that your deploy.ts
is actually using a Deployer
class from the
hardhat-zksync-deploy
plugin.
Which inside uses the ZKsync’s web3.js, that calls the contract deployer here
override getDeployTransaction(..) {
...
txRequest.to = CONTRACT_DEPLOYER_ADDRESS;
...
}
Also ContractDeployer
adding a special prefix for all the new contract addresses. This means that contract addresses
WILL be different on ZKsync
and Ethereum (and also leaves us the possibility of adding Ethereum addresses in the
future if needed).
You can look for CREATE2_PREFIX
and CREATE_PREFIX
in the code.
Gas costs
Another part, where ZKsync differs from Ethereum is gas cost. The best example for this are storage slots.
If you have two transactions that are updating the same storage slot - and they are in the same ‘batch’ - only the first one would be charged (as when we write the final storage to ethereum, we just write the final diff of what slots have changed - so updating the same slot multiple times doesn’t increase the amount of data that we have to write to L1).
Account abstraction and some method calls
As ZKsync
has a built-in Account Abstraction (more on this in a separate article) - you shouldn’t depend on some of
the solidity functions (like ecrecover
- that checks the keys, or tx.origin
) - in all the cases, the compiler will
try to warn you.
Summary
In this article, we looked at how contract development & deployment differs on Ethereum and ZKsync (looking at differences in VMs, compilers and system contracts).