1use std::num::NonZeroU8;
2
3use derive_more::{Display, From, Into};
4
5use crate::vault::wad::Wad;
6use crate::{
7 asset::{BorrowAsset, FungibleAsset},
8 supply::SupplyPosition,
9};
10pub use external::*;
11use near_sdk::{
12 env,
13 json_types::{U128, U64},
14 near, require, AccountId, Gas, Promise, PromiseOrValue,
15};
16pub use templar_curator_primitives::{
17 CapGroupId, CapGroupRecord, CapGroupUpdate, CapGroupUpdateKey,
18};
19pub use templar_vault_kernel::state::op_state::{
20 AllocatingState, IdleState, OpState, PayoutState, RefreshingState, TargetId, WithdrawingState,
21};
22pub use templar_vault_kernel::types::{ActualIdx, ExpectedIdx, TimestampNs};
23
24pub use event::{
25 AllocationPositionIssueKind, Event, PositionReportOutcome, QueueAction, QueueStatus, Reason,
26 UnbrickPhase, WithdrawProgressPhase, WithdrawalAccountingKind,
27};
28pub use params::*;
29
30pub mod errors;
31pub mod event;
32pub mod external;
33pub mod gas;
34pub mod lock;
35pub mod params;
36pub mod restrictions;
37pub mod wad;
38
39pub use errors::Error;
40pub use gas::*;
41pub use lock::Locker;
42pub use restrictions::*;
43
44pub mod prelude {
49 pub use super::event::{
50 AllocationPositionIssueKind, Event, PositionReportOutcome, QueueAction, QueueStatus,
51 Reason, UnbrickPhase, WithdrawProgressPhase, WithdrawalAccountingKind,
52 };
53 pub use super::external::*;
54 pub use super::gas::*;
55 pub use super::params::*;
56 pub use super::restrictions::*;
57 pub use super::wad::{
58 compute_fee_shares, compute_fee_shares_from_assets, mul_div_ceil, mul_div_floor,
59 mul_wad_floor, Wad, MAX_FEE_WAD, MAX_MANAGEMENT_FEE_WAD, MAX_PERFORMANCE_FEE_WAD,
60 };
61 pub use super::wad::{Number, WIDE};
62 pub use super::{
63 require_at_least, storage_bytes_for_account_id, ActualIdx, AllocationDelta, AllocationPlan,
64 AllocationWeights, CapGroupId, CapGroupRecord, CapGroupUpdate, CapGroupUpdateKey, Delta,
65 DepositMsg, Error, EscrowSettlement, ExpectedIdx, Fee, FeeAccrualAnchor, Fees,
66 IdleBalanceDelta, IdleResyncOutcome, Locker, MarketConfiguration, MarketId,
67 PendingWithdrawal, RealAssetsReport, ResyncIdleReport, TimestampNs, VaultConfiguration,
68 };
69}
70
71pub type AllocationWeights = Vec<(MarketId, U128)>;
72pub type AllocationPlan = Vec<(MarketId, u128)>;
73
74#[derive(Debug, Clone)]
76#[near(serializers = [borsh, json])]
77pub struct RealAssetsReport {
78 pub total_assets: U128,
79 pub per_market: Vec<(MarketId, U128)>,
80 pub refreshed_at: U64,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
86#[near(serializers = [borsh, json])]
87pub enum IdleResyncOutcome {
88 Ok,
90 BalanceReadFailed,
92 UnexpectedState,
94 Ignored,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
100#[near(serializers = [borsh, json])]
101pub struct ResyncIdleReport {
102 pub outcome: IdleResyncOutcome,
103 pub before_idle: U128,
105 pub actual_idle: U128,
107 pub after_idle: U128,
109 pub increased_by: U128,
111 pub decreased_by: U128,
113 pub fee_anchor_bump: U128,
115 pub resynced_at_ns: U64,
117}
118
119#[derive(
120 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, From, Into, Display,
121)]
122#[near(serializers = [borsh, json])]
123#[display("{_0}")]
124pub struct MarketId(pub u32);
125
126impl From<MarketId> for u64 {
127 fn from(value: MarketId) -> Self {
128 u64::from(value.0)
129 }
130}
131
132impl TryFrom<u64> for MarketId {
133 type Error = <u32 as TryFrom<u64>>::Error;
134
135 fn try_from(value: u64) -> Result<Self, Self::Error> {
136 u32::try_from(value).map(Self)
137 }
138}
139
140#[near(serializers = [json])]
143pub enum DepositMsg {
144 Supply,
146}
147
148#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))]
150#[derive(Clone, Default)]
151#[near]
152pub struct MarketConfiguration {
153 pub cap: U128,
155 pub enabled: bool,
157 pub removable_at: TimestampNs,
159 pub cap_group_id: Option<CapGroupId>,
161}
162
163#[derive(Clone, Debug, PartialEq, Eq)]
165#[near(serializers = [borsh, json])]
166pub struct Fee<T> {
167 pub fee: T,
168 pub recipient: AccountId,
169}
170
171#[derive(Clone, Debug, PartialEq, Eq)]
173#[near(serializers = [borsh, json])]
174pub struct Fees<T> {
175 pub performance: Fee<T>,
176 pub management: Fee<T>,
177 pub max_total_assets_growth_rate: Option<T>,
183}
184
185impl From<Fee<Wad>> for Fee<U128> {
186 fn from(value: Fee<Wad>) -> Self {
187 Self {
188 fee: U128(u128::from(value.fee)),
189 recipient: value.recipient,
190 }
191 }
192}
193
194impl From<Fees<Wad>> for Fees<U128> {
195 fn from(value: Fees<Wad>) -> Self {
196 Self {
197 performance: value.performance.into(),
198 management: value.management.into(),
199 max_total_assets_growth_rate: value
200 .max_total_assets_growth_rate
201 .map(|rate| U128(u128::from(rate))),
202 }
203 }
204}
205
206impl From<Fee<U128>> for Fee<Wad> {
207 fn from(value: Fee<U128>) -> Self {
208 Self {
209 fee: Wad::from(value.fee.0),
210 recipient: value.recipient,
211 }
212 }
213}
214
215impl From<Fees<U128>> for Fees<Wad> {
216 fn from(value: Fees<U128>) -> Self {
217 Self {
218 performance: value.performance.into(),
219 management: value.management.into(),
220 max_total_assets_growth_rate: value
221 .max_total_assets_growth_rate
222 .map(|rate| rate.0.into()),
223 }
224 }
225}
226
227#[derive(Clone)]
229#[near(serializers = [json, borsh])]
230pub struct VaultConfiguration {
231 pub owner: AccountId,
233 pub curator: AccountId,
235 pub sentinel: AccountId,
237 pub underlying_token: FungibleAsset<BorrowAsset>,
239 pub initial_timelock_ns: U64,
241 pub fees: Fees<Wad>,
243 pub skim_recipient: AccountId,
245 pub name: String,
247 pub symbol: String,
249 pub decimals: NonZeroU8,
251 pub restrictions: Option<Restrictions>,
253 pub refresh_cooldown_ns: Option<U64>,
255 pub idle_resync_cooldown_ns: Option<U64>,
257 pub withdrawal_cooldown_ns: Option<U64>,
259}
260
261#[derive(Debug, Clone)]
263#[near(serializers = [borsh, json])]
264pub struct Delta {
265 pub market: MarketId,
266 pub amount: U128,
267}
268
269impl Delta {
270 pub fn new<T: Into<U128>>(market: MarketId, amount: T) -> Self {
271 Delta {
272 market,
273 amount: amount.into(),
274 }
275 }
276 pub fn validate(&self) {
277 require!(self.amount.0 > 0, "Delta amount must be greater than zero");
278 }
279}
280
281#[derive(Debug, Clone)]
283#[near(serializers = [borsh, json])]
284pub enum AllocationDelta {
285 Supply(Delta),
286 Withdraw(Delta),
287}
288
289impl AsRef<Delta> for AllocationDelta {
290 fn as_ref(&self) -> &Delta {
291 match self {
292 AllocationDelta::Supply(d) | AllocationDelta::Withdraw(d) => d,
293 }
294 }
295}
296
297#[derive(Debug, Clone, Copy)]
299pub struct EscrowSettlement {
300 pub to_burn: u128,
301 pub refund: u128,
302}
303
304impl EscrowSettlement {
305 pub fn new(escrow_shares: u128, burn_shares: u128) -> Self {
306 let settlement = templar_vault_kernel::types::EscrowSettlement::from_escrow_and_burn(
307 escrow_shares,
308 burn_shares,
309 );
310 Self {
311 to_burn: settlement.to_burn,
312 refund: settlement.refund,
313 }
314 }
315}
316
317impl From<EscrowSettlement> for (u128, u128) {
318 fn from(tuple: EscrowSettlement) -> Self {
319 (tuple.to_burn, tuple.refund)
320 }
321}
322
323#[derive(Clone, Debug)]
325#[near(serializers = [borsh])]
326pub struct PendingWithdrawal {
327 pub owner: AccountId,
328 pub receiver: AccountId,
329 pub escrow_shares: u128,
330 pub expected_assets: u128,
331 pub requested_at: u64,
332}
333
334impl PendingWithdrawal {
335 #[must_use]
336 pub fn encoded_size() -> u64 {
337 storage_bytes_for_account_id()
338 + storage_bytes_for_account_id()
339 + 16 + 16 + 8 }
343}
344
345#[must_use]
347pub const fn storage_bytes_for_account_id() -> u64 {
348 4 + AccountId::MAX_LEN as u64
350}
351
352#[derive(Clone, Debug)]
354#[near(serializers = [borsh, json])]
355pub enum IdleBalanceDelta {
356 Increase(U128),
357 Decrease(U128),
358}
359
360impl IdleBalanceDelta {
361 pub fn apply(&self, balance: u128) -> u128 {
362 let new = match self {
363 IdleBalanceDelta::Increase(amount) => balance.saturating_add(amount.0),
364 IdleBalanceDelta::Decrease(amount) => balance.saturating_sub(amount.0),
365 };
366 Event::IdleBalanceUpdated {
367 prev: U128::from(balance),
368 delta: self.clone(),
369 }
370 .emit();
371 new
372 }
373}
374
375#[near(serializers = [borsh, json])]
377#[derive(Debug, Clone, Default)]
378pub struct FeeAccrualAnchor {
379 pub total_assets: U128,
380 pub timestamp_ns: U64,
381}
382
383#[cfg(test)]
384mod tests {
385 use super::{Fee, Fees, MarketId};
386 use crate::vault::wad::Wad;
387 use near_sdk::json_types::U128;
388 use near_sdk::AccountId;
389
390 #[test]
391 fn market_id_try_from_u64_accepts_u32_range() {
392 assert_eq!(
393 MarketId::try_from(u64::from(u32::MAX)),
394 Ok(MarketId(u32::MAX))
395 );
396 }
397
398 #[test]
399 fn market_id_try_from_u64_rejects_out_of_range() {
400 assert!(MarketId::try_from(u64::from(u32::MAX) + 1).is_err());
401 }
402
403 #[test]
404 fn fees_roundtrip_between_u128_and_wad_preserves_values() {
405 let fees_u128 = Fees {
406 performance: Fee {
407 fee: U128(10),
408 recipient: "perf.testnet"
409 .parse::<AccountId>()
410 .expect("valid account id"),
411 },
412 management: Fee {
413 fee: U128(20),
414 recipient: "mgmt.testnet"
415 .parse::<AccountId>()
416 .expect("valid account id"),
417 },
418 max_total_assets_growth_rate: Some(U128(30)),
419 };
420
421 let fees_wad: Fees<Wad> = fees_u128.clone().into();
422 assert_eq!(u128::from(fees_wad.performance.fee), 10);
423 assert_eq!(u128::from(fees_wad.management.fee), 20);
424 assert_eq!(
425 fees_wad
426 .max_total_assets_growth_rate
427 .map(u128::from)
428 .expect("max rate must be present"),
429 30
430 );
431
432 let roundtrip: Fees<U128> = fees_wad.into();
433 assert_eq!(roundtrip.performance.fee.0, 10);
434 assert_eq!(roundtrip.management.fee.0, 20);
435 assert_eq!(
436 roundtrip.max_total_assets_growth_rate.map(|v| v.0),
437 Some(30)
438 );
439 assert_eq!(roundtrip.performance.recipient.as_str(), "perf.testnet");
440 assert_eq!(roundtrip.management.recipient.as_str(), "mgmt.testnet");
441 }
442}