aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorBrandon Millman <brandon@0xproject.com>2018-08-24 08:42:48 +0800
committerGitHub <noreply@github.com>2018-08-24 08:42:48 +0800
commite96f36751acf89c131c324e311875922685bf89b (patch)
tree7a7eb979d9987aa712d1e60c3415bdf266ef7851 /packages
parent2c660e62d34bc59957c04a52fb43975470f009e7 (diff)
parent21c37ba62fcc517d0a332b951be874e255763fec (diff)
downloaddexon-0x-contracts-e96f36751acf89c131c324e311875922685bf89b.tar
dexon-0x-contracts-e96f36751acf89c131c324e311875922685bf89b.tar.gz
dexon-0x-contracts-e96f36751acf89c131c324e311875922685bf89b.tar.bz2
dexon-0x-contracts-e96f36751acf89c131c324e311875922685bf89b.tar.lz
dexon-0x-contracts-e96f36751acf89c131c324e311875922685bf89b.tar.xz
dexon-0x-contracts-e96f36751acf89c131c324e311875922685bf89b.tar.zst
dexon-0x-contracts-e96f36751acf89c131c324e311875922685bf89b.zip
Merge pull request #997 from 0xProject/feature/forwarder-helper/init
[forwarder-helper] Initial scaffolding for the forwarder-helper package
Diffstat (limited to 'packages')
-rw-r--r--packages/forwarder-helper/.npmignore8
-rw-r--r--packages/forwarder-helper/CHANGELOG.json11
-rw-r--r--packages/forwarder-helper/README.md83
-rw-r--r--packages/forwarder-helper/package.json74
-rw-r--r--packages/forwarder-helper/src/constants.ts5
-rw-r--r--packages/forwarder-helper/src/forwarder_helper_factory.ts25
-rw-r--r--packages/forwarder-helper/src/forwarder_helper_impl.ts64
-rw-r--r--packages/forwarder-helper/src/globals.d.ts6
-rw-r--r--packages/forwarder-helper/src/index.ts2
-rw-r--r--packages/forwarder-helper/src/types.ts43
-rw-r--r--packages/forwarder-helper/src/utils/forwarder_helper_impl_config_utils.ts92
-rw-r--r--packages/forwarder-helper/test/forwarder_helper_impl_test.ts136
-rw-r--r--packages/forwarder-helper/test/utils/chai_setup.ts13
-rw-r--r--packages/forwarder-helper/tsconfig.json7
-rw-r--r--packages/forwarder-helper/tslint.json3
-rw-r--r--packages/order-utils/CHANGELOG.json14
-rw-r--r--packages/order-utils/src/market_utils.ts4
-rw-r--r--packages/order-utils/src/sorting_utils.ts2
-rw-r--r--packages/order-utils/src/types.ts2
-rw-r--r--packages/order-utils/test/market_utils_test.ts24
20 files changed, 602 insertions, 16 deletions
diff --git a/packages/forwarder-helper/.npmignore b/packages/forwarder-helper/.npmignore
new file mode 100644
index 000000000..5333847e7
--- /dev/null
+++ b/packages/forwarder-helper/.npmignore
@@ -0,0 +1,8 @@
+.*
+yarn-error.log
+/src/
+/scripts/
+/schemas/
+test/
+tsconfig.json
+/lib/src/monorepo_scripts/
diff --git a/packages/forwarder-helper/CHANGELOG.json b/packages/forwarder-helper/CHANGELOG.json
new file mode 100644
index 000000000..be5b244b3
--- /dev/null
+++ b/packages/forwarder-helper/CHANGELOG.json
@@ -0,0 +1,11 @@
+[
+ {
+ "version": "1.0.1-rc.1",
+ "changes": [
+ {
+ "note": "Add initial forwarderHelperFactory",
+ "pr": 997
+ }
+ ]
+ }
+]
diff --git a/packages/forwarder-helper/README.md b/packages/forwarder-helper/README.md
new file mode 100644
index 000000000..c74526910
--- /dev/null
+++ b/packages/forwarder-helper/README.md
@@ -0,0 +1,83 @@
+## @0xproject/forwarder-helper
+
+Provides convenience objects to help work with the Forwarder Contract
+
+### Read the [Documentation](https://0xproject.com/docs/forwarder-helper).
+
+## Installation
+
+```bash
+yarn add @0xproject/forwarder-helper
+```
+
+**Import**
+
+```typescript
+import { forwarderHelperFactory } from '@0xproject/forwarder-helper';
+```
+
+or
+
+```javascript
+var forwarderHelperFactory = require('@0xproject/forwarder-helper').forwarderHelperFactory;
+```
+
+If your project is in [TypeScript](https://www.typescriptlang.org/), add the following to your `tsconfig.json`:
+
+```json
+"compilerOptions": {
+ "typeRoots": ["node_modules/@0xproject/typescript-typings/types", "node_modules/@types"],
+}
+```
+
+## Contributing
+
+We welcome improvements and fixes from the wider community! To report bugs within this package, please create an issue in this repository.
+
+Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
+
+### Install dependencies
+
+If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them:
+
+```bash
+yarn config set workspaces-experimental true
+```
+
+Then install dependencies
+
+```bash
+yarn install
+```
+
+### Build
+
+To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory:
+
+```bash
+PKG=@0xproject/forwarder-helper yarn build
+```
+
+Or continuously rebuild on change:
+
+```bash
+PKG=@0xproject/forwarder-helper yarn watch
+```
+
+### Clean
+
+```bash
+yarn clean
+```
+
+### Lint
+
+```bash
+yarn lint
+```
+
+### Run Tests
+
+```bash
+yarn test
+```
diff --git a/packages/forwarder-helper/package.json b/packages/forwarder-helper/package.json
new file mode 100644
index 000000000..fcb483885
--- /dev/null
+++ b/packages/forwarder-helper/package.json
@@ -0,0 +1,74 @@
+{
+ "name": "@0xproject/forwarder-helper",
+ "version": "1.0.0-rc.1",
+ "engines": {
+ "node": ">=6.12"
+ },
+ "description": "Convenience object for working with the forwarder contract",
+ "main": "lib/src/index.js",
+ "types": "lib/src/index.d.ts",
+ "scripts": {
+ "watch_without_deps": "tsc -w",
+ "lint": "tslint --project .",
+ "test": "yarn run_mocha",
+ "rebuild_and_test": "run-s clean build test",
+ "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
+ "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
+ "test:circleci": "yarn test:coverage",
+ "run_mocha":
+ "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit",
+ "clean": "shx rm -rf lib test_temp scripts",
+ "build": "tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
+ "manual:postpublish": "yarn build; node ./scripts/postpublish.js",
+ "docs:stage": "node scripts/stage_docs.js",
+ "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES",
+ "upload_docs_json":
+ "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json"
+ },
+ "config": {
+ "postpublish": {
+ "assets": []
+ }
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/0xProject/0x-monorepo.git"
+ },
+ "author": "",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/0xProject/0x-monorepo/issues"
+ },
+ "homepage": "https://github.com/0xProject/0x-monorepo/packages/forwarder-helper/README.md",
+ "dependencies": {
+ "@0xproject/assert": "^1.0.5",
+ "@0xproject/json-schemas": "^1.0.1-rc.4",
+ "@0xproject/order-utils": "^1.0.1-rc.3",
+ "@0xproject/types": "^1.0.1-rc.4",
+ "@0xproject/typescript-typings": "^1.0.4",
+ "@0xproject/utils": "^1.0.5",
+ "@types/node": "^8.0.53",
+ "lodash": "^4.17.10"
+ },
+ "devDependencies": {
+ "@0xproject/tslint-config": "^1.0.5",
+ "@types/lodash": "^4.14.116",
+ "@types/mocha": "^2.2.42",
+ "chai": "^4.0.1",
+ "chai-as-promised": "^7.1.0",
+ "chai-bignumber": "^2.0.1",
+ "copyfiles": "^1.2.0",
+ "dirty-chai": "^2.0.1",
+ "make-promises-safe": "^1.1.0",
+ "mocha": "^4.1.0",
+ "npm-run-all": "^4.1.2",
+ "nyc": "^11.0.1",
+ "shx": "^0.2.2",
+ "tslint": "5.11.0",
+ "typedoc": "0xProject/typedoc",
+ "typescript": "3.0.1"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/forwarder-helper/src/constants.ts b/packages/forwarder-helper/src/constants.ts
new file mode 100644
index 000000000..0ad30e4c0
--- /dev/null
+++ b/packages/forwarder-helper/src/constants.ts
@@ -0,0 +1,5 @@
+import { BigNumber } from '@0xproject/utils';
+
+export const constants = {
+ ZERO_AMOUNT: new BigNumber(0),
+};
diff --git a/packages/forwarder-helper/src/forwarder_helper_factory.ts b/packages/forwarder-helper/src/forwarder_helper_factory.ts
new file mode 100644
index 000000000..95f11f555
--- /dev/null
+++ b/packages/forwarder-helper/src/forwarder_helper_factory.ts
@@ -0,0 +1,25 @@
+import { assert } from '@0xproject/assert';
+import { schemas } from '@0xproject/json-schemas';
+import { SignedOrder } from '@0xproject/types';
+
+import { ForwarderHelperImpl, ForwarderHelperImplConfig } from './forwarder_helper_impl';
+import { ForwarderHelper } from './types';
+
+export const forwarderHelperFactory = {
+ /**
+ * Given an array of orders and an array of feeOrders
+ * @param orders An array of objects conforming to SignedOrder. Each order should specify the same makerAssetData and takerAssetData
+ * @param feeOrders An array of objects conforming to SignedOrder. Each order should specify ZRX as makerAssetData WETH as takerAssetData
+ * @return A ForwarderHelper, see type for definition
+ */
+ getForwarderHelperForOrders(orders: SignedOrder[], feeOrders: SignedOrder[] = []): ForwarderHelper {
+ assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema);
+ assert.doesConformToSchema('feeOrders', orders, schemas.signedOrdersSchema);
+ const config: ForwarderHelperImplConfig = {
+ orders,
+ feeOrders,
+ };
+ const helper = new ForwarderHelperImpl(config);
+ return helper;
+ },
+};
diff --git a/packages/forwarder-helper/src/forwarder_helper_impl.ts b/packages/forwarder-helper/src/forwarder_helper_impl.ts
new file mode 100644
index 000000000..a90edb0bb
--- /dev/null
+++ b/packages/forwarder-helper/src/forwarder_helper_impl.ts
@@ -0,0 +1,64 @@
+import { marketUtils } from '@0xproject/order-utils';
+import { SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { constants } from './constants';
+import { ForwarderHelper, ForwarderHelperError, MarketBuyOrdersInfo, MarketBuyOrdersInfoRequest } from './types';
+import { forwarderHelperImplConfigUtils } from './utils/forwarder_helper_impl_config_utils';
+
+const SLIPPAGE_PERCENTAGE = new BigNumber(0.2); // 20% slippage protection, possibly move this into request interface
+
+export interface ForwarderHelperImplConfig {
+ orders: SignedOrder[];
+ feeOrders: SignedOrder[];
+ remainingFillableMakerAssetAmounts?: BigNumber[];
+ remainingFillableFeeAmounts?: BigNumber[];
+}
+
+export class ForwarderHelperImpl implements ForwarderHelper {
+ public readonly config: ForwarderHelperImplConfig;
+ constructor(config: ForwarderHelperImplConfig) {
+ this.config = forwarderHelperImplConfigUtils.sortedConfig(config);
+ }
+ public getMarketBuyOrdersInfo(request: MarketBuyOrdersInfoRequest): MarketBuyOrdersInfo {
+ const { makerAssetFillAmount, feePercentage } = request;
+ const { orders, feeOrders, remainingFillableMakerAssetAmounts, remainingFillableFeeAmounts } = this.config;
+ // TODO: make the slippage percentage customizable
+ const slippageBufferAmount = makerAssetFillAmount.mul(SLIPPAGE_PERCENTAGE).round();
+ const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
+ orders,
+ makerAssetFillAmount,
+ {
+ remainingFillableMakerAssetAmounts,
+ slippageBufferAmount,
+ },
+ );
+ if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) {
+ throw new Error(ForwarderHelperError.InsufficientMakerAssetLiquidity);
+ }
+ // TODO: update this logic to find the minimum amount of feeOrders to cover the worst case as opposed to
+ // finding order that cover all fees, this will help with estimating ETH and minimizing gas usage
+ const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
+ resultOrders,
+ feeOrders,
+ {
+ remainingFillableMakerAssetAmounts,
+ remainingFillableFeeAmounts,
+ },
+ );
+ if (remainingFeeAmount.gt(constants.ZERO_AMOUNT)) {
+ throw new Error(ForwarderHelperError.InsufficientZrxLiquidity);
+ }
+ // TODO: calculate min and max eth usage
+ // TODO: optimize orders call data
+ return {
+ makerAssetFillAmount,
+ orders: resultOrders,
+ feeOrders: resultFeeOrders,
+ minEthAmount: constants.ZERO_AMOUNT,
+ maxEthAmount: constants.ZERO_AMOUNT,
+ feePercentage,
+ };
+ }
+}
diff --git a/packages/forwarder-helper/src/globals.d.ts b/packages/forwarder-helper/src/globals.d.ts
new file mode 100644
index 000000000..94e63a32d
--- /dev/null
+++ b/packages/forwarder-helper/src/globals.d.ts
@@ -0,0 +1,6 @@
+declare module '*.json' {
+ const json: any;
+ /* tslint:disable */
+ export default json;
+ /* tslint:enable */
+}
diff --git a/packages/forwarder-helper/src/index.ts b/packages/forwarder-helper/src/index.ts
new file mode 100644
index 000000000..eb3a34bd5
--- /dev/null
+++ b/packages/forwarder-helper/src/index.ts
@@ -0,0 +1,2 @@
+export { forwarderHelperFactory } from './forwarder_helper_factory';
+export { ForwarderHelper, ForwarderHelperError, MarketBuyOrdersInfoRequest, MarketBuyOrdersInfo } from './types';
diff --git a/packages/forwarder-helper/src/types.ts b/packages/forwarder-helper/src/types.ts
new file mode 100644
index 000000000..fb171cc90
--- /dev/null
+++ b/packages/forwarder-helper/src/types.ts
@@ -0,0 +1,43 @@
+import { SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+
+export interface ForwarderHelper {
+ /**
+ * Given a MarketBuyOrdersInfoRequest, returns a MarketBuyOrdersInfo containing all information relevant to fulfilling the request
+ * using the ForwarderContract marketBuyOrdersWithEth function.
+ * @param request An object that conforms to MarketBuyOrdersInfoRequest. See type definition for more information.
+ * @return An object that conforms to MarketBuyOrdersInfo that satisfies the request. See type definition for more information.
+ */
+ getMarketBuyOrdersInfo: (request: MarketBuyOrdersInfoRequest) => MarketBuyOrdersInfo;
+}
+
+export enum ForwarderHelperError {
+ InsufficientMakerAssetLiquidity = 'INSUFFICIENT_MAKER_ASSET_LIQUIDITY',
+ InsufficientZrxLiquidity = 'INSUFFICIENT_ZRX_LIQUIDITY',
+}
+
+/**
+ * makerAssetFillAmount: The amount of makerAsset requesting to be filled
+ * feePercentage: Optional affiliate percentage amount factoring into eth amount calculations
+ */
+export interface MarketBuyOrdersInfoRequest {
+ makerAssetFillAmount: BigNumber;
+ feePercentage?: BigNumber;
+}
+
+/**
+ * makerAssetFillAmount: The amount of makerAsset requesting to be filled
+ * orders: An array of objects conforming to SignedOrder. These orders can be used to cover the requested makerAssetFillAmount plus slippage
+ * feeOrders: An array of objects conforming to SignedOrder. These orders can be used to cover the fees for the orders param above
+ * minEthAmount: Amount of eth in wei to send with the tx for the most optimistic case
+ * maxEthAmount: Amount of eth in wei to send with the tx for the worst case
+ * feePercentage: Affiliate fee percentage used to calculate the eth amounts above. Passed thru directly from the request
+ */
+export interface MarketBuyOrdersInfo {
+ makerAssetFillAmount: BigNumber;
+ orders: SignedOrder[];
+ feeOrders: SignedOrder[];
+ minEthAmount: BigNumber;
+ maxEthAmount: BigNumber;
+ feePercentage?: BigNumber;
+}
diff --git a/packages/forwarder-helper/src/utils/forwarder_helper_impl_config_utils.ts b/packages/forwarder-helper/src/utils/forwarder_helper_impl_config_utils.ts
new file mode 100644
index 000000000..253384f65
--- /dev/null
+++ b/packages/forwarder-helper/src/utils/forwarder_helper_impl_config_utils.ts
@@ -0,0 +1,92 @@
+import { sortingUtils } from '@0xproject/order-utils';
+import { SignedOrder } from '@0xproject/types';
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import { ForwarderHelperImplConfig } from '../forwarder_helper_impl';
+
+interface SignedOrderWithAmount extends SignedOrder {
+ remainingFillAmount: BigNumber;
+}
+
+export const forwarderHelperImplConfigUtils = {
+ sortedConfig(config: ForwarderHelperImplConfig): ForwarderHelperImplConfig {
+ const { orders, feeOrders, remainingFillableMakerAssetAmounts, remainingFillableFeeAmounts } = config;
+ // TODO: provide a feeRate to the sorting function to more accurately sort based on the current market for ZRX tokens
+ const orderSorter = (ordersToSort: SignedOrder[]) => {
+ return sortingUtils.sortOrdersByFeeAdjustedRate(ordersToSort);
+ };
+ const sortOrdersResult = sortOrdersAndRemainingFillAmounts(
+ orderSorter,
+ orders,
+ remainingFillableMakerAssetAmounts,
+ );
+ const feeOrderSorter = (ordersToSort: SignedOrder[]) => {
+ return sortingUtils.sortFeeOrdersByFeeAdjustedRate(ordersToSort);
+ };
+ const sortFeeOrdersResult = sortOrdersAndRemainingFillAmounts(
+ feeOrderSorter,
+ feeOrders,
+ remainingFillableFeeAmounts,
+ );
+ return {
+ orders: sortOrdersResult.orders,
+ feeOrders: sortFeeOrdersResult.orders,
+ remainingFillableMakerAssetAmounts: sortOrdersResult.remainingFillAmounts,
+ remainingFillableFeeAmounts: sortFeeOrdersResult.remainingFillAmounts,
+ };
+ },
+};
+
+type OrderSorter = (orders: SignedOrder[]) => SignedOrder[];
+
+function sortOrdersAndRemainingFillAmounts(
+ orderSorter: OrderSorter,
+ orders: SignedOrder[],
+ remainingFillAmounts?: BigNumber[],
+): { orders: SignedOrder[]; remainingFillAmounts?: BigNumber[] } {
+ if (!_.isUndefined(remainingFillAmounts)) {
+ // Bundle orders together with their remainingFillAmounts so that we can sort them together
+ const orderWithAmounts = bundleSignedOrderWithAmounts(orders, remainingFillAmounts);
+ // Sort
+ const sortedOrderWithAmounts = orderSorter(orderWithAmounts) as SignedOrderWithAmount[];
+ // Unbundle after sorting
+ const unbundledSortedOrderWithAmounts = unbundleSignedOrderWithAmounts(sortedOrderWithAmounts);
+ return {
+ orders: unbundledSortedOrderWithAmounts.orders,
+ remainingFillAmounts: unbundledSortedOrderWithAmounts.amounts,
+ };
+ } else {
+ const sortedOrders = orderSorter(orders);
+ return {
+ orders: sortedOrders,
+ };
+ }
+}
+
+function bundleSignedOrderWithAmounts(orders: SignedOrder[], amounts: BigNumber[]): SignedOrderWithAmount[] {
+ const ordersAndAmounts = _.map(orders, (order, index) => {
+ return {
+ ...order,
+ remainingFillAmount: amounts[index],
+ };
+ });
+ return ordersAndAmounts;
+}
+
+function unbundleSignedOrderWithAmounts(
+ signedOrderWithAmounts: SignedOrderWithAmount[],
+): { orders: SignedOrder[]; amounts: BigNumber[] } {
+ const orders = _.map(signedOrderWithAmounts, order => {
+ const { remainingFillAmount, ...rest } = order;
+ return rest;
+ });
+ const amounts = _.map(signedOrderWithAmounts, order => {
+ const { remainingFillAmount } = order;
+ return remainingFillAmount;
+ });
+ return {
+ orders,
+ amounts,
+ };
+}
diff --git a/packages/forwarder-helper/test/forwarder_helper_impl_test.ts b/packages/forwarder-helper/test/forwarder_helper_impl_test.ts
new file mode 100644
index 000000000..3c3b6db92
--- /dev/null
+++ b/packages/forwarder-helper/test/forwarder_helper_impl_test.ts
@@ -0,0 +1,136 @@
+import { testOrderFactory } from '@0xproject/order-utils/lib/test/utils/test_order_factory';
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import * as _ from 'lodash';
+import 'mocha';
+
+import { ForwarderHelperImpl, ForwarderHelperImplConfig } from '../src/forwarder_helper_impl';
+import { ForwarderHelperError } from '../src/types';
+
+import { chaiSetup } from './utils/chai_setup';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+describe('ForwarderHelperImpl', () => {
+ // rate: 2 takerAsset / makerAsset
+ const testOrder1 = testOrderFactory.generateTestSignedOrder({
+ makerAssetAmount: new BigNumber(100),
+ takerAssetAmount: new BigNumber(200),
+ });
+ // rate: 1 takerAsset / makerAsset
+ const testOrder2 = testOrderFactory.generateTestSignedOrder({
+ makerAssetAmount: new BigNumber(100),
+ takerAssetAmount: new BigNumber(100),
+ });
+ // rate: 3 takerAsset / makerAsset
+ const testOrder3 = testOrderFactory.generateTestSignedOrder({
+ makerAssetAmount: new BigNumber(100),
+ takerAssetAmount: new BigNumber(300),
+ takerFee: new BigNumber(1),
+ });
+ // rate: 3 WETH / ZRX
+ const testFeeOrder1 = testOrderFactory.generateTestSignedOrder({
+ makerAssetAmount: new BigNumber(100),
+ takerAssetAmount: new BigNumber(300),
+ });
+ // rate: 2 WETH / ZRX
+ const testFeeOrder2 = testOrderFactory.generateTestSignedOrder({
+ makerAssetAmount: new BigNumber(100),
+ takerAssetAmount: new BigNumber(200),
+ });
+ // rate: 1 WETH / ZRX
+ const testFeeOrder3 = testOrderFactory.generateTestSignedOrder({
+ makerAssetAmount: new BigNumber(100),
+ takerAssetAmount: new BigNumber(100),
+ });
+ const inputForwarderHelperConfig: ForwarderHelperImplConfig = {
+ orders: [testOrder1, testOrder2, testOrder3],
+ feeOrders: [testFeeOrder1, testFeeOrder2, testFeeOrder3],
+ remainingFillableMakerAssetAmounts: [new BigNumber(1), new BigNumber(2), new BigNumber(3)],
+ remainingFillableFeeAmounts: [new BigNumber(4), new BigNumber(5), new BigNumber(6)],
+ };
+ describe('#constructor', () => {
+ const inputForwarderHelperConfigNoRemainingAmounts: ForwarderHelperImplConfig = {
+ orders: [testOrder1, testOrder2, testOrder3],
+ feeOrders: [testFeeOrder1, testFeeOrder2, testFeeOrder3],
+ };
+ it('sorts orders', () => {
+ const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfig);
+ expect(forwarderHelper.config.orders).deep.equals([testOrder2, testOrder1, testOrder3]);
+ });
+ it('sorts fee orders', () => {
+ const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfig);
+ expect(forwarderHelper.config.feeOrders).deep.equals([testFeeOrder3, testFeeOrder2, testFeeOrder1]);
+ });
+ it('sorts remainingFillableMakerAssetAmounts', () => {
+ const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfig);
+ expect(forwarderHelper.config.remainingFillableMakerAssetAmounts).to.be.not.undefined();
+ expect(_.nth(forwarderHelper.config.remainingFillableMakerAssetAmounts, 0)).to.bignumber.equal(
+ new BigNumber(2),
+ );
+ expect(_.nth(forwarderHelper.config.remainingFillableMakerAssetAmounts, 1)).to.bignumber.equal(
+ new BigNumber(1),
+ );
+ expect(_.nth(forwarderHelper.config.remainingFillableMakerAssetAmounts, 2)).to.bignumber.equal(
+ new BigNumber(3),
+ );
+ });
+ it('sorts remainingFillableFeeAmounts', () => {
+ const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfig);
+ expect(forwarderHelper.config.remainingFillableFeeAmounts).to.be.not.undefined();
+ expect(_.nth(forwarderHelper.config.remainingFillableFeeAmounts, 0)).to.bignumber.equal(new BigNumber(6));
+ expect(_.nth(forwarderHelper.config.remainingFillableFeeAmounts, 1)).to.bignumber.equal(new BigNumber(5));
+ expect(_.nth(forwarderHelper.config.remainingFillableFeeAmounts, 2)).to.bignumber.equal(new BigNumber(4));
+ });
+ it('remainingFillableMakerAssetAmounts is undefined if none provided', () => {
+ const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfigNoRemainingAmounts);
+ expect(forwarderHelper.config.remainingFillableMakerAssetAmounts).to.be.undefined();
+ });
+ it('remainingFillableFeeAmounts is undefined if none provided', () => {
+ const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfigNoRemainingAmounts);
+ expect(forwarderHelper.config.remainingFillableFeeAmounts).to.be.undefined();
+ });
+ });
+ describe('#getMarketBuyOrdersInfo', () => {
+ it('throws if not enough makerAsset liquidity', () => {
+ const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfig);
+ expect(() => {
+ // request for 6 makerAsset units, because we have exactly 6 available we should throw because there is a built in slippage buffer
+ forwarderHelper.getMarketBuyOrdersInfo({
+ makerAssetFillAmount: new BigNumber(6),
+ });
+ }).to.throw(ForwarderHelperError.InsufficientMakerAssetLiquidity);
+ });
+ it('throws if not enough ZRX liquidity', () => {
+ const inputForwarderHelperConfigNoFees: ForwarderHelperImplConfig = {
+ orders: [testOrder1, testOrder2, testOrder3],
+ feeOrders: [],
+ };
+ const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfigNoFees);
+ expect(() => {
+ // request for 4 makerAsset units, we need fees but no fee orders exist, show we should throw
+ forwarderHelper.getMarketBuyOrdersInfo({
+ makerAssetFillAmount: new BigNumber(250),
+ });
+ }).to.throw(ForwarderHelperError.InsufficientZrxLiquidity);
+ });
+ it('passes the makerAssetFillAmount from the request to the info response', () => {
+ const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfig);
+ const makerAssetFillAmount = new BigNumber(4);
+ const info = forwarderHelper.getMarketBuyOrdersInfo({
+ makerAssetFillAmount,
+ });
+ expect(info.makerAssetFillAmount).to.bignumber.equal(makerAssetFillAmount);
+ });
+ it('passes the feePercentage from the request to the info response', () => {
+ const forwarderHelper = new ForwarderHelperImpl(inputForwarderHelperConfig);
+ const feePercentage = new BigNumber(0.2);
+ const info = forwarderHelper.getMarketBuyOrdersInfo({
+ makerAssetFillAmount: new BigNumber(4),
+ feePercentage,
+ });
+ expect(info.feePercentage).to.bignumber.equal(feePercentage);
+ });
+ });
+});
diff --git a/packages/forwarder-helper/test/utils/chai_setup.ts b/packages/forwarder-helper/test/utils/chai_setup.ts
new file mode 100644
index 000000000..1a8733093
--- /dev/null
+++ b/packages/forwarder-helper/test/utils/chai_setup.ts
@@ -0,0 +1,13 @@
+import * as chai from 'chai';
+import chaiAsPromised = require('chai-as-promised');
+import ChaiBigNumber = require('chai-bignumber');
+import * as dirtyChai from 'dirty-chai';
+
+export const chaiSetup = {
+ configure(): void {
+ chai.config.includeStack = true;
+ chai.use(ChaiBigNumber());
+ chai.use(dirtyChai);
+ chai.use(chaiAsPromised);
+ },
+};
diff --git a/packages/forwarder-helper/tsconfig.json b/packages/forwarder-helper/tsconfig.json
new file mode 100644
index 000000000..e35816553
--- /dev/null
+++ b/packages/forwarder-helper/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig",
+ "compilerOptions": {
+ "outDir": "lib"
+ },
+ "include": ["./src/**/*", "./test/**/*"]
+}
diff --git a/packages/forwarder-helper/tslint.json b/packages/forwarder-helper/tslint.json
new file mode 100644
index 000000000..ffaefe83a
--- /dev/null
+++ b/packages/forwarder-helper/tslint.json
@@ -0,0 +1,3 @@
+{
+ "extends": ["@0xproject/tslint-config"]
+}
diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json
index cd2a742d2..871bc50a1 100644
--- a/packages/order-utils/CHANGELOG.json
+++ b/packages/order-utils/CHANGELOG.json
@@ -24,6 +24,20 @@
"note":
"Export types: `SignedOrder`, `Order`, `OrderRelevantState`, `OrderState`, `ECSignature`, `ERC20AssetData`, `ERC721AssetData`, `AssetProxyId`, `SignerType`, `SignatureType`, `OrderStateValid`, `OrderStateInvalid`, `ExchangeContractErrs`, `TradeSide`, `TransferType`, `FindFeeOrdersThatCoverFeesForTargetOrdersOpts`, `FindOrdersThatCoverMakerAssetFillAmountOpts`, `FeeOrdersAndRemainingFeeAmount`, `OrdersAndRemainingFillAmount`, `Provider`, `JSONRPCRequestPayload`, `JSONRPCErrorCallback` and `JSONRPCResponsePayload`",
"pr": 924
+ },
+ {
+ "note":
+ "Rename `resultOrders` to `resultFeeOrders` for object returned by `findFeeOrdersThatCoverFeesForTargetOrders` in `marketUtils` api",
+ "pr": 997
+ },
+ {
+ "note": "Make `sortFeeOrdersByFeeAdjustedRate` in `sortingUtils` generic",
+ "pr": 997
+ },
+ {
+ "note":
+ "Update `findFeeOrdersThatCoverFeesForTargetOrders` to round the the nearest integer when calculating required fees",
+ "pr": 997
}
]
},
diff --git a/packages/order-utils/src/market_utils.ts b/packages/order-utils/src/market_utils.ts
index 441c50e5c..4a664cb14 100644
--- a/packages/order-utils/src/market_utils.ts
+++ b/packages/order-utils/src/market_utils.ts
@@ -128,7 +128,7 @@ export const marketUtils = {
const makerAssetAmountAvailable = remainingFillableMakerAssetAmounts[index];
const feeToFillMakerAssetAmountAvailable = makerAssetAmountAvailable
.mul(order.takerFee)
- .div(order.makerAssetAmount);
+ .dividedToIntegerBy(order.makerAssetAmount);
return accFees.plus(feeToFillMakerAssetAmountAvailable);
},
constants.ZERO_AMOUNT,
@@ -142,7 +142,7 @@ export const marketUtils = {
},
);
return {
- resultOrders,
+ resultFeeOrders: resultOrders,
remainingFeeAmount: remainingFillAmount,
};
// TODO: add more orders here to cover rounding
diff --git a/packages/order-utils/src/sorting_utils.ts b/packages/order-utils/src/sorting_utils.ts
index 8811bcaf8..cd5163cf6 100644
--- a/packages/order-utils/src/sorting_utils.ts
+++ b/packages/order-utils/src/sorting_utils.ts
@@ -32,7 +32,7 @@ export const sortingUtils = {
* the makerAsset and WETH as the takerAsset.
* @return The input orders sorted by rate in ascending order
*/
- sortFeeOrdersByFeeAdjustedRate(feeOrders: Order[]): Order[] {
+ sortFeeOrdersByFeeAdjustedRate<T extends Order>(feeOrders: T[]): T[] {
assert.doesConformToSchema('feeOrders', feeOrders, schemas.ordersSchema);
const rateCalculator = rateUtils.getFeeAdjustedRateOfFeeOrder.bind(rateUtils);
const sortedOrders = sortOrders(feeOrders, rateCalculator);
diff --git a/packages/order-utils/src/types.ts b/packages/order-utils/src/types.ts
index 4088805dc..09292e557 100644
--- a/packages/order-utils/src/types.ts
+++ b/packages/order-utils/src/types.ts
@@ -71,7 +71,7 @@ export interface FindFeeOrdersThatCoverFeesForTargetOrdersOpts {
}
export interface FeeOrdersAndRemainingFeeAmount<T> {
- resultOrders: T[];
+ resultFeeOrders: T[];
remainingFeeAmount: BigNumber;
}
diff --git a/packages/order-utils/test/market_utils_test.ts b/packages/order-utils/test/market_utils_test.ts
index cce97e0e4..31986ba1a 100644
--- a/packages/order-utils/test/market_utils_test.ts
+++ b/packages/order-utils/test/market_utils_test.ts
@@ -140,11 +140,11 @@ describe('marketUtils', () => {
);
describe('no target orders', () => {
it('returns empty and zero remainingFeeAmount', async () => {
- const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
+ const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
[],
inputFeeOrders,
);
- expect(resultOrders).to.be.empty;
+ expect(resultFeeOrders).to.be.empty;
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
});
@@ -163,14 +163,14 @@ describe('marketUtils', () => {
// generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount
const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount];
it('returns empty and non-zero remainingFeeAmount', async () => {
- const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
+ const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
inputOrders,
[],
{
remainingFillableMakerAssetAmounts,
},
);
- expect(resultOrders).to.be.empty;
+ expect(resultFeeOrders).to.be.empty;
expect(remainingFeeAmount).to.be.bignumber.equal(new BigNumber(30));
});
});
@@ -184,11 +184,11 @@ describe('marketUtils', () => {
3,
);
it('returns empty and zero remainingFeeAmount', async () => {
- const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
+ const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
inputOrders,
inputFeeOrders,
);
- expect(resultOrders).to.be.empty;
+ expect(resultFeeOrders).to.be.empty;
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
});
@@ -205,11 +205,11 @@ describe('marketUtils', () => {
3,
);
it('returns input fee orders and zero remainingFeeAmount', async () => {
- const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
+ const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
inputOrders,
inputFeeOrders,
);
- expect(resultOrders).to.be.deep.equal(inputFeeOrders);
+ expect(resultFeeOrders).to.be.deep.equal(inputFeeOrders);
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
});
@@ -231,14 +231,14 @@ describe('marketUtils', () => {
// 3. order is completely fillable
const remainingFillableMakerAssetAmounts = [constants.ZERO_AMOUNT, new BigNumber(5), makerAssetAmount];
it('returns first two input fee orders and zero remainingFeeAmount', async () => {
- const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
+ const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
inputOrders,
inputFeeOrders,
{
remainingFillableMakerAssetAmounts,
},
);
- expect(resultOrders).to.be.deep.equal([inputFeeOrders[0], inputFeeOrders[1]]);
+ expect(resultFeeOrders).to.be.deep.equal([inputFeeOrders[0], inputFeeOrders[1]]);
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
});
@@ -255,11 +255,11 @@ describe('marketUtils', () => {
3,
);
it('returns input fee orders and non-zero remainingFeeAmount', async () => {
- const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
+ const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
inputOrders,
inputFeeOrders,
);
- expect(resultOrders).to.be.deep.equal(inputFeeOrders);
+ expect(resultFeeOrders).to.be.deep.equal(inputFeeOrders);
expect(remainingFeeAmount).to.be.bignumber.equal(new BigNumber(30));
});
});