Skip to main content

balancer_maths_rust/hooks/akron/
mod.rs

1use crate::common::errors::PoolError;
2use crate::common::maths::{div_down_fixed, div_up_fixed, mul_div_up_fixed, pow_up_fixed};
3use crate::common::types::{HookStateBase, SwapKind};
4use crate::hooks::types::{DynamicSwapFeeResult, HookState};
5use crate::hooks::{DefaultHook, HookBase, HookConfig};
6use alloy_primitives::U256;
7use serde::{Deserialize, Serialize};
8
9/// Akron hook state
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct AkronHookState {
12    /// Hook type
13    pub hook_type: String,
14    /// Pool weights
15    pub weights: Vec<U256>,
16    /// Minimum swap fee percentage (scaled 18)
17    pub minimum_swap_fee_percentage: U256,
18}
19
20impl HookStateBase for AkronHookState {
21    fn hook_type(&self) -> &str {
22        &self.hook_type
23    }
24}
25
26impl Default for AkronHookState {
27    fn default() -> Self {
28        Self {
29            hook_type: "Akron".to_string(),
30            weights: vec![],
31            minimum_swap_fee_percentage: U256::ZERO,
32        }
33    }
34}
35
36/// Akron hook implementation
37/// This hook implements Loss-Versus-Rebalancing (LVR) fee calculation for weighted pools
38pub struct AkronHook {
39    config: HookConfig,
40}
41
42impl AkronHook {
43    pub fn new() -> Self {
44        let config = HookConfig {
45            should_call_compute_dynamic_swap_fee: true,
46            ..Default::default()
47        };
48
49        Self { config }
50    }
51
52    /// Compute swap fee percentage for GivenIn swaps
53    fn compute_swap_fee_percentage_given_exact_in(
54        balance_in: &U256,
55        exponent: &U256,
56        amount_in: &U256,
57    ) -> Result<U256, PoolError> {
58        // swap fee is equal to outGivenExactIn(grossAmountIn) - outGivenExactInWithFees(grossAmountIn)
59        let balance_plus_amount = balance_in + amount_in;
60        let balance_plus_amount_times_2 = balance_in + amount_in * U256::from(2);
61
62        let power_with_fees = pow_up_fixed(
63            &div_up_fixed(&balance_plus_amount, &balance_plus_amount_times_2)?,
64            exponent,
65        )?;
66        let power_without_fees =
67            pow_up_fixed(&div_up_fixed(balance_in, &balance_plus_amount)?, exponent)?;
68
69        let numerator = mul_div_up_fixed(
70            &balance_plus_amount,
71            &(power_with_fees - power_without_fees),
72            &power_with_fees,
73        )?;
74
75        mul_div_up_fixed(exponent, &numerator, amount_in)
76    }
77
78    /// Compute swap fee percentage for GivenOut swaps
79    fn compute_swap_fee_percentage_given_exact_out(
80        balance_out: &U256,
81        exponent: &U256,
82        amount_out: &U256,
83    ) -> Result<U256, PoolError> {
84        // swap fee is equal to inGivenExactOutWithFees(grossAmountIn) - inGivenExactOut(grossAmountIn)
85        let balance_minus_amount = balance_out - amount_out;
86        let balance_minus_amount_times_2 = balance_out - amount_out * U256::from(2);
87
88        let power_with_fees = pow_up_fixed(
89            &div_up_fixed(&balance_minus_amount, &balance_minus_amount_times_2)?,
90            exponent,
91        )?;
92        let power_without_fees =
93            pow_up_fixed(&div_up_fixed(balance_out, &balance_minus_amount)?, exponent)?;
94
95        let numerator = power_with_fees - power_without_fees;
96        let denominator = power_with_fees - crate::common::constants::WAD;
97
98        div_up_fixed(&numerator, &denominator)
99    }
100}
101
102impl HookBase for AkronHook {
103    fn hook_type(&self) -> &str {
104        "Akron"
105    }
106
107    fn config(&self) -> &HookConfig {
108        &self.config
109    }
110
111    fn on_compute_dynamic_swap_fee(
112        &self,
113        swap_params: &crate::common::types::SwapParams,
114        _static_swap_fee_percentage: &U256,
115        hook_state: &HookState,
116    ) -> DynamicSwapFeeResult {
117        match hook_state {
118            HookState::Akron(state) => {
119                let calculated_swap_fee_percentage = if swap_params.swap_kind == SwapKind::GivenIn {
120                    let Ok(exponent) = div_down_fixed(
121                        &state.weights[swap_params.token_in_index],
122                        &state.weights[swap_params.token_out_index],
123                    ) else {
124                        return DynamicSwapFeeResult {
125                            success: false,
126                            dynamic_swap_fee: U256::ZERO,
127                        };
128                    };
129
130                    match Self::compute_swap_fee_percentage_given_exact_in(
131                        &swap_params.balances_live_scaled_18[swap_params.token_in_index],
132                        &exponent,
133                        &swap_params.amount_scaled_18,
134                    ) {
135                        Ok(fee) => fee,
136                        Err(_) => {
137                            return DynamicSwapFeeResult {
138                                success: false,
139                                dynamic_swap_fee: U256::ZERO,
140                            };
141                        }
142                    }
143                } else {
144                    let Ok(exponent) = div_up_fixed(
145                        &state.weights[swap_params.token_out_index],
146                        &state.weights[swap_params.token_in_index],
147                    ) else {
148                        return DynamicSwapFeeResult {
149                            success: false,
150                            dynamic_swap_fee: U256::ZERO,
151                        };
152                    };
153
154                    match Self::compute_swap_fee_percentage_given_exact_out(
155                        &swap_params.balances_live_scaled_18[swap_params.token_out_index],
156                        &exponent,
157                        &swap_params.amount_scaled_18,
158                    ) {
159                        Ok(fee) => fee,
160                        Err(_) => {
161                            return DynamicSwapFeeResult {
162                                success: false,
163                                dynamic_swap_fee: U256::ZERO,
164                            };
165                        }
166                    }
167                };
168
169                // Charge the static or calculated fee, whichever is greater
170                let dynamic_swap_fee =
171                    if state.minimum_swap_fee_percentage > calculated_swap_fee_percentage {
172                        state.minimum_swap_fee_percentage
173                    } else {
174                        calculated_swap_fee_percentage
175                    };
176
177                DynamicSwapFeeResult {
178                    success: true,
179                    dynamic_swap_fee,
180                }
181            }
182            _ => DynamicSwapFeeResult {
183                success: false,
184                dynamic_swap_fee: U256::ZERO,
185            },
186        }
187    }
188
189    // Delegate all other methods to DefaultHook
190    fn on_before_add_liquidity(
191        &self,
192        kind: crate::common::types::AddLiquidityKind,
193        max_amounts_in_scaled_18: &[U256],
194        min_bpt_amount_out: &U256,
195        balances_scaled_18: &[U256],
196        hook_state: &HookState,
197    ) -> crate::hooks::types::BeforeAddLiquidityResult {
198        DefaultHook::new().on_before_add_liquidity(
199            kind,
200            max_amounts_in_scaled_18,
201            min_bpt_amount_out,
202            balances_scaled_18,
203            hook_state,
204        )
205    }
206
207    fn on_after_add_liquidity(
208        &self,
209        kind: crate::common::types::AddLiquidityKind,
210        amounts_in_scaled_18: &[U256],
211        amounts_in_raw: &[U256],
212        bpt_amount_out: &U256,
213        balances_scaled_18: &[U256],
214        hook_state: &HookState,
215    ) -> crate::hooks::types::AfterAddLiquidityResult {
216        DefaultHook::new().on_after_add_liquidity(
217            kind,
218            amounts_in_scaled_18,
219            amounts_in_raw,
220            bpt_amount_out,
221            balances_scaled_18,
222            hook_state,
223        )
224    }
225
226    fn on_before_remove_liquidity(
227        &self,
228        kind: crate::common::types::RemoveLiquidityKind,
229        max_bpt_amount_in: &U256,
230        min_amounts_out_scaled_18: &[U256],
231        balances_scaled_18: &[U256],
232        hook_state: &HookState,
233    ) -> crate::hooks::types::BeforeRemoveLiquidityResult {
234        DefaultHook::new().on_before_remove_liquidity(
235            kind,
236            max_bpt_amount_in,
237            min_amounts_out_scaled_18,
238            balances_scaled_18,
239            hook_state,
240        )
241    }
242
243    fn on_after_remove_liquidity(
244        &self,
245        kind: crate::common::types::RemoveLiquidityKind,
246        bpt_amount_in: &U256,
247        amounts_out_scaled_18: &[U256],
248        amounts_out_raw: &[U256],
249        balances_scaled_18: &[U256],
250        hook_state: &HookState,
251    ) -> crate::hooks::types::AfterRemoveLiquidityResult {
252        DefaultHook::new().on_after_remove_liquidity(
253            kind,
254            bpt_amount_in,
255            amounts_out_scaled_18,
256            amounts_out_raw,
257            balances_scaled_18,
258            hook_state,
259        )
260    }
261
262    fn on_before_swap(
263        &self,
264        swap_params: &crate::common::types::SwapParams,
265        hook_state: &HookState,
266    ) -> crate::hooks::types::BeforeSwapResult {
267        DefaultHook::new().on_before_swap(swap_params, hook_state)
268    }
269
270    fn on_after_swap(
271        &self,
272        after_swap_params: &crate::hooks::types::AfterSwapParams,
273        hook_state: &HookState,
274    ) -> crate::hooks::types::AfterSwapResult {
275        DefaultHook::new().on_after_swap(after_swap_params, hook_state)
276    }
277}
278
279impl Default for AkronHook {
280    fn default() -> Self {
281        AkronHook::new()
282    }
283}