diff --git a/plugins/genlayer-dev/skills/write-contract/SKILL.md b/plugins/genlayer-dev/skills/write-contract/SKILL.md index 09b3b5b..630a6dc 100644 --- a/plugins/genlayer-dev/skills/write-contract/SKILL.md +++ b/plugins/genlayer-dev/skills/write-contract/SKILL.md @@ -1,417 +1,427 @@ --- name: write-contract -description: Write production-quality GenLayer intelligent contracts. Covers equivalence principle selection, validator patterns, storage rules, LLM resilience, and cross-contract interaction. +description: Write GenLayer intelligent contracts — storage types, decorators, non-deterministic blocks, equivalence principle patterns, and error handling. allowed-tools: - Bash - Read - Write - Edit - - Grep - - Glob + - mcp__genlayer-docs__search_docs + - mcp__genlayer-docs__fetch_url --- -# Write Intelligent Contract +# Writing Intelligent Contracts -Guidance for writing GenLayer intelligent contracts that pass consensus, handle errors correctly, and survive production. +Intelligent contracts are Python classes extending `gl.Contract`. State is persisted on-chain. Non-deterministic operations (LLM calls, web fetches) achieve consensus via the **Equivalence Principle**. -Always lint with `genvm-lint check` after writing or modifying a contract. +## Required File Headers -## Contract Skeleton +Every contract file **must** start with these two lines — deployment fails with `absent_runner_comment` without them: ```python -# { "Depends": "py-genlayer:test" } +# v0.1.0 +# { "Depends": "py-genlayer:latest" } +``` +## Contract Skeleton + +```python +# v0.1.0 +# { "Depends": "py-genlayer:latest" } from genlayer import * +import json -@gl.contract -class MyContract: - # Storage fields — typed, persisted on-chain +class MyContract(gl.Contract): owner: Address - items: TreeMap[str, Item] - item_order: DynArray[str] + name: str + count: u256 + items: TreeMap[str, str] + tags: DynArray[str] - def __init__(self, param: str): - self.owner = gl.message.sender_account + def __init__(self, name: str): + self.owner = gl.message.sender_address + self.name = name + self.count = u256(0) @gl.public.view - def get_item(self, item_id: str) -> dict: - return {"id": item_id, "value": self.items[item_id].value} + def get_info(self) -> str: + return json.dumps({"name": self.name, "count": int(self.count)}) @gl.public.write - def set_item(self, item_id: str, value: str) -> None: - if gl.message.sender_account != self.owner: - raise gl.UserError("Only owner") - self.items[item_id] = Item(value=value) - self.item_order.append(item_id) + def increment(self): + self.count = u256(int(self.count) + 1) ``` -## Equivalence Principle — Which One to Use +## Storage Types -This is the most critical decision. Pick wrong and consensus will fail or be trivially exploitable. +Never use plain `int`, `list`, or `dict` — they cause deployment failures. -### Decision Tree +| Python type | GenLayer type | Notes | +|-------------|---------------|-------| +| `int` | `u256` / `u8`–`u256` / `i8`–`i256` / `bigint` | No plain `int` | +| `list[T]` | `DynArray[T]` | | +| `dict[K, V]` | `TreeMap[K, V]` | Keys must be `str` or `u256` | +| `float`, `bool`, `str`, `bytes` | same | Work directly | +| `datetime` | `datetime.datetime` | | +| address | `Address` | | +**`u256` arithmetic** — convert to/from `int` explicitly (✅ tested): +```python +self.count = u256(int(self.count) + 1) +total = int(self.amount_a) + int(self.amount_b) ``` -Is the external call deterministic (same input → same output)? -├── YES → gl.eq_principle.strict_eq(fn) -│ Examples: blockchain RPC, stable REST APIs, DNS lookups -│ -└── NO (LLM, dynamic web content, variable APIs) - │ - Does the output have a clear "decision" field? - ├── YES → gl.eq_principle.prompt_comparative(fn, principle="...") - │ Examples: oracle resolution, content classification - │ principle: "`outcome` must match exactly. Analysis can differ." - │ - └── NO (numeric scores, complex multi-field output) - └── gl.vm.run_nondet_unsafe(leader_fn, validator_fn) - Write custom comparison with tolerances. - Examples: tweet scoring, price feeds with drift + +**Nested collections** — `TreeMap` can't hold `DynArray`. Use JSON strings (✅ tested): +```python +id_list = json.loads(self.index.get(key) or "[]") +id_list.append(new_id) +self.index[key] = json.dumps(id_list) ``` -### strict_eq — Deterministic calls only +## Method Decorators + +| Decorator | Purpose | +|-----------|---------| +| `@gl.public.view` | Read-only, free | +| `@gl.public.write` | Modifies state | +| `@gl.public.write.payable` | Modifies state + accepts tokens | +| `@gl.public.write.min_gas(leader=N, validator=N)` | Minimum gas for non-det operations | + +## Transaction Context ```python -def fetch_balance(self) -> int: - def call_rpc(): - res = gl.nondet.web.post(rpc_url, body=payload, headers=headers) - return json.loads(res.body.decode("utf-8"))["result"] - return gl.eq_principle.strict_eq(call_rpc) +gl.message.sender_address # caller (Address) +gl.message.value # tokens sent (u256, payable only) +self.balance # this contract's token balance ``` -Never use for LLM calls or web pages that change between requests. +## Error Handling -### prompt_comparative — LLM with clear outcome +Use `gl.vm.UserError` — never bare `ValueError` or `Exception` (linter blocks deployment): ```python -def resolve(self) -> str: - def analyze(): - page = gl.get_webpage(url, mode="text") - return gl.exec_prompt(f"Analyze: {page}\nReturn JSON with outcome and reasoning.") - - return gl.eq_principle.prompt_comparative( - analyze, - principle="`outcome` field must be exactly the same. All other fields must be similar.", - ) +if gl.message.sender_address != self.owner: + raise gl.vm.UserError("Only owner can call this") ``` -Good for: oracle resolution, content classification, yes/no decisions backed by reasoning. +**Error prefix convention** (classifies failures for validator logic): +```python +raise gl.vm.UserError("[EXPECTED] Resource not found") # business logic +raise gl.vm.UserError("[EXTERNAL] Fetch failed: 404") # external API error +raise gl.vm.UserError("[TRANSIENT] Timeout on request") # temporary failure +``` -### run_nondet_unsafe — Custom validator logic +## Address Handling -Use when you need tolerances, gate checks, or complex comparison. +Constructors must handle both `bytes` (test framework) and `str` (JS SDK) (✅ tested): ```python -def score_content(self, content: str) -> dict: - # Pre-read storage BEFORE entering nondet block - cached_data = gl.storage.copy_to_memory(self.reference_data) +def __init__(self, party_b: Address): + if isinstance(party_b, (str, bytes)): + party_b = Address(party_b) + self.party_b = party_b +``` - def leader_fn(): - analysis = gl.nondet.exec_prompt(prompt, response_format="json") - score = _parse_llm_score(analysis) - return {"score": score, "analysis": str(analysis.get("analysis", ""))} +⚠️ In tests, `create_address()` returns raw `bytes`. Pass them directly or as `"0x" + addr.hex()`. Never pass `str(raw_bytes)` — that produces Python repr (`"b'\\xd8...'"`) and raises `binascii.Error`. - def validator_fn(leaders_res: gl.vm.Result) -> bool: - if not isinstance(leaders_res, gl.vm.Return): - return _handle_leader_error(leaders_res, leader_fn) +--- - validator_result = leader_fn() - leader_score = leaders_res.calldata["score"] - validator_score = validator_result["score"] +## Non-Deterministic Blocks - # Gate check: if either is zero (reject), both must agree - if (leader_score == 0) != (validator_score == 0): - return False +Non-det code **must** be inside a zero-argument function passed to an equivalence principle function. Storage is **not accessible** inside — copy to locals first. - # Tolerance: within 5x/0.5x bounds (log10 comparison) - if leader_score > 0 and validator_score > 0: - ratio = leader_score / validator_score - if ratio > 5.0 or ratio < 0.2: - return False +```python +@gl.public.write +def evaluate(self, url: str): + target = url # capture for closure + name = self.name # copy storage to local - return True + def leader_fn(): + resp = gl.nondet.web.get(target) + return gl.nondet.exec_prompt(f"Analyze {name}: {resp.body.decode()[:4000]}") - return gl.vm.run_nondet_unsafe(leader_fn, validator_fn) + # ... pass to equivalence principle function ``` -## Error Classification - -Classify errors so validators know how to compare them. This is critical for consensus on failure paths. +### Web Access ```python -ERROR_EXPECTED = "[EXPECTED]" # Business logic (deterministic) — exact match required -ERROR_EXTERNAL = "[EXTERNAL]" # External API 4xx (deterministic) — exact match required -ERROR_TRANSIENT = "[TRANSIENT]" # Network/5xx (non-deterministic) — agree if both transient -ERROR_LLM = "[LLM_ERROR]" # LLM misbehavior — always disagree, force retry +resp = gl.nondet.web.get(url) # resp.status, resp.body (bytes) +resp = gl.nondet.web.post(url, body="...", headers={}) +text = gl.nondet.web.render(url, mode="text") # JS-rendered → string +img = gl.nondet.web.render(url, mode="screenshot") # JS-rendered → bytes (PNG) +img = gl.nondet.web.render(url, mode="screenshot", wait_after_loaded="1000ms") ``` -### Canonical error handler for validators +`mode='text'`/`'html'` → string. `mode='screenshot'` → bytes — only mode compatible with `images=[...]`. + +⚠️ `web.render(mode='screenshot')` cannot be mocked in direct tests (SDK v0.25.0 bug — returns empty bytes). To test visual contracts in direct mode, pass image bytes as a method argument. + +### LLM Calls ```python -def _handle_leader_error(leaders_res, leader_fn) -> bool: - leader_msg = leaders_res.message if hasattr(leaders_res, 'message') else '' - try: - leader_fn() - return False # Leader errored, validator succeeded — disagree - except gl.vm.UserError as e: - validator_msg = e.message if hasattr(e, 'message') else str(e) - # Deterministic errors: must match exactly - if validator_msg.startswith(ERROR_EXPECTED) or validator_msg.startswith(ERROR_EXTERNAL): - return validator_msg == leader_msg - # Transient: agree if both hit transient failure - if validator_msg.startswith(ERROR_TRANSIENT) and leader_msg.startswith(ERROR_TRANSIENT): - return True - # LLM or unknown: disagree — forces consensus retry - return False - except Exception: - return False +result = gl.nondet.exec_prompt("Your prompt here") +result = gl.nondet.exec_prompt("Describe this", images=[img_bytes]) # multimodal ``` -### Applying error prefixes +`exec_prompt` return type is **not guaranteed to be `str`** across different GenVM backends. Always use this helper (✅ all edge cases tested): ```python -# Web requests -if response.status >= 400 and response.status < 500: - raise gl.vm.UserError(f"{ERROR_EXTERNAL} API returned {response.status}") -elif response.status >= 500: - raise gl.vm.UserError(f"{ERROR_TRANSIENT} API temporarily unavailable") - -# LLM responses -if not isinstance(analysis, dict): - raise gl.vm.UserError(f"{ERROR_LLM} LLM returned non-dict: {type(analysis)}") - -# Business logic -if user_balance < amount: - raise gl.vm.UserError(f"{ERROR_EXPECTED} Insufficient balance") +def _parse_llm_json(raw): + if isinstance(raw, dict): + return raw + s = str(raw).strip().replace("```json", "").replace("```", "").strip() + start, end = s.find("{"), s.rfind("}") + 1 + if start >= 0 and end > start: + s = s[start:end] + return json.loads(s) ``` -## Storage Rules +--- -### Types — use GenLayer types, not Python builtins +## Equivalence Principle Patterns -| Python | GenLayer | Notes | -|--------|----------|-------| -| `dict` | `TreeMap[K, V]` | O(log n) lookup, persisted | -| `list` | `DynArray[T]` | Dynamic array, persisted | -| `int` | `u256` / `i256` | Sized integers for on-chain math | -| `float` | **avoid** | Use atto-scale integers (value * 10^18) | -| `enum` | `str` | Store `.value`, not the enum itself | +### Pattern 1 — Partial Field Matching (default choice) -### Dataclasses for complex state +Leader and validator each run independently. Compare only objective decision fields — ignore subjective text (✅ tested): ```python -@allow_storage -@dataclass -class Item: - name: str - status: str # Use str, not Enum - atto_amount: u256 # Atto-scale (value * 10^18), not float - created_at: str # ISO format string - tags: DynArray[str] +@gl.public.write +def resolve(self, match_id: str): + url = self.matches[match_id] + + def leader_fn(): + web_data = gl.nondet.web.get(url) + prompt = f""" +Find the match result. Page: {web_data.body.decode()[:4000]} +Return JSON: {{"score": "X:Y", "winner": 1 or 2 or 0 for draw, "analysis": "reasoning"}} +""" + return _parse_llm_json(gl.nondet.exec_prompt(prompt)) + + def validator_fn(leader_result) -> bool: + if not isinstance(leader_result, gl.vm.Return): # ✅ tested: correct type + return False + v = leader_fn() + ld = leader_result.calldata + # Only compare decision fields — analysis text will differ across LLMs + return ld["winner"] == v["winner"] and ld["score"] == v["score"] + + result = gl.vm.run_nondet_unsafe(leader_fn, validator_fn) + self.matches[match_id].winner = result["winner"] + self.matches[match_id].analysis = result["analysis"] ``` -### Layout rules +### Pattern 2 — Numeric Tolerance -- **Append new fields at END only.** Storage layout is order-sensitive. Reordering or inserting fields breaks deployed contracts. -- **Default values for new fields** — existing storage reads zero/empty for fields added after deployment. -- **Initialize DynArray/TreeMap by appending** in `__init__`, not by assignment. `self.items = [x]` does not work. -- **O(1) stat indexes** — maintain a `TreeMap[str, u256]` counter alongside collections for fast counts. +For prices or LLM scores that drift between leader and validator execution: -### Storage in non-deterministic blocks +```python +def validator_fn(leader_result) -> bool: + if not isinstance(leader_result, gl.vm.Return): + return False + v_price = leader_fn() + l_price = leader_result.calldata + if l_price == 0: + return v_price == 0 + return abs(l_price - v_price) / abs(l_price) <= 0.02 # 2% tolerance -Storage is **inaccessible** from inside `leader_fn` / `validator_fn`. Pre-read everything you need: +result = gl.vm.run_nondet_unsafe(leader_fn, validator_fn) +``` +For LLM scores (0–10) — handle the zero/rejection gate: ```python -# BEFORE the nondet block -cached_store = gl.storage.copy_to_memory(self.data_store) -cached_config = self.config_value # Simple values can be read directly - -def leader_fn(): - # Use cached_store and cached_config here - similar = list(cached_store.knn(embedding, 10)) - ... +if l == 0 or v == 0: + return l == v # both must agree on rejection +return abs(l - v) <= 1 # ±1 otherwise ``` -## LLM Resilience +### Pattern 3 — LLM Comparison (Comparative) -LLMs return unpredictable formats. Always defensively parse. +When results are too rich for programmatic comparison — an LLM judges equivalence: ```python -def _parse_llm_score(analysis: dict) -> int: - """Extract numeric score from LLM response, handling common variations.""" - if not isinstance(analysis, dict): - raise gl.vm.UserError(f"{ERROR_LLM} Non-dict response: {type(analysis)}") - - # Key aliasing — LLMs use alternate names - raw = analysis.get("score") - if raw is None: - for alt in ("rating", "points", "value", "result"): - if alt in analysis: - raw = analysis[alt] - break - - if raw is None: - raise gl.vm.UserError(f"{ERROR_LLM} Missing 'score'. Keys: {list(analysis.keys())}") - - # Coerce aggressively — handles int, float, "3", "3.5", whitespace - try: - return max(0, int(round(float(str(raw).strip())))) - except (ValueError, TypeError): - raise gl.vm.UserError(f"{ERROR_LLM} Non-numeric score: {raw}") +result = gl.eq_principle.prompt_comparative( + evaluate_fn, + principle="`outcome` must match exactly. Other fields may differ.", +) ``` -### JSON cleanup from LLM output - -```python -def _parse_json(text: str) -> dict: - """Clean LLM JSON: strip wrapping text, fix trailing commas.""" - import re - first = text.find("{") - last = text.rfind("}") - text = text[first:last + 1] - text = re.sub(r",(?!\s*?[\{\[\"\'\w])", "", text) # Remove trailing commas - return json.loads(text) -``` +### Pattern 4 — Non-Comparative -### Always use response_format="json" +Validators judge the leader's output against criteria without re-running the task. Use **only** for open-ended tasks with no web fetching (validators can't verify fetched data): ```python -result = gl.nondet.exec_prompt(task, response_format="json") +result = gl.eq_principle.prompt_non_comparative( + lambda: gl.nondet.web.get(url).body.decode(), + task="Summarize in 2-3 sentences", + criteria="Must capture the main point. Must be 2-3 sentences.", +) ``` -This tells the LLM to return JSON. Still validate and clean — LLMs don't always comply. +⚠️ Never use for oracle/price/data contracts — validators only check if output looks reasonable, not if the fetched data is correct. -## Cross-Contract Interaction +--- -### Read from another contract (synchronous) +## `run_nondet_unsafe` vs `run_nondet` -```python -other = gl.get_contract_at(Address(other_address)) -value = other.view().get_data() -``` +| | `run_nondet_unsafe` | `run_nondet` | +|---|---|---| +| Validator exceptions | Unhandled = Disagree | Caught + compared automatically | +| Error handling | You implement in `validator_fn` | Built-in `compare_user_errors` callback | +| Use for | All custom patterns (recommended) | Convenience functions internally | + +**Use `run_nondet_unsafe` for custom patterns.** Convenience functions (`strict_eq`, `prompt_comparative`, `prompt_non_comparative`) use `run_nondet` internally. -### Write to another contract (asynchronous) +## Validator Result Types ```python -other = gl.get_contract_at(Address(other_address)) -other.emit(on="accepted").process_data(payload) # Non-blocking +def validator_fn(leader_result) -> bool: + if not isinstance(leader_result, gl.vm.Return): # covers UserError + VMError + return False + data = leader_result.calldata + # ... ``` -`emit()` queues the call — it executes after current transaction. Use `on="accepted"` (fast) or `on="finalized"` (safe). +`gl.vm.Return` and `gl.vm.UserError` are real classes — safe for `isinstance`. `gl.vm.Result` is a type alias — do not use in `isinstance`. + +--- -**Warning:** If the current transaction is appealed after `emit()`, the emitted call still happens but the balance may already be decremented. +## Testing Validator Logic -### Factory pattern — deploy child contracts +Use `direct_vm.run_validator()` to test whether your validator agrees or disagrees. **Only works with `run_nondet_unsafe`** — `strict_eq` uses `spawn_sandbox()` which is not supported in the test mock (✅ tested): ```python -def __init__(self, num_workers: int): - with open("/contract/Worker.py", "rt") as f: - worker_code = f.read() - - for i in range(num_workers): - addr = gl.deploy_contract( - code=worker_code.encode("utf-8"), - args=[i, gl.message.contract_address], - salt_nonce=i + 1, - on="accepted", - ) - self.worker_addresses.append(addr) +def test_validator_disagrees(direct_vm, direct_deploy): + contract = direct_deploy("contracts/MyContract.py") + direct_vm.sender = b'\x01' * 20 + + # Leader run + direct_vm.mock_llm(r".*", '{"winner": 1, "score": "2:1", "analysis": "A won"}') + contract.resolve("match_1") + + # Swap mocks — different validator result + direct_vm.clear_mocks() + direct_vm.mock_llm(r".*", '{"winner": 2, "score": "0:1", "analysis": "B won"}') + assert direct_vm.run_validator() is False + +def test_validator_agrees(direct_vm, direct_deploy): + contract = direct_deploy("contracts/MyContract.py") + direct_vm.sender = b'\x01' * 20 + + direct_vm.mock_llm(r".*", '{"winner": 1, "score": "2:1", "analysis": "A won"}') + contract.resolve("match_1") + # Same mock = same winner+score = validator agrees + assert direct_vm.run_validator() is True ``` -Workers are immutable after deployment. Code changes require redeploying the factory. +`run_validator()` raises `RuntimeError("No validator captured")` if called before any nondet method. + +--- + +## Stable JSON Comparison -### Cross-chain RPC verification +When comparing structured output between leader and validator, always serialize with `sort_keys=True` — key order is not guaranteed (✅ tested): ```python -def verify_deposit(self, rpc_url: str, contract_addr: str, call_data: bytes) -> bytes: - """Verify state on another chain via eth_call.""" - payload = { - "jsonrpc": "2.0", "id": 1, - "method": "eth_call", - "params": [{"to": contract_addr, "data": "0x" + call_data.hex()}, "latest"], - } - - def fetch(): - res = gl.nondet.web.post(rpc_url, body=json.dumps(payload).encode(), - headers={"Content-Type": "application/json"}) - if res.status != 200: - raise gl.vm.UserError(f"{ERROR_EXTERNAL} RPC failed: {res.status}") - data = json.loads(res.body.decode("utf-8")) - if "error" in data: - raise gl.vm.UserError(f"{ERROR_EXTERNAL} RPC error: {data['error']}") - hex_result = data.get("result", "0x")[2:] - return bytes.fromhex(hex_result) if hex_result else b"" - - return gl.eq_principle.strict_eq(fetch) +json.dumps(result, sort_keys=True) # stable for exact comparison ``` -## Web Requests +--- -### Extracting stable fields for consensus +## Custom Storage Types -External APIs return variable data (timestamps, counts). Extract only stable fields: +Use `@allow_storage` + `@dataclass` for structured on-chain state: ```python -def leader_fn(): - res = gl.nondet.web.get(api_url) - data = json.loads(res.body.decode("utf-8")) - # Only return fields that won't change between leader and validator calls - return {"id": data["id"], "login": data["login"], "status": data["status"]} - # NOT: follower_count, updated_at, online_status -``` +from dataclasses import dataclass -### Deriving status from variable data +@allow_storage +@dataclass +class UserData: + name: str + balance: u256 + active: bool -When raw data may differ (e.g., CI check counts change), compare derived summaries: +class MyContract(gl.Contract): + users: TreeMap[Address, UserData] +``` -```python -def validator_fn(leaders_res: gl.vm.Result) -> bool: - validator_checks = leader_fn() +--- - def derive(checks): - if not checks: return "pending" - for c in checks: - if c.get("conclusion") != "success": return "failing" - return "success" +## Contract Interactions - return derive(leaders_res.calldata) == derive(validator_checks) +### Call Other Contracts (Dynamic) +```python +other = gl.get_contract_at(Address("0x...")) +result = other.view().some_method() +other.emit(on='accepted').update_status("active") +other.emit(on='finalized').confirm_transaction() ``` -## Anti-Patterns +### Call Other Contracts (Static — better IDE support) +```python +@gl.contract_interface +class TokenInterface: + class View: + def balance_of(self, owner: Address) -> u256: ... + class Write: + def transfer(self, to: Address, amount: u256) -> bool: ... + +token = TokenInterface(Address("0x...")) +balance = token.view().balance_of(my_address) +``` -| Don't | Do Instead | Why | -|-------|-----------|-----| -| `strict_eq()` for LLM calls | `prompt_comparative()` or `run_nondet_unsafe()` | LLM outputs are non-deterministic — strict_eq always fails consensus | -| Read storage inside `leader_fn` | `gl.storage.copy_to_memory()` before the nondet block | Storage is inaccessible in non-deterministic context | -| Store `list` or `dict` | `DynArray[T]` or `TreeMap[K, V]` | Python builtins aren't persistable | -| Use `float` for money/scores | Atto-scale `u256` (value * 10^18) | Floating point has rounding errors | -| Insert fields in the middle of a dataclass | Append at END only | Storage layout is positional — insertion shifts all subsequent fields | -| Store `Enum` directly | Store `enum.value` as `str` | Enum type not supported in storage | -| Ignore LLM response format | Validate type, sanitize JSON, alias keys | LLMs return unpredictable formats | -| Let validator agree on LLM errors | Return `False` (disagree) to force retry | Agreeing on broken LLM output locks bad state | -| Use bare `Exception` in contracts | Use `gl.vm.UserError` with error prefix | Bare exceptions become unrecoverable VMError | -| Compare variable API fields in validators | Extract stable fields or derive status | Timestamps, counts change between calls | -| O(n) scans over large collections | Maintain TreeMap indexes for O(1) lookups | Transactions have compute limits | +### Deploy Child Contracts +```python +child_addr = gl.deploy_contract(code=contract_code, salt=u256(1)) +``` -## Testing Strategy +### EVM Interop +```python +@gl.evm.contract_interface +class ERC20: + class View: + def balance_of(self, owner: Address) -> u256: ... + class Write: + def transfer(self, to: Address, amount: u256) -> bool: ... + +token = ERC20(evm_address) +balance = token.view().balance_of(addr) +token.emit().transfer(recipient, u256(100)) # Messages only on finality +``` -1. **Lint first**: `genvm-lint check contracts/my_contract.py` -2. **Direct mode tests**: Fast (30ms), no server. Tests business logic, validation, state transitions. Validator logic NOT exercised. -3. **Integration tests**: Slow (seconds-minutes), full consensus. Tests validator agreement, real web/LLM calls. Run before deployment. +--- -### DEV MODE for external dependencies +## Looking Up Docs -Skip cross-chain verification in tests by checking for zero address: +Use the `genlayer-docs` MCP server when you need detail beyond this skill: -```python -def __init__(self, bridge_sender: str): - self.bridge_sender = Address(bridge_sender) - -def verify_deposit(self, ...): - if self.bridge_sender == Address("0x" + "0" * 40): - print("DEV MODE: skipping verification") - return True - # Real verification logic... +``` +search_docs(library="genlayer-docs", query="") +search_docs(library="genlayer-sdk", query="") ``` -Deploy with `bridge_sender="0x0000...0000"` in tests, real address in production. +Examples: +- `search_docs(library="genlayer-docs", query="equivalence principle patterns")` +- `search_docs(library="genlayer-sdk", query="TreeMap DynArray storage")` +- `search_docs(library="genlayer-docs", query="security prompt injection")` + +If the MCP server is unavailable, fetch docs directly: + +| Topic | URL | +|-------|-----| +| **Intelligent Contracts overview** | https://docs.genlayer.com/developers/intelligent-contracts/introduction | +| **Storage types & features** | https://docs.genlayer.com/developers/intelligent-contracts/features/storage | +| **Equivalence Principle (full)** | https://docs.genlayer.com/developers/intelligent-contracts/equivalence-principle | +| **Development setup & workflow** | https://docs.genlayer.com/developers/intelligent-contracts/tooling-setup | +| **Debugging** | https://docs.genlayer.com/developers/intelligent-contracts/debugging | +| **Security & prompt injection** | https://docs.genlayer.com/developers/intelligent-contracts/security-and-best-practices | +| **Python SDK reference** | https://docs.genlayer.com/api-references/genlayer-py | +| **GenLayer Test reference** | https://docs.genlayer.com/api-references/genlayer-test | +| **GenLayer JS reference** | https://docs.genlayer.com/api-references/genlayer-js | +| **CLI reference** | https://docs.genlayer.com/api-references/genlayer-cli | +| **Python SDK source (API)** | https://sdk.genlayer.com/main/api/genlayer.html | +| **Contract examples** | https://github.com/genlayerlabs/genlayer-testing-suite/tree/main/tests/examples | +| **Project boilerplate** | https://github.com/genlayerlabs/genlayer-project-boilerplate | + +See also: `genvm-lint`, `direct-tests`, `integration-tests`, and `genlayer-cli` skills for tooling.