diff --git a/gltest/direct/sdk_loader.py b/gltest/direct/sdk_loader.py index d1fa7ab..869f72a 100644 --- a/gltest/direct/sdk_loader.py +++ b/gltest/direct/sdk_loader.py @@ -9,7 +9,9 @@ import re import sys import json +import shutil import tarfile +import platform import tempfile import urllib.error import urllib.request @@ -17,13 +19,28 @@ from typing import Optional, Dict, List CACHE_DIR = Path.home() / ".cache" / "gltest-direct" -GITHUB_RELEASES_URL = "https://github.com/genlayerlabs/genvm/releases" -GITHUB_API_RELEASES = "https://api.github.com/repos/genlayerlabs/genvm/releases" - -# GenVM 0.3.0 renamed this bundle from genvm-universal.tar.xz; newest name first. -RUNNER_BUNDLE_ASSETS = ("genvm-runners-all.tar.xz", "genvm-universal.tar.xz") +GITHUB_RELEASES_URL = "https://github.com/genlayerlabs/genvm-manager/releases" +GITHUB_API_RELEASES = "https://api.github.com/repos/genlayerlabs/genvm-manager/releases" + + +def _host_release_asset() -> str: + """genvm-manager (v0.6+) ships a whole-tree tarball per platform, named + genvm--.tar.xz — pick the one matching this host.""" + machine = platform.machine().lower() + arch = "arm64" if machine in ("aarch64", "arm64") else "amd64" + os_name = "macos" if platform.system().lower() == "darwin" else "linux" + return f"genvm-{arch}-{os_name}.tar.xz" + + +# Download candidates, newest-scheme first: the genvm-manager per-platform whole +# tree, then the pre-v0.6 runner-only bundles (genvm-runners-all → genvm-universal). +RUNNER_BUNDLE_ASSETS = ( + _host_release_asset(), + "genvm-runners-all.tar.xz", + "genvm-universal.tar.xz", +) GENVM_VERSION_ENV = "GENVM_VERSION" -FALLBACK_VERSION = "v0.2.16" +FALLBACK_VERSION = "v0.6.0-rc0" RUNNER_TYPE = "py-genlayer" STD_LIB_TYPE = "py-lib-genlayer-std" @@ -158,68 +175,72 @@ def download_artifacts(version: str) -> Path: ) from last_error +def _extract_local_runner( + root: Path, runner_type: str, runner_hash: Optional[str] +) -> Path: + """Extract a runner from a local prebuilt GenVM tree (GENVM_PREBUILT_DIR); globs + any *runners* dir so runners/ and executor//legacy-runners/ both match.""" + sub = ( + f"{runner_hash[:2]}/{runner_hash[2:]}.tar" + if runner_hash and runner_hash.lower() != "latest" + else "*/*.tar" + ) + hits = sorted(root.glob(f"**/*runners*/{runner_type}/{sub}")) + if not hits: + raise FileNotFoundError(f"runner {runner_type}:{runner_hash} not under {root}") + tar = hits[-1] + dest = CACHE_DIR / "extracted" / "local" / runner_type / (tar.parent.name + tar.stem) + if not dest.exists(): + dest.mkdir(parents=True, exist_ok=True) + with tarfile.open(tar, "r:") as inner: + inner.extractall(dest, filter="data") + return dest + + +def _extract_release_tree(tarball_path: Path, version: str) -> Path: + """Unpack a downloaded GenVM release tarball once (cached) into a local tree. + + genvm-manager ships the whole tree (bin/ lib/ runners/ executor/…), not a + runner-only bundle, so we unpack it to a directory that looks exactly like a + GENVM_PREBUILT_DIR and then resolve runners through the same globbing path. + """ + tree = CACHE_DIR / "trees" / version + if (tree / ".extracted").exists(): + return tree + trees = CACHE_DIR / "trees" + trees.mkdir(parents=True, exist_ok=True) + # Extract into a process-unique dir, mark it complete, then publish with an + # atomic rename. Concurrent cold-cache extractions each use their own tmp and + # race only on the final rename; the loser sees the winner's finished tree. + tmp = Path(tempfile.mkdtemp(dir=trees, prefix=f".{version}.")) + try: + with tarfile.open(tarball_path, "r:xz") as outer: + outer.extractall(tmp, filter="data") + (tmp / ".extracted").touch() + os.replace(tmp, tree) + except OSError: + shutil.rmtree(tmp, ignore_errors=True) + if (tree / ".extracted").exists(): + return tree # another extraction published first + raise + return tree + + def extract_runner( tarball_path: Path, runner_type: str, runner_hash: Optional[str] = None, version: Optional[str] = None, ) -> Path: - """Extract a runner from the tarball.""" + """Resolve a runner dir from a local prebuilt tree or a downloaded release.""" + prebuilt = os.environ.get("GENVM_PREBUILT_DIR") + if prebuilt: + return _extract_local_runner(Path(prebuilt), runner_type, runner_hash) if version is None: match = re.search(r"genvm-universal-(.+)\.tar\.xz", tarball_path.name) version = match.group(1) if match else "unknown" - - extract_base = CACHE_DIR / "extracted" / version / runner_type - - # Fast path: if hash specified and already extracted, skip tarball entirely - if runner_hash and runner_hash.lower() != "latest": - extract_dir = extract_base / runner_hash - if extract_dir.exists(): - return extract_dir - - # Check if any version already extracted (for "latest" case) - if extract_base.exists(): - existing = sorted(extract_base.iterdir(), reverse=True) - if existing and (not runner_hash or runner_hash.lower() == "latest"): - return existing[0] - - # Need to open tarball - this is slow (~13s for xz) - with tarfile.open(tarball_path, "r:xz") as outer_tar: - prefix = f"runners/{runner_type}/" - runner_tars = [ - m.name for m in outer_tar.getmembers() - if m.name.startswith(prefix) and m.name.endswith(".tar") - ] - - if not runner_tars: - raise ValueError(f"No {runner_type} runners found in tarball") - - # Treat "latest" as no specific hash - if runner_hash and runner_hash.lower() != "latest": - target = f"runners/{runner_type}/{runner_hash[:2]}/{runner_hash[2:]}.tar" - if target not in runner_tars: - raise ValueError(f"Runner hash {runner_hash} not found") - runner_tar_name = target - extract_dir = extract_base / runner_hash - else: - runner_tar_name = sorted(runner_tars)[-1] - parts = runner_tar_name.split("/") - runner_hash = parts[-2] + parts[-1].replace(".tar", "") - extract_dir = extract_base / runner_hash - - if extract_dir.exists(): - return extract_dir - - inner_tar_file = outer_tar.extractfile(runner_tar_name) - if inner_tar_file is None: - raise ValueError(f"Failed to read {runner_tar_name}") - - extract_dir.mkdir(parents=True, exist_ok=True) - - with tarfile.open(fileobj=inner_tar_file, mode="r:") as inner_tar: - inner_tar.extractall(extract_dir, filter='data') - - return extract_dir + tree = _extract_release_tree(tarball_path, version) + return _extract_local_runner(tree, runner_type, runner_hash) def parse_runner_manifest(runner_dir: Path) -> Dict[str, str]: @@ -256,10 +277,11 @@ def setup_sdk_paths( if contract_path and contract_path.exists(): contract_deps = parse_contract_header(contract_path) - if version is None: + prebuilt = os.environ.get("GENVM_PREBUILT_DIR") + if version is None and not prebuilt: version = resolve_version() - tarball = download_artifacts(version) + tarball = None if prebuilt else download_artifacts(version) runner_hash = contract_deps.get(RUNNER_TYPE) runner_dir = extract_runner(tarball, RUNNER_TYPE, runner_hash, version) diff --git a/tests/examples/contracts/football_prediction_market.py b/tests/examples/contracts/football_prediction_market.py index 2e33279..dfcb5f5 100644 --- a/tests/examples/contracts/football_prediction_market.py +++ b/tests/examples/contracts/football_prediction_market.py @@ -37,7 +37,7 @@ def __init__(self, game_date: str, team1: str, team2: str): ) self.team1 = team1 self.team2 = team2 - self.winner = gl.u256(0) + self.winner = 0 self.score = "" @gl.public.write diff --git a/tests/examples/contracts/intelligent_oracle.py b/tests/examples/contracts/intelligent_oracle.py index 8e9cd06..5bc0a13 100644 --- a/tests/examples/contracts/intelligent_oracle.py +++ b/tests/examples/contracts/intelligent_oracle.py @@ -19,10 +19,10 @@ class IntelligentOracle(gl.contract.Contract): prediction_market_id: str title: str description: str - potential_outcomes: gl.DynArray[str] - rules: gl.DynArray[str] - data_source_domains: gl.DynArray[str] - resolution_urls: gl.DynArray[str] + potential_outcomes: gl.storage.DynArray[str] + rules: gl.storage.DynArray[str] + data_source_domains: gl.storage.DynArray[str] + resolution_urls: gl.storage.DynArray[str] earliest_resolution_date: str # Store as ISO format string status: str # Store as string since Enum isn't supported analysis: str # Store analysis results diff --git a/tests/examples/contracts/intelligent_oracle_factory.py b/tests/examples/contracts/intelligent_oracle_factory.py index 0d147b8..c49e335 100644 --- a/tests/examples/contracts/intelligent_oracle_factory.py +++ b/tests/examples/contracts/intelligent_oracle_factory.py @@ -6,7 +6,7 @@ class Registry(gl.contract.Contract): # Declare persistent storage fields - contract_addresses: gl.DynArray[str] + contract_addresses: gl.storage.DynArray[str] intelligent_oracle_code: str def __init__(self, intelligent_oracle_code: str): diff --git a/tests/examples/contracts/llm_erc20.py b/tests/examples/contracts/llm_erc20.py index 85561e4..9b150ac 100644 --- a/tests/examples/contracts/llm_erc20.py +++ b/tests/examples/contracts/llm_erc20.py @@ -7,10 +7,10 @@ class LlmErc20(gl.contract.Contract): - balances: gl.TreeMap[gl.Address, gl.u256] + balances: gl.storage.TreeMap[gl.Address, gl.u256] def __init__(self, total_supply: int) -> None: - self.balances[gl.message.sender_address] = gl.u256(total_supply) + self.balances[gl.message.sender_address] = total_supply @gl.public.write def transfer(self, amount: int, to_address: str) -> None: diff --git a/tests/examples/contracts/log_indexer.py b/tests/examples/contracts/log_indexer.py index a3eed4f..5637eb7 100644 --- a/tests/examples/contracts/log_indexer.py +++ b/tests/examples/contracts/log_indexer.py @@ -53,14 +53,14 @@ def get_closest_vector(self, text: str) -> dict | None: @gl.public.write def add_log(self, log: str, log_id: int) -> None: emb = self.get_embedding(log) - self.vector_store.insert(emb, StoreValue(text=log, log_id=gl.u256(log_id))) + self.vector_store.insert(emb, StoreValue(text=log, log_id=log_id)) @gl.public.write def update_log(self, log_id: int, log: str) -> None: emb = self.get_embedding(log) for elem in self.vector_store.knn(emb, 2): if elem.value.text == log: - elem.value.log_id = gl.u256(log_id) + elem.value.log_id = log_id @gl.public.write def remove_log(self, id: int) -> None: diff --git a/tests/examples/contracts/multi_file_contract/__init__.py b/tests/examples/contracts/multi_file_contract/__init__.py index a2c75cd..8927ab5 100644 --- a/tests/examples/contracts/multi_file_contract/__init__.py +++ b/tests/examples/contracts/multi_file_contract/__init__.py @@ -10,8 +10,8 @@ def __init__(self): self.other_addr = gl.contract.deploy( code=text.encode("utf-8"), args=["123"], - salt_nonce=gl.u256(1), - value=gl.u256(0), + salt_nonce=1, + value=0, on="accepted", ) diff --git a/tests/examples/contracts/multi_read_erc20.py b/tests/examples/contracts/multi_read_erc20.py index 3bdf346..8ad807f 100644 --- a/tests/examples/contracts/multi_read_erc20.py +++ b/tests/examples/contracts/multi_read_erc20.py @@ -5,7 +5,7 @@ class multi_read_erc20(gl.contract.Contract): - balances: gl.TreeMap[gl.Address, gl.TreeMap[gl.Address, gl.u256]] + balances: gl.storage.TreeMap[gl.Address, gl.storage.TreeMap[gl.Address, gl.u256]] def __init__(self): pass diff --git a/tests/examples/contracts/multi_tenant_storage.py b/tests/examples/contracts/multi_tenant_storage.py index 6aa37e9..2bcd15d 100644 --- a/tests/examples/contracts/multi_tenant_storage.py +++ b/tests/examples/contracts/multi_tenant_storage.py @@ -12,9 +12,9 @@ class MultiTentantStorage(gl.contract.Contract): This is done to test contract calls between different contracts. """ - all_storage_contracts: gl.DynArray[gl.Address] - available_storage_contracts: gl.DynArray[gl.Address] - mappings: gl.TreeMap[ + all_storage_contracts: gl.storage.DynArray[gl.Address] + available_storage_contracts: gl.storage.DynArray[gl.Address] + mappings: gl.storage.TreeMap[ gl.Address, gl.Address ] # mapping of user address to storage contract address diff --git a/tests/examples/contracts/user_storage.py b/tests/examples/contracts/user_storage.py index a37a902..8f02bf9 100644 --- a/tests/examples/contracts/user_storage.py +++ b/tests/examples/contracts/user_storage.py @@ -5,7 +5,7 @@ class UserStorage(gl.contract.Contract): - storage: gl.TreeMap[gl.Address, str] + storage: gl.storage.TreeMap[gl.Address, str] # constructor def __init__(self): diff --git a/tests/glsim/deterministic_factory_contract.py b/tests/glsim/deterministic_factory_contract.py index 3b65b96..ef51fcf 100644 --- a/tests/glsim/deterministic_factory_contract.py +++ b/tests/glsim/deterministic_factory_contract.py @@ -24,7 +24,7 @@ def deploy_child(self, salt: int) -> str: code=CHILD_CODE.encode("utf-8"), args=[], kwargs={}, - salt_nonce=gl.u256(salt), + salt_nonce=salt, on="accepted", ) self.child_address = child_address.as_hex diff --git a/tests/gltest/artifact/contracts/current_style_contract.py b/tests/gltest/artifact/contracts/current_style_contract.py index caa7b20..ce6020a 100644 --- a/tests/gltest/artifact/contracts/current_style_contract.py +++ b/tests/gltest/artifact/contracts/current_style_contract.py @@ -3,7 +3,7 @@ import genlayer as gl -class CurrentStyleContract(gl.Contract): +class CurrentStyleContract(gl.contract.Contract): storage: str def __init__(self, initial_storage: str): diff --git a/tests/gltest_direct/test_sdk_loader.py b/tests/gltest_direct/test_sdk_loader.py index 7a7cbd6..dd28db5 100644 --- a/tests/gltest_direct/test_sdk_loader.py +++ b/tests/gltest_direct/test_sdk_loader.py @@ -162,7 +162,7 @@ def _download(url, dest): assert result == tmp_path / "genvm-universal-v0.3.0.tar.xz" assert result.read_bytes() == b"bundle" assert tried == [ - f"{sdk_loader.GITHUB_RELEASES_URL}/download/v0.3.0/genvm-runners-all.tar.xz" + f"{sdk_loader.GITHUB_RELEASES_URL}/download/v0.3.0/{sdk_loader.RUNNER_BUNDLE_ASSETS[0]}" ] def test_falls_back_to_old_asset_on_404(self, monkeypatch, tmp_path): @@ -171,7 +171,8 @@ def test_falls_back_to_old_asset_on_404(self, monkeypatch, tmp_path): def _download(url, dest): tried.append(url) - if url.endswith("genvm-runners-all.tar.xz"): + # 404 every asset except the last so the loop walks the full list. + if not url.endswith(sdk_loader.RUNNER_BUNDLE_ASSETS[-1]): raise self._http_404(url) dest.write_bytes(b"bundle")