Skip to content
Merged
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
97 changes: 68 additions & 29 deletions contract/contracts/predifi-contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,53 +394,89 @@ pub struct UserPredictionDetail {

/// Internal storage keys for contract data.
///
/// This enum defines all the keys used to store and retrieve data from
/// Soroban's storage. Each variant corresponds to a specific data type.
/// All variants use PascalCase. Abbreviated names are preserved for existing
/// on-chain keys to avoid storage migration (Soroban uses the variant name as
/// the XDR discriminant). New variants added here use full descriptive names.
///
/// # Naming conventions
/// - Existing abbreviated variants (e.g. `OutStake`, `UsrPrdCnt`) are kept
/// verbatim to preserve on-chain discriminant values.
/// - New variants added after the initial deployment use full PascalCase names
/// (e.g. `OracleConfig`, `PriceFeed`, `PriceCondition`).
/// - All variants are documented with their storage type mapping.
#[contracttype]
#[derive(Clone)]
pub enum DataKey {
/// Pool data by pool ID: Pool(pool_id) -> Pool
// ── Pool data ────────────────────────────────────────────────────────────

/// Pool data by pool ID: `Pool(pool_id)` -> `Pool`
Pool(u64),
/// User prediction by user address and pool ID: Pred(user, pool_id) -> Prediction
Pred(Address, u64),
/// Pool ID counter for generating unique pool IDs.
/// Pool ID counter for generating unique pool IDs: `PoolIdCtr` -> `u64`
PoolIdCtr,
/// Tracks whether a user has claimed winnings for a pool: Claimed(user, pool_id) -> bool
/// Participant count for a pool: `PartCnt(pool_id)` -> `u32`
PartCnt(u64),

// ── Predictions & stakes ─────────────────────────────────────────────────

/// User prediction by user address and pool ID: `Pred(user, pool_id)` -> `Prediction`
Pred(Address, u64),
/// Tracks whether a user has claimed winnings for a pool: `Claimed(user, pool_id)` -> `bool`
Claimed(Address, u64),
/// Stake amount for a specific outcome: OutStake(pool_id, outcome) -> i128
/// Stake amount for a specific outcome (backward-compat individual key):
/// `OutStake(pool_id, outcome)` -> `i128`
OutStake(u64, u32),
/// Optimized storage for markets with many outcomes (e.g., 32+ teams).
/// Stores all outcome stakes as a single `Vec<i128>` to reduce storage reads.
/// Optimized batch storage for all outcome stakes in a pool:
/// `OutStakes(pool_id)` -> `Vec<i128>`
///
/// Preferred over `OutStake` for pools with many outcomes. Falls back to
/// `OutStake` for backward compatibility when this key is absent.
OutStakes(u64),
/// User prediction count: UsrPrdCnt(user) -> u32
/// User prediction count: `UsrPrdCnt(user)` -> `u32`
UsrPrdCnt(Address),
/// User prediction index: UsrPrdIdx(user, index) -> UserPredictionDetail
/// User prediction index: `UsrPrdIdx(user, index)` -> `UserPredictionDetail`
UsrPrdIdx(Address, u32),
/// Global protocol configuration: Config -> Config

// ── Protocol configuration ───────────────────────────────────────────────

/// Global protocol configuration: `Config` -> `Config`
Config,
/// Contract pause state: Paused -> bool
/// Contract pause state: `Paused` -> `bool`
Paused,
/// Reentrancy guard: RentGuard -> bool
/// Contract version for safe upgrade migrations: `Version` -> `u32`
Version,
/// Referral cut in basis points: `ReferralCutBps` -> `u32`
ReferralCutBps,
/// Reentrancy guard (temporary storage): `RentGuard` -> `bool`
RentGuard,
/// Category pool count: CatPoolCt(category) -> u32

// ── Token whitelist ──────────────────────────────────────────────────────

/// Token whitelist entry: `TokenWl(token_address)` -> `bool`
///
/// Present (with value `true`) when the token is allowed for betting.
TokenWl(Address),

// ── Categories ───────────────────────────────────────────────────────────

/// Category pool count: `CatPoolCt(category)` -> `u32`
CatPoolCt(Symbol),
/// Category pool index: CatPoolIx(category, index) -> u64
/// Category pool index: `CatPoolIx(category, index)` -> `u64` (pool_id)
CatPoolIx(Symbol, u32),
/// Token whitelist: TokenWl(token_address) -> true if allowed for betting.
TokenWl(Address),
/// Participant count for a pool: PartCnt(pool_id) -> u32
PartCnt(u64),
/// Tracks if an oracle has already voted: ResVote(pool_id, oracle_address)

// ── Resolution voting ────────────────────────────────────────────────────

/// Tracks if an oracle/operator has already voted: `ResVote(pool_id, voter_address)` -> `()`
ResVote(u64, Address),
/// Tracks vote count for a specific outcome: ResVoteCt(pool_id, outcome)
/// Vote count for a specific outcome: `ResVoteCt(pool_id, outcome)` -> `u32`
ResVoteCt(u64, u32),
/// Tracks total number of votes cast for a pool: ResTotal(pool_id)
/// Total number of votes cast for a pool: `ResTotal(pool_id)` -> `u32`
ResTotal(u64),
/// Referral cut in basis points: ReferralCutBps -> u32
ReferralCutBps,
/// Referred volume for a referrer and pool: ReferredVolume(referrer, pool_id) -> i128

// ── Referrals ────────────────────────────────────────────────────────────

/// Referred volume for a referrer and pool: `ReferredVolume(referrer, pool_id)` -> `i128`
ReferredVolume(Address, u64),
/// Referrer address for a user and pool: Referrer(user, pool_id) -> Address
/// Referrer address for a user and pool: `Referrer(user, pool_id)` -> `Address`
///
/// FUTURE: Multiple referrers per user per pool
/// Currently a user can only have one referrer per pool. If multiple referrers are needed
Expand All @@ -451,7 +487,10 @@ pub enum DataKey {
/// and distribute proportional cuts. Until that requirement is confirmed, the single-referrer
/// model is kept for simplicity and gas efficiency.
Referrer(Address, u64),
/// User whitelist for private pools: Whitelist(pool_id, user_address)

// ── Private pools ────────────────────────────────────────────────────────

/// User whitelist for private pools: `Whitelist(pool_id, user_address)` -> `()`
Whitelist(u64, Address),
/// Contract version for safe upgrade migrations.
Version,
Expand Down
39 changes: 16 additions & 23 deletions contract/contracts/predifi-contract/src/price_feed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//! 4. **Resolve Pool**: Once the market ends, call `resolve_pool_from_price` to automatically
//! determine the winning outcome based on the latest valid price data.

use crate::PredifiError;
use crate::{DataKey, PredifiError};
use soroban_sdk::{contracttype, Address, Env, Symbol, Vec};

/// Price feed data structure for external oracle integration.
Expand Down Expand Up @@ -101,19 +101,13 @@ pub struct OracleConfig {

/// Storage keys for price feed data.
///
/// This enum defines all storage keys used by the price feed system.
#[derive(Clone)]
#[contracttype]
pub enum PriceFeedDataKey {
/// Oracle configuration: OracleConfig -> OracleConfig
OracleConfig,
/// Registered price feeds: PriceFeed(feed_pair) -> PriceFeed
PriceFeed(Symbol),
/// Price conditions for pools: PriceCondition(pool_id) -> PriceCondition
PriceCondition(u64),
/// Last update timestamp for each feed: LastUpdate(feed_pair) -> u64
LastUpdate(Symbol),
}
/// Deprecated: use `DataKey` from `lib.rs` directly. This type alias is kept
/// for documentation purposes only and will be removed in a future version.
///
/// All price-feed storage now uses the canonical `DataKey` variants:
/// - `DataKey::OracleConfig` — oracle configuration
/// - `DataKey::PriceFeed(feed_pair)` — price feed data
/// - `DataKey::PriceCondition(pool_id)` — per-pool price conditions

/// Price feed adapter for external oracle integration
#[allow(dead_code)]
Expand Down Expand Up @@ -143,7 +137,7 @@ impl PriceFeedAdapter {

env.storage()
.persistent()
.set(&PriceFeedDataKey::OracleConfig, &config);
.set(&DataKey::OracleConfig, &config);

Ok(())
}
Expand All @@ -152,7 +146,7 @@ impl PriceFeedAdapter {
pub fn get_oracle_config(env: &Env) -> OracleConfig {
env.storage()
.persistent()
.get(&PriceFeedDataKey::OracleConfig)
.get(&DataKey::OracleConfig)
.expect("Oracle config not initialized")
}

Expand Down Expand Up @@ -192,11 +186,10 @@ impl PriceFeedAdapter {
// Store price feed data
env.storage()
.persistent()
.set(&PriceFeedDataKey::PriceFeed(feed_pair.clone()), &feed);
.set(&DataKey::PriceFeed(feed_pair.clone()), &feed);

env.storage()
.persistent()
.set(&PriceFeedDataKey::LastUpdate(feed_pair), &timestamp);
// Note: last-update timestamp is embedded in PriceFeed.timestamp;
// no separate LastUpdate key is needed.

Ok(())
}
Expand All @@ -206,7 +199,7 @@ impl PriceFeedAdapter {
let feed: Option<PriceFeed> = env
.storage()
.persistent()
.get(&PriceFeedDataKey::PriceFeed(feed_pair.clone()));
.get(&DataKey::PriceFeed(feed_pair.clone()));

feed
}
Expand Down Expand Up @@ -243,7 +236,7 @@ impl PriceFeedAdapter {
) -> Result<(), PredifiError> {
env.storage()
.persistent()
.set(&PriceFeedDataKey::PriceCondition(pool_id), &condition);
.set(&DataKey::PriceCondition(pool_id), &condition);

Ok(())
}
Expand All @@ -252,7 +245,7 @@ impl PriceFeedAdapter {
pub fn get_price_condition(env: &Env, pool_id: u64) -> Option<PriceCondition> {
env.storage()
.persistent()
.get(&PriceFeedDataKey::PriceCondition(pool_id))
.get(&DataKey::PriceCondition(pool_id))
}

/// Evaluate price condition against current price data
Expand Down
Loading
Loading