-
-
Notifications
You must be signed in to change notification settings - Fork 16
Feat/fork choice #95
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?
Feat/fork choice #95
Changes from all commits
e232e2c
8e80004
1998d91
8423405
0812e46
e556935
116a09b
87b0d96
ce2cd52
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 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -101,6 +101,26 @@ It is encouraged that you develop an initial prototype during the application ph | |||||||||||
| * Read this book: https://www.marabu.dev/blockchain-foundations.pdf | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| --- | ||||||||||||
|
|
||||||||||||
| ## Smart Contracts | ||||||||||||
|
|
||||||||||||
| MiniChain supports fully-functional smart contracts written directly in Python! | ||||||||||||
| The execution engine uses `sys.settrace` for precise **Gas Metering** (charging 1 gas per executed opcode) and `multiprocessing` for **Sandboxed Execution** to ensure network security. | ||||||||||||
|
|
||||||||||||
| ### Writing a Contract | ||||||||||||
| Smart contracts in MiniChain have access to a persistent `storage` dictionary and a `msg` dictionary containing transaction context (`sender`, `value`, `data`). | ||||||||||||
|
|
||||||||||||
| Check out the `/examples` directory for tutorials: | ||||||||||||
| - `examples/counter.py` - A basic state mutation example. | ||||||||||||
| - `examples/stablecoin.py` - A minimal ERC-20 style fungible token. | ||||||||||||
| - `examples/dex.py` - An Automated Market Maker (AMM) using the constant product formula (x * y = k). | ||||||||||||
|
|
||||||||||||
| ### Interacting via CLI | ||||||||||||
|
Comment on lines
+118
to
+119
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. Add blank line before heading for better readability. Markdown best practice requires blank lines surrounding headings. 📝 Proposed fix - `examples/dex.py` - An Automated Market Maker (AMM) using the constant product formula (x * y = k).
+
### Interacting via CLI📝 Committable suggestion
Suggested change
🧰 Tools🪛 markdownlint-cli2 (0.22.1)[warning] 119-119: Headings should be surrounded by blank lines (MD022, blanks-around-headings) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||
| Start the interactive node using `python main.py` and use the following commands: | ||||||||||||
| 1. **Deploy:** `deploy <filepath> [amount] [gas_limit]` | ||||||||||||
| 2. **Call:** `call <contract_address> <payload> [amount] [gas_limit]` | ||||||||||||
|
|
||||||||||||
| --- | ||||||||||||
|
|
||||||||||||
| ## Tech Stack | ||||||||||||
|
|
||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # Counter Smart Contract Example | ||
| # | ||
| # This is a simple counter contract designed to demonstrate the basic | ||
| # structure of smart contracts in MiniChain. | ||
| # | ||
| # Available built-ins in the MiniChain Sandbox: | ||
| # - `storage`: A dictionary persisting state across executions. | ||
| # - `msg`: A dictionary containing transaction info: | ||
| # - `msg['sender']` : The address of the caller. | ||
| # - `msg['value']` : The amount of coins attached to the call. | ||
| # - `msg['data']` : The payload string. | ||
| # | ||
| # Available functions: range(), len(), min(), max(), abs(), str(), bool(), float(), int(), list(), dict(), tuple(), sum() | ||
| # | ||
| # NOTE: The sandbox does NOT allow imports, print(), or any double-underscore methods. | ||
|
|
||
| if msg['data'] == 'increment': | ||
| # Retrieve the current counter value, defaulting to 0 if it doesn't exist | ||
| current_value = storage.get('counter', 0) | ||
|
|
||
| # Increment the counter | ||
| storage['counter'] = current_value + 1 | ||
|
|
||
| elif msg['data'] == 'decrement': | ||
| current_value = storage.get('counter', 0) | ||
| storage['counter'] = current_value - 1 | ||
|
|
||
| elif msg['data'] == 'reset': | ||
| # You can restrict who can reset the counter by checking the sender! | ||
| # (Just an example, anyone can call this one) | ||
| storage['counter'] = 0 | ||
|
|
||
| else: | ||
| # If the payload doesn't match any known command, raise an exception. | ||
| # This will fail the transaction and refund the 'amount' to the sender, | ||
| # but the network will keep the 'fee' as gas. | ||
| raise Exception("Unknown command. Valid commands: increment, decrement, reset") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| # MiniSwap (DEX) Smart Contract Example | ||
| # | ||
| # This contract implements a minimal Automated Market Maker (AMM) | ||
| # using the x * y = k constant product formula. | ||
| # It trades the native MiniChain coin (msg['value']) against a minted DEX Token. | ||
| # | ||
| # Valid Payloads: | ||
| # - 'init' (Must send initial native coins to provide liquidity) | ||
| # - 'buy' (Sends native coins, receives DEX tokens) | ||
| # - 'sell:<amount>' (Sells DEX tokens, receives native coins) | ||
| # | ||
| # Note: Since native coins sent to the contract are automatically added to the | ||
| # contract's balance by the state manager BEFORE execution, msg['value'] is already | ||
| # inside the contract's physical balance. | ||
|
|
||
| if msg['data'] == 'init': | ||
| # Initialize the liquidity pool | ||
| if storage.get('k') is not None: | ||
| raise Exception("Already initialized") | ||
| if msg['value'] <= 0: | ||
| raise Exception("Must provide initial native coin liquidity") | ||
|
|
||
| # We will arbitrarily mint 1000 DEX tokens to match the initial coin liquidity | ||
| storage['native_reserve'] = msg['value'] | ||
| storage['token_reserve'] = 1000 | ||
| storage['k'] = storage['native_reserve'] * storage['token_reserve'] | ||
|
|
||
| # Give the initial tokens to the creator | ||
| storage[msg['sender']] = 1000 | ||
|
|
||
| elif msg['data'] == 'buy': | ||
| # User sends native coins to buy DEX tokens | ||
| if storage.get('k') is None: | ||
| raise Exception("Not initialized") | ||
| if msg['value'] <= 0: | ||
| raise Exception("Must send coins to buy tokens") | ||
|
|
||
| # Calculate how many tokens to give using x * y = k | ||
| # (native_reserve + msg['value']) * (token_reserve - tokens_out) = k | ||
| new_native_reserve = storage['native_reserve'] + msg['value'] | ||
| new_token_reserve = storage['k'] // new_native_reserve | ||
|
|
||
| tokens_out = storage['token_reserve'] - new_token_reserve | ||
| if tokens_out <= 0: | ||
| raise Exception("Not enough tokens to dispense") | ||
|
|
||
| # Update reserves | ||
| storage['native_reserve'] = new_native_reserve | ||
| storage['token_reserve'] = new_token_reserve | ||
|
Comment on lines
+38
to
+49
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. Integer division breaks constant product invariant. The For an educational example, consider documenting this limitation in comments, or use a rounding strategy that preserves 🧰 Tools🪛 Ruff (0.15.15)[error] 40-40: Undefined name (F821) [error] 40-40: Undefined name (F821) [error] 41-41: Undefined name (F821) [error] 43-43: Undefined name (F821) [warning] 45-45: Create your own exception (TRY002) [warning] 45-45: Avoid specifying long messages outside the exception class (TRY003) [error] 48-48: Undefined name (F821) [error] 49-49: Undefined name (F821) 🤖 Prompt for AI Agents |
||
|
|
||
| # Credit tokens to buyer | ||
| sender = msg['sender'] | ||
| storage[sender] = storage.get(sender, 0) + tokens_out | ||
|
|
||
| elif msg['data'].startswith('sell:'): | ||
| # User sells DEX tokens to get native coins back | ||
| if storage.get('k') is None: | ||
| raise Exception("Not initialized") | ||
|
|
||
| parts = msg['data'].split(':') | ||
| tokens_to_sell = int(parts[1]) | ||
|
Comment on lines
+60
to
+61
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. Add error handling for payload parsing. Line 61 calls 🛡️ Proposed fix parts = msg['data'].split(':')
- tokens_to_sell = int(parts[1])
+ try:
+ tokens_to_sell = int(parts[1])
+ except (ValueError, IndexError):
+ raise Exception("Invalid sell format. Use: sell:<amount>")🧰 Tools🪛 Ruff (0.15.15)[error] 60-60: Undefined name (F821) 🤖 Prompt for AI Agents |
||
|
|
||
| sender = msg['sender'] | ||
| sender_tokens = storage.get(sender, 0) | ||
| if sender_tokens < tokens_to_sell: | ||
| raise Exception("Insufficient token balance") | ||
|
|
||
| # Deduct tokens from user | ||
| storage[sender] -= tokens_to_sell | ||
|
|
||
| # Calculate how many native coins to give using x * y = k | ||
| # (token_reserve + tokens_to_sell) * (native_reserve - coins_out) = k | ||
| new_token_reserve = storage['token_reserve'] + tokens_to_sell | ||
| new_native_reserve = storage['k'] // new_token_reserve | ||
|
|
||
| coins_out = storage['native_reserve'] - new_native_reserve | ||
| if coins_out <= 0: | ||
| raise Exception("Not enough coins to dispense") | ||
|
|
||
| # Update reserves | ||
| storage['native_reserve'] = new_native_reserve | ||
| storage['token_reserve'] = new_token_reserve | ||
|
|
||
| # Wait! In MiniChain, smart contracts cannot arbitrarily initiate outgoing transactions yet. | ||
| # To properly implement 'sell', the contract engine would need a 'transfer_out' API. | ||
| # For now, we will just record their native coin balance in storage. | ||
| storage[f"{sender}_native_credit"] = storage.get(f"{sender}_native_credit", 0) + coins_out | ||
|
|
||
| else: | ||
| raise Exception("Unknown command.") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # Stablecoin (ERC-20 style) Smart Contract Example | ||
| # | ||
| # This contract implements a minimal fungible token. | ||
| # | ||
| # Valid Payloads: | ||
| # - 'mint:<amount>' | ||
| # - 'transfer:<recipient_address>:<amount>' | ||
|
|
||
| if msg['data'].startswith('mint:'): | ||
| # In a real contract, you would restrict this to an owner address! | ||
| # For this example, anyone can mint tokens to themselves. | ||
| amount = int(msg['data'].split(':')[1]) | ||
| if amount <= 0: | ||
| raise Exception("Amount must be positive") | ||
|
Comment on lines
+9
to
+14
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. Add error handling for payload parsing. Line 12 calls 🛡️ Proposed fix if msg['data'].startswith('mint:'):
# In a real contract, you would restrict this to an owner address!
# For this example, anyone can mint tokens to themselves.
- amount = int(msg['data'].split(':')[1])
+ try:
+ amount = int(msg['data'].split(':')[1])
+ except (ValueError, IndexError):
+ raise Exception("Invalid mint format. Use: mint:<amount>")
if amount <= 0:🧰 Tools🪛 Ruff (0.15.15)[error] 9-9: Undefined name (F821) [error] 12-12: Undefined name (F821) [warning] 14-14: Create your own exception (TRY002) [warning] 14-14: Avoid specifying long messages outside the exception class (TRY003) 🤖 Prompt for AI Agents |
||
|
|
||
| sender = msg['sender'] | ||
| storage[sender] = storage.get(sender, 0) + amount | ||
| storage['total_supply'] = storage.get('total_supply', 0) + amount | ||
|
|
||
| elif msg['data'].startswith('transfer:'): | ||
| parts = msg['data'].split(':') | ||
| if len(parts) != 3: | ||
| raise Exception("Invalid transfer format") | ||
|
|
||
| to_address = parts[1] | ||
| amount = int(parts[2]) | ||
|
|
||
| if amount <= 0: | ||
| raise Exception("Amount must be positive") | ||
|
Comment on lines
+20
to
+29
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. Add error handling for amount parsing. Line 26 calls 🛡️ Proposed fix to_address = parts[1]
- amount = int(parts[2])
+ try:
+ amount = int(parts[2])
+ except ValueError:
+ raise Exception("Invalid amount format. Must be an integer.")
if amount <= 0:🧰 Tools🪛 Ruff (0.15.15)[error] 20-20: Undefined name (F821) [error] 21-21: Undefined name (F821) [warning] 23-23: Create your own exception (TRY002) [warning] 23-23: Avoid specifying long messages outside the exception class (TRY003) [warning] 29-29: Create your own exception (TRY002) [warning] 29-29: Avoid specifying long messages outside the exception class (TRY003) 🤖 Prompt for AI Agents |
||
|
|
||
| sender = msg['sender'] | ||
| sender_balance = storage.get(sender, 0) | ||
|
|
||
| if sender_balance >= amount: | ||
| storage[sender] -= amount | ||
| storage[to_address] = storage.get(to_address, 0) + amount | ||
| else: | ||
| raise Exception("Insufficient token balance") | ||
|
|
||
| else: | ||
| raise Exception("Unknown command. Valid commands: mint:<amount>, transfer:<to>:<amount>") | ||
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.
Add blank line before heading for better readability.
Markdown best practice requires blank lines surrounding headings.
📝 Proposed fix
The execution engine uses `sys.settrace` for precise **Gas Metering** (charging 1 gas per executed opcode) and `multiprocessing` for **Sandboxed Execution** to ensure network security. + ### Writing a Contract🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 111-111: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
🤖 Prompt for AI Agents
Source: Linters/SAST tools