Skip to content

feat(presets): adventurer objective entry validator (beast achievements Phase 2) - #32

Open
starknetdev wants to merge 1 commit into
mainfrom
feat/adventurer-entry-validator
Open

feat(presets): adventurer objective entry validator (beast achievements Phase 2)#32
starknetdev wants to merge 1 commit into
mainfrom
feat/adventurer-entry-validator

Conversation

@starknetdev

Copy link
Copy Markdown
Member

Summary

Phase 2 of the Beast-Mode achievements build: a thin entry-requirement adapter on top of the Adventurer Oracle (#31). It lets Budokan gate tournament entry on "this adventurer completed objective N".

AdventurerValidator implements the metagame IEntryRequirementExtension framework (current EntryRequirementExtensionComponent, modeled on opus_troves_validator) and delegates the actual check to the oracle's completed_objective(token_id, objective_id). It stays game-agnostic — it only speaks the IMinigameObjectives interface (declared locally, no code dependency on the oracle crate), so any conforming objectives provider works.

Behavior

  • Each (context_owner, context_id) is configured (add_config) with an oracle address + a single objective_id (config = [oracle, objective_id, bannable?]).
  • The player supplies the adventurer NFT token_id as qualification[0].
  • Entry granted iff oracle.completed_objective(token_id, objective_id) and the player is under their per-context entry_limit (0 = unlimited).
  • Optional bannable flag revokes an existing entry once the objective is no longer satisfied (objectives read live state — e.g. a gold check can regress).

Tests

8 snforge tests (mock the oracle via start_mock_call): incomplete rejects, complete grants, unconfigured/empty-qualification reject, quota enforcement + free-on-remove, bannable ban-on-regress, non-bannable never bans, view getters. scarb build + scarb fmt --check clean.

Notes / follow-ups

🤖 Generated with Claude Code

Add `AdventurerValidator`, a thin `IEntryRequirementExtension` preset that
gates tournament entry on a Death Mountain adventurer having completed a
registered objective on the Adventurer Oracle (PR #31). It delegates to the
oracle's `completed_objective(token_id, objective_id)` and stays game-agnostic
by speaking only the `IMinigameObjectives` interface, so any conforming
objectives provider works.

Per `(context_owner, context_id)`: configured with an oracle address + a single
objective id; the player supplies the adventurer NFT token_id as
`qualification[0]`. Supports a per-player entry quota (`entry_limit`, 0 =
unlimited) and an optional `bannable` flag that revokes an entry once the
objective is no longer satisfied (objectives read live state).

Modeled on `opus_troves_validator` (external-state delegation, current
`EntryRequirementExtensionComponent` API). Adds 8 snforge tests mocking the
oracle via `start_mock_call`. `scarb build` + `scarb fmt --check` clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

GPT Code Review

Review execution failed (exit code 1).

The Codex CLI log was not posted because it can contain the full review prompt.
See the workflow run for job status: https://github.com/Provable-Games/metagame_extensions/actions/runs/28878609083

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Claude Code Review

Confirmed: no NFT ownership check. This is the core security finding. Let me write the review.

Findings:

[HIGH] No ownership binding between player_address and the adventurer token_id — quota bypass / shared-token abuse.

[MEDIUM] should_ban_entry ignores quota over-subscription (minor, but inconsistent with opus). Actually let me reconsider — for this validator quota can't regress since add/remove are symmetric. I'll drop it as low-signal.

[LOW] add_config doesn't reject malformed/trailing config or validate objective_id bounds. Low signal.

Let me focus on the highest-signal finding plus a genuine correctness one. Let me double-check whether the same token used by same player with unlimited (entry_limit 0) matters — the key issue is different players reusing one qualifying token. Let me also verify entries_left returns Some(0) when unsatisfied but that's before checking limit — consistent with "0 left". Good.

Let me verify there isn't a missing #[should_panic] test for oracle is zero / config too short — testing gaps only matter tied to a finding. The over-arching finding is the ownership one.

Here's my review:


[HIGH] packages/presets/src/entry_requirement/adventurer_validator.cairo:107-127 (objective_satisfied / qualifying_token_id) - The validator gates entry purely on oracle.completed_objective(token_id, objective_id) where token_id is taken verbatim from attacker-supplied qualification[0], with no check that player_address owns (or ever owned) that adventurer NFT.
Impact: Any player can pass any adventurer token_id that has completed the objective — including one they don't own — and pass validation. Because the per-context quota (context_entries_used) is keyed by player_address, a single qualifying adventurer can be reused by unlimited distinct wallets, each getting a fresh entry_limit. This defeats the entry gate and the quota entirely (Sybil / shared-token abuse). Note the tournament_validator ALL mode explicitly keys quota by token_id for exactly this reason ("transferring tokens to a fresh wallet cannot grant additional entries").
Fix: Bind the qualifying token to the player. Either (a) verify NFT ownership by calling IERC721(adventurer_nft).owner_of(token_id) == player_address inside objective_satisfied, and/or (b) key the quota by token_id instead of player_address, e.g. context_entries_used: Map<(ContractAddress, u64, felt252), u32> keyed by the adventurer token, so one completed adventurer yields one bounded set of entries regardless of caller wallet. Add a negative-path test asserting a player cannot enter with a token_id they don't own.

[LOW] packages/presets/src/entry_requirement/adventurer_validator.cairo:222-229 (add_config) - config accepts any length >= 2 and silently ignores trailing elements; objective_id is try_into().unwrap() with no domain check.
Impact: Malformed configs (wrong element ordering, extra payload) are accepted without error, making misconfiguration silent at setup time. Low blast radius since only the context owner calls it.
Fix: Assert an exact expected length (config.len() == 2 || config.len() == 3) to reject trailing payloads, and prefer expect('AdventurerValidator: bad objective_id') over unwrap() for actionable panic context.

The high-severity ownership/quota-bypass finding is the key blocker.

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.87755% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...s/src/entry_requirement/adventurer_validator.cairo 93.87% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@gemini-code-assist

Copy link
Copy Markdown

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant