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 num_bigint::BigInt;
9use num_traits::Zero;
10
11#[derive(Debug, Clone)]
13pub struct AddLiquidityUnbalancedResult {
14    pub bpt_amount_out: BigInt,
15    pub swap_fee_amounts: Vec<BigInt>,
16}
17
18#[derive(Debug, Clone)]
20pub struct AddLiquiditySingleTokenExactOutResult {
21    pub amount_in_with_fee: BigInt,
22    pub swap_fee_amounts: Vec<BigInt>,
23}
24
25#[derive(Debug, Clone)]
27pub struct RemoveLiquiditySingleTokenExactInResult {
28    pub amount_out_with_fee: BigInt,
29    pub swap_fee_amounts: Vec<BigInt>,
30}
31
32#[derive(Debug, Clone)]
34pub struct RemoveLiquiditySingleTokenExactOutResult {
35    pub bpt_amount_in: BigInt,
36    pub swap_fee_amounts: Vec<BigInt>,
37}
38
39#[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    let mut new_balances = vec![BigInt::zero(); num_tokens];
53    let mut swap_fee_amounts = vec![BigInt::zero(); num_tokens];
54
55    for index in 0..current_balances.len() {
57        new_balances[index] = ¤t_balances[index] + &exact_amounts[index] - BigInt::from(1);
58    }
59
60    let current_invariant = compute_invariant(current_balances, Rounding::RoundUp)?;
62    let new_invariant = compute_invariant(&new_balances, Rounding::RoundDown)?;
63
64    let invariant_ratio = div_down_fixed(&new_invariant, ¤t_invariant)?;
66
67    if &invariant_ratio > max_invariant_ratio {
69        return Err(PoolError::MathOverflow);
70    }
71
72    for index in 0..current_balances.len() {
74        let proportional_token_balance =
75            mul_down_fixed(&invariant_ratio, ¤t_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    let invariant_with_fees_applied = compute_invariant(&new_balances, Rounding::RoundDown)?;
86
87    let bpt_amount_out =
89        (total_supply * (&invariant_with_fees_applied - ¤t_invariant)) / ¤t_invariant;
90
91    Ok(AddLiquidityUnbalancedResult {
92        bpt_amount_out,
93        swap_fee_amounts,
94    })
95}
96
97#[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    if &invariant_ratio > max_invariant_ratio {
113        return Err(PoolError::MathOverflow);
114    }
115
116    let new_balance = compute_balance(current_balances, token_in_index, &invariant_ratio)?;
118    let amount_in = &new_balance - ¤t_balances[token_in_index];
119
120    let non_taxable_balance = div_down_fixed(
122        &mul_down_fixed(&new_supply, ¤t_balances[token_in_index])?,
123        total_supply,
124    )?;
125
126    let taxable_amount = &amount_in + ¤t_balances[token_in_index] - &non_taxable_balance;
127
128    let fee =
130        div_up_fixed(&taxable_amount, &complement_fixed(swap_fee_percentage)?)? - &taxable_amount;
131
132    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
143pub 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#[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    let new_supply = total_supply - exact_bpt_amount_in;
172
173    let invariant_ratio = div_up_fixed(&new_supply, total_supply)?;
174
175    if &invariant_ratio < min_invariant_ratio {
177        return Err(PoolError::MathOverflow);
178    }
179
180    let new_balance = compute_balance(current_balances, token_out_index, &invariant_ratio)?;
182
183    let amount_out = ¤t_balances[token_out_index] - &new_balance;
185
186    let new_balance_before_tax = mul_div_up_fixed(
187        &new_supply,
188        ¤t_balances[token_out_index],
189        total_supply,
190    )?;
191
192    let taxable_amount = &new_balance_before_tax - &new_balance;
194
195    let fee = mul_up_fixed(&taxable_amount, swap_fee_percentage)?;
197
198    let mut swap_fee_amounts = vec![BigInt::zero(); current_balances.len()];
200    swap_fee_amounts[token_out_index] = fee.clone();
201
202    let amount_out_with_fee = &amount_out - &fee;
204    Ok(RemoveLiquiditySingleTokenExactInResult {
205        amount_out_with_fee,
206        swap_fee_amounts,
207    })
208}
209
210#[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    let mut new_balances = vec![BigInt::zero(); num_tokens];
225
226    for index in 0..current_balances.len() {
228        new_balances[index] = ¤t_balances[index] - BigInt::from(1);
229    }
230
231    new_balances[token_out_index] = &new_balances[token_out_index] - exact_amount_out;
233
234    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        ¤t_invariant,
240    )?;
241
242    if &invariant_ratio < min_invariant_ratio {
244        return Err(PoolError::MathOverflow);
245    }
246
247    let taxable_amount = &mul_up_fixed(&invariant_ratio, ¤t_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    new_balances[token_out_index] = &new_balances[token_out_index] - &fee;
256
257    let invariant_with_fees_applied = compute_invariant(&new_balances, Rounding::RoundDown)?;
259
260    let mut swap_fee_amounts = vec![BigInt::zero(); num_tokens];
262    swap_fee_amounts[token_out_index] = fee;
263
264    let bpt_amount_in = mul_div_up_fixed(
266        total_supply,
267        &(¤t_invariant - &invariant_with_fees_applied),
268        ¤t_invariant,
269    )?;
270
271    Ok(RemoveLiquiditySingleTokenExactOutResult {
272        bpt_amount_in,
273        swap_fee_amounts,
274    })
275}