

Trepa
Lifting critical invariants on-chain ahead of mainnet. 15 findings across the Anchor program and resolver backend, 14 fixed and verified.
About the client
Trepa is a Solana-based short-horizon forecasting game (Flash Pools) where every player pays the same fixed entry fee and submits a numeric price estimate for an asset, starting with Bitcoin.
After settlement, each player's error is compared to the field median: closer estimates win their entry back and share a prize pool funded from losers' entries (after a platform take), with accuracy-weighted splits subject to per-round profit caps.
Players also earn a precision score and can qualify for streak mechanics, with a portion of the take flowing to a streak accumulator.
"Communication, coordination, and the overall audit process were excellent. The team presented well-structured findings and consistently supported their conclusions with detailed logs and reproducible tests."
"Their willingness to go beyond the defined scope made me realize their strong commitment to quality and added significant value to us."
The team on this audit

Lead Security Researcher at Sherlock. Over 100 audits performed and 500+ bugs found.

A promising Security Researcher. In the space for ~1 year, and showing remarkable results.

Specializes in Solana smart contracts. #1 cumulative ranking on Solana Audit Arena.

Strong AppSec background, active in bug bounties. Experienced in Web2 audits, primarily TypeScript.

Independent EVM + Solana Security Researcher. 50+ protocol reviews, 300+ impactful vulnerabilities.
High severity issues and fixes
Backend oracle finalizes pools with a stale price from a capped aggregate-trades page
The 120-second BTCUSDT lookup was capped at 1000 rows by the data provider, returning the oldest page instead of the newest. The stale page-tail price fed straight into pool resolution, producing wrong winners and a valid Merkle root for an economically incorrect outcome.
Replaced the wide window query with a direct nearest-before lookup (endTime: outcomeMs - 1, limit: 1) and added a drift bound, eliminating the truncation class entirely.
Streak reward claim transaction binding bypass enables double payout
The submit endpoint trusted the client-supplied streak_reward_id without verifying it matched the signed transaction. An attacker could swap the reward ID after signing to mark a different reward claimed in the DB while the chain executed the original payout, leaving the original reward repeatedly claimable and draining the streak pool.
The signed proof is now bound to a context object containing streak_reward_id and wallet_address. The submit endpoint re-verifies that context before updating is_claimed, so swapping the reward ID after signing now fails verification.
Plus 6 medium and 7 low severity findings. All documented in the full report.
Medium severity issues and fixes
Indexer consistency and resolution trust can lead to incorrect payout finalization
The indexer did not enforce event ordering and ran live subscription and backfill in parallel, so an old backfill event could overwrite a newer live update. Fixed with an on-chain prediction_revisions counter used as a sync cursor, enforced ordering, and resolver-side sync verification before finalization.
WASM reward cap waterfilling underpays uncapped winners
When some winners hit the per-round gain cap, leftover dividends were routed to the takeout instead of redistributed, silently underpaying uncapped winners. The waterfilling algorithm now redistributes the excess and explicitly fails on invalid inputs instead of silently falling back to zero.
Non-idempotent streak reward settlement can lead to duplicate payouts
If the chain accepted a claim but the backend missed the confirmation, the user could retry and receive a second payout. An on-chain replay-protection PDA seeded with (reward_id, streak_id, wallet) now makes the chain itself reject duplicate claims.
Non-qualifying streak participants are not reset until someone wins
Stale streak counters survived into later rounds, so a user with a qualify, fail, qualify sequence could be treated as a streak winner and generate invalid claimable rewards. The reset step now runs every round and the winner query is tightened to wallets that qualified in the current pool.
Pool can be permanently bricked when a winner's leaf exceeds max_roi
The WASM engine used a hardcoded 100x max_roi instead of the pool's configured value, so tighter pools could ship leaves above the on-chain bound: every claim would revert and the vault would be permanently stranded, no attacker required. The calculator now consumes the pool's configured cap.
Token-2022 ATA derivation mismatch can DoS finalize_pool on time-series pools
The canonical streak ATA was derived with legacy SPL derivation, ignoring the token program, so Token-2022 mints always failed validation and blocked settlement. Switched to get_associated_token_address_with_program_id, landed defensively even though Trepa does not currently use Token-2022 mints.
Plus 7 low severity findings. All documented in the full report.
Why Phage Security
Trepa's protocol splits its logic between a minimal Anchor program and a heavy backend, so it needed researchers comfortable on both sides of that boundary. We lifted the critical invariants on-chain and made every money-moving flow idempotent. Trepa shipped fixes for 14 of 15 findings within the remediation window, ready for mainnet.