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
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ print(stats) # Shows success rate, reliability score, execution time, etc.
### Network Configuration

Networks are configured in `gltest.config.yaml`:
- `localnet`: Local development (chain ID 61999)
- `localnet`: Local development (chain ID 61127)
- `studio_devnet`: Hosted Studio preview (chain ID 61997)
- `testnet_asimov`: Public testnet (chain ID 4221)

Paths can be configured in `gltest.config.yaml`:
Expand Down
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ Run with the `gltest` CLI:
```bash
gltest # Run all tests
gltest tests/test_mycontract.py # Specific file
gltest --network studio_devnet # Hosted release preview
gltest --network studionet # Specific network
gltest --leader-only # Skip consensus (faster)
gltest -v # Verbose output
Expand All @@ -152,6 +153,9 @@ networks:
url: "http://127.0.0.1:4000/api"
leader_only: false

studio_devnet:
# Pre-configured release preview — accounts auto-generated

studionet:
# Pre-configured — accounts auto-generated

Expand All @@ -169,7 +173,7 @@ environment: .env
```

Key options:
- **Networks**: `localnet` and `studionet` work out of the box. `testnet_asimov` requires account keys.
- **Networks**: `localnet`, `studio_devnet`, and `studionet` work out of the box. `testnet_asimov` requires account keys.
- **Paths**: Where your contracts and artifacts live.
- **Environment**: `.env` file for private keys.

