Skip to content

Fix 85#192

Open
Hydrax117 wants to merge 3 commits into
scout-off:mainfrom
Hydrax117:fix-85
Open

Fix 85#192
Hydrax117 wants to merge 3 commits into
scout-off:mainfrom
Hydrax117:fix-85

Conversation

@Hydrax117
Copy link
Copy Markdown

Pull Request — Issue #85

Title

feat(progress): implement get_progress_history with 50-entry gas cap

Closes

Closes #85


Summary

The README documents get_progress_history(player_id) as a first-class query function, but the contract only exposed get_history_entry(player_id, index), forcing callers to loop manually. This PR implements the missing function, adds a 50-entry gas cap, and covers both the happy path and the empty-history edge case with unit tests.


Changes

contracts/progress/src/lib.rs

Production code

Import — added Vec to the soroban_sdk import:

use soroban_sdk::{contract, contractimpl, Address, Env, Vec};

New query function — added after get_history_entry in the Queries section:

/// Return all history entries for a player in chronological order (index 1..=N).
/// Capped at 50 entries to bound gas consumption.
/// Returns an empty Vec if the player has no history.
pub fn get_progress_history(env: Env, player_id: u64) -> Vec<ProgressEntry> {
    const MAX_ENTRIES: u32 = 50;

    let count: u32 = env
        .storage()
        .persistent()
        .get(&DataKey::HistoryCounter(player_id))
        .unwrap_or(0u32);

    let limit = count.min(MAX_ENTRIES);
    let mut entries: Vec<ProgressEntry> = Vec::new(&env);

    for i in 1..=limit {
        if let Some(entry) = env
            .storage()
            .persistent()
            .get(&DataKey::HistoryEntry(player_id, i))
        {
            entries.push_back(entry);
        }
    }

    entries
}

Tests

test_get_progress_history_three_entries — advances through all three tiers and asserts all 3 entries are returned with correct old_level, new_level, and milestone_ref at each position:

#[test]
fn test_get_progress_history_three_entries() {
    let (env, client) = setup();
    let admin = Address::generate(&env);
    client.initialize(&admin);

    let validator = Address::generate(&env);
    let player_id = 10u64;

    client.advance_level(&validator, &player_id, &1u32);
    client.advance_level(&validator, &player_id, &2u32);
    client.advance_level(&validator, &player_id, &3u32);

    let history = client.get_progress_history(&player_id);

    assert_eq!(history.len(), 3);

    assert_eq!(history.get(0).unwrap().old_level, ProgressLevel::Unverified);
    assert_eq!(history.get(0).unwrap().new_level, ProgressLevel::VerifiedIdentity);
    assert_eq!(history.get(0).unwrap().milestone_ref, 1u32);

    assert_eq!(history.get(1).unwrap().old_level, ProgressLevel::VerifiedIdentity);
    assert_eq!(history.get(1).unwrap().new_level, ProgressLevel::PerformanceMilestones);
    assert_eq!(history.get(1).unwrap().milestone_ref, 2u32);

    assert_eq!(history.get(2).unwrap().old_level, ProgressLevel::PerformanceMilestones);
    assert_eq!(history.get(2).unwrap().new_level, ProgressLevel::EliteTier);
    assert_eq!(history.get(2).unwrap().milestone_ref, 3u32);
}

test_get_progress_history_empty — queries a player that has never had advance_level called and asserts an empty Vec is returned:

#[test]
fn test_get_progress_history_empty() {
    let (env, client) = setup();
    let admin = Address::generate(&env);
    client.initialize(&admin);

    let history = client.get_progress_history(&999u64);
    assert_eq!(history.len(), 0);
}

Acceptance Criteria

Criterion Status
Returns all entries for a player with ≤ 50 records ✅ Loop runs 1..=limit where limit = count.min(50)
Returns empty Vec for a player with no history HistoryCounter absent → count = 0 → loop skipped → empty Vec returned
Unit test advances level 3 times and asserts all 3 entries test_get_progress_history_three_entries checks len == 3 and all fields at each index

Design Notes

  • Gas capconst MAX_ENTRIES: u32 = 50 is applied via count.min(MAX_ENTRIES) before the loop, so the number of storage reads is bounded regardless of how many times advance_level has been called.
  • Chronological order — entries are stored at indices 1, 2, 3, … by advance_level and collected in that order, so the returned Vec is naturally chronological.
  • No new storage keys — the function reads from the existing HistoryCounter and HistoryEntry keys; no schema changes required.
  • if let Some — defensive guard inside the loop handles any theoretical gap in the index sequence without panicking, though under normal operation all indices 1..=count will be present.
  • soroban_sdk::Vec is used (not std::vec::Vec) because Soroban contracts compile to WASM and must use the SDK's host-managed collection types.

How to Verify

# Run the two new tests
cargo test -p scoutchain-progress test_get_progress_history_three_entries
cargo test -p scoutchain-progress test_get_progress_history_empty

# Full suite
cargo test --workspace

Expected output:

test tests::test_get_progress_history_three_entries ... ok
test tests::test_get_progress_history_empty         ... ok

Checklist

  • All contract tests pass: cargo test --workspace
  • Zero clippy warnings: cargo clippy --workspace -- -D warnings
  • Formatting clean: cargo fmt --all -- --check
  • New function documented with doc comment in source
  • Gas cap applied and explained in design notes
  • Acceptance criteria fully covered

@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented May 31, 2026

@Hydrax117 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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.

Implement get_progress_history batch query returning all ProgressEntry records

1 participant