Skip to content
Open
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
8 changes: 8 additions & 0 deletions contracts/predictify-hybrid/src/bets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,10 @@ impl BetManager {
// Require authentication from the user
user.require_auth();

// Enforce global per-ledger bet cap
let rate_limiter = crate::rate_limiter::RateLimiter::new(env.clone());
rate_limiter.rate_limit_global_bets_per_ledger()?;

// Slippage check: verify live fee is not above the maximum acceptable threshold
// max_fee_bps == 0 means no slippage guard
if max_fee_bps > 0 {
Expand Down Expand Up @@ -453,6 +457,10 @@ impl BetManager {
for bet_data in bets.iter() {
let (market_id, outcome, amount) = bet_data;

// Enforce global per-ledger bet cap for each bet in the batch
let rate_limiter = crate::rate_limiter::RateLimiter::new(env.clone());
rate_limiter.rate_limit_global_bets_per_ledger()?;

// Get and validate market
let market = MarketStateManager::get_market(env, &market_id)?;
BetValidator::validate_market_for_betting(env, &market)?;
Expand Down
2 changes: 2 additions & 0 deletions contracts/predictify-hybrid/src/err.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ pub enum Error {
ReplayedOverride = 526,
/// Oracle quote is an outlier relative to the rolling median history.
OracleQuoteOutlier = 527,
/// Global per-ledger bet cap has been exceeded to dampen flash-trading bursts.
PerLedgerBetCapExceeded = 528,
}

// ===== ERROR CATEGORIZATION AND RECOVERY SYSTEM =====
Expand Down
36 changes: 36 additions & 0 deletions contracts/predictify-hybrid/src/rate_limiter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,42 @@ impl RateLimiter {
Ok(())
}

/// Rate limit global bets per ledger to dampen flash-trading bursts.
/// Resets implicitly when ledger sequence advances.
pub fn rate_limit_global_bets_per_ledger(&self) -> Result<(), crate::err::Error> {
let cap: u32 = self.env.storage().persistent().get(&crate::storage::DataKey::PerLedgerBetCap).unwrap_or(0);
if cap == 0 {
return Ok(());
}

let seq = self.env.ledger().sequence();
let counter_key = crate::storage::DataKey::PerLedgerBetCounter;

// Stored as (ledger_sequence, count)
let mut count: (u32, u32) = self.env.storage().temporary().get(&counter_key).unwrap_or((seq, 0));

if count.0 != seq {
count = (seq, 0);
}

if count.1 >= cap {
return Err(crate::err::Error::PerLedgerBetCapExceeded);
}

count.1 += 1;
self.env.storage().temporary().set(&counter_key, &count);
self.env.storage().temporary().extend_ttl(&counter_key, 100, 100);

Ok(())
}

/// Set the global per-ledger bet cap (admin only).
pub fn set_per_ledger_bet_cap(&self, admin: Address, cap: u32) -> Result<(), RateLimiterError> {
admin.require_auth();
self.env.storage().persistent().set(&crate::storage::DataKey::PerLedgerBetCap, &cap);
Ok(())
}

/// Rate limit event creation: max events per admin per time window.
/// Caller (e.g. create_market) must have already authenticated admin.
pub fn rate_limit_admin_events(&self, admin: Address) -> Result<(), RateLimiterError> {
Expand Down
4 changes: 4 additions & 0 deletions contracts/predictify-hybrid/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ pub enum DataKey {
MarketCache(Symbol),
/// Nonce for admin override replay protection.
AdminOverrideNonce(Address),
/// Global counter for bets placed in the current ledger.
PerLedgerBetCounter,
/// Configurable per-ledger bet cap.
PerLedgerBetCap,
}

/// Storage format version for migration tracking
Expand Down
2 changes: 2 additions & 0 deletions scripts/check_wasm_size.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/bin/bash
set -e



# Default budget: 768 KiB = 768 * 1024 = 786432 bytes
BUDGET=${WASM_SIZE_BUDGET:-786432}

Expand Down
Loading