diff options
Diffstat (limited to 'packages/asset-buyer')
-rw-r--r-- | packages/asset-buyer/CHANGELOG.json | 61 | ||||
-rw-r--r-- | packages/asset-buyer/CHANGELOG.md | 19 | ||||
-rw-r--r-- | packages/asset-buyer/README.md | 14 | ||||
-rw-r--r-- | packages/asset-buyer/package.json | 42 | ||||
-rw-r--r-- | packages/asset-buyer/src/asset_buyer.ts | 116 | ||||
-rw-r--r-- | packages/asset-buyer/src/constants.ts | 14 | ||||
-rw-r--r-- | packages/asset-buyer/src/index.ts | 13 | ||||
-rw-r--r-- | packages/asset-buyer/src/order_providers/basic_order_provider.ts | 13 | ||||
-rw-r--r-- | packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts | 40 | ||||
-rw-r--r-- | packages/asset-buyer/src/types.ts | 11 | ||||
-rw-r--r-- | packages/asset-buyer/src/utils/assert.ts | 21 | ||||
-rw-r--r-- | packages/asset-buyer/src/utils/asset_data_utils.ts | 24 | ||||
-rw-r--r-- | packages/asset-buyer/src/utils/buy_quote_calculator.ts | 122 | ||||
-rw-r--r-- | packages/asset-buyer/src/utils/order_provider_response_processor.ts | 15 | ||||
-rw-r--r-- | packages/asset-buyer/src/utils/order_utils.ts | 72 | ||||
-rw-r--r-- | packages/asset-buyer/test/buy_quote_calculator_test.ts | 16 | ||||
-rw-r--r-- | packages/asset-buyer/tslint.json | 2 |
17 files changed, 408 insertions, 207 deletions
diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json index dbb801b69..df4531063 100644 --- a/packages/asset-buyer/CHANGELOG.json +++ b/packages/asset-buyer/CHANGELOG.json @@ -1,11 +1,70 @@ [ { + "version": "2.2.0", + "changes": [ + { + "note": "`getAssetBuyerForProvidedOrders` factory function now takes 3 args instead of 4", + "pr": 1187 + }, + { + "note": + "the `OrderProvider` now requires a new method `getAvailableMakerAssetDatasAsync` and the `StandardRelayerAPIOrderProvider` requires the network id at init.", + "pr": 1203 + }, + { + "note": "No longer require that provided orders all have the same maker and taker asset data", + "pr": 1197 + }, + { + "note": + "Fix bug where `BuyQuoteInfo` objects could return `totalEthAmount` and `feeEthAmount` that were not whole numbers", + "pr": 1207 + }, + { + "note": + "Fix bug where default values for `AssetBuyer` public facing methods could get overriden by `undefined` values", + "pr": 1207 + }, + { + "note": "Lower default expiry buffer from 5 minutes to 2 minutes", + "pr": 1217 + } + ], + "timestamp": 1541740904 + }, + { "version": "2.1.0", "changes": [ { "note": "Add `gasLimit` and `gasPrice` as optional properties on `BuyQuoteExecutionOpts`" + }, + { + "note": "Export `BuyQuoteInfo` type", + "pr": 1131 + }, + { + "note": + "Updated to use new modularized artifacts and the latest version of @0xproject/contract-wrappers", + "pr": 1105 + }, + { + "note": "Add `gasLimit` and `gasPrice` as optional properties on `BuyQuoteExecutionOpts`", + "pr": 1116 + }, + { + "note": "Add `docs:json` command to package.json", + "pr": 1139 + }, + { + "note": "Add missing types to public interface", + "pr": 1139 + }, + { + "note": "Throw `SignatureRequestDenied` and `TransactionValueTooLow` errors when executing buy", + "pr": 1147 } - ] + ], + "timestamp": 1539871071 }, { "version": "2.0.0", diff --git a/packages/asset-buyer/CHANGELOG.md b/packages/asset-buyer/CHANGELOG.md index 65d190a2d..d6220767e 100644 --- a/packages/asset-buyer/CHANGELOG.md +++ b/packages/asset-buyer/CHANGELOG.md @@ -5,6 +5,25 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.2.0 - _November 9, 2018_ + + * `getAssetBuyerForProvidedOrders` factory function now takes 3 args instead of 4 (#1187) + * the `OrderProvider` now requires a new method `getAvailableMakerAssetDatasAsync` and the `StandardRelayerAPIOrderProvider` requires the network id at init. (#1203) + * No longer require that provided orders all have the same maker and taker asset data (#1197) + * Fix bug where `BuyQuoteInfo` objects could return `totalEthAmount` and `feeEthAmount` that were not whole numbers (#1207) + * Fix bug where default values for `AssetBuyer` public facing methods could get overriden by `undefined` values (#1207) + * Lower default expiry buffer from 5 minutes to 2 minutes (#1217) + +## v2.1.0 - _October 18, 2018_ + + * Add `gasLimit` and `gasPrice` as optional properties on `BuyQuoteExecutionOpts` + * Export `BuyQuoteInfo` type (#1131) + * Updated to use new modularized artifacts and the latest version of @0xproject/contract-wrappers (#1105) + * Add `gasLimit` and `gasPrice` as optional properties on `BuyQuoteExecutionOpts` (#1116) + * Add `docs:json` command to package.json (#1139) + * Add missing types to public interface (#1139) + * Throw `SignatureRequestDenied` and `TransactionValueTooLow` errors when executing buy (#1147) + ## v2.0.0 - _October 4, 2018_ * Expand AssetBuyer to work with multiple assets at once (#1086) diff --git a/packages/asset-buyer/README.md b/packages/asset-buyer/README.md index 81c7e1e85..383a3836a 100644 --- a/packages/asset-buyer/README.md +++ b/packages/asset-buyer/README.md @@ -1,4 +1,4 @@ -## @0xproject/asset-buyer +## @0x/asset-buyer **Warning: In Beta, has not been extensively tested.** @@ -9,26 +9,26 @@ In its more advanced and useful form, it integrates with the [Standard Relayer A ## Installation ```bash -yarn add @0xproject/asset-buyer +yarn add @0x/asset-buyer ``` **Import** ```typescript -import { AssetBuyer } from '@0xproject/asset-buyer'; +import { AssetBuyer } from '@0x/asset-buyer'; ``` or ```javascript -var AssetBuyer = require('@0xproject/asset-buyer').AssetBuyer; +var AssetBuyer = require('@0x/asset-buyer').AssetBuyer; ``` 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"], + "typeRoots": ["node_modules/@0x/typescript-typings/types", "node_modules/@types"], } ``` @@ -57,13 +57,13 @@ yarn install To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: ```bash -PKG=@0xproject/asset-buyer yarn build +PKG=@0x/asset-buyer yarn build ``` Or continuously rebuild on change: ```bash -PKG=@0xproject/asset-buyer yarn watch +PKG=@0x/asset-buyer yarn watch ``` ### Clean diff --git a/packages/asset-buyer/package.json b/packages/asset-buyer/package.json index 291342169..fc2414952 100644 --- a/packages/asset-buyer/package.json +++ b/packages/asset-buyer/package.json @@ -1,6 +1,6 @@ { - "name": "@0xproject/asset-buyer", - "version": "2.0.0", + "name": "@0x/asset-buyer", + "version": "2.2.0", "engines": { "node": ">=6.12" }, @@ -8,18 +8,17 @@ "main": "lib/src/index.js", "types": "lib/src/index.d.ts", "scripts": { - "watch_without_deps": "tsc -w", - "lint": "tslint --project .", + "build": "yarn tsc -b", + "build:ci": "yarn build", + "lint": "tslint --format stylish --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", - "build:ci": "yarn build", - "manual:postpublish": "yarn build; node ./scripts/postpublish.js" + "clean": "shx rm -rf lib test_temp", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" }, "config": { "postpublish": { @@ -37,28 +36,27 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/asset-buyer/README.md", "dependencies": { - "@0xproject/assert": "^1.0.13", - "@0xproject/connect": "^3.0.1", - "@0xproject/contract-wrappers": "^2.0.2", - "@0xproject/json-schemas": "^1.0.7", - "@0xproject/order-utils": "^1.0.7", - "@0xproject/subproviders": "^2.0.7", - "@0xproject/types": "^1.1.4", - "@0xproject/typescript-typings": "^3.0.2", - "@0xproject/utils": "^2.0.2", - "@0xproject/web3-wrapper": "^3.0.3", - "ethereum-types": "^1.0.11", + "@0x/assert": "^1.0.15", + "@0x/connect": "^3.0.3", + "@0x/contract-wrappers": "^3.0.1", + "@0x/json-schemas": "^2.0.1", + "@0x/order-utils": "^2.0.1", + "@0x/subproviders": "^2.1.1", + "@0x/types": "^1.2.1", + "@0x/typescript-typings": "^3.0.4", + "@0x/utils": "^2.0.4", + "@0x/web3-wrapper": "^3.1.1", + "ethereum-types": "^1.1.2", "lodash": "^4.17.10" }, "devDependencies": { - "@0xproject/tslint-config": "^1.0.8", + "@0x/tslint-config": "^1.0.10", "@types/lodash": "^4.14.116", "@types/mocha": "^2.2.42", "@types/node": "*", "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", @@ -66,7 +64,7 @@ "nyc": "^11.0.1", "shx": "^0.2.2", "tslint": "5.11.0", - "typedoc": "0.12.0", + "typedoc": "0.13.0", "typescript": "3.0.1" }, "publishConfig": { diff --git a/packages/asset-buyer/src/asset_buyer.ts b/packages/asset-buyer/src/asset_buyer.ts index 50343efde..934410c55 100644 --- a/packages/asset-buyer/src/asset_buyer.ts +++ b/packages/asset-buyer/src/asset_buyer.ts @@ -1,9 +1,9 @@ -import { ContractWrappers } from '@0xproject/contract-wrappers'; -import { schemas } from '@0xproject/json-schemas'; -import { SignedOrder } from '@0xproject/order-utils'; -import { ObjectMap } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { ContractWrappers, ContractWrappersError, ForwarderWrapperError } from '@0x/contract-wrappers'; +import { schemas } from '@0x/json-schemas'; +import { SignedOrder } from '@0x/order-utils'; +import { ObjectMap } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; import { Provider } from 'ethereum-types'; import * as _ from 'lodash'; @@ -52,16 +52,12 @@ export class AssetBuyer { public static getAssetBuyerForProvidedOrders( provider: Provider, orders: SignedOrder[], - feeOrders: SignedOrder[] = [], options: Partial<AssetBuyerOpts> = {}, ): AssetBuyer { assert.isWeb3Provider('provider', provider); assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema); - assert.doesConformToSchema('feeOrders', feeOrders, schemas.signedOrdersSchema); - assert.areValidProvidedOrders('orders', orders); - assert.areValidProvidedOrders('feeOrders', feeOrders); assert.assert(orders.length !== 0, `Expected orders to contain at least one order`); - const orderProvider = new BasicOrderProvider(_.concat(orders, feeOrders)); + const orderProvider = new BasicOrderProvider(orders); const assetBuyer = new AssetBuyer(provider, orderProvider, options); return assetBuyer; } @@ -80,7 +76,8 @@ export class AssetBuyer { ): AssetBuyer { assert.isWeb3Provider('provider', provider); assert.isWebUri('sraApiUrl', sraApiUrl); - const orderProvider = new StandardRelayerAPIOrderProvider(sraApiUrl); + const networkId = options.networkId || constants.DEFAULT_ASSET_BUYER_OPTS.networkId; + const orderProvider = new StandardRelayerAPIOrderProvider(sraApiUrl, networkId); const assetBuyer = new AssetBuyer(provider, orderProvider, options); return assetBuyer; } @@ -93,10 +90,11 @@ export class AssetBuyer { * @return An instance of AssetBuyer */ constructor(provider: Provider, orderProvider: OrderProvider, options: Partial<AssetBuyerOpts> = {}) { - const { networkId, orderRefreshIntervalMs, expiryBufferSeconds } = { - ...constants.DEFAULT_ASSET_BUYER_OPTS, - ...options, - }; + const { networkId, orderRefreshIntervalMs, expiryBufferSeconds } = _.merge( + {}, + constants.DEFAULT_ASSET_BUYER_OPTS, + options, + ); assert.isWeb3Provider('provider', provider); assert.isValidOrderProvider('orderProvider', orderProvider); assert.isNumber('networkId', networkId); @@ -125,19 +123,25 @@ export class AssetBuyer { assetBuyAmount: BigNumber, options: Partial<BuyQuoteRequestOpts> = {}, ): Promise<BuyQuote> { - const { feePercentage, shouldForceOrderRefresh, slippagePercentage } = { - ...constants.DEFAULT_BUY_QUOTE_REQUEST_OPTS, - ...options, - }; + const { feePercentage, shouldForceOrderRefresh, slippagePercentage } = _.merge( + {}, + constants.DEFAULT_BUY_QUOTE_REQUEST_OPTS, + options, + ); assert.isString('assetData', assetData); assert.isBigNumber('assetBuyAmount', assetBuyAmount); assert.isValidPercentage('feePercentage', feePercentage); assert.isBoolean('shouldForceOrderRefresh', shouldForceOrderRefresh); assert.isNumber('slippagePercentage', slippagePercentage); const zrxTokenAssetData = this._getZrxTokenAssetDataOrThrow(); + const isMakerAssetZrxToken = assetData === zrxTokenAssetData; + // get the relevant orders for the makerAsset and fees + // if the requested assetData is ZRX, don't get the fee info const [ordersAndFillableAmounts, feeOrdersAndFillableAmounts] = await Promise.all([ this._getOrdersAndFillableAmountsAsync(assetData, shouldForceOrderRefresh), - this._getOrdersAndFillableAmountsAsync(zrxTokenAssetData, shouldForceOrderRefresh), + isMakerAssetZrxToken + ? Promise.resolve(constants.EMPTY_ORDERS_AND_FILLABLE_AMOUNTS) + : this._getOrdersAndFillableAmountsAsync(zrxTokenAssetData, shouldForceOrderRefresh), shouldForceOrderRefresh, ]); if (ordersAndFillableAmounts.orders.length === 0) { @@ -149,6 +153,7 @@ export class AssetBuyer { assetBuyAmount, feePercentage, slippagePercentage, + isMakerAssetZrxToken, ); return buyQuote; } @@ -183,10 +188,11 @@ export class AssetBuyer { buyQuote: BuyQuote, options: Partial<BuyQuoteExecutionOpts> = {}, ): Promise<string> { - const { ethAmount, takerAddress, feeRecipient, gasLimit, gasPrice } = { - ...constants.DEFAULT_BUY_QUOTE_EXECUTION_OPTS, - ...options, - }; + const { ethAmount, takerAddress, feeRecipient, gasLimit, gasPrice } = _.merge( + {}, + constants.DEFAULT_BUY_QUOTE_EXECUTION_OPTS, + options, + ); assert.isValidBuyQuote('buyQuote', buyQuote); if (!_.isUndefined(ethAmount)) { assert.isBigNumber('ethAmount', ethAmount); @@ -195,6 +201,12 @@ export class AssetBuyer { assert.isETHAddressHex('takerAddress', takerAddress); } assert.isETHAddressHex('feeRecipient', feeRecipient); + if (!_.isUndefined(gasLimit)) { + assert.isNumber('gasLimit', gasLimit); + } + if (!_.isUndefined(gasPrice)) { + assert.isBigNumber('gasPrice', gasPrice); + } const { orders, feeOrders, feePercentage, assetBuyAmount, worstCaseQuoteInfo } = buyQuote; // if no takerAddress is provided, try to get one from the provider let finalTakerAddress; @@ -210,21 +222,41 @@ export class AssetBuyer { throw new Error(AssetBuyerError.NoAddressAvailable); } } - // if no ethAmount is provided, default to the worst ethAmount from buyQuote - const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync( - orders, - assetBuyAmount, - finalTakerAddress, - ethAmount || worstCaseQuoteInfo.totalEthAmount, - feeOrders, - feePercentage, - feeRecipient, - { - gasLimit, - gasPrice, - }, - ); - return txHash; + try { + // if no ethAmount is provided, default to the worst ethAmount from buyQuote + const txHash = await this._contractWrappers.forwarder.marketBuyOrdersWithEthAsync( + orders, + assetBuyAmount, + finalTakerAddress, + ethAmount || worstCaseQuoteInfo.totalEthAmount, + feeOrders, + feePercentage, + feeRecipient, + { + gasLimit, + gasPrice, + shouldValidate: true, + }, + ); + return txHash; + } catch (err) { + if (_.includes(err.message, ContractWrappersError.SignatureRequestDenied)) { + throw new Error(AssetBuyerError.SignatureRequestDenied); + } else if (_.includes(err.message, ForwarderWrapperError.CompleteFillFailed)) { + throw new Error(AssetBuyerError.TransactionValueTooLow); + } else { + throw err; + } + } + } + /** + * Get the asset data of all assets that are purchaseable with ether token (wETH) in the order provider passed in at init. + * + * @return An array of asset data strings that can be purchased using wETH. + */ + public async getAvailableAssetDatasAsync(): Promise<string[]> { + const etherTokenAssetData = this._getEtherTokenAssetDataOrThrow(); + return this.orderProvider.getAvailableMakerAssetDatasAsync(etherTokenAssetData); } /** * Grab orders from the map, if there is a miss or it is time to refresh, fetch and process the orders @@ -283,13 +315,13 @@ export class AssetBuyer { * Will throw if WETH does not exist for the current network. */ private _getEtherTokenAssetDataOrThrow(): string { - return assetDataUtils.getEtherTokenAssetDataOrThrow(this._contractWrappers); + return assetDataUtils.getEtherTokenAssetData(this._contractWrappers); } /** * Get the assetData that represents the ZRX token. * Will throw if ZRX does not exist for the current network. */ private _getZrxTokenAssetDataOrThrow(): string { - return assetDataUtils.getZrxTokenAssetDataOrThrow(this._contractWrappers); + return this._contractWrappers.exchange.getZRXAssetData(); } } diff --git a/packages/asset-buyer/src/constants.ts b/packages/asset-buyer/src/constants.ts index e095dee06..c0e1bf27d 100644 --- a/packages/asset-buyer/src/constants.ts +++ b/packages/asset-buyer/src/constants.ts @@ -1,6 +1,7 @@ -import { BigNumber } from '@0xproject/utils'; +import { SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; -import { AssetBuyerOpts, BuyQuoteExecutionOpts, BuyQuoteRequestOpts } from './types'; +import { AssetBuyerOpts, BuyQuoteExecutionOpts, BuyQuoteRequestOpts, OrdersAndFillableAmounts } from './types'; const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'; const MAINNET_NETWORK_ID = 1; @@ -8,7 +9,7 @@ const MAINNET_NETWORK_ID = 1; const DEFAULT_ASSET_BUYER_OPTS: AssetBuyerOpts = { networkId: MAINNET_NETWORK_ID, orderRefreshIntervalMs: 10000, // 10 seconds - expiryBufferSeconds: 300, // 5 minutes + expiryBufferSeconds: 120, // 2 minutes }; const DEFAULT_BUY_QUOTE_REQUEST_OPTS: BuyQuoteRequestOpts = { @@ -22,6 +23,11 @@ const DEFAULT_BUY_QUOTE_EXECUTION_OPTS: BuyQuoteExecutionOpts = { feeRecipient: NULL_ADDRESS, }; +const EMPTY_ORDERS_AND_FILLABLE_AMOUNTS: OrdersAndFillableAmounts = { + orders: [] as SignedOrder[], + remainingFillableMakerAssetAmounts: [] as BigNumber[], +}; + export const constants = { ZERO_AMOUNT: new BigNumber(0), NULL_ADDRESS, @@ -30,5 +36,5 @@ export const constants = { DEFAULT_ASSET_BUYER_OPTS, DEFAULT_BUY_QUOTE_EXECUTION_OPTS, DEFAULT_BUY_QUOTE_REQUEST_OPTS, - MAX_PER_PAGE: 10000, + EMPTY_ORDERS_AND_FILLABLE_AMOUNTS, }; diff --git a/packages/asset-buyer/src/index.ts b/packages/asset-buyer/src/index.ts index 2da2724d7..8418edb42 100644 --- a/packages/asset-buyer/src/index.ts +++ b/packages/asset-buyer/src/index.ts @@ -1,6 +1,12 @@ -export { Provider } from 'ethereum-types'; -export { SignedOrder } from '@0xproject/types'; -export { BigNumber } from '@0xproject/utils'; +export { + JSONRPCRequestPayload, + JSONRPCResponsePayload, + JSONRPCResponseError, + JSONRPCErrorCallback, + Provider, +} from 'ethereum-types'; +export { SignedOrder } from '@0x/types'; +export { BigNumber } from '@0x/utils'; export { AssetBuyer } from './asset_buyer'; export { BasicOrderProvider } from './order_providers/basic_order_provider'; @@ -10,6 +16,7 @@ export { AssetBuyerOpts, BuyQuote, BuyQuoteExecutionOpts, + BuyQuoteInfo, BuyQuoteRequestOpts, OrderProvider, OrderProviderRequest, diff --git a/packages/asset-buyer/src/order_providers/basic_order_provider.ts b/packages/asset-buyer/src/order_providers/basic_order_provider.ts index 9bb2d90ac..76685f27a 100644 --- a/packages/asset-buyer/src/order_providers/basic_order_provider.ts +++ b/packages/asset-buyer/src/order_providers/basic_order_provider.ts @@ -1,5 +1,5 @@ -import { schemas } from '@0xproject/json-schemas'; -import { SignedOrder } from '@0xproject/types'; +import { schemas } from '@0x/json-schemas'; +import { SignedOrder } from '@0x/types'; import * as _ from 'lodash'; import { OrderProvider, OrderProviderRequest, OrderProviderResponse } from '../types'; @@ -29,4 +29,13 @@ export class BasicOrderProvider implements OrderProvider { }); return { orders }; } + /** + * Given a taker asset data string, return all availabled paired maker asset data strings. + * @param takerAssetData A string representing the taker asset data. + * @return An array of asset data strings that can be purchased using takerAssetData. + */ + public async getAvailableMakerAssetDatasAsync(takerAssetData: string): Promise<string[]> { + const ordersWithTakerAssetData = _.filter(this.orders, { takerAssetData }); + return _.map(ordersWithTakerAssetData, order => order.makerAssetData); + } } diff --git a/packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts b/packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts index 31942c25b..be1fc55d6 100644 --- a/packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts +++ b/packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts @@ -1,5 +1,5 @@ -import { HttpClient } from '@0xproject/connect'; -import { APIOrder, OrderbookResponse } from '@0xproject/types'; +import { HttpClient } from '@0x/connect'; +import { APIOrder, AssetPairsResponse, OrderbookResponse } from '@0x/types'; import * as _ from 'lodash'; import { @@ -14,6 +14,7 @@ import { orderUtils } from '../utils/order_utils'; export class StandardRelayerAPIOrderProvider implements OrderProvider { public readonly apiUrl: string; + public readonly networkId: number; private readonly _sraClient: HttpClient; /** * Given an array of APIOrder objects from a standard relayer api, return an array @@ -30,7 +31,7 @@ export class StandardRelayerAPIOrderProvider implements OrderProvider { 'remainingTakerAssetAmount', order.takerAssetAmount, ); - const remainingFillableMakerAssetAmount = orderUtils.calculateRemainingMakerAssetAmount( + const remainingFillableMakerAssetAmount = orderUtils.getRemainingMakerAmount( order, remainingFillableTakerAssetAmount, ); @@ -44,12 +45,15 @@ export class StandardRelayerAPIOrderProvider implements OrderProvider { } /** * Instantiates a new StandardRelayerAPIOrderProvider instance - * @param apiUrl The standard relayer API base HTTP url you would like to source orders from. + * @param apiUrl The standard relayer API base HTTP url you would like to source orders from. + * @param networkId The ethereum network id. * @return An instance of StandardRelayerAPIOrderProvider */ - constructor(apiUrl: string) { + constructor(apiUrl: string, networkId: number) { assert.isWebUri('apiUrl', apiUrl); + assert.isNumber('networkId', networkId); this.apiUrl = apiUrl; + this.networkId = networkId; this._sraClient = new HttpClient(apiUrl); } /** @@ -59,9 +63,9 @@ export class StandardRelayerAPIOrderProvider implements OrderProvider { */ public async getOrdersAsync(orderProviderRequest: OrderProviderRequest): Promise<OrderProviderResponse> { assert.isValidOrderProviderRequest('orderProviderRequest', orderProviderRequest); - const { makerAssetData, takerAssetData, networkId } = orderProviderRequest; + const { makerAssetData, takerAssetData } = orderProviderRequest; const orderbookRequest = { baseAssetData: makerAssetData, quoteAssetData: takerAssetData }; - const requestOpts = { networkId }; + const requestOpts = { networkId: this.networkId }; let orderbook: OrderbookResponse; try { orderbook = await this._sraClient.getOrderbookAsync(orderbookRequest, requestOpts); @@ -76,4 +80,26 @@ export class StandardRelayerAPIOrderProvider implements OrderProvider { orders, }; } + /** + * Given a taker asset data string, return all availabled paired maker asset data strings. + * @param takerAssetData A string representing the taker asset data. + * @return An array of asset data strings that can be purchased using takerAssetData. + */ + public async getAvailableMakerAssetDatasAsync(takerAssetData: string): Promise<string[]> { + // Return a maximum of 1000 asset datas + const maxPerPage = 1000; + const requestOpts = { networkId: this.networkId, perPage: maxPerPage }; + const assetPairsRequest = { assetDataA: takerAssetData }; + const fullRequest = { + ...requestOpts, + ...assetPairsRequest, + }; + let response: AssetPairsResponse; + try { + response = await this._sraClient.getAssetPairsAsync(fullRequest); + } catch (err) { + throw new Error(AssetBuyerError.StandardRelayerApiError); + } + return _.map(response.records, item => item.assetDataB.assetData); + } } diff --git a/packages/asset-buyer/src/types.ts b/packages/asset-buyer/src/types.ts index 6218f4ba4..3f1e6ff21 100644 --- a/packages/asset-buyer/src/types.ts +++ b/packages/asset-buyer/src/types.ts @@ -1,5 +1,5 @@ -import { SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +import { SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; /** * makerAssetData: The assetData representing the desired makerAsset. @@ -9,7 +9,6 @@ import { BigNumber } from '@0xproject/utils'; export interface OrderProviderRequest { makerAssetData: string; takerAssetData: string; - networkId: number; } /** @@ -27,10 +26,12 @@ export interface SignedOrderWithRemainingFillableMakerAssetAmount extends Signed remainingFillableMakerAssetAmount?: BigNumber; } /** - * Given an OrderProviderRequest, get an OrderProviderResponse. + * gerOrdersAsync: Given an OrderProviderRequest, get an OrderProviderResponse. + * getAvailableMakerAssetDatasAsync: Given a taker asset data string, return all availabled paired maker asset data strings. */ export interface OrderProvider { getOrdersAsync: (orderProviderRequest: OrderProviderRequest) => Promise<OrderProviderResponse>; + getAvailableMakerAssetDatasAsync: (takerAssetData: string) => Promise<string[]>; } /** @@ -112,6 +113,8 @@ export enum AssetBuyerError { NoAddressAvailable = 'NO_ADDRESS_AVAILABLE', InvalidOrderProviderResponse = 'INVALID_ORDER_PROVIDER_RESPONSE', AssetUnavailable = 'ASSET_UNAVAILABLE', + SignatureRequestDenied = 'SIGNATURE_REQUEST_DENIED', + TransactionValueTooLow = 'TRANSACTION_VALUE_TOO_LOW', } export interface OrdersAndFillableAmounts { diff --git a/packages/asset-buyer/src/utils/assert.ts b/packages/asset-buyer/src/utils/assert.ts index d43b71fee..2466f53a4 100644 --- a/packages/asset-buyer/src/utils/assert.ts +++ b/packages/asset-buyer/src/utils/assert.ts @@ -1,6 +1,5 @@ -import { assert as sharedAssert } from '@0xproject/assert'; -import { schemas } from '@0xproject/json-schemas'; -import { SignedOrder } from '@0xproject/types'; +import { assert as sharedAssert } from '@0x/assert'; +import { schemas } from '@0x/json-schemas'; import * as _ from 'lodash'; import { BuyQuote, BuyQuoteInfo, OrderProvider, OrderProviderRequest } from '../types'; @@ -29,22 +28,6 @@ export const assert = { isValidOrderProviderRequest(variableName: string, orderFetcherRequest: OrderProviderRequest): void { sharedAssert.isHexString(`${variableName}.makerAssetData`, orderFetcherRequest.makerAssetData); sharedAssert.isHexString(`${variableName}.takerAssetData`, orderFetcherRequest.takerAssetData); - sharedAssert.isNumber(`${variableName}.networkId`, orderFetcherRequest.networkId); - }, - areValidProvidedOrders(variableName: string, orders: SignedOrder[]): void { - if (orders.length === 0) { - return; - } - const makerAssetData = orders[0].makerAssetData; - const takerAssetData = orders[0].takerAssetData; - const filteredOrders = _.filter( - orders, - order => order.makerAssetData === makerAssetData && order.takerAssetData === takerAssetData, - ); - sharedAssert.assert( - orders.length === filteredOrders.length, - `Expected all orders in ${variableName} to have the same makerAssetData and takerAssetData.`, - ); }, isValidPercentage(variableName: string, percentage: number): void { assert.isNumber(variableName, percentage); diff --git a/packages/asset-buyer/src/utils/asset_data_utils.ts b/packages/asset-buyer/src/utils/asset_data_utils.ts index d05ff2504..70f646902 100644 --- a/packages/asset-buyer/src/utils/asset_data_utils.ts +++ b/packages/asset-buyer/src/utils/asset_data_utils.ts @@ -1,26 +1,12 @@ -import { ContractWrappers } from '@0xproject/contract-wrappers'; -import { assetDataUtils as sharedAssetDataUtils } from '@0xproject/order-utils'; +import { ContractWrappers } from '@0x/contract-wrappers'; +import { assetDataUtils as sharedAssetDataUtils } from '@0x/order-utils'; import * as _ from 'lodash'; -import { AssetBuyerError } from '../types'; - export const assetDataUtils = { ...sharedAssetDataUtils, - getEtherTokenAssetDataOrThrow(contractWrappers: ContractWrappers): string { - const etherTokenAddressIfExists = contractWrappers.etherToken.getContractAddressIfExists(); - if (_.isUndefined(etherTokenAddressIfExists)) { - throw new Error(AssetBuyerError.NoEtherTokenContractFound); - } - const etherTokenAssetData = sharedAssetDataUtils.encodeERC20AssetData(etherTokenAddressIfExists); + getEtherTokenAssetData(contractWrappers: ContractWrappers): string { + const etherTokenAddress = contractWrappers.forwarder.etherTokenAddress; + const etherTokenAssetData = sharedAssetDataUtils.encodeERC20AssetData(etherTokenAddress); return etherTokenAssetData; }, - getZrxTokenAssetDataOrThrow(contractWrappers: ContractWrappers): string { - let zrxTokenAssetData: string; - try { - zrxTokenAssetData = contractWrappers.exchange.getZRXAssetData(); - } catch (err) { - throw new Error(AssetBuyerError.NoZrxTokenContractFound); - } - return zrxTokenAssetData; - }, }; diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts index a1d334eef..6a67ed1ed 100644 --- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts +++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts @@ -1,10 +1,12 @@ -import { marketUtils, rateUtils } from '@0xproject/order-utils'; -import { BigNumber } from '@0xproject/utils'; +import { marketUtils, SignedOrder } from '@0x/order-utils'; +import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { constants } from '../constants'; import { AssetBuyerError, BuyQuote, BuyQuoteInfo, OrdersAndFillableAmounts } from '../types'; +import { orderUtils } from './order_utils'; + // Calculates a buy quote for orders that have WETH as the takerAsset export const buyQuoteCalculator = { calculate( @@ -13,6 +15,7 @@ export const buyQuoteCalculator = { assetBuyAmount: BigNumber, feePercentage: number, slippagePercentage: number, + isMakerAssetZrxToken: boolean, ): BuyQuote { const orders = ordersAndFillableAmounts.orders; const remainingFillableMakerAssetAmounts = ordersAndFillableAmounts.remainingFillableMakerAssetAmounts; @@ -32,22 +35,31 @@ export const buyQuoteCalculator = { if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) { throw new Error(AssetBuyerError.InsufficientAssetLiquidity); } + // if we are not buying ZRX: // given the orders calculated above, find the fee-orders that cover the desired assetBuyAmount (with slippage) // TODO(bmillman): optimization // 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, - feeOrdersRemainingFillableMakerAssetAmounts, - } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(resultOrders, feeOrders, { - remainingFillableMakerAssetAmounts: ordersRemainingFillableMakerAssetAmounts, - remainingFillableFeeAmounts, - }); - // if we do not have enough feeOrders to cover the fees, throw - if (remainingFeeAmount.gt(constants.ZERO_AMOUNT)) { - throw new Error(AssetBuyerError.InsufficientZrxLiquidity); + let resultFeeOrders = [] as SignedOrder[]; + let feeOrdersRemainingFillableMakerAssetAmounts = [] as BigNumber[]; + if (!isMakerAssetZrxToken) { + const feeOrdersAndRemainingFeeAmount = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( + resultOrders, + feeOrders, + { + remainingFillableMakerAssetAmounts: ordersRemainingFillableMakerAssetAmounts, + remainingFillableFeeAmounts, + }, + ); + // if we do not have enough feeOrders to cover the fees, throw + if (feeOrdersAndRemainingFeeAmount.remainingFeeAmount.gt(constants.ZERO_AMOUNT)) { + throw new Error(AssetBuyerError.InsufficientZrxLiquidity); + } + resultFeeOrders = feeOrdersAndRemainingFeeAmount.resultFeeOrders; + feeOrdersRemainingFillableMakerAssetAmounts = + feeOrdersAndRemainingFeeAmount.feeOrdersRemainingFillableMakerAssetAmounts; } + // assetData information for the result const assetData = orders[0].makerAssetData; // compile the resulting trimmed set of orders for makerAsset and feeOrders that are needed for assetBuyAmount @@ -64,6 +76,7 @@ export const buyQuoteCalculator = { trimmedFeeOrdersAndFillableAmounts, assetBuyAmount, feePercentage, + isMakerAssetZrxToken, ); // in order to calculate the maxRate, reverse the ordersAndFillableAmounts such that they are sorted from worst rate to best rate const worstCaseQuoteInfo = calculateQuoteInfo( @@ -71,6 +84,7 @@ export const buyQuoteCalculator = { reverseOrdersAndFillableAmounts(trimmedFeeOrdersAndFillableAmounts), assetBuyAmount, feePercentage, + isMakerAssetZrxToken, ); return { assetData, @@ -89,22 +103,30 @@ function calculateQuoteInfo( feeOrdersAndFillableAmounts: OrdersAndFillableAmounts, assetBuyAmount: BigNumber, feePercentage: number, + isMakerAssetZrxToken: boolean, ): BuyQuoteInfo { // find the total eth and zrx needed to buy assetAmount from the resultOrders from left to right - const [ethAmountToBuyAsset, zrxAmountToBuyAsset] = findEthAndZrxAmountNeededToBuyAsset( - ordersAndFillableAmounts, - assetBuyAmount, - ); - // find the total eth needed to buy fees - const ethAmountToBuyFees = findEthAmountNeededToBuyFees(feeOrdersAndFillableAmounts, zrxAmountToBuyAsset); - const affiliateFeeEthAmount = ethAmountToBuyAsset.mul(feePercentage); - const totalEthAmountWithoutAffiliateFee = ethAmountToBuyAsset.plus(ethAmountToBuyFees); - const totalEthAmount = totalEthAmountWithoutAffiliateFee.plus(affiliateFeeEthAmount); + let ethAmountToBuyAsset = constants.ZERO_AMOUNT; + let ethAmountToBuyZrx = constants.ZERO_AMOUNT; + if (isMakerAssetZrxToken) { + ethAmountToBuyAsset = findEthAmountNeededToBuyZrx(ordersAndFillableAmounts, assetBuyAmount); + } else { + // find eth and zrx amounts needed to buy + const ethAndZrxAmountToBuyAsset = findEthAndZrxAmountNeededToBuyAsset(ordersAndFillableAmounts, assetBuyAmount); + ethAmountToBuyAsset = ethAndZrxAmountToBuyAsset[0]; + const zrxAmountToBuyAsset = ethAndZrxAmountToBuyAsset[1]; + // find eth amount needed to buy zrx + ethAmountToBuyZrx = findEthAmountNeededToBuyZrx(feeOrdersAndFillableAmounts, zrxAmountToBuyAsset); + } + /// find the eth amount needed to buy the affiliate fee + const ethAmountToBuyAffiliateFee = ethAmountToBuyAsset.mul(feePercentage).ceil(); + const totalEthAmountWithoutAffiliateFee = ethAmountToBuyAsset.plus(ethAmountToBuyZrx); + const ethAmountTotal = totalEthAmountWithoutAffiliateFee.plus(ethAmountToBuyAffiliateFee); // divide into the assetBuyAmount in order to find rate of makerAsset / WETH const ethPerAssetPrice = totalEthAmountWithoutAffiliateFee.div(assetBuyAmount); return { - totalEthAmount, - feeEthAmount: affiliateFeeEthAmount, + totalEthAmount: ethAmountTotal, + feeEthAmount: ethAmountToBuyAffiliateFee, ethPerAssetPrice, }; } @@ -119,29 +141,38 @@ function reverseOrdersAndFillableAmounts(ordersAndFillableAmounts: OrdersAndFill }; } -function findEthAmountNeededToBuyFees( +function findEthAmountNeededToBuyZrx( feeOrdersAndFillableAmounts: OrdersAndFillableAmounts, - feeAmount: BigNumber, + zrxBuyAmount: BigNumber, ): BigNumber { const { orders, remainingFillableMakerAssetAmounts } = feeOrdersAndFillableAmounts; const result = _.reduce( orders, (acc, order, index) => { + const { totalEthAmount, remainingZrxBuyAmount } = acc; const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index]; - const amountToFill = BigNumber.min(acc.remainingFeeAmount, remainingFillableMakerAssetAmount); - const feeAdjustedRate = rateUtils.getFeeAdjustedRateOfFeeOrder(order); - const ethAmountForThisOrder = feeAdjustedRate.mul(amountToFill); + const makerFillAmount = BigNumber.min(remainingZrxBuyAmount, remainingFillableMakerAssetAmount); + const [takerFillAmount, adjustedMakerFillAmount] = orderUtils.getTakerFillAmountForFeeOrder( + order, + makerFillAmount, + ); + const extraFeeAmount = remainingFillableMakerAssetAmount.greaterThanOrEqualTo(adjustedMakerFillAmount) + ? constants.ZERO_AMOUNT + : adjustedMakerFillAmount.sub(makerFillAmount); return { - ethAmount: acc.ethAmount.plus(ethAmountForThisOrder), - remainingFeeAmount: BigNumber.max(constants.ZERO_AMOUNT, acc.remainingFeeAmount.minus(amountToFill)), + totalEthAmount: totalEthAmount.plus(takerFillAmount), + remainingZrxBuyAmount: BigNumber.max( + constants.ZERO_AMOUNT, + remainingZrxBuyAmount.minus(makerFillAmount).plus(extraFeeAmount), + ), }; }, { - ethAmount: constants.ZERO_AMOUNT, - remainingFeeAmount: feeAmount, + totalEthAmount: constants.ZERO_AMOUNT, + remainingZrxBuyAmount: zrxBuyAmount, }, ); - return result.ethAmount; + return result.totalEthAmount; } function findEthAndZrxAmountNeededToBuyAsset( @@ -152,28 +183,25 @@ function findEthAndZrxAmountNeededToBuyAsset( const result = _.reduce( orders, (acc, order, index) => { + const { totalEthAmount, totalZrxAmount, remainingAssetBuyAmount } = acc; const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index]; - const amountToFill = BigNumber.min(acc.remainingAssetBuyAmount, remainingFillableMakerAssetAmount); - // find the amount of eth required to fill amountToFill (amountToFill / makerAssetAmount) * takerAssetAmount - const ethAmountForThisOrder = amountToFill - .mul(order.takerAssetAmount) - .dividedToIntegerBy(order.makerAssetAmount); - // find the amount of zrx required to fill fees for amountToFill (amountToFill / makerAssetAmount) * takerFee - const zrxAmountForThisOrder = amountToFill.mul(order.takerFee).dividedToIntegerBy(order.makerAssetAmount); + const makerFillAmount = BigNumber.min(acc.remainingAssetBuyAmount, remainingFillableMakerAssetAmount); + const takerFillAmount = orderUtils.getTakerFillAmount(order, makerFillAmount); + const takerFeeAmount = orderUtils.getTakerFeeAmount(order, takerFillAmount); return { - ethAmount: acc.ethAmount.plus(ethAmountForThisOrder), - zrxAmount: acc.zrxAmount.plus(zrxAmountForThisOrder), + totalEthAmount: totalEthAmount.plus(takerFillAmount), + totalZrxAmount: totalZrxAmount.plus(takerFeeAmount), remainingAssetBuyAmount: BigNumber.max( constants.ZERO_AMOUNT, - acc.remainingAssetBuyAmount.minus(amountToFill), + remainingAssetBuyAmount.minus(makerFillAmount), ), }; }, { - ethAmount: constants.ZERO_AMOUNT, - zrxAmount: constants.ZERO_AMOUNT, + totalEthAmount: constants.ZERO_AMOUNT, + totalZrxAmount: constants.ZERO_AMOUNT, remainingAssetBuyAmount: assetBuyAmount, }, ); - return [result.ethAmount, result.zrxAmount]; + return [result.totalEthAmount, result.totalZrxAmount]; } diff --git a/packages/asset-buyer/src/utils/order_provider_response_processor.ts b/packages/asset-buyer/src/utils/order_provider_response_processor.ts index 74eec162d..28f684f3c 100644 --- a/packages/asset-buyer/src/utils/order_provider_response_processor.ts +++ b/packages/asset-buyer/src/utils/order_provider_response_processor.ts @@ -1,8 +1,8 @@ -import { OrderAndTraderInfo, OrderStatus, OrderValidatorWrapper } from '@0xproject/contract-wrappers'; -import { sortingUtils } from '@0xproject/order-utils'; -import { RemainingFillableCalculator } from '@0xproject/order-utils/lib/src/remaining_fillable_calculator'; -import { SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +import { OrderAndTraderInfo, OrderStatus, OrderValidatorWrapper } from '@0x/contract-wrappers'; +import { sortingUtils } from '@0x/order-utils'; +import { RemainingFillableCalculator } from '@0x/order-utils/lib/src/remaining_fillable_calculator'; +import { SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { constants } from '../constants'; @@ -110,10 +110,7 @@ function getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain( traderInfo.makerZrxBalance, ]); const remainingTakerAssetAmount = order.takerAssetAmount.minus(orderInfo.orderTakerAssetFilledAmount); - const remainingMakerAssetAmount = orderUtils.calculateRemainingMakerAssetAmount( - order, - remainingTakerAssetAmount, - ); + const remainingMakerAssetAmount = orderUtils.getRemainingMakerAmount(order, remainingTakerAssetAmount); const remainingFillableCalculator = new RemainingFillableCalculator( order.makerFee, order.makerAssetAmount, diff --git a/packages/asset-buyer/src/utils/order_utils.ts b/packages/asset-buyer/src/utils/order_utils.ts index cfc13a8a1..1cc2cf95f 100644 --- a/packages/asset-buyer/src/utils/order_utils.ts +++ b/packages/asset-buyer/src/utils/order_utils.ts @@ -1,5 +1,5 @@ -import { SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +import { SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import { constants } from '../constants'; @@ -12,19 +12,63 @@ export const orderUtils = { const currentUnixTimestampSec = new BigNumber(Date.now() / millisecondsInSecond).round(); return order.expirationTimeSeconds.lessThan(currentUnixTimestampSec.plus(secondsFromNow)); }, - calculateRemainingMakerAssetAmount(order: SignedOrder, remainingTakerAssetAmount: BigNumber): BigNumber { - if (remainingTakerAssetAmount.eq(0)) { - return constants.ZERO_AMOUNT; - } - return remainingTakerAssetAmount.times(order.makerAssetAmount).dividedToIntegerBy(order.takerAssetAmount); - }, - calculateRemainingTakerAssetAmount(order: SignedOrder, remainingMakerAssetAmount: BigNumber): BigNumber { - if (remainingMakerAssetAmount.eq(0)) { - return constants.ZERO_AMOUNT; - } - return remainingMakerAssetAmount.times(order.takerAssetAmount).dividedToIntegerBy(order.makerAssetAmount); - }, isOpenOrder(order: SignedOrder): boolean { return order.takerAddress === constants.NULL_ADDRESS; }, + // given a remaining amount of takerAsset, calculate how much makerAsset is available + getRemainingMakerAmount(order: SignedOrder, remainingTakerAmount: BigNumber): BigNumber { + const remainingMakerAmount = remainingTakerAmount + .times(order.makerAssetAmount) + .div(order.takerAssetAmount) + .floor(); + return remainingMakerAmount; + }, + // given a desired amount of makerAsset, calculate how much takerAsset is required to fill that amount + getTakerFillAmount(order: SignedOrder, makerFillAmount: BigNumber): BigNumber { + // Round up because exchange rate favors Maker + const takerFillAmount = makerFillAmount + .mul(order.takerAssetAmount) + .div(order.makerAssetAmount) + .ceil(); + return takerFillAmount; + }, + // given a desired amount of takerAsset to fill, calculate how much fee is required by the taker to fill that amount + getTakerFeeAmount(order: SignedOrder, takerFillAmount: BigNumber): BigNumber { + // Round down because Taker fee rate favors Taker + const takerFeeAmount = takerFillAmount + .mul(order.takerFee) + .div(order.takerAssetAmount) + .floor(); + return takerFeeAmount; + }, + // given a desired amount of takerAsset to fill, calculate how much makerAsset will be filled + getMakerFillAmount(order: SignedOrder, takerFillAmount: BigNumber): BigNumber { + // Round down because exchange rate favors Maker + const makerFillAmount = takerFillAmount + .mul(order.makerAssetAmount) + .div(order.takerAssetAmount) + .floor(); + return makerFillAmount; + }, + // given a desired amount of makerAsset, calculate how much fee is required by the maker to fill that amount + getMakerFeeAmount(order: SignedOrder, makerFillAmount: BigNumber): BigNumber { + // Round down because Maker fee rate favors Maker + const makerFeeAmount = makerFillAmount + .mul(order.makerFee) + .div(order.makerAssetAmount) + .floor(); + return makerFeeAmount; + }, + // given a desired amount of ZRX from a fee order, calculate how much takerAsset is required to fill that amount + // also calculate how much ZRX needs to be bought in order fill the desired amount + takerFee + getTakerFillAmountForFeeOrder(order: SignedOrder, makerFillAmount: BigNumber): [BigNumber, BigNumber] { + // For each unit of TakerAsset we buy (MakerAsset - TakerFee) + const adjustedTakerFillAmount = makerFillAmount + .mul(order.takerAssetAmount) + .div(order.makerAssetAmount.sub(order.takerFee)) + .ceil(); + // The amount that we buy will be greater than makerFillAmount, since we buy some amount for fees. + const adjustedMakerFillAmount = orderUtils.getMakerFillAmount(order, adjustedTakerFillAmount); + return [adjustedTakerFillAmount, adjustedMakerFillAmount]; + }, }; diff --git a/packages/asset-buyer/test/buy_quote_calculator_test.ts b/packages/asset-buyer/test/buy_quote_calculator_test.ts index fda6958cd..0ea371982 100644 --- a/packages/asset-buyer/test/buy_quote_calculator_test.ts +++ b/packages/asset-buyer/test/buy_quote_calculator_test.ts @@ -1,5 +1,5 @@ -import { orderFactory } from '@0xproject/order-utils/lib/src/order_factory'; -import { BigNumber } from '@0xproject/utils'; +import { orderFactory } from '@0x/order-utils/lib/src/order_factory'; +import { BigNumber } from '@0x/utils'; import * as chai from 'chai'; import * as _ from 'lodash'; import 'mocha'; @@ -49,9 +49,9 @@ describe('buyQuoteCalculator', () => { remainingFillableMakerAssetAmounts: [smallFeeOrder.makerAssetAmount], }; const largeFeeOrder = orderFactory.createSignedOrderFromPartial({ - makerAssetAmount: new BigNumber(110), + makerAssetAmount: new BigNumber(113), takerAssetAmount: new BigNumber(200), - takerFee: new BigNumber(10), + takerFee: new BigNumber(11), }); allFeeOrdersAndFillableAmounts = { orders: [smallFeeOrder, largeFeeOrder], @@ -70,6 +70,7 @@ describe('buyQuoteCalculator', () => { new BigNumber(500), 0, 0, + false, ), ).to.throw(AssetBuyerError.InsufficientAssetLiquidity); }); @@ -82,6 +83,7 @@ describe('buyQuoteCalculator', () => { new BigNumber(300), 0, 0, + false, ), ).to.throw(AssetBuyerError.InsufficientZrxLiquidity); }); @@ -97,6 +99,7 @@ describe('buyQuoteCalculator', () => { assetBuyAmount, feePercentage, slippagePercentage, + false, ); // test if orders are correct expect(buyQuote.orders).to.deep.equal([ordersAndFillableAmounts.orders[0]]); @@ -134,6 +137,7 @@ describe('buyQuoteCalculator', () => { assetBuyAmount, feePercentage, slippagePercentage, + false, ); // test if orders are correct expect(buyQuote.orders).to.deep.equal(ordersAndFillableAmounts.orders); @@ -149,9 +153,9 @@ describe('buyQuoteCalculator', () => { expect(buyQuote.bestCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedFeeEthAmount); expect(buyQuote.bestCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedTotalEthAmount); expect(buyQuote.bestCaseQuoteInfo.ethPerAssetPrice).to.bignumber.equal(expectedEthPerAssetPrice); - // 100 eth to fill the first order + 200 eth for fees + // 100 eth to fill the first order + 208 eth for fees const expectedWorstEthAmountForAsset = new BigNumber(100); - const expectedWorstEthAmountForZrxFees = new BigNumber(200); + const expectedWorstEthAmountForZrxFees = new BigNumber(208); const expectedWorstFillEthAmount = expectedWorstEthAmountForAsset.plus(expectedWorstEthAmountForZrxFees); const expectedWorstFeeEthAmount = expectedWorstEthAmountForAsset.mul(feePercentage); const expectedWorstTotalEthAmount = expectedWorstFillEthAmount.plus(expectedWorstFeeEthAmount); diff --git a/packages/asset-buyer/tslint.json b/packages/asset-buyer/tslint.json index ffaefe83a..dd9053357 100644 --- a/packages/asset-buyer/tslint.json +++ b/packages/asset-buyer/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0x/tslint-config"] } |