fixedpointmath/
utils.rs

1use std::ops::Shr;
2
3use ethers::types::{I256, U256};
4use eyre::{bail, Result};
5
6use crate::{int256, uint256};
7
8/// Parses a string into a U256 with support for scientific and decimal
9/// notation.
10///
11/// ## Example
12///
13/// ```
14/// use ethers::types::U256;
15/// use fixedpointmath::u256_from_str;
16///
17/// let u = u256_from_str("1.1e18").unwrap();
18/// assert_eq!(u, U256::from(11) * U256::from(10).pow(U256::from(17)));
19/// ```
20pub fn u256_from_str(s: &str) -> Result<U256> {
21    // Parse a string into a mantissa and an exponent. The U256 arithmetic will
22    // overflow if the mantissa or the exponent are too large.
23    let mut found_dot = false;
24    let mut found_e = false;
25    let mut mantissa = ethers::types::U256::zero();
26    let mut exponent = ethers::types::U256::zero();
27    let mut decimals = 0;
28
29    for digit in s.chars() {
30        if digit.is_ascii_digit() {
31            let d = digit.to_digit(10).unwrap();
32            if !found_e {
33                mantissa = mantissa * 10 + d;
34            } else {
35                exponent = exponent * 10 + d;
36            }
37            if found_dot && !found_e {
38                decimals += 1;
39            }
40        } else if digit == 'e' && !found_e {
41            found_e = true;
42        } else if digit == '.' && !found_dot {
43            found_dot = true;
44        } else if digit != '_' {
45            bail!("Unexpected character in U256: {digit}");
46        }
47    }
48
49    // Combine the mantissa and the exponent into a single U256. This will
50    // overflow if the exponent is too large. We also need to make sure that the
51    // final result is an integer.
52    let decimals = ethers::types::U256::from(decimals);
53    if exponent < decimals {
54        bail!("Exponent {exponent} is too small for U256: {s}");
55    }
56
57    Ok(mantissa * ethers::types::U256::from(10).pow(exponent - decimals))
58}
59
60/// Parse a string into an I256 with support for scientific and decimal
61/// notation.
62///
63/// ## Example
64///
65/// ```
66/// use ethers::types::I256;
67/// use fixedpointmath::i256_from_str;
68///
69/// let i = i256_from_str("-1.1e18").unwrap();
70/// assert_eq!(i, -I256::from(11) * I256::from(10).pow(17));
71/// ```
72pub fn i256_from_str(s: &str) -> Result<I256> {
73    // Parse a string into a mantissa and an exponent. The U256 arithmetic will
74    // overflow if the mantissa or the exponent are too large.
75    let mut sign = ethers::types::I256::one();
76    let mut found_dot = false;
77    let mut found_e = false;
78    let mut mantissa = ethers::types::I256::zero();
79    let mut exponent = 0;
80    let mut decimals = 0;
81
82    for digit in s.chars() {
83        if digit.is_ascii_digit() {
84            let d = digit.to_digit(10).unwrap();
85            if !found_e {
86                mantissa = mantissa * 10 + d;
87            } else {
88                exponent = exponent * 10 + d;
89            }
90            if found_dot && !found_e {
91                decimals += 1;
92            }
93        } else if digit == '-' {
94            sign = -ethers::types::I256::one();
95        } else if digit == 'e' && !found_e {
96            found_e = true;
97        } else if digit == '.' && !found_dot {
98            found_dot = true;
99        } else if digit != '_' {
100            bail!("Unexpected character in I256: {digit}");
101        }
102    }
103
104    // Combine the mantissa and the exponent I256. This will overflow if the
105    // exponent is too large. We also need to make sure that the final result is
106    // an integer.
107    if exponent < decimals {
108        bail!("Exponent {exponent} is too small for I256: {s}");
109    }
110
111    Ok(sign * mantissa * ethers::types::I256::from(10).pow(exponent - decimals))
112}
113
114/// Math
115
116pub fn exp(mut x: I256) -> Result<I256> {
117    // When the result is < 0.5 we return zero. This happens when x <=
118    // floor(log(0.5e18) * 1e18) ~ -42e18
119    if x <= I256::from(-42139678854452767551_i128) {
120        return Ok(I256::zero());
121    }
122
123    // When the result is > (2**255 - 1) / 1e18 we can not represent it as an
124    // int. This happens when x >= floor(log((2**255 - 1) / 1e18) * 1e18) ~ 135.
125    if x >= int256!(135305999368893231589) {
126        bail!("invalid exponent {x}");
127    }
128
129    // x is now in the range (-42, 136) * 1e18. Convert to (-42, 136) * 2**96
130    // for more intermediate precision and a binary basis. This base conversion
131    // is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
132    x = x.wrapping_shl(78) / int256!(5).pow(18);
133
134    // Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers of
135    // two such that exp(x) = exp(x') * 2**k, where k is an integer. Solving
136    // this gives k = round(x / log(2)) and x' = x - k * log(2).
137    let k = ((x.wrapping_shl(96) / int256!(54916777467707473351141471128))
138        .wrapping_add(int256!(2).pow(95)))
139    .asr(96);
140    x = x.wrapping_sub(k.wrapping_mul(54916777467707473351141471128_u128.into()));
141
142    // k is in the range [-61, 195].
143
144    // Evaluate using a (6, 7)-term rational approximation. p is made monic,
145    // we'll multiply by a scale factor later.
146    let mut y = x.wrapping_add(1346386616545796478920950773328_u128.into());
147    y = y
148        .wrapping_mul(x)
149        .asr(96)
150        .wrapping_add(57155421227552351082224309758442_u128.into());
151    let mut p = y
152        .wrapping_add(x)
153        .wrapping_sub(94201549194550492254356042504812_u128.into());
154    p = p
155        .wrapping_mul(y)
156        .asr(96)
157        .wrapping_add(28719021644029726153956944680412240_u128.into());
158    p = p
159        .wrapping_mul(x)
160        .wrapping_add(int256!(4385272521454847904659076985693276).wrapping_shl(96));
161
162    // We leave p in 2**192 basis so we don't need to scale it back up for the
163    // division.
164    let mut q = x.wrapping_sub(2855989394907223263936484059900_u128.into());
165    q = q
166        .wrapping_mul(x)
167        .asr(96)
168        .wrapping_add(50020603652535783019961831881945_u128.into());
169    q = q
170        .wrapping_mul(x)
171        .asr(96)
172        .wrapping_sub(533845033583426703283633433725380_u128.into());
173    q = q
174        .wrapping_mul(x)
175        .asr(96)
176        .wrapping_add(3604857256930695427073651918091429_u128.into());
177    q = q
178        .wrapping_mul(x)
179        .asr(96)
180        .wrapping_sub(14423608567350463180887372962807573_u128.into());
181    q = q
182        .wrapping_mul(x)
183        .asr(96)
184        .wrapping_add(26449188498355588339934803723976023_u128.into());
185
186    let mut r = p.wrapping_div(q);
187
188    // r should be in the range (0.09, 0.25) * 2**96.
189
190    // We now need to multiply r by:
191    // * the scale factor s = ~6.031367120.
192    // * the 2**k factor from the range reduction.
193    // * the 1e18 / 2**96 factor for base conversion. We do this all at once,
194    // with an intermediate result in 2**213 basis, so the final right shift is
195    // always by a positive amount.
196    r = I256::from_raw(
197        (r.into_raw()
198            .overflowing_mul(uint256!(3822833074963236453042738258902158003155416615667))
199            .0)
200            .shr(int256!(195).wrapping_sub(k).low_usize()),
201    );
202
203    Ok(r)
204}
205
206pub fn ln(mut x: I256) -> Result<I256> {
207    if x <= I256::zero() {
208        bail!("Cannot calculate ln of of negative number or zero.");
209    }
210
211    // We want to convert x from 10**18 fixed point to 2**96 fixed point. We do
212    // this by multiplying by 2**96 / 10**18. But since ln(x * C) = ln(x) +
213    // ln(C), we can simply do nothing here and add ln(2**96 / 10**18) at the
214    // end.
215
216    let mut r = I256::from((x > I256::from(0xffffffffffffffffffffffffffffffff_u128)) as u128)
217        .wrapping_shl(7);
218    r = r | I256::from((x.asr(r.as_usize()) > I256::from(0xffffffffffffffff_u128)) as u128)
219        .wrapping_shl(6);
220    r = r | I256::from((x.asr(r.as_usize()) > I256::from(0xffffffff_u128)) as u128).wrapping_shl(5);
221    r = r | I256::from((x.asr(r.as_usize()) > I256::from(0xffff_u128)) as u128).wrapping_shl(4);
222    r = r | I256::from((x.asr(r.as_usize()) > I256::from(0xff_u128)) as u128).wrapping_shl(3);
223    r = r | I256::from((x.asr(r.as_usize()) > I256::from(0xf_u128)) as u128).wrapping_shl(2);
224    r = r | I256::from((x.asr(r.as_usize()) > I256::from(0x3_u128)) as u128).wrapping_shl(1);
225    r = r | I256::from((x.asr(r.as_usize()) > I256::from(0x1_u128)) as u128);
226
227    // Reduce range of x to (1, 2) * 2**96 ln(2^k * x) = k * ln(2) + ln(x)
228    let k = r.wrapping_sub(int256!(96));
229    x = x.wrapping_shl(int256!(159).wrapping_sub(k).as_usize());
230    x = I256::from_raw(x.into_raw().shr(159));
231
232    // Evaluate using a (8, 8)-term rational approximation. p is made monic, we
233    // will multiply by a scale factor later.
234    let mut p = x.wrapping_add(int256!(3273285459638523848632254066296));
235    p = ((p.wrapping_mul(x)).asr(96)).wrapping_add(int256!(24828157081833163892658089445524));
236    p = ((p.wrapping_mul(x)).asr(96)).wrapping_add(int256!(43456485725739037958740375743393));
237    p = ((p.wrapping_mul(x)).asr(96)).wrapping_sub(int256!(11111509109440967052023855526967));
238    p = ((p.wrapping_mul(x)).asr(96)).wrapping_sub(int256!(45023709667254063763336534515857));
239    p = ((p.wrapping_mul(x)).asr(96)).wrapping_sub(int256!(14706773417378608786704636184526));
240    p = p
241        .wrapping_mul(x)
242        .wrapping_sub(int256!(795164235651350426258249787498).wrapping_shl(96));
243
244    // We leave p in 2**192 basis so we don't need to scale it back up for the
245    // division. q is monic by convention.
246    let mut q = x.wrapping_add(int256!(5573035233440673466300451813936));
247    q = (q.wrapping_mul(x).asr(96)).wrapping_add(int256!(71694874799317883764090561454958));
248    q = q
249        .wrapping_mul(x)
250        .asr(96)
251        .wrapping_add(int256!(283447036172924575727196451306956));
252    q = q
253        .wrapping_mul(x)
254        .asr(96)
255        .wrapping_add(int256!(401686690394027663651624208769553));
256    q = q
257        .wrapping_mul(x)
258        .asr(96)
259        .wrapping_add(int256!(204048457590392012362485061816622));
260    q = q
261        .wrapping_mul(x)
262        .asr(96)
263        .wrapping_add(int256!(31853899698501571402653359427138));
264    q = q
265        .wrapping_mul(x)
266        .asr(96)
267        .wrapping_add(int256!(909429971244387300277376558375));
268
269    r = p.wrapping_div(q);
270
271    // r is in the range (0, 0.125) * 2**96
272
273    // Finalization, we need to:
274    // * multiply by the scale factor s = 5.549…
275    // * add ln(2**96 / 10**18)
276    // * add k * ln(2)
277    // * multiply by 10**18 / 2**96 = 5**18 >> 78
278
279    // mul s * 5e18 * 2**96, base is now 5**18 * 2**192
280    r = r.wrapping_mul(int256!(1677202110996718588342820967067443963516166));
281    // add ln(2) * k * 5e18 * 2**192
282    r = r.wrapping_add(
283        int256!(16597577552685614221487285958193947469193820559219878177908093499208371)
284            .wrapping_mul(k),
285    );
286    // add ln(2**96 / 10**18) * 5e18 * 2**192
287    r = r.wrapping_add(int256!(
288        600920179829731861736702779321621459595472258049074101567377883020018308
289    ));
290    // base conversion: mul 2**18 / 2**192
291    r = r.asr(174);
292
293    Ok(r)
294}
295
296#[cfg(test)]
297mod tests {
298    use ethers::signers::Signer;
299    use hyperdrive_wrappers::wrappers::mock_fixed_point_math::MockFixedPointMath;
300    use rand::{thread_rng, Rng};
301    use test_utils::{chain::Chain, constants::DEPLOYER};
302
303    use super::*;
304    use crate::{fixed, uint256, FixedPoint};
305
306    #[tokio::test]
307    async fn fuzz_exp_narrow() -> Result<()> {
308        let chain = Chain::connect(None, None).await?;
309        chain.deal(DEPLOYER.address(), uint256!(100_000e18)).await?;
310        let client = chain.client(DEPLOYER.clone()).await?;
311        let mock_fixed_point_math = MockFixedPointMath::deploy(client, ())?.send().await?;
312
313        // Fuzz the rust and solidity implementations against each other.
314        let mut rng = thread_rng();
315        for _ in 0..10_000 {
316            let x: FixedPoint<I256> = rng.gen_range(fixed!(0)..=fixed!(1e18));
317            let actual = ln(x.raw());
318            match mock_fixed_point_math.ln(x.raw()).call().await {
319                Ok(expected) => assert_eq!(actual.unwrap(), expected),
320                Err(_) => assert!(actual.is_err()),
321            }
322        }
323
324        Ok(())
325    }
326
327    #[tokio::test]
328    async fn fuzz_exp() -> Result<()> {
329        let chain = Chain::connect(None, None).await?;
330        chain.deal(DEPLOYER.address(), uint256!(100_000e18)).await?;
331        let client = chain.client(DEPLOYER.clone()).await?;
332        let mock_fixed_point_math = MockFixedPointMath::deploy(client, ())?.send().await?;
333
334        // Fuzz the rust and solidity implementations against each other.
335        let mut rng = thread_rng();
336        for _ in 0..10_000 {
337            let x: FixedPoint<I256> = rng.gen_range(fixed!(0)..FixedPoint::<I256>::MAX);
338            let actual = exp(x.raw());
339            match mock_fixed_point_math.exp(x.raw()).call().await {
340                Ok(expected) => assert_eq!(actual.unwrap(), expected),
341                Err(_) => assert!(actual.is_err()),
342            }
343        }
344
345        Ok(())
346    }
347
348    #[tokio::test]
349    async fn fuzz_ln_narrow() -> Result<()> {
350        let chain = Chain::connect(None, None).await?;
351        chain.deal(DEPLOYER.address(), uint256!(100_000e18)).await?;
352        let client = chain.client(DEPLOYER.clone()).await?;
353        let mock_fixed_point_math = MockFixedPointMath::deploy(client, ())?.send().await?;
354
355        // Fuzz the rust and solidity implementations against each other.
356        let mut rng = thread_rng();
357        for _ in 0..10_000 {
358            let x: FixedPoint<I256> = rng.gen_range(fixed!(0)..=fixed!(1e18));
359            let actual = ln(x.raw());
360            match mock_fixed_point_math.ln(x.raw()).call().await {
361                Ok(expected) => assert_eq!(actual.unwrap(), expected),
362                Err(_) => assert!(actual.is_err()),
363            }
364        }
365
366        Ok(())
367    }
368
369    #[tokio::test]
370    async fn fuzz_ln() -> Result<()> {
371        let chain = Chain::connect(None, None).await?;
372        chain.deal(DEPLOYER.address(), uint256!(100_000e18)).await?;
373        let client = chain.client(DEPLOYER.clone()).await?;
374        let mock_fixed_point_math = MockFixedPointMath::deploy(client, ())?.send().await?;
375
376        // Fuzz the rust and solidity implementations against each other.
377        let mut rng = thread_rng();
378        for _ in 0..10_000 {
379            let x: FixedPoint<I256> = rng.gen_range(fixed!(0)..FixedPoint::<I256>::MAX);
380            let actual = ln(x.raw());
381            match mock_fixed_point_math.ln(x.raw()).call().await {
382                Ok(expected) => assert_eq!(actual.unwrap(), expected),
383                Err(_) => assert!(actual.is_err()),
384            }
385        }
386
387        Ok(())
388    }
389}