diff --git a/pages/_temp/security-and-best-practices/best-practices.md b/pages/_temp/security-and-best-practices/best-practices.md new file mode 100644 index 000000000..47099a1ad --- /dev/null +++ b/pages/_temp/security-and-best-practices/best-practices.md @@ -0,0 +1,149 @@ +# Best Practices for Intelligent Contracts + +> Best practices for writing efficient, reliable, and maintainable Intelligent Contracts on GenLayer. + +--- + +## 1. Structure your contract clearly + +Follow a consistent layout: storage declarations at the top, `__init__` next, then read-only (view) methods, then write methods. Non-deterministic blocks should be small, self-contained functions immediately adjacent to the call that uses them. + +**Recommended contract layout:** + +```python +from genlayer import * + +class MyContract(gl.Contract): + # 1. Storage + owner: Address + data: TreeMap[str, str] + + # 2. Init + def __init__(self, owner: Address): + self.owner = owner + self.data = TreeMap() + + # 3. View methods + @gl.public.view + def get_value(self, key: str) -> str: + return self.data.get(key, "") + + # 4. Write methods + @gl.public.write + def set_value(self, key: str, value: str) -> None: + assert gl.message.sender == self.owner, "Not authorized" + + def _fetch_and_validate() -> bool: + page = gl.get_webpage(value, mode="text") + return gl.eq_principle_prompt_comparative( + lambda: "Is this a valid non-empty URL response? " + page[:200], + "yes or no" + ) + + if gl.eq_principle_strict_eq(_fetch_and_validate): + self.data[key] = value +``` + +--- + +## 2. Keep non-deterministic blocks minimal + +Non-deterministic blocks run on every validator. The more they do, the more LLM calls and web fetches you pay for — and the harder it is to reach consensus. Extract only the judgment you need. + +**Avoid — doing too much in one block:** + +```python +def _block(): + page = gl.get_webpage(url) + summary = gl.exec_prompt("Summarize: " + page) + score = gl.exec_prompt("Rate 1-10: " + summary) + tags = gl.exec_prompt("Extract tags: " + page) + return summary, score, tags +``` + +**Prefer — one focused judgment per block:** + +```python +def _block(): + page = gl.get_webpage(url) + return gl.exec_prompt( + "Is this page about AI? Answer yes or no.\n" + page + ) +``` + +--- + +## 3. Choose the right equivalence principle + +Picking the wrong principle is the most common source of `UNDETERMINED` transactions. Use the table below to guide your choice: + +| Situation | Use | Example | +|---|---|---| +| All validators must match exactly | `strict_eq` | Fetching a price, parsing a number | +| LLM outputs may differ but mean the same | `prompt_comparative` | Sentiment check, yes/no decisions | +| You define what "equal enough" means | `prompt_non_comparative` | Grading, scoring, classification | + +> **Warning:** Using `strict_eq` on a free-text LLM response almost always results in `UNDETERMINED`. Always constrain LLM outputs to structured answers (yes/no, a number, a category) when using `strict_eq`. + +--- + +## 4. Validate external data before storing + +Never write raw web data directly into storage. Validate structure and length before persisting. + +```python +def _validate_price(url: str) -> float: + raw = gl.get_webpage(url, mode="text") + price_str = gl.exec_prompt( + f"Extract the numeric price from this text. " + f"Return only the number, no currency symbol.\n{raw[:500]}" + ) + price = float(price_str.strip()) + assert 0 < price < 1_000_000, "Price out of range" + return price +``` + +--- + +## 5. Minimize storage reads in write methods + +Storage reads inside write methods still incur cost. Cache values in local variables when you need them multiple times within a single method call. + +```python +# Instead of reading self.counter twice: +count = self.counter +count += 1 +self.counter = count +self._emit_count_event(count) # reuse local var +``` + +--- + +## 6. Use access control on every write method + +Any method decorated with `@gl.public.write` is callable by anyone on-chain. Explicitly check `gl.message.sender` at the top of every method that should be restricted. + +```python +@gl.public.write +def admin_reset(self) -> None: + assert gl.message.sender == self.owner, "Only owner" + self.data = TreeMap() +``` + +--- + +## 7. Test with multiple validators + +A contract that passes on 1 validator may fail with 5 due to LLM variance. Always run integration tests with at least 3–5 validators before deploying to testnet. + +```python +sim_createRandomValidators(5, "openai", ["gpt-4"]) +``` + +--- + +## See also + +- [Equivalence Principle](../core-concepts/equivalence-principle.md) +- [Security Checklist](./security-checklist.md) +- [Debugging Intelligent Contracts](./debugging.md) diff --git a/pages/_temp/security-and-best-practices/genlayer-vs-solidity.md b/pages/_temp/security-and-best-practices/genlayer-vs-solidity.md new file mode 100644 index 000000000..6dc012dde --- /dev/null +++ b/pages/_temp/security-and-best-practices/genlayer-vs-solidity.md @@ -0,0 +1,127 @@ +# GenLayer vs Solidity: Migration Guide + +> A quick-reference for developers coming from Ethereum/Solidity. This page maps familiar Solidity concepts to their GenLayer equivalents and highlights the key mental model shifts. + +--- + +## Concept mapping + +| Concept | Solidity | GenLayer (Python) | +|---|---|---| +| Language | Solidity (`.sol`) | Python (`.py`) via GenVM | +| Contract declaration | `contract Foo { }` | `class Foo(gl.Contract): ...` | +| State variables | Top-level typed declarations | Class-level annotated attributes, e.g. `count: u256` | +| Constructor | `constructor(...)` | `def __init__(self, ...)` | +| View function | `view` modifier | `@gl.public.view` | +| Write function | (default, no modifier needed) | `@gl.public.write` | +| Mapping | `mapping(K => V)` | `TreeMap[K, V]` | +| Dynamic array | `T[]` | `DynArray[T]` | +| Caller address | `msg.sender` | `gl.message.sender` | +| Value sent | `msg.value` | `gl.message.value` | +| Revert / error | `revert(...)` / `require(...)` | `assert ...` / `raise Exception(...)` | +| Cross-contract call | `IFoo(addr).bar()` | `gl.contract_at(addr, Foo).bar()` | +| External data (oracle) | Chainlink / oracle services | `gl.get_webpage(url)` natively | +| AI / LLM | Not possible | `gl.exec_prompt(prompt)` natively | +| Consensus model | Deterministic EVM execution | Optimistic Democracy + equivalence principles | +| Transaction outcomes | Success or revert | Success, revert, or `UNDETERMINED` | + +--- + +## Key mental model shifts + +### 1. Determinism is opt-in for AI and web operations + +In Solidity, every operation must be deterministic — the EVM enforces this at the protocol level. In GenLayer, deterministic and non-deterministic code coexist in the same contract, but they must be **explicitly separated** into non-deterministic blocks. + +Business logic outside those blocks is still fully deterministic. Think of non-deterministic blocks as sandboxed AI calls whose result is then fed back into deterministic logic. + +```python +@gl.public.write +def update_status(self, url: str) -> None: + # This outer code is deterministic + assert gl.message.sender == self.owner, "Not authorized" + + # Non-deterministic block — isolated AI/web logic + def _check(): + page = gl.get_webpage(url, mode="text") + return gl.exec_prompt("Is this page live? Answer yes or no.\n" + page[:300]) + + # Back to deterministic — store the consensus result + self.is_live = gl.eq_principle_strict_eq(_check) == "yes" +``` + +--- + +### 2. Transactions may be UNDETERMINED, not just reverted + +Solidity transactions have two outcomes: success or revert. GenLayer adds a third: **`UNDETERMINED`**, meaning validators ran the contract but could not reach consensus on the output. + +This happens when: +- An LLM produces sufficiently different outputs across validators. +- `strict_eq` is used on free-text LLM output. +- Web fetches return different content on different validators (e.g. live prices, timestamps). + +Design your equivalence principle and prompt structure to minimize `UNDETERMINED` results. Constraining LLM output to yes/no, a specific category, or a number is the most reliable approach. + +--- + +### 3. No native event system — use storage for history + +Solidity has `event` declarations and `emit` for off-chain indexing. GenLayer does not have a native event system yet. If you need queryable history, store it explicitly. + +```python +# Solidity pattern (not available in GenLayer): +# emit Transfer(from, to, amount); + +# GenLayer equivalent — store a log in a DynArray: +class TokenContract(gl.Contract): + transfer_log: DynArray[str] + + @gl.public.write + def transfer(self, to: Address, amount: u256) -> None: + # ... transfer logic ... + entry = f"{gl.message.sender}->{to}:{amount}" + self.transfer_log.append(entry) +``` + +--- + +### 4. No built-in oracles — web access is native + +In Solidity, fetching external data requires a trusted oracle service (Chainlink, etc.), which adds cost, latency, and a trust assumption. In GenLayer, `gl.get_webpage(url)` is a first-class protocol primitive — no third-party oracle needed. + +Multi-validator consensus on the fetched data replaces the oracle's trust model. + +--- + +### 5. Storage types are different + +GenLayer's storage types are Python-native but have blockchain-specific semantics. The key types: + +| Solidity | GenLayer | Notes | +|---|---|---| +| `mapping(K => V)` | `TreeMap[K, V]` | Ordered; supports iteration | +| `T[]` (dynamic) | `DynArray[T]` | Dynamic array | +| `T[N]` (fixed) | Python list (fixed size) | Initialized in `__init__` | +| `struct Foo` | `@dataclass class Foo` | Use `gl.Dataclass` | +| `address` | `Address` | GenLayer address type | +| `uint256` | `u256` | And `u8`, `u32`, `u64`, `i256`, etc. | + +--- + +## Quick start for Solidity developers + +1. Install the GenLayer CLI: `npm install -g genlayer` +2. Scaffold a project: `genlayer init my-project` +3. Replace your `.sol` file with a `.py` file following the `class MyContract(gl.Contract)` pattern +4. Run the GenLayer Studio locally to test with multiple validators +5. Deploy to testnet: `genlayer deploy --contract contracts/my_contract.py` + +--- + +## See also + +- [Introduction to Intelligent Contracts](../intelligent-contracts/introduction.md) +- [Types Reference](../intelligent-contracts/types.md) +- [Best Practices](./best-practices.md) +- [Your First Intelligent Contract](../intelligent-contracts/first-intelligent-contract.md) diff --git a/pages/_temp/security-and-best-practices/security-checklist.md b/pages/_temp/security-and-best-practices/security-checklist.md new file mode 100644 index 000000000..076295efe --- /dev/null +++ b/pages/_temp/security-and-best-practices/security-checklist.md @@ -0,0 +1,134 @@ +# Security Checklist + +> A pre-deployment checklist covering the most critical security concerns for Intelligent Contracts. Work through every section before deploying to testnet or mainnet. + +--- + +## Access control + +- [ ] **Owner check on restricted methods** — every write method that should be admin-only asserts `gl.message.sender == self.owner`. +- [ ] **No default public admin** — the deployer address is stored explicitly at `__init__`, not assumed later. +- [ ] **Role changes are guarded** — any method that updates `self.owner` or a similar privileged field requires the current owner to authorize it. + +```python +@gl.public.write +def transfer_ownership(self, new_owner: Address) -> None: + assert gl.message.sender == self.owner, "Only owner" + self.owner = new_owner +``` + +--- + +## Prompt injection + +LLM calls are an attack surface unique to Intelligent Contracts. Treat prompt inputs with the same care as SQL queries. + +- [ ] **User input is never embedded raw in prompts** — always sanitize or truncate caller-supplied strings before interpolating into `gl.exec_prompt`. +- [ ] **Prompts request structured output** — asking for yes/no, a number, or a specific category shrinks the attack surface versus open-ended generation. +- [ ] **Web content is trimmed before injection** — use `page[:500]` or similar to avoid embedding adversarial content hidden deep in a fetched page. +- [ ] **LLM output is validated post-generation** — parse and bounds-check any value extracted from an LLM before using it in business logic or storage. + +**Example — safe prompt construction:** + +```python +@gl.public.write +def classify(self, user_input: str) -> None: + # Sanitize: truncate and strip control characters + safe_input = user_input[:200].replace("\n", " ").replace("\r", " ") + + def _classify(): + return gl.exec_prompt( + f"Classify the following text as 'positive', 'negative', or 'neutral'. " + f"Return only one of those three words.\nText: {safe_input}" + ) + + result = gl.eq_principle_strict_eq(_classify).strip().lower() + assert result in ("positive", "negative", "neutral"), "Unexpected LLM output" + self.sentiment = result +``` + +--- + +## State consistency & reentrancy + +GenLayer does not have Solidity-style reentrancy guards, but the same underlying principle applies: validate before you mutate. + +- [ ] **Checks before effects** — validate all preconditions (`assert`) before any state mutation, following the checks–effects–interactions pattern. +- [ ] **No storage access inside non-deterministic blocks** — storage is intentionally inaccessible there, but ensure closures or captured variables don't smuggle stale state in. +- [ ] **Contract-to-contract calls use `gl.contract_at`** — external calls are explicit; never rely on implicit state sharing between contracts. + +**Pattern — checks before effects:** + +```python +@gl.public.write +def withdraw(self, amount: u256) -> None: + # CHECKS + assert gl.message.sender == self.owner, "Not authorized" + assert self.balance >= amount, "Insufficient balance" + + # EFFECTS (mutate state before any external interaction) + self.balance -= amount + + # INTERACTIONS (external calls last) + # ... send tokens ... +``` + +--- + +## Web data safety + +- [ ] **URLs are validated before fetching** — check that caller-supplied URLs start with `https://` and, where possible, belong to an expected domain allowlist. +- [ ] **Fetch failures are handled gracefully** — wrap `gl.get_webpage` calls so a network failure surfaces a clear exception rather than causing an `UNDETERMINED` transaction silently. +- [ ] **Web data is never stored without parsing** — raw HTML is not meaningful storage; always extract and validate the specific field you need before writing to state. + +```python +def _safe_fetch(url: str) -> str: + assert url.startswith("https://"), "Only HTTPS URLs allowed" + try: + content = gl.get_webpage(url, mode="text") + except Exception as e: + raise Exception(f"Web fetch failed: {e}") + assert len(content) > 0, "Empty response" + return content[:1000] +``` + +--- + +## Gas & economic safety + +Unlike EVM gas limits, unbounded operations in GenLayer can cause transactions to run indefinitely or become prohibitively expensive. Design defensively. + +- [ ] **Non-deterministic blocks are bounded** — LLM calls with unbounded input can spike costs; always cap input size passed to `gl.exec_prompt` and `gl.get_webpage`. +- [ ] **Loops over storage are avoided in write methods** — iterating a `TreeMap` of unbounded size inside a write method creates an unbounded gas cost vector. +- [ ] **No user-controlled iteration bounds** — if a caller can pass a `count` argument that drives a loop, cap it to a safe maximum constant. + +```python +MAX_ITEMS = 100 + +@gl.public.write +def batch_process(self, items: list) -> None: + assert len(items) <= MAX_ITEMS, f"Exceeds max batch size of {MAX_ITEMS}" + for item in items: + self._process(item) +``` + +--- + +## Pre-deployment summary + +Before deploying to testnet, confirm: + +1. Every restricted write method checks `gl.message.sender`. +2. All user-supplied strings are sanitized before reaching `gl.exec_prompt`. +3. LLM outputs are parsed and validated before being stored or used in logic. +4. State mutations follow checks–effects–interactions order. +5. Web fetches validate the URL and handle failures explicitly. +6. No unbounded loops or user-controlled iteration sizes exist in write methods. + +--- + +## See also + +- [Prompt Injection](./prompt-injection.md) +- [Best Practices](./best-practices.md) +- [Equivalence Principle](../core-concepts/equivalence-principle.md)