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