Skip to content

⛽ Leaderboard updates divide by zero under 60 seconds and use quadratic on-chain sorting #22

Description

@llinsss

Problem

SpellBlocLeaderboard.updatePlayerStats() can revert for ordinary early-session data and becomes increasingly expensive as leaderboards fill. A child whose first update occurs before 60 seconds cannot be ranked, and each update repeatedly scans and bubble-sorts multiple storage arrays.

Evidence

The speed score is calculated as:

stats.totalPlayTime > 0
    ? (stats.wordsLearned * 60) / (stats.totalPlayTime / 60)
    : 0;

For 1 <= totalPlayTime < 60, integer division makes the denominator zero and the transaction reverts. A stats update then calls _updateLeaderboard() for five categories plus the age leaderboard. Each call performs a linear search and an O(n²) storage bubble sort (up to 100 entries), making gas cost and liveness dependent on leaderboard size.

There are related consistency gaps: trimmed players retain a non-zero playerRanks value, inactive players remain in arrays, and lastGlobalUpdate is initialized but not updated on leaderboard changes.

Proposed implementation

  1. Fix speed units with multiplication-before-division using a clearly documented metric, e.g. words per minute based on seconds, and define zero-duration behavior.
  2. Avoid on-chain full sorting. Store canonical player stats/scores and compute pages/ranks off-chain from events, or use a bounded top-N structure with predictable insertion cost.
  3. Make rank semantics explicit for players outside top N.
  4. Cleanly handle inactive/removed players and stale entries.
  5. Validate monotonic/cumulative stats so updates cannot accidentally reduce totals or create impossible accuracy.
  6. Add gas budgets and invariant tests at maximum expected population.

Acceptance criteria

  • updates at 0, 1, 59, 60, and large session durations do not divide by zero and produce documented scores.
  • update gas stays below an agreed ceiling independent of historical player count (or within a proven bound for bounded top N).
  • a trimmed/inactive player never reports a stale rank.
  • events contain enough information for a deterministic off-chain leaderboard/indexer.
  • fuzz tests cover score arithmetic without overflow/revert for valid inputs.
  • tests cover ties, repeat updates, top-N entry/exit, inactivity, and the maximum configured board size.

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third CampaignbugSomething isn't workingdifficulty: hardSubstantial, multi-PR effort for experienced contributorssmart-contractsSolidity / Hardhat / Celo

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions