diff --git a/.github/workflows/lint-code-examples.yml b/.github/workflows/lint-code-examples.yml new file mode 100644 index 000000000..560ba4b47 --- /dev/null +++ b/.github/workflows/lint-code-examples.yml @@ -0,0 +1,23 @@ +name: Lint Code Examples + +on: + pull_request: + paths: + - 'pages/**/*.mdx' + - 'scripts/lint-code-examples.py' + push: + branches: [main] + paths: + - 'pages/**/*.mdx' + - 'scripts/lint-code-examples.py' + +jobs: + lint-code-examples: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: pip install genvm-linter + - run: python scripts/lint-code-examples.py diff --git a/pages/api-references/genlayer-test.mdx b/pages/api-references/genlayer-test.mdx index dc893e0ab..8ffedc33c 100644 --- a/pages/api-references/genlayer-test.mdx +++ b/pages/api-references/genlayer-test.mdx @@ -139,7 +139,7 @@ assert default_account != other_account For the following code examples, we'll use a Storage Intelligent Contract as a reference: ```python -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * diff --git a/pages/developers/intelligent-contracts/crafting-prompts.mdx b/pages/developers/intelligent-contracts/crafting-prompts.mdx index 8bc57ca67..c7a11ba7d 100644 --- a/pages/developers/intelligent-contracts/crafting-prompts.mdx +++ b/pages/developers/intelligent-contracts/crafting-prompts.mdx @@ -15,7 +15,7 @@ When crafting prompts for LLMs, it's important to use a format that clearly and In the example **Wizard of Coin** contract below, we want the LLM to decide whether the wizard should give the coin to an adventurer. ```python -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * import json @@ -55,13 +55,14 @@ This result should be perfectly parseable by a JSON parser without errors. """ def nondet(): - res = gl.exec_prompt(prompt) - res = res.replace("```json", "").replace("```", "") + res = gl.nondet.exec_prompt(prompt) + backticks = "``" + "`" + res = res.replace(backticks + "json", "").replace(backticks, "") print(res) dat = json.loads(res) return dat["give_coin"] - result = gl.eq_principle_strict_eq(nondet) + result = gl.eq_principle.strict_eq(nondet) assert isinstance(result, bool) self.have_coin = result diff --git a/pages/developers/intelligent-contracts/examples/fetch-github-profile.mdx b/pages/developers/intelligent-contracts/examples/fetch-github-profile.mdx index 126cc8a14..44fe990fb 100644 --- a/pages/developers/intelligent-contracts/examples/fetch-github-profile.mdx +++ b/pages/developers/intelligent-contracts/examples/fetch-github-profile.mdx @@ -3,7 +3,7 @@ The FetchGitHubProfile contract demonstrates how to fetch and store GitHub profile content within an intelligent contract. This contract shows how to use the [comparative equivalence principle](/developers/intelligent-contracts/equivalence-principle#comparative-equivalence-principle) to ensure all nodes agree on the same profile content. ```python -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * import typing @@ -20,9 +20,10 @@ class FetchGitHubProfile(gl.Contract): github_profile_url = "https://github.com/"+github_handle def fetch_github_profile_page_content() -> str: - return gl.get_webpage(github_profile_url, mode="text") - - self.github_profile = gl.eq_principle_strict_eq(fetch_github_profile_page_content) + response = gl.nondet.web.get(github_profile_url) + return response.body.decode("utf-8") + + self.github_profile = gl.eq_principle.strict_eq(fetch_github_profile_page_content) @gl.public.view def show_github_profile(self) -> str: @@ -35,14 +36,14 @@ class FetchGitHubProfile(gl.Contract): - **Write Method**: - `fetch_github_profile(github_handle)` takes a GitHub username and retrieves their profile content. - Constructs the profile URL using the provided handle. - - Uses `gl.eq_principle_strict_eq()` to ensure all nodes agree on the same profile content. + - Uses `gl.eq_principle.strict_eq()` to ensure all nodes agree on the same profile content. - **Read Method**: - `show_github_profile()` returns the stored profile content. ## Key Components -1. **GitHub Integration**: The contract uses `gl.get_webpage()` to fetch content from GitHub profiles. -2. **Deterministic Execution**: `gl.eq_principle_strict_eq()` ensures that all nodes in the network arrive at the same exact content. +1. **GitHub Integration**: The contract uses `gl.nondet.web.get()` to fetch content from GitHub profiles. +2. **Deterministic Execution**: `gl.eq_principle.strict_eq()` ensures that all nodes in the network arrive at the same exact content. 3. **State Management**: The contract maintains a single string state variable that stores the profile content. ## Deploying the Contract diff --git a/pages/developers/intelligent-contracts/examples/fetch-web-content.mdx b/pages/developers/intelligent-contracts/examples/fetch-web-content.mdx index 9f28cd50e..02dc92695 100644 --- a/pages/developers/intelligent-contracts/examples/fetch-web-content.mdx +++ b/pages/developers/intelligent-contracts/examples/fetch-web-content.mdx @@ -3,7 +3,7 @@ The FetchWebContent contract demonstrates how to fetch and store web content within an intelligent contract. This contract shows how to use the [comparative equivalence principle](/developers/intelligent-contracts/equivalence-principle#comparative-equivalence-principle) to ensure all nodes agree on the same web content. ```python -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * import typing @@ -19,9 +19,10 @@ class FetchWebContent(gl.Contract): def fetch_web_content(self) -> typing.Any: def fetch_web_url_content() -> str: - return gl.get_webpage("https://example.com/", mode="text") - - self.content = gl.eq_principle_strict_eq(fetch_web_url_content) + response = gl.nondet.web.get("https://example.com/") + return response.body.decode("utf-8") + + self.content = gl.eq_principle.strict_eq(fetch_web_url_content) @gl.public.view def show_content(self) -> str: @@ -33,15 +34,15 @@ class FetchWebContent(gl.Contract): - **Initialization**: The `FetchWebContent` class initializes with an empty string in the `content` variable. - **Write Method**: - `fetch_web_content()` retrieves content from a web page and stores it. - - It contains an inner function `fetch_web_url_content()` that uses `gl.get_webpage()` to fetch content. - - Uses `gl.eq_principle_strict_eq()` to ensure all nodes agree on the same content. + - It contains an inner function `fetch_web_url_content()` that uses `gl.nondet.web.get()` to fetch content. + - Uses `gl.eq_principle.strict_eq()` to ensure all nodes agree on the same content. - **Read Method**: - `show_content()` returns the stored web content. ## Key Components -1. **Web Integration**: The contract uses `gl.get_webpage()` to fetch content from web URLs. -2. **Deterministic Execution**: `gl.eq_principle_strict_eq()` ensures that all nodes in the network arrive at the same exact content. +1. **Web Integration**: The contract uses `gl.nondet.web.get()` to fetch content from web URLs. +2. **Deterministic Execution**: `gl.eq_principle.strict_eq()` ensures that all nodes in the network arrive at the same exact content. 3. **State Management**: The contract maintains a single string state variable that stores the web content. ## Deploying the Contract @@ -101,7 +102,7 @@ You can monitor the contract's behavior through transaction logs, which will sho ## HTML Mode for Web Content -The `gl.get_webpage()` function supports different modes for retrieving web content. While `mode="text"` returns the plain text content, `mode="html"` allows you to retrieve the complete HTML `` of the webpage. +The `gl.nondet.web.get()` function supports different modes for retrieving web content. While the default mode returns the plain text content, `gl.nondet.web.render()` with `mode="html"` allows you to retrieve the complete HTML `` of the webpage. Here's an example of using HTML mode: @@ -115,9 +116,9 @@ class FetchHTMLContent(gl.Contract): @gl.public.write def fetch_html_content(self) -> typing.Any: def fetch_web_url_html() -> str: - return gl.get_webpage("https://example.com/", mode="html") - - self.html_content = gl.eq_principle_strict_eq(fetch_web_url_html) + return gl.nondet.web.render("https://example.com/", mode='html') + + self.html_content = gl.eq_principle.strict_eq(fetch_web_url_html) @gl.public.view def show_html_content(self) -> str: diff --git a/pages/developers/intelligent-contracts/examples/github-profile-projects.mdx b/pages/developers/intelligent-contracts/examples/github-profile-projects.mdx index 00fc79ca2..cf6fd4bed 100644 --- a/pages/developers/intelligent-contracts/examples/github-profile-projects.mdx +++ b/pages/developers/intelligent-contracts/examples/github-profile-projects.mdx @@ -3,7 +3,7 @@ The GitHubProfilesRepositories contract demonstrates how to fetch GitHub profile data, analyze repository counts, and store profiles of high-contributing developers. This contract shows how to use the [comparative equivalence principle](/developers/intelligent-contracts/equivalence-principle#comparative-equivalence-principle) along with pattern matching to process web content. ```python -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * import typing @@ -20,7 +20,8 @@ class GitHubProfilesRepositories(gl.Contract): github_profile_url = "https://github.com/"+github_handle def fetch_github_profile_repositories() -> int: - profile_web_page = gl.get_webpage(github_profile_url, mode="text") + response = gl.nondet.web.get(github_profile_url) + profile_web_page = response.body.decode("utf-8") # Regular expression to find the number between "Repositories" and "Projects" pattern = r"Repositories\s+(\d+)\s+Projects" @@ -33,7 +34,7 @@ class GitHubProfilesRepositories(gl.Contract): else: return 0 - repositories = gl.eq_principle_strict_eq(fetch_github_profile_repositories) + repositories = gl.eq_principle.strict_eq(fetch_github_profile_repositories) if repositories > 25: self.github_profiles.append(github_handle) @@ -55,9 +56,9 @@ class GitHubProfilesRepositories(gl.Contract): ## Key Components -1. **GitHub Integration**: Uses `gl.get_webpage()` to fetch profile content. +1. **GitHub Integration**: Uses `gl.nondet.web.get()` to fetch profile content. 2. **Pattern Matching**: Employs regular expressions to extract repository counts. -3. **Deterministic Execution**: Uses `gl.eq_principle_strict_eq()` to ensure network consensus. +3. **Deterministic Execution**: Uses `gl.eq_principle.strict_eq()` to ensure network consensus. 4. **Conditional Storage**: Only stores profiles meeting specific criteria. ## Deploying the Contract diff --git a/pages/developers/intelligent-contracts/examples/github-profile-summary.mdx b/pages/developers/intelligent-contracts/examples/github-profile-summary.mdx index de0606b46..c44cbfb53 100644 --- a/pages/developers/intelligent-contracts/examples/github-profile-summary.mdx +++ b/pages/developers/intelligent-contracts/examples/github-profile-summary.mdx @@ -3,7 +3,7 @@ The GitHubProfilesSummaries contract demonstrates how to fetch GitHub profile data and generate AI-powered summaries of user profiles. This contract shows how to combine web scraping with AI analysis using both [comparative](/developers/intelligent-contracts/equivalence-principle#comparative-equivalence-principle) and [non-comparative](/developers/intelligent-contracts/equivalence-principle#non-comparative-equivalence-principle) equivalence principles. ```python -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * import typing @@ -25,9 +25,10 @@ class GitHubProfilesSummaries(gl.Contract): github_profile_url = "https://github.com/"+github_handle def fetch_github_profile_summaries() -> str: - return gl.get_webpage(github_profile_url, mode="text") + response = gl.nondet.web.get(github_profile_url) + return response.body.decode("utf-8") - profile_content = gl.eq_principle_strict_eq(fetch_github_profile_summaries) + profile_content = gl.eq_principle.strict_eq(fetch_github_profile_summaries) task = """Given the web page content of a github profile in HTML format, generate a comprehensive summary of the profile mentioning the key meta attributes and the GitHub contribution most important metrics""" @@ -35,7 +36,7 @@ summary of the profile mentioning the key meta attributes and the GitHub contrib criteria = """The summary provided should include different metrics and a summary of a GitHub profile""" profile_summary = ( - gl.eq_principle_prompt_non_comparative( + gl.eq_principle.prompt_non_comparative( lambda: profile_content, task=task, criteria=criteria, @@ -61,7 +62,7 @@ summary of the profile mentioning the key meta attributes and the GitHub contrib ## Key Components 1. **Data Storage**: Uses `TreeMap` for efficient key-value storage of profile summaries. -2. **Web Fetching**: Uses `gl.get_webpage()` with strict equivalence for deterministic content retrieval. +2. **Web Fetching**: Uses `gl.nondet.web.get()` with strict equivalence for deterministic content retrieval. 3. **AI Analysis**: Uses non-comparative equivalence for generating profile summaries. 4. **Duplicate Prevention**: Includes checks to prevent regenerating existing summaries. diff --git a/pages/developers/intelligent-contracts/examples/llm-hello-world-non-comparative.mdx b/pages/developers/intelligent-contracts/examples/llm-hello-world-non-comparative.mdx index e33f56587..8f805eea3 100644 --- a/pages/developers/intelligent-contracts/examples/llm-hello-world-non-comparative.mdx +++ b/pages/developers/intelligent-contracts/examples/llm-hello-world-non-comparative.mdx @@ -3,7 +3,7 @@ The LlmHelloWorldNonComparative contract demonstrates a simple example of integrating AI capabilities within an intelligent contract without requiring that all the validators execute the full task. They just need to evaluate the leader's response against the specified criteria. This is done by using the [non-comparative equivalence principle](/developers/intelligent-contracts/equivalence-principle#non-comparative-equivalence-principle). ```python -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * import typing @@ -17,7 +17,7 @@ class LlmHelloWorldNonComparative(gl.Contract): @gl.public.write def set_message(self) -> typing.Any: - self.message = gl.eq_principle_prompt_non_comparative( + self.message = gl.eq_principle.prompt_non_comparative( lambda: "There is no context, I just want you to answer with truthy value in python (for example: 'yes', 'True', 1)", task="Answer with truthy value in python (for example: 'yes', 'True', 1)", criteria="Answer should be a truthy value in python" @@ -33,7 +33,7 @@ class LlmHelloWorldNonComparative(gl.Contract): - **Initialization**: The `LlmHelloWorldNonComparative` class initializes with an empty string in the `message` variable. - **Write Method**: - `set_message()` uses AI functionality to generate and store a message. - - Uses `gl.eq_principle_prompt_non_comparative()` with three parameters: + - Uses `gl.eq_principle.prompt_non_comparative()` with three parameters: - A lambda function providing the prompt - A task description - Validation criteria for the response @@ -43,7 +43,7 @@ class LlmHelloWorldNonComparative(gl.Contract): ## Key Components 1. **AI Integration**: The contract uses non-comparative equivalence principle to interact with an AI model. -2. **Deterministic Execution**: `gl.eq_principle_prompt_non_comparative()` ensures that all nodes in the network accept responses that meet the specified criteria. +2. **Deterministic Execution**: `gl.eq_principle.prompt_non_comparative()` ensures that all nodes in the network accept responses that meet the specified criteria. 3. **State Management**: The contract maintains a single string state variable that stores the AI response. ## Deploying the Contract diff --git a/pages/developers/intelligent-contracts/examples/llm-hello-world.mdx b/pages/developers/intelligent-contracts/examples/llm-hello-world.mdx index d79a29953..0c31fbe73 100644 --- a/pages/developers/intelligent-contracts/examples/llm-hello-world.mdx +++ b/pages/developers/intelligent-contracts/examples/llm-hello-world.mdx @@ -3,7 +3,7 @@ The LlmHelloWorld contract demonstrates a simple example of integrating AI capabilities within an intelligent contract. This contract shows how to use the [comparative equivalence principle](/developers/intelligent-contracts/equivalence-principle#comparative-equivalence-principle) to call an LLM and store the response in the contract state. ```python -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * import typing @@ -20,11 +20,11 @@ class LlmHelloWorld(gl.Contract): def get_message() -> str: task = "There is no context, I just want you to answer with a string equal to 'yes'" - result = gl.exec_prompt(task) + result = gl.nondet.exec_prompt(task) print(result) return result - self.message = gl.eq_principle_strict_eq(get_message) + self.message = gl.eq_principle.strict_eq(get_message) @gl.public.view def get_message(self) -> str: @@ -37,14 +37,14 @@ class LlmHelloWorld(gl.Contract): - **Write Method**: - `set_message()` uses AI functionality to generate and store a message. - It contains an inner function `get_message()` that prompts an AI model with a simple task. - - Uses `gl.eq_principle_strict_eq()` to ensure deterministic AI responses across the network. + - Uses `gl.eq_principle.strict_eq()` to ensure deterministic AI responses across the network. - **Read Method**: - `get_message()` returns the stored message. ## Key Components -1. **AI Integration**: The contract uses `gl.exec_prompt()` to interact with an AI model. -2. **Deterministic Execution**: `gl.eq_principle_strict_eq()` ensures that all nodes in the network arrive at the same exact result. +1. **AI Integration**: The contract uses `gl.nondet.exec_prompt()` to interact with an AI model. +2. **Deterministic Execution**: `gl.eq_principle.strict_eq()` ensures that all nodes in the network arrive at the same exact result. 3. **State Management**: The contract maintains a single string state variable that stores the AI response. ## Deploying the Contract diff --git a/pages/developers/intelligent-contracts/examples/prediction.mdx b/pages/developers/intelligent-contracts/examples/prediction.mdx index 379680871..78bd5c285 100644 --- a/pages/developers/intelligent-contracts/examples/prediction.mdx +++ b/pages/developers/intelligent-contracts/examples/prediction.mdx @@ -3,7 +3,7 @@ The Prediction Market contract sets up a scenario to determine the outcome of a football game between two teams. The contract uses the Equivalence Principle to ensure accurate and consistent decision-making based on the game's resolution data. ```python filename="PredictionMarket" copy -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * @@ -49,7 +49,8 @@ class PredictionMarket(gl.Contract): return "Already resolved" def nondet() -> str: - web_data = gl.get_webpage(self.resolution_url, mode="text") + response = gl.nondet.web.get(self.resolution_url) + web_data = response.body.decode("utf-8") print(web_data) task = f"""In the following web page, find the winning team in a matchup between the following teams: @@ -73,11 +74,11 @@ class PredictionMarket(gl.Contract): your output must be only JSON without any formatting prefix or suffix. This result should be perfectly parsable by a JSON parser without errors. """ - result = gl.exec_prompt(task).replace("```json", "").replace("```", "") + result = gl.nondet.exec_prompt(task).replace("```json", "").replace("```", "") print(result) return json.dumps(json.loads(result), sort_keys=True) - result_json = json.loads(gl.eq_principle_strict_eq(nondet)) + result_json = json.loads(gl.eq_principle.strict_eq(nondet)) if result_json["winner"] > -1: self.has_resolved = True diff --git a/pages/developers/intelligent-contracts/examples/storage.mdx b/pages/developers/intelligent-contracts/examples/storage.mdx index c5b921b67..d838c3d23 100644 --- a/pages/developers/intelligent-contracts/examples/storage.mdx +++ b/pages/developers/intelligent-contracts/examples/storage.mdx @@ -3,7 +3,7 @@ The Storage contract sets up a simple scenario to store and retrieve a string value. This contract demonstrates basic data storage and retrieval functionality within a blockchain environment. ```python -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * diff --git a/pages/developers/intelligent-contracts/examples/user-storage.mdx b/pages/developers/intelligent-contracts/examples/user-storage.mdx index 91f07ce34..49e426494 100644 --- a/pages/developers/intelligent-contracts/examples/user-storage.mdx +++ b/pages/developers/intelligent-contracts/examples/user-storage.mdx @@ -4,7 +4,7 @@ The UserStorage contract sets up a scenario to store and retrieve string values associated with different user accounts. This contract demonstrates basic per-user data storage and retrieval functionality within a blockchain environment. ```python -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * diff --git a/pages/developers/intelligent-contracts/examples/vector-store-log-indexer.mdx b/pages/developers/intelligent-contracts/examples/vector-store-log-indexer.mdx index 24d930b75..54fd5105e 100644 --- a/pages/developers/intelligent-contracts/examples/vector-store-log-indexer.mdx +++ b/pages/developers/intelligent-contracts/examples/vector-store-log-indexer.mdx @@ -5,8 +5,8 @@ The LogIndexer contract demonstrates how to use the Vector Store database (VecDB ```python # { # "Seq": [ -# { "Depends": "py-lib-genlayermodelwrappers:test" }, -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-lib-genlayermodelwrappers:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" }, +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } # ] # } @@ -17,6 +17,7 @@ from dataclasses import dataclass import typing +@allow_storage @dataclass class StoreValue: log_id: u256 diff --git a/pages/developers/intelligent-contracts/examples/wizard-of-coin.mdx b/pages/developers/intelligent-contracts/examples/wizard-of-coin.mdx index bc2429851..581b58221 100644 --- a/pages/developers/intelligent-contracts/examples/wizard-of-coin.mdx +++ b/pages/developers/intelligent-contracts/examples/wizard-of-coin.mdx @@ -3,7 +3,7 @@ The Wizard of Coin contract sets up a scenario where a wizard possesses a valuable coin, which adventurers try to obtain. The wizard must decide whether to give the coin away based on specific conditions. ```python -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * import json @@ -43,13 +43,14 @@ This result should be perfectly parseable by a JSON parser without errors. """ def nondet(): - res = gl.exec_prompt(prompt) - res = res.replace("```json", "").replace("```", "") + res = gl.nondet.exec_prompt(prompt) + backticks = "``" + "`" + res = res.replace(backticks + "json", "").replace(backticks, "") print(res) dat = json.loads(res) return dat["give_coin"] - result = gl.eq_principle_strict_eq(nondet) + result = gl.eq_principle.strict_eq(nondet) assert isinstance(result, bool) self.have_coin = result diff --git a/pages/developers/intelligent-contracts/features/debugging.mdx b/pages/developers/intelligent-contracts/features/debugging.mdx index 5c3c750dc..608a6e22d 100644 --- a/pages/developers/intelligent-contracts/features/debugging.mdx +++ b/pages/developers/intelligent-contracts/features/debugging.mdx @@ -17,7 +17,7 @@ possibility to enable profiling by setting environment variable `GENLAYER_ENABLE # { # "Seq": [ # { "SetEnv": { "name": "GENLAYER_ENABLE_PROFILER", "value": "true" } }, -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } # ] # } ``` diff --git a/pages/developers/intelligent-contracts/features/upgradability.mdx b/pages/developers/intelligent-contracts/features/upgradability.mdx index 1bac0a6f5..d926033fd 100644 --- a/pages/developers/intelligent-contracts/features/upgradability.mdx +++ b/pages/developers/intelligent-contracts/features/upgradability.mdx @@ -29,7 +29,7 @@ To make a contract upgradable, you need to: ```python # v0.1.0 -# { "Depends": "py-genlayer:latest" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * @@ -70,7 +70,7 @@ The upgraded contract code must maintain the **same storage layout** for compati ```python # v0.1.0 -# { "Depends": "py-genlayer:latest" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * diff --git a/pages/developers/intelligent-contracts/features/vector-storage.mdx b/pages/developers/intelligent-contracts/features/vector-storage.mdx index 5c412c838..710d9c252 100644 --- a/pages/developers/intelligent-contracts/features/vector-storage.mdx +++ b/pages/developers/intelligent-contracts/features/vector-storage.mdx @@ -32,8 +32,8 @@ Here’s an example of a contract using the Vector Store for indexing and search ```python # { # "Seq": [ -# { "Depends": "py-lib-genlayermodelwrappers:test" }, -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-lib-genlayermodelwrappers:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" }, +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } # ] # } @@ -43,6 +43,7 @@ import numpy as np from dataclasses import dataclass +@allow_storage @dataclass class StoreValue: log_id: u256 diff --git a/pages/developers/intelligent-contracts/first-contract.mdx b/pages/developers/intelligent-contracts/first-contract.mdx index 320fb2bb0..07d839439 100644 --- a/pages/developers/intelligent-contracts/first-contract.mdx +++ b/pages/developers/intelligent-contracts/first-contract.mdx @@ -7,7 +7,7 @@ While a GenLayer Intelligent Contract is a pure Python program, there are few th ### Version Comment First of all, you need to place a magic comment with the version of GenVM you wish to use on the **first** line of your file ```py -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } ``` It is similar to Solidity's `pragma solidity`, and `test` after a colon is a version. When GenLayer releases, it will be changed to some exact hash of a version, which will be frozen forever. @@ -56,7 +56,7 @@ In your contracts, you can use any Python types, but for persisted fields, there Here is a simple example of an Intelligent Contract that stores a name and allows changing it: ```py -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * diff --git a/pages/developers/intelligent-contracts/first-intelligent-contract.mdx b/pages/developers/intelligent-contracts/first-intelligent-contract.mdx index a078bd61f..4b6de044c 100644 --- a/pages/developers/intelligent-contracts/first-intelligent-contract.mdx +++ b/pages/developers/intelligent-contracts/first-intelligent-contract.mdx @@ -12,22 +12,22 @@ To illustrate how it works, let's get a webpage as plain HTML, and verify that i ```py example_web_address = 'https://example.org' def my_non_deterministic_block(): - web_data = gl.get_webpage(example_web_address, mode='html') + web_data = gl.nondet.web.render(example_web_address, mode='html') return 'iana' in web_data -print(gl.eq_principle_strict_eq(my_non_deterministic_block)) +print(gl.eq_principle.strict_eq(my_non_deterministic_block)) ``` Here are a few important parts: -1. It is **mandatory** to call `gl.get_webpage` from a function invoked via `gl.eq_principle_*`, otherwise it will give an error +1. It is **mandatory** to call `gl.nondet.web.render` (or `gl.nondet.web.get`) from a function invoked via `gl.eq_principle.*`, otherwise it will give an error 2. Type annotations are not required 3. `example_web_address` gets captured without the need to specify it explicitly 4. We are getting the page in plain HTML, because we want text from a link (`` HTML tag), which is not visible in the text representation -5. We are using `eq_principle_strict_eq` because we return a `bool` (`True` or `False`), so there is no need to run LLMs or other complex computations: validators will agree _if_ they both get the exactly the same result, which makes sense for our case. However, if you wish to return more complex data (such as a text summary), you should use other `eq_principle`. More on this topic on the next page +5. We are using `gl.eq_principle.strict_eq` because we return a `bool` (`True` or `False`), so there is no need to run LLMs or other complex computations: validators will agree _if_ they both get the exactly the same result, which makes sense for our case. However, if you wish to return more complex data (such as a text summary), you should use other `gl.eq_principle` methods. More on this topic on the next page ### As a Full Contract ```py -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * @@ -37,7 +37,7 @@ class Contract(gl.Contract): def __init__(self): example_web_address = 'https://example.org' def my_non_deterministic_block(): - web_data = gl.get_webpage(example_web_address, mode='html') + web_data = gl.nondet.web.render(example_web_address, mode='html') return 'iana' in web_data - self.had_iana = gl.eq_principle_strict_eq(my_non_deterministic_block) + self.had_iana = gl.eq_principle.strict_eq(my_non_deterministic_block) ``` diff --git a/pages/developers/intelligent-contracts/introduction.mdx b/pages/developers/intelligent-contracts/introduction.mdx index 85665ec24..3fd3e8bbe 100644 --- a/pages/developers/intelligent-contracts/introduction.mdx +++ b/pages/developers/intelligent-contracts/introduction.mdx @@ -32,7 +32,7 @@ Intelligent Contracts are written in Python using the GenVM SDK library. The bas Here's an example: ```py - # { "Depends": "py-genlayer:test" } + # { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * class MyContract(gl.Contract): diff --git a/pages/developers/intelligent-contracts/storage.mdx b/pages/developers/intelligent-contracts/storage.mdx index 4d0ffb322..25f7fd903 100644 --- a/pages/developers/intelligent-contracts/storage.mdx +++ b/pages/developers/intelligent-contracts/storage.mdx @@ -121,7 +121,7 @@ Struct types are zero-initialized "recursively" Required for **generic storage classes** only. Regular dataclasses use normal initialization. ```python -# { "Depends": "py-genlayer:test" } +# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } from genlayer import * from dataclasses import dataclass diff --git a/pages/validators/setup-guide.mdx b/pages/validators/setup-guide.mdx index f05c885ce..d10ba3563 100644 --- a/pages/validators/setup-guide.mdx +++ b/pages/validators/setup-guide.mdx @@ -377,9 +377,6 @@ Set the `consensus` section in your `config.yaml` according to the network you w consensus: consensusaddress: "0xe66B434bc83805f380509642429eC8e43AE9874a" genesis: 17326 -rollup: - genlayerchainrpcurl: 'TODO: Set your GenLayer Chain ZKSync HTTP RPC URL here' - genlayerchainwebsocketurl: 'TODO: Set your GenLayer Chain ZKSync WebSocket RPC URL here' ``` @@ -390,9 +387,6 @@ rollup: consensus: consensusaddress: "0x8aCE036C8C3C5D603dB546b031302FCf149648E8" genesis: 501711 -rollup: - genlayerchainrpcurl: 'TODO: Set your GenLayer Chain ZKSync HTTP RPC URL here' - genlayerchainwebsocketurl: 'TODO: Set your GenLayer Chain ZKSync WebSocket RPC URL here' ``` diff --git a/scripts/lint-code-examples.py b/scripts/lint-code-examples.py new file mode 100644 index 000000000..c4ace4b0c --- /dev/null +++ b/scripts/lint-code-examples.py @@ -0,0 +1,437 @@ +#!/usr/bin/env python3 +"""Extract Python code blocks from MDX files and lint them with genvm-lint. + +Extracts complete contracts (those with a Depends header) from documentation, +writes them to temp files, and runs genvm-lint on each. + +Usage: + python scripts/lint-code-examples.py [--fix-version VERSION] [--lint-snippets] + +Checks: + 1. Complete contracts must not use "test" or "latest" as runner hash + 2. All complete contracts must pass genvm-lint lint (AST checks) + 3. Runner versions should be consistent across all examples + 4. GenLayer snippets must be valid Python (syntax check) + 5. Method snippets wrapped in contracts must pass lint (--lint-snippets) +""" + +import argparse +import ast +import re +import subprocess +import sys +import tempfile +import textwrap +from pathlib import Path + +DOCS_ROOT = Path(__file__).parent.parent / "pages" +DEPENDS_RE = re.compile(r'"Depends"\s*:\s*"([^:]+):([^"]+)"') +CODE_BLOCK_RE = re.compile(r"```py(?:thon)?[^\n]*\n(.*?)```", re.DOTALL) +DISALLOWED_HASHES = {"test", "latest"} + +# Old API → new API. Checked in both code blocks and prose. +DEPRECATED_APIS = { + "gl.eq_principle_strict_eq": "gl.eq_principle.strict_eq", + "gl.eq_principle_prompt_comparative": "gl.eq_principle.prompt_comparative", + "gl.eq_principle_prompt_non_comparative": "gl.eq_principle.prompt_non_comparative", + "gl.get_webpage": "gl.nondet.web.get / gl.nondet.web.render", + "gl.exec_prompt": "gl.nondet.exec_prompt", +} + + +def extract_python_blocks(mdx_path: Path) -> list[dict]: + """Extract Python code blocks from an MDX file. + + Returns list of dicts with 'code', 'line' (1-based), and 'is_contract' keys. + """ + content = mdx_path.read_text() + blocks = [] + + for match in CODE_BLOCK_RE.finditer(content): + code = match.group(1) + # Calculate line number + line = content[: match.start()].count("\n") + 1 + + # Check if this is a complete contract + has_depends = bool(DEPENDS_RE.search(code)) + has_contract_class = "gl.Contract" in code or "@gl.contract" in code + has_import = "from genlayer import" in code + + is_contract = has_depends or (has_import and has_contract_class) + is_genlayer = "gl." in code or "from genlayer" in code + + blocks.append( + { + "code": code, + "line": line, + "is_contract": is_contract, + "is_genlayer": is_genlayer and not is_contract, + "depends": dict(DEPENDS_RE.findall(code)), + } + ) + + return blocks + + +def check_runner_versions(blocks: list[tuple[Path, dict]]) -> list[str]: + """Check that no contract uses test/latest and versions are consistent.""" + errors = [] + runner_versions: dict[str, set[str]] = {} + + for mdx_path, block in blocks: + rel = mdx_path.relative_to(DOCS_ROOT) + for name, hash_val in block["depends"].items(): + if hash_val in DISALLOWED_HASHES: + errors.append( + f"{rel}:{block['line']}: {name} uses '{hash_val}' — " + f"pin to a specific runner hash" + ) + else: + runner_versions.setdefault(name, set()).add(hash_val) + + # Check consistency — all examples should use the same runner version + for name, versions in runner_versions.items(): + if len(versions) > 1: + errors.append( + f"Inconsistent {name} versions across examples: " + + ", ".join(sorted(versions)) + ) + + return errors + + +def lint_contract( + code: str, + mdx_path: Path, + line: int, + line_offset: int = 0, + ignore_codes: set[str] | None = None, +) -> list[str]: + """Run genvm-lint check on a code block. Returns list of error strings.""" + errors = [] + rel = mdx_path.relative_to(DOCS_ROOT) + ignore = ignore_codes or set() + + with tempfile.NamedTemporaryFile( + mode="w", suffix=".py", prefix="doc_example_", delete=False + ) as f: + f.write(code) + tmp_path = Path(f.name) + + try: + result = subprocess.run( + ["genvm-lint", "check", str(tmp_path), "--json"], + capture_output=True, + text=True, + timeout=120, + ) + + if result.returncode != 0: + import json + + try: + data = json.loads(result.stdout) + for w in data.get("warnings", []): + code_id = w.get("code", "") + if code_id in ignore: + continue + if code_id.startswith("E"): + w_line = w.get("line", "?") + if isinstance(w_line, int) and line_offset: + w_line = max(1, w_line - line_offset) + errors.append( + f"{rel}:{line}+{w_line}: [{code_id}] {w.get('msg', '')}" + ) + except json.JSONDecodeError: + errors.append(f"{rel}:{line}: genvm-lint failed: {result.stderr.strip()}") + except FileNotFoundError: + errors.append("genvm-lint not found — install with: pipx install genvm-linter") + except subprocess.TimeoutExpired: + errors.append(f"{rel}:{line}: genvm-lint timed out") + finally: + tmp_path.unlink(missing_ok=True) + + return errors + + +def classify_snippet(code: str) -> str: + """Classify a GenLayer snippet as 'method', 'function', or 'statement'.""" + stripped = code.strip() + lines = stripped.split("\n") + first_line = lines[0].strip() + + # Method: has @gl.public decorator or def with self + if first_line.startswith("@gl.public") or first_line.startswith("@gl.evm"): + return "method" + for line in lines: + line_s = line.strip() + if line_s.startswith("def ") and "(self" in line_s: + return "method" + + # Function: starts with def + if first_line.startswith("def ") or ( + first_line.startswith("@") and any(l.strip().startswith("def ") for l in lines) + ): + return "function" + + # Class definition + if first_line.startswith("class "): + return "class-def" + + return "statement" + + +def wrap_snippet(code: str, snippet_type: str, runner_hash: str) -> str: + """Wrap a snippet in a minimal contract shell for linting.""" + header = ( + "# {\n" + '# "Seq": [\n' + f'# {{ "Depends": "py-genlayer:{runner_hash}" }}\n' + "# ]\n" + "# }\n" + "from genlayer import *\n" + "import json\n\n" + ) + + if snippet_type == "method": + indented = textwrap.indent(code, " ") + return header + "@gl.contract\nclass _DocSnippet(gl.Contract):\n" + indented + + if snippet_type == "class-def": + return header + code + "\n\n@gl.contract\nclass _DocSnippet(gl.Contract):\n pass\n" + + if snippet_type == "function": + return header + code + "\n\n@gl.contract\nclass _DocSnippet(gl.Contract):\n pass\n" + + # statement — wrap inside a method + indented = textwrap.indent(code, " ") + return ( + header + + "@gl.contract\nclass _DocSnippet(gl.Contract):\n" + + " @gl.public.view\n" + + " def _check(self):\n" + + indented + ) + + +def syntax_check(code: str) -> str | None: + """Check if code is valid Python. Returns error message or None.""" + try: + ast.parse(code) + return None + except SyntaxError as e: + return f"line {e.lineno}: {e.msg}" + + +SNIPPET_IGNORE_CODES = { + "E010", # nondet outside eq_principle — snippets show functions in isolation + "E011", # multiple contracts in module — wrapper adds a second contract class + "E020", # view methods need return type — wrapper artifact +} + + +def lint_snippet(code: str, snippet_type: str, runner_hash: str, mdx_path: Path, line: int) -> list[str]: + """Wrap a snippet and lint it. Returns error strings.""" + wrapped = wrap_snippet(code, snippet_type, runner_hash) + # Calculate wrapper line offset (lines added before the snippet code) + wrapper_lines = wrapped.split("\n") + first_code_line = code.strip().split("\n")[0].strip() + offset = 0 + for i, wl in enumerate(wrapper_lines): + if first_code_line in wl: + offset = i + break + + return lint_contract( + wrapped, mdx_path, line, line_offset=offset, ignore_codes=SNIPPET_IGNORE_CODES + ) + + +def check_deprecated_apis(mdx_path: Path) -> list[str]: + """Check for deprecated API usage in both code blocks and prose.""" + errors = [] + rel = mdx_path.relative_to(DOCS_ROOT) + content = mdx_path.read_text() + + for i, line_text in enumerate(content.split("\n"), 1): + for old_api, new_api in DEPRECATED_APIS.items(): + if old_api in line_text: + errors.append( + f"{rel}:{i}: deprecated API '{old_api}' → use '{new_api}'" + ) + + return errors + + +def fix_versions(version_hash: str, blocks: list[tuple[Path, dict]]): + """Replace test/latest hashes with a specific version in MDX files.""" + files_to_fix: dict[Path, list[tuple[str, str]]] = {} + + for mdx_path, block in blocks: + for name, hash_val in block["depends"].items(): + if hash_val in DISALLOWED_HASHES: + files_to_fix.setdefault(mdx_path, []).append((name, hash_val)) + + for mdx_path, replacements in files_to_fix.items(): + content = mdx_path.read_text() + for name, old_hash in replacements: + content = content.replace(f'"{name}:{old_hash}"', f'"{name}:{version_hash}"') + mdx_path.write_text(content) + rel = mdx_path.relative_to(DOCS_ROOT) + print(f" Fixed {rel}: {len(replacements)} hash(es) → {version_hash}") + + +def main(): + parser = argparse.ArgumentParser(description="Lint Python code examples in docs") + parser.add_argument( + "--fix-version", + help="Replace test/latest hashes with this version (e.g., the latest runner hash)", + ) + parser.add_argument( + "--check-versions-only", + action="store_true", + help="Only check runner versions, skip AST lint", + ) + parser.add_argument( + "--lint-snippets", + action="store_true", + help="Also lint GenLayer snippets (wrapped in fake contracts)", + ) + args = parser.parse_args() + + # Collect all code blocks + all_contracts: list[tuple[Path, dict]] = [] + all_snippets: list[tuple[Path, dict]] = [] + total_blocks = 0 + + for mdx_file in sorted(DOCS_ROOT.rglob("*.mdx")): + # Skip unmigrated advanced examples + if "/_advanced/" in str(mdx_file) or "/_" in mdx_file.name: + continue + + blocks = extract_python_blocks(mdx_file) + total_blocks += len(blocks) + + for block in blocks: + if block["is_contract"]: + all_contracts.append((mdx_file, block)) + elif block["is_genlayer"]: + all_snippets.append((mdx_file, block)) + + print( + f"Found {total_blocks} Python code blocks, " + f"{len(all_contracts)} complete contracts, " + f"{len(all_snippets)} GenLayer snippets" + ) + print() + + # Fix versions if requested + if args.fix_version: + print(f"Fixing runner hashes to: {args.fix_version}") + fix_versions(args.fix_version, all_contracts) + print() + + # Re-parse after fixing + all_contracts = [] + for mdx_file in sorted(DOCS_ROOT.rglob("*.mdx")): + if "/_advanced/" in str(mdx_file) or "/_" in mdx_file.name: + continue + for block in extract_python_blocks(mdx_file): + if block["is_contract"]: + all_contracts.append((mdx_file, block)) + + errors = [] + + # Check runner versions + print("Checking runner versions...") + version_errors = check_runner_versions(all_contracts) + errors.extend(version_errors) + for e in version_errors: + print(f" ✗ {e}") + if not version_errors: + print(" ✓ All runner versions OK") + print() + + # Check for deprecated API usage + print("Checking for deprecated APIs...") + deprecated_files = set() + for mdx_file in sorted(DOCS_ROOT.rglob("*.mdx")): + if "/_advanced/" in str(mdx_file) or "/_" in mdx_file.name: + continue + dep_errors = check_deprecated_apis(mdx_file) + if dep_errors: + deprecated_files.add(mdx_file) + for e in dep_errors: + print(f" ✗ {e}") + errors.extend(dep_errors) + if not deprecated_files: + print(" ✓ No deprecated APIs found") + print() + + # Lint contracts + if not args.check_versions_only: + print("Running AST lint on complete contracts...") + for mdx_path, block in all_contracts: + rel = mdx_path.relative_to(DOCS_ROOT) + lint_errors = lint_contract(block["code"], mdx_path, block["line"]) + if lint_errors: + for e in lint_errors: + print(f" ✗ {e}") + errors.extend(lint_errors) + else: + print(f" ✓ {rel}:{block['line']}") + print() + + # Syntax-check GenLayer snippets + if all_snippets: + print("Syntax-checking GenLayer snippets...") + syntax_errors = 0 + for mdx_path, block in all_snippets: + rel = mdx_path.relative_to(DOCS_ROOT) + err = syntax_check(block["code"]) + if err: + msg = f"{rel}:{block['line']}: syntax error: {err}" + print(f" ✗ {msg}") + errors.append(msg) + syntax_errors += 1 + if not syntax_errors: + print(f" ✓ All {len(all_snippets)} snippets parse OK") + print() + + # Lint wrapped snippets + if args.lint_snippets and all_snippets and not args.check_versions_only: + # Get runner hash from first contract for the wrapper + runner_hash = "test" + for _, block in all_contracts: + if block["depends"].get("py-genlayer"): + runner_hash = block["depends"]["py-genlayer"] + break + + print("Linting wrapped GenLayer snippets...") + snippet_warnings = 0 + for mdx_path, block in all_snippets: + rel = mdx_path.relative_to(DOCS_ROOT) + snippet_type = classify_snippet(block["code"]) + lint_errors = lint_snippet( + block["code"], snippet_type, runner_hash, mdx_path, block["line"] + ) + if lint_errors: + for e in lint_errors: + print(f" ⚠ {e}") + snippet_warnings += 1 + # Don't add to errors — these are advisory for now + else: + print(f" ✓ {rel}:{block['line']} ({snippet_type})") + if snippet_warnings: + print(f" {snippet_warnings} snippet(s) had lint issues (advisory)") + print() + + if errors: + print(f"✗ {len(errors)} issue(s) found") + sys.exit(1) + else: + print("✓ All checks passed") + + +if __name__ == "__main__": + main()