docs: add complete examples and best practices for contract interactions#401
Conversation
- Add complete working example: TokenGatedVault contract - Add error handling section with try/catch examples - Add common mistakes section (view/emit confusion, return values, ordering) - Add best practices (static typing, address validation, reentrancy protection, finalization) - Add testing examples with pytest This enhancement provides developers with real-world examples and patterns instead of just syntax fragments, greatly improving the onboarding experience for contract-to-contract interactions.
✅ Deploy Preview for genlayer-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughWalkthroughThe documentation page is enhanced with comprehensive Python code examples and best practices for interacting with intelligent contracts, including contract interface patterns, error handling, anti-patterns, and pytest test examples. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 60 minutes.Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
pages/developers/intelligent-contracts/features/interacting-with-intelligent-contracts.mdx (1)
104-105: ⚡ Quick winConsider adding one sentence that highlights the “Intelligent Blockchain” angle.
This new section is strong technically; adding a short note about internet-access + subjective-decision use cases would better align with GenLayer’s differentiator and onboarding narrative.
Based on learnings: "The documentation should emphasize GenLayer's unique aspects as an 'Intelligent Blockchain' that enables contracts to access the internet and make subjective decisions beyond traditional deterministic logic."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pages/developers/intelligent-contracts/features/interacting-with-intelligent-contracts.mdx` around lines 104 - 105, Add a single sentence after the line "Here's a complete example showing a token contract interacting with another contract:" that explicitly calls out GenLayer as an "Intelligent Blockchain" and notes that contracts can access the internet and make subjective decisions (e.g., off-chain data, human-in-the-loop or ML-based inputs) to enable richer, non-deterministic workflows; place the sentence in the same section heading "Interacting with intelligent contracts" to tie the example to GenLayer’s unique internet-access and subjective-decision capabilities.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@pages/developers/intelligent-contracts/features/interacting-with-intelligent-contracts.mdx`:
- Around line 146-160: The withdraw function is unsafe because it lets any
address with token balance >= min_balance withdraw from the shared stored_value;
change it to track per-user deposits and enforce sender-specific limits: add a
mapping (e.g., deposits or balances) keyed by address to record each depositor's
deposited amount when they deposit, then in withdraw (the withdraw method using
TokenInterface and token.view().balance_of and reading stored_value) check that
deposits[gl.message.sender_address] >= amount, decrement both
deposits[gl.message.sender_address] and stored_value by amount, and only then
call gl.transfer(gl.message.sender_address, amount); this ties withdrawals to
who deposited and prevents any qualifying token holder from draining pooled
funds.
- Around line 141-178: The snippets use gl.UserError but the project standard is
gl.vm.UserError; update all occurrences to gl.vm.UserError (e.g., the raise in
the deposit/withdraw methods and the exception handlers in safe_transfer) so the
examples match the documented error type—search for symbols like withdraw,
deposit, safe_transfer and replace gl.UserError with gl.vm.UserError everywhere
(including the other referenced snippets at lines noted) to ensure consistency.
- Around line 197-207: The "CORRECT" example incorrectly uses
other.view().transfer(...) for a write operation; replace it with the documented
asynchronous write pattern using emit(), e.g. call
other.emit(on='finalized').transfer(recipient, amount) and then check the result
(assign to success and raise gl.UserError("Transfer failed") if not success) so
the example aligns with the Write interface and the Error Handling section.
---
Nitpick comments:
In
`@pages/developers/intelligent-contracts/features/interacting-with-intelligent-contracts.mdx`:
- Around line 104-105: Add a single sentence after the line "Here's a complete
example showing a token contract interacting with another contract:" that
explicitly calls out GenLayer as an "Intelligent Blockchain" and notes that
contracts can access the internet and make subjective decisions (e.g., off-chain
data, human-in-the-loop or ML-based inputs) to enable richer, non-deterministic
workflows; place the sentence in the same section heading "Interacting with
intelligent contracts" to tie the example to GenLayer’s unique internet-access
and subjective-decision capabilities.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 92940788-9edd-4513-b4a6-ddb867801e8a
📒 Files selected for processing (1)
pages/developers/intelligent-contracts/features/interacting-with-intelligent-contracts.mdx
| raise gl.UserError(f"Insufficient token balance. Need {self.min_balance}, have {balance}") | ||
|
|
||
| # Accept the deposit | ||
| self.stored_value += gl.message.value | ||
|
|
||
| @gl.public.write | ||
| def withdraw(self, amount: u256): | ||
| """Withdraw GEN from the vault (requires token balance)""" | ||
| token = TokenInterface(self.token_address) | ||
| balance = token.view().balance_of(gl.message.sender_address) | ||
|
|
||
| if balance < self.min_balance: | ||
| raise gl.UserError("Insufficient token balance") | ||
|
|
||
| if amount > self.stored_value: | ||
| raise gl.UserError("Insufficient vault balance") | ||
|
|
||
| self.stored_value -= amount | ||
| gl.transfer(gl.message.sender_address, amount) | ||
| ``` | ||
|
|
||
| ## Error Handling | ||
|
|
||
| Always handle errors when calling other contracts: | ||
|
|
||
| ```python | ||
| @gl.public.write | ||
| def safe_transfer(self, token_addr: Address, to: Address, amount: u256): | ||
| """Transfer tokens with proper error handling""" | ||
| try: | ||
| token = gl.get_contract_at(token_addr) | ||
| token.emit(on='finalized').transfer(to, amount) | ||
| except gl.UserError as e: | ||
| # Handle contract-specific errors | ||
| raise gl.UserError(f"Transfer failed: {e}") | ||
| except Exception as e: | ||
| # Handle unexpected errors | ||
| raise gl.UserError(f"Unexpected error: {e}") |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify UserError naming consistency across intelligent-contract docs
rg -n "gl\.UserError|gl\.vm\.UserError" pages/developers/intelligent-contracts/featuresRepository: genlayerlabs/genlayer-docs
Length of output: 3511
🏁 Script executed:
#!/bin/bash
# Check error-handling.mdx to see how errors are documented
cat pages/developers/intelligent-contracts/features/error-handling.mdxRepository: genlayerlabs/genlayer-docs
Length of output: 3401
🏁 Script executed:
#!/bin/bash
# Search for import statements and SDK references that clarify the API
rg -n "from genvm|import.*genvm|import.*gl" pages/developers/intelligent-contracts/features -B2 -A2 | head -100Repository: genlayerlabs/genlayer-docs
Length of output: 52
Standardize on the documented gl.vm.UserError type across all snippets.
This page uses gl.UserError while the error-handling documentation and other reference pages use gl.vm.UserError, creating inconsistency that breaks reader confidence when cross-referencing examples.
Suggested doc fix
-raise gl.UserError("...")
+raise gl.vm.UserError("...")
-except gl.UserError as e:
+except gl.vm.UserError as e:
-with pytest.raises(gl.UserError, match="..."):
+with pytest.raises(gl.vm.UserError, match="..."):Also applies to: lines 206, 246, 254, 271, 323
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@pages/developers/intelligent-contracts/features/interacting-with-intelligent-contracts.mdx`
around lines 141 - 178, The snippets use gl.UserError but the project standard
is gl.vm.UserError; update all occurrences to gl.vm.UserError (e.g., the raise
in the deposit/withdraw methods and the exception handlers in safe_transfer) so
the examples match the documented error type—search for symbols like withdraw,
deposit, safe_transfer and replace gl.UserError with gl.vm.UserError everywhere
(including the other referenced snippets at lines noted) to ensure consistency.
| @gl.public.write | ||
| def withdraw(self, amount: u256): | ||
| """Withdraw GEN from the vault (requires token balance)""" | ||
| token = TokenInterface(self.token_address) | ||
| balance = token.view().balance_of(gl.message.sender_address) | ||
|
|
||
| if balance < self.min_balance: | ||
| raise gl.UserError("Insufficient token balance") | ||
|
|
||
| if amount > self.stored_value: | ||
| raise gl.UserError("Insufficient vault balance") | ||
|
|
||
| self.stored_value -= amount | ||
| gl.transfer(gl.message.sender_address, amount) | ||
| ``` |
There was a problem hiding this comment.
withdraw allows any qualifying token holder to drain pooled funds.
In this “complete working example,” withdrawal is not tied to who deposited. Any address meeting min_balance can withdraw up to stored_value, which is a serious security pitfall for readers who reuse this pattern.
Suggested doc fix (track per-user balances)
class TokenGatedVault(gl.Contract):
@@
- stored_value: u256
+ stored_value: u256
+ balances: TreeMap[Address, u256]
@@
def deposit(self):
@@
self.stored_value += gl.message.value
+ sender = gl.message.sender_address
+ self.balances[sender] = self.balances.get(sender, u256(0)) + gl.message.value
@@
def withdraw(self, amount: u256):
@@
+ sender = gl.message.sender_address
+ user_balance = self.balances.get(sender, u256(0))
+ if amount > user_balance:
+ raise gl.vm.UserError("Insufficient user balance")
if amount > self.stored_value:
- raise gl.UserError("Insufficient vault balance")
+ raise gl.vm.UserError("Insufficient vault balance")
self.stored_value -= amount
- gl.transfer(gl.message.sender_address, amount)
+ self.balances[sender] = user_balance - amount
+ gl.transfer(sender, amount)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@pages/developers/intelligent-contracts/features/interacting-with-intelligent-contracts.mdx`
around lines 146 - 160, The withdraw function is unsafe because it lets any
address with token balance >= min_balance withdraw from the shared stored_value;
change it to track per-user deposits and enforce sender-specific limits: add a
mapping (e.g., deposits or balances) keyed by address to record each depositor's
deposited amount when they deposit, then in withdraw (the withdraw method using
TokenInterface and token.view().balance_of and reading stored_value) check that
deposits[gl.message.sender_address] >= amount, decrement both
deposits[gl.message.sender_address] and stored_value by amount, and only then
call gl.transfer(gl.message.sender_address, amount); this ties withdrawals to
who deposited and prevents any qualifying token holder from draining pooled
funds.
| ```python | ||
| # WRONG - Not checking if the call succeeded | ||
| other.view().transfer(recipient, amount) | ||
| ``` | ||
|
|
||
| ```python | ||
| # CORRECT - Check the return value | ||
| success = other.view().transfer(recipient, amount) | ||
| if not success: | ||
| raise gl.UserError("Transfer failed") | ||
| ``` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "interacting-with-intelligent-contracts.mdx" -type fRepository: genlayerlabs/genlayer-docs
Length of output: 163
🏁 Script executed:
wc -l ./pages/developers/intelligent-contracts/features/interacting-with-intelligent-contracts.mdxRepository: genlayerlabs/genlayer-docs
Length of output: 167
🏁 Script executed:
sed -n '180,220p' ./pages/developers/intelligent-contracts/features/interacting-with-intelligent-contracts.mdxRepository: genlayerlabs/genlayer-docs
Length of output: 969
🏁 Script executed:
sed -n '1,180p' ./pages/developers/intelligent-contracts/features/interacting-with-intelligent-contracts.mdx | tail -100Repository: genlayerlabs/genlayer-docs
Length of output: 3175
🏁 Script executed:
sed -n '1,196p' ./pages/developers/intelligent-contracts/features/interacting-with-intelligent-contracts.mdx | grep -A 20 -B 5 "view\|emit" | head -80Repository: genlayerlabs/genlayer-docs
Length of output: 2994
The "CORRECT" example uses view() for a write operation, contradicting the page's documented guidance.
transfer() is a write method (defined in the Write interface class). The page explicitly documents that view() is for synchronous reads and emit() is for asynchronous writes. The earlier mistake example correctly enforces this pattern; the "transfer" example violates it. The Error Handling section on the same page demonstrates the correct approach: token.emit(on='finalized').transfer(to, amount).
Suggested fix
-# CORRECT - Check the return value
-success = other.view().transfer(recipient, amount)
-if not success:
- raise gl.UserError("Transfer failed")
+# CORRECT - Use emit() for write operations
+other.emit(on='finalized').transfer(recipient, amount)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ```python | |
| # WRONG - Not checking if the call succeeded | |
| other.view().transfer(recipient, amount) | |
| ``` | |
| ```python | |
| # CORRECT - Check the return value | |
| success = other.view().transfer(recipient, amount) | |
| if not success: | |
| raise gl.UserError("Transfer failed") | |
| ``` |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@pages/developers/intelligent-contracts/features/interacting-with-intelligent-contracts.mdx`
around lines 197 - 207, The "CORRECT" example incorrectly uses
other.view().transfer(...) for a write operation; replace it with the documented
asynchronous write pattern using emit(), e.g. call
other.emit(on='finalized').transfer(recipient, amount) and then check the result
(assign to success and raise gl.UserError("Transfer failed") if not success) so
the example aligns with the Write interface and the Error Handling section.
Summary
Enhances the "Interacting with Intelligent Contracts" documentation with complete working examples, error handling patterns, common mistakes, and best practices.
Changes
TokenGatedVaultcontract demonstrating real-world contract-to-contract interactionsImpact
This PR significantly improves developer onboarding by providing complete, copy-paste ready examples instead of just syntax fragments. New developers can now see:
Files Changed
pages/developers/intelligent-contracts/features/interacting-with-intelligent-contracts.mdx(+226 lines)Related Issues
Addresses part of the ecosystem improvements discussed in #346 (adding more complete examples and best practices guides).
Summary by CodeRabbit