balancer_maths_rust/pools/liquidity_bootstrapping/
liquidity_bootstrapping_pool.rs

1//! Liquidity Bootstrapping pool implementation
2
3use crate::common::errors::PoolError;
4use crate::common::pool_base::PoolBase;
5use crate::common::types::{Rounding, SwapParams};
6use crate::pools::liquidity_bootstrapping::liquidity_bootstrapping_data::LiquidityBootstrappingState;
7use crate::pools::liquidity_bootstrapping::liquidity_bootstrapping_math::get_normalized_weights;
8use crate::pools::weighted::weighted_math::{MAX_INVARIANT_RATIO, MIN_INVARIANT_RATIO, *};
9use alloy_primitives::U256;
10
11/// Liquidity Bootstrapping pool implementation
12pub struct LiquidityBootstrappingPool {
13    /// Current normalized weights (scaled 18) based on time interpolation
14    normalized_weights: Vec<U256>,
15    /// Pool state
16    state: LiquidityBootstrappingState,
17}
18
19impl LiquidityBootstrappingPool {
20    /// Create a new Liquidity Bootstrapping pool
21    pub fn new(state: LiquidityBootstrappingState) -> Result<Self, PoolError> {
22        if state.immutable.start_weights.len() != 2 || state.immutable.end_weights.len() != 2 {
23            return Err(PoolError::InvalidSwapParameters);
24        }
25
26        // Calculate current normalized weights based on time interpolation
27        let normalized_weights = get_normalized_weights(
28            state.immutable.project_token_index,
29            &state.mutable.current_timestamp,
30            &state.immutable.start_time,
31            &state.immutable.end_time,
32            &state.immutable.start_weights[state.immutable.project_token_index],
33            &state.immutable.end_weights[state.immutable.project_token_index],
34        );
35
36        Ok(Self {
37            normalized_weights,
38            state,
39        })
40    }
41
42    /// Check if swaps are enabled
43    fn is_swap_enabled(&self) -> bool {
44        self.state.mutable.is_swap_enabled
45    }
46
47    /// Check if project token swap in is blocked
48    fn is_project_token_swap_in_blocked(&self) -> bool {
49        self.state.immutable.is_project_token_swap_in_blocked
50    }
51
52    /// Get normalized weights for a specific token pair
53    fn get_normalized_weight_pair(
54        &self,
55        index_in: usize,
56        index_out: usize,
57    ) -> Result<(U256, U256), PoolError> {
58        if index_in >= self.normalized_weights.len() || index_out >= self.normalized_weights.len() {
59            return Err(PoolError::InvalidTokenIndex);
60        }
61
62        let token_in_weight = self.normalized_weights[index_in];
63        let token_out_weight = self.normalized_weights[index_out];
64
65        Ok((token_in_weight, token_out_weight))
66    }
67
68    /// Validate swap parameters
69    fn validate_swap(&self, token_in_index: usize) -> Result<(), PoolError> {
70        if !self.is_swap_enabled() {
71            return Err(PoolError::InvalidSwapParameters);
72        }
73
74        // Check if project token swap in is blocked
75        if self.is_project_token_swap_in_blocked()
76            && token_in_index == self.state.immutable.project_token_index
77        {
78            return Err(PoolError::InvalidSwapParameters);
79        }
80
81        Ok(())
82    }
83}
84
85impl PoolBase for LiquidityBootstrappingPool {
86    fn on_swap(&self, swap_params: &SwapParams) -> Result<U256, PoolError> {
87        let token_in_index = swap_params.token_in_index;
88        let token_out_index = swap_params.token_out_index;
89
90        if token_in_index >= self.normalized_weights.len()
91            || token_out_index >= self.normalized_weights.len()
92        {
93            return Err(PoolError::InvalidTokenIndex);
94        }
95
96        // Validate swap parameters
97        self.validate_swap(token_in_index)?;
98
99        let balance_in = &swap_params.balances_live_scaled_18[token_in_index];
100        let balance_out = &swap_params.balances_live_scaled_18[token_out_index];
101        let amount_scaled_18 = &swap_params.amount_scaled_18;
102
103        let (weight_in, weight_out) =
104            self.get_normalized_weight_pair(token_in_index, token_out_index)?;
105
106        match swap_params.swap_kind {
107            crate::common::types::SwapKind::GivenIn => compute_out_given_exact_in(
108                balance_in,
109                &weight_in,
110                balance_out,
111                &weight_out,
112                amount_scaled_18,
113            ),
114            crate::common::types::SwapKind::GivenOut => compute_in_given_exact_out(
115                balance_in,
116                &weight_in,
117                balance_out,
118                &weight_out,
119                amount_scaled_18,
120            ),
121        }
122    }
123
124    fn compute_invariant(
125        &self,
126        balances_live_scaled_18: &[U256],
127        rounding: Rounding,
128    ) -> Result<U256, PoolError> {
129        match rounding {
130            Rounding::RoundDown => {
131                compute_invariant_down(&self.normalized_weights, balances_live_scaled_18)
132            }
133            Rounding::RoundUp => {
134                compute_invariant_up(&self.normalized_weights, balances_live_scaled_18)
135            }
136        }
137    }
138
139    fn compute_balance(
140        &self,
141        balances_live_scaled_18: &[U256],
142        token_in_index: usize,
143        invariant_ratio: &U256,
144    ) -> Result<U256, PoolError> {
145        if token_in_index >= balances_live_scaled_18.len()
146            || token_in_index >= self.normalized_weights.len()
147        {
148            return Err(PoolError::InvalidTokenIndex);
149        }
150
151        let current_balance = &balances_live_scaled_18[token_in_index];
152        let weight = &self.normalized_weights[token_in_index];
153
154        // Calculate the new balance based on the invariant ratio
155        compute_balance_out_given_invariant(current_balance, weight, invariant_ratio)
156    }
157
158    fn get_maximum_invariant_ratio(&self) -> U256 {
159        MAX_INVARIANT_RATIO
160    }
161
162    fn get_minimum_invariant_ratio(&self) -> U256 {
163        MIN_INVARIANT_RATIO
164    }
165}
166
167impl From<LiquidityBootstrappingState> for LiquidityBootstrappingPool {
168    fn from(liquidity_bootstrapping_state: LiquidityBootstrappingState) -> Self {
169        Self::new(liquidity_bootstrapping_state)
170            .expect("Failed to create LiquidityBootstrappingPool from state")
171    }
172}