From 815f2ca02f84bebd7c36cf11dc231445f67cb885 Mon Sep 17 00:00:00 2001 From: Sakariyah Abdulhazeem Date: Tue, 29 Sep 2026 18:12:31 +0100 Subject: [PATCH] fix(betting): defer bet events until transaction success --- Cargo.lock | 119 +++++++++++---------- contracts/predictify-hybrid/src/bets.rs | 136 +++++++++++++++--------- 2 files changed, 146 insertions(+), 109 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d2bd283..8bd79cd4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,13 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 - -[[package]] -name = "admin" -version = "0.0.0" -dependencies = [ - "soroban-sdk", -] +version = 4 [[package]] name = "ahash" @@ -35,12 +28,6 @@ dependencies = [ "soroban-sdk", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.6" @@ -297,9 +284,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.5.1" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" dependencies = [ "find-msvc-tools", "jobserver", @@ -808,9 +795,9 @@ checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" -version = "0.1.14" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "fnv" @@ -874,10 +861,21 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if 1.0.5", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if 1.0.5", + "libc", + "r-efi 6.0.0", +] + [[package]] name = "group" version = "0.13.0" @@ -1088,18 +1086,19 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.106" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ "cfg-if 1.0.5", "futures-util", @@ -1295,9 +1294,9 @@ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "portable-atomic-util" -version = "0.2.8" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] @@ -1405,6 +1404,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.8" @@ -1769,9 +1774,9 @@ checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" [[package]] name = "soroban-builtin-sdk-macros" -version = "22.1.0" +version = "22.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df6c3137afcdb5a62b9ed3b1994ff9439351eaa795d8d33f758b4386ce2d0060" +checksum = "cf2e42bf80fcdefb3aae6ff3c7101a62cf942e95320ed5b518a1705bc11c6b2f" dependencies = [ "itertools", "proc-macro2", @@ -1781,9 +1786,9 @@ dependencies = [ [[package]] name = "soroban-env-common" -version = "22.1.0" +version = "22.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24deb45507c219d3608f04768c3b700b627bc345cc4c19e31c4cc57fc7d77be9" +checksum = "027cd856171bfd6ad2c0ffb3b7dfe55ad7080fb3050c36ad20970f80da634472" dependencies = [ "arbitrary", "crate-git-revision", @@ -1800,9 +1805,9 @@ dependencies = [ [[package]] name = "soroban-env-guest" -version = "22.1.0" +version = "22.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b010a822db635139fcfdb29be8a333606e5204c445e044d3450a5c47eaba20a" +checksum = "9a07dda1ae5220d975979b19ad4fd56bc86ec7ec1b4b25bc1c5d403f934e592e" dependencies = [ "soroban-env-common", "static_assertions", @@ -1810,9 +1815,9 @@ dependencies = [ [[package]] name = "soroban-env-host" -version = "22.1.0" +version = "22.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee57de1756bae449a52da032c8312113f355cbcf7e906fbb8957a44827999dc" +checksum = "66e8b03a4191d485eab03f066336112b2a50541a7553179553dc838b986b94dd" dependencies = [ "ark-bls12-381", "ark-ec", @@ -1846,9 +1851,9 @@ dependencies = [ [[package]] name = "soroban-env-macros" -version = "22.1.0" +version = "22.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1528c2d0d19ab70e9947f930f8707fdc0e9bfd4baf6d275b8a2f67482c2f2d99" +checksum = "00eff744764ade3bc480e4909e3a581a240091f3d262acdce80b41f7069b2bd9" dependencies = [ "itertools", "proc-macro2", @@ -1861,9 +1866,9 @@ dependencies = [ [[package]] name = "soroban-ledger-snapshot" -version = "22.0.2" +version = "22.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8c348b829dbd5b1c4a9d9858ddf5c793c24ca94373feb82290cdf9746b1fa5" +checksum = "c30035cf1e8f02f65de3e594b6da113ecdaf1cd134d8480961d62568bb15adaf" dependencies = [ "serde", "serde_json", @@ -1875,9 +1880,9 @@ dependencies = [ [[package]] name = "soroban-sdk" -version = "22.0.0" +version = "22.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cc7f941162a765de4762987de60d6168ae7c92b42037212918818dfe9d6b242" +checksum = "ff18e8d7ca6d5340a211605ca2c86383bd4dfacc4f8253d72a1573974ffffe69" dependencies = [ "arbitrary", "bytes-lit", @@ -1897,9 +1902,9 @@ dependencies = [ [[package]] name = "soroban-sdk-macros" -version = "22.0.2" +version = "22.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad3dcc9d5fd9ddf5c8b5c9cdcf3807ccc127bfaef1be23fc4a5d51ad167381f7" +checksum = "42b205cd86b34d530db87667bd287fbb194166d79b368227fd842110a914fde8" dependencies = [ "crate-git-revision", "darling 0.20.11", @@ -1917,9 +1922,9 @@ dependencies = [ [[package]] name = "soroban-spec" -version = "22.0.2" +version = "22.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343672b1374951b1f601c07eca944db2c4f0d5c47e94b29d8fb2d42af1215f30" +checksum = "cb6a16f2de28852c759f4da5f28cda54ec0d8dfa4c0e6e8cb3495234a72b0cea" dependencies = [ "base64 0.13.1", "stellar-xdr", @@ -1929,9 +1934,9 @@ dependencies = [ [[package]] name = "soroban-spec-rust" -version = "22.0.2" +version = "22.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47205064fcd8fefe5dba103612cabb448dac64829a3e7bbcca221550a22f59df" +checksum = "cdc6db5902ab21290dddf63fec4ee95703fe59891a947646e7b8607536f043fc" dependencies = [ "prettyplease", "proc-macro2", @@ -1991,9 +1996,9 @@ dependencies = [ [[package]] name = "stellar-xdr" -version = "22.0.0" +version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c2130275cc730d042b3082f51145f0486f5a543d6d72fced02ed9048b82b57" +checksum = "2ce69db907e64d1e70a3dce8d4824655d154749426a6132b25395c49136013e4" dependencies = [ "arbitrary", "base64 0.13.1", @@ -2057,7 +2062,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", "rustix", "windows-sys", @@ -2196,9 +2201,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.129" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if 1.0.5", "once_cell", @@ -2209,9 +2214,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.129" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2219,22 +2224,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.129" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 3.0.6", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.129" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] diff --git a/contracts/predictify-hybrid/src/bets.rs b/contracts/predictify-hybrid/src/bets.rs index 3ab19388..6b5e484e 100644 --- a/contracts/predictify-hybrid/src/bets.rs +++ b/contracts/predictify-hybrid/src/bets.rs @@ -22,9 +22,9 @@ use soroban_sdk::{contracttype, symbol_short, Address, BytesN, Env, Map, String, Symbol, Vec}; use crate::err::Error; -use crate::reentrancy_guard::{ReentrancyGuard, GuardError as ReentrancyError}; use crate::events::EventEmitter; use crate::markets::{MarketStateManager, MarketUtils, MarketValidator}; +use crate::reentrancy_guard::{GuardError as ReentrancyError, ReentrancyGuard}; use crate::types::{Bet, BetLimits, BetStats, BetStatus, Market, MarketState}; // use crate::validation; @@ -321,11 +321,7 @@ pub fn get_market_max_bet_cap(env: &Env, market_id: &Symbol) -> Option { /// /// - [`Error::MarketNotFound`] if `market_id` does not correspond to an existing market /// - [`Error::InvalidInput`] if `min_amount` is zero, negative, or exceeds [`MAX_BET_AMOUNT`] -pub fn set_market_min_bet( - env: &Env, - market_id: &Symbol, - min_amount: i128, -) -> Result<(), Error> { +pub fn set_market_min_bet(env: &Env, market_id: &Symbol, min_amount: i128) -> Result<(), Error> { if min_amount <= 0 || min_amount > MAX_BET_AMOUNT { return Err(Error::InvalidInput); } @@ -608,13 +604,14 @@ impl BetManager { MarketStateManager::update_market(env, &market_id, &market); - // Emit bet placed event - EventEmitter::emit_bet_placed(env, &market_id, &user, &outcome, amount); - // Record resource snapshot for regression baseline. // write_count: BetKey + BetRegistryKey + MarketBetsKey + UserStake + Market = 5 writes crate::gas::BetSnapshotManager::record(env, _cpu_before, 5, &market_id); + // Publish only after every fallible operation has completed. This + // keeps a failed call from exposing an intermediate bet event. + EventEmitter::emit_bet_placed(env, &market_id, &user, &outcome, amount); + Ok(bet) } @@ -672,7 +669,8 @@ impl BetManager { user.require_auth(); // --- Idempotency guard: reject replayed batches --- - let idem_key = crate::storage::DataKey::PlaceBetsIdem(user.clone(), idempotency_key.clone()); + let idem_key = + crate::storage::DataKey::PlaceBetsIdem(user.clone(), idempotency_key.clone()); if env.storage().persistent().has(&idem_key) { return Err(Error::IdempotentBatchAlreadyApplied); } @@ -761,7 +759,6 @@ impl BetManager { total_amount = total_amount .checked_add(amount) .ok_or(Error::InvalidInput)?; - } // Enforce the global per-ledger bet cap for each bet in the batch. The @@ -780,7 +777,11 @@ impl BetManager { // ===================================================================== // Phase 3: Commit all bets. Every entry has already been validated. + // Defer event publication until the complete batch has committed so + // indexers never observe progress from a batch that later fails. // ===================================================================== + let mut pending_bet_events: soroban_sdk::Vec<(Symbol, String, i128)> = + soroban_sdk::Vec::new(env); let mut placed_bets = soroban_sdk::Vec::new(env); for bet_data in bets.iter() { @@ -826,21 +827,13 @@ impl BetManager { MarketStateManager::update_market(env, &market_id, &market); - // Emit bet placed event - EventEmitter::emit_bet_placed(env, &market_id, &user, &outcome, amount); + pending_bet_events.push_back((market_id, outcome, amount)); placed_bets.push_back(bet); } - // Phase 4: Emit batch event for the entire operation - EventEmitter::emit_bet_batch_placed( - env, - &user, - &bets, - total_amount, - ); - - // Phase 5: Consume the idempotency key so replays are rejected. + // Phase 4: Consume the idempotency key before publishing events. This + // is the final state-changing operation in the batch. // Retain the replay guard for the full market-record horizon. A short // seven-day TTL would allow a delayed transaction to be replayed after // the guard expired and charge the user a second time. @@ -848,6 +841,14 @@ impl BetManager { env.storage().persistent().set(&idem_key, &true); env.storage().persistent().extend_ttl(&idem_key, ttl, ttl); + // Phase 5: Publish events only after the whole batch has committed. + for (market_id, outcome, amount) in pending_bet_events.iter() { + EventEmitter::emit_bet_placed(env, &market_id, &user, &outcome, amount); + } + + // Emit batch event for the entire operation. + EventEmitter::emit_bet_batch_placed(env, &user, &bets, total_amount); + Ok(placed_bets) } @@ -1111,7 +1112,8 @@ impl BetManager { return Ok(0); } - let fee_percentage = crate::fees::FeeManager::get_fee_percentage_for_timestamp(env, bet.timestamp); + let fee_percentage = + crate::fees::FeeManager::get_fee_percentage_for_timestamp(env, bet.timestamp); let fee = (summary.total_pool * fee_percentage as i128) / 10_000; let distributable_pool = summary.total_pool - fee; @@ -1569,7 +1571,10 @@ impl BetValidator { /// # Errors /// /// - [`Error::BetBelowMarketMin`] when `amount < market.min_bet_amount` - pub fn validate_market_min_bet(market: &crate::types::Market, amount: i128) -> Result<(), Error> { + pub fn validate_market_min_bet( + market: &crate::types::Market, + amount: i128, + ) -> Result<(), Error> { if let Some(min) = market.min_bet_amount { if amount < min { return Err(Error::BetBelowMarketMin); @@ -1641,12 +1646,11 @@ impl BetValidator { pub fn validate_fee_slippage(env: &Env, max_fee_bps: i128) -> Result<(), Error> { let effective_fee_bps = match crate::config::ConfigManager::get_config(env) { Ok(cfg) => cfg.fees.platform_fee_percentage, - Err(_) => { - env.storage() - .persistent() - .get::(&Symbol::new(env, "plat_fee")) - .unwrap_or(crate::config::DEFAULT_PLATFORM_FEE_PERCENTAGE) - } + Err(_) => env + .storage() + .persistent() + .get::(&Symbol::new(env, "plat_fee")) + .unwrap_or(crate::config::DEFAULT_PLATFORM_FEE_PERCENTAGE), }; if effective_fee_bps > max_fee_bps { @@ -1704,7 +1708,11 @@ impl BetValidator { // Store the cap in persistent storage let key = crate::storage::DataKey::MaxBetCap; env.storage().persistent().set(&key, &cap); - env.storage().persistent().extend_ttl(&key, crate::storage::MARKET_TTL_LEDGERS, crate::storage::MARKET_TTL_LEDGERS); + env.storage().persistent().extend_ttl( + &key, + crate::storage::MARKET_TTL_LEDGERS, + crate::storage::MARKET_TTL_LEDGERS, + ); // Emit event crate::events::EventEmitter::emit_max_bet_cap_set(env, cap); @@ -1725,10 +1733,7 @@ impl BetValidator { /// Returns the cumulative amount the user has bet on this market (0 if no prior bets). pub fn get_user_stake(env: &Env, market_id: &Symbol, user: &Address) -> i128 { let key = crate::storage::DataKey::UserStake(user.clone(), market_id.clone()); - env.storage() - .persistent() - .get::<_, i128>(&key) - .unwrap_or(0) + env.storage().persistent().get::<_, i128>(&key).unwrap_or(0) } /// Update the cumulative stake for a user on a specific market. @@ -1747,16 +1752,23 @@ impl BetValidator { /// # Returns /// /// Returns `Ok(())` on success. - pub fn update_user_stake(env: &Env, market_id: &Symbol, user: &Address, amount: i128) -> Result<(), Error> { + pub fn update_user_stake( + env: &Env, + market_id: &Symbol, + user: &Address, + amount: i128, + ) -> Result<(), Error> { let current_stake = Self::get_user_stake(env, market_id, user); - let new_stake = current_stake - .checked_add(amount) - .ok_or(Error::Overflow)?; + let new_stake = current_stake.checked_add(amount).ok_or(Error::Overflow)?; let key = crate::storage::DataKey::UserStake(user.clone(), market_id.clone()); env.storage().persistent().set(&key, &new_stake); // Extend TTL to match market (365 days) - env.storage().persistent().extend_ttl(&key, crate::storage::MARKET_TTL_LEDGERS, crate::storage::MARKET_TTL_LEDGERS); + env.storage().persistent().extend_ttl( + &key, + crate::storage::MARKET_TTL_LEDGERS, + crate::storage::MARKET_TTL_LEDGERS, + ); Ok(()) } @@ -1791,9 +1803,7 @@ impl BetValidator { // Check if a cap is set; if not, no validation needed (uncapped) if let Some(cap) = Self::get_max_bet_cap(env) { let current_stake = Self::get_user_stake(env, market_id, user); - let new_total = current_stake - .checked_add(amount) - .ok_or(Error::Overflow)?; + let new_total = current_stake.checked_add(amount).ok_or(Error::Overflow)?; if new_total > cap { return Err(Error::MaxBetCapExceeded); @@ -2069,7 +2079,11 @@ mod tests { let cases: [(Symbol, String, Error); 3] = [ // Entry #3 targets a market that does not exist. - (Symbol::new(&env, "batch_none"), yes.clone(), Error::MarketNotFound), + ( + Symbol::new(&env, "batch_none"), + yes.clone(), + Error::MarketNotFound, + ), // Entry #3 has an invalid outcome. (m3.clone(), bogus, Error::InvalidOutcome), // Entry #3 repeats entry #1's market. @@ -2083,8 +2097,7 @@ mod tests { (m2.clone(), yes.clone(), amount), (third_market, third_outcome, amount), ]; - let result = - BetManager::place_bets(&env, user.clone(), bets, 10_000, key.clone()); + let result = BetManager::place_bets(&env, user.clone(), bets, 10_000, key.clone()); assert_eq!(result.err(), Some(expected)); for m in [&m1, &m2, &m3] { @@ -2403,7 +2416,10 @@ mod tests { min_bet: MIN_BET_AMOUNT - 1, max_bet: MAX_BET_AMOUNT, }; - assert_eq!(set_global_bet_limits(&env, &bad), Err(Error::InsufficientStake)); + assert_eq!( + set_global_bet_limits(&env, &bad), + Err(Error::InsufficientStake) + ); } #[test] @@ -2615,7 +2631,13 @@ mod tests { let user2 = Address::generate(&env); let outcome = String::from_str(&env, "yes"); - let bet1 = Bet::new(&env, user1.clone(), market_id.clone(), outcome.clone(), 1_000_000); + let bet1 = Bet::new( + &env, + user1.clone(), + market_id.clone(), + outcome.clone(), + 1_000_000, + ); let bet2 = Bet::new(&env, user2.clone(), market_id.clone(), outcome, 2_000_000); BetStorage::store_bet(&env, &bet1).unwrap(); BetStorage::store_bet(&env, &bet2).unwrap(); @@ -2629,7 +2651,10 @@ mod tests { let env = Env::default(); let market = test_market(&env, 10_000); // bet_deadline defaults to 0 in test_market - assert_eq!(BetValidator::effective_bet_deadline(&market).unwrap(), 10_000); + assert_eq!( + BetValidator::effective_bet_deadline(&market).unwrap(), + 10_000 + ); } #[test] @@ -2637,7 +2662,10 @@ mod tests { let env = Env::default(); let mut market = test_market(&env, 10_000); market.bet_deadline = 8_000; - assert_eq!(BetValidator::effective_bet_deadline(&market).unwrap(), 8_000); + assert_eq!( + BetValidator::effective_bet_deadline(&market).unwrap(), + 8_000 + ); } #[test] @@ -2667,7 +2695,9 @@ mod tests { Err(Error::InsufficientStake) ); // Within per-event bounds - assert!(BetValidator::validate_bet_amount_against_limits(&env, &market_id, 10_000_000).is_ok()); + assert!( + BetValidator::validate_bet_amount_against_limits(&env, &market_id, 10_000_000).is_ok() + ); // Above per-event max assert_eq!( BetValidator::validate_bet_amount_against_limits(&env, &market_id, 30_000_000_000), @@ -2687,7 +2717,9 @@ mod tests { Err(Error::BetExceedsCap) ); // Within per-market cap - assert!(BetValidator::validate_bet_amount_against_limits(&env, &market_id, 2_000_000).is_ok()); + assert!( + BetValidator::validate_bet_amount_against_limits(&env, &market_id, 2_000_000).is_ok() + ); } // ===== Bet cancellation window =====