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
22 changes: 19 additions & 3 deletions prediction_market/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ impl PredictionMarketContract {
pub fn cancel_refund(env: Env, user: Address, market_id: u64) -> Result<i128, MarketError> {
user.require_auth();

let market = Self::load_market(&env, market_id)?;
let mut market = Self::load_market(&env, market_id)?;
if !market.cancelled {
return Err(MarketError::MarketNotCancelled);
}
Expand All @@ -660,8 +660,24 @@ impl PredictionMarketContract {
}

let gross = entry.gross;
entry.gross = 0; // idempotency guard
let net = entry.net;
// Issue #58: zero both gross (idempotency guard) and net so that
// get_bet no longer reports a staked amount after the refund.
entry.gross = 0;
entry.net = 0;
env.storage().persistent().set(&bet_key, &entry);

// Issue #58: decrement market totals so total_yes/total_no reflect
// that this bet has been refunded. BetEntry already carries is_yes
// and net, so no storage-model change is required.
let mkt_key = DataKey::Market(market_id);
if entry.is_yes {
market.total_yes = market.total_yes.saturating_sub(net);
} else {
market.total_no = market.total_no.saturating_sub(net);
}
env.storage().persistent().set(&mkt_key, &market);

// Read-time TTL refresh (issue #9): a refund must not be able to observe
// an expired bet/market record — keep both alive so a user who returns
// late to a cancelled market can still pull their refund.
Expand All @@ -670,7 +686,7 @@ impl PredictionMarketContract {
.extend_ttl(&bet_key, TTL_BUMP, TTL_HIGH);
env.storage()
.persistent()
.extend_ttl(&DataKey::Market(market_id), TTL_BUMP, TTL_HIGH);
.extend_ttl(&mkt_key, TTL_BUMP, TTL_HIGH);

let cfg: Config = env.storage().instance().get(&DataKey::Cfg).unwrap();
token::Client::new(&env, &cfg.xlm_sac).transfer(
Expand Down
46 changes: 46 additions & 0 deletions prediction_market/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,52 @@ fn test_cancel_refund_double_claim_rejected() {
t.client.cancel_refund(&user, &id); // should fail: NoBetFound (gross zeroed)
}

// ── Issue #58: cancel_refund zeroes net and decrements market totals ─────────

#[test]
fn test_cancel_refund_clears_net_and_market_totals() {
let t = setup();
let id = create_test_market(&t);
let alice = Address::generate(&t.env);
let bob = Address::generate(&t.env);
fund_user(&t, &alice, 200_0000000);
fund_user(&t, &bob, 200_0000000);

// Alice bets YES 100 XLM → net = 100 * 9800/10000 = 98 XLM
// Bob bets NO 50 XLM → net = 50 * 9800/10000 = 49 XLM
t.client.place_bet(&alice, &id, &true, &100_0000000_i128);
t.client.place_bet(&bob, &id, &false, &50_0000000_i128);

let market_before = t.client.get_market(&id);
assert_eq!(market_before.total_yes, 98_0000000);
assert_eq!(market_before.total_no, 49_0000000);

// Confirm get_bet reports staked net before cancel
let alice_bet_before = t.client.get_bet(&id, &alice);
assert_eq!(alice_bet_before.amount, 98_0000000);

// Cancel the market then refund both bettors
t.client.cancel_market(&t.admin, &id);
t.client.cancel_refund(&alice, &id);
t.client.cancel_refund(&bob, &id);

// Issue #58 fix: get_bet must return amount == 0 after refund
let alice_bet_after = t.client.get_bet(&id, &alice);
assert_eq!(alice_bet_after.amount, 0, "net should be zeroed after cancel_refund");

let bob_bet_after = t.client.get_bet(&id, &bob);
assert_eq!(bob_bet_after.amount, 0, "net should be zeroed after cancel_refund");

// Issue #58 fix: market totals must be decremented to 0 after all refunds
let market_after = t.client.get_market(&id);
assert_eq!(market_after.total_yes, 0, "total_yes should be 0 after alice's refund");
assert_eq!(market_after.total_no, 0, "total_no should be 0 after bob's refund");

// Gross is still zeroed (idempotency guard unchanged)
assert_eq!(t.client.get_bet_gross(&id, &alice), 0);
assert_eq!(t.client.get_bet_gross(&id, &bob), 0);
}

// ── 19. cancel_refund on non-cancelled market rejected ────────────────────────

#[test]
Expand Down