genlayer-test 0.11.0__tar.gz → 0.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/PKG-INFO +67 -1
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/README.md +66 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/genlayer_test.egg-info/PKG-INFO +67 -1
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/genlayer_test.egg-info/SOURCES.txt +7 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/genlayer_test.egg-info/entry_points.txt +1 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/genlayer_test.egg-info/top_level.txt +1 -0
- genlayer_test-0.12.0/gltest/direct/__init__.py +31 -0
- genlayer_test-0.12.0/gltest/direct/loader.py +288 -0
- genlayer_test-0.12.0/gltest/direct/pytest_plugin.py +117 -0
- genlayer_test-0.12.0/gltest/direct/sdk_loader.py +260 -0
- genlayer_test-0.12.0/gltest/direct/types.py +18 -0
- genlayer_test-0.12.0/gltest/direct/vm.py +432 -0
- genlayer_test-0.12.0/gltest/direct/wasi_mock.py +219 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/pyproject.toml +2 -1
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/LICENSE +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/genlayer_test.egg-info/dependency_links.txt +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/genlayer_test.egg-info/requires.txt +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/__init__.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/accounts.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/artifacts/__init__.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/artifacts/contract.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/assertions.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/clients.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/contracts/__init__.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/contracts/contract.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/contracts/contract_factory.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/contracts/contract_functions.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/contracts/method_stats.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/contracts/stats_collector.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/contracts/utils.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/exceptions.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/fixtures.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/helpers/__init__.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/helpers/fixture_snapshot.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/helpers/take_snapshot.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/logging.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/types.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/utils.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/validators/__init__.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest/validators/validator_factory.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest_cli/config/__init__.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest_cli/config/constants.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest_cli/config/general.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest_cli/config/plugin.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest_cli/config/pytest_context.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest_cli/config/types.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest_cli/config/user.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest_cli/logging.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/gltest_cli/main.py +0 -0
- {genlayer_test-0.11.0 → genlayer_test-0.12.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: genlayer-test
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: GenLayer Testing Suite
|
|
5
5
|
Author: GenLayer
|
|
6
6
|
License-Expression: MIT
|
|
@@ -312,6 +312,72 @@ The chain type determines various behaviors including RPC endpoints, consensus m
|
|
|
312
312
|
- **Prompt Testing & Statistical Analysis** – Evaluate and statistically test prompts for AI-driven contract execution.
|
|
313
313
|
- **Scalability to Security & Audit Tools** – Designed to extend into security testing and smart contract auditing.
|
|
314
314
|
- **Custom Transaction Context** – Set custom validators with specific LLM providers and models, and configure GenVM datetime for deterministic testing scenarios.
|
|
315
|
+
- **Direct Execution Mode** – Run contracts directly in Python for ultra-fast unit testing (~ms vs minutes).
|
|
316
|
+
|
|
317
|
+
## ⚡ Direct vs Simulator Mode
|
|
318
|
+
|
|
319
|
+
The testing suite provides two execution modes:
|
|
320
|
+
|
|
321
|
+
| Mode | How it works | Speed | Use case |
|
|
322
|
+
|------|--------------|-------|----------|
|
|
323
|
+
| **Simulator** | Deploy to GenLayer simulator, interact via RPC | ~minutes | Integration tests, consensus validation |
|
|
324
|
+
| **Direct** | Run Python code directly in-memory | ~milliseconds | Unit tests, rapid development |
|
|
325
|
+
|
|
326
|
+
### Quick Start with Direct Mode
|
|
327
|
+
|
|
328
|
+
```python
|
|
329
|
+
def test_token_transfer(direct_vm, direct_deploy):
|
|
330
|
+
# Deploy contract directly in Python (no simulator)
|
|
331
|
+
token = direct_deploy("contracts/Token.py", initial_supply=1000)
|
|
332
|
+
|
|
333
|
+
# Create test addresses
|
|
334
|
+
from gltest.direct import create_address
|
|
335
|
+
alice = create_address("alice")
|
|
336
|
+
bob = create_address("bob")
|
|
337
|
+
|
|
338
|
+
# Set sender and interact
|
|
339
|
+
direct_vm.sender = alice
|
|
340
|
+
token.mint(alice, 500)
|
|
341
|
+
token.transfer(bob, 100)
|
|
342
|
+
|
|
343
|
+
assert token.balances[bob] == 100
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Available Fixtures
|
|
347
|
+
|
|
348
|
+
| Fixture | Description |
|
|
349
|
+
|---------|-------------|
|
|
350
|
+
| `direct_vm` | VM context with cheatcodes |
|
|
351
|
+
| `direct_deploy` | Deploy contracts directly |
|
|
352
|
+
| `direct_alice`, `direct_bob`, `direct_charlie` | Test addresses |
|
|
353
|
+
| `direct_owner` | Default sender address |
|
|
354
|
+
| `direct_accounts` | List of 10 test addresses |
|
|
355
|
+
|
|
356
|
+
### Cheatcodes
|
|
357
|
+
|
|
358
|
+
```python
|
|
359
|
+
# Change sender
|
|
360
|
+
direct_vm.sender = alice
|
|
361
|
+
|
|
362
|
+
# Prank (temporary sender change)
|
|
363
|
+
with direct_vm.prank(bob):
|
|
364
|
+
contract.method() # Called as bob
|
|
365
|
+
|
|
366
|
+
# Snapshots
|
|
367
|
+
snap_id = direct_vm.snapshot()
|
|
368
|
+
contract.modify_state()
|
|
369
|
+
direct_vm.revert(snap_id) # State restored
|
|
370
|
+
|
|
371
|
+
# Expect revert
|
|
372
|
+
with direct_vm.expect_revert("Insufficient balance"):
|
|
373
|
+
contract.transfer(bob, 1000000)
|
|
374
|
+
|
|
375
|
+
# Mock web/LLM (for nondet operations)
|
|
376
|
+
direct_vm.mock_web(r"api\.example\.com", {"status": 200, "body": "{}"})
|
|
377
|
+
direct_vm.mock_llm(r"analyze.*", "positive sentiment")
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
📖 **[Full Direct Mode Documentation](docs/direct-runner.md)**
|
|
315
381
|
|
|
316
382
|
## 📚 Examples
|
|
317
383
|
|
|
@@ -289,6 +289,72 @@ The chain type determines various behaviors including RPC endpoints, consensus m
|
|
|
289
289
|
- **Prompt Testing & Statistical Analysis** – Evaluate and statistically test prompts for AI-driven contract execution.
|
|
290
290
|
- **Scalability to Security & Audit Tools** – Designed to extend into security testing and smart contract auditing.
|
|
291
291
|
- **Custom Transaction Context** – Set custom validators with specific LLM providers and models, and configure GenVM datetime for deterministic testing scenarios.
|
|
292
|
+
- **Direct Execution Mode** – Run contracts directly in Python for ultra-fast unit testing (~ms vs minutes).
|
|
293
|
+
|
|
294
|
+
## ⚡ Direct vs Simulator Mode
|
|
295
|
+
|
|
296
|
+
The testing suite provides two execution modes:
|
|
297
|
+
|
|
298
|
+
| Mode | How it works | Speed | Use case |
|
|
299
|
+
|------|--------------|-------|----------|
|
|
300
|
+
| **Simulator** | Deploy to GenLayer simulator, interact via RPC | ~minutes | Integration tests, consensus validation |
|
|
301
|
+
| **Direct** | Run Python code directly in-memory | ~milliseconds | Unit tests, rapid development |
|
|
302
|
+
|
|
303
|
+
### Quick Start with Direct Mode
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
def test_token_transfer(direct_vm, direct_deploy):
|
|
307
|
+
# Deploy contract directly in Python (no simulator)
|
|
308
|
+
token = direct_deploy("contracts/Token.py", initial_supply=1000)
|
|
309
|
+
|
|
310
|
+
# Create test addresses
|
|
311
|
+
from gltest.direct import create_address
|
|
312
|
+
alice = create_address("alice")
|
|
313
|
+
bob = create_address("bob")
|
|
314
|
+
|
|
315
|
+
# Set sender and interact
|
|
316
|
+
direct_vm.sender = alice
|
|
317
|
+
token.mint(alice, 500)
|
|
318
|
+
token.transfer(bob, 100)
|
|
319
|
+
|
|
320
|
+
assert token.balances[bob] == 100
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Available Fixtures
|
|
324
|
+
|
|
325
|
+
| Fixture | Description |
|
|
326
|
+
|---------|-------------|
|
|
327
|
+
| `direct_vm` | VM context with cheatcodes |
|
|
328
|
+
| `direct_deploy` | Deploy contracts directly |
|
|
329
|
+
| `direct_alice`, `direct_bob`, `direct_charlie` | Test addresses |
|
|
330
|
+
| `direct_owner` | Default sender address |
|
|
331
|
+
| `direct_accounts` | List of 10 test addresses |
|
|
332
|
+
|
|
333
|
+
### Cheatcodes
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
# Change sender
|
|
337
|
+
direct_vm.sender = alice
|
|
338
|
+
|
|
339
|
+
# Prank (temporary sender change)
|
|
340
|
+
with direct_vm.prank(bob):
|
|
341
|
+
contract.method() # Called as bob
|
|
342
|
+
|
|
343
|
+
# Snapshots
|
|
344
|
+
snap_id = direct_vm.snapshot()
|
|
345
|
+
contract.modify_state()
|
|
346
|
+
direct_vm.revert(snap_id) # State restored
|
|
347
|
+
|
|
348
|
+
# Expect revert
|
|
349
|
+
with direct_vm.expect_revert("Insufficient balance"):
|
|
350
|
+
contract.transfer(bob, 1000000)
|
|
351
|
+
|
|
352
|
+
# Mock web/LLM (for nondet operations)
|
|
353
|
+
direct_vm.mock_web(r"api\.example\.com", {"status": 200, "body": "{}"})
|
|
354
|
+
direct_vm.mock_llm(r"analyze.*", "positive sentiment")
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
📖 **[Full Direct Mode Documentation](docs/direct-runner.md)**
|
|
292
358
|
|
|
293
359
|
## 📚 Examples
|
|
294
360
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: genlayer-test
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: GenLayer Testing Suite
|
|
5
5
|
Author: GenLayer
|
|
6
6
|
License-Expression: MIT
|
|
@@ -312,6 +312,72 @@ The chain type determines various behaviors including RPC endpoints, consensus m
|
|
|
312
312
|
- **Prompt Testing & Statistical Analysis** – Evaluate and statistically test prompts for AI-driven contract execution.
|
|
313
313
|
- **Scalability to Security & Audit Tools** – Designed to extend into security testing and smart contract auditing.
|
|
314
314
|
- **Custom Transaction Context** – Set custom validators with specific LLM providers and models, and configure GenVM datetime for deterministic testing scenarios.
|
|
315
|
+
- **Direct Execution Mode** – Run contracts directly in Python for ultra-fast unit testing (~ms vs minutes).
|
|
316
|
+
|
|
317
|
+
## ⚡ Direct vs Simulator Mode
|
|
318
|
+
|
|
319
|
+
The testing suite provides two execution modes:
|
|
320
|
+
|
|
321
|
+
| Mode | How it works | Speed | Use case |
|
|
322
|
+
|------|--------------|-------|----------|
|
|
323
|
+
| **Simulator** | Deploy to GenLayer simulator, interact via RPC | ~minutes | Integration tests, consensus validation |
|
|
324
|
+
| **Direct** | Run Python code directly in-memory | ~milliseconds | Unit tests, rapid development |
|
|
325
|
+
|
|
326
|
+
### Quick Start with Direct Mode
|
|
327
|
+
|
|
328
|
+
```python
|
|
329
|
+
def test_token_transfer(direct_vm, direct_deploy):
|
|
330
|
+
# Deploy contract directly in Python (no simulator)
|
|
331
|
+
token = direct_deploy("contracts/Token.py", initial_supply=1000)
|
|
332
|
+
|
|
333
|
+
# Create test addresses
|
|
334
|
+
from gltest.direct import create_address
|
|
335
|
+
alice = create_address("alice")
|
|
336
|
+
bob = create_address("bob")
|
|
337
|
+
|
|
338
|
+
# Set sender and interact
|
|
339
|
+
direct_vm.sender = alice
|
|
340
|
+
token.mint(alice, 500)
|
|
341
|
+
token.transfer(bob, 100)
|
|
342
|
+
|
|
343
|
+
assert token.balances[bob] == 100
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Available Fixtures
|
|
347
|
+
|
|
348
|
+
| Fixture | Description |
|
|
349
|
+
|---------|-------------|
|
|
350
|
+
| `direct_vm` | VM context with cheatcodes |
|
|
351
|
+
| `direct_deploy` | Deploy contracts directly |
|
|
352
|
+
| `direct_alice`, `direct_bob`, `direct_charlie` | Test addresses |
|
|
353
|
+
| `direct_owner` | Default sender address |
|
|
354
|
+
| `direct_accounts` | List of 10 test addresses |
|
|
355
|
+
|
|
356
|
+
### Cheatcodes
|
|
357
|
+
|
|
358
|
+
```python
|
|
359
|
+
# Change sender
|
|
360
|
+
direct_vm.sender = alice
|
|
361
|
+
|
|
362
|
+
# Prank (temporary sender change)
|
|
363
|
+
with direct_vm.prank(bob):
|
|
364
|
+
contract.method() # Called as bob
|
|
365
|
+
|
|
366
|
+
# Snapshots
|
|
367
|
+
snap_id = direct_vm.snapshot()
|
|
368
|
+
contract.modify_state()
|
|
369
|
+
direct_vm.revert(snap_id) # State restored
|
|
370
|
+
|
|
371
|
+
# Expect revert
|
|
372
|
+
with direct_vm.expect_revert("Insufficient balance"):
|
|
373
|
+
contract.transfer(bob, 1000000)
|
|
374
|
+
|
|
375
|
+
# Mock web/LLM (for nondet operations)
|
|
376
|
+
direct_vm.mock_web(r"api\.example\.com", {"status": 200, "body": "{}"})
|
|
377
|
+
direct_vm.mock_llm(r"analyze.*", "positive sentiment")
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
📖 **[Full Direct Mode Documentation](docs/direct-runner.md)**
|
|
315
381
|
|
|
316
382
|
## 📚 Examples
|
|
317
383
|
|
|
@@ -25,6 +25,13 @@ gltest/contracts/contract_functions.py
|
|
|
25
25
|
gltest/contracts/method_stats.py
|
|
26
26
|
gltest/contracts/stats_collector.py
|
|
27
27
|
gltest/contracts/utils.py
|
|
28
|
+
gltest/direct/__init__.py
|
|
29
|
+
gltest/direct/loader.py
|
|
30
|
+
gltest/direct/pytest_plugin.py
|
|
31
|
+
gltest/direct/sdk_loader.py
|
|
32
|
+
gltest/direct/types.py
|
|
33
|
+
gltest/direct/vm.py
|
|
34
|
+
gltest/direct/wasi_mock.py
|
|
28
35
|
gltest/helpers/__init__.py
|
|
29
36
|
gltest/helpers/fixture_snapshot.py
|
|
30
37
|
gltest/helpers/take_snapshot.py
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Native Python test runner for GenLayer intelligent contracts.
|
|
3
|
+
|
|
4
|
+
Runs contracts directly in Python without WASM/simulator, providing
|
|
5
|
+
Foundry-style cheatcodes and fast test execution.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from gltest.direct import VMContext, deploy_contract
|
|
9
|
+
|
|
10
|
+
def test_transfer():
|
|
11
|
+
vm = VMContext()
|
|
12
|
+
with vm.activate():
|
|
13
|
+
token = deploy_contract("path/to/Token.py", vm, owner)
|
|
14
|
+
vm.sender = alice
|
|
15
|
+
token.transfer(bob, 100)
|
|
16
|
+
assert token.balances[bob] == 100
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from .vm import VMContext
|
|
20
|
+
from .loader import deploy_contract, load_contract_class, create_address, create_test_addresses
|
|
21
|
+
from .wasi_mock import ContractRollback, MockNotFoundError
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"VMContext",
|
|
25
|
+
"deploy_contract",
|
|
26
|
+
"load_contract_class",
|
|
27
|
+
"create_address",
|
|
28
|
+
"create_test_addresses",
|
|
29
|
+
"ContractRollback",
|
|
30
|
+
"MockNotFoundError",
|
|
31
|
+
]
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Contract loader for direct test runner.
|
|
3
|
+
|
|
4
|
+
Handles:
|
|
5
|
+
- SDK version setup based on contract headers
|
|
6
|
+
- Message context injection via stdin
|
|
7
|
+
- WASI mock installation
|
|
8
|
+
- Contract class discovery and instantiation
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
import hashlib
|
|
15
|
+
import importlib.util
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Optional, Type, TYPE_CHECKING
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from .vm import VMContext
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def load_contract_class(
|
|
24
|
+
contract_path: Path,
|
|
25
|
+
vm: "VMContext",
|
|
26
|
+
sdk_version: Optional[str] = None,
|
|
27
|
+
) -> Type[Any]:
|
|
28
|
+
"""
|
|
29
|
+
Load a contract class from file.
|
|
30
|
+
|
|
31
|
+
Sets up SDK paths, WASI mock, and message context.
|
|
32
|
+
"""
|
|
33
|
+
contract_path = Path(contract_path).resolve()
|
|
34
|
+
|
|
35
|
+
if not contract_path.exists():
|
|
36
|
+
raise FileNotFoundError(f"Contract not found: {contract_path}")
|
|
37
|
+
|
|
38
|
+
# 1. Setup WASI mock FIRST (before genlayer is imported)
|
|
39
|
+
from . import wasi_mock
|
|
40
|
+
wasi_mock.set_vm(vm)
|
|
41
|
+
sys.modules['_genlayer_wasi'] = wasi_mock
|
|
42
|
+
|
|
43
|
+
# 2. Setup SDK paths (this adds genlayer to sys.path)
|
|
44
|
+
from .sdk_loader import setup_sdk_paths
|
|
45
|
+
setup_sdk_paths(contract_path, sdk_version)
|
|
46
|
+
|
|
47
|
+
# 3. Inject message context into fd 0 BEFORE importing contract
|
|
48
|
+
# (genlayer reads message from fd 0 at import time)
|
|
49
|
+
_inject_message_to_fd0(vm)
|
|
50
|
+
|
|
51
|
+
# 4. Load the contract module
|
|
52
|
+
module = _load_module(contract_path)
|
|
53
|
+
|
|
54
|
+
contract_cls = _find_contract_class(module)
|
|
55
|
+
|
|
56
|
+
if contract_cls is None:
|
|
57
|
+
raise ValueError(f"No contract class found in {contract_path}")
|
|
58
|
+
|
|
59
|
+
return contract_cls
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def deploy_contract(
|
|
63
|
+
contract_path: Path,
|
|
64
|
+
vm: "VMContext",
|
|
65
|
+
*args: Any,
|
|
66
|
+
sdk_version: Optional[str] = None,
|
|
67
|
+
**kwargs: Any,
|
|
68
|
+
) -> Any:
|
|
69
|
+
"""Deploy a contract and return an instance."""
|
|
70
|
+
contract_path = Path(contract_path).resolve()
|
|
71
|
+
|
|
72
|
+
if vm._contract_address is None:
|
|
73
|
+
addr_hash = hashlib.sha256(str(contract_path).encode()).digest()[:20]
|
|
74
|
+
vm._contract_address = addr_hash
|
|
75
|
+
|
|
76
|
+
contract_cls = load_contract_class(contract_path, vm, sdk_version)
|
|
77
|
+
|
|
78
|
+
instance = _allocate_contract(contract_cls, vm, *args, **kwargs)
|
|
79
|
+
|
|
80
|
+
return instance
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _inject_message_to_fd0(vm: "VMContext") -> None:
|
|
84
|
+
"""Inject message context by replacing stdin (fd 0) with encoded message."""
|
|
85
|
+
import os
|
|
86
|
+
import tempfile
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
from genlayer.py import calldata
|
|
90
|
+
from genlayer.py.types import Address
|
|
91
|
+
except ImportError:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
# Convert addresses to Address type
|
|
95
|
+
sender_addr = vm.sender
|
|
96
|
+
if isinstance(sender_addr, bytes):
|
|
97
|
+
sender_addr = Address(sender_addr)
|
|
98
|
+
|
|
99
|
+
contract_addr = vm._contract_address
|
|
100
|
+
if isinstance(contract_addr, bytes):
|
|
101
|
+
contract_addr = Address(contract_addr)
|
|
102
|
+
|
|
103
|
+
origin_addr = vm.origin
|
|
104
|
+
if isinstance(origin_addr, bytes):
|
|
105
|
+
origin_addr = Address(origin_addr)
|
|
106
|
+
|
|
107
|
+
# Build message dict
|
|
108
|
+
message_data = {
|
|
109
|
+
'contract_address': contract_addr,
|
|
110
|
+
'sender_address': sender_addr,
|
|
111
|
+
'origin_address': origin_addr,
|
|
112
|
+
'stack': [],
|
|
113
|
+
'value': vm._value,
|
|
114
|
+
'datetime': vm._datetime,
|
|
115
|
+
'is_init': False,
|
|
116
|
+
'chain_id': vm._chain_id,
|
|
117
|
+
'entry_kind': 0,
|
|
118
|
+
'entry_data': b'',
|
|
119
|
+
'entry_stage_data': None,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# Encode the message
|
|
123
|
+
encoded = calldata.encode(message_data)
|
|
124
|
+
|
|
125
|
+
# Create a temp file with the encoded message
|
|
126
|
+
fd, path = tempfile.mkstemp()
|
|
127
|
+
try:
|
|
128
|
+
os.write(fd, encoded)
|
|
129
|
+
os.lseek(fd, 0, os.SEEK_SET) # Reset to beginning
|
|
130
|
+
|
|
131
|
+
# Save original stdin fd
|
|
132
|
+
original_stdin = os.dup(0)
|
|
133
|
+
vm._original_stdin_fd = original_stdin
|
|
134
|
+
|
|
135
|
+
# Replace stdin with our temp file
|
|
136
|
+
os.dup2(fd, 0)
|
|
137
|
+
finally:
|
|
138
|
+
os.close(fd)
|
|
139
|
+
os.unlink(path)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _load_module(contract_path: Path) -> Any:
|
|
143
|
+
"""Load a Python module from file path."""
|
|
144
|
+
module_name = f"_contract_{contract_path.stem}"
|
|
145
|
+
|
|
146
|
+
if module_name in sys.modules:
|
|
147
|
+
del sys.modules[module_name]
|
|
148
|
+
|
|
149
|
+
spec = importlib.util.spec_from_file_location(module_name, contract_path)
|
|
150
|
+
if spec is None or spec.loader is None:
|
|
151
|
+
raise ImportError(f"Cannot load module from {contract_path}")
|
|
152
|
+
|
|
153
|
+
module = importlib.util.module_from_spec(spec)
|
|
154
|
+
sys.modules[module_name] = module
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
spec.loader.exec_module(module)
|
|
158
|
+
except Exception as e:
|
|
159
|
+
if module_name in sys.modules:
|
|
160
|
+
del sys.modules[module_name]
|
|
161
|
+
raise ImportError(f"Failed to load contract: {e}") from e
|
|
162
|
+
|
|
163
|
+
return module
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _find_contract_class(module: Any) -> Optional[Type[Any]]:
|
|
167
|
+
"""Find the contract class in a module."""
|
|
168
|
+
import dataclasses
|
|
169
|
+
|
|
170
|
+
candidates = []
|
|
171
|
+
|
|
172
|
+
for name in dir(module):
|
|
173
|
+
if name.startswith('_'):
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
obj = getattr(module, name)
|
|
177
|
+
|
|
178
|
+
if not isinstance(obj, type):
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
# Skip dataclasses - they're storage types, not contracts
|
|
182
|
+
if dataclasses.is_dataclass(obj):
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
# Highest priority: explicit __gl_contract__ marker
|
|
186
|
+
if getattr(obj, '__gl_contract__', False):
|
|
187
|
+
return obj
|
|
188
|
+
|
|
189
|
+
# Second priority: inherits from Contract
|
|
190
|
+
for base in obj.__mro__:
|
|
191
|
+
if base.__name__ in ('Contract', 'gl.Contract'):
|
|
192
|
+
return obj
|
|
193
|
+
|
|
194
|
+
# Third priority: has storage-like annotations
|
|
195
|
+
# Collect as candidates but don't return immediately
|
|
196
|
+
if hasattr(obj, '__annotations__'):
|
|
197
|
+
annotations = obj.__annotations__
|
|
198
|
+
storage_types = ('TreeMap', 'DynArray', 'Array', 'u256', 'Address')
|
|
199
|
+
for ann in annotations.values():
|
|
200
|
+
ann_str = str(ann)
|
|
201
|
+
if any(st in ann_str for st in storage_types):
|
|
202
|
+
candidates.append(obj)
|
|
203
|
+
break
|
|
204
|
+
|
|
205
|
+
# Return first candidate if no explicit contract found
|
|
206
|
+
if candidates:
|
|
207
|
+
return candidates[0]
|
|
208
|
+
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _allocate_contract(
|
|
213
|
+
contract_cls: Type[Any],
|
|
214
|
+
vm: "VMContext",
|
|
215
|
+
*args: Any,
|
|
216
|
+
**kwargs: Any,
|
|
217
|
+
) -> Any:
|
|
218
|
+
"""Allocate and initialize a contract instance."""
|
|
219
|
+
try:
|
|
220
|
+
from genlayer.py.storage import Root, ROOT_SLOT_ID
|
|
221
|
+
from genlayer.py.storage._internal.generate import (
|
|
222
|
+
ORIGINAL_INIT_ATTR,
|
|
223
|
+
_storage_build,
|
|
224
|
+
Lit,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Build the storage type descriptor
|
|
228
|
+
td = _storage_build(contract_cls, {})
|
|
229
|
+
assert not isinstance(td, Lit)
|
|
230
|
+
|
|
231
|
+
# Use the VM's storage manager
|
|
232
|
+
slot = vm._storage.get_store_slot(ROOT_SLOT_ID)
|
|
233
|
+
instance = td.get(slot, 0)
|
|
234
|
+
|
|
235
|
+
# Find and call the original __init__
|
|
236
|
+
init = getattr(td, 'cls', None)
|
|
237
|
+
if init is None:
|
|
238
|
+
init = getattr(contract_cls, '__init__', None)
|
|
239
|
+
else:
|
|
240
|
+
init = getattr(init, '__init__', None)
|
|
241
|
+
if init is not None:
|
|
242
|
+
if hasattr(init, ORIGINAL_INIT_ATTR):
|
|
243
|
+
init = getattr(init, ORIGINAL_INIT_ATTR)
|
|
244
|
+
init(instance, *args, **kwargs)
|
|
245
|
+
|
|
246
|
+
return instance
|
|
247
|
+
|
|
248
|
+
except ImportError:
|
|
249
|
+
pass
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
from genlayer.py.storage import Root
|
|
253
|
+
|
|
254
|
+
Root.MANAGER = vm._storage
|
|
255
|
+
|
|
256
|
+
root_slot = vm._storage.get_store_slot(b'\x00' * 32)
|
|
257
|
+
|
|
258
|
+
instance = contract_cls.__new__(contract_cls)
|
|
259
|
+
|
|
260
|
+
if hasattr(instance, '_storage_slot'):
|
|
261
|
+
instance._storage_slot = root_slot.indirect(0)
|
|
262
|
+
instance._off = 0
|
|
263
|
+
|
|
264
|
+
if args or kwargs:
|
|
265
|
+
instance.__init__(*args, **kwargs)
|
|
266
|
+
|
|
267
|
+
return instance
|
|
268
|
+
|
|
269
|
+
except ImportError:
|
|
270
|
+
pass
|
|
271
|
+
|
|
272
|
+
return contract_cls(*args, **kwargs)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def create_address(seed: str) -> Any:
|
|
276
|
+
"""Create a deterministic address from seed string."""
|
|
277
|
+
addr_bytes = hashlib.sha256(seed.encode()).digest()[:20]
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
from genlayer.py.types import Address
|
|
281
|
+
return Address(addr_bytes)
|
|
282
|
+
except ImportError:
|
|
283
|
+
return addr_bytes
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def create_test_addresses(count: int = 10) -> list:
|
|
287
|
+
"""Create a list of test addresses."""
|
|
288
|
+
return [create_address(f"test_address_{i}") for i in range(count)]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pytest plugin for direct GenLayer contract testing.
|
|
3
|
+
|
|
4
|
+
Provides fixtures:
|
|
5
|
+
- direct_vm: VMContext for Foundry-style cheatcodes
|
|
6
|
+
- direct_deploy: Factory for deploying contracts
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
def test_transfer(direct_vm, direct_deploy):
|
|
10
|
+
token = direct_deploy("contracts/Token.py")
|
|
11
|
+
direct_vm.sender = alice
|
|
12
|
+
token.transfer(bob, 100)
|
|
13
|
+
assert token.balances[bob] == 100
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, Optional, Callable
|
|
21
|
+
|
|
22
|
+
from .vm import VMContext
|
|
23
|
+
from .loader import deploy_contract, create_address, create_test_addresses
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def direct_vm() -> VMContext:
|
|
28
|
+
"""
|
|
29
|
+
Provides a fresh VMContext for each test.
|
|
30
|
+
The VM is automatically activated for the test scope.
|
|
31
|
+
"""
|
|
32
|
+
ctx = VMContext()
|
|
33
|
+
ctx.sender = create_address("default_sender")
|
|
34
|
+
|
|
35
|
+
with ctx.activate():
|
|
36
|
+
yield ctx
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.fixture
|
|
40
|
+
def direct_deploy(direct_vm: VMContext) -> Callable[..., Any]:
|
|
41
|
+
"""
|
|
42
|
+
Factory fixture for deploying contracts directly.
|
|
43
|
+
|
|
44
|
+
Usage:
|
|
45
|
+
def test_example(direct_deploy):
|
|
46
|
+
token = direct_deploy("path/to/Token.py", initial_supply=1000)
|
|
47
|
+
"""
|
|
48
|
+
def _deploy(
|
|
49
|
+
contract_path: str,
|
|
50
|
+
*args: Any,
|
|
51
|
+
sdk_version: Optional[str] = None,
|
|
52
|
+
**kwargs: Any,
|
|
53
|
+
) -> Any:
|
|
54
|
+
path = Path(contract_path)
|
|
55
|
+
|
|
56
|
+
if not path.is_absolute():
|
|
57
|
+
if path.exists():
|
|
58
|
+
path = path.resolve()
|
|
59
|
+
else:
|
|
60
|
+
for base in [
|
|
61
|
+
Path.cwd(),
|
|
62
|
+
Path.cwd() / "contracts",
|
|
63
|
+
Path.cwd() / "intelligent-contracts",
|
|
64
|
+
]:
|
|
65
|
+
candidate = base / contract_path
|
|
66
|
+
if candidate.exists():
|
|
67
|
+
path = candidate.resolve()
|
|
68
|
+
break
|
|
69
|
+
|
|
70
|
+
return deploy_contract(path, direct_vm, *args, sdk_version=sdk_version, **kwargs)
|
|
71
|
+
|
|
72
|
+
return _deploy
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.fixture
|
|
76
|
+
def direct_alice() -> Any:
|
|
77
|
+
"""Test address: Alice."""
|
|
78
|
+
return create_address("alice")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.fixture
|
|
82
|
+
def direct_bob() -> Any:
|
|
83
|
+
"""Test address: Bob."""
|
|
84
|
+
return create_address("bob")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.fixture
|
|
88
|
+
def direct_charlie() -> Any:
|
|
89
|
+
"""Test address: Charlie."""
|
|
90
|
+
return create_address("charlie")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@pytest.fixture
|
|
94
|
+
def direct_owner() -> Any:
|
|
95
|
+
"""Test address: Owner (default sender)."""
|
|
96
|
+
return create_address("default_sender")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.fixture
|
|
100
|
+
def direct_accounts() -> list:
|
|
101
|
+
"""List of 10 test addresses."""
|
|
102
|
+
return create_test_addresses(10)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def pytest_configure(config):
|
|
106
|
+
"""Register markers for direct tests."""
|
|
107
|
+
config.addinivalue_line(
|
|
108
|
+
"markers",
|
|
109
|
+
"direct: mark test as using direct contract execution (no simulator)",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def pytest_collection_modifyitems(config, items):
|
|
114
|
+
"""Auto-mark tests using direct fixtures."""
|
|
115
|
+
for item in items:
|
|
116
|
+
if 'direct_vm' in item.fixturenames or 'direct_deploy' in item.fixturenames:
|
|
117
|
+
item.add_marker(pytest.mark.direct)
|