1use solana_program::{account_info::AccountInfo, program_error::ProgramError};
4
5use crate::error::StreakError;
6use crate::pyth::{load_fresh_price_hermes, outcome_from_open_close, Price};
7use crate::state::{Market, Treasury};
8
9pub fn oracle_outcome_market_vs_pyth(
14 market: &Market,
15 pyth_price_feed_info: &AccountInfo<'_>,
16 unix_timestamp: i64,
17) -> Result<Option<u8>, ProgramError> {
18 if !market.has_open_oracle_snapshot() {
19 return Err(StreakError::MarketNotOracleAnchored.into());
20 }
21
22 let feed_id: [u8; 32] = market.pyth_price_feed.to_bytes();
23 let open_px = Price {
24 price: market.open_ref_price,
25 conf: 0,
26 expo: market.open_ref_expo,
27 publish_time: market.open_ref_publish_time,
28 };
29 let close_px = load_fresh_price_hermes(pyth_price_feed_info, &feed_id, unix_timestamp)?;
30 outcome_from_open_close(open_px, close_px)
31}
32
33pub fn apply_settlement_outcome(
36 market: &mut Market,
37 treasury: &mut Treasury,
38 period: u64,
39 outcome: u8,
40) -> Result<(), ProgramError> {
41 if market.period != period {
42 return Err(StreakError::BadMarketState.into());
43 }
44 if market.status == Market::STATUS_VOIDED {
45 return Err(StreakError::MarketVoided.into());
46 }
47 if market.status != Market::STATUS_OPEN {
48 return Err(StreakError::BadMarketState.into());
49 }
50 market.total_up = market
54 .total_up
55 .checked_add(market.committed_up)
56 .ok_or(StreakError::Overflow)?;
57 market.total_down = market
58 .total_down
59 .checked_add(market.committed_down)
60 .ok_or(StreakError::Overflow)?;
61 market.committed_up = 0;
62 market.committed_down = 0;
63
64 let winner_total = if outcome == Market::SIDE_UP {
65 market.total_up
66 } else {
67 market.total_down
68 };
69 let loser_pot = if outcome == Market::SIDE_UP {
70 market.total_down
71 } else {
72 market.total_up
73 };
74
75 let reward_pool = if winner_total == 0 {
76 0u64
77 } else {
78 treasury.daily_jackpot.min(loser_pot)
79 };
80
81 if reward_pool > 0 {
82 treasury.daily_jackpot = treasury
83 .daily_jackpot
84 .checked_sub(reward_pool)
85 .ok_or(StreakError::InsufficientTreasury)?;
86 }
87
88 market.loser_pot = loser_pot;
89 market.reward_pool = reward_pool;
90 market.winning_total = winner_total;
91 market.outcome = outcome;
92 market.status = Market::STATUS_SETTLED;
93
94 if winner_total == 0 {
97 treasury.bump_next_period_floor(market.series_id, period)?;
98 }
99
100 Ok(())
101}