Skip to main content

balancer_maths_rust/pools/quantamm/
quantamm_pool.rs

1//! QuantAmm pool implementation
2
3use crate::common::errors::PoolError;
4use crate::common::maths::mul_down_fixed;
5use crate::common::pool_base::PoolBase;
6use crate::common::types::{Rounding, SwapParams};
7use crate::pools::quantamm::quantamm_data::QuantAmmState;
8use crate::pools::quantamm::quantamm_math::{
9    calculate_block_normalised_weight, get_first_four_weights_and_multipliers,
10    get_second_four_weights_and_multipliers,
11};
12use crate::pools::weighted::weighted_math::{MAX_INVARIANT_RATIO, MIN_INVARIANT_RATIO, *};
13use alloy_primitives::{I256, U256};
14
15/// QuantAmm pool implementation
16pub struct QuantAmmPool {
17    /// Current normalized weights (scaled 18) based on time interpolation
18    normalized_weights: Vec<U256>,
19    /// Pool state
20    state: QuantAmmState,
21}
22
23impl QuantAmmPool {
24    /// Create a new QuantAmm pool
25    pub fn new(state: QuantAmmState) -> Result<Self, PoolError> {
26        let (first_weights, first_multipliers) = get_first_four_weights_and_multipliers(
27            &state.base.tokens,
28            &state.mutable.first_four_weights_and_multipliers,
29        );
30
31        let (second_weights, second_multipliers) = get_second_four_weights_and_multipliers(
32            &state.base.tokens,
33            &state.mutable.second_four_weights_and_multipliers,
34        );
35
36        let base_weights = [first_weights, second_weights].concat();
37        let multipliers = [first_multipliers, second_multipliers].concat();
38
39        if base_weights.is_empty() {
40            return Err(PoolError::InvalidSwapParameters);
41        }
42
43        // Calculate current normalized weights based on time interpolation
44        let normalized_weights = Self::calculate_normalized_weights(
45            &base_weights,
46            &multipliers,
47            &state.mutable.last_update_time,
48            &state.mutable.last_interop_time,
49            &state.mutable.current_timestamp,
50        );
51
52        Ok(Self {
53            normalized_weights,
54            state,
55        })
56    }
57
58    /// Calculate normalized weights based on time interpolation
59    fn calculate_normalized_weights(
60        base_weights: &[I256],
61        multipliers: &[I256],
62        last_update_time: &U256,
63        last_interop_time: &U256,
64        current_timestamp: &U256,
65    ) -> Vec<U256> {
66        let mut multiplier_time = *current_timestamp;
67
68        if current_timestamp >= last_interop_time {
69            multiplier_time = *last_interop_time;
70        }
71
72        let time_since_last_update = multiplier_time - last_update_time;
73
74        let mut normalized_weights = Vec::with_capacity(base_weights.len());
75
76        for i in 0..base_weights.len() {
77            let normalized_weight = calculate_block_normalised_weight(
78                &base_weights[i],
79                &multipliers[i],
80                &time_since_last_update,
81            );
82            normalized_weights.push(normalized_weight);
83        }
84
85        normalized_weights
86    }
87
88    /// Get normalized weights for a specific token pair
89    fn get_normalized_weight_pair(
90        &self,
91        index_in: usize,
92        index_out: usize,
93    ) -> Result<(U256, U256), PoolError> {
94        if index_in >= self.normalized_weights.len() || index_out >= self.normalized_weights.len() {
95            return Err(PoolError::InvalidTokenIndex);
96        }
97
98        let token_in_weight = self.normalized_weights[index_in];
99        let token_out_weight = self.normalized_weights[index_out];
100
101        Ok((token_in_weight, token_out_weight))
102    }
103
104    /// Check if trade size exceeds max trade size ratio
105    fn check_max_trade_size(
106        &self,
107        amount_scaled_18: &U256,
108        balance_scaled_18: &U256,
109    ) -> Result<(), PoolError> {
110        let max_amount = mul_down_fixed(
111            balance_scaled_18,
112            &self.state.immutable.max_trade_size_ratio,
113        )
114        .unwrap_or(U256::ZERO);
115
116        if amount_scaled_18 > &max_amount {
117            return Err(PoolError::InvalidSwapParameters);
118        }
119
120        Ok(())
121    }
122}
123
124impl PoolBase for QuantAmmPool {
125    fn on_swap(&self, swap_params: &SwapParams) -> Result<U256, PoolError> {
126        let token_in_index = swap_params.token_in_index;
127        let token_out_index = swap_params.token_out_index;
128
129        if token_in_index >= self.normalized_weights.len()
130            || token_out_index >= self.normalized_weights.len()
131        {
132            return Err(PoolError::InvalidTokenIndex);
133        }
134
135        let balance_in = &swap_params.balances_live_scaled_18[token_in_index];
136        let balance_out = &swap_params.balances_live_scaled_18[token_out_index];
137        let amount_scaled_18 = &swap_params.amount_scaled_18;
138
139        let (weight_in, weight_out) =
140            self.get_normalized_weight_pair(token_in_index, token_out_index)?;
141
142        match swap_params.swap_kind {
143            crate::common::types::SwapKind::GivenIn => {
144                // Check max trade size ratio for input
145                self.check_max_trade_size(amount_scaled_18, balance_in)?;
146
147                let amount_out_scaled_18 = compute_out_given_exact_in(
148                    balance_in,
149                    &weight_in,
150                    balance_out,
151                    &weight_out,
152                    amount_scaled_18,
153                )?;
154
155                // Check max trade size ratio for output
156                self.check_max_trade_size(&amount_out_scaled_18, balance_out)?;
157
158                Ok(amount_out_scaled_18)
159            }
160            crate::common::types::SwapKind::GivenOut => {
161                // Check max trade size ratio for output
162                self.check_max_trade_size(amount_scaled_18, balance_out)?;
163
164                let amount_in_scaled_18 = compute_in_given_exact_out(
165                    balance_in,
166                    &weight_in,
167                    balance_out,
168                    &weight_out,
169                    amount_scaled_18,
170                )?;
171
172                // Check max trade size ratio for input
173                self.check_max_trade_size(&amount_in_scaled_18, balance_in)?;
174
175                Ok(amount_in_scaled_18)
176            }
177        }
178    }
179
180    fn compute_invariant(
181        &self,
182        balances_live_scaled_18: &[U256],
183        rounding: Rounding,
184    ) -> Result<U256, PoolError> {
185        match rounding {
186            Rounding::RoundDown => {
187                compute_invariant_down(&self.normalized_weights, balances_live_scaled_18)
188            }
189            Rounding::RoundUp => {
190                compute_invariant_up(&self.normalized_weights, balances_live_scaled_18)
191            }
192        }
193    }
194
195    fn compute_balance(
196        &self,
197        balances_live_scaled_18: &[U256],
198        token_in_index: usize,
199        invariant_ratio: &U256,
200    ) -> Result<U256, PoolError> {
201        if token_in_index >= balances_live_scaled_18.len()
202            || token_in_index >= self.normalized_weights.len()
203        {
204            return Err(PoolError::InvalidTokenIndex);
205        }
206
207        let current_balance = &balances_live_scaled_18[token_in_index];
208        let weight = &self.normalized_weights[token_in_index];
209
210        // Calculate the new balance based on the invariant ratio
211        compute_balance_out_given_invariant(current_balance, weight, invariant_ratio)
212    }
213
214    fn get_maximum_invariant_ratio(&self) -> U256 {
215        MAX_INVARIANT_RATIO
216    }
217
218    fn get_minimum_invariant_ratio(&self) -> U256 {
219        MIN_INVARIANT_RATIO
220    }
221}
222
223impl From<QuantAmmState> for QuantAmmPool {
224    fn from(quant_amm_state: QuantAmmState) -> Self {
225        Self::new(quant_amm_state).expect("Failed to create QuantAmmPool from state")
226    }
227}