balancer_maths_rust/pools/quantamm/
quantamm_pool.rs1use 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
15pub struct QuantAmmPool {
17 normalized_weights: Vec<U256>,
19 state: QuantAmmState,
21}
22
23impl QuantAmmPool {
24 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 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 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 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 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 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 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 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 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 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}