forked from SO4-Markets/contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlib.rs
More file actions
234 lines (198 loc) · 8.03 KB
/
Copy pathlib.rs
File metadata and controls
234 lines (198 loc) · 8.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#![no_std]
use soroban_sdk::{Env, I256};
/// 30-decimal precision — matches GMX's FLOAT_PRECISION = 10^30.
pub const FLOAT_PRECISION: i128 = 1_000_000_000_000_000_000_000_000_000_000; // 10^30
/// sqrt(FLOAT_PRECISION) = 10^15 — used in sqrt_fp.
const SQRT_FLOAT_PRECISION: i128 = 1_000_000_000_000_000; // 10^15
/// Stellar standard token precision: 1 token = 10^7 stroops.
pub const TOKEN_PRECISION: i128 = 10_000_000; // 10^7
// ─── Core arithmetic ─────────────────────────────────────────────────────────
/// (a × b) / denominator using i128. Fast path; panics on denominator=0.
/// Use mul_div_wide for values near FLOAT_PRECISION where overflow is likely.
pub fn mul_div(a: i128, b: i128, denominator: i128) -> i128 {
if denominator == 0 {
return 0;
}
match a.checked_mul(b) {
Some(p) => p / denominator,
None => {
// Decompose to avoid overflow: (a/d)*b + (a%d)*b/d
let q = a / denominator;
let r = a % denominator;
q.saturating_mul(b).saturating_add(r.saturating_mul(b) / denominator)
}
}
}
/// (a × b) / denominator using I256 host arithmetic — safe for large USD values.
/// Required when a or b can approach FLOAT_PRECISION (10^30).
pub fn mul_div_wide(env: &Env, a: i128, b: i128, denominator: i128) -> i128 {
if denominator == 0 {
return 0;
}
let a256 = I256::from_i128(env, a);
let b256 = I256::from_i128(env, b);
let d256 = I256::from_i128(env, denominator);
let product = a256.mul(&b256);
let result = product.div(&d256);
// Saturate to i128 bounds if result is too large (shouldn't happen in normal protocol use)
result.to_i128().unwrap_or(if a > 0 { i128::MAX } else { i128::MIN })
}
// ─── Factor helpers ───────────────────────────────────────────────────────────
/// value / total expressed as a FLOAT_PRECISION fraction.
pub fn to_factor(value: i128, total: i128) -> i128 {
if total == 0 {
return 0;
}
mul_div(value, FLOAT_PRECISION, total)
}
/// value × factor / FLOAT_PRECISION.
pub fn apply_factor(value: i128, factor: i128) -> i128 {
mul_div(value, factor, FLOAT_PRECISION)
}
/// Wide version — safe when value is a large USD amount.
pub fn apply_factor_wide(env: &Env, value: i128, factor: i128) -> i128 {
mul_div_wide(env, value, factor, FLOAT_PRECISION)
}
// ─── Integer square root ─────────────────────────────────────────────────────
/// Integer square root via Newton's method (floor).
pub fn integer_sqrt(n: i128) -> i128 {
if n <= 0 {
return 0;
}
let mut x = n;
let mut y = (x + 1) / 2;
while y < x {
x = y;
y = (y + n / y) / 2;
}
x
}
/// sqrt of a FLOAT_PRECISION value, result also in FLOAT_PRECISION units.
///
/// sqrt_fp(v) where v = V × 10^30:
/// result = sqrt(V) × 10^30 = sqrt(V × 10^30) × 10^15
pub fn sqrt_fp(value: i128) -> i128 {
if value <= 0 {
return 0;
}
// sqrt(value) in native units, then multiply by 10^15
let s = integer_sqrt(value);
s.saturating_mul(SQRT_FLOAT_PRECISION)
}
// ─── Exponent factor (mirrors GMX Precision.applyExponentFactor) ──────────────
/// value^(exponent / FLOAT_PRECISION) where value and result are in FLOAT_PRECISION units.
///
/// Uses the same sqrt-based approximation as GMX:
/// 1. Compute integer part: value^floor(exponent / FLOAT_PRECISION)
/// 2. Approximate fractional part via sqrt: value^frac ≈ sqrt(value)^(2*frac)
///
/// Requires env for I256 intermediate arithmetic.
pub fn pow_factor(env: &Env, value: i128, exponent: i128) -> i128 {
if value <= 0 {
return 0;
}
if exponent == 0 {
return FLOAT_PRECISION; // x^0 = 1
}
if exponent == FLOAT_PRECISION {
return value; // x^1 = x
}
let whole = exponent / FLOAT_PRECISION;
let decimal = exponent % FLOAT_PRECISION;
// Integer power: value^whole (using wide arithmetic to prevent overflow)
let mut result = FLOAT_PRECISION; // 1.0
for _ in 0..whole {
result = mul_div_wide(env, result, value, FLOAT_PRECISION);
}
if decimal == 0 {
return result;
}
// Fractional power via sqrt:
// value^decimal = sqrt(value)^(2 * decimal / FLOAT_PRECISION)
let sqrt_value = sqrt_fp(value);
let double_decimal = decimal.saturating_mul(2);
let sqrt_whole = double_decimal / FLOAT_PRECISION; // 0 or 1
let sqrt_frac = double_decimal % FLOAT_PRECISION;
let mut sqrt_result = FLOAT_PRECISION;
for _ in 0..sqrt_whole {
sqrt_result = mul_div_wide(env, sqrt_result, sqrt_value, FLOAT_PRECISION);
}
// Linear interpolation for the remaining sub-half exponent:
// x^f ≈ 1 + f*(x - 1)
if sqrt_frac > 0 && sqrt_value > FLOAT_PRECISION {
let delta = mul_div(sqrt_value - FLOAT_PRECISION, sqrt_frac, FLOAT_PRECISION);
sqrt_result = sqrt_result.saturating_add(mul_div(sqrt_result, delta, FLOAT_PRECISION));
}
mul_div_wide(env, result, sqrt_result, FLOAT_PRECISION)
}
// ─── Utility ──────────────────────────────────────────────────────────────────
pub fn abs_safe(value: i128) -> i128 {
if value < 0 { value.saturating_neg() } else { value }
}
pub fn min(a: i128, b: i128) -> i128 {
if a < b { a } else { b }
}
pub fn max(a: i128, b: i128) -> i128 {
if a > b { a } else { b }
}
/// Clamp value to [0, ∞) — used for pool amounts that can't go negative.
pub fn bound_above_zero(value: i128) -> i128 {
if value < 0 { 0 } else { value }
}
// ─── Tests ────────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::Env;
#[test]
fn test_mul_div_basic() {
assert_eq!(mul_div(100, 200, 100), 200);
assert_eq!(mul_div(1, FLOAT_PRECISION, FLOAT_PRECISION), 1);
assert_eq!(mul_div(0, 1000, 100), 0);
assert_eq!(mul_div(100, 0, 100), 0);
}
#[test]
fn test_apply_factor() {
// 50% of FLOAT_PRECISION = 0.5
let half = FLOAT_PRECISION / 2;
assert_eq!(apply_factor(FLOAT_PRECISION, half), half);
// 1.0 factor = identity
assert_eq!(apply_factor(12345, FLOAT_PRECISION), 12345);
}
#[test]
fn test_to_factor() {
assert_eq!(to_factor(1, 2), FLOAT_PRECISION / 2);
assert_eq!(to_factor(FLOAT_PRECISION, FLOAT_PRECISION), FLOAT_PRECISION);
}
#[test]
fn test_integer_sqrt() {
assert_eq!(integer_sqrt(0), 0);
assert_eq!(integer_sqrt(1), 1);
assert_eq!(integer_sqrt(4), 2);
assert_eq!(integer_sqrt(9), 3);
assert_eq!(integer_sqrt(100), 10);
assert_eq!(integer_sqrt(2), 1); // floor
}
#[test]
fn test_mul_div_wide() {
let env = Env::default();
// Same as mul_div for small values
assert_eq!(mul_div_wide(&env, 100, 200, 100), 200);
// Large value: (FLOAT_PRECISION * FLOAT_PRECISION) / FLOAT_PRECISION = FLOAT_PRECISION
let fp = FLOAT_PRECISION;
assert_eq!(mul_div_wide(&env, fp, fp, fp), fp);
}
#[test]
fn test_pow_factor_integer_exponents() {
let env = Env::default();
let fp = FLOAT_PRECISION;
// x^1 = x
assert_eq!(pow_factor(&env, 1000 * fp, fp), 1000 * fp);
// x^0 = 1
assert_eq!(pow_factor(&env, 1000 * fp, 0), fp);
// 2^2 = 4 (in FLOAT_PRECISION units)
let two = 2 * fp;
let four = 4 * fp;
assert_eq!(pow_factor(&env, two, 2 * fp), four);
}
}