balancer_maths_rust/pools/stable/
stable_math.rs1use crate::common::errors::PoolError;
2use crate::common::maths::div_up;
3use alloy_primitives::U256;
4
5pub const AMP_PRECISION: u64 = 1000;
7
8pub const _MIN_INVARIANT_RATIO: u64 = 60e16 as u64; pub const _MAX_INVARIANT_RATIO: u64 = 500e16 as u64; pub fn compute_invariant(
20 amplification_parameter: &U256,
21 balances: &[U256],
22) -> Result<U256, PoolError> {
23 let total_balance: U256 = balances.iter().sum();
25 let num_tokens = balances.len() as u64;
26
27 if total_balance == U256::ZERO {
28 return Ok(U256::ZERO);
29 }
30
31 if balances.iter().any(|b| b.is_zero()) {
33 return Err(PoolError::StableZeroBalance);
34 }
35
36 let mut invariant = total_balance;
38 let amp_times_total = amplification_parameter * U256::from(num_tokens);
39
40 for _ in 0..255 {
42 let mut d_p = invariant;
43
44 for balance in balances {
45 d_p = (d_p * invariant) / (balance * U256::from(num_tokens));
46 }
47
48 let prev_invariant = invariant;
49
50 let numerator = (amp_times_total * total_balance) / U256::from(AMP_PRECISION);
51 let numerator = numerator + (d_p * U256::from(num_tokens));
52 let numerator = numerator * invariant;
53
54 let amp_minus_precision = amp_times_total - U256::from(AMP_PRECISION);
55 let denominator = (amp_minus_precision * invariant) / U256::from(AMP_PRECISION);
56 let denominator = denominator + (U256::from(num_tokens + 1) * d_p);
57
58 invariant = numerator / denominator;
59
60 if invariant > prev_invariant {
62 if invariant - prev_invariant <= U256::ONE {
63 return Ok(invariant);
64 }
65 } else if prev_invariant - invariant <= U256::ONE {
66 return Ok(invariant);
67 }
68 }
69
70 Err(PoolError::StableInvariantDidntConverge)
71}
72
73pub fn compute_out_given_exact_in(
75 amplification_parameter: &U256,
76 balances: &[U256],
77 token_index_in: usize,
78 token_index_out: usize,
79 token_amount_in: &U256,
80 invariant: &U256,
81) -> Result<U256, PoolError> {
82 let mut balances_copy = balances.to_vec();
83
84 balances_copy[token_index_in] += token_amount_in;
86
87 let final_balance_out = compute_balance(
89 amplification_parameter,
90 &balances_copy,
91 invariant,
92 token_index_out,
93 )?;
94
95 Ok(balances_copy[token_index_out] - final_balance_out - U256::ONE)
97}
98
99pub fn compute_in_given_exact_out(
101 amplification_parameter: &U256,
102 balances: &[U256],
103 token_index_in: usize,
104 token_index_out: usize,
105 token_amount_out: &U256,
106 invariant: &U256,
107) -> Result<U256, PoolError> {
108 if &balances[token_index_out] <= token_amount_out {
109 return Err(PoolError::TokenAmountOutIsGreaterThanBalance);
110 }
111
112 let mut balances_copy = balances.to_vec();
113
114 balances_copy[token_index_out] -= token_amount_out;
116
117 let final_balance_in = compute_balance(
119 amplification_parameter,
120 &balances_copy,
121 invariant,
122 token_index_in,
123 )?;
124
125 Ok(final_balance_in - balances_copy[token_index_in] + U256::ONE)
127}
128
129pub fn compute_balance(
134 amplification_parameter: &U256,
135 balances: &[U256],
136 invariant: &U256,
137 token_index: usize,
138) -> Result<U256, PoolError> {
139 if invariant.is_zero() {
140 return Err(PoolError::ZeroInvariant);
141 }
142 if balances.iter().any(|b| b.is_zero()) {
143 return Err(PoolError::StableZeroBalance);
144 }
145
146 let num_tokens = balances.len() as u64;
147 let amp_times_total = amplification_parameter * U256::from(num_tokens);
148
149 let mut sum = balances[0];
151 let mut p_d = balances[0] * U256::from(num_tokens);
152
153 for balance in balances.iter().skip(1) {
154 p_d = (p_d * balance * U256::from(num_tokens)) / invariant;
155 sum += balance;
156 }
157
158 sum -= &balances[token_index];
159
160 let inv2 = invariant * invariant;
162 let c = div_up(
163 &(inv2 * U256::from(AMP_PRECISION)),
164 &(amp_times_total * p_d),
165 )? * balances[token_index];
166
167 let b = sum + (invariant * U256::from(AMP_PRECISION)) / amp_times_total;
168
169 let mut token_balance = div_up(&(inv2 + c), &(invariant + b))?;
171
172 for _i in 0..255 {
174 let prev_token_balance = token_balance;
175 token_balance = div_up(
176 &(token_balance * token_balance + c),
177 &(token_balance * U256::from(2) + b - invariant),
178 )?;
179
180 if token_balance > prev_token_balance {
182 if token_balance - prev_token_balance <= U256::ONE {
183 return Ok(token_balance);
184 }
185 } else if prev_token_balance - token_balance <= U256::ONE {
186 return Ok(token_balance);
187 }
188 }
189
190 Err(PoolError::StableInvariantDidntConverge)
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn compute_invariant_all_zero_balances_returns_zero() {
199 let result = compute_invariant(&U256::from(1000u64), &[U256::ZERO, U256::ZERO]);
200 assert_eq!(result, Ok(U256::ZERO));
201 }
202
203 #[test]
204 fn compute_invariant_partial_zero_balance_errors() {
205 let balances = [
207 U256::from(1_000_000u64),
208 U256::ZERO,
209 U256::from(1_000_000u64),
210 ];
211 let result = compute_invariant(&U256::from(1000u64), &balances);
212 assert!(matches!(result, Err(PoolError::StableZeroBalance)));
213 }
214
215 #[test]
216 fn compute_balance_zero_balance_errors() {
217 let balances = [
218 U256::from(1_000_000u64),
219 U256::ZERO,
220 U256::from(1_000_000u64),
221 ];
222 let result = compute_balance(
223 &U256::from(1000u64),
224 &balances,
225 &U256::from(2_000_000u64),
226 0,
227 );
228 assert!(matches!(result, Err(PoolError::StableZeroBalance)));
229 }
230
231 #[test]
232 fn compute_balance_zero_invariant_errors() {
233 let balances = [U256::from(1_000_000u64), U256::from(1_000_000u64)];
234 let result = compute_balance(&U256::from(1000u64), &balances, &U256::ZERO, 0);
235 assert!(matches!(result, Err(PoolError::ZeroInvariant)));
236 }
237}