Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
test:
name: Build & Test (stable)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cache cargo registry & build artifacts
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-

- name: Build (debug)
run: cargo build --workspace

- name: Build (release)
run: cargo build --release --workspace

- name: Run full test suite
run: cargo test --workspace
42 changes: 8 additions & 34 deletions leaderboard/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub const MAX_TOP_PLAYERS: u32 = 50;
/// Must be numerically greater than every valid in-list rank so an unranked
/// player never sorts above a real position (issue #91).
pub const UNRANKED_RANK: u32 = MAX_TOP_PLAYERS + 1;
const MAX_PAGE_SIZE: u32 = 20;
const MAX_PAGE_SIZE: u32 = MAX_TOP_PLAYERS;
const TTL_BUMP: u32 = 3_153_600;
const TTL_HIGH: u32 = 6_307_200;

Expand Down Expand Up @@ -410,45 +410,19 @@ impl LeaderboardContract {
/// No-op stub retained for ABI compatibility. total_bets is derived at read
/// time, so a standalone "bet recorded" call does nothing.
pub fn record_bet(env: Env, caller: Address, _user: Address) -> Result<(), LeaderboardError> {
// Historical contract (unchanged since the initial repo commit):
// this hook is an authenticated notification only. Lifetime bet
// counters are maintained exclusively by add_pts / reward /
// add_bonus_pts at settlement time, so recording a bet here would
// double-count activity.
Self::require_not_paused(&env)?;
Self::require_market_contract(&env, &caller)?;

let mut stats = Self::stats_for_update(&env, &user);

// ── Legacy write functions (kept for backward-compat) ─────────────────────

/// Deprecated: use `reward()` instead. This function always returns
/// `UnauthorizedCaller` and will be removed in a future version.
pub fn add_pts(
_env: Env,
_caller: Address,
_user: Address,
_pts: u64,
_is_won: bool,
) -> Result<(), LeaderboardError> {
Err(LeaderboardError::UnauthorizedCaller)
}

/// Legacy: called by the referral contract to award bonus points.
/// Prefer reward_bonus() for new integrations (adds token minting).
pub fn add_bonus_pts(
env: Env,
caller: Address,
user: Address,
pts: u64,
) -> Result<(), LeaderboardError> {
let referral: Address = env
.storage()
.instance()
.get(&DataKey::ReferralContract)
.ok_or(LeaderboardError::NotInitialized)?;
if caller != referral {
return Err(LeaderboardError::UnauthorizedCaller);
}
caller.require_auth();
Ok(())
}

// ── Legacy write functions (kept for backward-compat) ─────────────────────

// ── View functions ────────────────────────────────────────────────────────

/// Points as of *now*, with decay applied (issue #69). A read — it never
Expand Down
33 changes: 27 additions & 6 deletions leaderboard/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -799,10 +799,31 @@ fn test_add_pts_always_rejected() {
let (env, client, _admin, market, _referral) = setup();
let user = Address::generate(&env);
let rando = Address::generate(&env);
let result = client.add_pts(&rando, &user, &10_u64, &true);
assert!(result.is_err(), "add_pts should always return an error");
match result {
Err(LeaderboardError::UnauthorizedCaller) => {}
other => panic!("add_pts returned unexpected error: {:?}", other),
}
// Generated clients panic on error unless the `try_*` variant is used.
assert!(
client.try_add_pts(&rando, &user, &10_u64, &true).is_err(),
"add_pts from a non-market caller must be rejected"
);
}

// ── record_bet: authenticated market notification hook (no stats mutation) ────

#[test]
fn test_record_bet_accepts_market_caller() {
let (env, client, _admin, market, _referral) = setup();
let user = Address::generate(&env);
// Authenticated call from the registered market succeeds and is a no-op:
// lifetime counters are owned by add_pts / reward / add_bonus_pts.
client.record_bet(&market, &user);
assert_eq!(client.get_stats(&user).total_bets, 0);
assert_eq!(client.get_points(&user), 0);
}

#[test]
#[should_panic(expected = "Error(Contract, #3)")]
fn test_record_bet_rejects_non_market_caller() {
let (env, client, _admin, _market, _referral) = setup();
let rando = Address::generate(&env);
let user = Address::generate(&env);
client.record_bet(&rando, &user);
}
Loading