-
Notifications
You must be signed in to change notification settings - Fork 17
docs: add best practices, security checklist, and solidity migration … #393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
|
Comment on lines
+122
to
+127
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Search for the actual locations of referenced documentation files
echo "=== Searching for intelligent contracts documentation ==="
fd -t f "introduction|types|first-intelligent-contract" --extension md --extension mdx --exec echo "Found: {}"
echo -e "\n=== Checking directory structure ==="
fd -t d "intelligent" --exec echo "Directory: {}"Repository: genlayerlabs/genlayer-docs Length of output: 458 🏁 Script executed: #!/bin/bash
# Search for types-related documentation in intelligent-contracts directory
echo "=== Checking intelligent-contracts directory contents ==="
ls -la pages/developers/intelligent-contracts/ 2>/dev/null | grep -E '\.(md|mdx)$'
echo -e "\n=== Broader search for 'types' files ==="
fd "types" pages/developers/intelligent-contracts/ -t f
echo -e "\n=== Verify Best Practices file referenced in links ==="
fd "best-practices" -t f --extension md --extension mdx
echo -e "\n=== Verify full path structure ==="
tree -L 3 pages/developers/ 2>/dev/null || find pages/developers -maxdepth 3 -type dRepository: genlayerlabs/genlayer-docs Length of output: 3403 Fix documentation link paths and extensions. The "See also" links have two critical issues:
Update lines 124-127 to:
Line 126 (./best-practices.md) is already correct. 🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
What are the exact equivalence principle functions available in GenLayer's Python API? Specifically, verify if gl.eq_principle_prompt_comparative and gl.eq_principle_strict_eq exist.💡 Result:
The exact equivalence principle functions available in GenLayer's Python API under gl.eq_principle are: - strict_eq: Checks for strict equality between leader and validator results. Most performant but strict. - prompt_comparative: Uses NLP/LLM to compare leader and validator results for equivalence based on a provided principle. - prompt_non_comparative: Non-comparative principle for subjective tasks, where validators check if the leader's output meets task criteria. gl.eq_principle_prompt_comparative and gl.eq_principle_strict_eq do not exist as named functions. The functions are accessed as gl.eq_principle.strict_eq and gl.eq_principle.prompt_comparative. There are similar functions in genlayer.std.eq_principles (eq_principle_prompt_comparative, eq_principle_strict_eq), but the primary API uses the gl.eq_principle namespace.
Citations:
🏁 Script executed:
Repository: genlayerlabs/genlayer-docs
Length of output: 7345
Update deprecated equivalence principle API calls.
Lines 38 and 43 use deprecated GenLayer API functions that must be updated:
gl.eq_principle_prompt_comparative→gl.eq_principle.prompt_comparative(line 38)gl.eq_principle_strict_eq→gl.eq_principle.strict_eq(line 43)Additionally, line 37 uses the deprecated
gl.get_webpagefunction, which should be replaced withgl.nondet.web.getorgl.nondet.web.render.🤖 Prompt for AI Agents