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)]
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 #[must_use]
49 pub const fn is_uninitialized(self) -> bool {
50 self.total_assets == 0 && self.timestamp_ns.as_u64() == 0
51 }
52
53 #[inline]
54 pub fn update(&mut self, total_assets: u128, timestamp_ns: TimestampNs) {
55 self.total_assets = total_assets;
56 self.timestamp_ns = timestamp_ns;
57 }
58}
59
60impl Default for FeeAccrualAnchor {
61 fn default() -> Self {
62 Self::zero()
63 }
64}
65
66#[templar_vault_macros::vault_derive(borsh, serde)]
76#[derive(Clone, Copy, PartialEq, Eq)]
77pub struct VaultConfig {
78 pub fees: FeesSpec,
79 pub min_withdrawal_assets: u128,
80 pub withdrawal_cooldown_ns: u64,
81 pub max_pending_withdrawals: u32,
82 pub paused: bool,
83 pub virtual_shares: u128,
84 pub virtual_assets: u128,
85}
86
87impl VaultConfig {
88 #[inline]
89 #[must_use]
90 pub fn is_max_pending_valid(&self) -> bool {
91 (self.max_pending_withdrawals as usize) <= MAX_PENDING
92 }
93}
94
95#[templar_vault_macros::vault_derive(borsh, serde)]
109#[derive(Clone, PartialEq, Eq)]
110pub struct VaultState {
111 pub total_assets: u128,
112 pub total_shares: u128,
113 pub idle_assets: u128,
114 pub external_assets: u128,
115 pub fee_anchor: FeeAccrualAnchor,
116 pub op_state: OpState,
117 pub withdraw_queue: WithdrawQueue,
118 pub next_op_id: u64,
119}
120
121impl VaultState {
122 #[inline]
123 #[must_use]
124 pub fn new() -> Self {
125 Self {
126 total_assets: 0,
127 total_shares: 0,
128 idle_assets: 0,
129 external_assets: 0,
130 fee_anchor: FeeAccrualAnchor::zero(),
131 op_state: OpState::Idle,
132 withdraw_queue: WithdrawQueue::new(),
133 next_op_id: 0,
134 }
135 }
136
137 #[inline]
138 #[must_use]
139 pub fn with_initial(
140 total_assets: u128,
141 total_shares: u128,
142 idle_assets: u128,
143 external_assets: u128,
144 timestamp_ns: TimestampNs,
145 ) -> Self {
146 let computed_total = crate::unwrap_abort!(
147 idle_assets.checked_add(external_assets),
148 crate::abort::OVERFLOW,
149 );
150 assert!(total_assets == computed_total);
151 Self {
152 total_assets,
153 total_shares,
154 idle_assets,
155 external_assets,
156 fee_anchor: FeeAccrualAnchor::new(total_assets, timestamp_ns),
157 op_state: OpState::Idle,
158 withdraw_queue: WithdrawQueue::new(),
159 next_op_id: 0,
160 }
161 }
162
163 #[inline]
167 #[must_use]
168 pub fn check_invariant(&self) -> bool {
169 self.idle_assets
170 .checked_add(self.external_assets)
171 .is_some_and(|sum| self.total_assets == sum)
172 && self.withdraw_queue.check_invariants()
173 }
174
175 #[inline]
179 pub fn allocate_op_id(&mut self) -> u64 {
180 let id = self.next_op_id;
181 self.next_op_id =
182 crate::unwrap_abort!(self.next_op_id.checked_add(1), crate::abort::OVERFLOW,);
183 id
184 }
185
186 #[inline]
188 #[must_use]
189 pub fn is_idle(&self) -> bool {
190 self.op_state.is_idle()
191 }
192
193 #[inline]
195 #[must_use]
196 pub fn current_op_id(&self) -> Option<u64> {
197 self.op_state.op_id()
198 }
199
200 #[inline]
205 pub fn sync_total_assets(&mut self) {
206 self.total_assets = crate::unwrap_abort!(
207 self.idle_assets.checked_add(self.external_assets),
208 crate::abort::OVERFLOW,
209 );
210 }
211
212 #[inline]
217 pub fn restore_to_idle(&mut self, amount: u128) {
218 self.idle_assets =
219 crate::unwrap_abort!(self.idle_assets.checked_add(amount), crate::abort::OVERFLOW,);
220 self.sync_total_assets();
221 }
222}
223
224impl Default for VaultState {
225 fn default() -> Self {
226 Self::new()
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::VaultState;
233
234 #[test]
235 #[should_panic]
236 fn with_initial_panics_on_overflowed_component_sum() {
237 let _ = VaultState::with_initial(u128::MAX, 0, u128::MAX, 1, crate::TimestampNs(0));
238 }
239
240 #[test]
241 #[should_panic]
242 fn allocate_op_id_panics_on_overflow() {
243 let mut state = VaultState::new();
244 state.next_op_id = u64::MAX;
245
246 let _ = state.allocate_op_id();
247 }
248
249 #[test]
250 #[should_panic]
251 fn sync_total_assets_panics_on_overflow() {
252 let mut state = VaultState::new();
253 state.idle_assets = u128::MAX;
254 state.external_assets = 1;
255
256 state.sync_total_assets();
257 }
258}