astroport_pair_stable/
math.rs

1use cosmwasm_std::{Decimal256, StdError, StdResult, Uint128, Uint64};
2
3use astroport::asset::Decimal256Ext;
4
5/// The maximum number of calculation steps for Newton's method.
6const ITERATIONS: u8 = 64;
7
8pub const MAX_AMP: u64 = 1_000_000;
9pub const MAX_AMP_CHANGE: u64 = 10;
10pub const MIN_AMP_CHANGING_TIME: u64 = 86400;
11pub const AMP_PRECISION: u64 = 100;
12/// N = 2
13pub const N_COINS: Decimal256 = Decimal256::raw(2000000000000000000);
14/// 1e-6
15pub const TOL: Decimal256 = Decimal256::raw(1000000000000);
16
17/// Computes the stableswap invariant (D).
18///
19/// * **Equation**
20///
21/// A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i))
22///
23pub fn compute_d(amp: Uint64, pools: &[Decimal256]) -> StdResult<Decimal256> {
24    let leverage = Decimal256::from_ratio(amp, AMP_PRECISION) * N_COINS;
25    let amount_a_times_coins = pools[0] * N_COINS;
26    let amount_b_times_coins = pools[1] * N_COINS;
27
28    let sum_x = pools[0].checked_add(pools[1])?; // sum(x_i), a.k.a S
29    if sum_x.is_zero() {
30        Ok(Decimal256::zero())
31    } else {
32        let mut d_previous: Decimal256;
33        let mut d: Decimal256 = sum_x;
34
35        // Newton's method to approximate D
36        for _ in 0..ITERATIONS {
37            let d_product = d.pow(3) / (amount_a_times_coins * amount_b_times_coins);
38            d_previous = d;
39            d = calculate_step(d, leverage, sum_x, d_product)?;
40            // Equality with the precision of 1e-6
41            if d.abs_diff(d_previous) <= TOL {
42                return Ok(d);
43            }
44        }
45
46        Err(StdError::generic_err(
47            "Newton method for D failed to converge",
48        ))
49    }
50}
51
52/// Helper function used to calculate the D invariant as a last step in the `compute_d` public function.
53///
54/// * **Equation**:
55///
56/// d = (leverage * sum_x + d_product * n_coins) * initial_d / ((leverage - 1) * initial_d + (n_coins + 1) * d_product)
57fn calculate_step(
58    initial_d: Decimal256,
59    leverage: Decimal256,
60    sum_x: Decimal256,
61    d_product: Decimal256,
62) -> StdResult<Decimal256> {
63    let leverage_mul = leverage.checked_mul(sum_x)?;
64    let d_p_mul = d_product.checked_mul(N_COINS)?;
65
66    let l_val = leverage_mul.checked_add(d_p_mul)?.checked_mul(initial_d)?;
67
68    let leverage_sub = initial_d.checked_mul(leverage - Decimal256::one())?;
69    let n_coins_sum = d_product.checked_mul(N_COINS.checked_add(Decimal256::one())?)?;
70
71    let r_val = leverage_sub.checked_add(n_coins_sum)?;
72
73    l_val
74        .checked_div(r_val)
75        .map_err(|e| StdError::generic_err(e.to_string()))
76}
77
78/// Compute the swap amount `y` in proportion to `x`.
79///
80/// * **Solve for y**
81///
82/// y**2 + y * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A)
83///
84/// y**2 + b*y = c
85pub(crate) fn calc_y(
86    amp: Uint64,
87    new_amount: Decimal256,
88    xp: &[Decimal256],
89    target_precision: u8,
90) -> StdResult<Uint128> {
91    let d = compute_d(amp, xp)?;
92    let leverage = Decimal256::from_ratio(amp, 1u8) * N_COINS;
93    let amp_prec = Decimal256::from_ratio(AMP_PRECISION, 1u8);
94
95    let c = d.checked_pow(3)?.checked_mul(amp_prec)?
96        / new_amount
97            .checked_mul(N_COINS * N_COINS)?
98            .checked_mul(leverage)?;
99
100    let b = new_amount.checked_add(d.checked_mul(amp_prec)? / leverage)?;
101
102    // Solve for y by approximating: y**2 + b*y = c
103    let mut y_prev;
104    let mut y = d;
105    for _ in 0..ITERATIONS {
106        y_prev = y;
107        y = y
108            .checked_pow(2)?
109            .checked_add(c)?
110            .checked_div(y.checked_mul(N_COINS)?.checked_add(b)?.checked_sub(d)?)
111            .map_err(|e| StdError::generic_err(e.to_string()))?;
112        if y.abs_diff(y_prev) <= TOL {
113            return y.to_uint128_with_precision(target_precision);
114        }
115    }
116
117    // Should definitely converge in 64 iterations.
118    Err(StdError::generic_err("y is not converging"))
119}