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 Serialize::serialize(&self.0, serializer)
65 }
66 }
67
68 impl<'de> Deserialize<'de> for Wad {
69 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
70 where
71 D: Deserializer<'de>,
72 {
73 <Number as Deserialize>::deserialize(deserializer).map(Wad)
74 }
75 }
76}
77
78#[cfg(feature = "borsh")]
79mod borsh_impl {
80 use super::*;
81 use borsh::{self, BorshDeserialize, BorshSerialize};
82
83 impl BorshSerialize for Wad {
84 fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
85 BorshSerialize::serialize(&self.0, writer)
86 }
87 }
88
89 impl BorshDeserialize for Wad {
90 fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
91 <Number as BorshDeserialize>::deserialize_reader(reader).map(Wad)
92 }
93 }
94}
95
96#[cfg(feature = "borsh-schema")]
97mod borsh_schema_impl {
98 use super::*;
99 use alloc::collections::BTreeMap;
100 use borsh::schema::{add_definition, Declaration, Definition};
101 use borsh::BorshSchema;
102
103 impl BorshSchema for Wad {
104 fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
105 let definition = Definition::Primitive(32);
106 add_definition(Self::declaration(), definition, definitions);
107 }
108
109 fn declaration() -> Declaration {
110 "Wad".into()
111 }
112 }
113}
114
115#[cfg(feature = "schemars")]
116mod schemars_impl {
117 use super::*;
118 use alloc::string::ToString;
119 use schemars::r#gen::SchemaGenerator;
120 use schemars::schema::Schema;
121 use schemars::JsonSchema;
122
123 impl JsonSchema for Wad {
124 fn schema_name() -> alloc::string::String {
125 "Wad".to_string()
126 }
127
128 fn json_schema(generator: &mut SchemaGenerator) -> Schema {
129 let mut schema = generator.subschema_for::<Number>().into_object();
130 schema.metadata().description =
131 Some("Wad fixed fraction backed by 256-bit unsigned integer".to_string());
132 schema.string().pattern = Some("^(0|[1-9][0-9]{0,77})$".to_string());
133 schema.into()
134 }
135 }
136}
137
138impl Wad {
139 pub const SCALE: u128 = 1_000_000_000_000_000_000u128;
141
142 pub const ZERO: Self = Wad(Number::ZERO);
143 pub const ONE: Self = Wad(Number(U256([Self::SCALE as u64, 0, 0, 0])));
144
145 #[inline]
147 #[must_use]
148 pub const fn zero() -> Self {
149 Self::ZERO
150 }
151
152 #[inline]
154 #[must_use]
155 pub const fn one() -> Self {
156 Self::ONE
157 }
158
159 #[inline]
160 #[must_use]
161 pub fn is_zero(&self) -> bool {
162 self.0.is_zero()
163 }
164
165 #[inline]
166 #[must_use]
167 pub fn is_one(&self) -> bool {
168 self.0 .0 == U256::from(Self::SCALE)
169 }
170
171 #[inline]
173 #[must_use]
174 pub fn as_u128_trunc(self) -> u128 {
175 self.0.as_u128_trunc()
176 }
177
178 #[inline]
180 #[must_use]
181 pub fn apply_floored(self, amount: Number) -> Number {
182 Number::mul_div_floor(amount, self.0, Number::from(Self::SCALE))
183 }
184}
185
186impl From<u128> for Wad {
187 #[inline]
188 fn from(v: u128) -> Self {
189 Wad(Number::from(v))
190 }
191}
192
193impl From<Wad> for u128 {
194 #[inline]
195 fn from(w: Wad) -> u128 {
196 w.as_u128_trunc()
197 }
198}
199
200impl Div<u128> for Wad {
201 type Output = Wad;
202 #[inline]
203 fn div(self, rhs: u128) -> Wad {
204 Wad(self.0 / rhs)
205 }
206}
207impl Div<Number> for Wad {
208 type Output = Wad;
209 #[inline]
210 fn div(self, rhs: Number) -> Wad {
211 Wad(self.0 / rhs)
212 }
213}
214
215#[inline]
224#[must_use]
225pub fn compute_fee_shares(
226 cur_total_assets: Number,
227 last_total_assets: Number,
228 performance_fee: Wad,
229 total_supply: Number,
230) -> Number {
231 let profit = cur_total_assets.saturating_sub(last_total_assets);
232 compute_fee_shares_from_assets(
233 performance_fee.apply_floored(profit),
234 cur_total_assets,
235 total_supply,
236 )
237}
238
239#[inline]
242#[must_use]
243pub fn compute_fee_shares_from_assets(
244 fee_assets: Number,
245 cur_total_assets: Number,
246 total_supply: Number,
247) -> Number {
248 if fee_assets.is_zero() || total_supply.is_zero() {
249 return Number::zero();
250 }
251 if fee_assets.0 >= cur_total_assets.0 {
252 return Number::zero();
253 }
254 let denom = Number(cur_total_assets.0 - fee_assets.0);
255 Number::mul_div_floor(fee_assets, total_supply, denom)
256}
257
258#[inline]
261#[must_use]
262pub fn mul_wad_floor(x: Number, y: Wad) -> Number {
263 y.apply_floored(x)
264}
265
266#[inline]
269#[must_use]
270pub fn mul_div_floor(x: Number, y: Number, denom: Number) -> Number {
271 Number::mul_div_floor(x, y, denom)
272}
273
274#[inline]
278#[must_use]
279pub fn mul_div_ceil(x: Number, y: Number, denom: Number) -> Number {
280 Number::mul_div_ceil(x, y, denom)
281}
282
283pub const YEAR_NS: u64 = 365 * 24 * 60 * 60 * 1_000_000_000;
285
286#[inline]
292#[must_use]
293pub fn total_assets_for_fee_accrual(
294 cur_total_assets: u128,
295 anchor_total_assets: u128,
296 anchor_timestamp_ns: u64,
297 now_ns: u64,
298 max_rate: Option<Wad>,
299) -> u128 {
300 let Some(max_rate) = max_rate else {
301 return cur_total_assets;
302 };
303 if cur_total_assets <= anchor_total_assets
304 || anchor_total_assets == 0
305 || now_ns < anchor_timestamp_ns
306 {
307 return cur_total_assets;
308 }
309 let elapsed_ns = now_ns - anchor_timestamp_ns;
310 if elapsed_ns == 0 {
311 return anchor_total_assets;
312 }
313 let annual_max_increase = max_rate.apply_floored(Number::from(anchor_total_assets));
314 let max_increase = mul_div_floor(
315 annual_max_increase,
316 Number::from(u128::from(elapsed_ns)),
317 Number::from(u128::from(YEAR_NS)),
318 )
319 .as_u128_saturating();
320 let max_total_assets = anchor_total_assets.saturating_add(max_increase);
321 cur_total_assets.min(max_total_assets)
322}
323
324#[inline]
328#[must_use]
329pub fn compute_management_fee_shares(
330 fee_assets_base: u128,
331 cur_total_assets: u128,
332 total_supply: u128,
333 management_fee_wad: Wad,
334 last_timestamp_ns: u64,
335 now_ns: u64,
336) -> Number {
337 if management_fee_wad.is_zero() || total_supply == 0 || now_ns <= last_timestamp_ns {
338 return Number::zero();
339 }
340 let elapsed_ns = now_ns - last_timestamp_ns;
341 let annual_fee_assets = management_fee_wad.apply_floored(Number::from(fee_assets_base));
342 let fee_assets = mul_div_floor(
343 annual_fee_assets,
344 Number::from(u128::from(elapsed_ns)),
345 Number::from(u128::from(YEAR_NS)),
346 );
347 compute_fee_shares_from_assets(
348 fee_assets,
349 Number::from(cur_total_assets),
350 Number::from(total_supply),
351 )
352}
353
354#[cfg(test)]
355mod tests;