Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions glsim/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,9 @@ Contracts that read sibling files via `open("/contract/OtherModule.py")` work
### Cross-Contract Calls

Contracts using `gl.contract.deploy()`, `gl.contract.get_at().view()`, and `gl.contract.get_at().emit()` work. glsim handles:
- **DeployContract** — deploys child contract with isolated storage
- **EmitInternalDeployMessage** — deploys child contract with isolated storage
- **CallContract** — calls method on deployed contract, returns result
- **PostMessage** — fire-and-forget call (no return value)
- **EmitInternalMessage** — fire-and-forget call (no return value)

### GenVM Library Stubs

Expand Down
10 changes: 9 additions & 1 deletion glsim/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,14 +614,22 @@ def _install_live_handlers(self) -> None:
self.vm._live_llm_handler = self._llm_handler

def install_cross_contract_hook(self) -> None:
"""Install gl_call hook for cross-contract calls (DeployContract, CallContract, PostMessage)."""
"""Install gl_call hooks for current and rc7 cross-contract calls."""
engine = self

def hook(vm, request):
if "EmitInternalDeployMessage" in request:
return engine._handle_deploy_in_contract(
vm, request["EmitInternalDeployMessage"]
)
if "DeployContract" in request:
return engine._handle_deploy_in_contract(vm, request["DeployContract"])
if "CallContract" in request:
return engine._handle_call_in_contract(vm, request["CallContract"])
if "EmitInternalMessage" in request:
return engine._handle_post_in_contract(
vm, request["EmitInternalMessage"]
)
if "PostMessage" in request:
return engine._handle_post_in_contract(vm, request["PostMessage"])
return None
Expand Down
32 changes: 24 additions & 8 deletions gltest/direct/sdk_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import json
import shutil
import tarfile
import zipfile
import platform
import tempfile
import urllib.error
Expand Down Expand Up @@ -42,6 +43,9 @@ def _host_release_asset() -> str:
GENVM_VERSION_ENV = "GENVM_VERSION"
FALLBACK_VERSION = "v0.6.0-rc0"

# v0.3 runner trees use .zip; the v0.2 legacy-runners tree uses .tar.
RUNNER_ARCHIVE_EXTS = (".tar", ".zip")

RUNNER_TYPE = "py-genlayer"
STD_LIB_TYPE = "py-lib-genlayer-std"
EMBEDDINGS_TYPE = "py-lib-genlayer-embeddings"
Expand Down Expand Up @@ -179,21 +183,33 @@ 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/<ver>/legacy-runners/ both match."""
any *runners* dir so runners/ and executor/<ver>/legacy-runners/ both match.
v0.3 runners ship as .zip, the v0.2 legacy tree still ships .tar."""
sub = (
f"{runner_hash[:2]}/{runner_hash[2:]}.tar"
f"{runner_hash[:2]}/{runner_hash[2:]}"
if runner_hash and runner_hash.lower() != "latest"
else "*/*.tar"
else "*/*"
)
hits = sorted(
hit
for ext in RUNNER_ARCHIVE_EXTS
for hit in root.glob(f"**/*runners*/{runner_type}/{sub}{ext}")
)
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)
archive = hits[-1]
dest = (
CACHE_DIR / "extracted" / "local" / runner_type
/ (archive.parent.name + archive.stem)
)
if not dest.exists():
dest.mkdir(parents=True, exist_ok=True)
with tarfile.open(tar, "r:") as inner:
inner.extractall(dest, filter="data")
if archive.suffix == ".zip":
with zipfile.ZipFile(archive) as inner:
inner.extractall(dest)
else:
with tarfile.open(archive, "r:") as inner:
inner.extractall(dest, filter="data")
return dest


Expand Down
2 changes: 1 addition & 1 deletion gltest/direct/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ class VMContext:
_live_web_handler: Optional[Any] = None
_live_llm_handler: Optional[Any] = None

# Cross-contract call hook (for glsim — handles DeployContract/CallContract/PostMessage)
# Cross-contract call hook (glsim accepts current and rc7 request names).
_gl_call_hook: Optional[Any] = None

# Debug tracing
Expand Down
10 changes: 9 additions & 1 deletion gltest/direct/wasi_mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,15 @@ def get_self_balance() -> int:
return vm._balances.get(addr_bytes, 0)


