|
14 | 14 | // |
15 | 15 | // You should have received a copy of the GNU General Public License |
16 | 16 | // along with Zeitgeist. If not, see <https://www.gnu.org/licenses/>. |
| 17 | +// |
| 18 | +// This file incorporates work covered by the following copyright and |
| 19 | +// permission notice: |
| 20 | +// |
| 21 | +// Copyright (c) 2019 Alain Brenzikofer, modified by GalacticCouncil(2021) |
| 22 | +// |
| 23 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 24 | +// you may not use this file except in compliance with the License. |
| 25 | +// You may obtain a copy of the License at |
| 26 | +// |
| 27 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 28 | +// |
| 29 | +// Unless required by applicable law or agreed to in writing, software |
| 30 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 31 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 32 | +// See the License for the specific language governing permissions and |
| 33 | +// limitations under the License. |
| 34 | +// |
| 35 | +// Original source: https://github.com/encointer/substrate-fixed |
| 36 | +// |
| 37 | +// The changes applied are: 1) Used same design for definition of `exp` |
| 38 | +// as in the source. 2) Re-used and extended tests for `exp` and other |
| 39 | +// functions. |
17 | 40 |
|
18 | | -use crate::{BalanceOf, Config, Error}; |
| 41 | +use crate::{ |
| 42 | + math::transcendental::{exp, ln}, |
| 43 | + BalanceOf, Config, Error, |
| 44 | +}; |
19 | 45 | use alloc::vec::Vec; |
20 | 46 | use core::marker::PhantomData; |
21 | 47 | use fixed::FixedU128; |
22 | | -use hydra_dx_math::transcendental::{exp, ln}; |
23 | 48 | use sp_runtime::{DispatchError, SaturatedConversion}; |
24 | 49 | use typenum::U80; |
25 | 50 |
|
@@ -102,6 +127,88 @@ impl<T: Config> MathOps<T> for Math<T> { |
102 | 127 | } |
103 | 128 | } |
104 | 129 |
|
| 130 | +mod transcendental { |
| 131 | + use fixed::traits::FixedUnsigned; |
| 132 | + pub(crate) use hydra_dx_math::transcendental::{exp as inner_exp, ln}; |
| 133 | + use sp_runtime::traits::One; |
| 134 | + |
| 135 | + pub(crate) fn exp<S, D>(operand: S, neg: bool) -> Result<D, ()> |
| 136 | + where |
| 137 | + S: FixedUnsigned + PartialOrd<D> + One, |
| 138 | + D: FixedUnsigned + PartialOrd<S> + From<S> + One, |
| 139 | + { |
| 140 | + if operand == S::one() && neg { |
| 141 | + let e_inverse = |
| 142 | + S::from_str("0.367879441171442321595523770161460867445").map_err(|_| ())?; |
| 143 | + return Ok(D::from(e_inverse)); |
| 144 | + } |
| 145 | + inner_exp(operand, neg) |
| 146 | + } |
| 147 | + |
| 148 | + #[cfg(test)] |
| 149 | + mod tests { |
| 150 | + use super::*; |
| 151 | + use alloc::str::FromStr; |
| 152 | + use fixed::types::U64F64; |
| 153 | + use test_case::test_case; |
| 154 | + |
| 155 | + type S = U64F64; |
| 156 | + type D = U64F64; |
| 157 | + |
| 158 | + #[test_case("0", false, "1")] |
| 159 | + #[test_case("0", true, "1")] |
| 160 | + #[test_case("1", false, "2.718281828459045235360287471352662497757")] |
| 161 | + #[test_case("1", true, "0.367879441171442321595523770161460867445")] |
| 162 | + #[test_case("2", false, "7.3890560989306502265")] |
| 163 | + #[test_case("2", true, "0.13533528323661269186")] |
| 164 | + #[test_case("0.1", false, "1.1051709180756476246")] |
| 165 | + #[test_case("0.1", true, "0.9048374180359595733")] |
| 166 | + #[test_case("0.9", false, "2.4596031111569496633")] |
| 167 | + #[test_case("0.9", true, "0.40656965974059911195")] |
| 168 | + #[test_case("1.5", false, "4.481689070338064822")] |
| 169 | + #[test_case("1.5", true, "0.22313016014842982894")] |
| 170 | + #[test_case("3.3", false, "27.1126389206578874259")] |
| 171 | + #[test_case("3.3", true, "0.03688316740124000543")] |
| 172 | + #[test_case("7.3456", false, "1549.3643050275008503592")] |
| 173 | + #[test_case("7.3456", true, "0.00064542599616831253")] |
| 174 | + #[test_case("12.3456789", false, "229964.194569082134542849")] |
| 175 | + #[test_case("12.3456789", true, "0.00000434850304358833")] |
| 176 | + #[test_case("13", false, "442413.39200892050332603603")] |
| 177 | + #[test_case("13", true, "0.0000022603294069810542")] |
| 178 | + fn exp_works(operand: &str, neg: bool, expected: &str) { |
| 179 | + let o = U64F64::from_str(operand).unwrap(); |
| 180 | + let e = U64F64::from_str(expected).unwrap(); |
| 181 | + assert_eq!(exp::<S, D>(o, neg).unwrap(), e); |
| 182 | + } |
| 183 | + |
| 184 | + #[test_case("1", "0", false)] |
| 185 | + #[test_case("2", "0.69314718055994530943", false)] |
| 186 | + #[test_case("3", "1.09861228866810969136", false)] |
| 187 | + #[test_case("2.718281828459045235360287471352662497757", "1", false)] |
| 188 | + #[test_case("1.1051709180756476246", "0.09999999999999999975", false)] |
| 189 | + #[test_case("2.4596031111569496633", "0.89999999999999999976", false)] |
| 190 | + #[test_case("4.481689070338064822", "1.49999999999999999984", false)] |
| 191 | + #[test_case("27.1126389206578874261", "3.3", false)] |
| 192 | + #[test_case("1549.3643050275008503592", "7.34560000000000000003", false)] |
| 193 | + #[test_case("229964.194569082134542849", "12.3456789000000000002", false)] |
| 194 | + #[test_case("442413.39200892050332603603", "13.0000000000000000002", false)] |
| 195 | + #[test_case("0.9048374180359595733", "0.09999999999999999975", true)] |
| 196 | + #[test_case("0.40656965974059911195", "0.8999999999999999998", true)] |
| 197 | + #[test_case("0.22313016014842982894", "1.4999999999999999999", true)] |
| 198 | + #[test_case("0.03688316740124000543", "3.3000000000000000005", true)] |
| 199 | + #[test_case("0.00064542599616831253", "7.34560000000000002453", true)] |
| 200 | + #[test_case("0.00000434850304358833", "12.34567890000000711117", true)] |
| 201 | + #[test_case("0.0000022603294069810542", "13.0000000000000045352", true)] |
| 202 | + fn ln_works(operand: &str, expected_abs: &str, expected_neg: bool) { |
| 203 | + let o = U64F64::from_str(operand).unwrap(); |
| 204 | + let e = U64F64::from_str(expected_abs).unwrap(); |
| 205 | + let (a, n) = ln::<S, D>(o).unwrap(); |
| 206 | + assert_eq!(a, e); |
| 207 | + assert_eq!(n, expected_neg); |
| 208 | + } |
| 209 | + } |
| 210 | +} |
| 211 | + |
105 | 212 | mod detail { |
106 | 213 | use super::*; |
107 | 214 | use zeitgeist_primitives::{ |
@@ -225,58 +332,112 @@ mod detail { |
225 | 332 | tmp_reserves.iter().map(|&r| r.checked_mul(liquidity)).collect::<Option<Vec<_>>>()?; |
226 | 333 | Some((liquidity, reserves)) |
227 | 334 | } |
| 335 | +} |
228 | 336 |
|
229 | | - #[cfg(test)] |
230 | | - mod tests { |
231 | | - use super::*; |
232 | | - use crate::{assert_approx, consts::*}; |
233 | | - use std::str::FromStr; |
234 | | - use test_case::test_case; |
| 337 | +#[cfg(test)] |
| 338 | +mod tests { |
| 339 | + use super::*; |
| 340 | + use crate::{consts::*, mock::Runtime as MockRuntime}; |
| 341 | + use alloc::str::FromStr; |
| 342 | + use test_case::test_case; |
235 | 343 |
|
236 | | - // Example taken from |
237 | | - // https://docs.gnosis.io/conditionaltokens/docs/introduction3/#an-example-with-lmsr |
238 | | - #[test] |
239 | | - fn calculate_swap_amount_out_for_buy_works() { |
240 | | - let liquidity = 144269504088; |
241 | | - assert_eq!( |
242 | | - calculate_swap_amount_out_for_buy(_10, _10, liquidity).unwrap(), |
243 | | - 58496250072 |
244 | | - ); |
245 | | - } |
| 344 | + type MockBalance = BalanceOf<MockRuntime>; |
| 345 | + type MockMath = Math<MockRuntime>; |
246 | 346 |
|
247 | | - #[test] |
248 | | - fn calculate_swap_amount_out_for_sell_works() { |
249 | | - let liquidity = 144269504088; |
250 | | - assert_eq!( |
251 | | - calculate_swap_amount_out_for_sell(_10, _10, liquidity).unwrap(), |
252 | | - 41503749928 |
253 | | - ); |
254 | | - } |
| 347 | + // 32.44892769177272 |
| 348 | + const EXP_OVERFLOW_THRESHOLD: Fixed = Fixed::from_bits(0x20_72EC_ECDA_6EBE_EACC_40C7); |
255 | 349 |
|
256 | | - #[test] |
257 | | - fn calcuate_spot_price_works() { |
258 | | - let liquidity = 144269504088; |
259 | | - assert_eq!(calculate_spot_price(_10, liquidity).unwrap(), _1_2); |
260 | | - assert_eq!(calculate_spot_price(_10 - 58496250072, liquidity).unwrap(), _3_4); |
261 | | - assert_eq!(calculate_spot_price(_20, liquidity).unwrap(), _1_4); |
262 | | - } |
| 350 | + // Example taken from |
| 351 | + // https://docs.gnosis.io/conditionaltokens/docs/introduction3/#an-example-with-lmsr |
| 352 | + #[test_case(_10, _10, 144_269_504_088, 58_496_250_072)] |
| 353 | + #[test_case(_1, _1, _1, 7_353_256_641)] |
| 354 | + #[test_case(_2, _2, _2, 14_706_513_281; "positive ln")] |
| 355 | + #[test_case(_1, _1_10, _3, 386_589_943; "negative ln")] |
| 356 | + // Limit value tests; functions shouldn't be called with these values, but these tests |
| 357 | + // demonstrate they can be called without risk. |
| 358 | + #[test_case(0, _1, _1, 0)] |
| 359 | + #[test_case(_1, 0, _1, 0)] |
| 360 | + #[test_case(_30, _30, _1 - 100_000, _30)] |
| 361 | + #[test_case(_1_10, _30, _1 - 100_000, _1_10)] |
| 362 | + #[test_case(_30, _1_10, _1 - 100_000, 276_478_645_689)] |
| 363 | + fn calculate_swap_amount_out_for_buy_works( |
| 364 | + reserve: MockBalance, |
| 365 | + amount_in: MockBalance, |
| 366 | + liquidity: MockBalance, |
| 367 | + expected: MockBalance, |
| 368 | + ) { |
| 369 | + assert_eq!( |
| 370 | + MockMath::calculate_swap_amount_out_for_buy(reserve, amount_in, liquidity).unwrap(), |
| 371 | + expected |
| 372 | + ); |
| 373 | + } |
263 | 374 |
|
264 | | - #[test] |
265 | | - fn calculate_reserves_from_spot_prices_works() { |
266 | | - let expected_liquidity = 144269504088; |
267 | | - let (liquidity, reserves) = |
268 | | - calculate_reserves_from_spot_prices(_10, vec![_1_2, _1_2]).unwrap(); |
269 | | - assert_approx!(liquidity, expected_liquidity, 1); |
270 | | - assert_eq!(reserves, vec![_10, _10]); |
271 | | - } |
| 375 | + #[test_case(_10, _10, 144_269_504_088, 41_503_749_928)] |
| 376 | + #[test_case(_1, _1, _1, 2_646_743_359)] |
| 377 | + #[test_case(_2, _2, _2, 5_293_486_719)] |
| 378 | + #[test_case(_17, _8, _7, 4_334_780_553; "positive ln")] |
| 379 | + #[test_case(_1, _11, 33_000_000_000, 41_104_447_891; "negative ln")] |
| 380 | + // Limit value tests; functions shouldn't be called with these values, but these tests |
| 381 | + // demonstrate they can be called without risk. |
| 382 | + #[test_case(_1, 0, _1, 0)] |
| 383 | + #[test_case(_30, _30, _1 - 100_000, 0)] |
| 384 | + #[test_case(_1_10, _30, _1 - 100_000, 23_521_354_311)] |
| 385 | + #[test_case(_30, _1_10, _1 - 100_000, 0)] |
| 386 | + fn calculate_swap_amount_out_for_sell_works( |
| 387 | + reserve: MockBalance, |
| 388 | + amount_in: MockBalance, |
| 389 | + liquidity: MockBalance, |
| 390 | + expected: MockBalance, |
| 391 | + ) { |
| 392 | + assert_eq!( |
| 393 | + MockMath::calculate_swap_amount_out_for_sell(reserve, amount_in, liquidity).unwrap(), |
| 394 | + expected |
| 395 | + ); |
| 396 | + } |
272 | 397 |
|
273 | | - // This test ensures that we don't mess anything up when we change precision. |
274 | | - #[test_case(false, Fixed::from_str("10686474581524.462146990468650739308072").unwrap())] |
275 | | - #[test_case(true, Fixed::from_str("0.000000000000093576229688").unwrap())] |
276 | | - fn exp_does_not_overflow_or_underflow(neg: bool, expected: Fixed) { |
277 | | - let value = 30; |
278 | | - let result: Fixed = exp(Fixed::checked_from_num(value).unwrap(), neg).unwrap(); |
279 | | - assert_eq!(result, expected); |
280 | | - } |
| 398 | + #[test_case(_10, 144_269_504_088, _1_2)] |
| 399 | + #[test_case(_10 - 58_496_250_072, 144_269_504_088, _3_4)] |
| 400 | + #[test_case(_20, 144_269_504_088, _1_4)] |
| 401 | + fn calcuate_spot_price_works( |
| 402 | + reserve: MockBalance, |
| 403 | + liquidity: MockBalance, |
| 404 | + expected: MockBalance, |
| 405 | + ) { |
| 406 | + assert_eq!(MockMath::calculate_spot_price(reserve, liquidity).unwrap(), expected); |
| 407 | + } |
| 408 | + |
| 409 | + #[test_case(_10, vec![_1_2, _1_2], vec![_10, _10], 144_269_504_089)] |
| 410 | + #[test_case(_20, vec![_3_4, _1_4], vec![_10 - 58_496_250_072, _20], 144_269_504_089)] |
| 411 | + #[test_case( |
| 412 | + _444, |
| 413 | + vec![_1_10, _2_10, _3_10, _4_10], |
| 414 | + vec![_444, 3_103_426_819_252, 2_321_581_629_045, 1_766_853_638_504], |
| 415 | + 1_928_267_499_650 |
| 416 | + )] |
| 417 | + #[test_case( |
| 418 | + _100, |
| 419 | + vec![50_000_000, 50_000_000, 50_000_000, 8_500_000_000], |
| 420 | + vec![_100, _100, _100, 30_673_687_183], |
| 421 | + 188_739_165_818 |
| 422 | + )] |
| 423 | + fn calculate_reserves_from_spot_prices_works( |
| 424 | + amount: MockBalance, |
| 425 | + spot_prices: Vec<MockBalance>, |
| 426 | + expected_reserves: Vec<MockBalance>, |
| 427 | + expected_liquidity: MockBalance, |
| 428 | + ) { |
| 429 | + let (liquidity, reserves) = |
| 430 | + MockMath::calculate_reserves_from_spot_prices(amount, spot_prices).unwrap(); |
| 431 | + assert_eq!(liquidity, expected_liquidity); |
| 432 | + assert_eq!(reserves, expected_reserves); |
| 433 | + } |
| 434 | + |
| 435 | + // This test ensures that we don't mess anything up when we change precision. |
| 436 | + #[test_case(false, Fixed::from_str("123705850708694.521074740553659523785099").unwrap())] |
| 437 | + #[test_case(true, Fixed::from_str("0.000000000000008083692034").unwrap())] |
| 438 | + fn exp_does_not_overflow_or_underflow(neg: bool, expected: Fixed) { |
| 439 | + let result: Fixed = |
| 440 | + exp(Fixed::checked_from_num(EXP_OVERFLOW_THRESHOLD).unwrap(), neg).unwrap(); |
| 441 | + assert_eq!(result, expected); |
281 | 442 | } |
282 | 443 | } |
0 commit comments