use solana_sdk::pubkey::Pubkey;
use strum::EnumString;
use super::VaultChainError;
use crate::chain::ix::{TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID};
use crate::types::VaultLeg;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString)]
#[strum(serialize_all = "snake_case")]
#[non_exhaustive]
pub enum VaultPhase {
Idle,
Active,
Settling,
ProcessingCapital,
}
impl TryFrom<u8> for VaultPhase {
type Error = VaultChainError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Idle),
1 => Ok(Self::Active),
2 => Ok(Self::Settling),
3 => Ok(Self::ProcessingCapital),
other => Err(VaultChainError::UnknownPhase(other)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString)]
#[strum(serialize_all = "snake_case")]
#[non_exhaustive]
pub enum VaultSafety {
Normal,
Frozen,
EmergencyUnwind,
WithdrawOnly,
Closing,
}
impl TryFrom<u8> for VaultSafety {
type Error = VaultChainError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Normal),
1 => Ok(Self::Frozen),
2 => Ok(Self::EmergencyUnwind),
3 => Ok(Self::WithdrawOnly),
4 => Ok(Self::Closing),
other => Err(VaultChainError::UnknownSafety(other)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct VaultAccount {
pub vault_id: [u8; 32],
pub phase: VaultPhase,
pub safety: VaultSafety,
pub equity_computed: bool,
pub accepting_deposits: bool,
pub governance_authority: Pubkey,
pub primary_delegate: Pubkey,
pub pending_governance_authority: Pubkey,
pub fee_recipient: Pubkey,
pub main_mint: Pubkey,
pub second_mint: Pubkey,
pub share_mint: Pubkey,
pub main_token_program: Pubkey,
pub second_token_program: Pubkey,
pub active_expiry: u64,
pub open_positions: u64,
pub total_shares: u64,
pub pending_deposit_count: u32,
pub pending_withdraw_count: u32,
pub capital_fallback_delay_secs: u64,
pub closing_started_at: u64,
pub boundary_equity: u64,
pub boundary_shares: u64,
pub staging: VaultStagingState,
pub second_leg_generation: u64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct VaultStagingState {
pub generation: u64,
pub open: Option<VaultStagedSlot>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VaultStagedSlot {
pub mint: Pubkey,
pub from_leg: VaultLeg,
pub amount: u64,
pub staged_at: u64,
}
const VAULT_DISCRIMINATOR: u8 = 9;
const VAULT_VERSION: u8 = 1;
const REQUEST_VERSION: u8 = 1;
const EQUITY_COMPUTED: u32 = 1 << 0;
const ACCEPTING_DEPOSITS: u32 = 1 << 1;
impl VaultAccount {
pub const LEN: usize = 712;
pub fn from_account(
account: &solana_sdk::account::Account,
program_id: &Pubkey,
) -> Result<Self, VaultChainError> {
if account.owner != *program_id {
return Err(VaultChainError::Owner {
expected: *program_id,
actual: account.owner,
});
}
Self::parse(&account.data)
}
pub fn parse(data: &[u8]) -> Result<Self, VaultChainError> {
if data.len() != Self::LEN {
return Err(VaultChainError::AccountLength {
expected: Self::LEN,
actual: data.len(),
});
}
let data = header(data, Self::LEN, VAULT_DISCRIMINATOR, VAULT_VERSION)?;
let flags = u32_at(data, 12);
if flags & !(EQUITY_COMPUTED | ACCEPTING_DEPOSITS) != 0 {
return Err(VaultChainError::UnknownAccountingFlags(flags));
}
if data[6] != 0 {
return Err(VaultChainError::UnknownStagedKind(data[6]));
}
let open = match u64_at(data, 488) {
0 => None,
amount => Some(VaultStagedSlot {
mint: pubkey(data, 456),
from_leg: match data[5] {
0 => VaultLeg::Main,
1 => VaultLeg::Second,
other => return Err(VaultChainError::UnknownStagedLeg(other)),
},
amount,
staged_at: u64_at(data, 496),
}),
};
Ok(Self {
vault_id: bytes32(data, 16),
phase: VaultPhase::try_from(data[3])?,
safety: VaultSafety::try_from(data[4])?,
equity_computed: flags & EQUITY_COMPUTED != 0,
accepting_deposits: flags & ACCEPTING_DEPOSITS != 0,
governance_authority: pubkey(data, 48),
primary_delegate: pubkey(data, 144),
pending_governance_authority: pubkey(data, 80),
fee_recipient: pubkey(data, 112),
main_mint: pubkey(data, 176),
second_mint: pubkey(data, 208),
share_mint: pubkey(data, 240),
main_token_program: token_program(data[402])?,
second_token_program: token_program(data[403])?,
active_expiry: u64_at(data, 272),
open_positions: u64_at(data, 280),
total_shares: u64_at(data, 288),
pending_deposit_count: u32_at(data, 320),
pending_withdraw_count: u32_at(data, 324),
capital_fallback_delay_secs: u64_at(data, 408),
closing_started_at: u64_at(data, 432),
boundary_equity: u64_at(data, 440),
boundary_shares: u64_at(data, 448),
staging: VaultStagingState {
generation: u64_at(data, 504),
open,
},
second_leg_generation: u64_at(data, 512),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VaultRequestKind {
Deposit,
Withdraw,
}
impl VaultRequestKind {
#[must_use]
pub const fn discriminator(self) -> u8 {
match self {
Self::Deposit => 12,
Self::Withdraw => 13,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VaultRequest {
pub user: Pubkey,
pub vault: Pubkey,
pub amount: u64,
pub created_at: u64,
}
impl VaultRequest {
pub const LEN: usize = 344;
pub const VAULT_OFFSET: usize = 40;
pub fn from_account(
account: &solana_sdk::account::Account,
program_id: &Pubkey,
kind: VaultRequestKind,
) -> Result<Self, VaultChainError> {
if account.owner != *program_id {
return Err(VaultChainError::Owner {
expected: *program_id,
actual: account.owner,
});
}
if account.data.len() != Self::LEN {
return Err(VaultChainError::AccountLength {
expected: Self::LEN,
actual: account.data.len(),
});
}
Self::parse(&account.data, kind)
}
pub fn parse(data: &[u8], kind: VaultRequestKind) -> Result<Self, VaultChainError> {
let data = header(data, Self::LEN, kind.discriminator(), REQUEST_VERSION)?;
Ok(Self {
user: pubkey(data, 8),
vault: pubkey(data, Self::VAULT_OFFSET),
amount: u64_at(data, 72),
created_at: u64_at(data, 80),
})
}
}
pub(super) fn header(
data: &[u8],
len: usize,
discriminator: u8,
version: u8,
) -> Result<&[u8], VaultChainError> {
let Some(data) = data.get(..len) else {
return Err(VaultChainError::AccountTooShort {
expected: len,
actual: data.len(),
});
};
if data[0] != discriminator {
return Err(VaultChainError::Discriminator {
expected: discriminator,
actual: data[0],
});
}
if data[1] != version {
return Err(VaultChainError::Version(data[1]));
}
Ok(data)
}
fn token_program(kind: u8) -> Result<Pubkey, VaultChainError> {
match kind {
0 => Ok(TOKEN_PROGRAM_ID),
1 => Ok(TOKEN_2022_PROGRAM_ID),
other => Err(VaultChainError::UnknownTokenProgramKind(other)),
}
}
pub(super) fn bytes32(data: &[u8], offset: usize) -> [u8; 32] {
let mut out = [0; 32];
out.copy_from_slice(&data[offset..offset + 32]);
out
}
pub(super) fn pubkey(data: &[u8], offset: usize) -> Pubkey {
Pubkey::new_from_array(bytes32(data, offset))
}
fn u32_at(data: &[u8], offset: usize) -> u32 {
let mut out = [0; 4];
out.copy_from_slice(&data[offset..offset + 4]);
u32::from_le_bytes(out)
}
fn u64_at(data: &[u8], offset: usize) -> u64 {
let mut out = [0; 8];
out.copy_from_slice(&data[offset..offset + 8]);
u64::from_le_bytes(out)
}