Skip to content

Feat/smart contracts#92

Open
SIDDHANTCOOKIE wants to merge 7 commits into
StabilityNexus:mainfrom
SIDDHANTCOOKIE:feat/smart-contracts
Open

Feat/smart contracts#92
SIDDHANTCOOKIE wants to merge 7 commits into
StabilityNexus:mainfrom
SIDDHANTCOOKIE:feat/smart-contracts

Conversation

@SIDDHANTCOOKIE

@SIDDHANTCOOKIE SIDDHANTCOOKIE commented Jun 6, 2026

Copy link
Copy Markdown
Member

Addressed Issues:

This PR finalizes the Smart Contracts for MiniChain by introducing end-to-end execution, sandboxing, and examples.

  • CLI Integration (main.py): Added deploy and call commands to the interactive shell. Users can now deploy python files directly from the CLI and interact with them.
  • Opcode Gas Metering (contract.py): Replaced the crude timeout mechanism with a perfect instruction-level Gas Meter using Python's native sys.settrace(). Contracts now consume exactly 1 gas per opcode executed, safely killing infinite loops via OutOfGasException.
  • Economic Refunds (state.py): fee is now treated as a Maximum Gas Limit. State deductions have been updated to seamlessly refund unspent gas to the sender, and route the precise gas_used to the miner as a reward.
  • Examples (/examples): Created counter.py, stablecoin.py, and dex.py to demonstrate the capabilities of the engine, including a Uniswap-style AMM and an ERC-20 style fungible token. All examples run flawlessly within the restricted sandbox.

Screenshots/Recordings:

TODO: If applicable, add screenshots or recordings that demonstrate the interface before and after the changes.

Additional Notes:

AI Usage Disclosure:

We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact. AI slop is strongly discouraged and may lead to banning and blocking. Do not spam our repos with AI slop.

Check one of the checkboxes below:

  • This PR does not contain AI-generated code at all.
  • This PR contains AI-generated code. I have read the AI Usage Policy and this PR complies with this policy. I have tested the code locally and I am responsible for it.

I have used the following AI models and tools: TODO

Checklist

  • My PR addresses a single issue, fixes a single bug or makes a single improvement.
  • My code follows the project's code style and conventions
  • If applicable, I have made corresponding changes or additions to the documentation
  • If applicable, I have made corresponding changes or additions to tests
  • My changes generate no new warnings or errors
  • I have joined the Discord server and I will share a link to this PR with the project maintainers there
  • I have read the Contribution Guidelines
  • Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments.
  • I have filled this PR template completely and carefully, and I understand that my PR may be closed without review otherwise.

Summary by CodeRabbit

Release Notes

  • New Features

    • Smart contract deployment and execution with gas metering and limits
    • Transaction fee system for transaction prioritization
    • Transaction receipts tracking execution results and contract addresses
    • Example contracts included: counter, decentralized exchange, and stablecoin
  • Documentation

    • Updated README with smart contract usage guide and CLI command reference

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

MiniChain now supports transaction fees and smart contract gas metering via a new Receipt model. Blocks carry receipt Merkle roots and explicit receipts; contracts execute in gas-metered subprocesses; mining rewards include transaction fees; and users can deploy and call contracts via CLI. The custom Merkle Patricia Trie was replaced with a library.

Changes

Receipt Model & Transaction Fee Infrastructure

Layer / File(s) Summary
Receipt model and serialization
minichain/receipt.py
Receipt class stores transaction hash, status, gas_used, error_message, logs, and contract_address; implements to_dict() and from_dict() for serialization.
Transaction fee field
minichain/transaction.py
Transaction._TX_FIELDS includes fee, constructor accepts fee (default 0), and fee is included in signing/hashing via to_signing_dict().
Dependency and import updates
requirements.txt
Adds trie>=3.1.0, removes libp2p==0.5.0; setup.py removed.

Block & Chain Receipt Integration

