From 91702bbae214f56e7ff66156fa799fcfb83f6e42 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Fri, 14 Sep 2018 13:45:14 +0200 Subject: Move packages/forwarder-helper into packages/asset-buyer --- packages/asset-buyer/.npmignore | 8 + packages/asset-buyer/CHANGELOG.json | 30 +++ packages/asset-buyer/CHANGELOG.md | 18 ++ packages/asset-buyer/README.md | 83 +++++++ packages/asset-buyer/package.json | 75 ++++++ packages/asset-buyer/src/constants.ts | 6 + .../asset-buyer/src/forwarder_helper_factory.ts | 261 +++++++++++++++++++++ packages/asset-buyer/src/forwarder_helper_impl.ts | 64 +++++ packages/asset-buyer/src/globals.d.ts | 6 + packages/asset-buyer/src/index.ts | 2 + packages/asset-buyer/src/types.ts | 49 ++++ .../utils/forwarder_helper_impl_config_utils.ts | 92 ++++++++ packages/asset-buyer/src/utils/order_utils.ts | 21 ++ .../asset-buyer/test/forwarder_helper_impl_test.ts | 136 +++++++++++ packages/asset-buyer/test/utils/chai_setup.ts | 13 + packages/asset-buyer/tsconfig.json | 8 + packages/asset-buyer/tslint.json | 3 + packages/asset-buyer/typedoc-tsconfig.json | 7 + packages/forwarder-helper/.npmignore | 8 - packages/forwarder-helper/CHANGELOG.json | 30 --- packages/forwarder-helper/CHANGELOG.md | 18 -- packages/forwarder-helper/README.md | 83 ------- packages/forwarder-helper/package.json | 75 ------ packages/forwarder-helper/src/constants.ts | 6 - .../src/forwarder_helper_factory.ts | 261 --------------------- .../forwarder-helper/src/forwarder_helper_impl.ts | 64 ----- packages/forwarder-helper/src/globals.d.ts | 6 - packages/forwarder-helper/src/index.ts | 2 - packages/forwarder-helper/src/types.ts | 49 ---- .../utils/forwarder_helper_impl_config_utils.ts | 92 -------- packages/forwarder-helper/src/utils/order_utils.ts | 21 -- .../test/forwarder_helper_impl_test.ts | 136 ----------- packages/forwarder-helper/test/utils/chai_setup.ts | 13 - packages/forwarder-helper/tsconfig.json | 8 - packages/forwarder-helper/tslint.json | 3 - packages/forwarder-helper/typedoc-tsconfig.json | 7 - 36 files changed, 882 insertions(+), 882 deletions(-) create mode 100644 packages/asset-buyer/.npmignore create mode 100644 packages/asset-buyer/CHANGELOG.json create mode 100644 packages/asset-buyer/CHANGELOG.md create mode 100644 packages/asset-buyer/README.md create mode 100644 packages/asset-buyer/package.json create mode 100644 packages/asset-buyer/src/constants.ts create mode 100644 packages/asset-buyer/src/forwarder_helper_factory.ts create mode 100644 packages/asset-buyer/src/forwarder_helper_impl.ts create mode 100644 packages/asset-buyer/src/globals.d.ts create mode 100644 packages/asset-buyer/src/index.ts create mode 100644 packages/asset-buyer/src/types.ts create mode 100644 packages/asset-buyer/src/utils/forwarder_helper_impl_config_utils.ts create mode 100644 packages/asset-buyer/src/utils/order_utils.ts create mode 100644 packages/asset-buyer/test/forwarder_helper_impl_test.ts create mode 100644 packages/asset-buyer/test/utils/chai_setup.ts create mode 100644 packages/asset-buyer/tsconfig.json create mode 100644 packages/asset-buyer/tslint.json create mode 100644 packages/asset-buyer/typedoc-tsconfig.json delete mode 100644 packages/forwarder-helper/.npmignore delete mode 100644 packages/forwarder-helper/CHANGELOG.json delete mode 100644 packages/forwarder-helper/CHANGELOG.md delete mode 100644 packages/forwarder-helper/README.md delete mode 100644 packages/forwarder-helper/package.json delete mode 100644 packages/forwarder-helper/src/constants.ts delete mode 100644 packages/forwarder-helper/src/forwarder_helper_factory.ts delete mode 100644 packages/forwarder-helper/src/forwarder_helper_impl.ts delete mode 100644 packages/forwarder-helper/src/globals.d.ts delete mode 100644 packages/forwarder-helper/src/index.ts delete mode 100644 packages/forwarder-helper/src/types.ts delete mode 100644 packages/forwarder-helper/src/utils/forwarder_helper_impl_config_utils.ts delete mode 100644 packages/forwarder-helper/src/utils/order_utils.ts delete mode 100644 packages/forwarder-helper/test/forwarder_helper_impl_test.ts delete mode 100644 packages/forwarder-helper/test/utils/chai_setup.ts delete mode 100644 packages/forwarder-helper/tsconfig.json delete mode 100644 packages/forwarder-helper/tslint.json delete mode 100644 packages/forwarder-helper/typedoc-tsconfig.json (limited to 'packages') diff --git a/packages/asset-buyer/.npmignore b/packages/asset-buyer/.npmignore new file mode 100644 index 000000000..5333847e7 --- /dev/null +++ b/packages/asset-buyer/.npmignore @@ -0,0 +1,8 @@ +.* +yarn-error.log +/src/ +/scripts/ +/schemas/ +test/ +tsconfig.json +/lib/src/monorepo_scripts/ diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json new file mode 100644 index 000000000..f0e646700 --- /dev/null +++ b/packages/asset-buyer/CHANGELOG.json @@ -0,0 +1,30 @@ +[ + { + "timestamp": 1536142250, + "version": "1.0.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "1.0.1-rc.2", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1535377027 + }, + { + "version": "1.0.1-rc.1", + "changes": [ + { + "note": "Add initial forwarderHelperFactory", + "pr": 997 + } + ], + "timestamp": 1535133899 + } +] diff --git a/packages/asset-buyer/CHANGELOG.md b/packages/asset-buyer/CHANGELOG.md new file mode 100644 index 000000000..d6b67aa1b --- /dev/null +++ b/packages/asset-buyer/CHANGELOG.md @@ -0,0 +1,18 @@ + + +CHANGELOG + +## v1.0.1 - _September 5, 2018_ + + * Dependencies updated + +## v1.0.1-rc.2 - _August 27, 2018_ + + * Dependencies updated + +## v1.0.1-rc.1 - _August 24, 2018_ + + * Add initial forwarderHelperFactory (#997) diff --git a/packages/asset-buyer/README.md b/packages/asset-buyer/README.md new file mode 100644 index 000000000..c74526910 --- /dev/null +++ b/packages/asset-buyer/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/asset-buyer/package.json b/packages/asset-buyer/package.json new file mode 100644 index 000000000..f849fb4ed --- /dev/null +++ b/packages/asset-buyer/package.json @@ -0,0 +1,75 @@ +{ + "name": "@0xproject/forwarder-helper", + "version": "1.0.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 --tsconfig typedoc-tsconfig.json --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.8", + "@0xproject/connect": "^2.0.0", + "@0xproject/contract-wrappers": "^1.0.1", + "@0xproject/json-schemas": "^1.0.1", + "@0xproject/order-utils": "^1.0.1", + "@0xproject/subproviders": "^2.0.2", + "@0xproject/types": "^1.0.1", + "@0xproject/typescript-typings": "^2.0.0", + "@0xproject/utils": "^1.0.8", + "lodash": "^4.17.10" + }, + "devDependencies": { + "@0xproject/tslint-config": "^1.0.7", + "@types/lodash": "^4.14.116", + "@types/mocha": "^2.2.42", + "@types/node": "^8.0.53", + "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": "0.12.0", + "typescript": "3.0.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/asset-buyer/src/constants.ts b/packages/asset-buyer/src/constants.ts new file mode 100644 index 000000000..c0a1b090e --- /dev/null +++ b/packages/asset-buyer/src/constants.ts @@ -0,0 +1,6 @@ +import { BigNumber } from '@0xproject/utils'; + +export const constants = { + ZERO_AMOUNT: new BigNumber(0), + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', +}; diff --git a/packages/asset-buyer/src/forwarder_helper_factory.ts b/packages/asset-buyer/src/forwarder_helper_factory.ts new file mode 100644 index 000000000..2b37ac98f --- /dev/null +++ b/packages/asset-buyer/src/forwarder_helper_factory.ts @@ -0,0 +1,261 @@ +import { assert } from '@0xproject/assert'; +import { APIOrder, HttpClient, OrderbookResponse } from '@0xproject/connect'; +import { ContractWrappers, OrderAndTraderInfo, OrderStatus } from '@0xproject/contract-wrappers'; +import { schemas } from '@0xproject/json-schemas'; +import { assetDataUtils } from '@0xproject/order-utils'; +import { RemainingFillableCalculator } from '@0xproject/order-utils/lib/src/remaining_fillable_calculator'; +import { RPCSubprovider, Web3ProviderEngine } from '@0xproject/subproviders'; +import { SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import * as _ from 'lodash'; + +import { constants } from './constants'; +import { ForwarderHelperImpl, ForwarderHelperImplConfig } from './forwarder_helper_impl'; +import { ForwarderHelper, ForwarderHelperFactoryError } from './types'; +import { orderUtils } from './utils/order_utils'; + +export const forwarderHelperFactory = { + /** + * Given an array of orders and an array of feeOrders, get a ForwarderHelper + * @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); + // TODO: Add assertion here for orders all having the same makerAsset and takerAsset + const config: ForwarderHelperImplConfig = { + orders, + feeOrders, + }; + const helper = new ForwarderHelperImpl(config); + return helper; + }, + /** + * Given a desired makerAsset and SRA url, get a ForwarderHelper + * @param makerAssetData An array of objects conforming to SignedOrder. Each order should specify the same makerAssetData and takerAssetData + * @param sraUrl A url pointing to an SRA v2 compliant endpoint. + * @param rpcUrl A url pointing to an ethereum node. + * @param networkId The ethereum networkId, defaults to 1 (mainnet). + * @return A ForwarderHelper, see type for definition + */ + async getForwarderHelperForMakerAssetDataAsync( + makerAssetData: string, + sraUrl: string, + rpcUrl?: string, + networkId: number = 1, + ): Promise { + assert.isHexString('makerAssetData', makerAssetData); + assert.isWebUri('sraUrl', sraUrl); + if (!_.isUndefined(rpcUrl)) { + assert.isWebUri('rpcUrl', rpcUrl); + } + assert.isNumber('networkId', networkId); + // create provider + const providerEngine = new Web3ProviderEngine(); + if (!_.isUndefined(rpcUrl)) { + providerEngine.addProvider(new RPCSubprovider(rpcUrl)); + } + providerEngine.start(); + // create contract wrappers given provider and networkId + const contractWrappers = new ContractWrappers(providerEngine, { networkId }); + // find ether token asset data + const etherTokenAddressIfExists = contractWrappers.etherToken.getContractAddressIfExists(); + if (_.isUndefined(etherTokenAddressIfExists)) { + throw new Error(ForwarderHelperFactoryError.NoEtherTokenContractFound); + } + const etherTokenAssetData = assetDataUtils.encodeERC20AssetData(etherTokenAddressIfExists); + // find zrx token asset data + let zrxTokenAssetData: string; + try { + zrxTokenAssetData = contractWrappers.exchange.getZRXAssetData(); + } catch (err) { + throw new Error(ForwarderHelperFactoryError.NoZrxTokenContractFound); + } + // get orderbooks for makerAsset/WETH and ZRX/WETH + const sraClient = new HttpClient(sraUrl); + const orderbookRequests = [ + { baseAssetData: makerAssetData, quoteAssetData: etherTokenAssetData }, + { baseAssetData: zrxTokenAssetData, quoteAssetData: etherTokenAssetData }, + ]; + const requestOpts = { networkId }; + let makerAssetOrderbook: OrderbookResponse; + let zrxOrderbook: OrderbookResponse; + try { + [makerAssetOrderbook, zrxOrderbook] = await Promise.all( + _.map(orderbookRequests, request => sraClient.getOrderbookAsync(request, requestOpts)), + ); + } catch (err) { + throw new Error(ForwarderHelperFactoryError.StandardRelayerApiError); + } + // validate orders and find remaining fillable from on chain state or sra api + let ordersAndRemainingFillableMakerAssetAmounts: OrdersAndRemainingFillableMakerAssetAmounts; + let feeOrdersAndRemainingFillableMakerAssetAmounts: OrdersAndRemainingFillableMakerAssetAmounts; + if (!_.isUndefined(rpcUrl)) { + // if we do have an rpc url, get on-chain orders and traders info via the OrderValidatorWrapper + const ordersFromSra = getOpenAsksFromOrderbook(makerAssetOrderbook); + const feeOrdersFromSra = getOpenAsksFromOrderbook(zrxOrderbook); + // TODO: try catch these requests and throw a more domain specific error + // TODO: optimization, reduce this to once RPC call buy combining orders into one array and then splitting up the response + const [makerAssetOrdersAndTradersInfo, feeOrdersAndTradersInfo] = await Promise.all( + _.map([ordersFromSra, feeOrdersFromSra], ordersToBeValidated => { + const takerAddresses = _.map(ordersToBeValidated, () => constants.NULL_ADDRESS); + return contractWrappers.orderValidator.getOrdersAndTradersInfoAsync( + ordersToBeValidated, + takerAddresses, + ); + }), + ); + // take maker asset orders from SRA + on chain information and find the valid orders and remaining fillable maker asset amounts + ordersAndRemainingFillableMakerAssetAmounts = getValidOrdersAndRemainingFillableMakerAssetAmountsFromOnChain( + ordersFromSra, + makerAssetOrdersAndTradersInfo, + zrxTokenAssetData, + ); + // take fee orders from SRA + on chain information and find the valid orders and remaining fillable maker asset amounts + feeOrdersAndRemainingFillableMakerAssetAmounts = getValidOrdersAndRemainingFillableMakerAssetAmountsFromOnChain( + feeOrdersFromSra, + feeOrdersAndTradersInfo, + zrxTokenAssetData, + ); + } else { + // if we don't have an rpc url, assume all orders are valid and fallback to optional fill amounts from SRA + // if fill amounts are not available from the SRA, assume all orders are completely fillable + const apiOrdersFromSra = makerAssetOrderbook.asks.records; + const feeApiOrdersFromSra = zrxOrderbook.asks.records; + // take maker asset orders from SRA and the valid orders and remaining fillable maker asset amounts + ordersAndRemainingFillableMakerAssetAmounts = getValidOrdersAndRemainingFillableMakerAssetAmountsFromApi( + apiOrdersFromSra, + ); + // take fee orders from SRA and find the valid orders and remaining fillable maker asset amounts + feeOrdersAndRemainingFillableMakerAssetAmounts = getValidOrdersAndRemainingFillableMakerAssetAmountsFromApi( + feeApiOrdersFromSra, + ); + } + // compile final config + const config: ForwarderHelperImplConfig = { + orders: ordersAndRemainingFillableMakerAssetAmounts.orders, + feeOrders: feeOrdersAndRemainingFillableMakerAssetAmounts.orders, + remainingFillableMakerAssetAmounts: + ordersAndRemainingFillableMakerAssetAmounts.remainingFillableMakerAssetAmounts, + remainingFillableFeeAmounts: + feeOrdersAndRemainingFillableMakerAssetAmounts.remainingFillableMakerAssetAmounts, + }; + const helper = new ForwarderHelperImpl(config); + return helper; + }, +}; + +interface OrdersAndRemainingFillableMakerAssetAmounts { + orders: SignedOrder[]; + remainingFillableMakerAssetAmounts: BigNumber[]; +} + +/** + * Given an array of APIOrder objects from a standard relayer api, return an array + * of fillable orders with their corresponding remainingFillableMakerAssetAmounts + */ +function getValidOrdersAndRemainingFillableMakerAssetAmountsFromApi( + apiOrders: APIOrder[], +): OrdersAndRemainingFillableMakerAssetAmounts { + const result = _.reduce( + apiOrders, + (acc, apiOrder) => { + // get current accumulations + const { orders, remainingFillableMakerAssetAmounts } = acc; + // get order and metadata + const { order, metaData } = apiOrder; + // if the order is expired or not open, move on + if (orderUtils.isOrderExpired(order) || !orderUtils.isOpenOrder(order)) { + return acc; + } + // calculate remainingFillableMakerAssetAmount from api metadata, else assume order is completely fillable + const remainingFillableTakerAssetAmount = _.get( + metaData, + 'remainingTakerAssetAmount', + order.takerAssetAmount, + ); + const remainingFillableMakerAssetAmount = orderUtils.calculateRemainingMakerAssetAmount( + order, + remainingFillableTakerAssetAmount, + ); + // if there is some amount of maker asset left to fill and add the order and remaining amount to the accumulations + // if there is not any maker asset left to fill, do not add + if (remainingFillableMakerAssetAmount.gt(constants.ZERO_AMOUNT)) { + return { + orders: _.concat(orders, order), + remainingFillableMakerAssetAmounts: _.concat( + remainingFillableMakerAssetAmounts, + remainingFillableMakerAssetAmount, + ), + }; + } else { + return acc; + } + }, + { orders: [] as SignedOrder[], remainingFillableMakerAssetAmounts: [] as BigNumber[] }, + ); + return result; +} + +/** + * Given an array of orders and corresponding on-chain infos, return a subset of the orders + * that are still fillable orders with their corresponding remainingFillableMakerAssetAmounts + */ +function getValidOrdersAndRemainingFillableMakerAssetAmountsFromOnChain( + inputOrders: SignedOrder[], + ordersAndTradersInfo: OrderAndTraderInfo[], + zrxAssetData: string, +): OrdersAndRemainingFillableMakerAssetAmounts { + // iterate through the input orders and find the ones that are still fillable + // for the orders that are still fillable, calculate the remaining fillable maker asset amount + const result = _.reduce( + inputOrders, + (acc, order, index) => { + // get current accumulations + const { orders, remainingFillableMakerAssetAmounts } = acc; + // get corresponding on-chain state for the order + const { orderInfo, traderInfo } = ordersAndTradersInfo[index]; + // if the order IS NOT fillable, do not add anything to the accumulations and continue iterating + if (orderInfo.orderStatus !== OrderStatus.FILLABLE) { + return acc; + } + // if the order IS fillable, add the order and calculate the remaining fillable amount + const transferrableAssetAmount = BigNumber.min([traderInfo.makerAllowance, traderInfo.makerBalance]); + const transferrableFeeAssetAmount = BigNumber.min([ + traderInfo.makerZrxAllowance, + traderInfo.makerZrxBalance, + ]); + const remainingTakerAssetAmount = order.takerAssetAmount.minus(orderInfo.orderTakerAssetFilledAmount); + const remainingMakerAssetAmount = orderUtils.calculateRemainingMakerAssetAmount( + order, + remainingTakerAssetAmount, + ); + const remainingFillableCalculator = new RemainingFillableCalculator( + order.makerFee, + order.makerAssetAmount, + order.makerAssetData === zrxAssetData, + transferrableAssetAmount, + transferrableFeeAssetAmount, + remainingMakerAssetAmount, + ); + const remainingFillableAmount = remainingFillableCalculator.computeRemainingFillable(); + return { + orders: _.concat(orders, order), + remainingFillableMakerAssetAmounts: _.concat( + remainingFillableMakerAssetAmounts, + remainingFillableAmount, + ), + }; + }, + { orders: [] as SignedOrder[], remainingFillableMakerAssetAmounts: [] as BigNumber[] }, + ); + return result; +} + +function getOpenAsksFromOrderbook(orderbookResponse: OrderbookResponse): SignedOrder[] { + const asks = _.map(orderbookResponse.asks.records, apiOrder => apiOrder.order); + const result = _.filter(asks, ask => orderUtils.isOpenOrder(ask)); + return result; +} diff --git a/packages/asset-buyer/src/forwarder_helper_impl.ts b/packages/asset-buyer/src/forwarder_helper_impl.ts new file mode 100644 index 000000000..a90edb0bb --- /dev/null +++ b/packages/asset-buyer/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/asset-buyer/src/globals.d.ts b/packages/asset-buyer/src/globals.d.ts new file mode 100644 index 000000000..94e63a32d --- /dev/null +++ b/packages/asset-buyer/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/asset-buyer/src/index.ts b/packages/asset-buyer/src/index.ts new file mode 100644 index 000000000..eb3a34bd5 --- /dev/null +++ b/packages/asset-buyer/src/index.ts @@ -0,0 +1,2 @@ +export { forwarderHelperFactory } from './forwarder_helper_factory'; +export { ForwarderHelper, ForwarderHelperError, MarketBuyOrdersInfoRequest, MarketBuyOrdersInfo } from './types'; diff --git a/packages/asset-buyer/src/types.ts b/packages/asset-buyer/src/types.ts new file mode 100644 index 000000000..a7f02ff8d --- /dev/null +++ b/packages/asset-buyer/src/types.ts @@ -0,0 +1,49 @@ +import { SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; + +export enum ForwarderHelperFactoryError { + NoEtherTokenContractFound = 'NO_ETHER_TOKEN_CONTRACT_FOUND', + NoZrxTokenContractFound = 'NO_ZRX_TOKEN_CONTRACT_FOUND', + StandardRelayerApiError = 'STANDARD_RELAYER_API_ERROR', +} + +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/asset-buyer/src/utils/forwarder_helper_impl_config_utils.ts b/packages/asset-buyer/src/utils/forwarder_helper_impl_config_utils.ts new file mode 100644 index 000000000..253384f65 --- /dev/null +++ b/packages/asset-buyer/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/asset-buyer/src/utils/order_utils.ts b/packages/asset-buyer/src/utils/order_utils.ts new file mode 100644 index 000000000..bb0bdb80f --- /dev/null +++ b/packages/asset-buyer/src/utils/order_utils.ts @@ -0,0 +1,21 @@ +import { SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; + +import { constants } from '../constants'; + +export const orderUtils = { + isOrderExpired(order: SignedOrder): boolean { + const millisecondsInSecond = 1000; + const currentUnixTimestampSec = new BigNumber(Date.now() / millisecondsInSecond).round(); + return order.expirationTimeSeconds.lessThan(currentUnixTimestampSec); + }, + calculateRemainingMakerAssetAmount(order: SignedOrder, remainingTakerAssetAmount: BigNumber): BigNumber { + const result = remainingTakerAssetAmount.eq(0) + ? constants.ZERO_AMOUNT + : remainingTakerAssetAmount.times(order.makerAssetAmount).dividedToIntegerBy(order.takerAssetAmount); + return result; + }, + isOpenOrder(order: SignedOrder): boolean { + return order.takerAddress === constants.NULL_ADDRESS; + }, +}; diff --git a/packages/asset-buyer/test/forwarder_helper_impl_test.ts b/packages/asset-buyer/test/forwarder_helper_impl_test.ts new file mode 100644 index 000000000..3c3b6db92 --- /dev/null +++ b/packages/asset-buyer/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/asset-buyer/test/utils/chai_setup.ts b/packages/asset-buyer/test/utils/chai_setup.ts new file mode 100644 index 000000000..1a8733093 --- /dev/null +++ b/packages/asset-buyer/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/asset-buyer/tsconfig.json b/packages/asset-buyer/tsconfig.json new file mode 100644 index 000000000..2ee711adc --- /dev/null +++ b/packages/asset-buyer/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "." + }, + "include": ["./src/**/*", "./test/**/*"] +} diff --git a/packages/asset-buyer/tslint.json b/packages/asset-buyer/tslint.json new file mode 100644 index 000000000..ffaefe83a --- /dev/null +++ b/packages/asset-buyer/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0xproject/tslint-config"] +} diff --git a/packages/asset-buyer/typedoc-tsconfig.json b/packages/asset-buyer/typedoc-tsconfig.json new file mode 100644 index 000000000..c9b0af1ae --- /dev/null +++ b/packages/asset-buyer/typedoc-tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../typedoc-tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/**/*", "./test/**/*"] +} diff --git a/packages/forwarder-helper/.npmignore b/packages/forwarder-helper/.npmignore deleted file mode 100644 index 5333847e7..000000000 --- a/packages/forwarder-helper/.npmignore +++ /dev/null @@ -1,8 +0,0 @@ -.* -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 deleted file mode 100644 index f0e646700..000000000 --- a/packages/forwarder-helper/CHANGELOG.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "timestamp": 1536142250, - "version": "1.0.1", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "version": "1.0.1-rc.2", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1535377027 - }, - { - "version": "1.0.1-rc.1", - "changes": [ - { - "note": "Add initial forwarderHelperFactory", - "pr": 997 - } - ], - "timestamp": 1535133899 - } -] diff --git a/packages/forwarder-helper/CHANGELOG.md b/packages/forwarder-helper/CHANGELOG.md deleted file mode 100644 index d6b67aa1b..000000000 --- a/packages/forwarder-helper/CHANGELOG.md +++ /dev/null @@ -1,18 +0,0 @@ - - -CHANGELOG - -## v1.0.1 - _September 5, 2018_ - - * Dependencies updated - -## v1.0.1-rc.2 - _August 27, 2018_ - - * Dependencies updated - -## v1.0.1-rc.1 - _August 24, 2018_ - - * Add initial forwarderHelperFactory (#997) diff --git a/packages/forwarder-helper/README.md b/packages/forwarder-helper/README.md deleted file mode 100644 index c74526910..000000000 --- a/packages/forwarder-helper/README.md +++ /dev/null @@ -1,83 +0,0 @@ -## @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 deleted file mode 100644 index f849fb4ed..000000000 --- a/packages/forwarder-helper/package.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "name": "@0xproject/forwarder-helper", - "version": "1.0.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 --tsconfig typedoc-tsconfig.json --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.8", - "@0xproject/connect": "^2.0.0", - "@0xproject/contract-wrappers": "^1.0.1", - "@0xproject/json-schemas": "^1.0.1", - "@0xproject/order-utils": "^1.0.1", - "@0xproject/subproviders": "^2.0.2", - "@0xproject/types": "^1.0.1", - "@0xproject/typescript-typings": "^2.0.0", - "@0xproject/utils": "^1.0.8", - "lodash": "^4.17.10" - }, - "devDependencies": { - "@0xproject/tslint-config": "^1.0.7", - "@types/lodash": "^4.14.116", - "@types/mocha": "^2.2.42", - "@types/node": "^8.0.53", - "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": "0.12.0", - "typescript": "3.0.1" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/packages/forwarder-helper/src/constants.ts b/packages/forwarder-helper/src/constants.ts deleted file mode 100644 index c0a1b090e..000000000 --- a/packages/forwarder-helper/src/constants.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { BigNumber } from '@0xproject/utils'; - -export const constants = { - ZERO_AMOUNT: new BigNumber(0), - NULL_ADDRESS: '0x0000000000000000000000000000000000000000', -}; diff --git a/packages/forwarder-helper/src/forwarder_helper_factory.ts b/packages/forwarder-helper/src/forwarder_helper_factory.ts deleted file mode 100644 index 2b37ac98f..000000000 --- a/packages/forwarder-helper/src/forwarder_helper_factory.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { assert } from '@0xproject/assert'; -import { APIOrder, HttpClient, OrderbookResponse } from '@0xproject/connect'; -import { ContractWrappers, OrderAndTraderInfo, OrderStatus } from '@0xproject/contract-wrappers'; -import { schemas } from '@0xproject/json-schemas'; -import { assetDataUtils } from '@0xproject/order-utils'; -import { RemainingFillableCalculator } from '@0xproject/order-utils/lib/src/remaining_fillable_calculator'; -import { RPCSubprovider, Web3ProviderEngine } from '@0xproject/subproviders'; -import { SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import * as _ from 'lodash'; - -import { constants } from './constants'; -import { ForwarderHelperImpl, ForwarderHelperImplConfig } from './forwarder_helper_impl'; -import { ForwarderHelper, ForwarderHelperFactoryError } from './types'; -import { orderUtils } from './utils/order_utils'; - -export const forwarderHelperFactory = { - /** - * Given an array of orders and an array of feeOrders, get a ForwarderHelper - * @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); - // TODO: Add assertion here for orders all having the same makerAsset and takerAsset - const config: ForwarderHelperImplConfig = { - orders, - feeOrders, - }; - const helper = new ForwarderHelperImpl(config); - return helper; - }, - /** - * Given a desired makerAsset and SRA url, get a ForwarderHelper - * @param makerAssetData An array of objects conforming to SignedOrder. Each order should specify the same makerAssetData and takerAssetData - * @param sraUrl A url pointing to an SRA v2 compliant endpoint. - * @param rpcUrl A url pointing to an ethereum node. - * @param networkId The ethereum networkId, defaults to 1 (mainnet). - * @return A ForwarderHelper, see type for definition - */ - async getForwarderHelperForMakerAssetDataAsync( - makerAssetData: string, - sraUrl: string, - rpcUrl?: string, - networkId: number = 1, - ): Promise { - assert.isHexString('makerAssetData', makerAssetData); - assert.isWebUri('sraUrl', sraUrl); - if (!_.isUndefined(rpcUrl)) { - assert.isWebUri('rpcUrl', rpcUrl); - } - assert.isNumber('networkId', networkId); - // create provider - const providerEngine = new Web3ProviderEngine(); - if (!_.isUndefined(rpcUrl)) { - providerEngine.addProvider(new RPCSubprovider(rpcUrl)); - } - providerEngine.start(); - // create contract wrappers given provider and networkId - const contractWrappers = new ContractWrappers(providerEngine, { networkId }); - // find ether token asset data - const etherTokenAddressIfExists = contractWrappers.etherToken.getContractAddressIfExists(); - if (_.isUndefined(etherTokenAddressIfExists)) { - throw new Error(ForwarderHelperFactoryError.NoEtherTokenContractFound); - } - const etherTokenAssetData = assetDataUtils.encodeERC20AssetData(etherTokenAddressIfExists); - // find zrx token asset data - let zrxTokenAssetData: string; - try { - zrxTokenAssetData = contractWrappers.exchange.getZRXAssetData(); - } catch (err) { - throw new Error(ForwarderHelperFactoryError.NoZrxTokenContractFound); - } - // get orderbooks for makerAsset/WETH and ZRX/WETH - const sraClient = new HttpClient(sraUrl); - const orderbookRequests = [ - { baseAssetData: makerAssetData, quoteAssetData: etherTokenAssetData }, - { baseAssetData: zrxTokenAssetData, quoteAssetData: etherTokenAssetData }, - ]; - const requestOpts = { networkId }; - let makerAssetOrderbook: OrderbookResponse; - let zrxOrderbook: OrderbookResponse; - try { - [makerAssetOrderbook, zrxOrderbook] = await Promise.all( - _.map(orderbookRequests, request => sraClient.getOrderbookAsync(request, requestOpts)), - ); - } catch (err) { - throw new Error(ForwarderHelperFactoryError.StandardRelayerApiError); - } - // validate orders and find remaining fillable from on chain state or sra api - let ordersAndRemainingFillableMakerAssetAmounts: OrdersAndRemainingFillableMakerAssetAmounts; - let feeOrdersAndRemainingFillableMakerAssetAmounts: OrdersAndRemainingFillableMakerAssetAmounts; - if (!_.isUndefined(rpcUrl)) { - // if we do have an rpc url, get on-chain orders and traders info via the OrderValidatorWrapper - const ordersFromSra = getOpenAsksFromOrderbook(makerAssetOrderbook); - const feeOrdersFromSra = getOpenAsksFromOrderbook(zrxOrderbook); - // TODO: try catch these requests and throw a more domain specific error - // TODO: optimization, reduce this to once RPC call buy combining orders into one array and then splitting up the response - const [makerAssetOrdersAndTradersInfo, feeOrdersAndTradersInfo] = await Promise.all( - _.map([ordersFromSra, feeOrdersFromSra], ordersToBeValidated => { - const takerAddresses = _.map(ordersToBeValidated, () => constants.NULL_ADDRESS); - return contractWrappers.orderValidator.getOrdersAndTradersInfoAsync( - ordersToBeValidated, - takerAddresses, - ); - }), - ); - // take maker asset orders from SRA + on chain information and find the valid orders and remaining fillable maker asset amounts - ordersAndRemainingFillableMakerAssetAmounts = getValidOrdersAndRemainingFillableMakerAssetAmountsFromOnChain( - ordersFromSra, - makerAssetOrdersAndTradersInfo, - zrxTokenAssetData, - ); - // take fee orders from SRA + on chain information and find the valid orders and remaining fillable maker asset amounts - feeOrdersAndRemainingFillableMakerAssetAmounts = getValidOrdersAndRemainingFillableMakerAssetAmountsFromOnChain( - feeOrdersFromSra, - feeOrdersAndTradersInfo, - zrxTokenAssetData, - ); - } else { - // if we don't have an rpc url, assume all orders are valid and fallback to optional fill amounts from SRA - // if fill amounts are not available from the SRA, assume all orders are completely fillable - const apiOrdersFromSra = makerAssetOrderbook.asks.records; - const feeApiOrdersFromSra = zrxOrderbook.asks.records; - // take maker asset orders from SRA and the valid orders and remaining fillable maker asset amounts - ordersAndRemainingFillableMakerAssetAmounts = getValidOrdersAndRemainingFillableMakerAssetAmountsFromApi( - apiOrdersFromSra, - ); - // take fee orders from SRA and find the valid orders and remaining fillable maker asset amounts - feeOrdersAndRemainingFillableMakerAssetAmounts = getValidOrdersAndRemainingFillableMakerAssetAmountsFromApi( - feeApiOrdersFromSra, - ); - } - // compile final config - const config: ForwarderHelperImplConfig = { - orders: ordersAndRemainingFillableMakerAssetAmounts.orders, - feeOrders: feeOrdersAndRemainingFillableMakerAssetAmounts.orders, - remainingFillableMakerAssetAmounts: - ordersAndRemainingFillableMakerAssetAmounts.remainingFillableMakerAssetAmounts, - remainingFillableFeeAmounts: - feeOrdersAndRemainingFillableMakerAssetAmounts.remainingFillableMakerAssetAmounts, - }; - const helper = new ForwarderHelperImpl(config); - return helper; - }, -}; - -interface OrdersAndRemainingFillableMakerAssetAmounts { - orders: SignedOrder[]; - remainingFillableMakerAssetAmounts: BigNumber[]; -} - -/** - * Given an array of APIOrder objects from a standard relayer api, return an array - * of fillable orders with their corresponding remainingFillableMakerAssetAmounts - */ -function getValidOrdersAndRemainingFillableMakerAssetAmountsFromApi( - apiOrders: APIOrder[], -): OrdersAndRemainingFillableMakerAssetAmounts { - const result = _.reduce( - apiOrders, - (acc, apiOrder) => { - // get current accumulations - const { orders, remainingFillableMakerAssetAmounts } = acc; - // get order and metadata - const { order, metaData } = apiOrder; - // if the order is expired or not open, move on - if (orderUtils.isOrderExpired(order) || !orderUtils.isOpenOrder(order)) { - return acc; - } - // calculate remainingFillableMakerAssetAmount from api metadata, else assume order is completely fillable - const remainingFillableTakerAssetAmount = _.get( - metaData, - 'remainingTakerAssetAmount', - order.takerAssetAmount, - ); - const remainingFillableMakerAssetAmount = orderUtils.calculateRemainingMakerAssetAmount( - order, - remainingFillableTakerAssetAmount, - ); - // if there is some amount of maker asset left to fill and add the order and remaining amount to the accumulations - // if there is not any maker asset left to fill, do not add - if (remainingFillableMakerAssetAmount.gt(constants.ZERO_AMOUNT)) { - return { - orders: _.concat(orders, order), - remainingFillableMakerAssetAmounts: _.concat( - remainingFillableMakerAssetAmounts, - remainingFillableMakerAssetAmount, - ), - }; - } else { - return acc; - } - }, - { orders: [] as SignedOrder[], remainingFillableMakerAssetAmounts: [] as BigNumber[] }, - ); - return result; -} - -/** - * Given an array of orders and corresponding on-chain infos, return a subset of the orders - * that are still fillable orders with their corresponding remainingFillableMakerAssetAmounts - */ -function getValidOrdersAndRemainingFillableMakerAssetAmountsFromOnChain( - inputOrders: SignedOrder[], - ordersAndTradersInfo: OrderAndTraderInfo[], - zrxAssetData: string, -): OrdersAndRemainingFillableMakerAssetAmounts { - // iterate through the input orders and find the ones that are still fillable - // for the orders that are still fillable, calculate the remaining fillable maker asset amount - const result = _.reduce( - inputOrders, - (acc, order, index) => { - // get current accumulations - const { orders, remainingFillableMakerAssetAmounts } = acc; - // get corresponding on-chain state for the order - const { orderInfo, traderInfo } = ordersAndTradersInfo[index]; - // if the order IS NOT fillable, do not add anything to the accumulations and continue iterating - if (orderInfo.orderStatus !== OrderStatus.FILLABLE) { - return acc; - } - // if the order IS fillable, add the order and calculate the remaining fillable amount - const transferrableAssetAmount = BigNumber.min([traderInfo.makerAllowance, traderInfo.makerBalance]); - const transferrableFeeAssetAmount = BigNumber.min([ - traderInfo.makerZrxAllowance, - traderInfo.makerZrxBalance, - ]); - const remainingTakerAssetAmount = order.takerAssetAmount.minus(orderInfo.orderTakerAssetFilledAmount); - const remainingMakerAssetAmount = orderUtils.calculateRemainingMakerAssetAmount( - order, - remainingTakerAssetAmount, - ); - const remainingFillableCalculator = new RemainingFillableCalculator( - order.makerFee, - order.makerAssetAmount, - order.makerAssetData === zrxAssetData, - transferrableAssetAmount, - transferrableFeeAssetAmount, - remainingMakerAssetAmount, - ); - const remainingFillableAmount = remainingFillableCalculator.computeRemainingFillable(); - return { - orders: _.concat(orders, order), - remainingFillableMakerAssetAmounts: _.concat( - remainingFillableMakerAssetAmounts, - remainingFillableAmount, - ), - }; - }, - { orders: [] as SignedOrder[], remainingFillableMakerAssetAmounts: [] as BigNumber[] }, - ); - return result; -} - -function getOpenAsksFromOrderbook(orderbookResponse: OrderbookResponse): SignedOrder[] { - const asks = _.map(orderbookResponse.asks.records, apiOrder => apiOrder.order); - const result = _.filter(asks, ask => orderUtils.isOpenOrder(ask)); - return result; -} diff --git a/packages/forwarder-helper/src/forwarder_helper_impl.ts b/packages/forwarder-helper/src/forwarder_helper_impl.ts deleted file mode 100644 index a90edb0bb..000000000 --- a/packages/forwarder-helper/src/forwarder_helper_impl.ts +++ /dev/null @@ -1,64 +0,0 @@ -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 deleted file mode 100644 index 94e63a32d..000000000 --- a/packages/forwarder-helper/src/globals.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index eb3a34bd5..000000000 --- a/packages/forwarder-helper/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -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 deleted file mode 100644 index a7f02ff8d..000000000 --- a/packages/forwarder-helper/src/types.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; - -export enum ForwarderHelperFactoryError { - NoEtherTokenContractFound = 'NO_ETHER_TOKEN_CONTRACT_FOUND', - NoZrxTokenContractFound = 'NO_ZRX_TOKEN_CONTRACT_FOUND', - StandardRelayerApiError = 'STANDARD_RELAYER_API_ERROR', -} - -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 deleted file mode 100644 index 253384f65..000000000 --- a/packages/forwarder-helper/src/utils/forwarder_helper_impl_config_utils.ts +++ /dev/null @@ -1,92 +0,0 @@ -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/src/utils/order_utils.ts b/packages/forwarder-helper/src/utils/order_utils.ts deleted file mode 100644 index bb0bdb80f..000000000 --- a/packages/forwarder-helper/src/utils/order_utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; - -import { constants } from '../constants'; - -export const orderUtils = { - isOrderExpired(order: SignedOrder): boolean { - const millisecondsInSecond = 1000; - const currentUnixTimestampSec = new BigNumber(Date.now() / millisecondsInSecond).round(); - return order.expirationTimeSeconds.lessThan(currentUnixTimestampSec); - }, - calculateRemainingMakerAssetAmount(order: SignedOrder, remainingTakerAssetAmount: BigNumber): BigNumber { - const result = remainingTakerAssetAmount.eq(0) - ? constants.ZERO_AMOUNT - : remainingTakerAssetAmount.times(order.makerAssetAmount).dividedToIntegerBy(order.takerAssetAmount); - return result; - }, - isOpenOrder(order: SignedOrder): boolean { - return order.takerAddress === constants.NULL_ADDRESS; - }, -}; diff --git a/packages/forwarder-helper/test/forwarder_helper_impl_test.ts b/packages/forwarder-helper/test/forwarder_helper_impl_test.ts deleted file mode 100644 index 3c3b6db92..000000000 --- a/packages/forwarder-helper/test/forwarder_helper_impl_test.ts +++ /dev/null @@ -1,136 +0,0 @@ -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 deleted file mode 100644 index 1a8733093..000000000 --- a/packages/forwarder-helper/test/utils/chai_setup.ts +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 2ee711adc..000000000 --- a/packages/forwarder-helper/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib", - "rootDir": "." - }, - "include": ["./src/**/*", "./test/**/*"] -} diff --git a/packages/forwarder-helper/tslint.json b/packages/forwarder-helper/tslint.json deleted file mode 100644 index ffaefe83a..000000000 --- a/packages/forwarder-helper/tslint.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["@0xproject/tslint-config"] -} diff --git a/packages/forwarder-helper/typedoc-tsconfig.json b/packages/forwarder-helper/typedoc-tsconfig.json deleted file mode 100644 index c9b0af1ae..000000000 --- a/packages/forwarder-helper/typedoc-tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../typedoc-tsconfig", - "compilerOptions": { - "outDir": "lib" - }, - "include": ["./src/**/*", "./test/**/*"] -} -- cgit v1.2.3