Skip to content

feat: add admin-only force-resolve market function with idempotency-key support#2

Open
muffti123 wants to merge 1 commit into
masterfrom
feature/admin-force-resolve
Open

feat: add admin-only force-resolve market function with idempotency-key support#2
muffti123 wants to merge 1 commit into
masterfrom
feature/admin-force-resolve

Conversation

@muffti123

Copy link
Copy Markdown
Owner

Summary

Implements an admin-only force_resolve_market entrypoint that allows the contract admin to resolve any market (Active, Ended, Disputed, or already Resolved) to an arbitrary set of winning outcomes, bypassing oracle resolution. Idempotency is guaranteed via a caller-supplied key, making the operation safe to retry.

Closes Predictify-org#179

New Files

File Purpose
contracts/predictify-hybrid/src/force_resolve.rs ForceResolveRecord struct and ForceResolveManager with persistent idempotency storage
contracts/predictify-hybrid/src/force_resolve_tests.rs 13 tests covering all paths

Modified Files

File Change
src/lib.rs Added mod force_resolve, mod force_resolve_tests, and force_resolve_market entrypoint
src/err.rs Added ForceResolveAlreadyUsed = 435 with full severity/category/recoverability classification
src/resolution.rs Added ForceResolve variant to ResolutionMethod enum (confidence 100)
Various (admin.rs, bets.rs, fees.rs, oracles.rs, storage.rs, etc.) Pre-existing compilation fixes needed to run tests

Design Decisions

Decision Rationale
Idempotency key Caller-provided String stored as (symbol_short!("frc_rslv"), market_id, key) — safe for retries
Key scope Per-market: same key on different markets = independent resolutions
No state restriction Force-resolve works on Active, Ended, Disputed, or Resolved markets (bypasses end_time)
Crash-safe ordering Idempotency record persisted AFTER market state so a mid-write crash does not permanently block resolution
Resolution method "ForceResolve" with confidence 100 (highest authority)

API

fn force_resolve_market(
    env: Env,
    admin: Address,
    market_id: Symbol,
    winning_outcomes: Vec<String>,
    idempotency_key: String,
);

Returns nothing. Panics on error (Standard Soroban pattern). try_* variant returns wrapped contract error codes.

Validation Order (in entrypoint)

  1. Admin auth check
  2. Empty outcomes → InvalidInput (401)
  3. Market lookup → MarketNotFound (101)
  4. Outcome membership → InvalidOutcome (108)
  5. Idempotency key check → early return (no-op) if already used

Tests (13 total, all passing)

  • ✅ Force-resolve active market
  • ✅ Force-resolve ended market
  • ✅ Force-resolve already-resolved market
  • ✅ Idempotency: same key re-use (no-op)
  • ✅ Idempotency: different keys on same market
  • ✅ Idempotency: record persistence after call
  • ✅ Idempotency: key uniqueness across markets
  • ✅ Unauthorized user -> Error::Unauthorized
  • ✅ Market not found -> Error::MarketNotFound
  • ✅ Invalid outcome -> Error::InvalidOutcome
  • ✅ Empty outcomes -> Error::InvalidInput
  • ✅ Event emission on resolution
  • ✅ Multiple winning outcomes (tie)

Implements an admin-only force_resolve_market entrypoint on the
PredictifyHybrid contract that can resolve a market BEFORE its
end_time, with strict idempotency-key replay protection.

Closes Predictify-org#648

=== API Changes ===

New function:
  force_resolve_market(env, admin, market_id, winning_outcomes,
                       reason, idempotency_key) -> Result<(), Error>

Key differences from existing resolve_market_manual:
  - Bypasses end_time check (resolves at any time)
  - Returns Result<(), Error> instead of panicking
  - Requires non-empty 'reason' (stored in audit trail)
  - Requires a unique idempotency_key (string, e.g. UUID) per market
  - Supports multiple winning outcomes (Vec<String>)

=== New Error Codes ===
  ForceResolveReplayed       (517) - idempotency key already used
  ForceResolveReasonEmpty    (518) - empty reason string

=== New Events ===
  ForceResolvedEvent (topic 'frc_rs') emitted on success

=== New Audit Action ===
  AuditAction::MarketForceResolved

=== New Module ===
  force_resolve.rs - ForceResolveManager for idempotency storage

=== Files ===
  M src/err.rs       - error variants, classification, recovery
  M src/events.rs    - event struct + ForceResolvedEvent emitter
  M src/lib.rs       - force_resolve_market function + test mod
  M src/audit_trail.rs - MarketForceResolved audit action
  A src/force_resolve_tests.rs - comprehensive tests
@muffti123 muffti123 force-pushed the feature/admin-force-resolve branch from 2ce1670 to 217cbb1 Compare June 28, 2026 11:02
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.

1 participant