Layer / File(s) Summary
Block receipt merkle and serialization
minichain/block.py
Block accepts receipt_root and receipts parameters; computes generic Merkle trees via _calculate_merkle_tree() helper; new calculate_receipt_root() function; to_header_dict() includes receipt_root; to_body_dict() includes serialized receipts; from_dict() parses and validates receipt integrity.
Chain block and receipt validation
minichain/chain.py
Chain.add_block() collects per-transaction receipts, computes total_fees from gas_used, credits miner with DEFAULT_MINING_REWARD + total_fees, validates receipt roots via calculate_receipt_root(), and rejects mismatched receipt payloads; genesis block includes empty receipts.
P2P block and transaction schema
minichain/p2p.py
Transaction validation requires fee field and allows zero amount; block validation requires receipt_root and receipts with per-receipt type checking (gas_used, error_message, contract_address).
Mempool fee-based transaction selection
minichain/mempool.py
get_transactions_for_block() ranks transactions by negative fee (highest first), then timestamp, sender, and nonce.

Contract Execution with Gas Metering & State Management

Layer / File(s) Summary
Gas metering and contract execution
minichain/contract.py
Adds GasMeter and OutOfGasException; _safe_exec_worker() traces opcodes, enforces gas limits, reports gas_used; ContractMachine.execute() gains gas_limit parameter and returns structured dicts with success, status, gas_used, and error details.
State transaction processing with receipts and fees
minichain/state.py
verify_transaction_logic() validates sender balance against amount + fee; validate_and_apply() returns None or Receipt; apply_transaction() deducts fees upfront, returns Receipt with status/error/gas_used, refunds unused contract gas on success.
Mining with fee-based reward calculation
main.py
mine_and_process_block() collects receipts, computes total fees from gas_used, credits miner with default reward plus fees; constructs blocks with state_root, receipt_root, and receipts.

Merkle Patricia Trie Library Integration

Layer / File(s) Summary
MPT library delegation
minichain/mpt.py
Replaces custom node-based implementation with wrapper around trie.HexaryTrie; delegates root hash, get/put to library with hex-to-bytes key conversion and value encoding/decoding.

Smart Contracts & User-Facing Features

Layer / File(s) Summary
Smart contract example implementations
examples/counter.py, examples/dex.py, examples/stablecoin.py
Three runnable contract examples: persistent counter with increment/decrement/reset; constant-product AMM trading native coin for DEX tokens; ERC-20–style token contract with mint and transfer operations.
CLI deploy and call commands
main.py
New deploy command reads source files and broadcasts contract-creation transactions; call command creates and broadcasts contract-call transactions with optional amount/fee; balance command tags accounts containing code.
Smart contract documentation
README.md
Documents Python-based contract support via sys.settrace gas metering and multiprocessing sandboxing; describes storage and msg contract inputs; lists CLI commands.

Tests & Persistence Updates

Layer / File(s) Summary
Persistence and deserialization updates
minichain/persistence.py
Simplifies block deserialization by removing _deserialize_block() helper; calls Block.from_dict() directly, enabling receipt persistence.
Contract execution tests
tests/test_contract.py
Updates tests to use receipt-based assertions; adds explicit fee parameters; validates receipt status, error_message, contract_address fields; includes comprehensive test_out_of_gas for gas-limit enforcement.
Core transaction and persistence tests
tests/test_core.py, tests/test_persistence.py, tests/test_persistence_runtime.py, tests/test_protocol_hardening.py, tests/test_transaction_signing.py
Adds test_transaction_fee() covering fee deduction and miner rewards; updates tampering tests via object.__setattr__; updates persistence helpers to populate receipt_root and receipts; adds test_receipt_data_preserved() to verify field preservation across save/load cycles.

🎯 4 (Complex) | ⏱️ ~75 minutes


