From 0023c5c6b41b2af38525667b2d2d4960fa45a2fd Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Wed, 9 Oct 2024 10:12:39 +0200 Subject: [PATCH 1/6] feat: add support for vyper archives --- boa/interpret.py | 121 ++++++++++++++++----- tests/unitary/fixtures/module_contract.vyz | Bin 0 -> 1282 bytes tests/unitary/test_modules.py | 6 +- 3 files changed, 99 insertions(+), 28 deletions(-) create mode 100644 tests/unitary/fixtures/module_contract.vyz diff --git a/boa/interpret.py b/boa/interpret.py index 17136d3c..942cd5b0 100644 --- a/boa/interpret.py +++ b/boa/interpret.py @@ -1,23 +1,30 @@ +import binascii import sys import textwrap +from base64 import b64decode from importlib.abc import MetaPathFinder from importlib.machinery import SourceFileLoader from importlib.util import spec_from_loader -from pathlib import Path +from io import BytesIO +from pathlib import Path, PurePath from typing import TYPE_CHECKING, Any, Union +from zipfile import BadZipFile, ZipFile import vvm import vyper from vyper.ast.parse import parse_to_ast +from vyper.cli.compile_archive import NotZipInput from vyper.cli.vyper_compile import get_search_paths from vyper.compiler.input_bundle import ( ABIInput, CompilerInput, FileInput, FilesystemInputBundle, + ZipInputBundle, ) from vyper.compiler.phases import CompilerData -from vyper.compiler.settings import Settings, anchor_settings +from vyper.compiler.settings import Settings, anchor_settings, merge_settings +from vyper.exceptions import BadArchive from vyper.semantics.analysis.module import analyze_module from vyper.semantics.types.module import ModuleT from vyper.utils import sha256sum @@ -129,22 +136,20 @@ def get_module_fingerprint( def compiler_data( - source_code: str, contract_name: str, filename: str | Path, deployer=None, **kwargs + source_code: str | bytes, + contract_name: str, + filename: str | Path, + deployer=None, + **kwargs, ) -> CompilerData: - global _disk_cache, _search_path + global _disk_cache path = Path(contract_name) 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) + compiler_input = _get_compiler_input(path, resolved_path, source_code, settings) + ret = CompilerData(**compiler_input) if _disk_cache is None: return ret @@ -168,25 +173,55 @@ def get_compiler_data(): return _disk_cache.caching_lookup(cache_key, get_compiler_data) +def _get_compiler_input( + path: Path, resolved_path: Path, source_code: str | bytes, settings: Settings +) -> dict: + if isinstance(source_code, bytes): + try: + return _get_compiler_zip_input(source_code, settings) + except NotZipInput: + source_code = source_code.decode() + + global _search_path + search_paths = get_search_paths(_search_path) + return { + "file_input": FileInput( + contents=source_code, source_id=-1, path=path, resolved_path=resolved_path + ), + "input_bundle": FilesystemInputBundle(search_paths), + "settings": settings, + } + + def load(filename: str | Path, *args, **kwargs) -> _Contract: # type: ignore name = Path(filename).stem # TODO: investigate if we can just put name in the signature if "name" in kwargs: name = kwargs.pop("name") - with open(filename) as f: - return loads(f.read(), *args, name=name, **kwargs, filename=filename) + with open(filename, "rb") as f: + source_code = f.read() + try: + source_code = source_code.decode() # type: ignore + except UnicodeDecodeError: + pass + return loads( + source_code, *args, name=name, dedent=False, **kwargs, filename=filename + ) def loads( - source_code, + source_code: bytes | str, *args, as_blueprint=False, name=None, filename=None, compiler_args=None, + dedent=True, **kwargs, ): - d = loads_partial(source_code, name, filename=filename, compiler_args=compiler_args) + d = loads_partial( + source_code, name, filename=filename, dedent=dedent, compiler_args=compiler_args + ) if as_blueprint: return d.deploy_as_blueprint(**kwargs) else: @@ -233,7 +268,7 @@ def loads_vyi(source_code: str, name: str = None, filename: str = None): def loads_partial( - source_code: str, + source_code: str | bytes, name: str = None, filename: str | Path | None = None, dedent: bool = True, @@ -242,17 +277,17 @@ def loads_partial( name = name or "VyperContract" filename = filename or "" - if dedent: - source_code = textwrap.dedent(source_code) + if isinstance(source_code, str): + if dedent: + source_code = textwrap.dedent(source_code) - version = _detect_version(source_code) - if version is not None and version != vyper.__version__: - filename = str(filename) # help mypy - # TODO: pass name to loads_partial_vvm, not filename - return _loads_partial_vvm(source_code, version, filename) + version = _detect_version(source_code) + if version is not None and version != vyper.__version__: + filename = str(filename) # help mypy + # TODO: pass name to loads_partial_vvm, not filename + return _loads_partial_vvm(source_code, version, filename) compiler_args = compiler_args or {} - deployer_class = _get_default_deployer_class() data = compiler_data(source_code, name, filename, deployer_class, **compiler_args) return deployer_class(data, filename=filename) @@ -286,6 +321,42 @@ def _compile(): return _disk_cache.caching_lookup(cache_key, _compile) +def _get_compiler_zip_input(zip_contents: bytes, settings: Settings): + try: + buf = BytesIO(zip_contents) + archive = ZipFile(buf, mode="r") + except BadZipFile as e1: + try: + # `validate=False` - tools like base64 can generate newlines + # for readability. validate=False does the "correct" thing and + # simply ignores these + zip_contents = b64decode(zip_contents, validate=False) + buf = BytesIO(zip_contents) + archive = ZipFile(buf, mode="r") + except (BadZipFile, binascii.Error): + raise NotZipInput() from e1 + + targets = archive.read("MANIFEST/compilation_targets").decode().splitlines() + if len(targets) != 1: + raise BadArchive("Multiple compilation targets not supported!") + + input_bundle = ZipInputBundle(archive) + main_path = PurePath(targets[0]) + archive_settings_txt = archive.read("MANIFEST/settings.json").decode() + archive_settings = Settings.from_dict(json.loads(archive_settings_txt)) + return { + "file_input": input_bundle.load_file(main_path), + "input_bundle": input_bundle, + "settings": merge_settings( + settings, + archive_settings, + lhs_source="command line", + rhs_source="archive settings", + ), + "integrity_sum": archive.read("MANIFEST/integrity").decode().strip(), + } + + def from_etherscan( address: Any, name: str = None, uri: str = None, api_key: str = None ): diff --git a/tests/unitary/fixtures/module_contract.vyz b/tests/unitary/fixtures/module_contract.vyz new file mode 100644 index 0000000000000000000000000000000000000000..aaa36bde325e1585ffc45b239e45f0ac08f21c03 GIT binary patch literal 1282 zcmWIWW@Zs#U|`^2*x>CM$vkOpk2sJg3dE8??Ca>~>E;?7qMw|fTacNPS`=TFT2!2w zpQmkn!T6%_1;2CW&z{w}=)Kj)bCbtL7KQ*6)A+lL0zhU90kI6GX^AC3gW^jPi_%j| zicg>Q*F5Xl>woUNwx*Yl-bL-z=%zV6y28f{G=&j}L8ijpR-Bqxl$=qJSdvkEnHkLq z&ij`wW&tW@0%9>#O(i9ndFjP^S;hHztJkrj=>sYS>jBZI0g;m#Uz`fnTT)SiZXx%9 zwZBb)njC-_!Ua-6+HkXu`Eg4%sW?!Ky{I}^j-?W(Ef(R5l?_2J%@ie2j>17A$c zIsW=ux%2hzJKjlCXL9$QT^qM<@q;O+ZTI z=w^WQfU*S$fRZ>2G%#`k0|uUMlhG19x-sYp9$}0VvN7=FkFFm*EhF?V2kJ*j+2~r) dlN>^;HPB<22{6E$l?|kZ0|+I7DlUV)4*-L2V{HHc literal 0 HcmV?d00001 diff --git a/tests/unitary/test_modules.py b/tests/unitary/test_modules.py index 16bfaf55..5a53a637 100644 --- a/tests/unitary/test_modules.py +++ b/tests/unitary/test_modules.py @@ -7,9 +7,9 @@ FIXTURES = Path(__file__).parent / "fixtures" -@pytest.fixture -def module_contract(): - return boa.load(FIXTURES / "module_contract.vy") +@pytest.fixture(params=["vyz", "vy"]) +def module_contract(request): + return boa.load(FIXTURES / f"module_contract.{request.param}") def test_user_raise(module_contract): From 942cc36f935b5c37d9f6b8bcb3b410f1a9e41356 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Wed, 9 Oct 2024 10:38:19 +0200 Subject: [PATCH 2/6] feat: add support for base64 --- boa/interpret.py | 32 ++++++++++++---------- tests/unitary/fixtures/module_contract.b64 | 16 +++++++++++ tests/unitary/test_modules.py | 2 +- 3 files changed, 34 insertions(+), 16 deletions(-) create mode 100644 tests/unitary/fixtures/module_contract.b64 diff --git a/boa/interpret.py b/boa/interpret.py index 942cd5b0..48d7f4b6 100644 --- a/boa/interpret.py +++ b/boa/interpret.py @@ -176,11 +176,13 @@ def get_compiler_data(): def _get_compiler_input( path: Path, resolved_path: Path, source_code: str | bytes, settings: Settings ) -> dict: + try: + return _get_compiler_zip_input(source_code, settings) + except NotZipInput: + pass + if isinstance(source_code, bytes): - try: - return _get_compiler_zip_input(source_code, settings) - except NotZipInput: - source_code = source_code.decode() + source_code = source_code.decode() global _search_path search_paths = get_search_paths(_search_path) @@ -200,13 +202,13 @@ def load(filename: str | Path, *args, **kwargs) -> _Contract: # type: ignore name = kwargs.pop("name") with open(filename, "rb") as f: source_code = f.read() - try: - source_code = source_code.decode() # type: ignore - except UnicodeDecodeError: - pass - return loads( - source_code, *args, name=name, dedent=False, **kwargs, filename=filename - ) + try: + source_code = source_code.decode() # type: ignore + except UnicodeDecodeError: + pass # source might be an archive file. Try to compile it. + return loads( + source_code, *args, name=name, dedent=False, **kwargs, filename=filename + ) def loads( @@ -321,15 +323,15 @@ def _compile(): return _disk_cache.caching_lookup(cache_key, _compile) -def _get_compiler_zip_input(zip_contents: bytes, settings: Settings): +def _get_compiler_zip_input(zip_contents: str | bytes, settings: Settings): + if isinstance(zip_contents, str): + zip_contents = zip_contents.encode() try: buf = BytesIO(zip_contents) archive = ZipFile(buf, mode="r") except BadZipFile as e1: try: - # `validate=False` - tools like base64 can generate newlines - # for readability. validate=False does the "correct" thing and - # simply ignores these + # don't validate base64 to allow for newlines zip_contents = b64decode(zip_contents, validate=False) buf = BytesIO(zip_contents) archive = ZipFile(buf, mode="r") diff --git a/tests/unitary/fixtures/module_contract.b64 b/tests/unitary/fixtures/module_contract.b64 new file mode 100644 index 00000000..396e2ba1 --- /dev/null +++ b/tests/unitary/fixtures/module_contract.b64 @@ -0,0 +1,16 @@ +UEsDBBQAAAAIAFZSSVkDkp2MFwAAABUAAAAZAAAATUFOSUZFU1QvY29tcGlsZXJfdmVyc2lvbisz0DPRM9BOzs/NzSzRS7VMSbJIsQQAUEs +DBBQAAAAIAFZSSVkPijJQFAAAABIAAAAcAAAATUFOSUZFU1QvY29tcGlsYXRpb25fdGFyZ2V0c8vNTynNSY1Pzs8rKUpMLtErqwQAUEsDBB +QAAAAIAFZSSVlC4tQOAwAAAAEAAAAUAAAATUFOSUZFU1Qvc2VhcmNocGF0aHPTAwBQSwMEFAAAAAgAVlJJWUO/pqMEAAAAAgAAABYAAABNQ +U5JRkVTVC9zZXR0aW5ncy5qc29uq64FAFBLAwQUAAAACABWUklZAAAAAAIAAAAAAAAAGQAAAE1BTklGRVNUL2NsaV9zZXR0aW5ncy50eHQD +AFBLAwQUAAAACABWUklZC8Ct+zUAAABAAAAAEgAAAE1BTklGRVNUL2ludGVncml0eQ3LwREAIAgDsJUqIOI4rZz7j6CvvLJipF8VPrMgENG +1hg8kM7ZJfjZk7DDyulqTFgnv89sDUEsDBBQAAAAIAFZSSVkkrxroaQAAAIYAAAANAAAAbW9kdWxlX2xpYi52eVWLsQqDQBBE+/2KQZukEQ +srIZAm3yELjnrFuWFX7rp8ewwpQqZ68N60eLquWVHokWzH69Z3Q9eL3EtilZkLjs2tXq6j4JxrCqJ5uJujpmNDZoSubOQXTzPL5NSw/f/X4 +jQjwjI/hG8jb1BLAwQUAAAACABWUklZ861qTWgAAACjAAAAEgAAAG1vZHVsZV9jb250cmFjdC52eXXLMQ6DMAxA0d2nsNQFloiBqRMnIXIV +01pKYmRC6PHLgoChf37/gbPROxFWtkU049i53nUAkma1gknDGtlHeQEM/C1smSIMVXiDwBNOJLFpn4B7p3XlY7o17fU5tA9cvTEtmv+NNwI +/UEsBAhQDFAAAAAgAVlJJWQOSnYwXAAAAFQAAABkAAAAAAAAAAAAAAIABAAAAAE1BTklGRVNUL2NvbXBpbGVyX3ZlcnNpb25QSwECFAMUAA +AACABWUklZD4oyUBQAAAASAAAAHAAAAAAAAAAAAAAAgAFOAAAATUFOSUZFU1QvY29tcGlsYXRpb25fdGFyZ2V0c1BLAQIUAxQAAAAIAFZSS +VlC4tQOAwAAAAEAAAAUAAAAAAAAAAAAAACAAZwAAABNQU5JRkVTVC9zZWFyY2hwYXRoc1BLAQIUAxQAAAAIAFZSSVlDv6ajBAAAAAIAAAAW +AAAAAAAAAAAAAACAAdEAAABNQU5JRkVTVC9zZXR0aW5ncy5qc29uUEsBAhQDFAAAAAgAVlJJWQAAAAACAAAAAAAAABkAAAAAAAAAAAAAAIA +BCQEAAE1BTklGRVNUL2NsaV9zZXR0aW5ncy50eHRQSwECFAMUAAAACABWUklZC8Ct+zUAAABAAAAAEgAAAAAAAAAAAAAAgAFCAQAATUFOSU +ZFU1QvaW50ZWdyaXR5UEsBAhQDFAAAAAgAVlJJWSSvGuhpAAAAhgAAAA0AAAAAAAAAAAAAAIABpwEAAG1vZHVsZV9saWIudnlQSwECFAMUA +AAACABWUklZ861qTWgAAACjAAAAEgAAAAAAAAAAAAAAgAE7AgAAbW9kdWxlX2NvbnRyYWN0LnZ5UEsFBgAAAAAIAAgAGQIAANMCAAAAAA== diff --git a/tests/unitary/test_modules.py b/tests/unitary/test_modules.py index 5a53a637..978414ad 100644 --- a/tests/unitary/test_modules.py +++ b/tests/unitary/test_modules.py @@ -7,7 +7,7 @@ FIXTURES = Path(__file__).parent / "fixtures" -@pytest.fixture(params=["vyz", "vy"]) +@pytest.fixture(params=["vyz", "vy", "b64"]) def module_contract(request): return boa.load(FIXTURES / f"module_contract.{request.param}") From 5cf3528c849fd4e2ecd5b29b89080f7206e057e4 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Wed, 9 Oct 2024 10:55:13 +0200 Subject: [PATCH 3/6] fix: remove lock before pickling --- boa/interpret.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/boa/interpret.py b/boa/interpret.py index 48d7f4b6..51d6e520 100644 --- a/boa/interpret.py +++ b/boa/interpret.py @@ -165,6 +165,10 @@ def get_compiler_data(): with anchor_settings(ret.settings): # force compilation to happen so DiskCache will cache the compiled artifact: _ = ret.bytecode, ret.bytecode_runtime + + if isinstance(ret.input_bundle, ZipInputBundle): + # workaround for `cannot pickle '_thread.RLock' object` + ret.input_bundle.archive._lock = None return ret assert isinstance(deployer, type) or deployer is None From 0f44256d933baffd7242742e9014f14f43cc0e0b Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Wed, 9 Oct 2024 11:08:28 +0200 Subject: [PATCH 4/6] refactor: return CompilerData --- boa/interpret.py | 72 +++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/boa/interpret.py b/boa/interpret.py index 51d6e520..e4707347 100644 --- a/boa/interpret.py +++ b/boa/interpret.py @@ -148,8 +148,7 @@ def compiler_data( resolved_path = Path(filename).resolve(strict=False) settings = Settings(**kwargs) - compiler_input = _get_compiler_input(path, resolved_path, source_code, settings) - ret = CompilerData(**compiler_input) + ret = _create_compiler_data(path, resolved_path, source_code, settings) if _disk_cache is None: return ret @@ -177,28 +176,6 @@ def get_compiler_data(): return _disk_cache.caching_lookup(cache_key, get_compiler_data) -def _get_compiler_input( - path: Path, resolved_path: Path, source_code: str | bytes, settings: Settings -) -> dict: - try: - return _get_compiler_zip_input(source_code, settings) - except NotZipInput: - pass - - if isinstance(source_code, bytes): - source_code = source_code.decode() - - global _search_path - search_paths = get_search_paths(_search_path) - return { - "file_input": FileInput( - contents=source_code, source_id=-1, path=path, resolved_path=resolved_path - ), - "input_bundle": FilesystemInputBundle(search_paths), - "settings": settings, - } - - def load(filename: str | Path, *args, **kwargs) -> _Contract: # type: ignore name = Path(filename).stem # TODO: investigate if we can just put name in the signature @@ -216,7 +193,7 @@ def load(filename: str | Path, *args, **kwargs) -> _Contract: # type: ignore def loads( - source_code: bytes | str, + source_code: str | bytes, *args, as_blueprint=False, name=None, @@ -327,7 +304,28 @@ def _compile(): return _disk_cache.caching_lookup(cache_key, _compile) -def _get_compiler_zip_input(zip_contents: str | bytes, settings: Settings): +def _create_compiler_data( + path: Path, resolved_path: Path, source_code: str | bytes, settings: Settings +) -> CompilerData: + try: + return _create_archive_compiler_data(source_code, settings) + except NotZipInput: + pass + + if isinstance(source_code, bytes): + source_code = source_code.decode() + + global _search_path + file_input = FileInput( + contents=source_code, source_id=-1, path=path, resolved_path=resolved_path + ) + input_bundle = FilesystemInputBundle(get_search_paths(_search_path)) + return CompilerData(file_input, input_bundle, settings) + + +def _create_archive_compiler_data( + zip_contents: str | bytes, settings: Settings +) -> CompilerData: if isinstance(zip_contents, str): zip_contents = zip_contents.encode() try: @@ -349,18 +347,16 @@ def _get_compiler_zip_input(zip_contents: str | bytes, settings: Settings): input_bundle = ZipInputBundle(archive) main_path = PurePath(targets[0]) archive_settings_txt = archive.read("MANIFEST/settings.json").decode() - archive_settings = Settings.from_dict(json.loads(archive_settings_txt)) - return { - "file_input": input_bundle.load_file(main_path), - "input_bundle": input_bundle, - "settings": merge_settings( - settings, - archive_settings, - lhs_source="command line", - rhs_source="archive settings", - ), - "integrity_sum": archive.read("MANIFEST/integrity").decode().strip(), - } + file = input_bundle.load_file(main_path) + assert isinstance(file, FileInput) # help mypy + settings = merge_settings( + settings, + Settings.from_dict(json.loads(archive_settings_txt)), + lhs_source="command line", + rhs_source="archive settings", + ) + integrity_sum = archive.read("MANIFEST/integrity").decode().strip() + return CompilerData(file, input_bundle, settings, integrity_sum) def from_etherscan( From 5d9cc9a45ad9184b5d9ad37d23ddc2ba8d7fae34 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Tue, 22 Oct 2024 09:39:22 +0200 Subject: [PATCH 5/6] fix: avoid pickle issues by using JsonInput --- boa/contracts/vyper/compiler_utils.py | 4 ++ boa/interpret.py | 53 ++++++++++++++++----------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/boa/contracts/vyper/compiler_utils.py b/boa/contracts/vyper/compiler_utils.py index f73cb784..3711d426 100644 --- a/boa/contracts/vyper/compiler_utils.py +++ b/boa/contracts/vyper/compiler_utils.py @@ -1,4 +1,8 @@ import textwrap +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + pass import vyper.ast as vy_ast import vyper.semantics.analysis as analysis diff --git a/boa/interpret.py b/boa/interpret.py index e4707347..40520af8 100644 --- a/boa/interpret.py +++ b/boa/interpret.py @@ -20,7 +20,7 @@ CompilerInput, FileInput, FilesystemInputBundle, - ZipInputBundle, + JSONInputBundle, ) from vyper.compiler.phases import CompilerData from vyper.compiler.settings import Settings, anchor_settings, merge_settings @@ -165,9 +165,6 @@ def get_compiler_data(): # force compilation to happen so DiskCache will cache the compiled artifact: _ = ret.bytecode, ret.bytecode_runtime - if isinstance(ret.input_bundle, ZipInputBundle): - # workaround for `cannot pickle '_thread.RLock' object` - ret.input_bundle.archive._lock = None return ret assert isinstance(deployer, type) or deployer is None @@ -326,39 +323,51 @@ def _create_compiler_data( def _create_archive_compiler_data( zip_contents: str | bytes, settings: Settings ) -> CompilerData: - if isinstance(zip_contents, str): - zip_contents = zip_contents.encode() - try: - buf = BytesIO(zip_contents) - archive = ZipFile(buf, mode="r") - except BadZipFile as e1: - try: - # don't validate base64 to allow for newlines - zip_contents = b64decode(zip_contents, validate=False) - buf = BytesIO(zip_contents) - archive = ZipFile(buf, mode="r") - except (BadZipFile, binascii.Error): - raise NotZipInput() from e1 + with _open_zip(zip_contents) as archive: + # read the whole zip into memory so it can be serialized to the cache + files = {name: archive.read(name).decode() for name in archive.namelist()} - targets = archive.read("MANIFEST/compilation_targets").decode().splitlines() + targets = files["MANIFEST/compilation_targets"].splitlines() if len(targets) != 1: raise BadArchive("Multiple compilation targets not supported!") - input_bundle = ZipInputBundle(archive) + input_bundle = JSONInputBundle( + input_json={ + PurePath(name): {"content": content} for name, content in files.items() + }, + search_paths=[PurePath(p) for p in files["MANIFEST/searchpaths"].splitlines()], + ) + main_path = PurePath(targets[0]) - archive_settings_txt = archive.read("MANIFEST/settings.json").decode() file = input_bundle.load_file(main_path) assert isinstance(file, FileInput) # help mypy + settings_json = json.loads(files["MANIFEST/settings.json"]) settings = merge_settings( settings, - Settings.from_dict(json.loads(archive_settings_txt)), + Settings.from_dict(settings_json), lhs_source="command line", rhs_source="archive settings", ) - integrity_sum = archive.read("MANIFEST/integrity").decode().strip() + integrity_sum = files["MANIFEST/integrity"].strip() return CompilerData(file, input_bundle, settings, integrity_sum) +def _open_zip(zip_contents): + if isinstance(zip_contents, str): + zip_contents = zip_contents.encode() + try: + buf = BytesIO(zip_contents) + return ZipFile(buf, mode="r") + except BadZipFile as e1: + try: + # don't validate base64 to allow for newlines + zip_contents = b64decode(zip_contents, validate=False) + buf = BytesIO(zip_contents) + return ZipFile(buf, mode="r") + except (BadZipFile, binascii.Error): + raise NotZipInput() from e1 + + def from_etherscan( address: Any, name: str = None, uri: str = None, api_key: str = None ): From e94e9f9e2a938cd05a82d524f339c47067919626 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Tue, 22 Oct 2024 10:57:23 +0200 Subject: [PATCH 6/6] unused import --- boa/contracts/vyper/compiler_utils.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/boa/contracts/vyper/compiler_utils.py b/boa/contracts/vyper/compiler_utils.py index 3711d426..f73cb784 100644 --- a/boa/contracts/vyper/compiler_utils.py +++ b/boa/contracts/vyper/compiler_utils.py @@ -1,8 +1,4 @@ import textwrap -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - pass import vyper.ast as vy_ast import vyper.semantics.analysis as analysis