From 92497d7df4b61ee62acfdd58bfb98569ff67214e Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 23 Aug 2018 11:01:52 -0700 Subject: Fix isRoundingError --- .../src/2.0.0/protocol/Exchange/libs/LibMath.sol | 27 ++++++++++++++-------- 1 file changed, 17 insertions(+), 10 deletions(-) (limited to 'packages') diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol index fa09da6ac..146bb9943 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol @@ -46,7 +46,7 @@ contract LibMath is return partialAmount; } - /// @dev Checks if rounding error > 0.1%. + /// @dev Checks if rounding error >= 0.1%. /// @param numerator Numerator. /// @param denominator Denominator. /// @param target Value to multiply with numerator/denominator. @@ -60,16 +60,23 @@ contract LibMath is pure returns (bool isError) { + // The absolute rounding error is the difference between the rounded + // value and the ideal value. The relative rounding error is the + // absolute rounding error divided by the absolute value of the + // ideal value. We want the relative rounding error to be strictly less + // than 0.1%. + // Let's call `numerator * target % denominator` the remainder. + // The ideal value is `numerator * target / denominator`. + // The absolute error is `remainder / denominator`. + // The relative error is `remainder / numerator * target`. + // We want the relative error less than 1 / 1000: + // remainder / numerator * denominator < 1 / 1000 + // or equivalently: + // 1000 * remainder < numerator * target + // so we have a rounding error iff: + // 1000 * remainder >= numerator * target uint256 remainder = mulmod(target, numerator, denominator); - if (remainder == 0) { - return false; // No rounding error. - } - - uint256 errPercentageTimes1000000 = safeDiv( - safeMul(remainder, 1000000), - safeMul(numerator, target) - ); - isError = errPercentageTimes1000000 > 1000; + isError = safeMul(1000, remainder) >= safeMul(numerator, target); return isError; } } -- cgit v1.2.3 From 4159e45aff14c04f2d799ec8a7b6e7f1a4894064 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 23 Aug 2018 12:54:13 -0700 Subject: Update tests --- packages/contracts/test/exchange/internal.ts | 26 +++++++++++++++----------- packages/contracts/test/exchange/libs.ts | 4 ++-- packages/types/src/index.ts | 1 + 3 files changed, 18 insertions(+), 13 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/exchange/internal.ts b/packages/contracts/test/exchange/internal.ts index 67d1d2d2c..0231cc3f1 100644 --- a/packages/contracts/test/exchange/internal.ts +++ b/packages/contracts/test/exchange/internal.ts @@ -63,6 +63,7 @@ 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,6 +80,9 @@ 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 @@ -215,26 +219,26 @@ describe('Exchange core internal functions', () => { denominator: BigNumber, target: BigNumber, ): Promise { - 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, diff --git a/packages/contracts/test/exchange/libs.ts b/packages/contracts/test/exchange/libs.ts index 6c3305d1d..c08d62758 100644 --- a/packages/contracts/test/exchange/libs.ts +++ b/packages/contracts/test/exchange/libs.ts @@ -71,13 +71,13 @@ 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 () => { + 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.publicIsRoundingError.callAsync(numerator, denominator, target); - expect(isRoundingError).to.be.false(); + expect(isRoundingError).to.be.true(); }); it('should return false if there is a rounding of 0.09%', async () => { const numerator = new BigNumber(20); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 298fa77d4..2353c0807 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -175,6 +175,7 @@ export enum RevertReason { InvalidSender = 'INVALID_SENDER', InvalidOrderSignature = 'INVALID_ORDER_SIGNATURE', InvalidTakerAmount = 'INVALID_TAKER_AMOUNT', + DivisionByZero = 'DIVISION_BY_ZERO', RoundingError = 'ROUNDING_ERROR', InvalidSignature = 'INVALID_SIGNATURE', SignatureIllegal = 'SIGNATURE_ILLEGAL', -- cgit v1.2.3 From e68942ee78eb19c27a96fb0b6b8b05c83b647bcc Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 23 Aug 2018 12:54:39 -0700 Subject: Handle zero case --- .../src/2.0.0/protocol/Exchange/libs/LibMath.sol | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol index 146bb9943..758b7ec90 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol @@ -60,14 +60,26 @@ contract LibMath is pure returns (bool isError) { + require(denominator > 0, "DIVISION_BY_ZERO"); + // The absolute rounding error is the difference between the rounded // value and the ideal value. The relative rounding error is the // absolute rounding error divided by the absolute value of the - // ideal value. We want the relative rounding error to be strictly less - // than 0.1%. - // Let's call `numerator * target % denominator` the remainder. + // ideal value. This is undefined when the ideal value is zero. + // // The ideal value is `numerator * target / denominator`. + // Let's call `numerator * target % denominator` the remainder. // The absolute error is `remainder / denominator`. + // + // When the ideal value is zero, we require the absolute error to + // be zero. Fortunately, this is always the case. The ideal value is + // zero iff `numerator == 0` and/or `target == 0`. In this case the + // remainder and absolute error are also zero. + if (target == 0 || numerator == 0) { + return false; + } + // Otherwise, we want the relative rounding error to be strictly + // less than 0.1%. // The relative error is `remainder / numerator * target`. // We want the relative error less than 1 / 1000: // remainder / numerator * denominator < 1 / 1000 -- cgit v1.2.3 From ab5df342e1dc4add20223fab7128f9323a114b8e Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Wed, 22 Aug 2018 12:22:49 -0700 Subject: Add getPartialAmountCeil --- .../src/2.0.0/protocol/Exchange/libs/LibMath.sol | 35 ++++++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol index 758b7ec90..3e70d1b60 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol @@ -29,7 +29,7 @@ contract LibMath is /// @param numerator Numerator. /// @param denominator Denominator. /// @param target Value to calculate partial of. - /// @return Partial value of target. + /// @return Partial value of target rounded down. function getPartialAmount( uint256 numerator, uint256 denominator, @@ -45,8 +45,37 @@ contract LibMath is ); return partialAmount; } - - /// @dev Checks if rounding error >= 0.1%. + + /// @dev Calculates partial value given a numerator and denominator. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to calculate partial of. + /// @return Partial value of target rounded up. + function getPartialAmountCeil( + uint256 numerator, + uint256 denominator, + uint256 target + ) + internal + pure + returns (uint256 partialAmount) + { + require( + denominator > 0, + "DIVISION_BY_ZERO" + ); + + // SafeDiv rounds down. To use it to round up we need to add + // denominator - 1. This causes anything less than an exact multiple + // of denominator to be rounded up. + partialAmount = safeDiv( + safeAdd(safeMul(numerator, target), safeSub(denominator, 1)), + denominator + ); + return partialAmount; + } + + /// @dev Checks if rounding error > 0.1%. /// @param numerator Numerator. /// @param denominator Denominator. /// @param target Value to multiply with numerator/denominator. -- cgit v1.2.3 From 5d70df771b151800b8a04b5569529843701c9fbd Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Wed, 22 Aug 2018 12:22:58 -0700 Subject: Add isRoundingErrorCeil --- .../src/2.0.0/protocol/Exchange/libs/LibMath.sol | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'packages') diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol index 3e70d1b60..c4aa2abb5 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol @@ -107,6 +107,7 @@ contract LibMath is if (target == 0 || numerator == 0) { return false; } + // Otherwise, we want the relative rounding error to be strictly // less than 0.1%. // The relative error is `remainder / numerator * target`. @@ -120,4 +121,32 @@ contract LibMath is isError = safeMul(1000, remainder) >= safeMul(numerator, target); return isError; } + + /// @dev Checks if rounding error > 0.1%. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to multiply with numerator/denominator. + /// @return Rounding error is present. + function isRoundingErrorCeil( + uint256 numerator, + uint256 denominator, + uint256 target + ) + internal + pure + returns (bool isError) + { + require(denominator > 0, "DIVISION_BY_ZERO"); + + if (target == 0 || numerator == 0) { + return false; + } + uint256 remainder = mulmod(target, numerator, denominator); + if (remainder == 0) { + return false; + } + remainder = safeSub(denominator, remainder); + isError = safeMul(1000, remainder) >= safeMul(numerator, target); + return isError; + } } -- cgit v1.2.3 From 3dad6ee55e9f51daa66cfe3c83dd17abc31f23f5 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Wed, 22 Aug 2018 12:23:32 -0700 Subject: Add tests for getPartialAmountCeil --- .../TestExchangeInternals.sol | 17 +++++++++ packages/contracts/test/exchange/internal.ts | 40 ++++++++++++++++++++++ 2 files changed, 57 insertions(+) (limited to 'packages') diff --git a/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol b/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol index d9cec9edc..239dd10a8 100644 --- a/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol +++ b/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol @@ -79,6 +79,23 @@ contract TestExchangeInternals is return getPartialAmount(numerator, denominator, target); } + /// @dev Calculates partial value given a numerator and denominator. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to calculate partial of. + /// @return Partial value of target. + function publicGetPartialAmountCeil( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (uint256 partialAmount) + { + return getPartialAmountCeil(numerator, denominator, target); + } + /// @dev Checks if rounding error > 0.1%. /// @param numerator Numerator. /// @param denominator Denominator. diff --git a/packages/contracts/test/exchange/internal.ts b/packages/contracts/test/exchange/internal.ts index 0231cc3f1..69625ee1d 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); @@ -213,6 +216,43 @@ describe('Exchange core internal functions', () => { ); }); + describe.only('getPartialAmountCeil', async () => { + async function referenceGetPartialAmountCeilAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ) { + if (denominator.eq(0)) { + throw new Error('revert DIVISION_BY_ZERO'); + } + 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 { + return testExchange.publicGetPartialAmountCeil.callAsync(numerator, denominator, target); + } + await testCombinatoriallyWithReferenceFuncAsync( + 'getPartialAmountCeil', + referenceGetPartialAmountCeilAsync, + testGetPartialAmountCeilAsync, + [uint256Values, uint256Values, uint256Values], + ); + }); + describe('isRoundingError', async () => { async function referenceIsRoundingErrorAsync( numerator: BigNumber, -- cgit v1.2.3 From 50fab9feb3b80b7d5ac1e94df9af68cad4aaabe0 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Wed, 22 Aug 2018 12:22:58 -0700 Subject: Improve getPartialAmountCeil docs --- packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol index c4aa2abb5..d123c55a1 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol @@ -65,9 +65,9 @@ contract LibMath is "DIVISION_BY_ZERO" ); - // SafeDiv rounds down. To use it to round up we need to add - // denominator - 1. This causes anything less than an exact multiple - // of denominator to be rounded up. + // safeDiv computes `floor(a / b)`. We use the identity (a, b integer): + // ceil(a / b) = floor((a + b - 1) / b) + // To implement `ceil(a / b)` using safeDiv. partialAmount = safeDiv( safeAdd(safeMul(numerator, target), safeSub(denominator, 1)), denominator -- cgit v1.2.3 From c109d1f5451c43afd92dd0fb4bebb48cba65c661 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 23 Aug 2018 13:33:05 -0700 Subject: Remove .only --- packages/contracts/test/exchange/internal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/contracts/test/exchange/internal.ts b/packages/contracts/test/exchange/internal.ts index 69625ee1d..2f25b1708 100644 --- a/packages/contracts/test/exchange/internal.ts +++ b/packages/contracts/test/exchange/internal.ts @@ -216,7 +216,7 @@ describe('Exchange core internal functions', () => { ); }); - describe.only('getPartialAmountCeil', async () => { + describe('getPartialAmountCeil', async () => { async function referenceGetPartialAmountCeilAsync( numerator: BigNumber, denominator: BigNumber, -- cgit v1.2.3 From 4219af1430f1cfc105d3521616941b7947fde4e3 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 23 Aug 2018 14:22:59 -0700 Subject: Add DIVISION_BY_ZERO to getPartialAmount for consistency --- .../src/2.0.0/protocol/Exchange/libs/LibMath.sol | 7 +++-- packages/contracts/test/exchange/internal.ts | 31 +++++++++++----------- 2 files changed, 18 insertions(+), 20 deletions(-) (limited to 'packages') diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol index d123c55a1..f4e2f1958 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol @@ -39,6 +39,8 @@ contract LibMath is pure returns (uint256 partialAmount) { + require(denominator > 0, "DIVISION_BY_ZERO"); + partialAmount = safeDiv( safeMul(numerator, target), denominator @@ -60,10 +62,7 @@ contract LibMath is pure returns (uint256 partialAmount) { - require( - denominator > 0, - "DIVISION_BY_ZERO" - ); + require(denominator > 0, "DIVISION_BY_ZERO"); // safeDiv computes `floor(a / b)`. We use the identity (a, b integer): // ceil(a / b) = floor((a + b - 1) / b) diff --git a/packages/contracts/test/exchange/internal.ts b/packages/contracts/test/exchange/internal.ts index 2f25b1708..48e1e620c 100644 --- a/packages/contracts/test/exchange/internal.ts +++ b/packages/contracts/test/exchange/internal.ts @@ -46,22 +46,6 @@ const emptySignedOrder: SignedOrder = { const overflowErrorForCall = new Error(RevertReason.Uint256Overflow); -async function referenceGetPartialAmountAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, -): Promise { - 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; @@ -91,6 +75,21 @@ describe('Exchange core internal functions', () => { // Note(albrow): Don't forget to add beforeEach and afterEach calls to reset // the blockchain state for any tests which modify it! + async function referenceGetPartialAmountAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise { + 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 { -- cgit v1.2.3 From 0fb7617a785b765415cb7f43448c9b8ea905963f Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 23 Aug 2018 15:18:45 -0700 Subject: Fix incorect modulus --- packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol index f4e2f1958..1c14dbcae 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol @@ -141,10 +141,8 @@ contract LibMath is return false; } uint256 remainder = mulmod(target, numerator, denominator); - if (remainder == 0) { - return false; - } - remainder = safeSub(denominator, remainder); + // TODO: safeMod + remainder = safeSub(denominator, remainder) % denominator; isError = safeMul(1000, remainder) >= safeMul(numerator, target); return isError; } -- cgit v1.2.3 From 6734f2f1bcdab8f0d50524a26195707da00bd8ed Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 23 Aug 2018 15:18:55 -0700 Subject: Add docs --- packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol index 1c14dbcae..8a1444af1 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol @@ -74,7 +74,7 @@ contract LibMath is return partialAmount; } - /// @dev Checks if rounding error > 0.1%. + /// @dev Checks if rounding error >= 0.1% when rounding down. /// @param numerator Numerator. /// @param denominator Denominator. /// @param target Value to multiply with numerator/denominator. @@ -121,7 +121,7 @@ contract LibMath is return isError; } - /// @dev Checks if rounding error > 0.1%. + /// @dev Checks if rounding error >= 0.1% when rounding up. /// @param numerator Numerator. /// @param denominator Denominator. /// @param target Value to multiply with numerator/denominator. @@ -137,9 +137,14 @@ contract LibMath is { require(denominator > 0, "DIVISION_BY_ZERO"); + // See the comments in `isRoundingError`. if (target == 0 || numerator == 0) { + // When either is zero, the ideal value and rounded value are zero + // and there is no rounding error. (Although the relative error + // is undefined.) return false; } + // Compute remainder as before uint256 remainder = mulmod(target, numerator, denominator); // TODO: safeMod remainder = safeSub(denominator, remainder) % denominator; -- cgit v1.2.3 From 7f78d7da9dd13d1f0068a292bcd1ee3c5439d5a8 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 23 Aug 2018 15:19:29 -0700 Subject: Add tests --- .../TestExchangeInternals.sol | 19 +++++- .../contracts/src/2.0.0/test/TestLibs/TestLibs.sol | 34 ++++++++++ packages/contracts/test/exchange/internal.ts | 43 +++++++++++++ packages/contracts/test/exchange/libs.ts | 72 +++++++++++++++------- 4 files changed, 145 insertions(+), 23 deletions(-) (limited to 'packages') diff --git a/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol b/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol index 239dd10a8..5e2ae2f0e 100644 --- a/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol +++ b/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol @@ -96,7 +96,7 @@ contract TestExchangeInternals is return getPartialAmountCeil(numerator, denominator, target); } - /// @dev Checks if rounding error > 0.1%. + /// @dev Checks if rounding error >= 0.1%. /// @param numerator Numerator. /// @param denominator Denominator. /// @param target Value to multiply with numerator/denominator. @@ -112,6 +112,23 @@ contract TestExchangeInternals is { return isRoundingError(numerator, denominator, target); } + + /// @dev Checks if rounding error >= 0.1%. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to multiply with numerator/denominator. + /// @return Rounding error is present. + function publicIsRoundingErrorCeil( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (bool isError) + { + return isRoundingErrorCeil(numerator, denominator, target); + } /// @dev Updates state with results of a fill order. /// @param order that was filled. diff --git a/packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol b/packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol index 4a99dd9c1..8c3d12226 100644 --- a/packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol +++ b/packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol @@ -66,6 +66,23 @@ contract TestLibs is return partialAmount; } + function publicGetPartialAmountCeil( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (uint256 partialAmount) + { + partialAmount = getPartialAmountCeil( + numerator, + denominator, + target + ); + return partialAmount; + } + function publicIsRoundingError( uint256 numerator, uint256 denominator, @@ -83,6 +100,23 @@ contract TestLibs is return isError; } + function publicIsRoundingErrorCeil( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (bool isError) + { + isError = isRoundingErrorCeil( + numerator, + denominator, + target + ); + return isError; + } + function publicGetOrderHash(Order memory order) public view diff --git a/packages/contracts/test/exchange/internal.ts b/packages/contracts/test/exchange/internal.ts index 48e1e620c..0c6ab3707 100644 --- a/packages/contracts/test/exchange/internal.ts +++ b/packages/contracts/test/exchange/internal.ts @@ -294,6 +294,49 @@ describe('Exchange core internal functions', () => { ); }); + describe('isRoundingErrorCeil', async () => { + async function referenceIsRoundingErrorAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise { + 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 { + 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 c08d62758..a5f31a498 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 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.publicIsRoundingError.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.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.publicIsRoundingError.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.publicIsRoundingError.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.publicIsRoundingError.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(); + }); }); }); -- cgit v1.2.3 From f6080367fe3a90762afea76a653c5df1315c45fa Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 24 Aug 2018 14:11:45 -0700 Subject: Disambiguate the operator precedence --- packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol index 8a1444af1..90ec9e2b6 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol @@ -109,9 +109,9 @@ contract LibMath is // Otherwise, we want the relative rounding error to be strictly // less than 0.1%. - // The relative error is `remainder / numerator * target`. + // The relative error is `remainder / (numerator * target)`. // We want the relative error less than 1 / 1000: - // remainder / numerator * denominator < 1 / 1000 + // remainder / (numerator * denominator) < 1 / 1000 // or equivalently: // 1000 * remainder < numerator * target // so we have a rounding error iff: -- cgit v1.2.3 From 80291caf7cef16f0b300484755960d92d6750a6a Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 24 Aug 2018 16:16:44 -0700 Subject: Append -Floor to getPartialAmount and isRoundingError --- .../extensions/Forwarder/MixinExchangeWrapper.sol | 4 ++-- .../extensions/Forwarder/MixinForwarderCore.sol | 4 ++-- .../src/2.0.0/extensions/Forwarder/MixinWeth.sol | 2 +- .../2.0.0/protocol/Exchange/MixinExchangeCore.sol | 8 ++++---- .../src/2.0.0/protocol/Exchange/MixinMatchOrders.sol | 4 ++-- .../protocol/Exchange/MixinWrapperFunctions.sol | 4 ++-- .../src/2.0.0/protocol/Exchange/libs/LibMath.sol | 8 ++++---- .../TestExchangeInternals/TestExchangeInternals.sol | 8 ++++---- .../contracts/src/2.0.0/test/TestLibs/TestLibs.sol | 8 ++++---- packages/contracts/test/exchange/internal.ts | 20 ++++++++++---------- packages/contracts/test/exchange/libs.ts | 6 +++--- .../test/utils/fill_order_combinatorial_utils.ts | 12 ++++++------ packages/contracts/test/utils/order_utils.ts | 2 +- packages/order-utils/src/order_state_utils.ts | 4 ++-- packages/order-utils/src/order_validation_utils.ts | 10 +++++----- packages/order-utils/src/utils.ts | 2 +- .../order-utils/test/order_validation_utils_test.ts | 12 ++++++------ 17 files changed, 59 insertions(+), 59 deletions(-) (limited to 'packages') diff --git a/packages/contracts/src/2.0.0/extensions/Forwarder/MixinExchangeWrapper.sol b/packages/contracts/src/2.0.0/extensions/Forwarder/MixinExchangeWrapper.sol index 218713d3c..a7ff400b9 100644 --- a/packages/contracts/src/2.0.0/extensions/Forwarder/MixinExchangeWrapper.sol +++ b/packages/contracts/src/2.0.0/extensions/Forwarder/MixinExchangeWrapper.sol @@ -163,7 +163,7 @@ contract MixinExchangeWrapper is // Convert the remaining amount of makerAsset to buy into remaining amount // of takerAsset to sell, assuming entire amount can be sold in the current order - uint256 remainingTakerAssetFillAmount = getPartialAmount( + uint256 remainingTakerAssetFillAmount = getPartialAmountFloor( orders[i].takerAssetAmount, orders[i].makerAssetAmount, remainingMakerAssetFillAmount @@ -231,7 +231,7 @@ contract MixinExchangeWrapper is // Convert the remaining amount of ZRX to buy into remaining amount // of WETH to sell, assuming entire amount can be sold in the current order. - uint256 remainingWethSellAmount = getPartialAmount( + uint256 remainingWethSellAmount = getPartialAmountFloor( orders[i].takerAssetAmount, safeSub(orders[i].makerAssetAmount, orders[i].takerFee), // our exchange rate after fees remainingZrxBuyAmount diff --git a/packages/contracts/src/2.0.0/extensions/Forwarder/MixinForwarderCore.sol b/packages/contracts/src/2.0.0/extensions/Forwarder/MixinForwarderCore.sol index 42cec4d36..14f191879 100644 --- a/packages/contracts/src/2.0.0/extensions/Forwarder/MixinForwarderCore.sol +++ b/packages/contracts/src/2.0.0/extensions/Forwarder/MixinForwarderCore.sol @@ -87,7 +87,7 @@ contract MixinForwarderCore is uint256 makerAssetAmountPurchased; if (orders[0].makerAssetData.equals(ZRX_ASSET_DATA)) { // Calculate amount of WETH that won't be spent on ETH fees. - wethSellAmount = getPartialAmount( + wethSellAmount = getPartialAmountFloor( PERCENTAGE_DENOMINATOR, safeAdd(PERCENTAGE_DENOMINATOR, feePercentage), msg.value @@ -103,7 +103,7 @@ contract MixinForwarderCore is makerAssetAmountPurchased = safeSub(orderFillResults.makerAssetFilledAmount, orderFillResults.takerFeePaid); } else { // 5% of WETH is reserved for filling feeOrders and paying feeRecipient. - wethSellAmount = getPartialAmount( + wethSellAmount = getPartialAmountFloor( MAX_WETH_FILL_PERCENTAGE, PERCENTAGE_DENOMINATOR, msg.value diff --git a/packages/contracts/src/2.0.0/extensions/Forwarder/MixinWeth.sol b/packages/contracts/src/2.0.0/extensions/Forwarder/MixinWeth.sol index 93e85e599..5863b522d 100644 --- a/packages/contracts/src/2.0.0/extensions/Forwarder/MixinWeth.sol +++ b/packages/contracts/src/2.0.0/extensions/Forwarder/MixinWeth.sol @@ -82,7 +82,7 @@ contract MixinWeth is uint256 wethRemaining = safeSub(msg.value, wethSold); // Calculate ETH fee to pay to feeRecipient. - uint256 ethFee = getPartialAmount( + uint256 ethFee = getPartialAmountFloor( feePercentage, PERCENTAGE_DENOMINATOR, wethSoldExcludingFeeOrders diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinExchangeCore.sol index ab5c6e507..f3a0591b2 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinExchangeCore.sol @@ -320,7 +320,7 @@ contract MixinExchangeCore is // Validate fill order rounding require( - !isRoundingError( + !isRoundingErrorFloor( takerAssetFilledAmount, order.takerAssetAmount, order.makerAssetAmount @@ -376,17 +376,17 @@ contract MixinExchangeCore is { // Compute proportional transfer amounts fillResults.takerAssetFilledAmount = takerAssetFilledAmount; - fillResults.makerAssetFilledAmount = getPartialAmount( + fillResults.makerAssetFilledAmount = getPartialAmountFloor( takerAssetFilledAmount, order.takerAssetAmount, order.makerAssetAmount ); - fillResults.makerFeePaid = getPartialAmount( + fillResults.makerFeePaid = getPartialAmountFloor( takerAssetFilledAmount, order.takerAssetAmount, order.makerFee ); - fillResults.takerFeePaid = getPartialAmount( + fillResults.takerFeePaid = getPartialAmountFloor( takerAssetFilledAmount, order.takerAssetAmount, order.takerFee diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinMatchOrders.sol index 56b309a1b..20f4b1c33 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinMatchOrders.sol @@ -183,7 +183,7 @@ contract MixinMatchOrders is leftTakerAssetFilledAmount = leftTakerAssetAmountRemaining; // The right order receives an amount proportional to how much was spent. - rightTakerAssetFilledAmount = getPartialAmount( + rightTakerAssetFilledAmount = getPartialAmountFloor( rightOrder.takerAssetAmount, rightOrder.makerAssetAmount, leftTakerAssetFilledAmount @@ -193,7 +193,7 @@ contract MixinMatchOrders is rightTakerAssetFilledAmount = rightTakerAssetAmountRemaining; // The left order receives an amount proportional to how much was spent. - leftTakerAssetFilledAmount = getPartialAmount( + leftTakerAssetFilledAmount = getPartialAmountFloor( rightOrder.makerAssetAmount, rightOrder.takerAssetAmount, rightTakerAssetFilledAmount diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol index 86194f461..1b77cfbcb 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinWrapperFunctions.sol @@ -298,7 +298,7 @@ contract MixinWrapperFunctions is // Convert the remaining amount of makerAsset to buy into remaining amount // of takerAsset to sell, assuming entire amount can be sold in the current order - uint256 remainingTakerAssetFillAmount = getPartialAmount( + uint256 remainingTakerAssetFillAmount = getPartialAmountFloor( orders[i].takerAssetAmount, orders[i].makerAssetAmount, remainingMakerAssetFillAmount @@ -350,7 +350,7 @@ contract MixinWrapperFunctions is // Convert the remaining amount of makerAsset to buy into remaining amount // of takerAsset to sell, assuming entire amount can be sold in the current order - uint256 remainingTakerAssetFillAmount = getPartialAmount( + uint256 remainingTakerAssetFillAmount = getPartialAmountFloor( orders[i].takerAssetAmount, orders[i].makerAssetAmount, remainingMakerAssetFillAmount diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol index 90ec9e2b6..7d58f70ce 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol @@ -25,12 +25,12 @@ contract LibMath is SafeMath { - /// @dev Calculates partial value given a numerator and denominator. + /// @dev Calculates partial value given a numerator and denominator rounded down. /// @param numerator Numerator. /// @param denominator Denominator. /// @param target Value to calculate partial of. /// @return Partial value of target rounded down. - function getPartialAmount( + function getPartialAmountFloor( uint256 numerator, uint256 denominator, uint256 target @@ -48,7 +48,7 @@ contract LibMath is return partialAmount; } - /// @dev Calculates partial value given a numerator and denominator. + /// @dev Calculates partial value given a numerator and denominator rounded down. /// @param numerator Numerator. /// @param denominator Denominator. /// @param target Value to calculate partial of. @@ -79,7 +79,7 @@ contract LibMath is /// @param denominator Denominator. /// @param target Value to multiply with numerator/denominator. /// @return Rounding error is present. - function isRoundingError( + function isRoundingErrorFloor( uint256 numerator, uint256 denominator, uint256 target diff --git a/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol b/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol index 5e2ae2f0e..da9313e02 100644 --- a/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol +++ b/packages/contracts/src/2.0.0/test/TestExchangeInternals/TestExchangeInternals.sol @@ -67,7 +67,7 @@ contract TestExchangeInternals is /// @param denominator Denominator. /// @param target Value to calculate partial of. /// @return Partial value of target. - function publicGetPartialAmount( + function publicGetPartialAmountFloor( uint256 numerator, uint256 denominator, uint256 target @@ -76,7 +76,7 @@ contract TestExchangeInternals is pure returns (uint256 partialAmount) { - return getPartialAmount(numerator, denominator, target); + return getPartialAmountFloor(numerator, denominator, target); } /// @dev Calculates partial value given a numerator and denominator. @@ -101,7 +101,7 @@ contract TestExchangeInternals is /// @param denominator Denominator. /// @param target Value to multiply with numerator/denominator. /// @return Rounding error is present. - function publicIsRoundingError( + function publicIsRoundingErrorFloor( uint256 numerator, uint256 denominator, uint256 target @@ -110,7 +110,7 @@ contract TestExchangeInternals is pure returns (bool isError) { - return isRoundingError(numerator, denominator, target); + return isRoundingErrorFloor(numerator, denominator, target); } /// @dev Checks if rounding error >= 0.1%. diff --git a/packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol b/packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol index 8c3d12226..c8c58545f 100644 --- a/packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol +++ b/packages/contracts/src/2.0.0/test/TestLibs/TestLibs.sol @@ -49,7 +49,7 @@ contract TestLibs is return fillOrderCalldata; } - function publicGetPartialAmount( + function publicGetPartialAmountFloor( uint256 numerator, uint256 denominator, uint256 target @@ -58,7 +58,7 @@ contract TestLibs is pure returns (uint256 partialAmount) { - partialAmount = getPartialAmount( + partialAmount = getPartialAmountFloor( numerator, denominator, target @@ -83,7 +83,7 @@ contract TestLibs is return partialAmount; } - function publicIsRoundingError( + function publicIsRoundingErrorFloor( uint256 numerator, uint256 denominator, uint256 target @@ -92,7 +92,7 @@ contract TestLibs is pure returns (bool isError) { - isError = isRoundingError( + isError = isRoundingErrorFloor( numerator, denominator, target diff --git a/packages/contracts/test/exchange/internal.ts b/packages/contracts/test/exchange/internal.ts index 0c6ab3707..14c0ff4cb 100644 --- a/packages/contracts/test/exchange/internal.ts +++ b/packages/contracts/test/exchange/internal.ts @@ -75,7 +75,7 @@ describe('Exchange core internal functions', () => { // Note(albrow): Don't forget to add beforeEach and afterEach calls to reset // the blockchain state for any tests which modify it! - async function referenceGetPartialAmountAsync( + async function referenceGetPartialAmountFloorAsync( numerator: BigNumber, denominator: BigNumber, target: BigNumber, @@ -165,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, @@ -199,18 +199,18 @@ describe('Exchange core internal functions', () => { ); }); - describe('getPartialAmount', async () => { - async function testGetPartialAmountAsync( + describe('getPartialAmountFloor', async () => { + async function testGetPartialAmountFloorAsync( numerator: BigNumber, denominator: BigNumber, target: BigNumber, ): Promise { - return testExchange.publicGetPartialAmount.callAsync(numerator, denominator, target); + return testExchange.publicGetPartialAmountFloor.callAsync(numerator, denominator, target); } await testCombinatoriallyWithReferenceFuncAsync( 'getPartialAmount', - referenceGetPartialAmountAsync, - testGetPartialAmountAsync, + referenceGetPartialAmountFloorAsync, + testGetPartialAmountFloorAsync, [uint256Values, uint256Values, uint256Values], ); }); @@ -284,7 +284,7 @@ describe('Exchange core internal functions', () => { denominator: BigNumber, target: BigNumber, ): Promise { - return testExchange.publicIsRoundingError.callAsync(numerator, denominator, target); + return testExchange.publicIsRoundingErrorFloor.callAsync(numerator, denominator, target); } await testCombinatoriallyWithReferenceFuncAsync( 'isRoundingError', diff --git a/packages/contracts/test/exchange/libs.ts b/packages/contracts/test/exchange/libs.ts index a5f31a498..37234489e 100644 --- a/packages/contracts/test/exchange/libs.ts +++ b/packages/contracts/test/exchange/libs.ts @@ -77,7 +77,7 @@ describe('Exchange libs', () => { 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); + 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 () => { @@ -85,7 +85,7 @@ describe('Exchange libs', () => { 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); + 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 () => { @@ -93,7 +93,7 @@ describe('Exchange libs', () => { 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); + const isRoundingError = await libs.publicIsRoundingErrorFloor.callAsync(numerator, denominator, target); expect(isRoundingError).to.be.true(); }); }); diff --git a/packages/contracts/test/utils/fill_order_combinatorial_utils.ts b/packages/contracts/test/utils/fill_order_combinatorial_utils.ts index a9318571c..92d0f4003 100644 --- a/packages/contracts/test/utils/fill_order_combinatorial_utils.ts +++ b/packages/contracts/test/utils/fill_order_combinatorial_utils.ts @@ -467,17 +467,17 @@ export class FillOrderCombinatorialUtils { ? remainingTakerAmountToFill : alreadyFilledTakerAmount.add(takerAssetFillAmount); - const expFilledMakerAmount = orderUtils.getPartialAmount( + const expFilledMakerAmount = orderUtils.getPartialAmountFloor( expFilledTakerAmount, signedOrder.takerAssetAmount, signedOrder.makerAssetAmount, ); - const expMakerFeePaid = orderUtils.getPartialAmount( + const expMakerFeePaid = orderUtils.getPartialAmountFloor( expFilledTakerAmount, signedOrder.takerAssetAmount, signedOrder.makerFee, ); - const expTakerFeePaid = orderUtils.getPartialAmount( + const expTakerFeePaid = orderUtils.getPartialAmountFloor( expFilledTakerAmount, signedOrder.takerAssetAmount, signedOrder.takerFee, @@ -668,7 +668,7 @@ export class FillOrderCombinatorialUtils { signedOrder: SignedOrder, takerAssetFillAmount: BigNumber, ): Promise { - const makerAssetFillAmount = orderUtils.getPartialAmount( + const makerAssetFillAmount = orderUtils.getPartialAmountFloor( takerAssetFillAmount, signedOrder.takerAssetAmount, signedOrder.makerAssetAmount, @@ -705,7 +705,7 @@ export class FillOrderCombinatorialUtils { ); } - const makerFee = orderUtils.getPartialAmount( + const makerFee = orderUtils.getPartialAmountFloor( takerAssetFillAmount, signedOrder.takerAssetAmount, signedOrder.makerFee, @@ -829,7 +829,7 @@ export class FillOrderCombinatorialUtils { ); } - const takerFee = orderUtils.getPartialAmount( + const takerFee = orderUtils.getPartialAmountFloor( takerAssetFillAmount, signedOrder.takerAssetAmount, signedOrder.takerFee, diff --git a/packages/contracts/test/utils/order_utils.ts b/packages/contracts/test/utils/order_utils.ts index 019f6e74b..444e27c44 100644 --- a/packages/contracts/test/utils/order_utils.ts +++ b/packages/contracts/test/utils/order_utils.ts @@ -5,7 +5,7 @@ import { constants } from './constants'; import { CancelOrder, MatchOrder } from './types'; export const orderUtils = { - getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { + getPartialAmountFloor(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { const partialAmount = numerator .mul(target) .div(denominator) diff --git a/packages/order-utils/src/order_state_utils.ts b/packages/order-utils/src/order_state_utils.ts index a0e24acf0..8398776aa 100644 --- a/packages/order-utils/src/order_state_utils.ts +++ b/packages/order-utils/src/order_state_utils.ts @@ -81,7 +81,7 @@ export class OrderStateUtils { const remainingTakerAssetAmount = signedOrder.takerAssetAmount.minus( sidedOrderRelevantState.filledTakerAssetAmount, ); - const isRoundingError = OrderValidationUtils.isRoundingError( + const isRoundingError = OrderValidationUtils.isRoundingErrorFloor( remainingTakerAssetAmount, signedOrder.takerAssetAmount, signedOrder.makerAssetAmount, @@ -191,7 +191,7 @@ export class OrderStateUtils { ); const remainingFillableTakerAssetAmountGivenMakersStatus = signedOrder.makerAssetAmount.eq(0) ? new BigNumber(0) - : utils.getPartialAmount( + : utils.getPartialAmountFloor( orderRelevantMakerState.remainingFillableAssetAmount, signedOrder.makerAssetAmount, signedOrder.takerAssetAmount, diff --git a/packages/order-utils/src/order_validation_utils.ts b/packages/order-utils/src/order_validation_utils.ts index 972e6f6d6..8227fb07c 100644 --- a/packages/order-utils/src/order_validation_utils.ts +++ b/packages/order-utils/src/order_validation_utils.ts @@ -24,7 +24,7 @@ export class OrderValidationUtils { * @param denominator Denominator value. When used to check an order, pass in `order.takerAssetAmount` * @param target Target value. When used to check an order, pass in `order.makerAssetAmount` */ - public static isRoundingError(numerator: BigNumber, denominator: BigNumber, target: BigNumber): boolean { + public static isRoundingErrorFloor(numerator: BigNumber, denominator: BigNumber, target: BigNumber): boolean { // Solidity's mulmod() in JS // Source: https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#mathematical-and-cryptographic-functions if (denominator.eq(0)) { @@ -58,7 +58,7 @@ export class OrderValidationUtils { zrxAssetData: string, ): Promise { try { - const fillMakerTokenAmount = utils.getPartialAmount( + const fillMakerTokenAmount = utils.getPartialAmountFloor( fillTakerAssetAmount, signedOrder.takerAssetAmount, signedOrder.makerAssetAmount, @@ -79,7 +79,7 @@ export class OrderValidationUtils { TradeSide.Taker, TransferType.Trade, ); - const makerFeeAmount = utils.getPartialAmount( + const makerFeeAmount = utils.getPartialAmountFloor( fillTakerAssetAmount, signedOrder.takerAssetAmount, signedOrder.makerFee, @@ -92,7 +92,7 @@ export class OrderValidationUtils { TradeSide.Maker, TransferType.Fee, ); - const takerFeeAmount = utils.getPartialAmount( + const takerFeeAmount = utils.getPartialAmountFloor( fillTakerAssetAmount, signedOrder.takerAssetAmount, signedOrder.takerFee, @@ -218,7 +218,7 @@ export class OrderValidationUtils { zrxAssetData, ); - const wouldRoundingErrorOccur = OrderValidationUtils.isRoundingError( + const wouldRoundingErrorOccur = OrderValidationUtils.isRoundingErrorFloor( desiredFillTakerTokenAmount, signedOrder.takerAssetAmount, signedOrder.makerAssetAmount, diff --git a/packages/order-utils/src/utils.ts b/packages/order-utils/src/utils.ts index 7aaaf0609..0ff05e8ed 100644 --- a/packages/order-utils/src/utils.ts +++ b/packages/order-utils/src/utils.ts @@ -12,7 +12,7 @@ export const utils = { const milisecondsInSecond = 1000; return new BigNumber(Date.now() / milisecondsInSecond).round(); }, - getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { + getPartialAmountFloor(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { const fillMakerTokenAmount = numerator .mul(target) .div(denominator) diff --git a/packages/order-utils/test/order_validation_utils_test.ts b/packages/order-utils/test/order_validation_utils_test.ts index d3ff867d7..d3133c0a6 100644 --- a/packages/order-utils/test/order_validation_utils_test.ts +++ b/packages/order-utils/test/order_validation_utils_test.ts @@ -16,7 +16,7 @@ describe('OrderValidationUtils', () => { 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 = OrderValidationUtils.isRoundingError(numerator, denominator, target); + const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); expect(isRoundingError).to.be.false(); }); @@ -25,7 +25,7 @@ describe('OrderValidationUtils', () => { 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 = OrderValidationUtils.isRoundingError(numerator, denominator, target); + const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); expect(isRoundingError).to.be.false(); }); @@ -34,7 +34,7 @@ describe('OrderValidationUtils', () => { 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 = OrderValidationUtils.isRoundingError(numerator, denominator, target); + const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); expect(isRoundingError).to.be.true(); }); @@ -43,7 +43,7 @@ describe('OrderValidationUtils', () => { const denominator = new BigNumber(7); const target = new BigNumber(10); // rounding error = ((3*10/7) - floor(3*10/7)) / (3*10/7) = 6.67% - const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target); + const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); expect(isRoundingError).to.be.true(); }); @@ -52,7 +52,7 @@ describe('OrderValidationUtils', () => { const denominator = new BigNumber(2); const target = new BigNumber(10); - const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target); + const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); expect(isRoundingError).to.be.false(); }); @@ -63,7 +63,7 @@ describe('OrderValidationUtils', () => { const target = new BigNumber(105762562); // rounding error = ((76564*105762562/676373677) - floor(76564*105762562/676373677)) / // (76564*105762562/676373677) = 0.0007% - const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target); + const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); expect(isRoundingError).to.be.false(); }); }); -- cgit v1.2.3 From bc686fcbf3a0f9eea0c96107b9880314027f2d02 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 24 Aug 2018 16:17:02 -0700 Subject: Stylistic fixes --- .../src/2.0.0/protocol/Exchange/libs/LibMath.sol | 25 +++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) (limited to 'packages') diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol index 7d58f70ce..0e0fba5d2 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibMath.sol @@ -39,7 +39,10 @@ contract LibMath is pure returns (uint256 partialAmount) { - require(denominator > 0, "DIVISION_BY_ZERO"); + require( + denominator > 0, + "DIVISION_BY_ZERO" + ); partialAmount = safeDiv( safeMul(numerator, target), @@ -62,13 +65,19 @@ contract LibMath is pure returns (uint256 partialAmount) { - require(denominator > 0, "DIVISION_BY_ZERO"); + require( + denominator > 0, + "DIVISION_BY_ZERO" + ); // safeDiv computes `floor(a / b)`. We use the identity (a, b integer): // ceil(a / b) = floor((a + b - 1) / b) // To implement `ceil(a / b)` using safeDiv. partialAmount = safeDiv( - safeAdd(safeMul(numerator, target), safeSub(denominator, 1)), + safeAdd( + safeMul(numerator, target), + safeSub(denominator, 1) + ), denominator ); return partialAmount; @@ -88,7 +97,10 @@ contract LibMath is pure returns (bool isError) { - require(denominator > 0, "DIVISION_BY_ZERO"); + require( + denominator > 0, + "DIVISION_BY_ZERO" + ); // The absolute rounding error is the difference between the rounded // value and the ideal value. The relative rounding error is the @@ -135,7 +147,10 @@ contract LibMath is pure returns (bool isError) { - require(denominator > 0, "DIVISION_BY_ZERO"); + require( + denominator > 0, + "DIVISION_BY_ZERO" + ); // See the comments in `isRoundingError`. if (target == 0 || numerator == 0) { -- cgit v1.2.3 From cc1fac9bbee2656bdb327490de42922abfc5125a Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Fri, 24 Aug 2018 17:02:42 -0700 Subject: Fix linting errors --- packages/contracts/test/exchange/internal.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/exchange/internal.ts b/packages/contracts/test/exchange/internal.ts index 14c0ff4cb..2521665c2 100644 --- a/packages/contracts/test/exchange/internal.ts +++ b/packages/contracts/test/exchange/internal.ts @@ -220,9 +220,9 @@ describe('Exchange core internal functions', () => { numerator: BigNumber, denominator: BigNumber, target: BigNumber, - ) { + ): Promise { if (denominator.eq(0)) { - throw new Error('revert DIVISION_BY_ZERO'); + throw divisionByZeroErrorForCall; } const product = numerator.mul(target); const offset = product.add(denominator.sub(1)); -- cgit v1.2.3