diff --git a/contracts/predictify-hybrid/src/disputes.rs b/contracts/predictify-hybrid/src/disputes.rs index ecbf1b6a..e02461b6 100644 --- a/contracts/predictify-hybrid/src/disputes.rs +++ b/contracts/predictify-hybrid/src/disputes.rs @@ -72,6 +72,13 @@ pub struct Dispute { pub status: DisputeStatus, } +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DisputeDecayConfig { + pub half_life_seconds: u64, + pub floor_bps: u32, +} + /// Represents the current lifecycle status of a dispute. /// /// Disputes progress through various states from creation to final resolution. @@ -2723,12 +2730,16 @@ impl DisputeUtils { // Update voting statistics voting_data.total_votes += 1; + + // Calculate the decayed stake using tally_votes + let decayed_stake = Self::tally_votes(env, vote.stake, vote.timestamp, voting_data.voting_start); + if vote.vote { voting_data.support_votes += 1; - voting_data.total_support_stake += vote.stake; + voting_data.total_support_stake += decayed_stake; } else { voting_data.against_votes += 1; - voting_data.total_against_stake += vote.stake; + voting_data.total_against_stake += decayed_stake; } // Store updated voting data @@ -2740,6 +2751,46 @@ impl DisputeUtils { Ok(()) } + /// Calculate the stake weight using exponential decay approximation + /// so late votes count less than early votes. + pub fn tally_votes(env: &Env, raw_stake: i128, vote_time: u64, window_start: u64) -> i128 { + let config_key = symbol_short!("decaycfg"); + let config: Option = env.storage().persistent().get(&config_key); + + let cfg = match config { + Some(c) => c, + None => return raw_stake, + }; + + if cfg.half_life_seconds == 0 { + return raw_stake; + } + + let elapsed = vote_time.saturating_sub(window_start); + let num_half_lives = elapsed / cfg.half_life_seconds; + let rem = elapsed % cfg.half_life_seconds; + + let shift = num_half_lives.min(16) as u32; + let weight_at_n = 10000u32.checked_shr(shift).unwrap_or(0); + let weight_at_n_plus_1 = 10000u32.checked_shr(shift + 1).unwrap_or(0); + + let diff = weight_at_n.saturating_sub(weight_at_n_plus_1); + let exact_weight = weight_at_n.saturating_sub((diff as u64 * rem / cfg.half_life_seconds) as u32); + + let final_weight = exact_weight.max(cfg.floor_bps); + + (raw_stake * final_weight as i128) / 10000 + } + + pub fn set_dispute_decay_config(env: &Env, admin: Address, config: DisputeDecayConfig) -> Result<(), Error> { + admin.require_auth(); + DisputeValidator::validate_admin_permissions(env, &admin)?; + let key = symbol_short!("decaycfg"); + env.storage().persistent().set(&key, &config); + env.storage().persistent().extend_ttl(&key, 535680, 535680); + Ok(()) + } + /// Get dispute voting data pub fn get_dispute_voting(env: &Env, dispute_id: &Symbol) -> Result { let key = (symbol_short!("dispute_v"), dispute_id.clone()); diff --git a/docs/contracts/EVENT_ARCHIVE.md b/docs/contracts/EVENT_ARCHIVE.md index 56b9033c..ee24bc22 100644 --- a/docs/contracts/EVENT_ARCHIVE.md +++ b/docs/contracts/EVENT_ARCHIVE.md @@ -2,6 +2,8 @@ Bounded on-chain storage for resolved and cancelled prediction markets, with paginated historical queries. + + ## Overview `EventArchive` (in `contracts/predictify-hybrid/src/event_archive.rs`) lets admins mark resolved or cancelled markets as archived and exposes read-only paginated queries for analytics and UI. Only public metadata is returned — no votes, individual stakes, or addresses. diff --git a/scripts/check_wasm_size.sh b/scripts/check_wasm_size.sh index 65e18946..ab7fac09 100755 --- a/scripts/check_wasm_size.sh +++ b/scripts/check_wasm_size.sh @@ -1,6 +1,7 @@ #!/bin/bash set -e + # Default budget: 768 KiB = 768 * 1024 = 786432 bytes BUDGET=${WASM_SIZE_BUDGET:-786432}