Skip to content

[CRITICAL] Persistent storage TTLs can expire before claims/refunds — user funds permanently locked #174

Description

@grantfox-oss

Summary

prediction_market/src/lib.rs stores BetEntry records in persistent storage with fixed TTLs (lines ~1200-1240). These TTLs are not extended on the read path — neither get_payout nor get_market_state calls extend_ttl. If a market's resolution or payout window extends beyond the stored TTL, the BetEntry expires and the user's claimable funds are permanently lost.

The same issue affects RefundEntry in cancel_refund and PendingWithdrawal in the fee withdrawal flow.

Impact

  • Permanent loss: an expired BetEntry means the user cannot claim their winnings — the XLM is trapped in the contract with no retrieval path.
  • Read-accelerated expiry: every call to get_payout or get_market_state consumes ledger gas without bumping TTL, so frequent queries accelerate expiry.
  • No keeper guarantee: if the automated keeper that calls refresh_market_keys is offline, all near-expiry entries are lost.
  • Market lifecycle mismatch: markets can have resolution windows of 60+ days, but TTLs are set to ~30 days at creation time.

Why it's hard

  • The TTL must be set at write time, but the full claim window is not known until resolve_market is called.
  • Extending on read costs gas, which may make querying prohibitively expensive.
  • A batch-refresh mechanism requires iterating an unbounded list of entries (gas DoS).

Fix

  • Extend TTL in every read path that touches user-claimable state.
  • Set TTLs relative to the maximum possible claim window (resolution_time + 30 days), not the creation time.
  • Add a public bump_ttl(market_id, user) callable by anyone for entries close to expiry.
  • Emit a ClaimWindowExpired event so off-chain indexers can alert users.

Metadata

Metadata

Labels

GrantFox OSSIssue tracked in GrantFox OSSThird CampaignCampaign: Third CampaigncriticalCritical severity - funds at riskprediction-marketPrediction market contractttl-storageTTL / storage lifecycle

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions