templar_vault_kernel/fee/
mod.rs

1//! Fee structures for vault operations.
2//!
3//! Portable across NEAR and Soroban.
4//!
5//! This module provides two fee representation approaches:
6//! - `Fee<T>` / `Fees<T>`: Generic types with string recipients for chain flexibility
7//! - `FeeSlot` / `FeesSpec`: Spec-compliant types with fixed-size `Address` recipients
8//!
9//! Use `Fee<Wad>` for NEAR where `AccountId` is naturally a string.
10//! Use `FeeSlot` when strict spec conformance with 32-byte addresses is required.
11
12use alloc::string::String;
13
14use crate::math::wad::Wad;
15use crate::types::Address;
16
17// Generic Fee Types (String recipient - flexible)
18
19/// A fee configuration with a rate and recipient.
20///
21/// This generic type uses a string recipient for maximum chain flexibility.
22/// For spec-compliant 32-byte address recipients, see `FeeSlot`.
23#[templar_vault_macros::vault_derive(borsh, serde, postcard)]
24#[derive(Clone, PartialEq, Eq)]
25pub struct Fee<T> {
26    /// The fee rate (interpretation depends on T).
27    pub fee: T,
28    /// The recipient identifier (account/address as string).
29    pub recipient: String,
30}
31
32impl<T> Fee<T> {
33    /// Create a new generic fee entry.
34    #[inline]
35    #[must_use]
36    pub fn new(fee: T, recipient: impl Into<String>) -> Self {
37        Self {
38            fee,
39            recipient: recipient.into(),
40        }
41    }
42}
43
44/// Collection of fees for a vault.
45///
46/// This generic type uses `Fee<T>` with string recipients.
47/// For spec-compliant types, see `FeesSpec`.
48#[templar_vault_macros::vault_derive(borsh, serde, postcard)]
49#[derive(Clone, PartialEq, Eq)]
50pub struct Fees<T> {
51    /// Performance fee (charged on profits).
52    pub performance: Fee<T>,
53    /// Management fee (charged over time).
54    pub management: Fee<T>,
55    /// Optional cap on how fast `total_assets` is allowed to grow for fee accrual.
56    ///
57    /// Interpreted as an annualized WAD rate (1e18 = 100% per year). When set,
58    /// fee accrual uses `min(cur_total_assets, last_total_assets * (1 + max_rate * dt / YEAR))`
59    /// as the effective `cur_total_assets`.
60    pub max_total_assets_growth_rate: Option<T>,
61}
62
63impl<T> Fees<T> {
64    /// Create a new generic fees configuration.
65    #[inline]
66    #[must_use]
67    pub const fn new(
68        performance: Fee<T>,
69        management: Fee<T>,
70        max_total_assets_growth_rate: Option<T>,
71    ) -> Self {
72        Self {
73            performance,
74            management,
75            max_total_assets_growth_rate,
76        }
77    }
78}
79
80// Spec-Compliant Fee Types (Address recipient - fixed size)
81
82/// A fee slot with a WAD-scaled rate and 32-byte address recipient.
83///
84/// This type matches the kernel spec exactly:
85/// - `fee_wad`: WAD-scaled fee rate (1e18 = 100%)
86/// - `recipient`: 32-byte canonical address
87///
88/// The executor is responsible for mapping chain-native addresses to/from
89/// this canonical 32-byte format.
90#[templar_vault_macros::vault_derive(borsh, serde, postcard)]
91#[derive(Clone, Copy, PartialEq, Eq)]
92pub struct FeeSlot {
93    /// The fee rate as a WAD value (1e18 = 100%).
94    pub fee_wad: Wad,
95    /// The recipient as a 32-byte canonical address.
96    pub recipient: Address,
97}
98
99impl FeeSlot {
100    /// Create a new fee slot.
101    #[inline]
102    #[must_use]
103    pub const fn new(fee_wad: Wad, recipient: Address) -> Self {
104        Self { fee_wad, recipient }
105    }
106
107    pub const ZERO: Self = Self {
108        fee_wad: Wad::ZERO,
109        recipient: Address([0u8; 32]),
110    };
111
112    /// Create a zero fee slot (no fee, zero address).
113    #[inline]
114    #[must_use]
115    pub const fn zero() -> Self {
116        Self::ZERO
117    }
118
119    #[inline]
120    #[must_use]
121    pub fn has_rate(&self) -> bool {
122        !self.fee_wad.is_zero()
123    }
124
125    /// Check if this fee slot has a zero rate.
126    #[inline]
127    #[must_use]
128    pub fn is_zero_rate(&self) -> bool {
129        !self.has_rate()
130    }
131}
132
133impl Default for FeeSlot {
134    fn default() -> Self {
135        Self::zero()
136    }
137}
138
139/// Spec-compliant fee collection using `FeeSlot` with 32-byte addresses.
140///
141/// This type matches the kernel spec exactly and uses fixed-size addresses
142/// for performance and predictable serialization.
143#[templar_vault_macros::vault_derive(borsh, serde, postcard)]
144#[derive(Clone, Copy, PartialEq, Eq)]
145pub struct FeesSpec {
146    /// Performance fee (charged on profits).
147    pub performance: FeeSlot,
148    /// Management fee (charged over time).
149    pub management: FeeSlot,
150    /// Optional cap on total assets growth rate for fee accrual.
151    ///
152    /// Interpreted as an annualized WAD rate (1e18 = 100% per year).
153    pub max_total_assets_growth_rate: Option<Wad>,
154}
155
156impl FeesSpec {
157    /// Create a new fees configuration.
158    #[inline]
159    #[must_use]
160    pub const fn new(
161        performance: FeeSlot,
162        management: FeeSlot,
163        max_total_assets_growth_rate: Option<Wad>,
164    ) -> Self {
165        Self {
166            performance,
167            management,
168            max_total_assets_growth_rate,
169        }
170    }
171
172    #[inline]
173    #[must_use]
174    pub fn has_active_slot_fees(&self) -> bool {
175        self.performance.has_rate() || self.management.has_rate()
176    }
177
178    #[inline]
179    #[must_use]
180    pub fn has_growth_cap(&self) -> bool {
181        self.max_total_assets_growth_rate.is_some()
182    }
183
184    /// Returns true when all fee fields are unset/zeroed.
185    #[inline]
186    #[must_use]
187    pub fn is_zero(&self) -> bool {
188        !self.has_active_slot_fees() && !self.has_growth_cap()
189    }
190
191    pub const ZERO: Self = Self {
192        performance: FeeSlot::ZERO,
193        management: FeeSlot::ZERO,
194        max_total_assets_growth_rate: None,
195    };
196
197    /// Create a fees configuration with no fees.
198    #[inline]
199    #[must_use]
200    pub const fn zero() -> Self {
201        Self::ZERO
202    }
203}
204
205impl Default for FeesSpec {
206    fn default() -> Self {
207        Self::zero()
208    }
209}
210
211// Tests
212
213#[cfg(test)]
214mod tests;