From c30d59d5d3eebd3da902733e0d6edcf35839ed95 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 31 Jul 2019 15:56:36 -0400 Subject: [PATCH] `@0x/types`: Add `FillResults`, `MatchedFillResults`, and `BatchMatchedFillResults` types. `@0x/contracts-utils`: Add reference functions for `SafeMath`. `@0x/contracts-exchange-libs`: Add reference functions for `LibMath` and `LibFillResults`. `@0x/contracts-test-utils`: Move `*FillResults` types to `@0x/types`. `@0x/contracts-test-utils`: Add `log_utils.ts`. `@0x/contracts-test-utils`: Add `hexRandom()` to `hex_utils.ts`. `@0x/contracts-test-utils`: Add the contstants: `MAX_UINT256`, `ADDRESS_LENGTH`. --- contracts/exchange-libs/CHANGELOG.json | 55 ++++---- contracts/exchange-libs/src/index.ts | 3 + .../exchange-libs/src/reference_functions.ts | 122 ++++++++++++++++++ contracts/test-utils/CHANGELOG.json | 16 +++ contracts/test-utils/src/index.ts | 3 - contracts/test-utils/src/types.ts | 21 --- contracts/utils/CHANGELOG.json | 4 + contracts/utils/src/index.ts | 3 + contracts/utils/src/reference_functions.ts | 44 +++++++ packages/types/CHANGELOG.json | 4 + packages/types/src/index.ts | 21 +++ 11 files changed, 249 insertions(+), 47 deletions(-) create mode 100644 contracts/exchange-libs/src/reference_functions.ts create mode 100644 contracts/utils/src/reference_functions.ts diff --git a/contracts/exchange-libs/CHANGELOG.json b/contracts/exchange-libs/CHANGELOG.json index f5f95a1b70..f8ce90f4df 100644 --- a/contracts/exchange-libs/CHANGELOG.json +++ b/contracts/exchange-libs/CHANGELOG.json @@ -1,29 +1,7 @@ [ { - "timestamp": 1563193019, - "version": "3.0.2", + "version": "3.1.0", "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1563047529, - "version": "3.0.1", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "version": "3.0.0", - "changes": [ - { - "note": "Move `LibTransactionDecoder` to contracts/dev-utils package", - "pr": 1848 - }, { "note": "Break up `LibEIP712` into reusable components", "pr": 1742 @@ -75,6 +53,37 @@ { "note": "Add `expirationTimeSeconds` to `ZeroExTransaction` struct", "pr": 1823 + }, + { + "note": "Add reference functions for `LibMath` and `LibFillResults`", + "pr": "TODO" + } + ] + }, + { + "timestamp": 1563193019, + "version": "3.0.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1563047529, + "version": "3.0.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "3.0.0", + "changes": [ + { + "note": "Move `LibTransactionDecoder` to contracts/dev-utils package", + "pr": 1848 } ], "timestamp": 1563006338 diff --git a/contracts/exchange-libs/src/index.ts b/contracts/exchange-libs/src/index.ts index d55f08ea2d..7e9c4805a6 100644 --- a/contracts/exchange-libs/src/index.ts +++ b/contracts/exchange-libs/src/index.ts @@ -1,2 +1,5 @@ export * from './artifacts'; export * from './wrappers'; + +import * as reference_functions from './reference_functions'; +export import ReferenceFunctions = reference_functions; diff --git a/contracts/exchange-libs/src/reference_functions.ts b/contracts/exchange-libs/src/reference_functions.ts new file mode 100644 index 0000000000..6a41601166 --- /dev/null +++ b/contracts/exchange-libs/src/reference_functions.ts @@ -0,0 +1,122 @@ +import { ReferenceFunctions } from '@0x/contracts-utils'; +import { LibMathRevertErrors } from '@0x/order-utils'; +import { FillResults } from '@0x/types'; +import { BigNumber } from '@0x/utils'; + +const { + safeAdd, + safeSub, + safeMul, + safeDiv, +} = ReferenceFunctions; + +export function isRoundingErrorFloor( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, +): boolean { + if (denominator.eq(0)) { + throw new LibMathRevertErrors.DivisionByZeroError(); + } + if (numerator.eq(0) || target.eq(0)) { + return false; + } + const remainder = numerator.multipliedBy(target).mod(denominator); + return safeMul(new BigNumber(1000), remainder).gte(safeMul(numerator, target)); +} + +export function isRoundingErrorCeil( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, +): boolean { + if (denominator.eq(0)) { + throw new LibMathRevertErrors.DivisionByZeroError(); + } + if (numerator.eq(0) || target.eq(0)) { + return false; + } + let remainder = numerator.multipliedBy(target).mod(denominator); + remainder = safeSub(denominator, remainder).mod(denominator); + return safeMul(new BigNumber(1000), remainder).gte(safeMul(numerator, target)); +} + +export function safeGetPartialAmountFloor( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, +): BigNumber { + if (denominator.eq(0)) { + throw new LibMathRevertErrors.DivisionByZeroError(); + } + if (isRoundingErrorFloor(numerator, denominator, target)) { + throw new LibMathRevertErrors.RoundingError(numerator, denominator, target); + } + return safeDiv( + safeMul(numerator, target), + denominator, + ); +} + +export function safeGetPartialAmountCeil( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, +): BigNumber { + if (denominator.eq(0)) { + throw new LibMathRevertErrors.DivisionByZeroError(); + } + if (isRoundingErrorCeil(numerator, denominator, target)) { + throw new LibMathRevertErrors.RoundingError(numerator, denominator, target); + } + return safeDiv( + safeAdd( + safeMul(numerator, target), + safeSub(denominator, new BigNumber(1)), + ), + denominator, + ); +} + +export function getPartialAmountFloor( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, +): BigNumber { + if (denominator.eq(0)) { + throw new LibMathRevertErrors.DivisionByZeroError(); + } + return safeDiv( + safeMul(numerator, target), + denominator, + ); +} + +export function getPartialAmountCeil( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, +): BigNumber { + if (denominator.eq(0)) { + throw new LibMathRevertErrors.DivisionByZeroError(); + } + return safeDiv( + safeAdd( + safeMul(numerator, target), + safeSub(denominator, new BigNumber(1)), + ), + denominator, + ); +} + +export function addFillResults( + a: FillResults, + b: FillResults, +): FillResults { + return { + makerAssetFilledAmount: safeAdd(a.makerAssetFilledAmount, b.makerAssetFilledAmount), + takerAssetFilledAmount: safeAdd(a.takerAssetFilledAmount, b.takerAssetFilledAmount), + makerFeePaid: safeAdd(a.makerFeePaid, b.makerFeePaid), + takerFeePaid: safeAdd(a.takerFeePaid, b.takerFeePaid), + }; +} diff --git a/contracts/test-utils/CHANGELOG.json b/contracts/test-utils/CHANGELOG.json index 21eb15b419..3b921bea73 100644 --- a/contracts/test-utils/CHANGELOG.json +++ b/contracts/test-utils/CHANGELOG.json @@ -37,6 +37,22 @@ { "note": "Introduce Mocha blockchain extensions", "pr": 2007 + }, + { + "note": "Move `*FillResults` types to `@0x/types`", + "pr": "TODO" + }, + { + "note": "Add `log_utils.ts`", + "pr": "TODO" + }, + { + "note": "Add `haxRandom()` to `hex_utils.ts`", + "pr": "TODO" + }, + { + "note": "Add the constants: `MAX_UINT256`, `ADDRESS_LENGTH`", + "pr": "TODO" } ] }, diff --git a/contracts/test-utils/src/index.ts b/contracts/test-utils/src/index.ts index c626fbb77a..040ba809d7 100644 --- a/contracts/test-utils/src/index.ts +++ b/contracts/test-utils/src/index.ts @@ -29,7 +29,6 @@ export { TransactionFactory } from './transaction_factory'; export { testWithReferenceFuncAsync } from './test_with_reference'; export { hexConcat, hexRandom } from './hex_utils'; export { - BatchMatchedFillResults, BatchMatchOrder, ContractName, ERC20BalancesByOwner, @@ -37,10 +36,8 @@ export { ERC1155HoldingsByOwner, ERC1155NonFungibleHoldingsByOwner, ERC721TokenIdsByOwner, - FillResults, MarketBuyOrders, MarketSellOrders, - MatchedFillResults, OrderInfo, OrderStatus, Token, diff --git a/contracts/test-utils/src/types.ts b/contracts/test-utils/src/types.ts index b085a34d0c..5c534b43f6 100644 --- a/contracts/test-utils/src/types.ts +++ b/contracts/test-utils/src/types.ts @@ -143,24 +143,3 @@ export interface MatchOrder { leftSignature: string; rightSignature: string; } - -export interface FillResults { - makerAssetFilledAmount: BigNumber; - takerAssetFilledAmount: BigNumber; - makerFeePaid: BigNumber; - takerFeePaid: BigNumber; -} - -export interface MatchedFillResults { - left: FillResults; - right: FillResults; - profitInLeftMakerAsset: BigNumber; - profitInRightMakerAsset: BigNumber; -} - -export interface BatchMatchedFillResults { - left: FillResults[]; - right: FillResults[]; - profitInLeftMakerAsset: BigNumber; - profitInRightMakerAsset: BigNumber; -} diff --git a/contracts/utils/CHANGELOG.json b/contracts/utils/CHANGELOG.json index 8cf9dbdc51..19a0c19fd0 100644 --- a/contracts/utils/CHANGELOG.json +++ b/contracts/utils/CHANGELOG.json @@ -33,6 +33,10 @@ { "note": "Updated Ownable to revert when the owner attempts to transfer ownership to the zero address", "pr": 2019 + }, + { + "note": "Add reference functions for `SafeMath` functions.", + "pr": "2031" } ] }, diff --git a/contracts/utils/src/index.ts b/contracts/utils/src/index.ts index d55f08ea2d..7e9c4805a6 100644 --- a/contracts/utils/src/index.ts +++ b/contracts/utils/src/index.ts @@ -1,2 +1,5 @@ export * from './artifacts'; export * from './wrappers'; + +import * as reference_functions from './reference_functions'; +export import ReferenceFunctions = reference_functions; diff --git a/contracts/utils/src/reference_functions.ts b/contracts/utils/src/reference_functions.ts new file mode 100644 index 0000000000..717baafc09 --- /dev/null +++ b/contracts/utils/src/reference_functions.ts @@ -0,0 +1,44 @@ +import { AnyRevertError, BigNumber, SafeMathRevertErrors } from '@0x/utils'; + +const MAX_UINT256 = new BigNumber(2).pow(256).minus(1); + +export function safeAdd(a: BigNumber, b: BigNumber): BigNumber { + const r = a.plus(b); + if (r.isGreaterThan(MAX_UINT256)) { + throw new SafeMathRevertErrors.SafeMathError( + SafeMathRevertErrors.SafeMathErrorCodes.Uint256AdditionOverflow, + a, + b, + ); + } + return r; +} + +export function safeSub(a: BigNumber, b: BigNumber): BigNumber { + const r = a.minus(b); + if (r.isLessThan(0)) { + throw new SafeMathRevertErrors.SafeMathError( + SafeMathRevertErrors.SafeMathErrorCodes.Uint256SubtractionUnderflow, + a, + b, + ); + } + return r; +} + +export function safeMul(a: BigNumber, b: BigNumber): BigNumber { + const r = a.times(b); + if (r.isGreaterThan(MAX_UINT256)) { + // Solidity implementation does not throw a reason. + throw new AnyRevertError(); + } + return r; +} + +export function safeDiv(a: BigNumber, b: BigNumber): BigNumber { + if (b.isEqualTo(0)) { + // Solidity implementation does not throw a reason. + throw new AnyRevertError(); + } + return a.dividedToIntegerBy(b); +} diff --git a/packages/types/CHANGELOG.json b/packages/types/CHANGELOG.json index f47eea8aec..a7e3f894fb 100644 --- a/packages/types/CHANGELOG.json +++ b/packages/types/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Add `OrderStatus` type", "pr": 1761 + }, + { + "note": "Add `FillResults`, `MatchedFillResults`, `BatchMatchedFillResults` types", + "pr": "TODO" } ] }, diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 57ce884d27..b5bc97b63a 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -810,3 +810,24 @@ export enum OrderTransferResults { MakerFeeAssetDataFailed, TransfersSuccessful, } + +export interface FillResults { + makerAssetFilledAmount: BigNumber; + takerAssetFilledAmount: BigNumber; + makerFeePaid: BigNumber; + takerFeePaid: BigNumber; +} + +export interface MatchedFillResults { + left: FillResults; + right: FillResults; + profitInLeftMakerAsset: BigNumber; + profitInRightMakerAsset: BigNumber; +} + +export interface BatchMatchedFillResults { + left: FillResults[]; + right: FillResults[]; + profitInLeftMakerAsset: BigNumber; + profitInRightMakerAsset: BigNumber; +}