@0x/contracts-staking: Write LibFixedMath unit tests.

This commit is contained in:
Lawrence Forman
2019-08-28 14:07:54 -04:00
committed by Lawrence Forman
parent 1c37334b18
commit 0c6a6743ab
3 changed files with 193 additions and 53 deletions

View File

@@ -55,12 +55,13 @@
"@0x/dev-utils": "^2.2.1",
"@0x/sol-compiler": "^3.1.6",
"@0x/tslint-config": "^3.0.1",
"@0x/utils": "^4.3.1",
"@types/lodash": "4.14.104",
"@types/node": "*",
"@0x/utils": "^4.3.1",
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
"chai-bignumber": "^3.0.0",
"decimal.js": "^10.2.0",
"dirty-chai": "^2.0.1",
"make-promises-safe": "^1.1.0",
"mocha": "^4.1.0",
@@ -73,9 +74,9 @@
},
"dependencies": {
"@0x/base-contract": "^5.1.0",
"@0x/contracts-utils": "^3.2.1",
"@0x/contracts-asset-proxy": "^2.2.5",
"@0x/contracts-erc20": "^2.2.0",
"@0x/contracts-utils": "^3.2.1",
"@0x/order-utils": "^8.1.0",
"@0x/types": "^2.2.2",
"@0x/typescript-typings": "^4.2.2",

View File

@@ -1,17 +1,20 @@
import { blockchainTests, expect } from '@0x/contracts-test-utils';
import { blockchainTests, expect, hexRandom } from '@0x/contracts-test-utils';
import { BigNumber, FixedMathRevertErrors } from '@0x/utils';
import { Decimal } from 'decimal.js';
import * as _ from 'lodash';
import { artifacts, TestLibFixedMathContract } from '../src/';
Decimal.set({ precision: 128 });
// tslint:disable:no-unnecessary-type-assertion
blockchainTests('LibFixedMath', env => {
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 MAX_SIGNED_NUMBER = fromFixed(MAX_FIXED_VALUE);
const MIN_SIGNED_NUMBER = fromFixed(MIN_FIXED_VALUE);
const MIN_EXP_NUMBER = new BigNumber('-69.07755278982137043720332303294494434957608235919531845909733017243007692132225692604324818191230406');
const FUZZ_COUNT = 1024;
type Numberish = BigNumber | string | number;
@@ -43,6 +46,34 @@ blockchainTests('LibFixedMath', env => {
return fromFixed(toFixed(a).times(FIXED_POINT_DIVISOR).dividedBy(toFixed(b)));
}
function ln(x: Numberish): BigNumber {
return new BigNumber(toDecimal(x).ln().toFixed(128));
}
function exp(x: Numberish): BigNumber {
return new BigNumber(toDecimal(x).exp().toFixed(128));
}
function toDecimal(x: Numberish): Decimal {
if (BigNumber.isBigNumber(x)) {
return new Decimal(x.toString(10));
}
return new Decimal(x);
}
function getRandomNumber(min: Numberish, max: Numberish): BigNumber {
const range = new BigNumber(max).minus(min);
const random = fromFixed(new BigNumber(hexRandom().substr(2), 16));
return random.mod(range).plus(min);
}
function toPrecision(n: Numberish, precision: number = 13): BigNumber {
const _n = new BigNumber(n);
const integerDigits = _n.integerValue().sd(true);
const base = 10 ** (precision - integerDigits);
return _n.times(base).integerValue(BigNumber.ROUND_HALF_FLOOR).dividedBy(base);
}
function assertFixedEquals(
actual: BigNumber | string | number,
expected: BigNumber | string | number,
@@ -50,6 +81,16 @@ blockchainTests('LibFixedMath', env => {
expect(fromFixed(actual)).to.bignumber.eq(numberToFixedToNumber(expected));
}
function assertFixedRoughlyEquals(
actual: BigNumber | string | number,
expected: BigNumber | string | number,
precision: number = 13,
): void {
// SD is not what we want.
expect(toPrecision(fromFixed(actual), precision))
.to.bignumber.eq(toPrecision(numberToFixedToNumber(expected), precision));
}
let testContract: TestLibFixedMathContract;
before(async () => {
@@ -466,51 +507,122 @@ blockchainTests('LibFixedMath', env => {
});
});
// describe('LibFeesMath', () => {
// it('nth root', async () => {
// const base = new BigNumber(1419857);
// const n = new BigNumber(5);
// const root = await stakingWrapper.nthRootAsync(base, n);
// expect(root).to.be.bignumber.equal(17);
// });
//
// it('nth root #2', async () => {
// const base = new BigNumber(3375);
// const n = new BigNumber(3);
// const root = await stakingWrapper.nthRootAsync(base, n);
// expect(root).to.be.bignumber.equal(15);
// });
//
// it('nth root #3 with fixed point', async () => {
// const decimals = 18;
// const base = StakingWrapper.toFixedPoint(4.234, decimals);
// const n = new BigNumber(2);
// const root = await stakingWrapper.nthRootFixedPointAsync(base, n);
// const rootAsFloatingPoint = StakingWrapper.toFloatingPoint(root, decimals);
// const expectedResult = new BigNumber(2.057668584);
// expect(rootAsFloatingPoint).to.be.bignumber.equal(expectedResult);
// });
//
// it('nth root #3 with fixed point (integer nth root would fail here)', async () => {
// const decimals = 18;
// const base = StakingWrapper.toFixedPoint(5429503678976, decimals);
// const n = new BigNumber(9);
// const root = await stakingWrapper.nthRootFixedPointAsync(base, n);
// const rootAsFloatingPoint = StakingWrapper.toFloatingPoint(root, decimals);
// const expectedResult = new BigNumber(26);
// expect(rootAsFloatingPoint).to.be.bignumber.equal(expectedResult);
// });
//
// it.skip('nth root #4 with fixed point (integer nth root would fail here) (max number of decimals - currently does not retain)', async () => {
// // @TODO This is the gold standard for nth root. Retain all these decimals :)
// const decimals = 18;
// const base = StakingWrapper.toFixedPoint(new BigNumber('5429503678976.295036789761543678', 10), decimals);
// const n = new BigNumber(9);
// const root = await stakingWrapper.nthRootFixedPointAsync(base, n);
// const rootAsFloatingPoint = StakingWrapper.toFloatingPoint(root, decimals);
// const expectedResult = new BigNumber(26);
// expect(rootAsFloatingPoint).to.be.bignumber.equal(expectedResult);
// });
//
// });
describe('ln()', () => {
it('ln(x = 0) throws', async () => {
const x = toFixed(0);
const expectedError = new FixedMathRevertErrors.FixedMathSignedValueError(
FixedMathRevertErrors.ValueErrorCodes.TooSmall,
x,
);
const tx = testContract.ln.callAsync(x);
return expect(tx).to.revertWith(expectedError);
});
it('ln(x > 1) throws', async () => {
const x = toFixed(1.000001);
const expectedError = new FixedMathRevertErrors.FixedMathSignedValueError(
FixedMathRevertErrors.ValueErrorCodes.TooLarge,
x,
);
const tx = testContract.ln.callAsync(x);
return expect(tx).to.revertWith(expectedError);
});
it('ln(x < 0) throws', async () => {
const x = toFixed(-0.000001);
const expectedError = new FixedMathRevertErrors.FixedMathSignedValueError(
FixedMathRevertErrors.ValueErrorCodes.TooSmall,
x,
);
const tx = testContract.ln.callAsync(x);
return expect(tx).to.revertWith(expectedError);
});
it('ln(x = 1) == 0', async () => {
const x = toFixed(1);
const r = await testContract.ln.callAsync(x);
assertFixedEquals(r, 0);
});
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));
});
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));
});
it('ln(x = 0.5)', async () => {
const x = 0.5;
const r = await testContract.ln.callAsync(toFixed(x));
assertFixedRoughlyEquals(r, ln(x));
});
blockchainTests.optional('fuzzing', () => {
const inputs = _.times(FUZZ_COUNT, () => getRandomNumber(0, 1));
for (const x of inputs) {
it(`ln(${x.toString(10)})`, async () => {
const r = await testContract.ln.callAsync(toFixed(x));
assertFixedRoughlyEquals(r, ln(x), 11);
});
}
});
});
describe('exp()', () => {
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);
const expectedError = new FixedMathRevertErrors.FixedMathSignedValueError(
FixedMathRevertErrors.ValueErrorCodes.TooLarge,
x,
);
const tx = testContract.exp.callAsync(x);
return expect(tx).to.revertWith(expectedError);
});
it('exp(x < EXP_MIN_VAL) == 0', async () => {
const x = toFixed(MIN_EXP_NUMBER).minus(1);
const r = await testContract.exp.callAsync(x);
assertFixedEquals(r, 0);
});
it('exp(x), where x is close to 0', async () => {
const x = new BigNumber('-1e-27');
const r = await testContract.exp.callAsync(toFixed(x));
assertFixedRoughlyEquals(r, exp(x));
});
it('exp(x), where x is close to EXP_MIN_VAL', async () => {
const x = MIN_EXP_NUMBER.plus('1e-27');
const r = await testContract.exp.callAsync(toFixed(x));
assertFixedRoughlyEquals(r, exp(x));
});
it('exp(x = -0.5)', async () => {
const x = -0.5;
const r = await testContract.exp.callAsync(toFixed(x));
assertFixedRoughlyEquals(r, exp(x));
});
blockchainTests.optional('fuzzing', () => {
const inputs = _.times(FUZZ_COUNT, () => getRandomNumber(MIN_EXP_NUMBER, 0));
for (const x of inputs) {
it(`exp(${x.toString(10)})`, async () => {
const r = await testContract.exp.callAsync(toFixed(x));
assertFixedRoughlyEquals(r, exp(x), 11);
});
}
});
});
});
// tslint:disable-next-line: max-file-line-count