Skip to content

Commit 3d69b05

Browse files
committed
resolve precision loss issue
1 parent f3387ec commit 3d69b05

File tree

1 file changed

+68
-26
lines changed

1 file changed

+68
-26
lines changed

packages/transmuter_math/src/rebalancing_incentive.rs

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ use cosmwasm_std::{ensure, Decimal, Decimal256, SignedDecimal256, Uint128};
22

33
use crate::TransmuterMathError;
44

5+
6+
7+
58
/// Calculating impact factor component
69
///
710
/// Considering change of balance of asset $i$, fee/incentive impact factor component $\gamma_i$ is
@@ -24,7 +27,9 @@ use crate::TransmuterMathError;
2427
/// \left(\frac{b - \phi_u}{\delta - \phi_u}\right)^2 & \text{if } \phi_u \lt b \leq \delta
2528
/// \end{cases}
2629
/// $$
27-
pub fn calculate_cumulative_impact_factor_component(
30+
///
31+
/// This function returns √C(b) to delay precision loss handling
32+
pub fn calculate_sqrt_cumulative_impact_factor_component(
2833
normalized_balance: Decimal,
2934
ideal_balance_lower_bound: Decimal,
3035
ideal_balance_upper_bound: Decimal,
@@ -41,15 +46,13 @@ pub fn calculate_cumulative_impact_factor_component(
4146
ideal_balance_lower_bound // phi_l
4247
.checked_sub(normalized_balance)? // - b
4348
.checked_div(ideal_balance_lower_bound)? // / phi_l
44-
.checked_pow(2)? // ^2
4549
} else if normalized_balance > ideal_balance_upper_bound {
4650
normalized_balance // b
4751
.checked_sub(ideal_balance_upper_bound)? // - phi_u
4852
// delta - phi_u will never be 0 as this case requires b > phi_u,
4953
// delta - phi_u = 0 then delta = phi_u
5054
// since b > delta is restricted by limiter, and delta <= phi_u, this will never happen
5155
.checked_div(upper_limit.checked_sub(ideal_balance_upper_bound)?)? // / delta - phi_u
52-
.checked_pow(2)? // ^2
5356
} else {
5457
// within ideal balance
5558
Decimal::zero()
@@ -120,26 +123,39 @@ impl ImpactFactorParamGroup {
120123
}
121124

122125
fn calculate_impact_factor_component(&self) -> Result<SignedDecimal256, TransmuterMathError> {
123-
// C(b)
124-
let c_b = SignedDecimal256::from(calculate_cumulative_impact_factor_component(
126+
// C(b)
127+
let sqrt_c_b = SignedDecimal256::from(calculate_sqrt_cumulative_impact_factor_component(
125128
self.prev_normalized_balance,
126129
self.ideal_balance_lower_bound,
127130
self.ideal_balance_upper_bound,
128131
self.upper_limit,
129132
)?);
130133

131-
// C(b')
132-
let c_b_prime = SignedDecimal256::from(calculate_cumulative_impact_factor_component(
134+
// C(b')
135+
let sqrt_c_b_prime = SignedDecimal256::from(calculate_sqrt_cumulative_impact_factor_component(
133136
self.update_normalized_balance,
134137
self.ideal_balance_lower_bound,
135138
self.ideal_balance_upper_bound,
136139
self.upper_limit,
137140
)?);
138141

142+
139143
// \gamma_i = C(b') - C(b)
140-
c_b_prime
141-
.checked_sub(c_b)
142-
.map_err(TransmuterMathError::OverflowError)
144+
let c_b_prime = sqrt_c_b_prime.checked_pow(2)?;
145+
let c_b = sqrt_c_b.checked_pow(2)?;
146+
let gamma_i = c_b_prime.checked_sub(c_b)?;
147+
148+
// gamma_i = 0 might be due to precision loss after squaring
149+
// if C(b') - C(b) > 0 is counted as fee factor
150+
// round to most precise positive number representable in SignedDecimal256
151+
//
152+
// C(b') - C(b) < 0 case is not handled here, as it will be counted as incentive factor
153+
// keep it as 0 to prevent overincentive
154+
if gamma_i.is_zero() && sqrt_c_b_prime > sqrt_c_b {
155+
return Ok(SignedDecimal256::raw(1));
156+
}
157+
158+
Ok(gamma_i)
143159
}
144160
}
145161

@@ -460,12 +476,12 @@ mod tests {
460476
#[case] upper_limit: Decimal,
461477
#[case] expected: Result<Decimal, TransmuterMathError>,
462478
) {
463-
let actual = calculate_cumulative_impact_factor_component(
479+
let actual = calculate_sqrt_cumulative_impact_factor_component(
464480
normalized_balance,
465481
ideal_balance_lower_bound,
466482
ideal_balance_upper_bound,
467483
upper_limit,
468-
);
484+
).map(|x| x.pow(2));
469485
assert_eq!(expected, actual);
470486
}
471487

@@ -484,7 +500,7 @@ mod tests {
484500
let ideal_balance_upper_bound = Decimal::raw(ideal_balance_upper_bound);
485501
let upper_limit = Decimal::raw(upper_limit);
486502

487-
match calculate_cumulative_impact_factor_component(
503+
match calculate_sqrt_cumulative_impact_factor_component(
488504
normalized_balance,
489505
ideal_balance_lower_bound,
490506
ideal_balance_upper_bound,
@@ -505,7 +521,7 @@ mod tests {
505521
let ideal_balance_lower_bound = Decimal::raw(normalized_balance.saturating_sub(ideal_balance_lower_bound_from_normalized_balance));
506522
let normalized_balance = Decimal::raw(normalized_balance);
507523
let upper_limit = ideal_balance_upper_bound;
508-
let actual = calculate_cumulative_impact_factor_component(
524+
let actual = calculate_sqrt_cumulative_impact_factor_component(
509525
normalized_balance,
510526
ideal_balance_lower_bound,
511527
ideal_balance_upper_bound,
@@ -531,14 +547,14 @@ mod tests {
531547
let upper_limit = Decimal::raw(upper_limit);
532548
let epsilon = Decimal::raw(1000u128);
533549

534-
let c1 = calculate_cumulative_impact_factor_component(
550+
let c1 = calculate_sqrt_cumulative_impact_factor_component(
535551
normalized_balance - epsilon,
536552
ideal_balance_lower_bound,
537553
ideal_balance_upper_bound,
538554
upper_limit,
539555
).unwrap();
540556

541-
let c2 = calculate_cumulative_impact_factor_component(
557+
let c2 = calculate_sqrt_cumulative_impact_factor_component(
542558
normalized_balance,
543559
ideal_balance_lower_bound,
544560
ideal_balance_upper_bound,
@@ -565,14 +581,14 @@ mod tests {
565581
let upper_limit = Decimal::raw(upper_limit);
566582
let epsilon = Decimal::raw(1000u128);
567583

568-
let c1 = calculate_cumulative_impact_factor_component(
584+
let c1 = calculate_sqrt_cumulative_impact_factor_component(
569585
normalized_balance - epsilon,
570586
ideal_balance_lower_bound,
571587
ideal_balance_upper_bound,
572588
upper_limit,
573589
).unwrap();
574590

575-
let c2 = calculate_cumulative_impact_factor_component(
591+
let c2 = calculate_sqrt_cumulative_impact_factor_component(
576592
normalized_balance,
577593
ideal_balance_lower_bound,
578594
ideal_balance_upper_bound,
@@ -729,14 +745,40 @@ mod tests {
729745
Decimal::one(),
730746
Ok(SignedDecimal256::from_str("0.078125").unwrap())
731747
)]
732-
// #[case::precision_issue(
733-
// Decimal::from_str("0.600000000000000001").unwrap(),
734-
// Decimal::from_str("0.600000000000000002").unwrap(),
735-
// Decimal::percent(40),
736-
// Decimal::percent(60),
737-
// Decimal::one(),
738-
// Ok(SignedDecimal256::from_str("-0.000000000000000001").unwrap())
739-
// )]
748+
// precision loss for fee impact factor >> 0.000000000000000001
749+
#[case::precision_loss_positive_impact(
750+
Decimal::from_str("0.600000000000000001").unwrap(),
751+
Decimal::from_str("0.600000000000000002").unwrap(),
752+
Decimal::percent(40),
753+
Decimal::percent(60),
754+
Decimal::one(),
755+
Ok(SignedDecimal256::from_str("0.000000000000000001").unwrap())
756+
)]
757+
#[case::precision_loss_positive_impact(
758+
Decimal::from_str("0.499999999999999999").unwrap(),
759+
Decimal::from_str("0.600000000000000001").unwrap(),
760+
Decimal::percent(40),
761+
Decimal::percent(60),
762+
Decimal::one(),
763+
Ok(SignedDecimal256::from_str("0.000000000000000001").unwrap())
764+
)]
765+
// precision loss for incentive impact factor >> 0
766+
#[case::precision_loss_negative_impact(
767+
Decimal::from_str("0.600000000000000002").unwrap(),
768+
Decimal::from_str("0.600000000000000001").unwrap(),
769+
Decimal::percent(40),
770+
Decimal::percent(60),
771+
Decimal::one(),
772+
Ok(SignedDecimal256::zero())
773+
)]
774+
#[case::precision_loss_negative_impact(
775+
Decimal::from_str("0.600000000000000001").unwrap(),
776+
Decimal::from_str("0.499999999999999999").unwrap(),
777+
Decimal::percent(40),
778+
Decimal::percent(60),
779+
Decimal::one(),
780+
Ok(SignedDecimal256::zero())
781+
)]
740782
fn test_calculate_impact_factor_component(
741783
#[case] prev_normalized_balance: Decimal,
742784
#[case] update_normalized_balance: Decimal,

0 commit comments

Comments
 (0)