templar_vault_kernel/state/vault/
mod.rs1use crate::fee::FeesSpec;
8use crate::state::op_state::OpState;
9use crate::state::queue::WithdrawQueue;
10use crate::types::TimestampNs;
11
12pub const MAX_PENDING: usize = 1024;
15
16#[templar_vault_macros::vault_derive(borsh, serde, postcard)]
22#[derive(Clone, Copy, PartialEq, Eq)]
23pub struct FeeAccrualAnchor {
24 pub total_assets: u128,
25 pub timestamp_ns: TimestampNs,
26}
27
28impl FeeAccrualAnchor {
29 #[inline]
30 #[must_use]
31 pub const fn new(total_assets: u128, timestamp_ns: TimestampNs) -> Self {
32 Self {
33 total_assets,
34 timestamp_ns,
35 }
36 }
37
38 #[inline]
39 #[must_use]
40 pub const fn zero() -> Self {
41 Self {
42 total_assets: 0,
43 timestamp_ns: TimestampNs::ZERO,
44 }
45 }
46
47 #[inline]
48 pub fn update(&mut self, total_assets: u128, timestamp_ns: TimestampNs) {
49 self.total_assets = total_assets;
50 self.timestamp_ns = timestamp_ns;
51 }
52}
53
54impl Default for FeeAccrualAnchor {
55 fn default() -> Self {
56 Self::zero()
57 }
58}
59
60#[templar_vault_macros::vault_derive(borsh, serde, postcard)]
70#[derive(Clone, Copy, PartialEq, Eq)]
71pub struct VaultConfig {
72 pub fees: FeesSpec,
73 pub min_withdrawal_assets: u128,
74 pub withdrawal_cooldown_ns: u64,
75 pub max_pending_withdrawals: u32,
76 pub paused: bool,
77 pub virtual_shares: u128,
78 pub virtual_assets: u128,
79}
80
81impl VaultConfig {
82 #[inline]
83 #[must_use]
84 pub fn is_max_pending_valid(&self) -> bool {
85 (self.max_pending_withdrawals as usize) <= MAX_PENDING
86 }
87}
88
89#[templar_vault_macros::vault_derive(borsh, serde, postcard)]
103#[derive(Clone, PartialEq, Eq)]
104pub struct VaultState {
105 pub total_assets: u128,
106 pub total_shares: u128,
107 pub idle_assets: u128,
108 pub external_assets: u128,
109 pub fee_anchor: FeeAccrualAnchor,
110 pub op_state: OpState,
111 pub withdraw_queue: WithdrawQueue,
112 pub next_op_id: u64,
113}
114
115impl VaultState {
116 #[inline]
117 #[must_use]
118 pub fn new() -> Self {
119 Self {
120 total_assets: 0,
121 total_shares: 0,
122 idle_assets: 0,
123 external_assets: 0,
124 fee_anchor: FeeAccrualAnchor::zero(),
125 op_state: OpState::Idle,
126 withdraw_queue: WithdrawQueue::new(),
127 next_op_id: 0,
128 }
129 }
130
131 #[inline]
132 #[must_use]
133 pub fn with_initial(
134 total_assets: u128,
135 total_shares: u128,
136 idle_assets: u128,
137 external_assets: u128,
138 timestamp_ns: TimestampNs,
139 ) -> Self {
140 let computed_total = idle_assets
141 .checked_add(external_assets)
142 .expect("total_assets invariant overflow: idle + external");
143 assert!(total_assets == computed_total);
144 Self {
145 total_assets,
146 total_shares,
147 idle_assets,
148 external_assets,
149 fee_anchor: FeeAccrualAnchor::new(total_assets, timestamp_ns),
150 op_state: OpState::Idle,
151 withdraw_queue: WithdrawQueue::new(),
152 next_op_id: 0,
153 }
154 }
155
156 #[inline]
160 #[must_use]
161 pub fn check_invariant(&self) -> bool {
162 self.idle_assets
163 .checked_add(self.external_assets)
164 .is_some_and(|sum| self.total_assets == sum)
165 && self.withdraw_queue.check_invariants()
166 }
167
168 #[inline]
172 pub fn allocate_op_id(&mut self) -> u64 {
173 let id = self.next_op_id;
174 self.next_op_id = self.next_op_id.checked_add(1).expect("op_id overflow");
175 id
176 }
177
178 #[inline]
180 #[must_use]
181 pub fn is_idle(&self) -> bool {
182 self.op_state.is_idle()
183 }
184
185 #[inline]
187 #[must_use]
188 pub fn current_op_id(&self) -> Option<u64> {
189 self.op_state.op_id()
190 }
191
192 #[inline]
197 pub fn sync_total_assets(&mut self) {
198 self.total_assets = self
199 .idle_assets
200 .checked_add(self.external_assets)
201 .expect("total_assets overflow: idle + external");
202 }
203
204 #[inline]
209 pub fn restore_to_idle(&mut self, amount: u128) {
210 self.idle_assets = self.idle_assets.checked_add(amount).unwrap();
211 self.sync_total_assets();
212 }
213}
214
215impl Default for VaultState {
216 fn default() -> Self {
217 Self::new()
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::VaultState;
224
225 #[test]
226 #[should_panic(expected = "total_assets invariant overflow: idle + external")]
227 fn with_initial_panics_on_overflowed_component_sum() {
228 let _ = VaultState::with_initial(u128::MAX, 0, u128::MAX, 1, crate::TimestampNs(0));
229 }
230
231 #[test]
232 #[should_panic(expected = "op_id overflow")]
233 fn allocate_op_id_panics_on_overflow() {
234 let mut state = VaultState::new();
235 state.next_op_id = u64::MAX;
236
237 let _ = state.allocate_op_id();
238 }
239
240 #[test]
241 #[should_panic(expected = "total_assets overflow: idle + external")]
242 fn sync_total_assets_panics_on_overflow() {
243 let mut state = VaultState::new();
244 state.idle_assets = u128::MAX;
245 state.external_assets = 1;
246
247 state.sync_total_assets();
248 }
249}