Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions examples/contracts/rock_paper_scissors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" }
from genlayer import *
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Ruff configuration ==="
configs="$(fd -HI '^(pyproject\.toml|ruff\.toml|\.ruff\.toml|setup\.cfg)$' . || true)"
if [ -n "$configs" ]; then
  while IFS= read -r file; do
    [ -n "$file" ] || continue
    echo "-- $file"
    rg -n -C2 'F403|F405|per-file-ignores|extend-ignore|ignore' "$file" || true
  done <<< "$configs"
else
  echo "No Ruff config files found."
fi

echo
echo "=== GenLayer import patterns in the repo ==="
rg -n -C2 'from genlayer import \*|import genlayer' . || true

Repository: genlayerlabs/genlayer-studio

Length of output: 12946


Clarify whether from genlayer import * is intentional or if examples should be exempt from linting.

This star import pattern is used consistently across the entire codebase (tests, examples, and integration contracts), but F403 and F405 are not listed in the global ignore rules in ruff.toml. Either confirm that examples are excluded from linting in pre-commit or CI configuration, or adopt explicit imports if the SDK API is intended to be visible at the call site.

🧰 Tools
🪛 Ruff (0.15.5)

[error] 2-2: from genlayer import * used; unable to detect undefined names

(F403)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/contracts/rock_paper_scissors.py` at line 2, Replace the wildcard
import in examples/contracts/rock_paper_scissors.py (from genlayer import *)
with explicit imports of only the symbols this example uses: inspect the file to
determine which functions/classes/constants from genlayer are referenced, then
change the import to "from genlayer import <SymbolA>, <SymbolB>, ..." (and
update other example files the same way for consistency) so that F403/F405 lint
errors are avoided without changing global ruff config; if you truly intend to
exempt examples from linting instead, update the repo CI/pre-commit
configuration to exclude the examples directory and document that decision.

import json

class RockPaperScissors(gl.Contract):
last_result: str
player_wins: u256
ai_wins: u256
ties: u256

def __init__(self):
self.last_result = "No game played yet"
self.player_wins = 0
self.ai_wins = 0
self.ties = 0

@gl.public.write
def play(self, player_move: str) -> None:
player_move = player_move.lower().strip()
if player_move not in ["rock", "paper", "scissors"]:
return
Comment on lines +19 to +21
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't silently accept invalid moves.

Line 21 returns without updating last_result or surfacing an error, so a caller can get a successful write and then read the previous game's result back. Revert here, or persist an explicit invalid-move outcome instead.

🛠️ Proposed fix
         player_move = player_move.lower().strip()
         if player_move not in ["rock", "paper", "scissors"]:
-            return
+            raise ValueError("player_move must be one of: rock, paper, scissors")
📝 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
player_move = player_move.lower().strip()
if player_move not in ["rock", "paper", "scissors"]:
return
player_move = player_move.lower().strip()
if player_move not in ["rock", "paper", "scissors"]:
raise ValueError("player_move must be one of: rock, paper, scissors")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/contracts/rock_paper_scissors.py` around lines 19 - 21, The code
currently silently returns when player_move is invalid (the
player_move.lower().strip() check) which leaves last_result unchanged; instead,
handle invalid moves explicitly by either raising an error or persisting an
explicit invalid outcome: update the same state path that records the game
result (last_result) with a clear status like "invalid move" and any metadata
(original input) or raise a ValueError so callers see the failure; locate the
invalid-check around player_move and modify the branch to set last_result (or
throw) rather than returning silently.


prompt = """
You are playing Rock Paper Scissors.
Pick one move from: rock, paper, scissors.

Respond using ONLY the following format:
{
"move": str
}
It is mandatory that you respond only using the JSON format above,
nothing else. Don't include any other words or characters,
your output must be only JSON without any formatting prefix or suffix.
This result should be perfectly parseable by a JSON parser without errors.
"""

def nondet():
res = gl.nondet.exec_prompt(prompt)
backticks = "``" + "`"
res = res.replace(backticks + "json", "").replace(backticks, "")
dat = json.loads(res)
return dat["move"].lower().strip()

ai_move = gl.eq_principle.strict_eq(nondet)

if ai_move not in ["rock", "paper", "scissors"]:
ai_move = "rock"
Comment on lines +37 to +47
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Move AI-response fallback inside nondet().

The fallback on Lines 46-47 only runs after gl.eq_principle.strict_eq(nondet) has already succeeded. If the model returns malformed JSON, omits "move", or produces a non-string, json.loads/dat["move"] will raise before you ever reach that branch, and divergent invalid outputs will still fail consensus. Normalize and default inside nondet() so strict_eq sees a deterministic value.

🛠️ Proposed fix
-        def nondet() -> str:
+        def nondet() -> str:
             res = gl.nondet.exec_prompt(prompt)
             backticks = "``" + "`"
-            res = res.replace(backticks + "json", "").replace(backticks, "")
-            dat = json.loads(res)
-            return dat["move"].lower().strip()
+            cleaned = res.replace(backticks + "json", "").replace(backticks, "")
+            try:
+                move = json.loads(cleaned)["move"].lower().strip()
+            except (TypeError, KeyError, json.JSONDecodeError):
+                return "rock"
+            return move if move in ["rock", "paper", "scissors"] else "rock"
 
         ai_move = gl.eq_principle.strict_eq(nondet)
-
-        if ai_move not in ["rock", "paper", "scissors"]:
-            ai_move = "rock"
🧰 Tools
🪛 Ruff (0.15.5)

[error] 38-38: gl may be undefined, or defined from star imports

(F405)


[error] 44-44: gl may be undefined, or defined from star imports

(F405)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/contracts/rock_paper_scissors.py` around lines 37 - 47, The nondet()
helper currently parses JSON and directly accesses dat["move"], so any
parse/key/type errors happen before gl.eq_principle.strict_eq(nondet) runs; move
and implement normalization and fallback inside nondet(): catch json.loads and
KeyError/TypeError, strip and lower the extracted value (use dat.get("move")
safely), verify it's one of "rock","paper","scissors", and on any error or
invalid value return the default "rock" so strict_eq always receives a
deterministic string; keep the existing backtick-cleaning but ensure all
validation and defaulting happens before returning from nondet().


if player_move == ai_move:
outcome = "tie"
self.ties += 1
elif (
(player_move == "rock" and ai_move == "scissors") or
(player_move == "scissors" and ai_move == "paper") or
(player_move == "paper" and ai_move == "rock")
):
outcome = "player"
self.player_wins += 1
else:
outcome = "ai"
self.ai_wins += 1

self.last_result = (
f"Player: {player_move} | AI: {ai_move} | "
f"Winner: {outcome}"
)

@gl.public.view
def get_last_result(self) -> str:
return self.last_result

@gl.public.view
def get_score(self) -> str:
return (
f"Player: {self.player_wins} | "
f"AI: {self.ai_wins} | "
f"Ties: {self.ties}"
)