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 num_bigint::BigInt;
9use num_traits::Zero;
10
11/// Result of add liquidity unbalanced operation
12#[derive(Debug, Clone)]
13pub struct AddLiquidityUnbalancedResult {
14    pub bpt_amount_out: BigInt,
15    pub swap_fee_amounts: Vec<BigInt>,
16}
17
18/// Result of add liquidity single token exact out operation
19#[derive(Debug, Clone)]
20pub struct AddLiquiditySingleTokenExactOutResult {
21    pub amount_in_with_fee: BigInt,
22    pub swap_fee_amounts: Vec<BigInt>,
23}
24
25/// Result of remove liquidity single token exact in operation
26#[derive(Debug, Clone)]
27pub struct RemoveLiquiditySingleTokenExactInResult {
28    pub amount_out_with_fee: BigInt,
29    pub swap_fee_amounts: Vec<BigInt>,
30}
31
32/// Result of remove liquidity single token exact out operation
33#[derive(Debug, Clone)]
34pub struct RemoveLiquiditySingleTokenExactOutResult {
35    pub bpt_amount_in: BigInt,
36    pub swap_fee_amounts: Vec<BigInt>,
37}
38
39/// Compute add liquidity for unbalanced amounts
40#[allow(clippy::type_complexity)]
41pub fn compute_add_liquidity_unbalanced(
42    current_balances: &[BigInt],
43    exact_amounts: &[BigInt],
44    total_supply: &BigInt,
45    swap_fee_percentage: &BigInt,
46    max_invariant_ratio: &BigInt,
47    compute_invariant: &dyn Fn(&[BigInt], Rounding) -> Result<BigInt, PoolError>,
48) -> Result<AddLiquidityUnbalancedResult, PoolError> {
49    let num_tokens = current_balances.len();
50
51    // Create new balances with added amounts
52    let mut new_balances = vec![BigInt::zero(); num_tokens];
53    let mut swap_fee_amounts = vec![BigInt::zero(); num_tokens];
54
55    // Loop through each token, updating the balance with the added amount
56    for index in 0..current_balances.len() {
57        new_balances[index] = &current_balances[index] + &exact_amounts[index] - BigInt::from(1);
58    }
59
60    // Calculate current and new invariants
61    let current_invariant = compute_invariant(current_balances, Rounding::RoundUp)?;
62    let new_invariant = compute_invariant(&new_balances, Rounding::RoundDown)?;
63
64    // Calculate invariant ratio
65    let invariant_ratio = div_down_fixed(&new_invariant, &current_invariant)?;
66
67    // Check invariant ratio bounds
68    if &invariant_ratio > max_invariant_ratio {
69        return Err(PoolError::MathOverflow);
70    }
71
72    // Apply fees to non-proportional amounts
73    for index in 0..current_balances.len() {
74        let proportional_token_balance =
75            mul_down_fixed(&invariant_ratio, &current_balances[index])?;
76        if new_balances[index] > proportional_token_balance {
77            let taxable_amount = &new_balances[index] - &proportional_token_balance;
78            let fee_amount = mul_up_fixed(&taxable_amount, swap_fee_percentage)?;
79            swap_fee_amounts[index] = fee_amount.clone();
80            new_balances[index] = &new_balances[index] - &fee_amount;
81        }
82    }
83
84    // Calculate invariant with fees applied
85    let invariant_with_fees_applied = compute_invariant(&new_balances, Rounding::RoundDown)?;
86
87    // Calculate BPT amount out
88    let bpt_amount_out =
89        (total_supply * (&invariant_with_fees_applied - &current_invariant)) / &current_invariant;
90
91    Ok(AddLiquidityUnbalancedResult {
92        bpt_amount_out,
93        swap_fee_amounts,
94    })
95}
96
97/// Compute add liquidity for single token exact out
98#[allow(clippy::type_complexity)]
99pub fn compute_add_liquidity_single_token_exact_out(
100    current_balances: &[BigInt],
101    token_in_index: usize,
102    exact_bpt_amount_out: &BigInt,
103    total_supply: &BigInt,
104    swap_fee_percentage: &BigInt,
105    max_invariant_ratio: &BigInt,
106    compute_balance: &dyn Fn(&[BigInt], usize, &BigInt) -> Result<BigInt, PoolError>,
107) -> Result<AddLiquiditySingleTokenExactOutResult, PoolError> {
108    let new_supply = exact_bpt_amount_out + total_supply;
109    let invariant_ratio = div_up_fixed(&new_supply, total_supply)?;
110
111    // Check invariant ratio bounds
112    if &invariant_ratio > max_invariant_ratio {
113        return Err(PoolError::MathOverflow);
114    }
115
116    // Calculate new balance needed
117    let new_balance = compute_balance(current_balances, token_in_index, &invariant_ratio)?;
118    let amount_in = &new_balance - &current_balances[token_in_index];
119
120    // Calculate non-taxable balance
121    let non_taxable_balance = div_down_fixed(
122        &mul_down_fixed(&new_supply, &current_balances[token_in_index])?,
123        total_supply,
124    )?;
125
126    let taxable_amount = &amount_in + &current_balances[token_in_index] - &non_taxable_balance;
127
128    // Calculate fee
129    let fee =
130        div_up_fixed(&taxable_amount, &complement_fixed(swap_fee_percentage)?)? - &taxable_amount;
131
132    // Create swap fees array
133    let mut swap_fee_amounts = vec![BigInt::zero(); current_balances.len()];
134    swap_fee_amounts[token_in_index] = fee.clone();
135
136    let amount_in_with_fee = &amount_in + &fee;
137    Ok(AddLiquiditySingleTokenExactOutResult {
138        amount_in_with_fee,
139        swap_fee_amounts,
140    })
141}
142
143/// Compute proportional amounts out for remove liquidity
144pub fn compute_proportional_amounts_out(
145    balances: &[BigInt],
146    bpt_total_supply: &BigInt,
147    bpt_amount_in: &BigInt,
148) -> Result<Vec<BigInt>, PoolError> {
149    let mut amounts_out = Vec::with_capacity(balances.len());
150
151    for balance in balances {
152        let amount_out = (balance * bpt_amount_in) / bpt_total_supply;
153        amounts_out.push(amount_out);
154    }
155
156    Ok(amounts_out)
157}
158
159/// Compute remove liquidity single token exact in
160#[allow(clippy::type_complexity)]
161pub fn compute_remove_liquidity_single_token_exact_in(
162    current_balances: &[BigInt],
163    token_out_index: usize,
164    exact_bpt_amount_in: &BigInt,
165    total_supply: &BigInt,
166    swap_fee_percentage: &BigInt,
167    min_invariant_ratio: &BigInt,
168    compute_balance: &dyn Fn(&[BigInt], usize, &BigInt) -> Result<BigInt, PoolError>,
169) -> Result<RemoveLiquiditySingleTokenExactInResult, PoolError> {
170    // Calculate new supply accounting for burning exact_bpt_amount_in
171    let new_supply = total_supply - exact_bpt_amount_in;
172
173    let invariant_ratio = div_up_fixed(&new_supply, total_supply)?;
174
175    // Check invariant ratio bounds
176    if &invariant_ratio < min_invariant_ratio {
177        return Err(PoolError::MathOverflow);
178    }
179
180    // Calculate the new balance of the output token after the BPT burn
181    let new_balance = compute_balance(current_balances, token_out_index, &invariant_ratio)?;
182
183    // Compute the amount to be withdrawn from the pool
184    let amount_out = &current_balances[token_out_index] - &new_balance;
185
186    let new_balance_before_tax = mul_div_up_fixed(
187        &new_supply,
188        &current_balances[token_out_index],
189        total_supply,
190    )?;
191
192    // Compute the taxable amount: the difference between the non-taxable balance and actual withdrawal
193    let taxable_amount = &new_balance_before_tax - &new_balance;
194
195    // Calculate the swap fee on the taxable amount
196    let fee = mul_up_fixed(&taxable_amount, swap_fee_percentage)?;
197
198    // Create swap fees array
199    let mut swap_fee_amounts = vec![BigInt::zero(); current_balances.len()];
200    swap_fee_amounts[token_out_index] = fee.clone();
201
202    // Return the net amount after subtracting the fee
203    let amount_out_with_fee = &amount_out - &fee;
204    Ok(RemoveLiquiditySingleTokenExactInResult {
205        amount_out_with_fee,
206        swap_fee_amounts,
207    })
208}
209
210/// Compute remove liquidity single token exact out
211#[allow(clippy::type_complexity)]
212pub fn compute_remove_liquidity_single_token_exact_out(
213    current_balances: &[BigInt],
214    token_out_index: usize,
215    exact_amount_out: &BigInt,
216    total_supply: &BigInt,
217    swap_fee_percentage: &BigInt,
218    min_invariant_ratio: &BigInt,
219    compute_invariant: &dyn Fn(&[BigInt], Rounding) -> Result<BigInt, PoolError>,
220) -> Result<RemoveLiquiditySingleTokenExactOutResult, PoolError> {
221    let num_tokens = current_balances.len();
222
223    // Create new balances array
224    let mut new_balances = vec![BigInt::zero(); num_tokens];
225
226    // Copy current_balances to new_balances
227    for index in 0..current_balances.len() {
228        new_balances[index] = &current_balances[index] - BigInt::from(1);
229    }
230
231    // Update the balance of token_out_index with exact_amount_out
232    new_balances[token_out_index] = &new_balances[token_out_index] - exact_amount_out;
233
234    // Calculate the invariant using the current balances
235    let current_invariant = compute_invariant(current_balances, Rounding::RoundUp)?;
236
237    let invariant_ratio = div_up_fixed(
238        &compute_invariant(&new_balances, Rounding::RoundUp)?,
239        &current_invariant,
240    )?;
241
242    // Check invariant ratio bounds
243    if &invariant_ratio < min_invariant_ratio {
244        return Err(PoolError::MathOverflow);
245    }
246
247    // Taxable amount is proportional to invariant ratio
248    let taxable_amount = &mul_up_fixed(&invariant_ratio, &current_balances[token_out_index])?
249        - &new_balances[token_out_index];
250
251    let fee =
252        div_up_fixed(&taxable_amount, &complement_fixed(swap_fee_percentage)?)? - &taxable_amount;
253
254    // Update new balances array with a fee
255    new_balances[token_out_index] = &new_balances[token_out_index] - &fee;
256
257    // Calculate the new invariant with fees applied
258    let invariant_with_fees_applied = compute_invariant(&new_balances, Rounding::RoundDown)?;
259
260    // Create swap fees array
261    let mut swap_fee_amounts = vec![BigInt::zero(); num_tokens];
262    swap_fee_amounts[token_out_index] = fee;
263
264    // Calculate the amount of BPT to burn
265    let bpt_amount_in = mul_div_up_fixed(
266        total_supply,
267        &(&current_invariant - &invariant_with_fees_applied),
268        &current_invariant,
269    )?;
270
271    Ok(RemoveLiquiditySingleTokenExactOutResult {
272        bpt_amount_in,
273        swap_fee_amounts,
274    })
275}