balancer_maths_rust/hooks/exit_fee/
mod.rs

1//! Exit fee hook implementation
2
3use crate::common::maths::mul_down_fixed;
4use crate::common::types::{HookStateBase, RemoveLiquidityKind};
5use crate::hooks::types::{AfterRemoveLiquidityResult, HookState};
6use crate::hooks::{DefaultHook, HookBase, HookConfig};
7use num_bigint::BigInt;
8use num_traits::Zero;
9use serde::{Deserialize, Serialize};
10
11/// Exit fee hook state
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub struct ExitFeeHookState {
14    /// Hook type
15    pub hook_type: String,
16    /// Token addresses
17    pub tokens: Vec<String>,
18    /// Remove liquidity hook fee percentage (scaled 18)
19    pub remove_liquidity_hook_fee_percentage: BigInt,
20}
21
22impl HookStateBase for ExitFeeHookState {
23    fn hook_type(&self) -> &str {
24        &self.hook_type
25    }
26}
27
28impl Default for ExitFeeHookState {
29    fn default() -> Self {
30        Self {
31            hook_type: "ExitFee".to_string(),
32            tokens: vec![],
33            remove_liquidity_hook_fee_percentage: BigInt::zero(),
34        }
35    }
36}
37
38/// Exit fee hook implementation
39/// This hook implements the ExitFeeHookExample found in mono-repo: https://github.com/balancer/balancer-v3-monorepo/blob/c848c849cb44dc35f05d15858e4fba9f17e92d5f/pkg/pool-hooks/contracts/ExitFeeHookExample.sol
40pub struct ExitFeeHook {
41    config: HookConfig,
42}
43
44impl ExitFeeHook {
45    pub fn new() -> Self {
46        let config = HookConfig {
47            should_call_after_remove_liquidity: true,
48            enable_hook_adjusted_amounts: true,
49            ..Default::default()
50        };
51
52        Self { config }
53    }
54}
55
56impl HookBase for ExitFeeHook {
57    fn hook_type(&self) -> &str {
58        "ExitFee"
59    }
60
61    fn config(&self) -> &HookConfig {
62        &self.config
63    }
64
65    fn on_after_remove_liquidity(
66        &self,
67        kind: RemoveLiquidityKind,
68        _bpt_amount_in: &BigInt,
69        _amounts_out_scaled_18: &[BigInt],
70        amounts_out_raw: &[BigInt],
71        _balances_scaled_18: &[BigInt],
72        hook_state: &HookState,
73    ) -> AfterRemoveLiquidityResult {
74        match hook_state {
75            HookState::ExitFee(state) => {
76                // Our current architecture only supports fees on tokens. Since we must always respect exact `amountsOut`, and
77                // non-proportional remove liquidity operations would require taking fees in BPT, we only support proportional
78                // removeLiquidity.
79                if kind != RemoveLiquidityKind::Proportional {
80                    return AfterRemoveLiquidityResult {
81                        success: false,
82                        hook_adjusted_amounts_out_raw: amounts_out_raw.to_vec(),
83                    };
84                }
85
86                let mut accrued_fees = vec![BigInt::zero(); state.tokens.len()];
87                let mut hook_adjusted_amounts_out_raw = amounts_out_raw.to_vec();
88
89                if state.remove_liquidity_hook_fee_percentage > BigInt::zero() {
90                    // Charge fees proportional to amounts out of each token
91                    for i in 0..amounts_out_raw.len() {
92                        let hook_fee = mul_down_fixed(
93                            &amounts_out_raw[i],
94                            &state.remove_liquidity_hook_fee_percentage,
95                        )
96                        .unwrap_or_else(|_| BigInt::zero());
97
98                        accrued_fees[i] = hook_fee.clone();
99                        hook_adjusted_amounts_out_raw[i] =
100                            &hook_adjusted_amounts_out_raw[i] - &hook_fee;
101                        // Fees don't need to be transferred to the hook, because donation will reinsert them in the vault
102                    }
103
104                    // In SC Hook Donates accrued fees back to LPs
105                    // _vault.addLiquidity(
106                    //     AddLiquidityParams({
107                    //         pool: pool,
108                    //         to: msg.sender, // It would mint BPTs to router, but it's a donation so no BPT is minted
109                    //         maxAmountsIn: accruedFees, // Donate all accrued fees back to the pool (i.e. to the LPs)
110                    //         minBptAmountOut: 0, // Donation does not return BPTs, any number above 0 will revert
111                    //         kind: AddLiquidityKind.DONATION,
112                    //         userData: bytes(''), // User data is not used by donation, so we can set to an empty string
113                    //     }),
114                    // );
115                }
116
117                AfterRemoveLiquidityResult {
118                    success: true,
119                    hook_adjusted_amounts_out_raw,
120                }
121            }
122            _ => AfterRemoveLiquidityResult {
123                success: false,
124                hook_adjusted_amounts_out_raw: amounts_out_raw.to_vec(),
125            },
126        }
127    }
128
129    // Delegate all other methods to DefaultHook
130    fn on_before_add_liquidity(
131        &self,
132        kind: crate::common::types::AddLiquidityKind,
133        max_amounts_in_scaled_18: &[BigInt],
134        min_bpt_amount_out: &BigInt,
135        balances_scaled_18: &[BigInt],
136        hook_state: &HookState,
137    ) -> crate::hooks::types::BeforeAddLiquidityResult {
138        DefaultHook::new().on_before_add_liquidity(
139            kind,
140            max_amounts_in_scaled_18,
141            min_bpt_amount_out,
142            balances_scaled_18,
143            hook_state,
144        )
145    }
146
147    fn on_after_add_liquidity(
148        &self,
149        kind: crate::common::types::AddLiquidityKind,
150        amounts_in_scaled_18: &[BigInt],
151        amounts_in_raw: &[BigInt],
152        bpt_amount_out: &BigInt,
153        balances_scaled_18: &[BigInt],
154        hook_state: &HookState,
155    ) -> crate::hooks::types::AfterAddLiquidityResult {
156        DefaultHook::new().on_after_add_liquidity(
157            kind,
158            amounts_in_scaled_18,
159            amounts_in_raw,
160            bpt_amount_out,
161            balances_scaled_18,
162            hook_state,
163        )
164    }
165
166    fn on_before_remove_liquidity(
167        &self,
168        kind: crate::common::types::RemoveLiquidityKind,
169        max_bpt_amount_in: &BigInt,
170        min_amounts_out_scaled_18: &[BigInt],
171        balances_scaled_18: &[BigInt],
172        hook_state: &HookState,
173    ) -> crate::hooks::types::BeforeRemoveLiquidityResult {
174        DefaultHook::new().on_before_remove_liquidity(
175            kind,
176            max_bpt_amount_in,
177            min_amounts_out_scaled_18,
178            balances_scaled_18,
179            hook_state,
180        )
181    }
182
183    fn on_before_swap(
184        &self,
185        swap_params: &crate::common::types::SwapParams,
186        hook_state: &HookState,
187    ) -> crate::hooks::types::BeforeSwapResult {
188        DefaultHook::new().on_before_swap(swap_params, hook_state)
189    }
190
191    fn on_after_swap(
192        &self,
193        after_swap_params: &crate::hooks::types::AfterSwapParams,
194        hook_state: &HookState,
195    ) -> crate::hooks::types::AfterSwapResult {
196        DefaultHook::new().on_after_swap(after_swap_params, hook_state)
197    }
198
199    fn on_compute_dynamic_swap_fee(
200        &self,
201        swap_params: &crate::common::types::SwapParams,
202        static_swap_fee_percentage: &BigInt,
203        hook_state: &HookState,
204    ) -> crate::hooks::types::DynamicSwapFeeResult {
205        DefaultHook::new().on_compute_dynamic_swap_fee(
206            swap_params,
207            static_swap_fee_percentage,
208            hook_state,
209        )
210    }
211}
212
213impl Default for ExitFeeHook {
214    fn default() -> Self {
215        ExitFeeHook::new()
216    }
217}