@0x/contracts-staking
: Update LibFixedMath
ln()
and exp()
input domains and improve precision.
`@0x/contracts-staking`: Add `_invert()` and `_mulDiv()` to `LibFixedMath`. `@0x/contracts-staking`: Update `MixinExchangeFees._cobbDouglas()` to work with `LibFixedMath`. `@0x/contracts-staking`: Add unit and fuzz tests for `_cobbDouglas()` and remaining `LibFixedMath` functions.
This commit is contained in:
committed by
Lawrence Forman
parent
0c6a6743ab
commit
9a63bea763
@@ -308,10 +308,12 @@ contract MixinExchangeFees is
|
||||
pure
|
||||
returns (uint256 ownerRewards)
|
||||
{
|
||||
assert(alphaNumerator > alphaDenominator);
|
||||
assert(alphaNumerator <= alphaDenominator);
|
||||
int256 feeRatio = LibFixedMath._toFixed(ownerFees, totalFees);
|
||||
int256 stakeRatio = LibFixedMath._toFixed(ownerStake, totalStake);
|
||||
int256 alpha = LibFixedMath._toFixed(alphaNumerator, alphaDenominator);
|
||||
if (feeRatio == 0 || stakeRatio == 0) {
|
||||
return ownerRewards = 0;
|
||||
}
|
||||
|
||||
// The cobb-doublas function has the form:
|
||||
// totalRewards * feeRatio ^ alpha * stakeRatio ^ (1-alpha)
|
||||
@@ -319,12 +321,27 @@ contract MixinExchangeFees is
|
||||
// totalRewards * stakeRatio * e^(alpha * (ln(feeRatio) - ln(stakeRatio)))
|
||||
|
||||
// Compute e^(alpha * (ln(feeRatio) - ln(stakeRatio)))
|
||||
int256 n = LibFixedMath._exp(
|
||||
LibFixedMath._mul(
|
||||
alpha,
|
||||
LibFixedMath._sub(LibFixedMath._ln(feeRatio), LibFixedMath._ln(stakeRatio))
|
||||
)
|
||||
);
|
||||
int256 logFeeRatio = LibFixedMath._ln(feeRatio);
|
||||
int256 logStakeRatio = LibFixedMath._ln(stakeRatio);
|
||||
int256 n;
|
||||
if (logFeeRatio <= logStakeRatio) {
|
||||
n = LibFixedMath._exp(
|
||||
LibFixedMath._mulDiv(
|
||||
LibFixedMath._sub(logFeeRatio, logStakeRatio),
|
||||
int256(alphaNumerator),
|
||||
int256(alphaDenominator)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
n = LibFixedMath._exp(
|
||||
LibFixedMath._mulDiv(
|
||||
LibFixedMath._sub(logStakeRatio, logFeeRatio),
|
||||
int256(alphaNumerator),
|
||||
int256(alphaDenominator)
|
||||
)
|
||||
);
|
||||
n = LibFixedMath._invert(n);
|
||||
}
|
||||
// Multiply the above with totalRewards * stakeRatio
|
||||
ownerRewards = LibFixedMath._uintMul(
|
||||
LibFixedMath._mul(n, stakeRatio),
|
||||
|
@@ -25,11 +25,16 @@ library LibFixedMath {
|
||||
|
||||
// 1
|
||||
int256 private constant FIXED_1 = int256(0x0000000000000000000000000000000080000000000000000000000000000000);
|
||||
|
||||
// 1^2 (in fixed-point)
|
||||
int256 private constant FIXED_1_SQUARED = int256(0x4000000000000000000000000000000000000000000000000000000000000000);
|
||||
// 1
|
||||
int256 private constant LN_MAX_VAL = FIXED_1;
|
||||
// -69.07755278982137043720332303294494434957608235919531845909733017243007692132225692604324818191230406
|
||||
int256 private constant EXP_MIN_VAL = int256(0xffffffffffffffffffffffffffffffdd7612c00c0077ada1b83518e8cafc0e90);
|
||||
// e ^ -63.875
|
||||
int256 private constant LN_MIN_VAL = int256(0x0000000000000000000000000000000000000000000000000000000733048c5a);
|
||||
// 0
|
||||
int256 private constant EXP_MAX_VAL = 0;
|
||||
// -63.875
|
||||
int256 private constant EXP_MIN_VAL = -int256(0x0000000000000000000000000000001ff0000000000000000000000000000000);
|
||||
|
||||
/// @dev Get one as a fixed-point number.
|
||||
function _one() internal pure returns (int256 f) {
|
||||
@@ -56,6 +61,11 @@ library LibFixedMath {
|
||||
c = __div(__mul(a, FIXED_1), b);
|
||||
}
|
||||
|
||||
/// @dev Performs (a * n) / d, without scaling for precision.
|
||||
function _mulDiv(int256 a, int256 n, int256 d) internal pure returns (int256 c) {
|
||||
c = __div(__mul(a, n), d);
|
||||
}
|
||||
|
||||
/// @dev Returns the unsigned integer result of multiplying a fixed-point
|
||||
/// number with an integer, reverting if the multiplication overflows.
|
||||
/// Negative results are clamped to zero.
|
||||
@@ -74,14 +84,19 @@ library LibFixedMath {
|
||||
}
|
||||
|
||||
/// @dev Returns the absolute value of a fixed point number.
|
||||
function _abs(int256 a) internal pure returns (int256 c) {
|
||||
if (a >= 0) {
|
||||
c = a;
|
||||
function _abs(int256 f) internal pure returns (int256 c) {
|
||||
if (f >= 0) {
|
||||
c = f;
|
||||
} else {
|
||||
c = -a;
|
||||
c = -f;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Returns 1 / `x`, where `x` is a fixed-point number.
|
||||
function _invert(int256 f) internal pure returns (int256 c) {
|
||||
c = __div(FIXED_1_SQUARED, f);
|
||||
}
|
||||
|
||||
/// @dev Convert signed `n` / 1 to a fixed-point number.
|
||||
function _toFixed(int256 n) internal pure returns (int256 f) {
|
||||
f = __mul(n, FIXED_1);
|
||||
@@ -129,9 +144,6 @@ library LibFixedMath {
|
||||
|
||||
/// @dev Get the natural logarithm of a fixed-point number 0 < `x` <= LN_MAX_VAL
|
||||
function _ln(int256 x) internal pure returns (int256 r) {
|
||||
if (x == FIXED_1) {
|
||||
return 0;
|
||||
}
|
||||
if (x > LN_MAX_VAL) {
|
||||
LibRichErrors.rrevert(LibFixedMathRichErrors.FixedMathSignedValueError(
|
||||
LibFixedMathRichErrors.ValueErrorCodes.TOO_LARGE,
|
||||
@@ -144,58 +156,65 @@ library LibFixedMath {
|
||||
x
|
||||
));
|
||||
}
|
||||
if (x == FIXED_1) {
|
||||
return 0;
|
||||
}
|
||||
if (x <= LN_MIN_VAL) {
|
||||
return EXP_MIN_VAL;
|
||||
}
|
||||
|
||||
int256 y;
|
||||
int256 z;
|
||||
int256 w;
|
||||
|
||||
// Rewrite the input as a quotient of negative natural exponents and a single residual q, such that 1 < q < 2
|
||||
// For example: log(0.3) = log(e^-1 * e^-0.25 * 1.0471028872385522) = 1 - 0.25 - log(1 + 0.0471028872385522)
|
||||
// e ^ -64
|
||||
if (x <= int256(0x000000000000000000000000000000000000000000000000000000065a751cc8)) {
|
||||
r += int256(0xffffffffffffffffffffffffffffffe000000000000000000000000000000000); // - 64
|
||||
x = x * FIXED_1 / int256(0x000000000000000000000000000000000000000000000000000000065a751cc8); // / e ^ -64
|
||||
}
|
||||
// For example: log(0.3) = log(e^-1 * e^-0.25 * 1.0471028872385522)
|
||||
// = 1 - 0.25 - log(1 + 0.0471028872385522)
|
||||
// e ^ -32
|
||||
if (x <= int256(0x00000000000000000000000000000000000000000001c8464f76164760000000)) {
|
||||
r += int256(0xfffffffffffffffffffffffffffffff000000000000000000000000000000000); // - 32
|
||||
r -= int256(0x0000000000000000000000000000001000000000000000000000000000000000); // - 32
|
||||
x = x * FIXED_1 / int256(0x00000000000000000000000000000000000000000001c8464f76164760000000); // / e ^ -32
|
||||
}
|
||||
// e ^ -16
|
||||
if (x <= int256(0x00000000000000000000000000000000000000f1aaddd7742e90000000000000)) {
|
||||
r += int256(0xfffffffffffffffffffffffffffffff800000000000000000000000000000000); // - 16
|
||||
r -= int256(0x0000000000000000000000000000000800000000000000000000000000000000); // - 16
|
||||
x = x * FIXED_1 / int256(0x00000000000000000000000000000000000000f1aaddd7742e90000000000000); // / e ^ -16
|
||||
}
|
||||
// e ^ -8
|
||||
if (x <= int256(0x00000000000000000000000000000000000afe10820813d78000000000000000)) {
|
||||
r += int256(0xfffffffffffffffffffffffffffffffc00000000000000000000000000000000); // - 8
|
||||
r -= int256(0x0000000000000000000000000000000400000000000000000000000000000000); // - 8
|
||||
x = x * FIXED_1 / int256(0x00000000000000000000000000000000000afe10820813d78000000000000000); // / e ^ -8
|
||||
}
|
||||
// e ^ -4
|
||||
if (x <= int256(0x0000000000000000000000000000000002582ab704279ec00000000000000000)) {
|
||||
r += int256(0xfffffffffffffffffffffffffffffffe00000000000000000000000000000000); // - 4
|
||||
r -= int256(0x0000000000000000000000000000000200000000000000000000000000000000); // - 4
|
||||
x = x * FIXED_1 / int256(0x0000000000000000000000000000000002582ab704279ec00000000000000000); // / e ^ -4
|
||||
}
|
||||
// e ^ -2
|
||||
if (x <= int256(0x000000000000000000000000000000001152aaa3bf81cc000000000000000000)) {
|
||||
r += int256(0xffffffffffffffffffffffffffffffff00000000000000000000000000000000); // - 2
|
||||
r -= int256(0x0000000000000000000000000000000100000000000000000000000000000000); // - 2
|
||||
x = x * FIXED_1 / int256(0x000000000000000000000000000000001152aaa3bf81cc000000000000000000); // / e ^ -2
|
||||
}
|
||||
// e ^ -1
|
||||
if (x <= int256(0x000000000000000000000000000000002f16ac6c59de70000000000000000000)) {
|
||||
r += int256(0xffffffffffffffffffffffffffffffff80000000000000000000000000000000); // - 1
|
||||
r -= int256(0x0000000000000000000000000000000080000000000000000000000000000000); // - 1
|
||||
x = x * FIXED_1 / int256(0x000000000000000000000000000000002f16ac6c59de70000000000000000000); // / e ^ -1
|
||||
}
|
||||
// e ^ -0.5
|
||||
if (x <= int256(0x000000000000000000000000000000004da2cbf1be5828000000000000000000)) {
|
||||
r += int256(0xffffffffffffffffffffffffffffffffc0000000000000000000000000000000); // - 0.5
|
||||
r -= int256(0x0000000000000000000000000000000040000000000000000000000000000000); // - 0.5
|
||||
x = x * FIXED_1 / int256(0x000000000000000000000000000000004da2cbf1be5828000000000000000000); // / e ^ -0.5
|
||||
}
|
||||
// e ^ -0.25
|
||||
if (x <= int256(0x0000000000000000000000000000000063afbe7ab2082c000000000000000000)) {
|
||||
r += int256(0xffffffffffffffffffffffffffffffffe0000000000000000000000000000000); // - 0.25
|
||||
r -= int256(0x0000000000000000000000000000000020000000000000000000000000000000); // - 0.25
|
||||
x = x * FIXED_1 / int256(0x0000000000000000000000000000000063afbe7ab2082c000000000000000000); // / e ^ -0.25
|
||||
}
|
||||
// e ^ -0.125
|
||||
if (x <= int256(0x0000000000000000000000000000000070f5a893b608861e1f58934f97aea57d)) {
|
||||
r -= int256(0x0000000000000000000000000000000010000000000000000000000000000000); // - 0.125
|
||||
x = x * FIXED_1 / int256(0x0000000000000000000000000000000070f5a893b608861e1f58934f97aea57d); // / e ^ -0.125
|
||||
}
|
||||
// `x` is now our residual in the range of 1 <= x <= 2 (or close enough).
|
||||
|
||||
// Add the taylor series for log(1 + z), where z = x - 1
|
||||
@@ -213,102 +232,99 @@ library LibFixedMath {
|
||||
|
||||
/// @dev Compute the natural exponent for a fixed-point number EXP_MIN_VAL <= `x` <= 1
|
||||
function _exp(int256 x) internal pure returns (int256 r) {
|
||||
if (x <= EXP_MIN_VAL) {
|
||||
if (x < EXP_MIN_VAL) {
|
||||
// Saturate to zero below EXP_MIN_VAL.
|
||||
return 0;
|
||||
}
|
||||
if (x == 0) {
|
||||
return FIXED_1;
|
||||
}
|
||||
if (x > 0) {
|
||||
if (x > EXP_MAX_VAL) {
|
||||
LibRichErrors.rrevert(LibFixedMathRichErrors.FixedMathSignedValueError(
|
||||
LibFixedMathRichErrors.ValueErrorCodes.TOO_LARGE,
|
||||
x
|
||||
));
|
||||
}
|
||||
|
||||
// Rewrite the input as a product of positive natural exponents and a
|
||||
// Rewrite the input as a product of natural exponents and a
|
||||
// single residual q, where q is a number of small magnitude.
|
||||
// For example: e^-34.419 = e^(-32 - 2 - 0.419) = e^-32 * e^-2 * e^-0.419
|
||||
r = FIXED_1;
|
||||
// e ^ 64
|
||||
if (x <= -int256(0x0000000000000000000000000000002000000000000000000000000000000000)) {
|
||||
x += int256(0x0000000000000000000000000000002000000000000000000000000000000000); // + 64
|
||||
r = r * int256(0x000000000000000000000000000000000000000000000000000000065a751cc8) / FIXED_1; // * e ^ 64
|
||||
}
|
||||
// e ^ 32
|
||||
if (x <= -int256(0x0000000000000000000000000000001000000000000000000000000000000000)) {
|
||||
x += int256(0x0000000000000000000000000000001000000000000000000000000000000000); // + 32
|
||||
r = r * int256(0x00000000000000000000000000000000000000000001c8464f76164681e299a0) / FIXED_1; // * e ^ 32
|
||||
}
|
||||
// e ^ 16
|
||||
if (x <= -int256(0x0000000000000000000000000000000800000000000000000000000000000000)) {
|
||||
x += int256(0x0000000000000000000000000000000800000000000000000000000000000000); // + 16
|
||||
r = r * int256(0x00000000000000000000000000000000000000f1aaddd7742e56d32fb9f99744) / FIXED_1; // * e ^ 16
|
||||
}
|
||||
// e ^ 8
|
||||
if (x <= -int256(0x0000000000000000000000000000000400000000000000000000000000000000)) {
|
||||
x += int256(0x0000000000000000000000000000000400000000000000000000000000000000); // + 8
|
||||
r = r * int256(0x00000000000000000000000000000000000afe10820813d65dfe6a33c07f738f) / FIXED_1; // * e ^ 8
|
||||
}
|
||||
// e ^ 4
|
||||
if (x <= -int256(0x0000000000000000000000000000000200000000000000000000000000000000)) {
|
||||
x += int256(0x0000000000000000000000000000000200000000000000000000000000000000); // + 4
|
||||
r = r * int256(0x0000000000000000000000000000000002582ab704279e8efd15e0265855c47a) / FIXED_1; // * e ^ 4
|
||||
}
|
||||
// e ^ 2
|
||||
if (x <= -int256(0x0000000000000000000000000000000100000000000000000000000000000000)) {
|
||||
x += int256(0x0000000000000000000000000000000100000000000000000000000000000000); // + 2
|
||||
r = r * int256(0x000000000000000000000000000000001152aaa3bf81cb9fdb76eae12d029571) / FIXED_1; // * e ^ 2
|
||||
}
|
||||
// e ^ 1
|
||||
if (x <= -int256(0x0000000000000000000000000000000080000000000000000000000000000000)) {
|
||||
x += int256(0x0000000000000000000000000000000080000000000000000000000000000000); // + 1
|
||||
r = r * int256(0x000000000000000000000000000000002f16ac6c59de6f8d5d6f63c1482a7c86) / FIXED_1; // * e ^ 1
|
||||
}
|
||||
// e ^ 0.5
|
||||
if (x <= -int256(0x0000000000000000000000000000000040000000000000000000000000000000)) {
|
||||
x += int256(0x0000000000000000000000000000000040000000000000000000000000000000); // + 0.5
|
||||
r = r * int256(0x000000000000000000000000000000004da2cbf1be5827f9eb3ad1aa9866ebb3) / FIXED_1; // * e ^ 0.5
|
||||
}
|
||||
// e ^ 0.25
|
||||
if (x <= -int256(0x0000000000000000000000000000000020000000000000000000000000000000)) {
|
||||
x += int256(0x0000000000000000000000000000000020000000000000000000000000000000); // + 0.25
|
||||
r = r * int256(0x0000000000000000000000000000000063afbe7ab2082ba1a0ae5e4eb1b479dc) / FIXED_1; // * e ^ 0.25
|
||||
}
|
||||
// e ^ 0.125
|
||||
if (x <= -int256(0x0000000000000000000000000000000010000000000000000000000000000000)) {
|
||||
x += int256(0x0000000000000000000000000000000010000000000000000000000000000000); // + 0.125
|
||||
r = r * int256(0x0000000000000000000000000000000070f5a893b608861e1f58934f97aea57d) / FIXED_1; // * e ^ 0.125
|
||||
}
|
||||
// x is now a small residual close to 0
|
||||
// For example: e^-34.419 = e^(-32 - 2 - 0.25 - 0.125 - 0.044)
|
||||
// = e^-32 * e^-2 * e^-0.25 * e^-0.125 * e^-0.044
|
||||
// -> q = -0.044
|
||||
|
||||
// Multiply with the taylor series for e^q
|
||||
int256 y;
|
||||
int256 z;
|
||||
int256 t;
|
||||
z = y = x;
|
||||
z = z * y / FIXED_1; t += z * 0x10e1b3be415a0000; // add y^02 * (20! / 02!)
|
||||
z = z * y / FIXED_1; t += z * 0x05a0913f6b1e0000; // add y^03 * (20! / 03!)
|
||||
z = z * y / FIXED_1; t += z * 0x0168244fdac78000; // add y^04 * (20! / 04!)
|
||||
z = z * y / FIXED_1; t += z * 0x004807432bc18000; // add y^05 * (20! / 05!)
|
||||
z = z * y / FIXED_1; t += z * 0x000c0135dca04000; // add y^06 * (20! / 06!)
|
||||
z = z * y / FIXED_1; t += z * 0x0001b707b1cdc000; // add y^07 * (20! / 07!)
|
||||
z = z * y / FIXED_1; t += z * 0x000036e0f639b800; // add y^08 * (20! / 08!)
|
||||
z = z * y / FIXED_1; t += z * 0x00000618fee9f800; // add y^09 * (20! / 09!)
|
||||
z = z * y / FIXED_1; t += z * 0x0000009c197dcc00; // add y^10 * (20! / 10!)
|
||||
z = z * y / FIXED_1; t += z * 0x0000000e30dce400; // add y^11 * (20! / 11!)
|
||||
z = z * y / FIXED_1; t += z * 0x000000012ebd1300; // add y^12 * (20! / 12!)
|
||||
z = z * y / FIXED_1; t += z * 0x0000000017499f00; // add y^13 * (20! / 13!)
|
||||
z = z * y / FIXED_1; t += z * 0x0000000001a9d480; // add y^14 * (20! / 14!)
|
||||
z = z * y / FIXED_1; t += z * 0x00000000001c6380; // add y^15 * (20! / 15!)
|
||||
z = z * y / FIXED_1; t += z * 0x000000000001c638; // add y^16 * (20! / 16!)
|
||||
z = z * y / FIXED_1; t += z * 0x0000000000001ab8; // add y^17 * (20! / 17!)
|
||||
z = z * y / FIXED_1; t += z * 0x000000000000017c; // add y^18 * (20! / 18!)
|
||||
z = z * y / FIXED_1; t += z * 0x0000000000000014; // add y^19 * (20! / 19!)
|
||||
z = z * y / FIXED_1; t += z * 0x0000000000000001; // add y^20 * (20! / 20!)
|
||||
t = t / 0x21c3677c82b40000 + y + FIXED_1; // divide by 20! and then add y^1 / 1! + y^0 / 0!
|
||||
r = r * t / FIXED_1;
|
||||
// q = x % 0.125
|
||||
z = y = x % 0x0000000000000000000000000000000010000000000000000000000000000000;
|
||||
z = z * y / FIXED_1; r += z * 0x10e1b3be415a0000; // add y^02 * (20! / 02!)
|
||||
z = z * y / FIXED_1; r += z * 0x05a0913f6b1e0000; // add y^03 * (20! / 03!)
|
||||
z = z * y / FIXED_1; r += z * 0x0168244fdac78000; // add y^04 * (20! / 04!)
|
||||
z = z * y / FIXED_1; r += z * 0x004807432bc18000; // add y^05 * (20! / 05!)
|
||||
z = z * y / FIXED_1; r += z * 0x000c0135dca04000; // add y^06 * (20! / 06!)
|
||||
z = z * y / FIXED_1; r += z * 0x0001b707b1cdc000; // add y^07 * (20! / 07!)
|
||||
z = z * y / FIXED_1; r += z * 0x000036e0f639b800; // add y^08 * (20! / 08!)
|
||||
z = z * y / FIXED_1; r += z * 0x00000618fee9f800; // add y^09 * (20! / 09!)
|
||||
z = z * y / FIXED_1; r += z * 0x0000009c197dcc00; // add y^10 * (20! / 10!)
|
||||
z = z * y / FIXED_1; r += z * 0x0000000e30dce400; // add y^11 * (20! / 11!)
|
||||
z = z * y / FIXED_1; r += z * 0x000000012ebd1300; // add y^12 * (20! / 12!)
|
||||
z = z * y / FIXED_1; r += z * 0x0000000017499f00; // add y^13 * (20! / 13!)
|
||||
z = z * y / FIXED_1; r += z * 0x0000000001a9d480; // add y^14 * (20! / 14!)
|
||||
z = z * y / FIXED_1; r += z * 0x00000000001c6380; // add y^15 * (20! / 15!)
|
||||
z = z * y / FIXED_1; r += z * 0x000000000001c638; // add y^16 * (20! / 16!)
|
||||
z = z * y / FIXED_1; r += z * 0x0000000000001ab8; // add y^17 * (20! / 17!)
|
||||
z = z * y / FIXED_1; r += z * 0x000000000000017c; // add y^18 * (20! / 18!)
|
||||
z = z * y / FIXED_1; r += z * 0x0000000000000014; // add y^19 * (20! / 19!)
|
||||
z = z * y / FIXED_1; r += z * 0x0000000000000001; // add y^20 * (20! / 20!)
|
||||
r = r / 0x21c3677c82b40000 + y + FIXED_1; // divide by 20! and then add y^1 / 1! + y^0 / 0!
|
||||
|
||||
// Multiply with the non-residual terms.
|
||||
x = -x;
|
||||
// e ^ -32
|
||||
if ((x & int256(0x0000000000000000000000000000001000000000000000000000000000000000)) != 0) {
|
||||
r = r * int256(0x00000000000000000000000000000000000000f1aaddd7742e56d32fb9f99744)
|
||||
/ int256(0x0000000000000000000000000043cbaf42a000812488fc5c220ad7b97bf6e99e); // * e ^ -32
|
||||
}
|
||||
// e ^ -16
|
||||
if ((x & int256(0x0000000000000000000000000000000800000000000000000000000000000000)) != 0) {
|
||||
r = r * int256(0x00000000000000000000000000000000000afe10820813d65dfe6a33c07f738f)
|
||||
/ int256(0x000000000000000000000000000005d27a9f51c31b7c2f8038212a0574779991); // * e ^ -16
|
||||
}
|
||||
// e ^ -8
|
||||
if ((x & int256(0x0000000000000000000000000000000400000000000000000000000000000000)) != 0) {
|
||||
r = r * int256(0x0000000000000000000000000000000002582ab704279e8efd15e0265855c47a)
|
||||
/ int256(0x0000000000000000000000000000001b4c902e273a58678d6d3bfdb93db96d02); // * e ^ -8
|
||||
}
|
||||
// e ^ -4
|
||||
if ((x & int256(0x0000000000000000000000000000000200000000000000000000000000000000)) != 0) {
|
||||
r = r * int256(0x000000000000000000000000000000001152aaa3bf81cb9fdb76eae12d029571)
|
||||
/ int256(0x00000000000000000000000000000003b1cc971a9bb5b9867477440d6d157750); // * e ^ -4
|
||||
}
|
||||
// e ^ -2
|
||||
if ((x & int256(0x0000000000000000000000000000000100000000000000000000000000000000)) != 0) {
|
||||
r = r * int256(0x000000000000000000000000000000002f16ac6c59de6f8d5d6f63c1482a7c86)
|
||||
/ int256(0x000000000000000000000000000000015bf0a8b1457695355fb8ac404e7a79e3); // * e ^ -2
|
||||
}
|
||||
// e ^ -1
|
||||
if ((x & int256(0x0000000000000000000000000000000080000000000000000000000000000000)) != 0) {
|
||||
r = r * int256(0x000000000000000000000000000000004da2cbf1be5827f9eb3ad1aa9866ebb3)
|
||||
/ int256(0x00000000000000000000000000000000d3094c70f034de4b96ff7d5b6f99fcd8); // * e ^ -1
|
||||
}
|
||||
// e ^ -0.5
|
||||
if ((x & int256(0x0000000000000000000000000000000040000000000000000000000000000000)) != 0) {
|
||||
r = r * int256(0x0000000000000000000000000000000063afbe7ab2082ba1a0ae5e4eb1b479dc)
|
||||
/ int256(0x00000000000000000000000000000000a45af1e1f40c333b3de1db4dd55f29a7); // * e ^ -0.5
|
||||
}
|
||||
// e ^ -0.25
|
||||
if ((x & int256(0x0000000000000000000000000000000020000000000000000000000000000000)) != 0) {
|
||||
r = r * int256(0x0000000000000000000000000000000070f5a893b608861e1f58934f97aea57d)
|
||||
/ int256(0x00000000000000000000000000000000910b022db7ae67ce76b441c27035c6a1); // * e ^ -0.25
|
||||
}
|
||||
// e ^ -0.125
|
||||
if ((x & int256(0x0000000000000000000000000000000010000000000000000000000000000000)) != 0) {
|
||||
r = r * int256(0x00000000000000000000000000000000783eafef1c0a8f3978c7f81824d62ebf)
|
||||
/ int256(0x0000000000000000000000000000000088415abbe9a76bead8d00cf112e4d4a8); // * e ^ -0.125
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Returns the multiplication two numbers, reverting on overflow.
|
||||
|
@@ -25,6 +25,10 @@ contract TestLibFixedMath {
|
||||
function one() external pure returns (int256) {
|
||||
return LibFixedMath._one();
|
||||
}
|
||||
|
||||
function mulDiv(int256 a, int256 n, int256 d) external pure returns (int256) {
|
||||
return LibFixedMath._mulDiv(a, n, d);
|
||||
}
|
||||
|
||||
function mul(int256 a, int256 b) external pure returns (int256) {
|
||||
return LibFixedMath._mul(a, b);
|
||||
@@ -50,6 +54,10 @@ contract TestLibFixedMath {
|
||||
return LibFixedMath._abs(a);
|
||||
}
|
||||
|
||||
function invert(int256 a) external pure returns (int256) {
|
||||
return LibFixedMath._invert(a);
|
||||
}
|
||||
|
||||
function toFixedSigned(int256 n, int256 d) external pure returns (int256) {
|
||||
return LibFixedMath._toFixed(n, d);
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@
|
||||
"test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov",
|
||||
"test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html",
|
||||
"test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha",
|
||||
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit",
|
||||
"run_mocha": "UNLIMITED_CONTRACT_SIZE=true mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit",
|
||||
"compile": "sol-compiler",
|
||||
"watch": "sol-compiler -w",
|
||||
"clean": "shx rm -rf lib generated-artifacts generated-wrappers",
|
||||
|
@@ -1,8 +1,271 @@
|
||||
import { blockchainTests, expect } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { blockchainTests, expect, hexRandom } from '@0x/contracts-test-utils';
|
||||
import { AnyRevertError, BigNumber, FixedMathRevertErrors } from '@0x/utils';
|
||||
import { Decimal } from 'decimal.js';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts, TestCobbDouglasContract } from '../src/';
|
||||
|
||||
Decimal.set({ precision: 128 });
|
||||
type Numberish = BigNumber | string | number;
|
||||
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
blockchainTests('Cobb-Douglas', env => {
|
||||
const FUZZ_COUNT = 1024;
|
||||
|
||||
let testContract: TestCobbDouglasContract;
|
||||
|
||||
before(async () => {
|
||||
testContract = await TestCobbDouglasContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestCobbDouglas,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
function toPrecision(n: Numberish, precision: number): BigNumber {
|
||||
const _n = new BigNumber(n);
|
||||
const integerDigits = _n.integerValue().sd(true);
|
||||
const base = 10 ** (precision - integerDigits);
|
||||
return _n.times(base).integerValue(BigNumber.ROUND_FLOOR).dividedBy(base);
|
||||
}
|
||||
|
||||
function toDecimal(x: Numberish): Decimal {
|
||||
if (BigNumber.isBigNumber(x)) {
|
||||
return new Decimal(x.toString(10));
|
||||
}
|
||||
return new Decimal(x);
|
||||
}
|
||||
|
||||
function assertRoughlyEquals(
|
||||
actual: Numberish,
|
||||
expected: Numberish,
|
||||
precision: number = 14,
|
||||
): void {
|
||||
// SD is not what we want.
|
||||
expect(toPrecision(actual, precision)).to.bignumber.eq(toPrecision(expected, precision));
|
||||
}
|
||||
|
||||
function getRandomAmount(min: Numberish, max: Numberish): BigNumber {
|
||||
const range = new BigNumber(max).minus(min);
|
||||
const random = new BigNumber(hexRandom().substr(2), 16);
|
||||
return random.mod(range).plus(min);
|
||||
}
|
||||
|
||||
function getRandomPortion(total: Numberish): BigNumber {
|
||||
return new BigNumber(total).times(Math.random()).integerValue();
|
||||
}
|
||||
|
||||
describe('cobbDouglas()', () => {
|
||||
interface CobbDouglasParams {
|
||||
totalRewards: Numberish;
|
||||
ownerFees: Numberish;
|
||||
totalFees: Numberish;
|
||||
ownerStake: Numberish;
|
||||
totalStake: Numberish;
|
||||
alphaNumerator: Numberish;
|
||||
alphaDenominator: Numberish;
|
||||
gas?: number;
|
||||
}
|
||||
|
||||
const MAX_COBB_DOUGLAS_GAS = 15e3;
|
||||
const TX_GAS_FEE = 21e3;
|
||||
const DEFAULT_COBB_DOUGLAS_PARAMS: CobbDouglasParams = {
|
||||
totalRewards: 100e18,
|
||||
ownerFees: 10e18,
|
||||
totalFees: 500e18,
|
||||
ownerStake: 1.1e21,
|
||||
totalStake: 3e27,
|
||||
alphaNumerator: 1,
|
||||
alphaDenominator: 3,
|
||||
gas: MAX_COBB_DOUGLAS_GAS,
|
||||
};
|
||||
|
||||
async function callCobbDouglasAsync(params?: Partial<CobbDouglasParams>): Promise<BigNumber> {
|
||||
const _params = {
|
||||
...DEFAULT_COBB_DOUGLAS_PARAMS,
|
||||
...params,
|
||||
};
|
||||
return testContract.cobbDouglas.callAsync(
|
||||
new BigNumber(_params.totalRewards),
|
||||
new BigNumber(_params.ownerFees),
|
||||
new BigNumber(_params.totalFees),
|
||||
new BigNumber(_params.ownerStake),
|
||||
new BigNumber(_params.totalStake),
|
||||
new BigNumber(_params.alphaNumerator),
|
||||
new BigNumber(_params.alphaDenominator),
|
||||
{
|
||||
gas: TX_GAS_FEE + (_params.gas === undefined ? MAX_COBB_DOUGLAS_GAS : _params.gas),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function cobbDouglas(params?: Partial<CobbDouglasParams>): BigNumber {
|
||||
const {
|
||||
totalRewards,
|
||||
ownerFees,
|
||||
totalFees,
|
||||
ownerStake,
|
||||
totalStake,
|
||||
alphaNumerator,
|
||||
alphaDenominator,
|
||||
} = {
|
||||
...DEFAULT_COBB_DOUGLAS_PARAMS,
|
||||
...params,
|
||||
};
|
||||
const feeRatio = toDecimal(ownerFees).dividedBy(toDecimal(totalFees));
|
||||
const stakeRatio = toDecimal(ownerStake).dividedBy(toDecimal(totalStake));
|
||||
const alpha = toDecimal(alphaNumerator).dividedBy(toDecimal(alphaDenominator));
|
||||
// totalRewards * feeRatio ^ alpha * stakeRatio ^ (1-alpha)
|
||||
return new BigNumber(
|
||||
feeRatio.pow(alpha).times(
|
||||
stakeRatio.pow(new Decimal(1).minus(alpha)),
|
||||
).times(toDecimal(totalRewards)).toFixed(0, BigNumber.ROUND_FLOOR),
|
||||
);
|
||||
}
|
||||
|
||||
function getRandomParams(overrides?: Partial<CobbDouglasParams>): CobbDouglasParams {
|
||||
const totalRewards = _.get(overrides, 'totalRewards', getRandomAmount(0, 1e27)) as Numberish;
|
||||
const totalFees = _.get(overrides, 'totalFees', getRandomAmount(1, 1e27)) as Numberish;
|
||||
const ownerFees = _.get(overrides, 'ownerFees', getRandomPortion(totalFees)) as Numberish;
|
||||
const totalStake = _.get(overrides, 'totalStake', getRandomAmount(1, 1e27)) as Numberish;
|
||||
const ownerStake = _.get(overrides, 'ownerStake', getRandomPortion(totalStake)) as Numberish;
|
||||
const alphaDenominator = _.get(overrides, 'alphaDenominator', getRandomAmount(1, 1e6)) as Numberish;
|
||||
const alphaNumerator = _.get(overrides, 'alphaNumerator', getRandomPortion(alphaDenominator)) as Numberish;
|
||||
return {
|
||||
totalRewards,
|
||||
ownerFees,
|
||||
totalFees,
|
||||
ownerStake,
|
||||
totalStake,
|
||||
alphaNumerator,
|
||||
alphaDenominator,
|
||||
};
|
||||
}
|
||||
|
||||
it('throws if `alphaNumerator` > `alphaDenominator`', async () => {
|
||||
return expect(callCobbDouglasAsync({
|
||||
alphaNumerator: 11,
|
||||
alphaDenominator: 10,
|
||||
})).to.revertWith(new AnyRevertError()); // Just an assertion failure.
|
||||
});
|
||||
|
||||
it('throws if `ownerFees` > `totalFees`', async () => {
|
||||
const expectedError = new FixedMathRevertErrors.FixedMathSignedValueError(
|
||||
FixedMathRevertErrors.ValueErrorCodes.TooLarge,
|
||||
);
|
||||
return expect(callCobbDouglasAsync({
|
||||
ownerFees: 11,
|
||||
totalFees: 10,
|
||||
})).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
it('throws if `ownerStake` > `totalStake`', async () => {
|
||||
const expectedError = new FixedMathRevertErrors.FixedMathSignedValueError(
|
||||
FixedMathRevertErrors.ValueErrorCodes.TooLarge,
|
||||
);
|
||||
return expect(callCobbDouglasAsync({
|
||||
ownerStake: 11,
|
||||
totalStake: 10,
|
||||
})).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
it('computes the correct reward', async () => {
|
||||
const expected = cobbDouglas();
|
||||
const r = await callCobbDouglasAsync();
|
||||
assertRoughlyEquals(r, expected);
|
||||
});
|
||||
|
||||
it('computes the correct reward with zero stake ratio', async () => {
|
||||
const ownerStake = 0;
|
||||
const expected = cobbDouglas({ ownerStake });
|
||||
const r = await callCobbDouglasAsync({ ownerStake });
|
||||
assertRoughlyEquals(r, expected);
|
||||
});
|
||||
|
||||
it('computes the correct reward with full stake ratio', async () => {
|
||||
const ownerStake = DEFAULT_COBB_DOUGLAS_PARAMS.totalStake;
|
||||
const expected = cobbDouglas({ ownerStake });
|
||||
const r = await callCobbDouglasAsync({ ownerStake });
|
||||
assertRoughlyEquals(r, expected);
|
||||
});
|
||||
|
||||
it('computes the correct reward with a very low stake ratio', async () => {
|
||||
const ownerStake = new BigNumber(DEFAULT_COBB_DOUGLAS_PARAMS.totalStake).times(1e-18);
|
||||
const expected = cobbDouglas({ ownerStake });
|
||||
const r = await callCobbDouglasAsync({ ownerStake });
|
||||
assertRoughlyEquals(r, expected);
|
||||
});
|
||||
|
||||
it('computes the correct reward with a very high stake ratio', async () => {
|
||||
const ownerStake = new BigNumber(DEFAULT_COBB_DOUGLAS_PARAMS.totalStake).times(1 - 1e-18);
|
||||
const expected = cobbDouglas({ ownerStake });
|
||||
const r = await callCobbDouglasAsync({ ownerStake });
|
||||
assertRoughlyEquals(r, expected);
|
||||
});
|
||||
|
||||
it('computes the correct reward with zero fee ratio', async () => {
|
||||
const ownerFees = 0;
|
||||
const expected = cobbDouglas({ ownerFees });
|
||||
const r = await callCobbDouglasAsync({ ownerFees });
|
||||
assertRoughlyEquals(r, expected);
|
||||
});
|
||||
|
||||
it('computes the correct reward with full fee ratio', async () => {
|
||||
const ownerFees = DEFAULT_COBB_DOUGLAS_PARAMS.totalFees;
|
||||
const expected = cobbDouglas({ ownerFees });
|
||||
const r = await callCobbDouglasAsync({ ownerFees });
|
||||
assertRoughlyEquals(r, expected);
|
||||
});
|
||||
|
||||
it('computes the correct reward with a very low fee ratio', async () => {
|
||||
const ownerFees = new BigNumber(DEFAULT_COBB_DOUGLAS_PARAMS.totalFees).times(1e-18);
|
||||
const expected = cobbDouglas({ ownerFees });
|
||||
const r = await callCobbDouglasAsync({ ownerFees });
|
||||
assertRoughlyEquals(r, expected);
|
||||
});
|
||||
|
||||
it('computes the correct reward with a very high fee ratio', async () => {
|
||||
const ownerFees = new BigNumber(DEFAULT_COBB_DOUGLAS_PARAMS.totalFees).times(1 - 1e-18);
|
||||
const expected = cobbDouglas({ ownerFees });
|
||||
const r = await callCobbDouglasAsync({ ownerFees });
|
||||
assertRoughlyEquals(r, expected);
|
||||
});
|
||||
|
||||
it('computes the correct reward with equal fee and stake ratios', async () => {
|
||||
const ownerFees = new BigNumber(DEFAULT_COBB_DOUGLAS_PARAMS.totalFees).times(0.5);
|
||||
const ownerStake = new BigNumber(DEFAULT_COBB_DOUGLAS_PARAMS.totalStake).times(0.5);
|
||||
const expected = cobbDouglas({ ownerFees, ownerStake });
|
||||
const r = await callCobbDouglasAsync({ ownerFees, ownerStake });
|
||||
assertRoughlyEquals(r, expected);
|
||||
});
|
||||
|
||||
it('computes the correct reward with full fee and stake ratios', async () => {
|
||||
const ownerFees = new BigNumber(DEFAULT_COBB_DOUGLAS_PARAMS.totalFees);
|
||||
const ownerStake = new BigNumber(DEFAULT_COBB_DOUGLAS_PARAMS.totalStake);
|
||||
const expected = cobbDouglas({ ownerFees, ownerStake });
|
||||
const r = await callCobbDouglasAsync({ ownerFees, ownerStake });
|
||||
assertRoughlyEquals(r, expected);
|
||||
});
|
||||
|
||||
it('computes the correct reward with zero fee and stake ratios', async () => {
|
||||
const ownerFees = 0;
|
||||
const ownerStake = 0;
|
||||
const expected = cobbDouglas({ ownerFees, ownerStake });
|
||||
const r = await callCobbDouglasAsync({ ownerFees, ownerStake });
|
||||
assertRoughlyEquals(r, expected);
|
||||
});
|
||||
|
||||
blockchainTests.optional('fuzzing', () => {
|
||||
const inputs = _.times(FUZZ_COUNT, () => getRandomParams());
|
||||
for (const params of inputs) {
|
||||
it(`cobbDouglas(${JSON.stringify(params)})`, async () => {
|
||||
const expected = cobbDouglas(params);
|
||||
const r = await callCobbDouglasAsync(params);
|
||||
assertRoughlyEquals(r, expected, 12);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:enable:no-unnecessary-type-assertion
|
||||
|
@@ -9,11 +9,25 @@ Decimal.set({ precision: 128 });
|
||||
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
blockchainTests('LibFixedMath', env => {
|
||||
let testContract: TestLibFixedMathContract;
|
||||
|
||||
before(async () => {
|
||||
testContract = await TestLibFixedMathContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestLibFixedMath,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
const BITS_OF_PRECISION = 127;
|
||||
const FIXED_POINT_DIVISOR = new BigNumber(2).pow(BITS_OF_PRECISION);
|
||||
const MAX_FIXED_VALUE = new BigNumber(2).pow(255).minus(1);
|
||||
const MIN_FIXED_VALUE = new BigNumber(2).pow(255).times(-1);
|
||||
const MIN_EXP_NUMBER = new BigNumber('-69.07755278982137043720332303294494434957608235919531845909733017243007692132225692604324818191230406');
|
||||
const MIN_EXP_NUMBER = new BigNumber('-63.875');
|
||||
const MAX_EXP_NUMBER = new BigNumber(0);
|
||||
// e ^ MIN_EXP_NUMBER
|
||||
const MIN_LN_NUMBER = new BigNumber(new Decimal(MIN_EXP_NUMBER.toFixed(128)).exp().toFixed(128));
|
||||
const FUZZ_COUNT = 1024;
|
||||
|
||||
type Numberish = BigNumber | string | number;
|
||||
@@ -75,33 +89,22 @@ blockchainTests('LibFixedMath', env => {
|
||||
}
|
||||
|
||||
function assertFixedEquals(
|
||||
actual: BigNumber | string | number,
|
||||
expected: BigNumber | string | number,
|
||||
actual: Numberish,
|
||||
expected: Numberish,
|
||||
): void {
|
||||
expect(fromFixed(actual)).to.bignumber.eq(numberToFixedToNumber(expected));
|
||||
}
|
||||
|
||||
function assertFixedRoughlyEquals(
|
||||
actual: BigNumber | string | number,
|
||||
expected: BigNumber | string | number,
|
||||
precision: number = 13,
|
||||
actual: Numberish,
|
||||
expected: Numberish,
|
||||
precision: number = 18,
|
||||
): void {
|
||||
// SD is not what we want.
|
||||
expect(toPrecision(fromFixed(actual), precision))
|
||||
.to.bignumber.eq(toPrecision(numberToFixedToNumber(expected), precision));
|
||||
}
|
||||
|
||||
let testContract: TestLibFixedMathContract;
|
||||
|
||||
before(async () => {
|
||||
testContract = await TestLibFixedMathContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestLibFixedMath,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
describe('one()', () => {
|
||||
it('equals 1', async () => {
|
||||
const r = await testContract.one.callAsync();
|
||||
@@ -129,6 +132,75 @@ blockchainTests('LibFixedMath', env => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('invert()', () => {
|
||||
it('invert(1) == 1', async () => {
|
||||
const n = 1;
|
||||
const r = await testContract.invert.callAsync(toFixed(n));
|
||||
assertFixedEquals(r, n);
|
||||
});
|
||||
|
||||
it('invert(n) == 1 / n', async () => {
|
||||
const n = 1337.5912;
|
||||
const r = await testContract.invert.callAsync(toFixed(n));
|
||||
assertFixedRoughlyEquals(r, 1 / n);
|
||||
});
|
||||
|
||||
it('invert(-n) == -1 / n', async () => {
|
||||
const n = -1337.5912;
|
||||
const r = await testContract.invert.callAsync(toFixed(n));
|
||||
assertFixedRoughlyEquals(r, 1 / n);
|
||||
});
|
||||
|
||||
it('invert(0) throws', async () => {
|
||||
const expectedError = new FixedMathRevertErrors.FixedMathBinOpError(
|
||||
FixedMathRevertErrors.BinOpErrorCodes.DivisionByZero,
|
||||
);
|
||||
const tx = testContract.invert.callAsync(toFixed(0));
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mulDiv()', () => {
|
||||
it('mulDiv(0, 0, 1) == 0', async () => {
|
||||
const [ a, n, d ] = [ 0, 0, 1 ];
|
||||
const r = await testContract.mulDiv.callAsync(toFixed(a), new BigNumber(n), new BigNumber(d));
|
||||
assertFixedEquals(r, 0);
|
||||
});
|
||||
|
||||
it('mulDiv(0, x, y) == 0', async () => {
|
||||
const [ a, n, d ] = [ 0, 13, 300 ];
|
||||
const r = await testContract.mulDiv.callAsync(toFixed(a), new BigNumber(n), new BigNumber(d));
|
||||
assertFixedEquals(r, 0);
|
||||
});
|
||||
|
||||
it('mulDiv(x, y, y) == x', async () => {
|
||||
const [ a, n, d ] = [ 1.2345, 149, 149 ];
|
||||
const r = await testContract.mulDiv.callAsync(toFixed(a), new BigNumber(n), new BigNumber(d));
|
||||
assertFixedEquals(r, a);
|
||||
});
|
||||
|
||||
it('mulDiv(x, -y, y) == -x', async () => {
|
||||
const [ a, n, d ] = [ 1.2345, -149, 149 ];
|
||||
const r = await testContract.mulDiv.callAsync(toFixed(a), new BigNumber(n), new BigNumber(d));
|
||||
assertFixedEquals(r, -a);
|
||||
});
|
||||
|
||||
it('mulDiv(-x, -y, y) == x', async () => {
|
||||
const [ a, n, d ] = [ -1.2345, -149, 149 ];
|
||||
const r = await testContract.mulDiv.callAsync(toFixed(a), new BigNumber(n), new BigNumber(d));
|
||||
assertFixedEquals(r, -a);
|
||||
});
|
||||
|
||||
it('mulDiv(x, y, 0) throws', async () => {
|
||||
const [ a, n, d ] = [ 1.2345, 149, 0 ];
|
||||
const expectedError = new FixedMathRevertErrors.FixedMathBinOpError(
|
||||
FixedMathRevertErrors.BinOpErrorCodes.DivisionByZero,
|
||||
);
|
||||
const tx = testContract.mulDiv.callAsync(toFixed(a), new BigNumber(n), new BigNumber(d));
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('add()', () => {
|
||||
it('0 + 0 == 0', async () => {
|
||||
const [ a, b ] = [ 0, 0 ];
|
||||
@@ -508,6 +580,8 @@ blockchainTests('LibFixedMath', env => {
|
||||
});
|
||||
|
||||
describe('ln()', () => {
|
||||
const LN_PRECISION = 13;
|
||||
|
||||
it('ln(x = 0) throws', async () => {
|
||||
const x = toFixed(0);
|
||||
const expectedError = new FixedMathRevertErrors.FixedMathSignedValueError(
|
||||
@@ -544,22 +618,28 @@ blockchainTests('LibFixedMath', env => {
|
||||
assertFixedEquals(r, 0);
|
||||
});
|
||||
|
||||
it('ln(x < LN_MIN_VAL) == EXP_MIN_VAL', async () => {
|
||||
const x = toFixed(MIN_LN_NUMBER).minus(1);
|
||||
const r = await testContract.ln.callAsync(x);
|
||||
assertFixedEquals(r, MIN_EXP_NUMBER);
|
||||
});
|
||||
|
||||
it('ln(x), where x is close to 0', async () => {
|
||||
const x = new BigNumber('1e-27');
|
||||
const r = await testContract.ln.callAsync(toFixed(x));
|
||||
assertFixedRoughlyEquals(r, ln(x));
|
||||
assertFixedRoughlyEquals(r, ln(x), LN_PRECISION);
|
||||
});
|
||||
|
||||
it('ln(x), where x is close to 1', async () => {
|
||||
const x = new BigNumber(1).minus('1e-27');
|
||||
const r = await testContract.ln.callAsync(toFixed(x));
|
||||
assertFixedRoughlyEquals(r, ln(x));
|
||||
assertFixedRoughlyEquals(r, ln(x), LN_PRECISION);
|
||||
});
|
||||
|
||||
it('ln(x = 0.5)', async () => {
|
||||
const x = 0.5;
|
||||
it('ln(x = 0.85)', async () => {
|
||||
const x = 0.85;
|
||||
const r = await testContract.ln.callAsync(toFixed(x));
|
||||
assertFixedRoughlyEquals(r, ln(x));
|
||||
assertFixedRoughlyEquals(r, ln(x), LN_PRECISION);
|
||||
});
|
||||
|
||||
blockchainTests.optional('fuzzing', () => {
|
||||
@@ -567,21 +647,23 @@ blockchainTests('LibFixedMath', env => {
|
||||
for (const x of inputs) {
|
||||
it(`ln(${x.toString(10)})`, async () => {
|
||||
const r = await testContract.ln.callAsync(toFixed(x));
|
||||
assertFixedRoughlyEquals(r, ln(x), 11);
|
||||
assertFixedRoughlyEquals(r, ln(x), LN_PRECISION);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('exp()', () => {
|
||||
const EXP_PRECISION = 18;
|
||||
|
||||
it('exp(x = 0) == 1', async () => {
|
||||
const x = toFixed(0);
|
||||
const r = await testContract.exp.callAsync(x);
|
||||
assertFixedEquals(r, 1);
|
||||
});
|
||||
|
||||
it('exp(x > 0) throws', async () => {
|
||||
const x = toFixed(1e-18);
|
||||
it('exp(x > EXP_MAX_VAL) throws', async () => {
|
||||
const x = toFixed(MAX_EXP_NUMBER).plus(1);
|
||||
const expectedError = new FixedMathRevertErrors.FixedMathSignedValueError(
|
||||
FixedMathRevertErrors.ValueErrorCodes.TooLarge,
|
||||
x,
|
||||
@@ -596,30 +678,30 @@ blockchainTests('LibFixedMath', env => {
|
||||
assertFixedEquals(r, 0);
|
||||
});
|
||||
|
||||
it('exp(x), where x is close to 0', async () => {
|
||||
const x = new BigNumber('-1e-27');
|
||||
it('exp(x < 0), where x is close to 0', async () => {
|
||||
const x = new BigNumber('-1e-18');
|
||||
const r = await testContract.exp.callAsync(toFixed(x));
|
||||
assertFixedRoughlyEquals(r, exp(x));
|
||||
assertFixedRoughlyEquals(r, exp(x), EXP_PRECISION);
|
||||
});
|
||||
|
||||
it('exp(x), where x is close to EXP_MIN_VAL', async () => {
|
||||
const x = MIN_EXP_NUMBER.plus('1e-27');
|
||||
const x = MIN_EXP_NUMBER.plus('1e-18');
|
||||
const r = await testContract.exp.callAsync(toFixed(x));
|
||||
assertFixedRoughlyEquals(r, exp(x));
|
||||
assertFixedRoughlyEquals(r, exp(x), EXP_PRECISION);
|
||||
});
|
||||
|
||||
it('exp(x = -0.5)', async () => {
|
||||
const x = -0.5;
|
||||
it('exp(x = -0.85)', async () => {
|
||||
const x = -0.85;
|
||||
const r = await testContract.exp.callAsync(toFixed(x));
|
||||
assertFixedRoughlyEquals(r, exp(x));
|
||||
assertFixedRoughlyEquals(r, exp(x), EXP_PRECISION);
|
||||
});
|
||||
|
||||
blockchainTests.optional('fuzzing', () => {
|
||||
const inputs = _.times(FUZZ_COUNT, () => getRandomNumber(MIN_EXP_NUMBER, 0));
|
||||
const inputs = _.times(FUZZ_COUNT, () => getRandomNumber(MIN_EXP_NUMBER, MAX_EXP_NUMBER));
|
||||
for (const x of inputs) {
|
||||
it(`exp(${x.toString(10)})`, async () => {
|
||||
const r = await testContract.exp.callAsync(toFixed(x));
|
||||
assertFixedRoughlyEquals(r, exp(x), 11);
|
||||
assertFixedRoughlyEquals(r, exp(x), EXP_PRECISION);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
Reference in New Issue
Block a user