Possible inconsistent issues in zkSync 2.0 testnet

Today I’m testing my codes in the zkSync 2.0 testnet which is compatible with Solidity contracts. However, I found that some codes returned bad data and after short investigating and testing, here are the results.

The problems mentioned in this article is not in the Known issues according to the docs:

Testing and results

Checking contract existence will fail

There are two common ways to check whether an address is an existing contract.

  • assembly

    uint256 size;
    assembly { size := extcodesize(address) }
    return size > 0;
    
  • code.length

    address.code.length > 0;
    

Both usages could be seen on the most used OpenZeppelin library:

However, both usages are broken in the zkSync 2.0 testnet. Applying the check to arbitrary addresses will always return true.

For example with the following codes:

  • Solidity

    function isContractAssembly(address addr) public view returns (bool) {
        uint256 size;
        assembly { size := extcodesize(addr) }
        return size > 0;
    }
    
    function isContractCodeLength(address addr) public view returns (bool) {
        return addr.code.length > 0;
    }
    
  • Account

    I generated a fresh empty account to test, please do not use it to store funds.

    address:
    0xbE67e48CDEB9424715e9A4eB83B86Dab9D42fB63
    
    privateKey:
    7c70e3e7ade2bca3f93f624af00a0f78068c2f5ad7f1dc63bb83c3bc0ca9b608
    
  • JavaScript with ethers.js

    const isContract = await testContract.isContract('0xbE67e48CDEB9424715e9A4eB83B86Dab9D42fB63');
    
    const isContractCodeLength = await testContract.isContractCodeLength('0xbE67e48CDEB9424715e9A4eB83B86Dab9D42fB63');
    

Both of isContract and isContractCodeLength is true. However, deploying the same code to the Polygon network with the same testing codes, the results are both false.

Checking ERC20 balance may get bad data

Another important part is calling the balanceOf method of ERC20 tokens. It seems to work well with existing tokens such as ETH, but when trying to get account balance from not existing (yet) tokens will return bad data.

It’s worth noting that according to the docs, ERC20 tokens deposited from the mainnet will have the same address as its mainnet address. However, the bad data could be returned from both mainnet token addresses or fully empty accounts (EOA).

  • Solidity

    function balanceOf(address erc20, address account) external view returns (uint256) {
        return IERC20(erc20).balanceOf(account);
    }
    
  • JavaScript with ethers.js

    const mainnetDAI = '0x84c3766CCFCc1771b712E80361672faD327b735b';
    
    const balanceOf = await testContract.balanceOfUnchecked(mainnetDAI, account);
    

The mainnet address of DAI is not (yet) a contract in the zkSync 2.0 testnet, however, it will return a balance (the bad data). The data seems to be a random value but for different addresses, the data are the same (in hex and decimal):

0x70a082310000000000000000000000006544df975cf58a0b2c9a361a8db2e00d
50942633119752846454219349998365661925743347005834525140342
888893659768610829

Replacing the mainnet DAI address with arbitrary addresses even EOA will return the same data. The same contract and testing in the Polygon network work as intended (the request are reverted).

Does always return bad data for arbitrary not existing functions or only balanceOf? I tried to call totalSupply() however it got reverted. It’s not pretty clear yet whether it works only with balanceOf(address) or affects other functions too.

The testing contract and env

This is just very simple testing about this and I believe it is worth more testing and investigating. You could do this by yourself if you are interested. Here are the deployed contracts and codes I used:

ERC20ConsistentTest (zkSync)
0xb4eBf77DAC2998E647F1637aCd2A86149F3D5A78

ERC20ConsistentTest (Polygon)
0xCc1ef95Af1fB6835b6EA42AB1CE7Eddc8948723B
  • ABI for ethers.js

    const abi = [
        "function balanceOf(address erc20, address account) external view returns (bool success, uint256 balance)",
        "function balanceOfUnchecked(address erc20, address account) external view returns (uint256)",
        "function totalSupply(address erc20) external view returns (uint256)",
        "function transferFrom(address erc20, address account) external returns (bool success)",
        "function isContract(address erc20) public view returns (bool)",
        "function isContractCodeLength(address erc20) public view returns (bool)"
    ];
    
  • Solidity code

    https://gist.github.com/cakoyo/724ab9f18e3abb9746e71c117e999494

  • env

    Polygon RPC
    https://polygon-rpc.com
    
    zkSync 2.0 testnet RPC
    https://zksync2-testnet.zksync.dev
    
    solidity version
    0.8.12
    

    https://v2-docs.zksync.io/dev/guide/hello-world.html#prerequisites

    You can reference this tutorial to develop on zkSync.

Thanks for reading!

Donate: 0x62B8c9b9967D444cb2ca8920ED09044393cc80F6

discord Sotr#8761

Subscribe to bilibilimoe.eth
Receive the latest updates directly to your inbox.
Verification
This entry has been permanently stored onchain and signed by its creator.