balancer_maths_rust/vault/
base_pool_math.rs1use crate::common::errors::PoolError;
4use crate::common::maths::{
5 complement_fixed, div_down_fixed, div_up_fixed, mul_div_up_fixed, mul_down_fixed, mul_up_fixed,
6};
7use crate::common::types::Rounding;
8use alloy_primitives::U256;
9
10#[derive(Debug, Clone)]
12pub struct AddLiquidityUnbalancedResult {
13 pub bpt_amount_out: U256,
14 pub swap_fee_amounts: Vec<U256>,
15}
16
17#[derive(Debug, Clone)]
19pub struct AddLiquiditySingleTokenExactOutResult {
20 pub amount_in_with_fee: U256,
21 pub swap_fee_amounts: Vec<U256>,
22}
23
24#[derive(Debug, Clone)]
26pub struct RemoveLiquiditySingleTokenExactInResult {
27 pub amount_out_with_fee: U256,
28 pub swap_fee_amounts: Vec<U256>,
29}
30
31#[derive(Debug, Clone)]
33pub struct RemoveLiquiditySingleTokenExactOutResult {
34 pub bpt_amount_in: U256,
35 pub swap_fee_amounts: Vec<U256>,
36}
37
38#[allow(clippy::type_complexity)]
40pub fn compute_add_liquidity_unbalanced(
41 current_balances: &[U256],
42 exact_amounts: &[U256],
43 total_supply: &U256,
44 swap_fee_percentage: &U256,
45 max_invariant_ratio: &U256,
46 compute_invariant: &dyn Fn(&[U256], Rounding) -> Result<U256, PoolError>,
47) -> Result<AddLiquidityUnbalancedResult, PoolError> {
48 let num_tokens = current_balances.len();
49
50 let mut new_balances = vec![U256::ZERO; num_tokens];
52 let mut swap_fee_amounts = vec![U256::ZERO; num_tokens];
53
54 for index in 0..current_balances.len() {
56 new_balances[index] = current_balances[index] + exact_amounts[index] - U256::ONE;
57 }
58
59 let current_invariant = compute_invariant(current_balances, Rounding::RoundUp)?;
61 let new_invariant = compute_invariant(&new_balances, Rounding::RoundDown)?;
62
63 let invariant_ratio = div_down_fixed(&new_invariant, ¤t_invariant)?;
65
66 if &invariant_ratio > max_invariant_ratio {
68 return Err(PoolError::MathOverflow);
69 }
70
71 for index in 0..current_balances.len() {
73 let proportional_token_balance =
74 mul_down_fixed(&invariant_ratio, ¤t_balances[index])?;
75 if new_balances[index] > proportional_token_balance {
76 let taxable_amount = new_balances[index] - proportional_token_balance;
77 let fee_amount = mul_up_fixed(&taxable_amount, swap_fee_percentage)?;
78 swap_fee_amounts[index] = fee_amount;
79 new_balances[index] -= fee_amount;
80 }
81 }
82
83 let invariant_with_fees_applied = compute_invariant(&new_balances, Rounding::RoundDown)?;
85
86 let bpt_amount_out =
88 (total_supply * (invariant_with_fees_applied - current_invariant)) / current_invariant;
89
90 Ok(AddLiquidityUnbalancedResult {
91 bpt_amount_out,
92 swap_fee_amounts,
93 })
94}
95
96#[allow(clippy::type_complexity)]
98pub fn compute_add_liquidity_single_token_exact_out(
99 current_balances: &[U256],
100 token_in_index: usize,
101 exact_bpt_amount_out: &U256,
102 total_supply: &U256,
103 swap_fee_percentage: &U256,
104 max_invariant_ratio: &U256,
105 compute_balance: &dyn Fn(&[U256], usize, &U256) -> Result<U256, PoolError>,
106) -> Result<AddLiquiditySingleTokenExactOutResult, PoolError> {
107 let new_supply = exact_bpt_amount_out + total_supply;
108 let invariant_ratio = div_up_fixed(&new_supply, total_supply)?;
109
110 if &invariant_ratio > max_invariant_ratio {
112 return Err(PoolError::MathOverflow);
113 }
114
115 let new_balance = compute_balance(current_balances, token_in_index, &invariant_ratio)?;
117 let amount_in = new_balance - current_balances[token_in_index];
118
119 let non_taxable_balance = div_down_fixed(
121 &mul_down_fixed(&new_supply, ¤t_balances[token_in_index])?,
122 total_supply,
123 )?;
124
125 let taxable_amount = amount_in + current_balances[token_in_index] - non_taxable_balance;
126
127 let fee =
129 div_up_fixed(&taxable_amount, &complement_fixed(swap_fee_percentage)?)? - taxable_amount;
130
131 let mut swap_fee_amounts = vec![U256::ZERO; current_balances.len()];
133 swap_fee_amounts[token_in_index] = fee;
134
135 let amount_in_with_fee = amount_in + fee;
136 Ok(AddLiquiditySingleTokenExactOutResult {
137 amount_in_with_fee,
138 swap_fee_amounts,
139 })
140}
141
142pub fn compute_proportional_amounts_out(
144 balances: &[U256],
145 bpt_total_supply: &U256,
146 bpt_amount_in: &U256,
147) -> Result<Vec<U256>, PoolError> {
148 let mut amounts_out = Vec::with_capacity(balances.len());
149
150 for balance in balances {
151 let amount_out = (balance * bpt_amount_in) / bpt_total_supply;
152 amounts_out.push(amount_out);
153 }
154
155 Ok(amounts_out)
156}
157
158#[allow(clippy::type_complexity)]
160pub fn compute_remove_liquidity_single_token_exact_in(
161 current_balances: &[U256],
162 token_out_index: usize,
163 exact_bpt_amount_in: &U256,
164 total_supply: &U256,
165 swap_fee_percentage: &U256,
166 min_invariant_ratio: &U256,
167 compute_balance: &dyn Fn(&[U256], usize, &U256) -> Result<U256, PoolError>,
168) -> Result<RemoveLiquiditySingleTokenExactInResult, PoolError> {
169 let new_supply = total_supply - exact_bpt_amount_in;
171
172 let invariant_ratio = div_up_fixed(&new_supply, total_supply)?;
173
174 if &invariant_ratio < min_invariant_ratio {
176 return Err(PoolError::MathOverflow);
177 }
178
179 let new_balance = compute_balance(current_balances, token_out_index, &invariant_ratio)?;
181
182 let amount_out = current_balances[token_out_index] - new_balance;
184
185 let new_balance_before_tax = mul_div_up_fixed(
186 &new_supply,
187 ¤t_balances[token_out_index],
188 total_supply,
189 )?;
190
191 let taxable_amount = new_balance_before_tax - new_balance;
193
194 let fee = mul_up_fixed(&taxable_amount, swap_fee_percentage)?;
196
197 let mut swap_fee_amounts = vec![U256::ZERO; current_balances.len()];
199 swap_fee_amounts[token_out_index] = fee;
200
201 let amount_out_with_fee = amount_out - fee;
203 Ok(RemoveLiquiditySingleTokenExactInResult {
204 amount_out_with_fee,
205 swap_fee_amounts,
206 })
207}
208
209#[allow(clippy::type_complexity)]
211pub fn compute_remove_liquidity_single_token_exact_out(
212 current_balances: &[U256],
213 token_out_index: usize,
214 exact_amount_out: &U256,
215 total_supply: &U256,
216 swap_fee_percentage: &U256,
217 min_invariant_ratio: &U256,
218 compute_invariant: &dyn Fn(&[U256], Rounding) -> Result<U256, PoolError>,
219) -> Result<RemoveLiquiditySingleTokenExactOutResult, PoolError> {
220 let num_tokens = current_balances.len();
221
222 let mut new_balances = vec![U256::ZERO; num_tokens];
224
225 for index in 0..current_balances.len() {
227 new_balances[index] = current_balances[index] - U256::ONE;
228 }
229
230 new_balances[token_out_index] -= exact_amount_out;
232
233 let current_invariant = compute_invariant(current_balances, Rounding::RoundUp)?;
235
236 let invariant_ratio = div_up_fixed(
237 &compute_invariant(&new_balances, Rounding::RoundUp)?,
238 ¤t_invariant,
239 )?;
240
241 if &invariant_ratio < min_invariant_ratio {
243 return Err(PoolError::MathOverflow);
244 }
245
246 let taxable_amount = mul_up_fixed(&invariant_ratio, ¤t_balances[token_out_index])?
248 - new_balances[token_out_index];
249
250 let fee =
251 div_up_fixed(&taxable_amount, &complement_fixed(swap_fee_percentage)?)? - taxable_amount;
252
253 new_balances[token_out_index] -= fee;
255
256 let invariant_with_fees_applied = compute_invariant(&new_balances, Rounding::RoundDown)?;
258
259 let mut swap_fee_amounts = vec![U256::ZERO; num_tokens];
261 swap_fee_amounts[token_out_index] = fee;
262
263 let bpt_amount_in = mul_div_up_fixed(
265 total_supply,
266 &(current_invariant - invariant_with_fees_applied),
267 ¤t_invariant,
268 )?;
269
270 Ok(RemoveLiquiditySingleTokenExactOutResult {
271 bpt_amount_in,
272 swap_fee_amounts,
273 })
274}