Skip to main content

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);