Possibly related PRs

  • StabilityNexus/MiniChain#60: Both PRs modify minichain/mempool.py transaction selection—main PR adds fee-based prioritization, while #60 refactors the mempool to a sorted pending-queue model.
  • StabilityNexus/MiniChain#58: Both PRs modify Mempool.get_transactions_for_block() transaction-selection behavior and related pool semantics.

Suggested labels

Python Lang, Documentation


Suggested reviewers

  • Zahnentferner

Poem

🐰 Receipts now flow, like coins in trade,
Each contract's gas in ledgers weighed,
Smart counters, swaps, and tokens bright—
MiniChain's sandbox, burning right!
Hop forward now, the trie's at rest,
Fee-driven chains perform their best.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The PR title 'Feat/smart contracts' is vague and uses a generic prefix pattern. While it broadly relates to the changeset, it does not clearly convey the primary objective or main functionality being added. Consider a more descriptive title such as 'Add smart contract execution with gas metering and CLI integration' to better reflect the main changes in the PR.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
minichain/p2p.py (1)

121-144: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject negative fees during transaction payload validation.

fee is type-checked but never range-checked. A negative fee currently passes schema validation and can break downstream economic accounting.

Suggested fix
         if payload["amount"] < 0:
             return False
+        if payload["fee"] < 0:
+            return False
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@minichain/p2p.py` around lines 121 - 144, The transaction payload validation
currently type-checks "fee" but doesn't reject negative fees; update the
validation in the payload checker (the block using
required_fields/optional_fields/allowed_fields and payload) to also range-check
fee by returning False when payload["fee"] < 0 (similar to the existing
payload["amount"] < 0 check), ensuring negative fees are rejected before further
processing.
main.py (1)

171-185: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update CLI help text to include deploy/call and fee-aware syntax.

The runtime help banner still advertises only legacy commands, while Lines 247–312 add user-facing deploy/call flows and fee parameters. This makes discoverability inconsistent in the interactive shell.

Suggested patch
 HELP_TEXT = """
@@
-║  send <to> <amount>   - send coins             ║
+║  send <to> <amount> [fee] - send coins         ║
+║  deploy <filepath> [amount] [fee] - deploy     ║
+║  call <contract> <payload> [amount] [fee]      ║
 ║  mine                 - mine a block           ║

Also applies to: 247-312

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.py` around lines 171 - 185, The CLI help banner in HELP_TEXT is outdated
and should list the new user-facing commands and fee syntax; update the
HELP_TEXT constant to include entries for "deploy <path> [--fee <amount>]" and
"call <contract> <method> [--fee <amount>]" (and any related flags used by the
runtime flows for deploy/call), ensuring spacing/box layout matches the existing
ASCII art so the interactive shell shows the new commands that are implemented
by the deploy and call flows.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/dex.py`:
- Around line 60-70: The code parses tokens_to_sell from parts but doesn't
validate it's strictly positive; add an explicit guard after computing
tokens_to_sell (from parts and before reading/modifying storage) to raise an
error if tokens_to_sell <= 0, so functions handling sell in examples/dex.py
(variables parts, tokens_to_sell, sender, storage) never debit the sender for
non-positive amounts.

In `@main.py`:
- Around line 253-256: The cli_loop coroutine uses blocking file I/O (with
open(filepath, "r") as f: code = f.read()) which stalls the event loop; change
it to perform file reads off the event loop (e.g., await
asyncio.to_thread(lambda: open(...).read()) or use an async file library like
aiofiles and await aiofiles.open/reader) and preserve the existing
FileNotFoundError handling around the read; update the read site in async def
cli_loop to await the non-blocking read and re-raise or log the
FileNotFoundError as before so the rest of cli_loop continues to run without
blocking.

In `@minichain/chain.py`:
- Around line 49-51: Replace the existing generic error log in the genesis load
failure handler with an exception-aware log so the stacktrace is preserved: in
the except block that currently calls logger.error("Failed to load genesis
config: %s", e) (around the genesis-loading logic in minichain/chain.py), call
logger.exception("Failed to load genesis config") instead and keep the
subsequent sys.exit(1) to preserve behavior while providing full traceback
context.

In `@minichain/contract.py`:
- Around line 16-19: The gas check in the opcode event handler currently raises
OutOfGasException when self.gas <= 0, causing an off-by-one premature halt;
change the condition in the opcode branch so that after decrementing self.gas
you only raise when self.gas < 0 (i.e., allow zero gas to complete the
configured number of opcodes) — update the check in the method handling "event
== 'opcode'" where self.gas is decremented and OutOfGasException is raised.

In `@minichain/state.py`:
- Around line 77-79: The code currently only validates tx.amount; add a semantic
check to ensure tx.fee is an integer and non-negative before any arithmetic
(similar to the existing tx.amount check). Locate the validation block that
checks "if not isinstance(tx.amount, int) or tx.amount < 0: return None" and
extend it (or add adjacent logic) to also enforce "isinstance(tx.fee, int) and
tx.fee >= 0" so that subsequent calculations involving total_cost and the sender
balance update (uses of tx.fee and total_cost) cannot be abused by negative
fees.

In `@README.md`:
- Around line 121-123: The README entries for the CLI currently document
`[gas_limit]` but the CLI parser in main.py exposes `[fee]`; update the README
lines for the `deploy` and `call` commands to use `[fee]` (or change main.py's
usage strings to `[gas_limit]` if you prefer changing the code) so the
documented arguments match the implemented usage strings in main.py; ensure the
parameter name in README exactly matches the token used in main.py's command
usage.

In `@tests/test_core.py`:
- Around line 112-113: The assertion uses a hardcoded mining reward (50);
replace the magic value with the protocol constant DEFAULT_MINING_REWARD so the
expected balance is computed as amount + DEFAULT_MINING_REWARD + fee. Update the
test in tests/test_core.py to reference DEFAULT_MINING_REWARD (import it at the
top if not already present) and use it when building the expected value passed
to self.assertEqual for self.chain.state.get_account(self.bob_pk)['balance'].

---

Outside diff comments:
In `@main.py`:
- Around line 171-185: The CLI help banner in HELP_TEXT is outdated and should
list the new user-facing commands and fee syntax; update the HELP_TEXT constant
to include entries for "deploy <path> [--fee <amount>]" and "call <contract>
<method> [--fee <amount>]" (and any related flags used by the runtime flows for
deploy/call), ensuring spacing/box layout matches the existing ASCII art so the
interactive shell shows the new commands that are implemented by the deploy and
call flows.

In `@minichain/p2p.py`:
- Around line 121-144: The transaction payload validation currently type-checks
"fee" but doesn't reject negative fees; update the validation in the payload
checker (the block using required_fields/optional_fields/allowed_fields and
payload) to also range-check fee by returning False when payload["fee"] < 0
(similar to the existing payload["amount"] < 0 check), ensuring negative fees
are rejected before further processing.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 21f89c03-9c2a-4859-a0f7-ce13b1ae278b

📥 Commits

Reviewing files that changed from the base of the PR and between 329e7cb and 116a09b.

📒 Files selected for processing (23)
  • README.md
  • examples/counter.py
  • examples/dex.py
  • examples/stablecoin.py
  • main.py
  • minichain/block.py
  • minichain/chain.py
  • minichain/contract.py
  • minichain/mempool.py
  • minichain/mpt.py
  • minichain/p2p.py
  • minichain/persistence.py
  • minichain/receipt.py
  • minichain/state.py
  • minichain/transaction.py
  • requirements.txt
  • setup.py
  • tests/test_contract.py
  • tests/test_core.py
  • tests/test_persistence.py
  • tests/test_persistence_runtime.py
  • tests/test_protocol_hardening.py
  • tests/test_transaction_signing.py
💤 Files with no reviewable changes (1)
  • setup.py

Comment thread examples/dex.py
Comment on lines +60 to +70
parts = msg['data'].split(':')
tokens_to_sell = int(parts[1])

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate sell amount is strictly positive before debiting sender balance.

tokens_to_sell is parsed but never checked for <= 0. Add an explicit guard before mutating balances.

Suggested fix
     parts = msg['data'].split(':')
+    if len(parts) != 2:
+        raise Exception("Invalid sell format")
     tokens_to_sell = int(parts[1])
+    if tokens_to_sell <= 0:
+        raise Exception("Amount must be positive")
     
     sender = msg['sender']
     sender_tokens = storage.get(sender, 0)
📝 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.

Suggested change
parts = msg['data'].split(':')
tokens_to_sell = int(parts[1])
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
parts = msg['data'].split(':')
if len(parts) != 2:
raise Exception("Invalid sell format")
tokens_to_sell = int(parts[1])
if tokens_to_sell <= 0:
raise Exception("Amount must be positive")
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
🧰 Tools
🪛 Ruff (0.15.15)

[error] 60-60: Undefined name msg

(F821)


[error] 63-63: Undefined name msg

(F821)


[error] 64-64: Undefined name storage

(F821)


[warning] 66-66: Create your own exception

(TRY002)


[warning] 66-66: Avoid specifying long messages outside the exception class

(TRY003)


[error] 69-69: Undefined name storage

(F821)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/dex.py` around lines 60 - 70, The code parses tokens_to_sell from
parts but doesn't validate it's strictly positive; add an explicit guard after
computing tokens_to_sell (from parts and before reading/modifying storage) to
raise an error if tokens_to_sell <= 0, so functions handling sell in
examples/dex.py (variables parts, tokens_to_sell, sender, storage) never debit
the sender for non-positive amounts.