Expand Down Expand Up @@ -241,6 +245,7 @@ than a consumed fee amount.
{
"version": 1,
"network": "localnet",
"chainId": 61127,
"measuredAt": "2026-06-10T12:00:00Z",
"deploy": {
"leaderTimeunitsAllocation": "125",
Expand All @@ -266,7 +271,9 @@ include consumed fee data. Testnet receipts do not expose consumed fees yet, and
direct/sim mode does not go through these receipt paths. Time-unit allocations
are recorded from the submitted fee distribution when the backend receipt
includes it. Live price caps and `feeValue` are intentionally omitted so the SDK
can quote them from the current network policy at transaction time.
can quote them from the current network policy at transaction time. The numeric
`chainId` scopes the measurements to the exact selected runtime chain; consumers
must fall back to network defaults when it is absent or does not match.

### Assertions

Expand Down
6 changes: 5 additions & 1 deletion docs/api-references/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ Run with the `gltest` CLI:
```bash
gltest # Run all tests
gltest tests/test_mycontract.py # Specific file
gltest --network studio_devnet # Hosted release preview
gltest --network studionet # Specific network
gltest --leader-only # Skip consensus (faster)
gltest -v # Verbose output
Expand All @@ -147,6 +148,9 @@ networks:
url: "http://127.0.0.1:4000/api"
leader_only: false

studio_devnet:
# Pre-configured release preview — accounts auto-generated

studionet:
# Pre-configured — accounts auto-generated

Expand All @@ -164,7 +168,7 @@ environment: .env
```

Key options:
- **Networks**: `localnet` and `studionet` work out of the box. `testnet_asimov` requires account keys.
- **Networks**: `localnet`, `studio_devnet`, and `studionet` work out of the box. `testnet_asimov` requires account keys.
- **Paths**: Where your contracts and artifacts live.
- **Environment**: `.env` file for private keys.

Expand Down
13 changes: 9 additions & 4 deletions docs/studio-runner.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Deploy and test intelligent contracts against a running GenLayer Studio instance
Studio mode deploys contracts to GenLayer Studio (or a remote network) and interacts through JSON-RPC. Every write operation goes through the full consensus pipeline.

- **Fidelity**: Real consensus, real validators, real transaction lifecycle
- **Networks**: localnet (Docker), studionet, testnet_asimov, or custom
- **Networks**: localnet (Docker), Studio Devnet, studionet, public testnets, or custom
- **Mocking**: Mock validators with predefined LLM/web responses for deterministic tests

### When to Use Studio Mode
Expand Down Expand Up @@ -74,6 +74,9 @@ networks:
url: "http://127.0.0.1:4000/api"
leader_only: false

studio_devnet:
# Pre-configured release preview — accounts auto-generated

studionet:
# Pre-configured — accounts auto-generated
# Override any settings if needed
Expand Down Expand Up @@ -107,8 +110,9 @@ environment: .env # File containing private keys and secrets

| Network | Accounts | Notes |
|---------|----------|-------|
| `localnet` | Auto-generated | Local Docker deployment |
| `studionet` | Auto-generated | GenLayer Studio cloud |
| `localnet` | Auto-generated | Local Docker deployment (chain 61127) |
| `studio_devnet` | Auto-generated | Hosted release preview (chain 61997) |
| `studionet` | Auto-generated | Stable GenLayer Studio cloud (chain 61999) |
| `testnet_asimov` | Must be configured | Public testnet |

**Network configuration fields:**
Expand All @@ -117,7 +121,7 @@ environment: .env # File containing private keys and secrets
|-------|----------|-------------|
| `url` | For custom networks | RPC endpoint |
| `id` | For custom networks | Chain ID |
| `chain_type` | For custom networks | One of: `localnet`, `studionet`, `testnet_asimov` |
| `chain_type` | For custom networks | One of the pre-configured network types above |
| `accounts` | For testnet/custom | List of private keys (use env vars) |
| `from` | No | Which account to use as default (defaults to first) |
| `leader_only` | No | Skip consensus for all operations |
Expand Down Expand Up @@ -163,6 +167,7 @@ gltest -v # Verbose output

```bash
gltest --network localnet # Specific network (from config)
gltest --network studio_devnet
gltest --network studionet
gltest --network testnet_asimov
gltest --rpc-url http://custom:4000/api # Custom RPC URL
Expand Down
18 changes: 15 additions & 3 deletions gltest/fees/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,16 @@ def record_method(self, method_name: str, receipt: Dict[str, Any]) -> None:
method_values = self._methods.setdefault(method_name, {})
self._record_max(method_values, observation)

def build_profile(self, network: str, headroom: float) -> Dict[str, Any]:
def build_profile(
self, network: str, headroom: float, chain_id: Optional[int] = None
) -> Dict[str, Any]:
profile: Dict[str, Any] = {
"version": 1,
"network": network,
"measuredAt": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
}
if chain_id is not None:
profile["chainId"] = chain_id
if self._deploy:
profile["deploy"] = self._apply_headroom(self._deploy, headroom)
profile["methods"] = {
Expand All @@ -56,8 +60,16 @@ def build_profile(self, network: str, headroom: float) -> Dict[str, Any]:
}
return profile

def write(self, path: Path, network: str, headroom: float) -> Dict[str, Any]:
profile = self.build_profile(network=network, headroom=headroom)
def write(
self,
path: Path,
network: str,
headroom: float,
chain_id: Optional[int] = None,
) -> Dict[str, Any]:
profile = self.build_profile(
network=network, headroom=headroom, chain_id=chain_id
)
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(profile, indent=2) + "\n", encoding="utf-8")
Expand Down
10 changes: 8 additions & 2 deletions gltest_cli/config/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@

GLTEST_CONFIG_FILE = "gltest.config.yaml"
DEFAULT_NETWORK = "localnet"
PRECONFIGURED_NETWORKS = ["localnet", "studionet", "testnet_asimov", "testnet_bradbury"]
CHAINS = ["localnet", "studionet", "testnet_asimov", "testnet_bradbury"]
PRECONFIGURED_NETWORKS = [
"localnet",
"studio_devnet",
"studionet",
"testnet_asimov",
"testnet_bradbury",
]
CHAINS = PRECONFIGURED_NETWORKS.copy()
DEFAULT_RPC_URL = SIMULATOR_JSON_RPC_URL
DEFAULT_ENVIRONMENT = ".env"
DEFAULT_CONTRACTS_DIR = Path("contracts")
Expand Down
1 change: 1 addition & 0 deletions gltest_cli/config/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ def pytest_sessionfinish(session, exitstatus):
path=fee_profile_path,
network=general_config.get_network_name(),
headroom=general_config.get_fee_profile_headroom(),
chain_id=general_config.get_chain().id,
)
logger.info(f"Wrote fee profile to {fee_profile_path}")
if not collector.has_observations():
Expand Down
40 changes: 37 additions & 3 deletions gltest_cli/config/types.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
from copy import deepcopy
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, List, Optional
from genlayer_py.chains import localnet, studionet, testnet_asimov, testnet_bradbury
from genlayer_py.chains import (
localnet,
studio_devnet,
studionet,
testnet_asimov,
testnet_bradbury,
)
from genlayer_py.types import GenLayerChain
from gltest_cli.config.constants import PRECONFIGURED_NETWORKS
from gltest_cli.config.constants import (
Expand Down Expand Up @@ -190,12 +197,39 @@ def get_chain_type(self) -> str:
def get_chain(self) -> GenLayerChain:
chain_map = {
"localnet": localnet,
"studio_devnet": studio_devnet,
"studionet": studionet,
"testnet_asimov": testnet_asimov,
"testnet_bradbury": testnet_bradbury,
}
chain_type = self.get_chain_type()
return chain_map[chain_type]
chain = deepcopy(chain_map[chain_type])

# The selected network is the runtime authority. This is especially
# important for custom Studio deployments: the endpoint and chain ID
# must travel together or signed transactions use the preset's ID.
network_config = self.user_config.networks.get(self.get_network_name())
uses_network_chain_type = (
self.plugin_config.chain_type is None
or (
network_config is not None
and self.plugin_config.chain_type == network_config.chain_type
)
)
if (
uses_network_chain_type
and network_config is not None
and network_config.id is not None
):
chain.id = network_config.id
rpc_url = (
self.get_rpc_url()
if uses_network_chain_type or self.plugin_config.rpc_url is not None
else None
)
if rpc_url:
chain.rpc_urls["default"]["http"] = [rpc_url]
return chain

def get_default_wait_interval(self) -> int:
if self.plugin_config.default_wait_interval is not None:
Expand Down Expand Up @@ -243,4 +277,4 @@ def check_local_rpc(self) -> bool:
return self.get_chain_type() == "localnet"

def check_studio_based_rpc(self) -> bool:
return self.get_chain_type() in ("studionet", "localnet")
return self.get_chain_type() in ("studio_devnet", "studionet", "localnet")
20 changes: 19 additions & 1 deletion gltest_cli/config/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@
DEFAULT_LEADER_ONLY,
CHAINS,
)
from genlayer_py.chains import localnet, studionet, testnet_asimov, testnet_bradbury
from genlayer_py.chains import (
localnet,
studio_devnet,
studionet,
testnet_asimov,
testnet_bradbury,
)
from gltest_cli.config.types import UserConfig, NetworkConfigData, PathConfig

VALID_ROOT_KEYS = ["networks", "paths", "environment"]
Expand Down Expand Up @@ -51,6 +57,16 @@ def get_default_user_config() -> UserConfig:
default_wait_retries=DEFAULT_WAIT_RETRIES,
chain_type="localnet",
),
"studio_devnet": NetworkConfigData(
id=studio_devnet.id,
url=studio_devnet.rpc_urls["default"]["http"][0],
accounts=accounts_private_keys,
from_account=accounts_private_keys[0],
leader_only=DEFAULT_LEADER_ONLY,
default_wait_interval=DEFAULT_WAIT_INTERVAL,
default_wait_retries=DEFAULT_WAIT_RETRIES,
chain_type="studio_devnet",
),
"studionet": NetworkConfigData(
id=studionet.id,
url=studionet.rpc_urls["default"]["http"][0],
Expand Down Expand Up @@ -275,6 +291,8 @@ def _get_overridden_networks(raw_config: dict) -> tuple[dict, str]:

if "url" in network_config:
networks_config[network_name].url = network_config["url"]
if "id" in network_config:
networks_config[network_name].id = network_config["id"]
if "accounts" in network_config:
networks_config[network_name].accounts = network_config["accounts"]
networks_config[network_name].from_account = network_config["accounts"][
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"pytest",
"genlayer-py>=0.19.0rc1,<0.20.0",
"genlayer-py>=0.19.0rc2,<0.20.0",
"colorama>=0.4.6",
"pyyaml",
"python-dotenv"
Expand Down
10 changes: 8 additions & 2 deletions tests/gltest/fees/test_fee_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,13 @@ def test_collector_records_max_and_applies_headroom_with_big_int():
collector.record_deploy(fee_receipt(101, 10))
collector.record_method("create_bet", fee_receipt(big_value, 0))

profile = collector.build_profile(network="localnet", headroom=1.25)
profile = collector.build_profile(
network="localnet", headroom=1.25, chain_id=61127
)

assert profile["version"] == 1
assert profile["network"] == "localnet"
assert profile["chainId"] == 61127
assert profile["deploy"] == {
"leaderTimeunitsAllocation": "125",
"validatorTimeunitsAllocation": "250",
Expand Down Expand Up @@ -257,10 +260,13 @@ def test_write_creates_parent_dirs_and_round_trips_json(tmp_path):
collector.record_deploy(fee_receipt(10, 0))
output_path = tmp_path / "profiles" / "fees.json"

profile = collector.write(output_path, network="localnet", headroom=1.25)
profile = collector.write(
output_path, network="localnet", headroom=1.25, chain_id=61127
)

assert output_path.exists()
assert json.loads(output_path.read_text(encoding="utf-8")) == profile
assert profile["chainId"] == 61127


def test_transact_records_fee_profile_observation(monkeypatch, tmp_path):
Expand Down
5 changes: 5 additions & 0 deletions tests/gltest_cli/config/test_config_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,11 @@ def test_multiple_networks():
assert general_config.get_network_name() == "testnet_asimov_custom"
rpc_url = general_config.get_rpc_url()
assert rpc_url == "https://testnet.example.com"
chain = general_config.get_chain()
assert chain.id == 55554
assert chain.rpc_urls["default"]["http"] == [
"https://testnet.example.com"
]
"""
)

Expand Down
30 changes: 30 additions & 0 deletions tests/gltest_cli/config/test_fee_profile_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import Mock

from gltest_cli.config import plugin


def test_session_profile_uses_the_selected_runtime_chain_id(monkeypatch, tmp_path):
output_path = tmp_path / "fees.json"
general_config = SimpleNamespace(
get_fee_profile_path=Mock(return_value=output_path),
get_network_name=Mock(return_value="preview"),
get_fee_profile_headroom=Mock(return_value=1.25),
get_chain=Mock(return_value=SimpleNamespace(id=61997)),
)
collector = SimpleNamespace(
write=Mock(return_value={"methods": {"create_bet": {}}}),
has_observations=Mock(return_value=True),
)
monkeypatch.setattr(plugin, "get_general_config", lambda: general_config)
monkeypatch.setattr(plugin, "get_fee_profile_collector", lambda: collector)

plugin.pytest_sessionfinish(session=None, exitstatus=0)

collector.write.assert_called_once_with(
path=Path(output_path),
network="preview",
headroom=1.25,
chain_id=61997,
)
Loading
Loading