From 30596bb7e9aca0e32470da08a8d7d7629a40516a Mon Sep 17 00:00:00 2001 From: Hussein Adeiza Date: Mon, 28 Sep 2026 19:00:44 +0200 Subject: [PATCH] fix(direct): resolve SDK modules from the current genlayer.py layout The SDK moved its Python-side modules under `genlayer.py` (v0.6+), but `import_calldata`/`import_types` still imported them from the package root. On a 0.6 runner that raised ImportError, which `_inject_message_to_fd0` swallowed, so the encoded message was never written to fd 0 and the SDK decoded real stdin instead: ImportError: Failed to load contract: unexpected end of memory Import from the current location first and fall back to the legacy path so older pinned runners keep working. Fixes #113 --- gltest/direct/sdk_compat.py | 28 ++++++++---- tests/gltest_direct/test_sdk_compat.py | 63 ++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 9 deletions(-) create mode 100644 tests/gltest_direct/test_sdk_compat.py diff --git a/gltest/direct/sdk_compat.py b/gltest/direct/sdk_compat.py index 61c5d17..8830def 100644 --- a/gltest/direct/sdk_compat.py +++ b/gltest/direct/sdk_compat.py @@ -1,25 +1,35 @@ -"""Helpers for the GenVM v0.3 SDK layout.""" +"""Helpers for resolving modules from the active GenVM SDK layout. + +The SDK moved its Python-side modules under ``genlayer.py`` (v0.6 and later). +Pinned runner bundles below that expose them at the package root still work, so +each helper tries the current location first and falls back to the legacy one. +""" from __future__ import annotations +import importlib import sys from typing import Any _UNSET = object() -def import_calldata() -> Any: - """Return the v0.3 calldata module from the active SDK path.""" - from genlayer import calldata +def _import_sdk_module(name: str) -> Any: + """Import ``name`` from the active SDK, current layout first.""" + try: + return importlib.import_module(f"genlayer.py.{name}") + except ModuleNotFoundError: + return importlib.import_module(f"genlayer.{name}") - return calldata +def import_calldata() -> Any: + """Return the calldata module from the active SDK path.""" + return _import_sdk_module("calldata") -def import_types() -> Any: - """Return the v0.3 types module from the active SDK path.""" - from genlayer import types as sdk_types - return sdk_types +def import_types() -> Any: + """Return the types module from the active SDK path.""" + return _import_sdk_module("types") def import_address() -> type: diff --git a/tests/gltest_direct/test_sdk_compat.py b/tests/gltest_direct/test_sdk_compat.py new file mode 100644 index 0000000..599e1cd --- /dev/null +++ b/tests/gltest_direct/test_sdk_compat.py @@ -0,0 +1,63 @@ +"""Tests for resolving SDK modules from the active GenVM layout. + +The SDK relocated its Python-side modules under ``genlayer.py``. These helpers +feed the fd-0 message injection and address coercion in the direct runner, so a +stale import path there fails at contract-load time with an opaque +``DecodingError: unexpected end of memory`` rather than an ImportError. +""" + +import pytest + +from gltest.direct import sdk_compat + +CONTRACT = ( + '# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" }\n' + "from genlayer import *\n" + "\n" + "\n" + "class NoArgContract(gl.Contract):\n" + " def __init__(self):\n" + " self.value: u256 = 1\n" +) + + +@pytest.fixture +def sdk_on_path(direct_deploy, tmp_path): + """Deploy once so the SDK is on sys.path, as it is for real test runs.""" + contract = tmp_path / "NoArgContract.py" + contract.write_text(CONTRACT) + assert direct_deploy(str(contract)) is not None + + +class TestSdkModuleResolution: + """import_calldata / import_types must find the modules the SDK ships.""" + + def test_import_calldata_resolves(self, sdk_on_path): + calldata = sdk_compat.import_calldata() + assert hasattr(calldata, "encode") + assert hasattr(calldata, "decode") + + def test_import_types_resolves(self, sdk_on_path): + types = sdk_compat.import_types() + assert hasattr(types, "Address") + + def test_import_address_is_a_type(self, sdk_on_path): + address = sdk_compat.import_address() + assert isinstance(address, type) + assert address.SIZE == 20 + + def test_import_address_u256_pair(self, sdk_on_path): + address, u256 = sdk_compat.import_address_u256() + assert isinstance(address, type) + assert u256 is not None + + def test_modules_come_from_same_package(self, sdk_on_path): + assert sdk_compat.import_calldata().__name__.startswith("genlayer") + assert sdk_compat.import_types().__name__.startswith("genlayer") + + +def test_deploy_no_arg_contract(direct_deploy, tmp_path): + """A no-argument contract must load; this is the import-time failure mode.""" + contract = tmp_path / "NoArgContract.py" + contract.write_text(CONTRACT) + assert direct_deploy(str(contract)) is not None