Merge pull request #997 from 0xProject/feature/forwarder-helper/init
[forwarder-helper] Initial scaffolding for the forwarder-helper package
This commit is contained in:
8
packages/forwarder-helper/.npmignore
Normal file
8
packages/forwarder-helper/.npmignore
Normal file
@@ -0,0 +1,8 @@
|
||||
.*
|
||||
yarn-error.log
|
||||
/src/
|
||||
/scripts/
|
||||
/schemas/
|
||||
test/
|
||||
tsconfig.json
|
||||
/lib/src/monorepo_scripts/
|
||||
11
packages/forwarder-helper/CHANGELOG.json
Normal file
11
packages/forwarder-helper/CHANGELOG.json
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"version": "1.0.1-rc.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add initial forwarderHelperFactory",
|
||||
"pr": 997
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
83
packages/forwarder-helper/README.md
Normal file
83
packages/forwarder-helper/README.md
Normal file
@@ -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
|
||||
```
|
||||
74
packages/forwarder-helper/package.json
Normal file
74
packages/forwarder-helper/package.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"name": "@0xproject/forwarder-helper",
|
||||
"version": "1.0.0-rc.1",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
"description": "Convenience object for working with the forwarder contract",
|
||||
"main": "lib/src/index.js",
|
||||
"types": "lib/src/index.d.ts",
|
||||
"scripts": {
|
||||
"watch_without_deps": "tsc -w",
|
||||
"lint": "tslint --project .",
|
||||
"test": "yarn run_mocha",
|
||||
"rebuild_and_test": "run-s clean build test",
|
||||
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
|
||||
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
|
||||
"test:circleci": "yarn test:coverage",
|
||||
"run_mocha":
|
||||
"mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit",
|
||||
"clean": "shx rm -rf lib test_temp scripts",
|
||||
"build": "tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
|
||||
"manual:postpublish": "yarn build; node ./scripts/postpublish.js",
|
||||
"docs:stage": "node scripts/stage_docs.js",
|
||||
"docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES",
|
||||
"upload_docs_json":
|
||||
"aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json"
|
||||
},
|
||||
"config": {
|
||||
"postpublish": {
|
||||
"assets": []
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/0xProject/0x-monorepo.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/0xProject/0x-monorepo/issues"
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/packages/forwarder-helper/README.md",
|
||||
"dependencies": {
|
||||
"@0xproject/assert": "^1.0.5",
|
||||
"@0xproject/json-schemas": "^1.0.1-rc.4",
|
||||
"@0xproject/order-utils": "^1.0.1-rc.3",
|
||||
"@0xproject/types": "^1.0.1-rc.4",
|
||||
"@0xproject/typescript-typings": "^1.0.4",
|
||||
"@0xproject/utils": "^1.0.5",
|
||||
"@types/node": "^8.0.53",
|
||||
"lodash": "^4.17.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@0xproject/tslint-config": "^1.0.5",
|
||||
"@types/lodash": "^4.14.116",
|
||||
"@types/mocha": "^2.2.42",
|
||||
"chai": "^4.0.1",
|
||||
"chai-as-promised": "^7.1.0",
|
||||
"chai-bignumber": "^2.0.1",
|
||||
"copyfiles": "^1.2.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"make-promises-safe": "^1.1.0",
|
||||
"mocha": "^4.1.0",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"nyc": "^11.0.1",
|
||||
"shx": "^0.2.2",
|
||||
"tslint": "5.11.0",
|
||||
"typedoc": "0xProject/typedoc",
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
5
packages/forwarder-helper/src/constants.ts
Normal file
5
packages/forwarder-helper/src/constants.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
|
||||
export const constants = {
|
||||
ZERO_AMOUNT: new BigNumber(0),
|
||||
};
|
||||
25
packages/forwarder-helper/src/forwarder_helper_factory.ts
Normal file
25
packages/forwarder-helper/src/forwarder_helper_factory.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { assert } from '@0xproject/assert';
|
||||
import { schemas } from '@0xproject/json-schemas';
|
||||
import { SignedOrder } from '@0xproject/types';
|
||||
|
||||
import { ForwarderHelperImpl, ForwarderHelperImplConfig } from './forwarder_helper_impl';
|
||||
import { ForwarderHelper } from './types';
|
||||
|
||||
export const forwarderHelperFactory = {
|
||||
/**
|
||||
* Given an array of orders and an array of feeOrders
|
||||
* @param orders An array of objects conforming to SignedOrder. Each order should specify the same makerAssetData and takerAssetData
|
||||
* @param feeOrders An array of objects conforming to SignedOrder. Each order should specify ZRX as makerAssetData WETH as takerAssetData
|
||||
* @return A ForwarderHelper, see type for definition
|
||||
*/
|
||||
getForwarderHelperForOrders(orders: SignedOrder[], feeOrders: SignedOrder[] = []): ForwarderHelper {
|
||||
assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema);
|
||||
assert.doesConformToSchema('feeOrders', orders, schemas.signedOrdersSchema);
|
||||
const config: ForwarderHelperImplConfig = {
|
||||
orders,
|
||||
feeOrders,
|
||||
};
|
||||
const helper = new ForwarderHelperImpl(config);
|
||||
return helper;
|
||||
},
|
||||
};
|
||||
64
packages/forwarder-helper/src/forwarder_helper_impl.ts
Normal file
64
packages/forwarder-helper/src/forwarder_helper_impl.ts
Normal file
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
6
packages/forwarder-helper/src/globals.d.ts
vendored
Normal file
6
packages/forwarder-helper/src/globals.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
declare module '*.json' {
|
||||
const json: any;
|
||||
/* tslint:disable */
|
||||
export default json;
|
||||
/* tslint:enable */
|
||||
}
|
||||
2
packages/forwarder-helper/src/index.ts
Normal file
2
packages/forwarder-helper/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { forwarderHelperFactory } from './forwarder_helper_factory';
|
||||
export { ForwarderHelper, ForwarderHelperError, MarketBuyOrdersInfoRequest, MarketBuyOrdersInfo } from './types';
|
||||
43
packages/forwarder-helper/src/types.ts
Normal file
43
packages/forwarder-helper/src/types.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { SignedOrder } from '@0xproject/types';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
|
||||
export interface ForwarderHelper {
|
||||
/**
|
||||
* Given a MarketBuyOrdersInfoRequest, returns a MarketBuyOrdersInfo containing all information relevant to fulfilling the request
|
||||
* using the ForwarderContract marketBuyOrdersWithEth function.
|
||||
* @param request An object that conforms to MarketBuyOrdersInfoRequest. See type definition for more information.
|
||||
* @return An object that conforms to MarketBuyOrdersInfo that satisfies the request. See type definition for more information.
|
||||
*/
|
||||
getMarketBuyOrdersInfo: (request: MarketBuyOrdersInfoRequest) => MarketBuyOrdersInfo;
|
||||
}
|
||||
|
||||
export enum ForwarderHelperError {
|
||||
InsufficientMakerAssetLiquidity = 'INSUFFICIENT_MAKER_ASSET_LIQUIDITY',
|
||||
InsufficientZrxLiquidity = 'INSUFFICIENT_ZRX_LIQUIDITY',
|
||||
}
|
||||
|
||||
/**
|
||||
* makerAssetFillAmount: The amount of makerAsset requesting to be filled
|
||||
* feePercentage: Optional affiliate percentage amount factoring into eth amount calculations
|
||||
*/
|
||||
export interface MarketBuyOrdersInfoRequest {
|
||||
makerAssetFillAmount: BigNumber;
|
||||
feePercentage?: BigNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* makerAssetFillAmount: The amount of makerAsset requesting to be filled
|
||||
* orders: An array of objects conforming to SignedOrder. These orders can be used to cover the requested makerAssetFillAmount plus slippage
|
||||
* feeOrders: An array of objects conforming to SignedOrder. These orders can be used to cover the fees for the orders param above
|
||||
* minEthAmount: Amount of eth in wei to send with the tx for the most optimistic case
|
||||
* maxEthAmount: Amount of eth in wei to send with the tx for the worst case
|
||||
* feePercentage: Affiliate fee percentage used to calculate the eth amounts above. Passed thru directly from the request
|
||||
*/
|
||||
export interface MarketBuyOrdersInfo {
|
||||
makerAssetFillAmount: BigNumber;
|
||||
orders: SignedOrder[];
|
||||
feeOrders: SignedOrder[];
|
||||
minEthAmount: BigNumber;
|
||||
maxEthAmount: BigNumber;
|
||||
feePercentage?: BigNumber;
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
136
packages/forwarder-helper/test/forwarder_helper_impl_test.ts
Normal file
136
packages/forwarder-helper/test/forwarder_helper_impl_test.ts
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
13
packages/forwarder-helper/test/utils/chai_setup.ts
Normal file
13
packages/forwarder-helper/test/utils/chai_setup.ts
Normal file
@@ -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);
|
||||
},
|
||||
};
|
||||
7
packages/forwarder-helper/tsconfig.json
Normal file
7
packages/forwarder-helper/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["./src/**/*", "./test/**/*"]
|
||||
}
|
||||
3
packages/forwarder-helper/tslint.json
Normal file
3
packages/forwarder-helper/tslint.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["@0xproject/tslint-config"]
|
||||
}
|
||||
@@ -24,6 +24,20 @@
|
||||
"note":
|
||||
"Export types: `SignedOrder`, `Order`, `OrderRelevantState`, `OrderState`, `ECSignature`, `ERC20AssetData`, `ERC721AssetData`, `AssetProxyId`, `SignerType`, `SignatureType`, `OrderStateValid`, `OrderStateInvalid`, `ExchangeContractErrs`, `TradeSide`, `TransferType`, `FindFeeOrdersThatCoverFeesForTargetOrdersOpts`, `FindOrdersThatCoverMakerAssetFillAmountOpts`, `FeeOrdersAndRemainingFeeAmount`, `OrdersAndRemainingFillAmount`, `Provider`, `JSONRPCRequestPayload`, `JSONRPCErrorCallback` and `JSONRPCResponsePayload`",
|
||||
"pr": 924
|
||||
},
|
||||
{
|
||||
"note":
|
||||
"Rename `resultOrders` to `resultFeeOrders` for object returned by `findFeeOrdersThatCoverFeesForTargetOrders` in `marketUtils` api",
|
||||
"pr": 997
|
||||
},
|
||||
{
|
||||
"note": "Make `sortFeeOrdersByFeeAdjustedRate` in `sortingUtils` generic",
|
||||
"pr": 997
|
||||
},
|
||||
{
|
||||
"note":
|
||||
"Update `findFeeOrdersThatCoverFeesForTargetOrders` to round the the nearest integer when calculating required fees",
|
||||
"pr": 997
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -128,7 +128,7 @@ export const marketUtils = {
|
||||
const makerAssetAmountAvailable = remainingFillableMakerAssetAmounts[index];
|
||||
const feeToFillMakerAssetAmountAvailable = makerAssetAmountAvailable
|
||||
.mul(order.takerFee)
|
||||
.div(order.makerAssetAmount);
|
||||
.dividedToIntegerBy(order.makerAssetAmount);
|
||||
return accFees.plus(feeToFillMakerAssetAmountAvailable);
|
||||
},
|
||||
constants.ZERO_AMOUNT,
|
||||
@@ -142,7 +142,7 @@ export const marketUtils = {
|
||||
},
|
||||
);
|
||||
return {
|
||||
resultOrders,
|
||||
resultFeeOrders: resultOrders,
|
||||
remainingFeeAmount: remainingFillAmount,
|
||||
};
|
||||
// TODO: add more orders here to cover rounding
|
||||
|
||||
@@ -32,7 +32,7 @@ export const sortingUtils = {
|
||||
* the makerAsset and WETH as the takerAsset.
|
||||
* @return The input orders sorted by rate in ascending order
|
||||
*/
|
||||
sortFeeOrdersByFeeAdjustedRate(feeOrders: Order[]): Order[] {
|
||||
sortFeeOrdersByFeeAdjustedRate<T extends Order>(feeOrders: T[]): T[] {
|
||||
assert.doesConformToSchema('feeOrders', feeOrders, schemas.ordersSchema);
|
||||
const rateCalculator = rateUtils.getFeeAdjustedRateOfFeeOrder.bind(rateUtils);
|
||||
const sortedOrders = sortOrders(feeOrders, rateCalculator);
|
||||
|
||||
@@ -71,7 +71,7 @@ export interface FindFeeOrdersThatCoverFeesForTargetOrdersOpts {
|
||||
}
|
||||
|
||||
export interface FeeOrdersAndRemainingFeeAmount<T> {
|
||||
resultOrders: T[];
|
||||
resultFeeOrders: T[];
|
||||
remainingFeeAmount: BigNumber;
|
||||
}
|
||||
|
||||
|
||||
@@ -140,11 +140,11 @@ describe('marketUtils', () => {
|
||||
);
|
||||
describe('no target orders', () => {
|
||||
it('returns empty and zero remainingFeeAmount', async () => {
|
||||
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
|
||||
const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
|
||||
[],
|
||||
inputFeeOrders,
|
||||
);
|
||||
expect(resultOrders).to.be.empty;
|
||||
expect(resultFeeOrders).to.be.empty;
|
||||
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
});
|
||||
@@ -163,14 +163,14 @@ describe('marketUtils', () => {
|
||||
// generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount
|
||||
const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount];
|
||||
it('returns empty and non-zero remainingFeeAmount', async () => {
|
||||
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
|
||||
const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
|
||||
inputOrders,
|
||||
[],
|
||||
{
|
||||
remainingFillableMakerAssetAmounts,
|
||||
},
|
||||
);
|
||||
expect(resultOrders).to.be.empty;
|
||||
expect(resultFeeOrders).to.be.empty;
|
||||
expect(remainingFeeAmount).to.be.bignumber.equal(new BigNumber(30));
|
||||
});
|
||||
});
|
||||
@@ -184,11 +184,11 @@ describe('marketUtils', () => {
|
||||
3,
|
||||
);
|
||||
it('returns empty and zero remainingFeeAmount', async () => {
|
||||
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
|
||||
const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
|
||||
inputOrders,
|
||||
inputFeeOrders,
|
||||
);
|
||||
expect(resultOrders).to.be.empty;
|
||||
expect(resultFeeOrders).to.be.empty;
|
||||
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
});
|
||||
@@ -205,11 +205,11 @@ describe('marketUtils', () => {
|
||||
3,
|
||||
);
|
||||
it('returns input fee orders and zero remainingFeeAmount', async () => {
|
||||
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
|
||||
const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
|
||||
inputOrders,
|
||||
inputFeeOrders,
|
||||
);
|
||||
expect(resultOrders).to.be.deep.equal(inputFeeOrders);
|
||||
expect(resultFeeOrders).to.be.deep.equal(inputFeeOrders);
|
||||
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
});
|
||||
@@ -231,14 +231,14 @@ describe('marketUtils', () => {
|
||||
// 3. order is completely fillable
|
||||
const remainingFillableMakerAssetAmounts = [constants.ZERO_AMOUNT, new BigNumber(5), makerAssetAmount];
|
||||
it('returns first two input fee orders and zero remainingFeeAmount', async () => {
|
||||
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
|
||||
const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
|
||||
inputOrders,
|
||||
inputFeeOrders,
|
||||
{
|
||||
remainingFillableMakerAssetAmounts,
|
||||
},
|
||||
);
|
||||
expect(resultOrders).to.be.deep.equal([inputFeeOrders[0], inputFeeOrders[1]]);
|
||||
expect(resultFeeOrders).to.be.deep.equal([inputFeeOrders[0], inputFeeOrders[1]]);
|
||||
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
});
|
||||
@@ -255,11 +255,11 @@ describe('marketUtils', () => {
|
||||
3,
|
||||
);
|
||||
it('returns input fee orders and non-zero remainingFeeAmount', async () => {
|
||||
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
|
||||
const { resultFeeOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
|
||||
inputOrders,
|
||||
inputFeeOrders,
|
||||
);
|
||||
expect(resultOrders).to.be.deep.equal(inputFeeOrders);
|
||||
expect(resultFeeOrders).to.be.deep.equal(inputFeeOrders);
|
||||
expect(remainingFeeAmount).to.be.bignumber.equal(new BigNumber(30));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user