import { constants, describe, expect } from '@0x/contracts-test-utils'; import { SafeMathRevertErrors } from '@0x/contracts-utils'; import { BigNumber, LibMathRevertErrors } from '@0x/utils'; import * as _ from 'lodash'; import { addFillResults, getPartialAmountCeil, getPartialAmountFloor, isRoundingErrorCeil, isRoundingErrorFloor, safeGetPartialAmountCeil, safeGetPartialAmountFloor, } from '../src/reference_functions'; describe('Reference Functions', () => { const { ONE_ETHER, MAX_UINT256, MAX_UINT256_ROOT, ZERO_AMOUNT } = constants; describe('LibFillResults', () => { describe('addFillResults', () => { const DEFAULT_FILL_RESULTS = [ { makerAssetFilledAmount: ONE_ETHER, takerAssetFilledAmount: ONE_ETHER.times(2), makerFeePaid: ONE_ETHER.times(0.001), takerFeePaid: ONE_ETHER.times(0.002), protocolFeePaid: ONE_ETHER.times(0.003), }, { makerAssetFilledAmount: ONE_ETHER.times(0.01), takerAssetFilledAmount: ONE_ETHER.times(2).times(0.01), makerFeePaid: ONE_ETHER.times(0.001).times(0.01), takerFeePaid: ONE_ETHER.times(0.002).times(0.01), protocolFeePaid: ONE_ETHER.times(0.003).times(0.01), }, ]; it('reverts if computing `makerAssetFilledAmount` overflows', () => { const [a, b] = _.cloneDeep(DEFAULT_FILL_RESULTS); b.makerAssetFilledAmount = MAX_UINT256; const expectedError = new SafeMathRevertErrors.Uint256BinOpError( SafeMathRevertErrors.BinOpErrorCodes.AdditionOverflow, a.makerAssetFilledAmount, b.makerAssetFilledAmount, ); expect(() => addFillResults(a, b)).to.throw(expectedError.message); }); it('reverts if computing `takerAssetFilledAmount` overflows', () => { const [a, b] = _.cloneDeep(DEFAULT_FILL_RESULTS); b.takerAssetFilledAmount = MAX_UINT256; const expectedError = new SafeMathRevertErrors.Uint256BinOpError( SafeMathRevertErrors.BinOpErrorCodes.AdditionOverflow, a.takerAssetFilledAmount, b.takerAssetFilledAmount, ); expect(() => addFillResults(a, b)).to.throw(expectedError.message); }); it('reverts if computing `makerFeePaid` overflows', () => { const [a, b] = _.cloneDeep(DEFAULT_FILL_RESULTS); b.makerFeePaid = MAX_UINT256; const expectedError = new SafeMathRevertErrors.Uint256BinOpError( SafeMathRevertErrors.BinOpErrorCodes.AdditionOverflow, a.makerFeePaid, b.makerFeePaid, ); expect(() => addFillResults(a, b)).to.throw(expectedError.message); }); it('reverts if computing `takerFeePaid` overflows', () => { const [a, b] = _.cloneDeep(DEFAULT_FILL_RESULTS); b.takerFeePaid = MAX_UINT256; const expectedError = new SafeMathRevertErrors.Uint256BinOpError( SafeMathRevertErrors.BinOpErrorCodes.AdditionOverflow, a.takerFeePaid, b.takerFeePaid, ); expect(() => addFillResults(a, b)).to.throw(expectedError.message); }); it('reverts if computing `protocolFeePaid` overflows', () => { const [a, b] = _.cloneDeep(DEFAULT_FILL_RESULTS); b.protocolFeePaid = MAX_UINT256; const expectedError = new SafeMathRevertErrors.Uint256BinOpError( SafeMathRevertErrors.BinOpErrorCodes.AdditionOverflow, a.protocolFeePaid, b.protocolFeePaid, ); expect(() => addFillResults(a, b)).to.throw(expectedError.message); }); }); }); describe('LibMath', () => { describe('getPartialAmountFloor', () => { describe('explicit tests', () => { it('reverts if `denominator` is zero', () => { const numerator = ONE_ETHER; const denominator = ZERO_AMOUNT; const target = ONE_ETHER.times(0.01); const expectedError = new SafeMathRevertErrors.Uint256BinOpError( SafeMathRevertErrors.BinOpErrorCodes.DivisionByZero, numerator.times(target), denominator, ); return expect(() => getPartialAmountFloor(numerator, denominator, target)).to.throw( expectedError.message, ); }); it('reverts if `numerator * target` overflows', () => { const numerator = MAX_UINT256; const denominator = ONE_ETHER.dividedToIntegerBy(2); const target = MAX_UINT256_ROOT.times(2); const expectedError = new SafeMathRevertErrors.Uint256BinOpError( SafeMathRevertErrors.BinOpErrorCodes.MultiplicationOverflow, numerator, target, ); return expect(() => getPartialAmountFloor(numerator, denominator, target)).to.throw( expectedError.message, ); }); }); }); describe('getPartialAmountCeil', () => { describe('explicit tests', () => { it('reverts if `denominator` is zero', () => { const numerator = ONE_ETHER; const denominator = ZERO_AMOUNT; const target = ONE_ETHER.times(0.01); // This will actually manifest as a subtraction underflow. const expectedError = new SafeMathRevertErrors.Uint256BinOpError( SafeMathRevertErrors.BinOpErrorCodes.SubtractionUnderflow, denominator, new BigNumber(1), ); return expect(() => getPartialAmountCeil(numerator, denominator, target)).to.throw( expectedError.message, ); }); it('reverts if `numerator * target` overflows', () => { const numerator = MAX_UINT256; const denominator = ONE_ETHER.dividedToIntegerBy(2); const target = MAX_UINT256_ROOT.times(2); const expectedError = new SafeMathRevertErrors.Uint256BinOpError( SafeMathRevertErrors.BinOpErrorCodes.MultiplicationOverflow, numerator, target, ); return expect(() => getPartialAmountCeil(numerator, denominator, target)).to.throw( expectedError.message, ); }); }); }); describe('safeGetPartialAmountFloor', () => { describe('explicit tests', () => { it('reverts for a rounding error', () => { const numerator = new BigNumber(1e3); const denominator = new BigNumber(1e4); const target = new BigNumber(333); const expectedError = new LibMathRevertErrors.RoundingError(numerator, denominator, target); return expect(() => safeGetPartialAmountFloor(numerator, denominator, target)).to.throw( expectedError.message, ); }); it('reverts if `denominator` is zero', () => { const numerator = ONE_ETHER; const denominator = ZERO_AMOUNT; const target = ONE_ETHER.times(0.01); const expectedError = new LibMathRevertErrors.DivisionByZeroError(); return expect(() => safeGetPartialAmountFloor(numerator, denominator, target)).to.throw( expectedError.message, ); }); it('reverts if `numerator * target` overflows', () => { const numerator = MAX_UINT256; const denominator = ONE_ETHER.dividedToIntegerBy(2); const target = MAX_UINT256_ROOT.times(2); const expectedError = new SafeMathRevertErrors.Uint256BinOpError( SafeMathRevertErrors.BinOpErrorCodes.MultiplicationOverflow, numerator, target, ); return expect(() => safeGetPartialAmountFloor(numerator, denominator, target)).to.throw( expectedError.message, ); }); }); }); describe('safeGetPartialAmountCeil', () => { describe('explicit tests', () => { it('reverts for a rounding error', () => { const numerator = new BigNumber(1e3); const denominator = new BigNumber(1e4); const target = new BigNumber(333); const expectedError = new LibMathRevertErrors.RoundingError(numerator, denominator, target); return expect(() => safeGetPartialAmountCeil(numerator, denominator, target)).to.throw( expectedError.message, ); }); it('reverts if `denominator` is zero', () => { const numerator = ONE_ETHER; const denominator = ZERO_AMOUNT; const target = ONE_ETHER.times(0.01); const expectedError = new LibMathRevertErrors.DivisionByZeroError(); return expect(() => safeGetPartialAmountCeil(numerator, denominator, target)).to.throw( expectedError.message, ); }); it('reverts if `numerator * target` overflows', () => { const numerator = MAX_UINT256; const denominator = ONE_ETHER.dividedToIntegerBy(2); const target = MAX_UINT256_ROOT.times(2); const expectedError = new SafeMathRevertErrors.Uint256BinOpError( SafeMathRevertErrors.BinOpErrorCodes.MultiplicationOverflow, numerator, target, ); return expect(() => safeGetPartialAmountCeil(numerator, denominator, target)).to.throw( expectedError.message, ); }); }); }); describe('isRoundingErrorFloor', () => { describe('explicit tests', () => { it('returns true when `numerator * target / denominator` produces an error >= 0.1%', async () => { const numerator = new BigNumber(100); const denominator = new BigNumber(102); const target = new BigNumber(52); // tslint:disable-next-line: boolean-naming const actual = isRoundingErrorFloor(numerator, denominator, target); expect(actual).to.eq(true); }); it('returns false when `numerator * target / denominator` produces an error < 0.1%', async () => { const numerator = new BigNumber(100); const denominator = new BigNumber(101); const target = new BigNumber(92); // tslint:disable-next-line: boolean-naming const actual = isRoundingErrorFloor(numerator, denominator, target); expect(actual).to.eq(false); }); it('reverts if `denominator` is zero', () => { const numerator = ONE_ETHER; const denominator = ZERO_AMOUNT; const target = ONE_ETHER.times(0.01); const expectedError = new LibMathRevertErrors.DivisionByZeroError(); return expect(() => isRoundingErrorFloor(numerator, denominator, target)).to.throw( expectedError.message, ); }); it('reverts if `numerator * target` overflows', () => { const numerator = MAX_UINT256; const denominator = ONE_ETHER.dividedToIntegerBy(2); const target = MAX_UINT256_ROOT.times(2); const expectedError = new SafeMathRevertErrors.Uint256BinOpError( SafeMathRevertErrors.BinOpErrorCodes.MultiplicationOverflow, numerator, target, ); return expect(() => isRoundingErrorFloor(numerator, denominator, target)).to.throw( expectedError.message, ); }); }); }); describe('isRoundingErrorCeil', () => { describe('explicit tests', () => { it('returns true when `numerator * target / (denominator - 1)` produces an error >= 0.1%', async () => { const numerator = new BigNumber(100); const denominator = new BigNumber(101); const target = new BigNumber(92); // tslint:disable-next-line: boolean-naming const actual = isRoundingErrorCeil(numerator, denominator, target); expect(actual).to.eq(true); }); it('returns false when `numerator * target / (denominator - 1)` produces an error < 0.1%', async () => { const numerator = new BigNumber(100); const denominator = new BigNumber(102); const target = new BigNumber(52); // tslint:disable-next-line: boolean-naming const actual = isRoundingErrorCeil(numerator, denominator, target); expect(actual).to.eq(false); }); it('reverts if `denominator` is zero', () => { const numerator = ONE_ETHER; const denominator = ZERO_AMOUNT; const target = ONE_ETHER.times(0.01); const expectedError = new LibMathRevertErrors.DivisionByZeroError(); return expect(() => isRoundingErrorCeil(numerator, denominator, target)).to.throw( expectedError.message, ); }); it('reverts if `numerator * target` overflows', () => { const numerator = MAX_UINT256; const denominator = ONE_ETHER.dividedToIntegerBy(2); const target = MAX_UINT256_ROOT.times(2); const expectedError = new SafeMathRevertErrors.Uint256BinOpError( SafeMathRevertErrors.BinOpErrorCodes.MultiplicationOverflow, numerator, target, ); return expect(() => isRoundingErrorCeil(numerator, denominator, target)).to.throw( expectedError.message, ); }); }); }); }); });