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}