block-explorer icon indicating copy to clipboard operation
block-explorer copied to clipboard

Vyper Interfaces Verification

Open PatrickAlphaC opened this issue 1 year ago • 23 comments

šŸ› Bug Report

If a smart contract in vyper uses an interface, the ZKsync explorer will throw an error.

{'status': 'failed', 'error': 'zkvyper error: ./etc/vyper-bin/0.4.0/vyper error: vyper.exceptions.ModuleNotFound: interfaces.IERC20Permit\n\n  contract "/tmp/.tmp9OzJyE/lib/pypi/snekmate/tokens/erc20.vy:71", line 71:0 \n       70 # syntax.\n  ---> 71 import interfaces.IERC20Permit as IERC20Permit\n  --------^\n       72 implements: IERC20Permit\n\n[30563] Failed to execute script \'vyper_compile\' due to unhandled exception!\n'}

However, zkvyper compiles successfully. This is because the explorer verification process is not correctly understanding the python paths.

šŸ”„ Reproduction Steps

  1. Download moccasin

Easiest way is to download uv, then download mox

curl -LsSf https://astral.sh/uv/install.sh | sh # downloads uv 
uv tool install moccasin # downloads moccasin/mox 

Make sure you have zkvyper and era_test_node also installed

  1. Create a new moccasin project
mkdir verify_erc20
cd verify_erc20
mox init
  1. Add erc20 details

First, install snekmate with:

mox install snekmate

Then create a file called snek_token.vy and add:

# pragma version 0.4.0
"""
@license MIT
@title snek_token
@author You!
@notice This is my ERC20 token!
"""

# ------------------------------------------------------------------
#                             IMPORTS
# ------------------------------------------------------------------
from ethereum.ercs import IERC20

implements: IERC20
from ethereum.ercs import IERC20Detailed

implements: IERC20Detailed
from snekmate.auth import ownable as ow

# from pcaversaccio.snekmate.src.snekmate.auth import ownable as ow

initializes: ow
from snekmate.tokens import erc20

initializes: erc20[ownable := ow]

exports: erc20.__interface__

# ------------------------------------------------------------------
#                         STATE VARIABLES
# ------------------------------------------------------------------
# Constants & Immutables
NAME: constant(String[25]) = "snek_token"
SYMBOL: constant(String[5]) = "SNEK"
DECIMALS: constant(uint8) = 18
EIP712_VERSOIN: constant(String[20]) = "1"

# Storage - none!

# ------------------------------------------------------------------
#                            FUNCTIONS
# ------------------------------------------------------------------
@deploy
def __init__(initial_supply: uint256):
    ow.__init__()
    erc20.__init__(NAME, SYMBOL, DECIMALS, NAME, EIP712_VERSOIN)
    erc20._mint(msg.sender, initial_supply)

  1. Add zksync network details

Add the following to your moccasin.toml

[networks.zksync]
url = "$ZKSYNC_RPC_URL"
is_zksync = true
explorer_uri = "https://zksync2-mainnet-explorer.zksync.io"
explorer_type = "zksyncexplorer"
prompt_live = false

Where ZKSYNC_RPC_URL is your zksync mainnet RPC URL.

  1. Create a file called verify.py in your script folder with this code:
from eth_utils import to_wei, to_bytes
from moccasin.boa_tools import VyperContract
from moccasin.config import get_active_network

from src import snek_token


def moccasin_main():
    contract_addy = "0x60074aD7A8e789d4fDeebB40Eb04B5000d6fE6eE"
    snek = snek_token.at(contract_addy)
    snek.constructor_calldata = to_bytes(
        hexstr="0x00000000000000000000000000000000000000000000003635c9adc5dea00000"
    )

    active_network = get_active_network()
    result = active_network.moccasin_verify(snek)
    result.wait_for_verification()
  1. Run it
mox run verify --network zksync

