balancer_maths_rust/vault/
base_pool_math.rs

1//! Base pool math functions for vault operations
2
3use 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/// Result of add liquidity unbalanced operation
11#[derive(Debug, Clone)]
12pub struct AddLiquidityUnbalancedResult {
13    pub bpt_amount_out: U256,
14    pub swap_fee_amounts: Vec<U256>,
15}
16
17/// Result of add liquidity single token exact out operation
18#[derive(Debug, Clone)]
19pub struct AddLiquiditySingleTokenExactOutResult {
20    pub amount_in_with_fee: U256,
21    pub swap_fee_amounts: Vec<U256>,
22}
23
24/// Result of remove liquidity single token exact in operation
25#[derive(Debug, Clone)]
26pub struct RemoveLiquiditySingleTokenExactInResult {
27    pub amount_out_with_fee: U256,
28    pub swap_fee_amounts: Vec<U256>,
29}
30
31/// Result of remove liquidity single token exact out operation
32#[derive(Debug, Clone)]
33pub struct RemoveLiquiditySingleTokenExactOutResult {
34    pub bpt_amount_in: U256,
35    pub swap_fee_amounts: Vec<U256>,
36}
37
38/// Compute add liquidity for unbalanced amounts
39#[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    // Create new balances with added amounts
51    let mut new_balances = vec![U256::ZERO; num_tokens];
52    let mut swap_fee_amounts = vec![U256::ZERO; num_tokens];
53
54    // Loop through each token, updating the balance with the added amount
55    for index in 0..current_balances.len() {
56        new_balances[index] = current_balances[index] + exact_amounts[index] - U256::ONE;
57    }
58
59    // Calculate current and new invariants
60    let current_invariant = compute_invariant(current_balances, Rounding::RoundUp)?;
61    let new_invariant = compute_invariant(&new_balances, Rounding::RoundDown)?;
62
63    // Calculate invariant ratio
64    let invariant_ratio = div_down_fixed(&new_invariant, &current_invariant)?;
65
66    // Check invariant ratio bounds
67    if &invariant_ratio > max_invariant_ratio {
68        return Err(PoolError::MathOverflow);
69    }
70
71    // Apply fees to non-proportional amounts
72    for index in 0..current_balances.len() {
73        let proportional_token_balance =
74            mul_down_fixed(&invariant_ratio, &current_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    // Calculate invariant with fees applied
84    let invariant_with_fees_applied = compute_invariant(&new_balances, Rounding::RoundDown)?;
85
86    // Calculate BPT amount out
87    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/// Compute add liquidity for single token exact out
97#[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    // Check invariant ratio bounds
111    if &invariant_ratio > max_invariant_ratio {
112        return Err(PoolError::MathOverflow);
113    }
114
115    // Calculate new balance needed
116    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    // Calculate non-taxable balance
120    let non_taxable_balance = div_down_fixed(
121        &mul_down_fixed(&new_supply, &current_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    // Calculate fee
128    let fee =
129        div_up_fixed(&taxable_amount, &complement_fixed(swap_fee_percentage)?)? - taxable_amount;
130
131    // Create swap fees array
132    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
142/// Compute proportional amounts out for remove liquidity
143pub 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/// Compute remove liquidity single token exact in
159#[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    // Calculate new supply accounting for burning exact_bpt_amount_in
170    let new_supply = total_supply - exact_bpt_amount_in;
171
172    let invariant_ratio = div_up_fixed(&new_supply, total_supply)?;
173
174    // Check invariant ratio bounds
175    if &invariant_ratio < min_invariant_ratio {
176        return Err(PoolError::MathOverflow);
177    }
178
179    // Calculate the new balance of the output token after the BPT burn
180    let new_balance = compute_balance(current_balances, token_out_index, &invariant_ratio)?;
181
182    // Compute the amount to be withdrawn from the pool
183    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        &current_balances[token_out_index],
188        total_supply,
189    )?;
190
191    // Compute the taxable amount: the difference between the non-taxable balance and actual withdrawal
192    let taxable_amount = new_balance_before_tax - new_balance;
193
194    // Calculate the swap fee on the taxable amount
195    let fee = mul_up_fixed(&taxable_amount, swap_fee_percentage)?;
196
197    // Create swap fees array
198    let mut swap_fee_amounts = vec![U256::ZERO; current_balances.len()];
199    swap_fee_amounts[token_out_index] = fee;
200
201    // Return the net amount after subtracting the fee
202    let amount_out_with_fee = amount_out - fee;
203    Ok(RemoveLiquiditySingleTokenExactInResult {
204        amount_out_with_fee,
205        swap_fee_amounts,
206    })
207}
208
209/// Compute remove liquidity single token exact out
210#[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    // Create new balances array
223    let mut new_balances = vec![U256::ZERO; num_tokens];
224
225    // Copy current_balances to new_balances
226    for index in 0..current_balances.len() {
227        new_balances[index] = current_balances[index] - U256::ONE;
228    }
229
230    // Update the balance of token_out_index with exact_amount_out
231    new_balances[token_out_index] -= exact_amount_out;
232
233    // Calculate the invariant using the current balances
234    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        &current_invariant,
239    )?;
240
241    // Check invariant ratio bounds
242    if &invariant_ratio < min_invariant_ratio {
243        return Err(PoolError::MathOverflow);
244    }
245
246    // Taxable amount is proportional to invariant ratio
247    let taxable_amount = mul_up_fixed(&invariant_ratio, &current_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    // Update new balances array with a fee
254    new_balances[token_out_index] -= fee;
255
256    // Calculate the new invariant with fees applied
257    let invariant_with_fees_applied = compute_invariant(&new_balances, Rounding::RoundDown)?;
258
259    // Create swap fees array
260    let mut swap_fee_amounts = vec![U256::ZERO; num_tokens];
261    swap_fee_amounts[token_out_index] = fee;
262
263    // Calculate the amount of BPT to burn
264    let bpt_amount_in = mul_div_up_fixed(
265        total_supply,
266        &(current_invariant - invariant_with_fees_applied),
267        &current_invariant,
268    )?;
269
270    Ok(RemoveLiquiditySingleTokenExactOutResult {
271        bpt_amount_in,
272        swap_fee_amounts,
273    })
274}