Comment thread main.py
Comment on lines +253 to +256
try:
with open(filepath, "r") as f:
code = f.read()
except FileNotFoundError:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify async functions that still perform blocking open(...)
python - <<'PY'
import ast
from pathlib import Path

for p in Path(".").rglob("*.py"):
    try:
        tree = ast.parse(p.read_text(encoding="utf-8"))
    except Exception:
        continue
    for node in ast.walk(tree):
        if isinstance(node, ast.AsyncFunctionDef):
            for sub in ast.walk(node):
                if isinstance(sub, ast.Call) and isinstance(sub.func, ast.Name) and sub.func.id == "open":
                    print(f"{p}:{sub.lineno} async function '{node.name}' uses open(...)")
PY

Repository: StabilityNexus/MiniChain

Length of output: 121


Avoid blocking disk I/O on the cli_loop event loop path.

main.py’s async def cli_loop reads the contract with with open(filepath, "r") as f: code = f.read() (line ~254), which blocks the event loop while loading large files.

Suggested patch
+from pathlib import Path
@@
-            try:
-                with open(filepath, "r") as f:
-                    code = f.read()
+            try:
+                code = await asyncio.to_thread(Path(filepath).read_text, encoding="utf-8")
             except FileNotFoundError:
                 print(f"  File not found: {filepath}")
                 continue