You'll get the error. This is an error on the explorer's side. This contract should be able to be installed.

PatrickAlphaC avatar Nov 27 '24 19:11 PatrickAlphaC

Or, more basically, the contract on zksync era mainnet at address: 0x60074aD7A8e789d4fDeebB40Eb04B5000d6fE6eE should verify. The code is here:

# pragma version 0.4.0
"""
@license MIT
@title snek_token
@author You!
@notice This is my ERC20 token!
"""

# ------------------------------------------------------------------
#                             IMPORTS
# ------------------------------------------------------------------
from ethereum.ercs import IERC20

implements: IERC20
from ethereum.ercs import IERC20Detailed

implements: IERC20Detailed
from snekmate.auth import ownable as ow

# from pcaversaccio.snekmate.src.snekmate.auth import ownable as ow

initializes: ow
from snekmate.tokens import erc20

initializes: erc20[ownable := ow]

exports: erc20.__interface__

# ------------------------------------------------------------------
#                         STATE VARIABLES
# ------------------------------------------------------------------
# Constants & Immutables
NAME: constant(String[25]) = "snek_token"
SYMBOL: constant(String[5]) = "SNEK"
DECIMALS: constant(uint8) = 18
EIP712_VERSOIN: constant(String[20]) = "1"

# Storage - none!

# ------------------------------------------------------------------
#                            FUNCTIONS
# ------------------------------------------------------------------
@deploy
def __init__(initial_supply: uint256):
    ow.__init__()
    erc20.__init__(NAME, SYMBOL, DECIMALS, NAME, EIP712_VERSOIN)
    erc20._mint(msg.sender, initial_supply)

This needs the snekmate package. I am successfully able to get my local zkvyper to compile it.

PatrickAlphaC avatar Nov 27 '24 19:11 PatrickAlphaC

Hey @PatrickAlphaC

It feels like not all the files are being included into the compilation request. Could you please check what network request is being sent to the explorer? Does it include all the dependencies? Also, if you try to verify the same contract with hardhat-zksync-verify-vyper, does it succeed or fail with the same error?

popzxc avatar Nov 28 '24 08:11 popzxc

@popzxc I think the issue is that the PATH that the zkvyper uses on the server is different from the users PATH.

Let me see if it makes sense to fix on the user's end...

PatrickAlphaC avatar Nov 30 '24 21:11 PatrickAlphaC

@PatrickAlphaC hey, did you manage to fix this on the user's side?

dimazhornyk avatar Dec 03 '24 12:12 dimazhornyk

@dimazhornyk no. Here is where we are.

The (truncated) body of a request (for example, for this address live on ZKsync: 0x36266F07E40aeaFD5FBeaB736f85d2f1F1B4Bb06)

{
  "contractAddress": "0x36266F07E40aeaFD5FBeaB736f85d2f1F1B4Bb06",
  "sourceCode": {
    "lib/pypi/snekmate/tokens/interfaces/IERC721Metadata.vyi": "# pragma version ~=0.4.0\n\"\"\"...",
    "lib/pypi/snekmate/tokens/interfaces/IERC721Enumerable.vyi": "# pragma version ~=0.4.0\n\"\"\"...",
    "lib/pypi/snekmate/tokens/interfaces/IERC721Permit.vyi": "# pragma version ~=0.4.0\n\"\"\"...",
    "lib/pypi/snekmate/tokens/interfaces/IERC4906.vyi": "# pragma version ~=0.4.0\n\"\"\"...",
    "lib/pypi/snekmate/utils/interfaces/IERC5267.vyi": "# pragma version ~=0.4.0\n\"\"\"...",
    "lib/pypi/snekmate/tokens/interfaces/IERC721Receiver.vyi": "# pragma version ~=0.4.0\n\"\"\"...",
    "lib/pypi/snekmate/auth/ownable.vy": "# pragma version ~=0.4.0\n\"\"\"...",
    "lib/pypi/snekmate/utils/ecdsa.vy": "# pragma version ~=0.4.0\n\"\"\"...",
    "lib/pypi/snekmate/utils/message_hash_utils.vy": "# pragma version ~=0.4.0\n\"\"\"...",
    "lib/pypi/snekmate/utils/eip712_domain_separator.vy": "# pragma version ~=0.4.0\n\"\"\"...",
    "lib/pypi/snekmate/tokens/erc721.vy": "# pragma version ~=0.4.0\n\"\"\"...",
    "birthday_coffee": "# pragma version 0.4.0\n\"\"\"...\""
  },
  "codeFormat": "vyper-multi-file",
  "contractName": "birthday_coffee",
  "compilerVyperVersion": "0.4.0",
  "compilerZkvyperVersion": "v1.5.7",
  "constructorArguments": "0x",
  "optimizationUsed": true
}

