-
Notifications
You must be signed in to change notification settings - Fork 17
docs: add complete examples and best practices for contract interactions #401
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 | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -98,3 +98,229 @@ def __init__(self, num_workers: int): | |||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Child contracts are immutable after deployment. To update worker logic, redeploy through the factory. | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ## Complete Working Example | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Here's a complete example showing a token contract interacting with another contract: | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||
| from genlayer import * | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @gl.contract_interface | ||||||||||||||||||||||||
| class TokenInterface: | ||||||||||||||||||||||||
| class View: | ||||||||||||||||||||||||
| def balance_of(self, account: Address) -> u256: ... | ||||||||||||||||||||||||
| def total_supply(self) -> u256: ... | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| class Write: | ||||||||||||||||||||||||
| def transfer(self, to: Address, amount: u256): ... | ||||||||||||||||||||||||
| def approve(self, spender: Address, amount: u256): ... | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| class TokenGatedVault(gl.Contract): | ||||||||||||||||||||||||
| """A vault that requires minimum token balance to access""" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| token_address: Address | ||||||||||||||||||||||||
| min_balance: u256 | ||||||||||||||||||||||||
| stored_value: u256 | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def __init__(self, token_addr: Address, min_bal: u256): | ||||||||||||||||||||||||
| self.token_address = token_addr | ||||||||||||||||||||||||
| self.min_balance = min_bal | ||||||||||||||||||||||||
| self.stored_value = u256(0) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @gl.public.write.payable | ||||||||||||||||||||||||
| def deposit(self): | ||||||||||||||||||||||||
| """Deposit GEN into the vault (requires token balance)""" | ||||||||||||||||||||||||
| # Get token contract reference | ||||||||||||||||||||||||
| token = TokenInterface(self.token_address) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # Check caller's token balance | ||||||||||||||||||||||||
| balance = token.view().balance_of(gl.message.sender_address) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if balance < self.min_balance: | ||||||||||||||||||||||||
| 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}") | ||||||||||||||||||||||||
|
Comment on lines
+141
to
+178
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
# 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 This page uses 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 |
||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ## Common Mistakes | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ### ❌ Don't: Call view() on emit() | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||
| # WRONG - view() is for reading, emit() is for writing | ||||||||||||||||||||||||
| other.view().update_status("active") # This will fail! | ||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||
| # CORRECT | ||||||||||||||||||||||||
| other.emit(on='finalized').update_status("active") | ||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ### ❌ Don't: Forget to check return values | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ```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") | ||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||
|
Comment on lines
+197
to
+207
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: 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
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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ### ❌ Don't: Use accepted when order matters | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||
| # WRONG - These might execute out of order or multiple times | ||||||||||||||||||||||||
| other.emit(on='accepted').step_one() | ||||||||||||||||||||||||
| other.emit(on='accepted').step_two() | ||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||
| # CORRECT - Use finalized for sequential operations | ||||||||||||||||||||||||
| other.emit(on='finalized').step_one() | ||||||||||||||||||||||||
| other.emit(on='finalized').step_two() | ||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ## Best Practices | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ### 1. Use Static Typing for Better IDE Support | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||
| # Preferred: Define interface upfront | ||||||||||||||||||||||||
| @gl.contract_interface | ||||||||||||||||||||||||
| class MyContractInterface: | ||||||||||||||||||||||||
| class View: | ||||||||||||||||||||||||
| def get_balance(self, addr: Address) -> u256: ... | ||||||||||||||||||||||||
| class Write: | ||||||||||||||||||||||||
| def update_balance(self, addr: Address, amount: u256): ... | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| other = MyContractInterface(contract_address) | ||||||||||||||||||||||||
| # Now you get autocomplete and type checking! | ||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ### 2. Validate Contract Addresses | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||
| def __init__(self, oracle_addr: Address): | ||||||||||||||||||||||||
| # Verify the address is valid | ||||||||||||||||||||||||
| if oracle_addr == Address("0x0000000000000000000000000000000000000000"): | ||||||||||||||||||||||||
| raise gl.UserError("Invalid oracle address") | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # Verify the contract exists (optional but recommended) | ||||||||||||||||||||||||
| oracle = gl.get_contract_at(oracle_addr) | ||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||
| # Try calling a view method to verify it's the right contract | ||||||||||||||||||||||||
| oracle.view().get_version() | ||||||||||||||||||||||||
| except: | ||||||||||||||||||||||||
| raise gl.UserError("Oracle contract not found or incompatible") | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| self.oracle_address = oracle_addr | ||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ### 3. Handle Reentrancy | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||
| class Vault(gl.Contract): | ||||||||||||||||||||||||
| balances: TreeMap[Address, u256] = TreeMap() | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @gl.public.write | ||||||||||||||||||||||||
| def withdraw(self, amount: u256): | ||||||||||||||||||||||||
| # Check balance FIRST | ||||||||||||||||||||||||
| balance = self.balances[gl.message.sender_address] | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if amount > balance: | ||||||||||||||||||||||||
| raise gl.UserError("Insufficient balance") | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # Update state BEFORE external call | ||||||||||||||||||||||||
| self.balances[gl.message.sender_address] = balance - amount | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # External call last (prevents reentrancy) | ||||||||||||||||||||||||
| gl.transfer(gl.message.sender_address, amount) | ||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ### 4. Use Finalized for Critical Operations | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||
| @gl.public.write | ||||||||||||||||||||||||
| def execute_payment(self, recipient: Address, amount: u256): | ||||||||||||||||||||||||
| """Critical payment - must be finalized""" | ||||||||||||||||||||||||
| token = gl.get_contract_at(self.token_address) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # Use 'finalized' for important operations | ||||||||||||||||||||||||
| # This ensures the transaction won't be appealed | ||||||||||||||||||||||||
| token.emit(on='finalized').transfer(recipient, amount) | ||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ## Testing Contract Interactions | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Example test for contract-to-contract calls: | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||
| # In your test file | ||||||||||||||||||||||||
| def test_token_gated_vault(): | ||||||||||||||||||||||||
| # Deploy token contract first | ||||||||||||||||||||||||
| token = deploy_contract("Token", args=[u256(1000000)]) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # Deploy vault with token requirement | ||||||||||||||||||||||||
| vault = deploy_contract("TokenGatedVault", args=[ | ||||||||||||||||||||||||
| token.address, | ||||||||||||||||||||||||
| u256(100) # minimum balance | ||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # Give user some tokens | ||||||||||||||||||||||||
| token.transfer(user_address, u256(500)) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # Test deposit with sufficient balance | ||||||||||||||||||||||||
| vault.deposit(value=u256(1000)) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| assert vault.view().stored_value() == u256(1000) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # Test deposit with insufficient balance | ||||||||||||||||||||||||
| vault2 = deploy_contract("TokenGatedVault", args=[ | ||||||||||||||||||||||||
| token.address, | ||||||||||||||||||||||||
| u256(10000) # very high requirement | ||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| with pytest.raises(gl.UserError, match="Insufficient token balance"): | ||||||||||||||||||||||||
| vault2.deposit(value=u256(1000)) | ||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
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.
withdrawallows any qualifying token holder to drain pooled funds.In this “complete working example,” withdrawal is not tied to who deposited. Any address meeting
min_balancecan withdraw up tostored_value, which is a serious security pitfall for readers who reuse this pattern.Suggested doc fix (track per-user balances)
🤖 Prompt for AI Agents