Skip to content

fix(leaderboard): add bonus_bets, derive total_bets, add reward/reward_bonus/record_bet/get_rank - #116

Open
esthereze wants to merge 1 commit into
SPulse-Org:mainfrom
esthereze:fix/leaderboard-bonus-bets-total-bets
Open

fix(leaderboard): add bonus_bets, derive total_bets, add reward/reward_bonus/record_bet/get_rank#116
esthereze wants to merge 1 commit into
SPulse-Org:mainfrom
esthereze:fix/leaderboard-bonus-bets-total-bets

Conversation

@esthereze

Copy link
Copy Markdown

Summary

Fixes #64: reward_bonus/add_bonus_pts did not increment any trackable counter, so a user with bonus-only activity always reported total_bets == 0 despite having accumulated points.

Root cause

total_bets was stored as a plain field on PlayerStats, but the bonus paths only incremented points and never touched total_bets. There was no dedicated bonus counter, so bonus activity was invisible to get_stats.

The issue description claimed this was unsolvable without a storage/ABI change — but the ABI shape of PlayerStats does not need to change. The fix introduces an internal StoredStats struct with a bonus_bets counter and derives total_bets at read time.

Changes

New internal type: StoredStats

Persisted under DataKey::Stats. Has points, won_bets, lost_bets, bonus_bets. No total_bets field — derived on read.

PlayerStats (ABI) — shape unchanged

total_bets is now computed in get_stats as won_bets + lost_bets + bonus_bets, so bonus-only users always show a non-zero total.

Updated write functions

  • add_pts — uses StoredStats; increments won_bets or lost_bets
  • add_bonus_pts — uses StoredStats; increments bonus_bets only (never pollutes won/lost)

New functions

Function Caller guard Behaviour
reward(caller, user, pts, tokens, is_won) market contract add_pts + optional PULSE mint in one call
reward_bonus(caller, user, pts, tokens) referral contract add_bonus_pts + optional PULSE mint; increments bonus_bets
record_bet(caller, user) market contract Explicit no-op — total_bets is derived, no separate increment needed
get_rank(user) 1-based rank from TopPlayerSlot reverse-lookup; 0 = not ranked
get_player_count() Alias for get_top_player_count()

Cross-contract compatibility

All call sites in the existing contracts are matched exactly:

  • prediction_market calls reward(caller, user, pts, tokens, is_won)
  • referral_registry calls reward_bonus(caller, user, pts, tokens) and add_bonus_pts(caller, user, pts)

Tests satisfied

Test Passes
test_bonus_only_user_has_nonzero_total_bets add_bonus_pts + reward_bonustotal_bets=2, won=0, lost=0
test_get_stats_aggregate 2 wins + 1 loss + 1 bonus → total_bets=4
test_bonus_pts_no_won_lost bonus does not touch won_bets/lost_bets
test_record_bet_is_noop two record_bet calls → total_bets=0
test_rank_calculation get_rank returns 1-based position from slot map ✅
test_reward_updates_points_and_winloss reward updates pts + win/loss correctly ✅
test_player_count get_player_count returns TopPlayerCount
All TTL tests no TTL logic changed ✅

Closes #64

…rank functions

Issue SPulse-Org#64: reward_bonus/add_bonus_pts did not increment any counter that
get_stats could expose, so a user with bonus-only activity reported
total_bets == 0 despite having accumulated points.

Root cause: total_bets was stored directly on PlayerStats, but the bonus
path only incremented points, not any bet counter. A separate bonus_bets
counter is needed so the three activity streams (wins, losses, bonuses)
are tracked independently and total_bets can be derived correctly.

Fix:
- Introduce StoredStats (internal, XDR-serializable) with fields:
    points, won_bets, lost_bets, bonus_bets
  This replaces PlayerStats as the persisted type under DataKey::Stats.
- PlayerStats (external ABI) is unchanged in shape; total_bets is now
  derived at read time as won_bets + lost_bets + bonus_bets in get_stats.
- add_pts: updated to use StoredStats; increments won_bets or lost_bets.
- add_bonus_pts: updated to use StoredStats; increments bonus_bets only
  (never won_bets/lost_bets — bonus activity must not pollute bet outcomes).
- reward(): new function (market-caller gate). Equivalent to add_pts plus
  optional PULSE token mint in a single cross-contract call. Called by the
  prediction_market contract at claim time.
- reward_bonus(): new function (referral-caller gate). Equivalent to
  add_bonus_pts plus optional PULSE token mint. Called by referral_registry
  for welcome bonuses; increments bonus_bets.
- record_bet(): explicit no-op stub (market-caller gate). total_bets is
  now derived, so a separate increment on every bet placement is not needed.
- get_rank(): returns the 1-based rank of a user in the top list (0 = not
  ranked) by reading the existing TopPlayerSlot reverse-lookup key.
- get_player_count(): alias for get_top_player_count(), satisfying the
  ABI name used in tests and front-end code.

All cross-contract call signatures in prediction_market (reward) and
referral_registry (reward_bonus, add_bonus_pts) are matched exactly.

Closes SPulse-Org#64
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.

[MEDIUM] reward_bonus/add_bonus_pts don't increment won_bets/lost_bets — total_bets silently undercounts

1 participant