However, Vyper uses the Python Path to identify contracts. So instead of:

from lib.pypi.snekmate.tokens.interfaces import IERC721Metadata

We can just do:

from snekmate.tokens.interfaces import IERC721Metadata

Here are the options we can do to fix this:

  1. The user rewrites all their imports to be the exact path in the folders, instead of the python path
  2. The ZKsync explorer adds a field for python path

I think 1 is probably (sadly) the correct answer... So we'll have to do some work to do this automatically for users...

PatrickAlphaC avatar Dec 03 '24 19:12 PatrickAlphaC

Because otherwise you'll get:

ValueError: Verification failed: zkvyper error: ./etc/vyper-bin/0.4.0/vyper error: vyper.exceptions.ModuleNotFound: snekmate.tokens.erc721

  contract "/tmp/.tmp7HTS1k/birthday_coffee.vy:7", line 7:0 
       6
  ---> 7 from snekmate.tokens import erc721
  -------^
       8 from snekmate.auth import ownable as ow

[64919] Failed to execute script 'vyper_compile' due to unhandled exception!

PatrickAlphaC avatar Dec 03 '24 19:12 PatrickAlphaC

i think the issue here is that the search path (see "search_paths" key here: https://docs.vyperlang.org/en/latest/compiling-a-contract.html#input-json-description) is not being included. @dimazhornyk @popzxc what is the correct way to include the settings key in the json bundle?

charles-cooper avatar Dec 03 '24 22:12 charles-cooper

@charles-cooper right now this setting is not passed, the list of files is just passed in the "sources" field (see the code). I'm not sure if adding search_paths can be easily done, since (I assume) this field must have absoulte paths, while on the server everything is performed in temporary folders.

I see a few ways around it:

  • Add $base_dir/lib/pypi to the search_paths on the server in case there are contracts with lib/pypi in the sources. This will be a heuristic, but I assume it will work for most cases?
  • Add a configuration option to specify search_paths in verification request -- but it will likely require pre-processing on the plugin side, since you will need to make them relative for it to work.

WDYT?

popzxc avatar Dec 04 '24 12:12 popzxc

@charles-cooper right now this setting is not passed, the list of files is just passed in the "sources" field (see the code). I'm not sure if adding search_paths can be easily done, since (I assume) this field must have absoulte paths, while on the server everything is performed in temporary folders.

I see a few ways around it:

  • Add $base_dir/lib/pypi to the search_paths on the server in case there are contracts with lib/pypi in the sources. This will be a heuristic, but I assume it will work for most cases?
  • Add a configuration option to specify search_paths in verification request -- but it will likely require pre-processing on the plugin side, since you will need to make them relative for it to work.

WDYT?

i'm not sure how the verifier is calling vyper, but if it's passing standard json, then everything should "just work" so long as search_paths is correctly added to the standard json bundle

charles-cooper avatar Dec 04 '24 12:12 charles-cooper

'm not sure if adding search_paths can be easily done, since (I assume) this field must have absoulte paths, while on the server everything is performed in temporary folders.

Btw, the search paths do not need to be absolute.

charles-cooper avatar Dec 04 '24 12:12 charles-cooper

I will try to implement the first proposal next week and let's see if it works.

popzxc avatar Dec 06 '24 06:12 popzxc

I will try to implement the first proposal next week and let's see if it works.

I think it will work for a specific case, but the proper thing to do is to allow the settings to be properly set in the input bundle.

charles-cooper avatar Dec 06 '24 11:12 charles-cooper

OK, I investigated it further, and looks like unfortunately this issue is not actionable right now šŸ˜“ zkvyper only supports combined JSON, so you cannot pass search_paths there at all.

There are some experiments with standard JSON going on there, but it's hard to say when it will reach production support. For now, I guess, the only workaround is not to rely on the search_paths.

popzxc avatar Dec 06 '24 12:12 popzxc

OK, I investigated it further, and looks like unfortunately this issue is not actionable right now šŸ˜“ zkvyper only supports combined JSON, so you cannot pass search_paths there at all.

There are some experiments with standard JSON going on there, but it's hard to say when it will reach production support. For now, I guess, the only workaround is not to rely on the search_paths.

search paths can be set on the command line for combined_json with the -p flag

cc @hedgar2017

charles-cooper avatar Dec 06 '24 13:12 charles-cooper

Thanks @charles-cooper. We can add its propagation to zkvyper v1.5.8 which is expected in a couple of days.

@popzxc, but full migration to standard JSON can only be done starting from the Hardhat plugin. Right now it relies on combined JSON output. The optimal path would be to test the plugin with vyper first.

hedgar2017 avatar Dec 06 '24 13:12 hedgar2017

I guess if -p works (I wasn't aware of it), we can live with combined JSON for a bit longer.

popzxc avatar Dec 06 '24 13:12 popzxc

I guess if -p works (I wasn't aware of it), we can live with combined JSON for a bit longer.

I will try now.

@charles-cooper is it safe to use with all versions of vyper?

hedgar2017 avatar Dec 06 '24 13:12 hedgar2017

I guess if -p works (I wasn't aware of it), we can live with combined JSON for a bit longer.

I will try now.

@charles-cooper is it safe to use with all versions of vyper?

it is, but it has different semantics for < 0.4.0 (for <0.4.0, it is not as important, because prior to 0.4.0 multi-file contracts are not supported).

i would recommend guarding the feature to only apply for >=0.4.0, or more simply just propagate it to the compiler and let the compiler raise an error if the semantics are invalid, as the user would not really be setting search paths prior to 0.4.0.

charles-cooper avatar Dec 06 '24 13:12 charles-cooper

@charles-cooper thanks, understood!

Could you also drop a quick comment on the status of combined JSON mode? I cannot find enough info on it in the docs. Is it being deprecated in favor of standard JSON?

hedgar2017 avatar Dec 06 '24 15:12 hedgar2017

@charles-cooper thanks, understood!

Could you also drop a quick comment on the status of combined JSON mode? I cannot find enough info on it in the docs? Is it being deprecated in favor of standard JSON?

it is not being deprecated, it is just the "json output" mode for standard cli so that the output is more structured than just text.

charles-cooper avatar Dec 06 '24 15:12 charles-cooper

Hey @PatrickAlphaC @popzxc - could you try this build? The paths can be added as zkvyper ... --search-paths <path1> <path2> ... <pathN>.

hedgar2017 avatar Dec 07 '24 17:12 hedgar2017

@hedgar2017 so this will be passed to the explorer server? This fixes verification? I’m a bit confused

PatrickAlphaC avatar Dec 10 '24 18:12 PatrickAlphaC

@PatrickAlphaC sorry for the confusion! We've just released the compiler part. @popzxc will look into the explorer part soon.

hedgar2017 avatar Dec 11 '24 02:12 hedgar2017