streak_api/state/treasury.rs
1//! Protocol USDC custody (`treasury` PDA).
2//!
3//! The treasury serves two roles:
4//!
5//! 1. **USDC custodian** — the treasury ATA holds all deposited USDC. Deposits come in via
6//! `Deposit`; payouts go out via `AdminPayout` (executor-signed).
7//!
8//! 2. **Price chain** — `last_close_*` carries the Pyth close price from the most recently
9//! settled period. `AdminInstantSettlement` reads it as the open reference and writes the
10//! new close price back, forming a self-perpetuating chain. Zero until `Initialize` seeds it.
11
12use steel::*;
13
14use super::{treasury_pda, StreakAccount};
15
16#[repr(C)]
17#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
18pub struct Treasury {
19 /// Daily jackpot pool — credited on `Deposit`; debited via `AdminPayout`.
20 pub daily_jackpot: u64,
21 /// Weekly jackpot pool — credited on `Deposit`; debited via `AdminPayout`.
22 pub weekly_jackpot: u64,
23 /// Buyback reserve (10% of ticket sales).
24 pub buyback: u64,
25 /// Close price from the most recently settled period — used as the open reference
26 /// for the next settlement. Zero until `Initialize` seeds it.
27 pub last_close_price: i64,
28 /// Pyth exponent matching `last_close_price`.
29 pub last_close_expo: i32,
30 pub _pad_close: [u8; 4],
31 /// `publish_time` from the Pyth feed at last settlement.
32 pub last_close_publish_time: i64,
33 /// Stability reserve — excess weekly accrual + 60% of routed trading fees (brief v2).
34 pub stability_reserve: u64,
35}
36
37impl Treasury {
38 pub fn pda() -> (Pubkey, u8) {
39 treasury_pda()
40 }
41
42 /// Body length before `stability_reserve` (deployed accounts pre-0.3.12).
43 pub const LEGACY_BODY_LEN: usize = 48;
44
45 /// Decode raw treasury account data (8-byte Steel discriminator + body).
46 ///
47 /// Legacy accounts are shorter; `stability_reserve` reads as 0 until the first
48 /// `AdminSetTreasuryPools` realloc + write.
49 pub fn decode_account_data(data: &[u8]) -> Result<Self, &'static str> {
50 const DISC: usize = 8;
51 let body = data.get(DISC..).ok_or("treasury account empty")?;
52 let full = std::mem::size_of::<Self>();
53 if body.len() >= full {
54 return Ok(*bytemuck::from_bytes(&body[..full]));
55 }
56 if body.len() >= Self::LEGACY_BODY_LEN {
57 #[repr(C)]
58 #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
59 struct Legacy {
60 daily_jackpot: u64,
61 weekly_jackpot: u64,
62 buyback: u64,
63 last_close_price: i64,
64 last_close_expo: i32,
65 _pad_close: [u8; 4],
66 last_close_publish_time: i64,
67 }
68 let leg: &Legacy = bytemuck::from_bytes(&body[..Self::LEGACY_BODY_LEN]);
69 return Ok(Self {
70 daily_jackpot: leg.daily_jackpot,
71 weekly_jackpot: leg.weekly_jackpot,
72 buyback: leg.buyback,
73 last_close_price: leg.last_close_price,
74 last_close_expo: leg.last_close_expo,
75 _pad_close: leg._pad_close,
76 last_close_publish_time: leg.last_close_publish_time,
77 stability_reserve: 0,
78 });
79 }
80 Err("treasury account too short")
81 }
82}
83
84account!(StreakAccount, Treasury);