Skip to main content

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