Skip to main content

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