aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts/test/exchange
diff options
context:
space:
mode:
authorAmir Bandeali <abandeali1@gmail.com>2018-08-25 08:29:09 +0800
committerGitHub <noreply@github.com>2018-08-25 08:29:09 +0800
commit74ce893f520db6ae4617432975b5c9321ff5b89b (patch)
tree45853fe1c16066290834ee5827b3c269a9fb7734 /packages/contracts/test/exchange
parent94e01be9ed5bfe2d0ace4cd0562fced889b34108 (diff)
parentcc1fac9bbee2656bdb327490de42922abfc5125a (diff)
downloaddexon-sol-tools-74ce893f520db6ae4617432975b5c9321ff5b89b.tar
dexon-sol-tools-74ce893f520db6ae4617432975b5c9321ff5b89b.tar.gz
dexon-sol-tools-74ce893f520db6ae4617432975b5c9321ff5b89b.tar.bz2
dexon-sol-tools-74ce893f520db6ae4617432975b5c9321ff5b89b.tar.lz
dexon-sol-tools-74ce893f520db6ae4617432975b5c9321ff5b89b.tar.xz
dexon-sol-tools-74ce893f520db6ae4617432975b5c9321ff5b89b.tar.zst
dexon-sol-tools-74ce893f520db6ae4617432975b5c9321ff5b89b.zip
Merge pull request #1003 from 0xProject/feature/contracts/roundup
[contracts] Add getPartialAmountCeil and isRoundingErrorCeil
Diffstat (limited to 'packages/contracts/test/exchange')
-rw-r--r--packages/contracts/test/exchange/internal.ts158
-rw-r--r--packages/contracts/test/exchange/libs.ts72
2 files changed, 172 insertions, 58 deletions
diff --git a/packages/contracts/test/exchange/internal.ts b/packages/contracts/test/exchange/internal.ts
index 67d1d2d2c..2521665c2 100644
--- a/packages/contracts/test/exchange/internal.ts
+++ b/packages/contracts/test/exchange/internal.ts
@@ -1,6 +1,7 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
import { Order, RevertReason, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
import * as _ from 'lodash';
import { TestExchangeInternalsContract } from '../../generated_contract_wrappers/test_exchange_internals';
@@ -16,6 +17,8 @@ import { FillResults } from '../utils/types';
import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
chaiSetup.configure();
+const expect = chai.expect;
+
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1);
@@ -43,26 +46,11 @@ const emptySignedOrder: SignedOrder = {
const overflowErrorForCall = new Error(RevertReason.Uint256Overflow);
-async function referenceGetPartialAmountAsync(
- numerator: BigNumber,
- denominator: BigNumber,
- target: BigNumber,
-): Promise<BigNumber> {
- const invalidOpcodeErrorForCall = new Error(await getInvalidOpcodeErrorMessageForCallAsync());
- const product = numerator.mul(target);
- if (product.greaterThan(MAX_UINT256)) {
- throw overflowErrorForCall;
- }
- if (denominator.eq(0)) {
- throw invalidOpcodeErrorForCall;
- }
- return product.dividedToIntegerBy(denominator);
-}
-
describe('Exchange core internal functions', () => {
let testExchange: TestExchangeInternalsContract;
let invalidOpcodeErrorForCall: Error | undefined;
let overflowErrorForSendTransaction: Error | undefined;
+ let divisionByZeroErrorForCall: Error | undefined;
before(async () => {
await blockchainLifecycle.startAsync();
@@ -79,11 +67,29 @@ describe('Exchange core internal functions', () => {
overflowErrorForSendTransaction = new Error(
await getRevertReasonOrErrorMessageForSendTransactionAsync(RevertReason.Uint256Overflow),
);
+ divisionByZeroErrorForCall = new Error(
+ await getRevertReasonOrErrorMessageForSendTransactionAsync(RevertReason.DivisionByZero),
+ );
invalidOpcodeErrorForCall = new Error(await getInvalidOpcodeErrorMessageForCallAsync());
});
// Note(albrow): Don't forget to add beforeEach and afterEach calls to reset
// the blockchain state for any tests which modify it!
+ async function referenceGetPartialAmountFloorAsync(
+ numerator: BigNumber,
+ denominator: BigNumber,
+ target: BigNumber,
+ ): Promise<BigNumber> {
+ if (denominator.eq(0)) {
+ throw divisionByZeroErrorForCall;
+ }
+ const product = numerator.mul(target);
+ if (product.greaterThan(MAX_UINT256)) {
+ throw overflowErrorForCall;
+ }
+ return product.dividedToIntegerBy(denominator);
+ }
+
describe('addFillResults', async () => {
function makeFillResults(value: BigNumber): FillResults {
return {
@@ -159,18 +165,18 @@ describe('Exchange core internal functions', () => {
// implementation or the Solidity implementation of
// calculateFillResults.
return {
- makerAssetFilledAmount: await referenceGetPartialAmountAsync(
+ makerAssetFilledAmount: await referenceGetPartialAmountFloorAsync(
takerAssetFilledAmount,
orderTakerAssetAmount,
otherAmount,
),
takerAssetFilledAmount,
- makerFeePaid: await referenceGetPartialAmountAsync(
+ makerFeePaid: await referenceGetPartialAmountFloorAsync(
takerAssetFilledAmount,
orderTakerAssetAmount,
otherAmount,
),
- takerFeePaid: await referenceGetPartialAmountAsync(
+ takerFeePaid: await referenceGetPartialAmountFloorAsync(
takerAssetFilledAmount,
orderTakerAssetAmount,
otherAmount,
@@ -193,18 +199,55 @@ describe('Exchange core internal functions', () => {
);
});
- describe('getPartialAmount', async () => {
- async function testGetPartialAmountAsync(
+ describe('getPartialAmountFloor', async () => {
+ async function testGetPartialAmountFloorAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<BigNumber> {
- return testExchange.publicGetPartialAmount.callAsync(numerator, denominator, target);
+ return testExchange.publicGetPartialAmountFloor.callAsync(numerator, denominator, target);
}
await testCombinatoriallyWithReferenceFuncAsync(
'getPartialAmount',
- referenceGetPartialAmountAsync,
- testGetPartialAmountAsync,
+ referenceGetPartialAmountFloorAsync,
+ testGetPartialAmountFloorAsync,
+ [uint256Values, uint256Values, uint256Values],
+ );
+ });
+
+ describe('getPartialAmountCeil', async () => {
+ async function referenceGetPartialAmountCeilAsync(
+ numerator: BigNumber,
+ denominator: BigNumber,
+ target: BigNumber,
+ ): Promise<BigNumber> {
+ if (denominator.eq(0)) {
+ throw divisionByZeroErrorForCall;
+ }
+ const product = numerator.mul(target);
+ const offset = product.add(denominator.sub(1));
+ if (offset.greaterThan(MAX_UINT256)) {
+ throw overflowErrorForCall;
+ }
+ const result = offset.dividedToIntegerBy(denominator);
+ if (product.mod(denominator).eq(0)) {
+ expect(result.mul(denominator)).to.be.bignumber.eq(product);
+ } else {
+ expect(result.mul(denominator)).to.be.bignumber.gt(product);
+ }
+ return result;
+ }
+ async function testGetPartialAmountCeilAsync(
+ numerator: BigNumber,
+ denominator: BigNumber,
+ target: BigNumber,
+ ): Promise<BigNumber> {
+ return testExchange.publicGetPartialAmountCeil.callAsync(numerator, denominator, target);
+ }
+ await testCombinatoriallyWithReferenceFuncAsync(
+ 'getPartialAmountCeil',
+ referenceGetPartialAmountCeilAsync,
+ testGetPartialAmountCeilAsync,
[uint256Values, uint256Values, uint256Values],
);
});
@@ -215,33 +258,33 @@ describe('Exchange core internal functions', () => {
denominator: BigNumber,
target: BigNumber,
): Promise<boolean> {
- const product = numerator.mul(target);
if (denominator.eq(0)) {
- throw invalidOpcodeErrorForCall;
+ throw divisionByZeroErrorForCall;
}
- const remainder = product.mod(denominator);
- if (remainder.eq(0)) {
+ if (numerator.eq(0)) {
+ return false;
+ }
+ if (target.eq(0)) {
return false;
}
+ const product = numerator.mul(target);
+ const remainder = product.mod(denominator);
+ const remainderTimes1000 = remainder.mul('1000');
+ const isError = remainderTimes1000.gt(product);
if (product.greaterThan(MAX_UINT256)) {
throw overflowErrorForCall;
}
- if (product.eq(0)) {
- throw invalidOpcodeErrorForCall;
- }
- const remainderTimes1000000 = remainder.mul('1000000');
- if (remainderTimes1000000.greaterThan(MAX_UINT256)) {
+ if (remainderTimes1000.greaterThan(MAX_UINT256)) {
throw overflowErrorForCall;
}
- const errPercentageTimes1000000 = remainderTimes1000000.dividedToIntegerBy(product);
- return errPercentageTimes1000000.greaterThan('1000');
+ return isError;
}
async function testIsRoundingErrorAsync(
numerator: BigNumber,
denominator: BigNumber,
target: BigNumber,
): Promise<boolean> {
- return testExchange.publicIsRoundingError.callAsync(numerator, denominator, target);
+ return testExchange.publicIsRoundingErrorFloor.callAsync(numerator, denominator, target);
}
await testCombinatoriallyWithReferenceFuncAsync(
'isRoundingError',
@@ -251,6 +294,49 @@ describe('Exchange core internal functions', () => {
);
});
+ describe('isRoundingErrorCeil', async () => {
+ async function referenceIsRoundingErrorAsync(
+ numerator: BigNumber,
+ denominator: BigNumber,
+ target: BigNumber,
+ ): Promise<boolean> {
+ if (denominator.eq(0)) {
+ throw divisionByZeroErrorForCall;
+ }
+ if (numerator.eq(0)) {
+ return false;
+ }
+ if (target.eq(0)) {
+ return false;
+ }
+ const product = numerator.mul(target);
+ const remainder = product.mod(denominator);
+ const error = denominator.sub(remainder).mod(denominator);
+ const errorTimes1000 = error.mul('1000');
+ const isError = errorTimes1000.gt(product);
+ if (product.greaterThan(MAX_UINT256)) {
+ throw overflowErrorForCall;
+ }
+ if (errorTimes1000.greaterThan(MAX_UINT256)) {
+ throw overflowErrorForCall;
+ }
+ return isError;
+ }
+ async function testIsRoundingErrorCeilAsync(
+ numerator: BigNumber,
+ denominator: BigNumber,
+ target: BigNumber,
+ ): Promise<boolean> {
+ return testExchange.publicIsRoundingErrorCeil.callAsync(numerator, denominator, target);
+ }
+ await testCombinatoriallyWithReferenceFuncAsync(
+ 'isRoundingErrorCeil',
+ referenceIsRoundingErrorAsync,
+ testIsRoundingErrorCeilAsync,
+ [uint256Values, uint256Values, uint256Values],
+ );
+ });
+
describe('updateFilledState', async () => {
// Note(albrow): Since updateFilledState modifies the state by calling
// sendTransaction, we must reset the state after each test.
diff --git a/packages/contracts/test/exchange/libs.ts b/packages/contracts/test/exchange/libs.ts
index 6c3305d1d..37234489e 100644
--- a/packages/contracts/test/exchange/libs.ts
+++ b/packages/contracts/test/exchange/libs.ts
@@ -71,29 +71,57 @@ describe('Exchange libs', () => {
// combinatorial tests in test/exchange/internal. They test specific edge
// cases that are not covered by the combinatorial tests.
describe('LibMath', () => {
- it('should return false if there is a rounding error of 0.1%', async () => {
- const numerator = new BigNumber(20);
- const denominator = new BigNumber(999);
- const target = new BigNumber(50);
- // rounding error = ((20*50/999) - floor(20*50/999)) / (20*50/999) = 0.1%
- const isRoundingError = await libs.publicIsRoundingError.callAsync(numerator, denominator, target);
- expect(isRoundingError).to.be.false();
- });
- it('should return false if there is a rounding of 0.09%', async () => {
- const numerator = new BigNumber(20);
- const denominator = new BigNumber(9991);
- const target = new BigNumber(500);
- // rounding error = ((20*500/9991) - floor(20*500/9991)) / (20*500/9991) = 0.09%
- const isRoundingError = await libs.publicIsRoundingError.callAsync(numerator, denominator, target);
- expect(isRoundingError).to.be.false();
+ describe('isRoundingError', () => {
+ it('should return true if there is a rounding error of 0.1%', async () => {
+ const numerator = new BigNumber(20);
+ const denominator = new BigNumber(999);
+ const target = new BigNumber(50);
+ // rounding error = ((20*50/999) - floor(20*50/999)) / (20*50/999) = 0.1%
+ const isRoundingError = await libs.publicIsRoundingErrorFloor.callAsync(numerator, denominator, target);
+ expect(isRoundingError).to.be.true();
+ });
+ it('should return false if there is a rounding of 0.09%', async () => {
+ const numerator = new BigNumber(20);
+ const denominator = new BigNumber(9991);
+ const target = new BigNumber(500);
+ // rounding error = ((20*500/9991) - floor(20*500/9991)) / (20*500/9991) = 0.09%
+ const isRoundingError = await libs.publicIsRoundingErrorFloor.callAsync(numerator, denominator, target);
+ expect(isRoundingError).to.be.false();
+ });
+ it('should return true if there is a rounding error of 0.11%', async () => {
+ const numerator = new BigNumber(20);
+ const denominator = new BigNumber(9989);
+ const target = new BigNumber(500);
+ // rounding error = ((20*500/9989) - floor(20*500/9989)) / (20*500/9989) = 0.011%
+ const isRoundingError = await libs.publicIsRoundingErrorFloor.callAsync(numerator, denominator, target);
+ expect(isRoundingError).to.be.true();
+ });
});
- it('should return true if there is a rounding error of 0.11%', async () => {
- const numerator = new BigNumber(20);
- const denominator = new BigNumber(9989);
- const target = new BigNumber(500);
- // rounding error = ((20*500/9989) - floor(20*500/9989)) / (20*500/9989) = 0.011%
- const isRoundingError = await libs.publicIsRoundingError.callAsync(numerator, denominator, target);
- expect(isRoundingError).to.be.true();
+ describe('isRoundingErrorCeil', () => {
+ it('should return true if there is a rounding error of 0.1%', async () => {
+ const numerator = new BigNumber(20);
+ const denominator = new BigNumber(1001);
+ const target = new BigNumber(50);
+ // rounding error = (ceil(20*50/1001) - (20*50/1001)) / (20*50/1001) = 0.1%
+ const isRoundingError = await libs.publicIsRoundingErrorCeil.callAsync(numerator, denominator, target);
+ expect(isRoundingError).to.be.true();
+ });
+ it('should return false if there is a rounding of 0.09%', async () => {
+ const numerator = new BigNumber(20);
+ const denominator = new BigNumber(10009);
+ const target = new BigNumber(500);
+ // rounding error = (ceil(20*500/10009) - (20*500/10009)) / (20*500/10009) = 0.09%
+ const isRoundingError = await libs.publicIsRoundingErrorCeil.callAsync(numerator, denominator, target);
+ expect(isRoundingError).to.be.false();
+ });
+ it('should return true if there is a rounding error of 0.11%', async () => {
+ const numerator = new BigNumber(20);
+ const denominator = new BigNumber(10011);
+ const target = new BigNumber(500);
+ // rounding error = (ceil(20*500/10011) - (20*500/10011)) / (20*500/10011) = 0.11%
+ const isRoundingError = await libs.publicIsRoundingErrorCeil.callAsync(numerator, denominator, target);
+ expect(isRoundingError).to.be.true();
+ });
});
});