balancer_maths_rust/hooks/akron/
mod.rs

1use crate::common::maths::{div_down_fixed, div_up_fixed, mul_div_up_fixed, pow_up_fixed};
2use crate::common::types::{HookStateBase, SwapKind};
3use crate::hooks::types::{DynamicSwapFeeResult, HookState};
4use crate::hooks::{DefaultHook, HookBase, HookConfig};
5use num_bigint::BigInt;
6use num_traits::Zero;
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<BigInt>,
16    /// Minimum swap fee percentage (scaled 18)
17    pub minimum_swap_fee_percentage: BigInt,
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: BigInt::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: &BigInt,
55        exponent: &BigInt,
56        amount_in: &BigInt,
57    ) -> Result<BigInt, crate::common::errors::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 * BigInt::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.clone() - 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: &BigInt,
81        exponent: &BigInt,
82        amount_out: &BigInt,
83    ) -> Result<BigInt, crate::common::errors::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 * BigInt::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.clone() - power_without_fees;
96        let denominator = power_with_fees.clone() - crate::common::constants::WAD.clone();
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: &BigInt,
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 exponent = div_down_fixed(
121                        &state.weights[swap_params.token_in_index],
122                        &state.weights[swap_params.token_out_index],
123                    )
124                    .unwrap_or_else(|_| BigInt::zero());
125
126                    Self::compute_swap_fee_percentage_given_exact_in(
127                        &swap_params.balances_live_scaled_18[swap_params.token_in_index],
128                        &exponent,
129                        &swap_params.amount_scaled_18,
130                    )
131                    .unwrap_or_else(|_| BigInt::zero())
132                } else {
133                    let exponent = div_up_fixed(
134                        &state.weights[swap_params.token_out_index],
135                        &state.weights[swap_params.token_in_index],
136                    )
137                    .unwrap_or_else(|_| BigInt::zero());
138
139                    Self::compute_swap_fee_percentage_given_exact_out(
140                        &swap_params.balances_live_scaled_18[swap_params.token_out_index],
141                        &exponent,
142                        &swap_params.amount_scaled_18,
143                    )
144                    .unwrap_or_else(|_| BigInt::zero())
145                };
146
147                // Charge the static or calculated fee, whichever is greater
148                let dynamic_swap_fee =
149                    if state.minimum_swap_fee_percentage > calculated_swap_fee_percentage {
150                        state.minimum_swap_fee_percentage.clone()
151                    } else {
152                        calculated_swap_fee_percentage
153                    };
154
155                DynamicSwapFeeResult {
156                    success: true,
157                    dynamic_swap_fee,
158                }
159            }
160            _ => DynamicSwapFeeResult {
161                success: false,
162                dynamic_swap_fee: BigInt::zero(),
163            },
164        }
165    }
166
167    // Delegate all other methods to DefaultHook
168    fn on_before_add_liquidity(
169        &self,
170        kind: crate::common::types::AddLiquidityKind,
171        max_amounts_in_scaled_18: &[BigInt],
172        min_bpt_amount_out: &BigInt,
173        balances_scaled_18: &[BigInt],
174        hook_state: &HookState,
175    ) -> crate::hooks::types::BeforeAddLiquidityResult {
176        DefaultHook::new().on_before_add_liquidity(
177            kind,
178            max_amounts_in_scaled_18,
179            min_bpt_amount_out,
180            balances_scaled_18,
181            hook_state,
182        )
183    }
184
185    fn on_after_add_liquidity(
186        &self,
187        kind: crate::common::types::AddLiquidityKind,
188        amounts_in_scaled_18: &[BigInt],
189        amounts_in_raw: &[BigInt],
190        bpt_amount_out: &BigInt,
191        balances_scaled_18: &[BigInt],
192        hook_state: &HookState,
193    ) -> crate::hooks::types::AfterAddLiquidityResult {
194        DefaultHook::new().on_after_add_liquidity(
195            kind,
196            amounts_in_scaled_18,
197            amounts_in_raw,
198            bpt_amount_out,
199            balances_scaled_18,
200            hook_state,
201        )
202    }
203
204    fn on_before_remove_liquidity(
205        &self,
206        kind: crate::common::types::RemoveLiquidityKind,
207        max_bpt_amount_in: &BigInt,
208        min_amounts_out_scaled_18: &[BigInt],
209        balances_scaled_18: &[BigInt],
210        hook_state: &HookState,
211    ) -> crate::hooks::types::BeforeRemoveLiquidityResult {
212        DefaultHook::new().on_before_remove_liquidity(
213            kind,
214            max_bpt_amount_in,
215            min_amounts_out_scaled_18,
216            balances_scaled_18,
217            hook_state,
218        )
219    }
220
221    fn on_after_remove_liquidity(
222        &self,
223        kind: crate::common::types::RemoveLiquidityKind,
224        bpt_amount_in: &BigInt,
225        amounts_out_scaled_18: &[BigInt],
226        amounts_out_raw: &[BigInt],
227        balances_scaled_18: &[BigInt],
228        hook_state: &HookState,
229    ) -> crate::hooks::types::AfterRemoveLiquidityResult {
230        DefaultHook::new().on_after_remove_liquidity(
231            kind,
232            bpt_amount_in,
233            amounts_out_scaled_18,
234            amounts_out_raw,
235            balances_scaled_18,
236            hook_state,
237        )
238    }
239
240    fn on_before_swap(
241        &self,
242        swap_params: &crate::common::types::SwapParams,
243        hook_state: &HookState,
244    ) -> crate::hooks::types::BeforeSwapResult {
245        DefaultHook::new().on_before_swap(swap_params, hook_state)
246    }
247
248    fn on_after_swap(
249        &self,
250        after_swap_params: &crate::hooks::types::AfterSwapParams,
251        hook_state: &HookState,
252    ) -> crate::hooks::types::AfterSwapResult {
253        DefaultHook::new().on_after_swap(after_swap_params, hook_state)
254    }
255}
256
257impl Default for AkronHook {
258    fn default() -> Self {
259        AkronHook::new()
260    }
261}