Skip to content
Closed
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
13 changes: 11 additions & 2 deletions contracts/predictify-hybrid/src/circuit_breaker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ pub struct CircuitBreakerState {
#[derive(Clone, Debug, PartialEq, Eq)]
#[contracttype]
pub enum PauseScope {
/// Pauses only betting operations: place_bet, place_bets
BettingOnly,
/// Pauses all fund movement operations: deposit, withdraw, place_bet, claim_winnings, distribute_payouts, collect_fees
FundsOnly,
/// Pauses all operations
Full,
}

Expand Down Expand Up @@ -288,7 +292,7 @@ impl CircuitBreaker {
}

/// Check whether a specific operation is allowed under current pause scope.
/// `op` examples: "betting", "create_event", "withdraw", etc.
/// Supported `op` values: "deposit", "withdraw", "place_bet", "claim_winnings", "distribute_payouts", "collect_fees", "betting", "create_event", etc.
pub fn is_operation_allowed(env: &Env, op: &str) -> Result<bool, Error> {
let state = Self::get_state(env)?;

Expand All @@ -297,12 +301,17 @@ impl CircuitBreaker {
BreakerState::Open => match state.pause_scope {
PauseScope::Full => Ok(false),
PauseScope::BettingOnly => {
if op == "betting" {
if op == "betting" || op == "place_bet" {
Ok(false)
} else {
Ok(true)
}
}
PauseScope::FundsOnly => match op {
"deposit" | "withdraw" | "place_bet" | "claim_winnings"
| "distribute_payouts" | "collect_fees" | "betting" => Ok(false),
_ => Ok(true),
},
},
BreakerState::HalfOpen => {
let config = Self::get_config(env)?;
Expand Down
90 changes: 59 additions & 31 deletions contracts/predictify-hybrid/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ mod metadata_limits_tests;
mod monitoring;
#[cfg(test)]
mod multi_admin_multisig_tests;
#[cfg(test)]
mod require_auth_coverage_tests;
mod oracles;
mod performance_benchmarks;
mod queries;
mod rate_limiter;
mod recovery;
mod reentrancy_guard;
#[cfg(test)]
mod require_auth_coverage_tests;
mod resolution;
mod statistics;
mod storage;
Expand All @@ -69,10 +69,10 @@ mod validation;
mod versioning;
mod voting;

#[cfg(any())]
mod test_audit_trail;
#[cfg(test)]
mod override_audit_tests;
#[cfg(any())]
mod test_audit_trail;
// #[cfg(any())]
// mod utils_tests;
// THis is the band protocol wasm std_reference.wasm
Expand Down Expand Up @@ -333,9 +333,10 @@ impl PredictifyHybrid {
events_per_admin_limit: 0,
time_window_seconds: 3600,
};
env.storage()
.persistent()
.set(&crate::rate_limiter::RateLimiterData::Config, &rate_limit_config);
env.storage().persistent().set(
&crate::rate_limiter::RateLimiterData::Config,
&rate_limit_config,
);

// Seed default runtime configuration so validators and query paths have
// deterministic bounds immediately after deployment.
Expand All @@ -344,17 +345,19 @@ impl PredictifyHybrid {

// Seed permissive-but-valid rate limits so admin entrypoints do not
// fail before a custom policy is configured.
crate::rate_limiter::RateLimiter::new(env.clone()).init_rate_limiter(
admin.clone(),
crate::rate_limiter::RateLimitConfig {
voting_limit: 10_000,
dispute_limit: 1_000,
oracle_call_limit: 1_000,
bet_limit: 10_000,
events_per_admin_limit: 1_000,
time_window_seconds: 3_600,
},
).map_err(Error::from)?;
crate::rate_limiter::RateLimiter::new(env.clone())
.init_rate_limiter(
admin.clone(),
crate::rate_limiter::RateLimitConfig {
voting_limit: 10_000,
dispute_limit: 1_000,
oracle_call_limit: 1_000,
bet_limit: 10_000,
events_per_admin_limit: 1_000,
time_window_seconds: 3_600,
},
)
.map_err(Error::from)?;

// Initialize allowed assets
if let Some(assets) = allowed_assets {
Expand Down Expand Up @@ -441,6 +444,11 @@ impl PredictifyHybrid {
asset: ReflectorAsset,
amount: i128,
) -> Result<Balance, Error> {
if let Err(e) =
crate::circuit_breaker::CircuitBreaker::require_write_allowed(&env, "deposit")
{
return Err(e);
}
balances::BalanceManager::deposit(&env, user, asset, amount)
}

Expand All @@ -465,6 +473,11 @@ impl PredictifyHybrid {
asset: ReflectorAsset,
amount: i128,
) -> Result<Balance, Error> {
if let Err(e) =
crate::circuit_breaker::CircuitBreaker::require_write_allowed(&env, "withdraw")
{
return Err(e);
}
balances::BalanceManager::withdraw(&env, user, asset, amount)
}

Expand Down Expand Up @@ -1175,6 +1188,11 @@ impl PredictifyHybrid {
outcome: String,
amount: i128,
) -> crate::types::Bet {
if let Err(e) =
crate::circuit_breaker::CircuitBreaker::require_write_allowed(&env, "place_bet")
{
panic_with_error!(env, e);
}
// Use the BetManager to handle the bet placement
match bets::BetManager::place_bet(&env, user.clone(), market_id, outcome, amount) {
Ok(bet) => {
Expand Down Expand Up @@ -1253,6 +1271,11 @@ impl PredictifyHybrid {
user: Address,
bets: Vec<(Symbol, String, i128)>,
) -> Vec<crate::types::Bet> {
if let Err(e) =
crate::circuit_breaker::CircuitBreaker::require_write_allowed(&env, "place_bet")
{
panic_with_error!(env, e);
}
match bets::BetManager::place_bets(&env, user, bets) {
Ok(placed_bets) => placed_bets,
Err(e) => panic_with_error!(env, e),
Expand Down Expand Up @@ -1669,6 +1692,11 @@ impl PredictifyHybrid {
///
/// State-changing paths may emit events through internal managers; read-only query paths emit no events.
pub fn claim_winnings(env: Env, user: Address, market_id: Symbol) {
if let Err(e) =
crate::circuit_breaker::CircuitBreaker::require_write_allowed(&env, "claim_winnings")
{
panic_with_error!(env, e);
}
user.require_auth();

let mut market: Market = env
Expand Down Expand Up @@ -2884,14 +2912,7 @@ impl PredictifyHybrid {
);

// Emit the dedicated override event for off-chain monitors
EventEmitter::emit_admin_override(
&env,
&market_id,
&admin,
&old_result,
&outcome,
&reason,
);
EventEmitter::emit_admin_override(&env, &market_id, &admin, &old_result, &outcome, &reason);

Ok(())
}
Expand Down Expand Up @@ -3257,6 +3278,11 @@ impl PredictifyHybrid {
///
/// State-changing paths may emit events through internal managers; read-only query paths emit no events.
pub fn collect_fees(env: Env, admin: Address, market_id: Symbol) -> Result<i128, Error> {
if let Err(e) =
crate::circuit_breaker::CircuitBreaker::require_write_allowed(&env, "collect_fees")
{
return Err(e);
}
Self::require_primary_admin(&env, &admin)?;

fees::FeeManager::collect_fees(&env, admin, market_id)
Expand Down Expand Up @@ -3329,6 +3355,12 @@ impl PredictifyHybrid {
///
/// Returns [`Error`] when validation, authorization, storage, or subsystem checks fail.
pub fn distribute_payouts(env: Env, market_id: Symbol) -> Result<i128, Error> {
if let Err(e) = crate::circuit_breaker::CircuitBreaker::require_write_allowed(
&env,
"distribute_payouts",
) {
return Err(e);
}
let mut market: Market = env
.storage()
.persistent()
Expand Down Expand Up @@ -3822,11 +3854,7 @@ impl PredictifyHybrid {
max_confidence_bps,
max_deviation_bps,
};
crate::oracles::OracleValidationConfigManager::set_event_config(
&env,
&market_id,
&config,
)?;
crate::oracles::OracleValidationConfigManager::set_event_config(&env, &market_id, &config)?;

let mut details = Map::new(&env);
details.set(
Expand Down
Loading