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//!    `BuyTicket` / `AdminRouteFees`; USDC out via `AdminDisburseFromTreasury` (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 ticket/fee inflows; debited on daily disbursement.
20    pub daily_jackpot: u64,
21    /// Weekly accrual bucket — credited all week; debited on weekly disbursement, then
22    /// swept to `stability_reserve` via `AdminFinalizeWeekly`.
23    pub weekly_jackpot: u64,
24    /// Buyback reserve (10% of ticket sales).
25    pub buyback: u64,
26    /// Close price from the most recently settled period — used as the open reference
27    /// for the next settlement. Zero until `Initialize` seeds it.
28    pub last_close_price: i64,
29    /// Pyth exponent matching `last_close_price`.
30    pub last_close_expo: i32,
31    pub _pad_close: [u8; 4],
32    /// `publish_time` from the Pyth feed at last settlement.
33    pub last_close_publish_time: i64,
34    /// Stability reserve — excess weekly accrual + 60% of routed trading fees (brief v2).
35    pub stability_reserve: u64,
36}
37
38impl Treasury {
39    pub fn pda() -> (Pubkey, u8) {
40        treasury_pda()
41    }
42
43    /// Body length before `stability_reserve` (deployed accounts pre-0.3.12).
44    pub const LEGACY_BODY_LEN: usize = 48;
45
46    /// Decode raw treasury account data (8-byte Steel discriminator + body).
47    ///
48    /// Legacy accounts are shorter; `stability_reserve` reads as 0 until the first
49    /// `AdminSetTreasuryPools` realloc + write.
50    pub fn decode_account_data(data: &[u8]) -> Result<Self, &'static str> {
51        const DISC: usize = 8;
52        let body = data.get(DISC..).ok_or("treasury account empty")?;
53        let full = std::mem::size_of::<Self>();
54        if body.len() >= full {
55            return Ok(*bytemuck::from_bytes(&body[..full]));
56        }
57        if body.len() >= Self::LEGACY_BODY_LEN {
58            #[repr(C)]
59            #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
60            struct Legacy {
61                daily_jackpot: u64,
62                weekly_jackpot: u64,
63                buyback: u64,
64                last_close_price: i64,
65                last_close_expo: i32,
66                _pad_close: [u8; 4],
67                last_close_publish_time: i64,
68            }
69            let leg: &Legacy = bytemuck::from_bytes(&body[..Self::LEGACY_BODY_LEN]);
70            return Ok(Self {
71                daily_jackpot: leg.daily_jackpot,
72                weekly_jackpot: leg.weekly_jackpot,
73                buyback: leg.buyback,
74                last_close_price: leg.last_close_price,
75                last_close_expo: leg.last_close_expo,
76                _pad_close: leg._pad_close,
77                last_close_publish_time: leg.last_close_publish_time,
78                stability_reserve: 0,
79            });
80        }
81        Err("treasury account too short")
82    }
83}
84
85account!(StreakAccount, Treasury);