templar_vault_kernel/math/
wad.rs1use core::ops::Div;
6
7use derive_more::{From, Into};
8use primitive_types::U256;
9
10use super::number::Number;
11
12pub const MAX_MANAGEMENT_FEE_WAD: u128 = Wad::SCALE / 100 * 5;
14
15pub const MAX_PERFORMANCE_FEE_WAD: u128 = Wad::SCALE / 100 * 50;
17
18pub const MAX_FEE_WAD: u128 = MAX_PERFORMANCE_FEE_WAD;
20
21#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))]
26#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, From, Into)]
27pub struct Wad(pub Number);
28
29#[cfg(all(feature = "serde", not(feature = "postcard")))]
30mod serde_impl {
31 use super::*;
32 use serde::{Deserialize, Deserializer, Serialize, Serializer};
33
34 impl Serialize for Wad {
35 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
36 where
37 S: Serializer,
38 {
39 Serialize::serialize(&self.0, serializer)
41 }
42 }
43
44 impl<'de> Deserialize<'de> for Wad {
45 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
46 where
47 D: Deserializer<'de>,
48 {
49 <Number as Deserialize>::deserialize(deserializer).map(Wad)
50 }
51 }
52}
53
54#[cfg(feature = "postcard")]
55mod postcard_serde_impl {
56 use super::*;
57 use serde::{Deserialize, Deserializer, Serialize, Serializer};
58
59 impl Serialize for Wad {
60 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
61 where
62 S: Serializer,
63 {
64 #[cfg(feature = "soroban")]
65 {
66 self.0.serialize(serializer)
67 }
68
69 #[cfg(not(feature = "soroban"))]
70 {
71 Serialize::serialize(&self.0, serializer)
72 }
73 }
74 }
75
76 impl<'de> Deserialize<'de> for Wad {
77 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
78 where
79 D: Deserializer<'de>,
80 {
81 #[cfg(feature = "soroban")]
82 {
83 u128::deserialize(deserializer).map(|value| Wad(Number::from(value)))
84 }
85
86 #[cfg(not(feature = "soroban"))]
87 {
88 <Number as Deserialize>::deserialize(deserializer).map(Wad)
89 }
90 }
91 }
92}
93
94#[cfg(feature = "borsh")]
95mod borsh_impl {
96 use super::*;
97 use borsh::{self, BorshDeserialize, BorshSerialize};
98
99 impl BorshSerialize for Wad {
100 fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
101 BorshSerialize::serialize(&self.0, writer)
102 }
103 }
104
105 impl BorshDeserialize for Wad {
106 fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
107 <Number as BorshDeserialize>::deserialize_reader(reader).map(Wad)
108 }
109 }
110}
111
112#[cfg(feature = "borsh-schema")]
113mod borsh_schema_impl {
114 use super::*;
115 use alloc::collections::BTreeMap;
116 use borsh::schema::{add_definition, Declaration, Definition};
117 use borsh::BorshSchema;
118
119 impl BorshSchema for Wad {
120 fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
121 let definition = Definition::Primitive(32);
122 add_definition(Self::declaration(), definition, definitions);
123 }
124
125 fn declaration() -> Declaration {
126 "Wad".into()
127 }
128 }
129}
130
131#[cfg(feature = "schemars")]
132mod schemars_impl {
133 use super::*;
134 use alloc::string::ToString;
135 use schemars::r#gen::SchemaGenerator;
136 use schemars::schema::Schema;
137 use schemars::JsonSchema;
138
139 impl JsonSchema for Wad {
140 fn schema_name() -> alloc::string::String {
141 "Wad".to_string()
142 }
143
144 fn json_schema(generator: &mut SchemaGenerator) -> Schema {
145 let mut schema = generator.subschema_for::<Number>().into_object();
146 schema.metadata().description =
147 Some("Wad fixed fraction backed by 256-bit unsigned integer".to_string());
148 schema.string().pattern = Some("^(0|[1-9][0-9]{0,77})$".to_string());
149 schema.into()
150 }
151 }
152}
153
154impl Wad {
155 pub const SCALE: u128 = 1_000_000_000_000_000_000u128;
157
158 pub const ZERO: Self = Wad(Number::ZERO);
159 pub const ONE: Self = Wad(Number(U256([Self::SCALE as u64, 0, 0, 0])));
160
161 #[inline]
163 #[must_use]
164 pub const fn zero() -> Self {
165 Self::ZERO
166 }
167
168 #[inline]
170 #[must_use]
171 pub const fn one() -> Self {
172 Self::ONE
173 }
174
175 #[inline]
176 #[must_use]
177 pub fn is_zero(&self) -> bool {
178 self.0.is_zero()
179 }
180
181 #[inline]
182 #[must_use]
183 pub fn is_one(&self) -> bool {
184 self.0 .0 == U256::from(Self::SCALE)
185 }
186
187 #[inline]
189 #[must_use]
190 pub fn as_u128_trunc(self) -> u128 {
191 self.0.as_u128_trunc()
192 }
193
194 #[inline]
196 #[must_use]
197 pub fn apply_floored(self, amount: Number) -> Number {
198 mul_wad_floor(amount, self)
199 }
200}
201
202impl From<u128> for Wad {
203 #[inline]
204 fn from(v: u128) -> Self {
205 Wad(Number::from(v))
206 }
207}
208
209impl From<Wad> for u128 {
210 #[inline]
211 fn from(w: Wad) -> u128 {
212 w.as_u128_trunc()
213 }
214}
215
216impl Div<u128> for Wad {
217 type Output = Wad;
218 #[inline]
219 fn div(self, rhs: u128) -> Wad {
220 Wad(self.0 / rhs)
221 }
222}
223impl Div<Number> for Wad {
224 type Output = Wad;
225 #[inline]
226 fn div(self, rhs: Number) -> Wad {
227 Wad(self.0 / rhs)
228 }
229}
230
231#[inline]
240#[must_use]
241pub fn compute_fee_shares(
242 cur_total_assets: Number,
243 last_total_assets: Number,
244 performance_fee: Wad,
245 total_supply: Number,
246) -> Number {
247 let profit = cur_total_assets.saturating_sub(last_total_assets);
248 compute_fee_shares_from_assets(
249 performance_fee.apply_floored(profit),
250 cur_total_assets,
251 total_supply,
252 )
253}
254
255#[inline]
258#[must_use]
259pub fn compute_fee_shares_from_assets(
260 fee_assets: Number,
261 cur_total_assets: Number,
262 total_supply: Number,
263) -> Number {
264 if fee_assets.is_zero() || total_supply.is_zero() {
265 return Number::zero();
266 }
267 if fee_assets.0 >= cur_total_assets.0 {
268 return Number::zero();
269 }
270 let denom = Number(cur_total_assets.0 - fee_assets.0);
271 Number::mul_div_floor(fee_assets, total_supply, denom)
272}
273
274#[inline]
277#[must_use]
278pub fn mul_wad_floor(x: Number, y: Wad) -> Number {
279 Number::mul_div_floor(x, y.0, Number::from(Wad::SCALE))
280}
281
282#[inline]
285#[must_use]
286pub fn mul_div_floor(x: Number, y: Number, denom: Number) -> Number {
287 Number::mul_div_floor(x, y, denom)
288}
289
290#[inline]
294#[must_use]
295pub fn mul_div_ceil(x: Number, y: Number, denom: Number) -> Number {
296 Number::mul_div_ceil(x, y, denom)
297}
298
299pub const YEAR_NS: u64 = 365 * 24 * 60 * 60 * 1_000_000_000;
301
302#[inline]
311#[must_use]
312pub fn total_assets_for_fee_accrual(
313 cur_total_assets: u128,
314 anchor_total_assets: u128,
315 anchor_timestamp_ns: u64,
316 now_ns: u64,
317 max_rate: Option<Wad>,
318) -> u128 {
319 let Some(max_rate) = max_rate else {
320 return cur_total_assets;
321 };
322 if cur_total_assets <= anchor_total_assets || now_ns < anchor_timestamp_ns {
323 return cur_total_assets;
324 }
325 if anchor_total_assets == 0 {
326 return 0;
327 }
328 let elapsed_ns = now_ns - anchor_timestamp_ns;
329 if elapsed_ns == 0 {
330 return anchor_total_assets;
331 }
332 let annual_max_increase = max_rate.apply_floored(Number::from(anchor_total_assets));
333 let max_increase = mul_div_floor(
334 annual_max_increase,
335 Number::from(u128::from(elapsed_ns)),
336 Number::from(u128::from(YEAR_NS)),
337 )
338 .as_u128_saturating();
339 let max_total_assets = anchor_total_assets.saturating_add(max_increase);
340 cur_total_assets.min(max_total_assets)
341}
342
343#[inline]
347#[must_use]
348pub fn compute_management_fee_shares(
349 fee_assets_base: u128,
350 cur_total_assets: u128,
351 total_supply: u128,
352 management_fee_wad: Wad,
353 last_timestamp_ns: u64,
354 now_ns: u64,
355) -> Number {
356 if management_fee_wad.is_zero() || total_supply == 0 || now_ns <= last_timestamp_ns {
357 return Number::zero();
358 }
359 let elapsed_ns = now_ns - last_timestamp_ns;
360 let annual_fee_assets = management_fee_wad.apply_floored(Number::from(fee_assets_base));
361 let fee_assets = mul_div_floor(
362 annual_fee_assets,
363 Number::from(u128::from(elapsed_ns)),
364 Number::from(u128::from(YEAR_NS)),
365 );
366 compute_fee_shares_from_assets(
367 fee_assets,
368 Number::from(cur_total_assets),
369 Number::from(total_supply),
370 )
371}
372
373#[cfg(test)]
374mod tests;