Skip to content

Commit 08861eb

Browse files
Merge pull request #2 from zeitgeistpm/mkl-hotfix-exp
Mkl hotfix exp
2 parents 0fd61d6 + bf1b9fc commit 08861eb

File tree

2 files changed

+224
-49
lines changed

2 files changed

+224
-49
lines changed

zrml/neo-swaps/src/consts.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,22 @@ pub(crate) const _2: u128 = 2 * _1;
2525
pub(crate) const _3: u128 = 3 * _1;
2626
pub(crate) const _4: u128 = 4 * _1;
2727
pub(crate) const _5: u128 = 5 * _1;
28+
pub(crate) const _7: u128 = 7 * _1;
29+
pub(crate) const _8: u128 = 8 * _1;
2830
pub(crate) const _9: u128 = 9 * _1;
2931
pub(crate) const _10: u128 = 10 * _1;
32+
pub(crate) const _11: u128 = 11 * _1;
33+
pub(crate) const _17: u128 = 17 * _1;
3034
pub(crate) const _20: u128 = 20 * _1;
35+
pub(crate) const _30: u128 = 30 * _1;
3136
pub(crate) const _70: u128 = 70 * _1;
3237
pub(crate) const _80: u128 = 80 * _1;
3338
pub(crate) const _100: u128 = 100 * _1;
3439
pub(crate) const _101: u128 = 101 * _1;
40+
pub(crate) const _444: u128 = 444 * _1;
41+
pub(crate) const _500: u128 = 500 * _1;
42+
pub(crate) const _777: u128 = 777 * _1;
43+
pub(crate) const _1000: u128 = 1_000 * _1;
3544

3645
pub(crate) const _1_2: u128 = _1 / 2;
3746

@@ -45,3 +54,8 @@ pub(crate) const _1_5: u128 = _1 / 5;
4554

4655
pub(crate) const _1_6: u128 = _1 / 6;
4756
pub(crate) const _5_6: u128 = _5 / 6;
57+
58+
pub(crate) const _1_10: u128 = _1 / 10;
59+
pub(crate) const _2_10: u128 = _2 / 10;
60+
pub(crate) const _3_10: u128 = _3 / 10;
61+
pub(crate) const _4_10: u128 = _4 / 10;

zrml/neo-swaps/src/math.rs

Lines changed: 210 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,37 @@
1414
//
1515
// You should have received a copy of the GNU General Public License
1616
// 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.
1740

18-
use crate::{BalanceOf, Config, Error};
41+
use crate::{
42+
math::transcendental::{exp, ln},
43+
BalanceOf, Config, Error,
44+
};
1945
use alloc::vec::Vec;
2046
use core::marker::PhantomData;
2147
use fixed::FixedU128;
22-
use hydra_dx_math::transcendental::{exp, ln};
2348
use sp_runtime::{DispatchError, SaturatedConversion};
2449
use typenum::U80;
2550

@@ -102,6 +127,88 @@ impl<T: Config> MathOps<T> for Math<T> {
102127
}
103128
}
104129

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+
105212
mod detail {
106213
use super::*;
107214
use zeitgeist_primitives::{
@@ -225,58 +332,112 @@ mod detail {
225332
tmp_reserves.iter().map(|&r| r.checked_mul(liquidity)).collect::<Option<Vec<_>>>()?;
226333
Some((liquidity, reserves))
227334
}
335+
}
228336

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;
235343

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>;
246346

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);
255349

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+
}
263374

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+
}
272397

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);
281442
}
282443
}

0 commit comments

Comments
 (0)