_CROSS_CONTRACT_OPS = frozenset({"DeployContract", "CallContract", "PostMessage"})
_CROSS_CONTRACT_OPS = frozenset(
{
"DeployContract",
"EmitInternalDeployMessage",
"CallContract",
"PostMessage",
"EmitInternalMessage",
}
)


def gl_call(data: bytes, /) -> int:
Expand Down
2 changes: 1 addition & 1 deletion tests/examples/contracts/intelligent_oracle_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def create_new_prediction_market(
earliest_resolution_date,
],
salt_nonce=registered_contracts + 1,
on="accepted",
on="decided",
)
print("contract_address", contract_address)
print("contract_address type", type(contract_address))
Expand Down
4 changes: 3 additions & 1 deletion tests/examples/contracts/log_indexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ class StoreValue:

# contract class
class LogIndexer(gl.contract.Contract):
vector_store: gle.VecDB[np.float32, typing.Literal[384], StoreValue]
vector_store: gle.VecDB[
np.float32, typing.Literal[384], StoreValue, gle.EuclideanDistance
]

def __init__(self):
pass
Expand Down
2 changes: 1 addition & 1 deletion tests/examples/contracts/multi_file_contract/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def __init__(self):
args=["123"],
salt_nonce=1,
value=0,
on="accepted",
on="decided",
)

@gl.public.write
Expand Down
2 changes: 1 addition & 1 deletion tests/examples/contracts/multi_tenant_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ def update_storage(self, new_storage: str) -> None:
self.available_storage_contracts.pop()

contract_to_use = self.mappings[gl.message.sender_address]
gl.contract.get_at(contract_to_use).emit(on="accepted").update_storage(
gl.contract.get_at(contract_to_use).emit(on="decided").update_storage(
new_storage
)
2 changes: 1 addition & 1 deletion tests/glsim/deterministic_factory_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def deploy_child(self, salt: int) -> str:
args=[],
kwargs={},
salt_nonce=salt,
on="accepted",
on="decided",
)
self.child_address = child_address.as_hex
return self.child_address
Expand Down
11 changes: 6 additions & 5 deletions tests/gltest_direct/test_direct_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ class TestNondetRestrictions:
"""Tests that cross-contract calls are forbidden inside nondet context.

GenVM raises SystemError: 6 (forbidden) when contract code attempts
cross-contract calls (DeployContract, CallContract, PostMessage) inside
cross-contract calls (EmitInternalDeployMessage, CallContract,
EmitInternalMessage) inside
eq_principle/run_nondet. These tests verify gltest enforces the same
restriction in direct mode.
"""
Expand Down Expand Up @@ -278,23 +279,23 @@ def bad_leader():
gl_vm.run_nondet(bad_leader, lambda r: True)

def test_deploy_contract_forbidden_in_nondet(self, direct_vm, direct_deploy):
"""DeployContract inside run_nondet raises RuntimeError."""
"""EmitInternalDeployMessage inside run_nondet raises RuntimeError."""
direct_deploy(str(CONTRACTS_DIR / "storage.py"), "v")

import genlayer.vm as gl_vm
from genlayer import calldata
from gltest.direct import wasi_mock

def bad_leader():
request = {"DeployContract": {"code": b"pass", "calldata": {}}}
request = {"EmitInternalDeployMessage": {"code": b"pass", "calldata": {}}}
wasi_mock.gl_call(calldata.encode(request))
return "should not reach"

with pytest.raises(RuntimeError, match="Cross-contract call.*forbidden"):
gl_vm.run_nondet(bad_leader, lambda r: True)

def test_post_message_forbidden_in_nondet(self, direct_vm, direct_deploy):
"""PostMessage inside run_nondet raises RuntimeError."""
"""EmitInternalMessage inside run_nondet raises RuntimeError."""
direct_deploy(str(CONTRACTS_DIR / "storage.py"), "v")

import genlayer.vm as gl_vm
Expand All @@ -303,7 +304,7 @@ def test_post_message_forbidden_in_nondet(self, direct_vm, direct_deploy):

def bad_leader():
request = {
"PostMessage": {
"EmitInternalMessage": {
"address": b"\x00" * 20,
"calldata": {"method": "bar", "args": []},
}
Expand Down
Loading