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
95 changes: 84 additions & 11 deletions contracts/predictify-hybrid/src/disputes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,7 @@ impl DisputeManager {
pub fn set_history_cap(env: &Env, admin: Address, cap: u32) -> Result<(), Error> {
admin.require_auth();
DisputeValidator::validate_admin_permissions(env, &admin)?;
Self::check_admin_cooldown(env, &admin, &Symbol::new(env, "set_history_cap"))?;

let key = DataKey::DisputeHistoryCap;
env.storage().persistent().set(&key, &cap);
Expand All @@ -789,6 +790,7 @@ impl DisputeManager {
pub fn set_anti_grief_floor(env: &Env, admin: Address, floor: i128) -> Result<(), Error> {
admin.require_auth();
DisputeValidator::validate_admin_permissions(env, &admin)?;
Self::check_admin_cooldown(env, &admin, &Symbol::new(env, "set_anti_grief_floor"))?;

let key = DataKey::AntiGriefFloor;
env.storage().persistent().set(&key, &floor);
Expand All @@ -806,6 +808,7 @@ impl DisputeManager {
pub fn set_collusion_detector_config(env: &Env, admin: Address, config: CollusionDetectorConfig) -> Result<(), Error> {
admin.require_auth();
DisputeValidator::validate_admin_permissions(env, &admin)?;
Self::check_admin_cooldown(env, &admin, &Symbol::new(env, "set_collusion_detector_config"))?;

let key = DataKey::CollusionDetectorConfig;
env.storage().persistent().set(&key, &config);
Expand Down Expand Up @@ -1155,6 +1158,9 @@ impl DisputeManager {
// Validate admin permissions
DisputeValidator::validate_admin_permissions(env, &admin)?;

// Enforce admin action cooldown
Self::check_admin_cooldown(env, &admin, &Symbol::new(env, "resolve_dispute"))?;

// Get and validate market
let mut market = MarketStateManager::get_market(env, &market_id)?;
DisputeValidator::validate_market_for_resolution(env, &market)?;
Expand Down Expand Up @@ -2067,6 +2073,12 @@ impl DisputeManager {
// Validate admin permissions
DisputeValidator::validate_admin_permissions(env, &admin)?;

// Enforce admin action cooldown
Self::check_admin_cooldown(env, &admin, &Symbol::new(env, "set_dispute_timeout"))?;

// Enforce admin action cooldown
Self::check_admin_cooldown(env, &admin, &Symbol::new(env, "set_dispute_timeout"))?;

// Validate timeout hours
if timeout_hours == 0 || timeout_hours > 720 {
// Max 30 days
Expand Down Expand Up @@ -2277,6 +2289,12 @@ impl DisputeManager {
// Validate admin permissions
DisputeValidator::validate_admin_permissions(env, &admin)?;

// Enforce admin action cooldown
Self::check_admin_cooldown(env, &admin, &Symbol::new(env, "extend_dispute_timeout"))?;

// Enforce admin action cooldown
Self::check_admin_cooldown(env, &admin, &Symbol::new(env, "extend_dispute_timeout"))?;

// Validate additional hours
if additional_hours == 0 || additional_hours > 168 {
// Max 7 days extension
Expand Down Expand Up @@ -2363,6 +2381,57 @@ impl DisputeManager {
let cap_key = crate::storage::DataKey::DisputeCumulativeStakeCap(user.clone());
env.storage().persistent().get(&cap_key).unwrap_or(0)
}

// ── Admin Cooldown ───────────────────────────────────────────────────────

/// Sets the cooldown period (in seconds) between admin actions on disputes.
///
/// A zero value disables the cooldown entirely. Only the contract admin
/// may call this.
pub fn set_admin_cooldown(env: &Env, admin: &Address, seconds: u64) -> Result<(), Error> {
admin.require_auth();
DisputeValidator::validate_admin_permissions(env, admin)?;
let key = DataKey::DisputeCooldownSeconds;
env.storage().persistent().set(&key, &seconds);
env.storage().persistent().extend_ttl(&key, 535680, 535680);
Ok(())
}

/// Retrieves the configured dispute admin cooldown period in seconds.
/// Returns 0 (no cooldown) when not configured.
pub fn get_admin_cooldown(env: &Env) -> u64 {
let key = DataKey::DisputeCooldownSeconds;
env.storage().persistent().get(&key).unwrap_or(0)
}

/// Enforces the per-function admin cooldown for a named dispute operation.
///
/// * `function_name` – a short identifier (`"set_history_cap"`, `"resolve_dispute"`, …).
///
/// # Errors
/// Returns `Error::AdminActionTimelocked` if the cooldown has not yet elapsed
/// since the last invocation of *this specific* function.
pub fn check_admin_cooldown(
env: &Env,
admin: &Address,
function_name: &Symbol,
) -> Result<(), Error> {
admin.require_auth();
DisputeValidator::validate_admin_permissions(env, admin)?;
let cooldown = Self::get_admin_cooldown(env);
if cooldown == 0 {
return Ok(());
}
let now = env.ledger().timestamp();
let last_key = DataKey::DisputeAdminLastAction(function_name.clone());
let last_action: u64 = env.storage().persistent().get(&last_key).unwrap_or(0);
if last_action > 0 && now < last_action.saturating_add(cooldown) {
return Err(Error::AdminActionTimelocked);
}
env.storage().persistent().set(&last_key, &now);
env.storage().persistent().extend_ttl(&last_key, 535680, 535680);
Ok(())
}
}

// ===== DISPUTE VALIDATOR =====
Expand Down Expand Up @@ -3024,29 +3093,33 @@ impl DisputeUtils {

pub fn emit_dispute_vote_event(
env: &Env,
_dispute_id: &Symbol,
dispute_id: &Symbol,
user: &Address,
vote: bool,
stake: i128,
) {
// In a real implementation, this would emit an event
// For now, we'll just store it in persistent storage
let event_key = symbol_short!("vote_evt");
let event_data = (user.clone(), vote, stake, env.ledger().timestamp());
env.storage().persistent().set(&event_key, &event_data);
crate::events::EventEmitter::emit_dispute_vote_cast(
env,
dispute_id,
user,
vote,
stake,
);
}

/// Emit fee distribution event

pub fn emit_fee_distribution_event(
env: &Env,
_dispute_id: &Symbol,
dispute_id: &Symbol,
distribution: &DisputeFeeDistribution,
) {
// In a real implementation, this would emit an event
// For now, we'll just store it in persistent storage
let event_key = symbol_short!("fee_event");
env.storage().persistent().set(&event_key, distribution);
crate::events::EventEmitter::emit_dispute_fee_distributed(
env,
dispute_id,
distribution.total_fees,
distribution.fees_distributed,
);
}

/// Emit dispute escalation event
Expand Down
12 changes: 12 additions & 0 deletions contracts/predictify-hybrid/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,18 @@ pub enum DataKey {
MarketCache(Symbol),
/// Minimum anti-grief stake floor for disputes.
AntiGriefFloor,
/// Cooldown period in seconds for admin actions on disputes.
DisputeCooldownSeconds,
/// Last admin action timestamp for a specific dispute admin function.
DisputeAdminLastAction(Symbol),
/// Cooldown period in seconds for admin actions on disputes.
DisputeCooldownSeconds,
/// Last admin action timestamp for a specific dispute admin function.
DisputeAdminLastAction(Symbol),
/// Cooldown period in seconds for admin actions on resolution.
ResolutionCooldownSeconds,
/// Last admin action timestamp for a specific resolution admin function.
ResolutionAdminLastAction(Symbol),
/// Global protocol configuration record.
GlobalConfig,
/// Consumed `place_bets` idempotency key, scoped per user.
Expand Down
Loading