From 190f71a5e5c1c1630641e3b48e98cf7a9adbed8f Mon Sep 17 00:00:00 2001 From: MuhamadRifansyah <90816850+MuhamadRifansyah@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:05:50 +0700 Subject: [PATCH 1/3] feat: add input validation for bet creation Added input validation to ensure valid bet creation, preventing identical teams, empty inputs, and invalid predictions. --- contracts/football_bets.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/contracts/football_bets.py b/contracts/football_bets.py index 963dba3..3e24169 100644 --- a/contracts/football_bets.py +++ b/contracts/football_bets.py @@ -58,6 +58,14 @@ def get_match_result() -> str: def create_bet( self, game_date: str, team1: str, team2: str, predicted_winner: str ) -> None: + if team1 == team2: + raise Exception("Teams cannot be the same") + + if not team1 or not team2: + raise Exception("Team names cannot be empty") + + if predicted_winner not in [team1, team2, "draw"]: + raise Exception("Invalid prediction") match_resolution_url = ( "https://www.bbc.com/sport/football/scores-fixtures/" + game_date ) From 903d2518bb20c8266edcac870372b0032002a15b Mon Sep 17 00:00:00 2001 From: muhamadrifansyah Date: Mon, 23 Mar 2026 14:18:20 +0700 Subject: [PATCH 2/3] fix: correct validation logic and indentation in create_bet --- contracts/football_bets.py | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/contracts/football_bets.py b/contracts/football_bets.py index 3e24169..4e67227 100644 --- a/contracts/football_bets.py +++ b/contracts/football_bets.py @@ -58,14 +58,28 @@ def get_match_result() -> str: def create_bet( self, game_date: str, team1: str, team2: str, predicted_winner: str ) -> None: + """Create a new bet for the caller on a scheduled football match. + + Args: + game_date: Match date in `YYYY-MM-DD` format. + team1: Name of the first team. + team2: Name of the second team. + predicted_winner: Winner code where `"1"` is `team1`, `"2"` is + `team2`, and `"0"` is a draw. + + Raises: + Exception: If the team names are invalid, the prediction code is + invalid, or the same bet already exists for the caller. + """ if team1 == team2: - raise Exception("Teams cannot be the same") + raise Exception("Teams cannot be the same") + + if not team1 or not team2: + raise Exception("Team names cannot be empty") - if not team1 or not team2: - raise Exception("Team names cannot be empty") + if predicted_winner not in ["1", "2", "0"]: + raise Exception('Invalid prediction, use "1", "2", or "0"') - if predicted_winner not in [team1, team2, "draw"]: - raise Exception("Invalid prediction") match_resolution_url = ( "https://www.bbc.com/sport/football/scores-fixtures/" + game_date ) @@ -96,6 +110,15 @@ def create_bet( @gl.public.write def resolve_bet(self, bet_id: str) -> None: + """Resolve an existing bet for the caller using the configured result URL. + + Args: + bet_id: Unique identifier of the bet to resolve. + + Raises: + Exception: If the bet is already resolved or the match has not + finished yet. + """ if self.bets[gl.message.sender_address][bet_id].has_resolved: raise Exception("Bet already resolved") @@ -116,12 +139,15 @@ def resolve_bet(self, bet_id: str) -> None: @gl.public.view def get_bets(self) -> dict: + """Return all bets keyed by player address.""" return {k.as_hex: v for k, v in self.bets.items()} @gl.public.view def get_points(self) -> dict: + """Return the points table keyed by player address.""" return {k.as_hex: v for k, v in self.points.items()} @gl.public.view def get_player_points(self, player_address: str) -> int: + """Return the current points total for a single player address.""" return self.points.get(Address(player_address), 0) From 79b1da1f20b85d85fcbc5e630f13b6a89090e92e Mon Sep 17 00:00:00 2001 From: muhamadrifansyah Date: Mon, 23 Mar 2026 14:37:48 +0700 Subject: [PATCH 3/3] fix: improve validation robustness and safe bet lookup --- contracts/football_bets.py | 57 ++++++++++++++------------------------ 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/contracts/football_bets.py b/contracts/football_bets.py index 4e67227..fbd369c 100644 --- a/contracts/football_bets.py +++ b/contracts/football_bets.py @@ -40,14 +40,10 @@ def get_match_result() -> str: Respond in JSON: {{ - "score": str, // e.g., "1:2" or "-" if unresolved - "winner": int // 0 for draw, -1 if unresolved + "score": str, + "winner": int }} -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 parsable by a JSON parser without errors. - """ +""" result = gl.nondet.exec_prompt(task, response_format="json") return json.dumps(result, sort_keys=True) @@ -58,40 +54,29 @@ def get_match_result() -> str: def create_bet( self, game_date: str, team1: str, team2: str, predicted_winner: str ) -> None: - """Create a new bet for the caller on a scheduled football match. + """Create a new bet for a football match.""" - Args: - game_date: Match date in `YYYY-MM-DD` format. - team1: Name of the first team. - team2: Name of the second team. - predicted_winner: Winner code where `"1"` is `team1`, `"2"` is - `team2`, and `"0"` is a draw. - - Raises: - Exception: If the team names are invalid, the prediction code is - invalid, or the same bet already exists for the caller. - """ - if team1 == team2: - raise Exception("Teams cannot be the same") + # 🔹 Validation + team1 = team1.strip() + team2 = team2.strip() if not team1 or not team2: raise Exception("Team names cannot be empty") + if team1.lower() == team2.lower(): + raise Exception("Teams cannot be the same") + if predicted_winner not in ["1", "2", "0"]: raise Exception('Invalid prediction, use "1", "2", or "0"') match_resolution_url = ( "https://www.bbc.com/sport/football/scores-fixtures/" + game_date ) - # commented to allow to test matches in the past. - # match_status = await self._check_match(match_resolution_url, team1, team2) - - # if int(match_status["winner"]) > -1: - # raise Exception("Game already finished") sender_address = gl.message.sender_address bet_id = f"{game_date}_{team1}_{team2}".lower() + if sender_address in self.bets and bet_id in self.bets[sender_address]: raise Exception("Bet already created") @@ -106,23 +91,26 @@ def create_bet( real_winner="", real_score="", ) + self.bets.get_or_insert_default(sender_address)[bet_id] = bet @gl.public.write def resolve_bet(self, bet_id: str) -> None: - """Resolve an existing bet for the caller using the configured result URL. - - Args: - bet_id: Unique identifier of the bet to resolve. + """Resolve a bet by checking the match result. Raises: + Exception: If the bet does not exist for the caller. Exception: If the bet is already resolved or the match has not finished yet. """ - if self.bets[gl.message.sender_address][bet_id].has_resolved: + sender_address = gl.message.sender_address + if sender_address not in self.bets or bet_id not in self.bets[sender_address]: + raise Exception("Bet not found") + + if self.bets[sender_address][bet_id].has_resolved: raise Exception("Bet already resolved") - bet = self.bets[gl.message.sender_address][bet_id] + bet = self.bets[sender_address][bet_id] bet_status = self._check_match(bet.resolution_url, bet.team1, bet.team2) if int(bet_status["winner"]) < 0: @@ -139,15 +127,12 @@ def resolve_bet(self, bet_id: str) -> None: @gl.public.view def get_bets(self) -> dict: - """Return all bets keyed by player address.""" return {k.as_hex: v for k, v in self.bets.items()} @gl.public.view def get_points(self) -> dict: - """Return the points table keyed by player address.""" return {k.as_hex: v for k, v in self.points.items()} @gl.public.view def get_player_points(self, player_address: str) -> int: - """Return the current points total for a single player address.""" - return self.points.get(Address(player_address), 0) + return self.points.get(Address(player_address), 0) \ No newline at end of file