balancer_maths_rust/common/
utils.rs

1//! Common utility functions for Balancer pools
2
3use crate::common::errors::PoolError;
4use crate::common::maths::{div_down_fixed, div_up_fixed, mul_down_fixed, mul_up_fixed};
5use crate::common::types::PoolState;
6use num_bigint::BigInt;
7use num_traits::Zero;
8
9/// Maximum uint256 value
10pub const MAX_UINT256: &str =
11    "115792089237316195423570985008687907853269984665640564039457584007913129639935";
12
13/// Find case insensitive index in list
14pub fn find_case_insensitive_index_in_list(strings: &[String], target: &str) -> Option<usize> {
15    let lowercase_target = target.to_lowercase();
16
17    for (index, string) in strings.iter().enumerate() {
18        if string.to_lowercase() == lowercase_target {
19            return Some(index);
20        }
21    }
22
23    None
24}
25
26/// Convert to scaled 18 with rate applied, rounding down
27pub fn to_scaled_18_apply_rate_round_down(
28    amount: &BigInt,
29    scaling_factor: &BigInt,
30    rate: &BigInt,
31) -> Result<BigInt, PoolError> {
32    mul_down_fixed(&(amount * scaling_factor), rate)
33}
34
35/// Convert to scaled 18 with rate applied, rounding up
36pub fn to_scaled_18_apply_rate_round_up(
37    amount: &BigInt,
38    scaling_factor: &BigInt,
39    rate: &BigInt,
40) -> Result<BigInt, PoolError> {
41    mul_up_fixed(&(amount * scaling_factor), rate)
42}
43
44/// Convert scaled 18 amount back to raw amount, rounding down
45/// Reverses the `scalingFactor` and `tokenRate` applied to `amount`,
46/// resulting in a smaller or equal value depending on whether it needed scaling/rate adjustment or not.
47/// The result is rounded down.
48pub fn to_raw_undo_rate_round_down(
49    amount: &BigInt,
50    scaling_factor: &BigInt,
51    token_rate: &BigInt,
52) -> Result<BigInt, PoolError> {
53    // Do division last. Scaling factor is not a FP18, but a FP18 normalized by FP(1).
54    // `scalingFactor * tokenRate` is a precise FP18, so there is no rounding direction here.
55    let denominator = scaling_factor * token_rate;
56    let result = div_down_fixed(amount, &denominator)?;
57    Ok(result)
58}
59
60/// Convert scaled 18 amount back to raw amount, rounding up
61/// Reverses the `scalingFactor` and `tokenRate` applied to `amount`,
62/// resulting in a smaller or equal value depending on whether it needed scaling/rate adjustment or not.
63/// The result is rounded up.
64pub fn to_raw_undo_rate_round_up(
65    amount: &BigInt,
66    scaling_factor: &BigInt,
67    token_rate: &BigInt,
68) -> Result<BigInt, PoolError> {
69    // Do division last. Scaling factor is not a FP18, but a FP18 normalized by FP(1).
70    // `scalingFactor * tokenRate` is a precise FP18, so there is no rounding direction here.
71    div_up_fixed(amount, &(scaling_factor * token_rate))
72}
73
74/// Check if two addresses are the same (case insensitive)
75pub fn is_same_address(address_one: &str, address_two: &str) -> bool {
76    address_one.to_lowercase() == address_two.to_lowercase()
77}
78
79/// Copy amounts to scaled 18 with rate applied, rounding down
80pub fn copy_to_scaled18_apply_rate_round_down_array(
81    amounts: &[BigInt],
82    scaling_factors: &[BigInt],
83    token_rates: &[BigInt],
84) -> Result<Vec<BigInt>, PoolError> {
85    let mut scaled_amounts = Vec::with_capacity(amounts.len());
86
87    for (i, amount) in amounts.iter().enumerate() {
88        let scaled_amount =
89            to_scaled_18_apply_rate_round_down(amount, &scaling_factors[i], &token_rates[i])?;
90        scaled_amounts.push(scaled_amount);
91    }
92
93    Ok(scaled_amounts)
94}
95
96/// Copy amounts to scaled 18 with rate applied, rounding up
97pub fn copy_to_scaled18_apply_rate_round_up_array(
98    amounts: &[BigInt],
99    scaling_factors: &[BigInt],
100    token_rates: &[BigInt],
101) -> Result<Vec<BigInt>, PoolError> {
102    let mut scaled_amounts = Vec::with_capacity(amounts.len());
103
104    for (i, amount) in amounts.iter().enumerate() {
105        let scaled_amount =
106            to_scaled_18_apply_rate_round_up(amount, &scaling_factors[i], &token_rates[i])?;
107        scaled_amounts.push(scaled_amount);
108    }
109
110    Ok(scaled_amounts)
111}
112
113/// Compute and charge aggregate swap fees
114pub fn compute_and_charge_aggregate_swap_fees(
115    swap_fee_amount_scaled18: &BigInt,
116    aggregate_swap_fee_percentage: &BigInt,
117    decimal_scaling_factors: &[BigInt],
118    token_rates: &[BigInt],
119    index: usize,
120) -> Result<BigInt, PoolError> {
121    if swap_fee_amount_scaled18 > &BigInt::zero() && aggregate_swap_fee_percentage > &BigInt::zero()
122    {
123        // The total swap fee does not go into the pool; amountIn does, and the raw fee at this point does not
124        // modify it. Given that all of the fee may belong to the pool creator (i.e. outside pool balances),
125        // we round down to protect the invariant.
126        let total_swap_fee_amount_raw = to_raw_undo_rate_round_down(
127            swap_fee_amount_scaled18,
128            &decimal_scaling_factors[index],
129            &token_rates[index],
130        )?;
131
132        Ok(mul_down_fixed(
133            &total_swap_fee_amount_raw,
134            aggregate_swap_fee_percentage,
135        )?)
136    } else {
137        Ok(BigInt::zero())
138    }
139}
140
141/// Get single input index from amounts
142pub fn get_single_input_index(max_amounts_in: &[BigInt]) -> Result<usize, PoolError> {
143    let length = max_amounts_in.len();
144    let mut input_index = length;
145
146    for (i, amount) in max_amounts_in.iter().enumerate() {
147        if amount != &BigInt::zero() {
148            if input_index != length {
149                return Err(PoolError::Custom(
150                    "Multiple non-zero inputs for single token add".to_string(),
151                ));
152            }
153            input_index = i;
154        }
155    }
156
157    if input_index >= length {
158        return Err(PoolError::Custom(
159            "All zero inputs for single token add".to_string(),
160        ));
161    }
162
163    Ok(input_index)
164}
165
166/// Require unbalanced liquidity to be enabled
167pub fn require_unbalanced_liquidity_enabled(pool_state: &PoolState) -> Result<(), PoolError> {
168    if !pool_state.base().supports_unbalanced_liquidity {
169        return Err(PoolError::Custom(
170            "DoesNotSupportUnbalancedLiquidity".to_string(),
171        ));
172    }
173    Ok(())
174}