Vyper Interfaces Verification
š 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
- 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
- Create a new moccasin project
mkdir verify_erc20
cd verify_erc20
mox init
- 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)
- 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.
- Create a file called
verify.pyin yourscriptfolder 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()
- 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.
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.
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 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 hey, did you manage to fix this on the user's side?
@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:
- The user rewrites all their imports to be the exact path in the folders, instead of the python path
- 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...
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!
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 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/pypito thesearch_pathson the server in case there are contracts withlib/pypiin the sources. This will be a heuristic, but I assume it will work for most cases? - Add a configuration option to specify
search_pathsin 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?
@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_pathscan 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/pypito thesearch_pathson the server in case there are contracts withlib/pypiin the sources. This will be a heuristic, but I assume it will work for most cases?- Add a configuration option to specify
search_pathsin 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
'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.
I will try to implement the first proposal next week and let's see if it works.
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.
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.
OK, I investigated it further, and looks like unfortunately this issue is not actionable right now š
zkvyperonly supports combined JSON, so you cannot passsearch_pathsthere 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
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.
I guess if -p works (I wasn't aware of it), we can live with combined JSON for a bit longer.
I guess if
-pworks (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?
I guess if
-pworks (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 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?
@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.
Hey @PatrickAlphaC @popzxc - could you try this build?
The paths can be added as zkvyper ... --search-paths <path1> <path2> ... <pathN>.
@hedgar2017 so this will be passed to the explorer server? This fixes verification? Iām a bit confused
@PatrickAlphaC sorry for the confusion! We've just released the compiler part. @popzxc will look into the explorer part soon.