titanoboa
titanoboa copied to clipboard
Something is off with the version selector, causing weird issues across imports
I get the following errors:
vyper.exceptions.VersionException: Version specification "==0.4.0" is not compatible with compiler version "0.4.1"
contract "src/interfaces/i_decentralized_stable_coin.vyi:1", line 1:0
---> 1 # pragma version 0.4.0
-------^
2 """
vyper.exceptions.ModuleNotFound: interfaces.IERC20Permit
contract "lib/pypi/snekmate/tokens/erc20.vy:69", line 69:0
68 # syntax.
---> 69 import interfaces.IERC20Permit as IERC20Permit
--------^
70 implements: IERC20Permit
After running the following:
git clone https://github.com/cyfrin/mox-stablecoin-cu
cd mox-stablecoin-cu
mox install
mox build
I'm thinking maybe it's getting confused with using snekmate? I'm not sure where it's breaking down...
I might have an idea. It will be an addition to Titanoboa and also Moccasin to make it works with prior versions of the latest vyper. And let project with vyper==0.4.0 to run without crashing with the VVM.
The idea is to have access to the base_path in interpret.load_partial() to add it to _load_partial_vvm:
def _loads_partial_vvm(
source_code: str,
version: Version,
name: Optional[str],
filename: str,
base_path=None,
):
global _disk_cache
if base_path is None:
base_path = Path(".") # --> here if I a put manually the path `./contracts` or `./src` it works
The goal is to send this from the moccasin compile command so that VVM does not break.
- /home/s3bc40/gh-projects/moccasin/tests/data/complex_project
+ /home/s3bc40/gh-projects/moccasin/tests/data/complex_project/contracts
+ /home/s3bc40/gh-projects/moccasin/tests/data/complex_project/src
We avoid the following import error, which works if we are not going through the VVM:
- import contracts.interfaces.IERC20Permit as IERC20Permit
+ import interfaces.IERC20Permit as IERC20Permit
Two steps
-
[ ] 1. Modifying mox
compile_function to add thebase_pathtoload_partialand handlevvm.VyperErrorto display the right path (not tmp to be able toctrl+click) -
[ ] 2. Adding the
base_pathas an optionnalarginload_partialandloads_partialto reach_loads_partial_vvm
@PatrickAlphaC @charles-cooper does the process sound logical to you?
My proposal here: https://github.com/vyperlang/titanoboa/pull/391
Tell me if there is any breakpoint or if it is not relevant, I don't mind!
Thanks for your time!
I tried the get_search_paths(_search_path) but the main problem does not seem to be this. I struggled between my personal mistakes and the errors, but I always end up in the same error when we try to import from Moccasin lib:
from snekmate.auth import ownable as ow # does not work in VVM
from lib.pypi.snekmate.auth import ownable as ow # it works
I can not redo what seems to make the vyper compiler to take into account all the paths available for VVM like this:
# interpret.compiler_data()
global _disk_cache, _search_path
path = Path(filename)
resolved_path = Path(filename).resolve(strict=False)
file_input = FileInput(
contents=source_code, source_id=-1, path=path, resolved_path=resolved_path
)
search_paths = get_search_paths(_search_path)
input_bundle = FilesystemInputBundle(search_paths)
settings = Settings(**kwargs)
ret = CompilerData(file_input, input_bundle, settings)
if _disk_cache is None:
return ret
with anchor_settings(ret.settings):
# note that this actually parses and analyzes all dependencies,
# even if they haven't changed. an optimization would be to
# somehow convince vyper (in ModuleAnalyzer) to get the module_t
# from the cache.
module_t = ret.annotated_vyper_module._metadata["type"]
fingerprint = get_module_fingerprint(module_t)
It's the only missing part, my previous assumptions were not correct, thinking it was only the base_path value. I do not know if it is possible to make the vyper installed from _load_partial_vvm aware of the search_path availables:
# interpret._loads_partial_vvm
# install the requested version if not already installed
vvm.install_vyper(version=version)
def _compile():
return vvm.compile_source(
source_code, vyper_version=version, base_path=base_path
)
New update with something that "works" but I do not think it is stable. At least it leads to something.
I essentially investigated inside the VVM code from the lib in my venv, and tried to get something out of it.
I ended up in the .venv/lib64/python3.12/site-packages/vvm/wrapper.py and added something that compile from the VVM and does not break on mox imports for snekmate or interfaces (last one is kind of random):
# [...]
from vyper.cli.vyper_compile import get_search_paths
# [...]
def vyper_wrapper(
vyper_binary: Union[Path, str] = None,
stdin: str = None,
source_files: Union[List, Path, str] = None,
success_return_code: int = 0,
**kwargs: Any,
) -> Tuple[str, str, List, subprocess.Popen]:
# [...]
if source_files is not None:
if isinstance(source_files, (str, Path)):
command.append(_to_string("source_files", source_files))
else:
command.extend([_to_string("source_files", i) for i in source_files])
for key, value in kwargs.items():
if value is None or value is False:
continue
if len(key) == 1:
key = f"-{key}"
else:
key = f"--{key.replace('_', '-')}"
if value is True:
command.append(key)
else:
command.extend([key, _to_string(key, value)])
# ------------------ HERE ------------------------
# Using search_paths and removing `.zip` path
search_paths = [path for path in get_search_paths() if not str(path).endswith(".zip")]
for path in search_paths:
command.extend(["-p", path.as_posix()])
# ------------------ HERE ------------------------
if stdin is not None:
stdin = str(stdin)
# [...]
Why removing .zip? Because of the following error while compiling with the vyper binary:
FileNotFoundError: [Errno 2] No such file or directory: '/usr/lib/python312.zip'
I also had this error with a standalone python added in uv (3.13), that I removed afterward.
It is not a stable solution, but for the sake of the investigation I had to understand and find a lead.
I have added some print and disabled the _disk_cache to tests and see how it looks like (I couldn't see the prints after one successful compilation):
source_file.name: /tmp/vyper-b8ki6row.vy
base_path: .
command: [PosixPath('/home/s3bc40/.vvm/vyper-0.4.0'), '/tmp/vyper-g_nd5nr0.vy', '-f', 'combined_json', '-p', 'src', '-p', '/home/s3bc40/mox-stablecoin-cu/.venv/lib/python3.12/site-packages', '-p', '/usr/lib/python3.12/lib-dynload', '-p', '/usr/lib/python3.12', '-p', '/home/s3bc40/mox-stablecoin-cu/.venv/bin', '-p', '/home/s3bc40/mox-stablecoin-cu/lib', '-p', '/home/s3bc40/mox-stablecoin-cu/lib/pypi', '-p', '/home/s3bc40/mox-stablecoin-cu/lib/github', '-p', '/home/s3bc40/mox-stablecoin-cu/src', '-p', '/home/s3bc40/mox-stablecoin-cu', '-p', '.']
What I can't understand is why I need to do this? Even if the vyper cli function vyper_compile already has this system:
# vyper/cli/vyper_compile.py
@_apply_warnings_filter
def compile_files(
input_files: list[str],
output_formats: OutputFormats,
paths: list[str] = None,
include_sys_path: bool = True,
show_gas_estimates: bool = False,
settings: Optional[Settings] = None,
storage_layout_paths: list[str] = None,
no_bytecode_metadata: bool = False,
warnings_control: Optional[str] = None,
) -> dict:
search_paths = get_search_paths(paths, include_sys_path)
input_bundle = FilesystemInputBundle(search_paths)
# [...]
output = vyper.compile_from_file_input(
file,
input_bundle=input_bundle,
output_formats=final_formats,
exc_handler=exc_handler,
settings=settings,
storage_layout_override=storage_layout_override,
show_gas_estimates=show_gas_estimates,
no_bytecode_metadata=no_bytecode_metadata,
)
I think I won't search any deeper since I'm lacking of experience and knowledge. I might make more mistakes along the way. Hope this helps @PatrickAlphaC @charles-cooper 🙏