📝 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.

Suggested change
try:
with open(filepath, "r") as f:
code = f.read()
except FileNotFoundError:
try:
code = await asyncio.to_thread(Path(filepath).read_text, encoding="utf-8")
except FileNotFoundError:
print(f" File not found: {filepath}")
continue
🧰 Tools
🪛 Ruff (0.15.15)

[warning] 254-254: Async functions should not open files with blocking methods like open

(ASYNC230)


[warning] 254-254: Unnecessary mode argument

Remove mode argument

(UP015)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.py` around lines 253 - 256, The cli_loop coroutine uses blocking file
I/O (with open(filepath, "r") as f: code = f.read()) which stalls the event
loop; change it to perform file reads off the event loop (e.g., await
asyncio.to_thread(lambda: open(...).read()) or use an async file library like
aiofiles and await aiofiles.open/reader) and preserve the existing
FileNotFoundError handling around the read; update the read site in async def
cli_loop to await the non-blocking read and re-raise or log the
FileNotFoundError as before so the rest of cli_loop continues to run without
blocking.

Source: Linters/SAST tools

Comment thread minichain/chain.py
Comment on lines 49 to 51
except Exception as e:
logger.error(f"Failed to load genesis config: {e}")
logger.error("Failed to load genesis config: %s", e)
sys.exit(1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use traceback logging for genesis parse failures.

At Line 50, this branch exits the process after logging. Switching to logger.exception(...) preserves stack context and makes startup-failure debugging significantly easier.

Suggested patch
-            except Exception as e:
-                logger.error("Failed to load genesis config: %s", e)
+            except Exception:
+                logger.exception("Failed to load genesis config")
                 sys.exit(1)
📝 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.

Suggested change
except Exception as e:
logger.error(f"Failed to load genesis config: {e}")
logger.error("Failed to load genesis config: %s", e)
sys.exit(1)
except Exception:
logger.exception("Failed to load genesis config")
sys.exit(1)
🧰 Tools
🪛 Ruff (0.15.15)

[warning] 49-49: Do not catch blind exception: Exception

(BLE001)


[warning] 50-50: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@minichain/chain.py` around lines 49 - 51, Replace the existing generic error
log in the genesis load failure handler with an exception-aware log so the
stacktrace is preserved: in the except block that currently calls
logger.error("Failed to load genesis config: %s", e) (around the genesis-loading
logic in minichain/chain.py), call logger.exception("Failed to load genesis
config") instead and keep the subsequent sys.exit(1) to preserve behavior while
providing full traceback context.

Source: Linters/SAST tools

Comment thread minichain/contract.py
Comment on lines +16 to +19
if event == 'opcode':
self.gas -= 1
if self.gas <= 0:
raise OutOfGasException("Out of gas!")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix off-by-one in gas exhaustion condition.

At Line 18, raising on <= 0 makes execution halt one opcode earlier than the configured limit. Raise only when gas drops below zero.

🔧 Proposed fix
         if event == 'opcode':
             self.gas -= 1
-            if self.gas <= 0:
+            if self.gas < 0:
                 raise OutOfGasException("Out of gas!")
📝 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.

Suggested change
if event == 'opcode':
self.gas -= 1
if self.gas <= 0:
raise OutOfGasException("Out of gas!")
if event == 'opcode':
self.gas -= 1
if self.gas < 0:
raise OutOfGasException("Out of gas!")
🧰 Tools
🪛 Ruff (0.15.15)

[warning] 19-19: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@minichain/contract.py` around lines 16 - 19, The gas check in the opcode
event handler currently raises OutOfGasException when self.gas <= 0, causing an
off-by-one premature halt; change the condition in the opcode branch so that
after decrementing self.gas you only raise when self.gas < 0 (i.e., allow zero
gas to complete the configured number of opcodes) — update the check in the
method handling "event == 'opcode'" where self.gas is decremented and
OutOfGasException is raised.

Comment thread minichain/state.py
Comment on lines 77 to +79
# Semantic validation: amount must be an integer and non-negative
if not isinstance(tx.amount, int) or tx.amount < 0:
return False
return None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Validate tx.fee as a non-negative integer before applying cost math.

Line 51/Line 93 use fee in arithmetic, but Line 78 only validates amount. A negative fee can increase sender balance at Line 96 (-= total_cost), breaking economic invariants and receipt gas accounting.

🔧 Proposed fix
 def validate_and_apply(self, tx):
@@
-        if not isinstance(tx.amount, int) or tx.amount < 0:
+        fee = getattr(tx, "fee", None)
+        if not isinstance(tx.amount, int) or tx.amount < 0:
+            return None
+        if not isinstance(fee, int) or fee < 0:
             return None
@@
-        total_cost = tx.amount + getattr(tx, 'fee', 0)
+        total_cost = tx.amount + tx.fee
@@
-        total_cost = tx.amount + getattr(tx, 'fee', 0)
+        total_cost = tx.amount + tx.fee
@@
-            gas_limit = getattr(tx, 'fee', 0)
+            gas_limit = tx.fee
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@minichain/state.py` around lines 77 - 79, The code currently only validates
tx.amount; add a semantic check to ensure tx.fee is an integer and non-negative
before any arithmetic (similar to the existing tx.amount check). Locate the
validation block that checks "if not isinstance(tx.amount, int) or tx.amount <
0: return None" and extend it (or add adjacent logic) to also enforce
"isinstance(tx.fee, int) and tx.fee >= 0" so that subsequent calculations
involving total_cost and the sender balance update (uses of tx.fee and
total_cost) cannot be abused by negative fees.

Comment thread README.md
Comment on lines +121 to +123
1. **Deploy:** `deploy <filepath> [amount] [gas_limit]`
2. **Call:** `call <contract_address> <payload> [amount] [gas_limit]`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align README CLI args with implemented command names.

Lines 121–123 document [gas_limit], but the CLI parser uses [fee] (see main.py command usage strings). Please keep naming consistent to avoid failed command attempts.

Suggested patch
-1. **Deploy:** `deploy <filepath> [amount] [gas_limit]`
-2. **Call:** `call <contract_address> <payload> [amount] [gas_limit]`
+1. **Deploy:** `deploy <filepath> [amount] [fee]`
+2. **Call:** `call <contract_address> <payload> [amount] [fee]`
📝 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.

Suggested change
1. **Deploy:** `deploy <filepath> [amount] [gas_limit]`
2. **Call:** `call <contract_address> <payload> [amount] [gas_limit]`
1. **Deploy:** `deploy <filepath> [amount] [fee]`
2. **Call:** `call <contract_address> <payload> [amount] [fee]`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 121 - 123, The README entries for the CLI currently
document `[gas_limit]` but the CLI parser in main.py exposes `[fee]`; update the
README lines for the `deploy` and `call` commands to use `[fee]` (or change
main.py's usage strings to `[gas_limit]` if you prefer changing the code) so the
documented arguments match the implemented usage strings in main.py; ensure the
parameter name in README exactly matches the token used in main.py's command
usage.

Comment thread tests/test_core.py
Comment on lines +112 to +113
# Bob was the miner. Bob gets amount(40) + mining_reward(50) + fee(5) = 95
self.assertEqual(self.chain.state.get_account(self.bob_pk)['balance'], 95)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use protocol constant instead of hardcoded mining reward in assertion.

At Lines 112–113, hardcoding 50 couples the test to a magic value. Build expected balance from DEFAULT_MINING_REWARD to keep this test stable across reward policy updates.

Suggested patch
-        # Bob was the miner. Bob gets amount(40) + mining_reward(50) + fee(5) = 95
-        self.assertEqual(self.chain.state.get_account(self.bob_pk)['balance'], 95)
+        expected = 40 + self.chain.state.DEFAULT_MINING_REWARD + 5
+        self.assertEqual(self.chain.state.get_account(self.bob_pk)['balance'], expected)
📝 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.

Suggested change
# Bob was the miner. Bob gets amount(40) + mining_reward(50) + fee(5) = 95
self.assertEqual(self.chain.state.get_account(self.bob_pk)['balance'], 95)
expected = 40 + self.chain.state.DEFAULT_MINING_REWARD + 5
self.assertEqual(self.chain.state.get_account(self.bob_pk)['balance'], expected)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_core.py` around lines 112 - 113, The assertion uses a hardcoded
mining reward (50); replace the magic value with the protocol constant
DEFAULT_MINING_REWARD so the expected balance is computed as amount +
DEFAULT_MINING_REWARD + fee. Update the test in tests/test_core.py to reference
DEFAULT_MINING_REWARD (import it at the top if not already present) and use it
when building the expected value passed to self.assertEqual for
self.chain.state.get_account(self.bob_pk)['balance'].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant