diff options
author | Fabio Berger <me@fabioberger.com> | 2018-08-14 04:01:32 +0800 |
---|---|---|
committer | Fabio Berger <me@fabioberger.com> | 2018-08-14 04:01:32 +0800 |
commit | 9d3c287918389d07f884245bd1bc968955768b6f (patch) | |
tree | 460fded537c7d64154972b7d14332f88554d14c0 /packages/order-utils/src | |
parent | c2b5fe3d844d35966c5498326000bd8317fb547c (diff) | |
parent | 15e15f994a1b18cf2e9be151194c826d53a01601 (diff) | |
download | dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar.gz dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar.bz2 dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar.lz dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar.xz dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar.zst dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.zip |
Merge branch 'sol-cov-fixes' of github.com:0xProject/0x-monorepo into sol-cov-fixes
* 'sol-cov-fixes' of github.com:0xProject/0x-monorepo: (49 commits)
Add @return comments
Import marshaller directly
Update comment about ethers checksummed address behavior
Add packages/coverage/.gitkeep file
Update CI config and package.json to run @0xproject/utils tests on CI
Update remaining CHANGELOG.json files
Change amir picture
Update CHANGELOG.json for contract-wrappers
Update ethers typings for TypeScript 2.9.2
Update CHANGELOG.json for base-contract
Move some ethers-related types to typescript-typings/ethers
Apply prettier
Add strictArgumentEncodingCheck to BaseContract and use it in contract templates
fix(monorepo-scripts): Fix typo in git tag command
feat(monorepo-scripts): Add confirmation prompt before publishing
fix comments and styling for MixinSignatureValidator
Update TypeScript to version 2.9.2
Use asm for hashEIP712Message, increment free memory pointer after asm hashing functions
Fix comments, styling, and optimize hashOrder
Remove assertion comments
...
Diffstat (limited to 'packages/order-utils/src')
-rw-r--r-- | packages/order-utils/src/constants.ts | 3 | ||||
-rw-r--r-- | packages/order-utils/src/index.ts | 11 | ||||
-rw-r--r-- | packages/order-utils/src/market_utils.ts | 133 | ||||
-rw-r--r-- | packages/order-utils/src/order_factory.ts | 78 | ||||
-rw-r--r-- | packages/order-utils/src/types.ts | 12 |
5 files changed, 214 insertions, 23 deletions
diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts index bb7482184..c23578c20 100644 --- a/packages/order-utils/src/constants.ts +++ b/packages/order-utils/src/constants.ts @@ -2,6 +2,7 @@ import { BigNumber } from '@0xproject/utils'; export const constants = { NULL_ADDRESS: '0x0000000000000000000000000000000000000000', + NULL_BYTES: '0x', // tslint:disable-next-line:custom-no-magic-numbers UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1), TESTRPC_NETWORK_ID: 50, @@ -10,4 +11,6 @@ export const constants = { ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH: 53, SELECTOR_LENGTH: 4, BASE_16: 16, + INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite + ZERO_AMOUNT: new BigNumber(0), }; diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts index 76be63bb8..858f500c6 100644 --- a/packages/order-utils/src/index.ts +++ b/packages/order-utils/src/index.ts @@ -13,7 +13,15 @@ export { orderFactory } from './order_factory'; export { constants } from './constants'; export { crypto } from './crypto'; export { generatePseudoRandomSalt } from './salt'; -export { OrderError, MessagePrefixType, MessagePrefixOpts, EIP712Parameter, EIP712Schema, EIP712Types } from './types'; +export { + CreateOrderOpts, + OrderError, + MessagePrefixType, + MessagePrefixOpts, + EIP712Parameter, + EIP712Schema, + EIP712Types, +} from './types'; export { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher'; export { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher'; export { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store'; @@ -24,3 +32,4 @@ export { assetDataUtils } from './asset_data_utils'; export { EIP712Utils } from './eip712_utils'; export { OrderValidationUtils } from './order_validation_utils'; export { ExchangeTransferSimulator } from './exchange_transfer_simulator'; +export { marketUtils } from './market_utils'; diff --git a/packages/order-utils/src/market_utils.ts b/packages/order-utils/src/market_utils.ts new file mode 100644 index 000000000..681059ddf --- /dev/null +++ b/packages/order-utils/src/market_utils.ts @@ -0,0 +1,133 @@ +import { schemas } from '@0xproject/json-schemas'; +import { SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import * as _ from 'lodash'; + +import { assert } from './assert'; +import { constants } from './constants'; + +export const marketUtils = { + /** + * Takes an array of orders and returns a subset of those orders that has enough makerAssetAmount (taking into account on-chain balances, + * allowances, and partial fills) in order to fill the input makerAssetFillAmount plus slippageBufferAmount. Iterates from first order to last. + * Sort the input by ascending rate in order to get the subset of orders that will cost the least ETH. + * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify the same makerAsset. + * All orders should specify WETH as the takerAsset. + * @param remainingFillableMakerAssetAmounts An array of BigNumbers corresponding to the signedOrders parameter. + * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups + * for these values. + * @param makerAssetFillAmount The amount of makerAsset desired to be filled. + * @param slippageBufferAmount An additional amount of makerAsset to be covered by the result in case of trade collisions or partial fills. + * @return Resulting orders and remaining fill amount that could not be covered by the input. + */ + findOrdersThatCoverMakerAssetFillAmount( + signedOrders: SignedOrder[], + remainingFillableMakerAssetAmounts: BigNumber[], + makerAssetFillAmount: BigNumber, + slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, + ): { resultOrders: SignedOrder[]; remainingFillAmount: BigNumber } { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + _.forEach(remainingFillableMakerAssetAmounts, (amount, index) => + assert.isValidBaseUnitAmount(`remainingFillableMakerAssetAmount[${index}]`, amount), + ); + assert.isValidBaseUnitAmount('makerAssetFillAmount', makerAssetFillAmount); + assert.isValidBaseUnitAmount('slippageBufferAmount', slippageBufferAmount); + assert.assert( + signedOrders.length === remainingFillableMakerAssetAmounts.length, + 'Expected signedOrders.length to equal remainingFillableMakerAssetAmounts.length', + ); + // calculate total amount of makerAsset needed to be filled + const totalFillAmount = makerAssetFillAmount.plus(slippageBufferAmount); + // iterate through the signedOrders input from left to right until we have enough makerAsset to fill totalFillAmount + const result = _.reduce( + signedOrders, + ({ resultOrders, remainingFillAmount }, order, index) => { + if (remainingFillAmount.lessThanOrEqualTo(constants.ZERO_AMOUNT)) { + return { resultOrders, remainingFillAmount: constants.ZERO_AMOUNT }; + } else { + const makerAssetAmountAvailable = remainingFillableMakerAssetAmounts[index]; + // if there is no makerAssetAmountAvailable do not append order to resultOrders + // if we have exceeded the total amount we want to fill set remainingFillAmount to 0 + return { + resultOrders: makerAssetAmountAvailable.gt(constants.ZERO_AMOUNT) + ? _.concat(resultOrders, order) + : resultOrders, + remainingFillAmount: BigNumber.max( + constants.ZERO_AMOUNT, + remainingFillAmount.minus(makerAssetAmountAvailable), + ), + }; + } + }, + { resultOrders: [] as SignedOrder[], remainingFillAmount: totalFillAmount }, + ); + return result; + }, + /** + * Takes an array of orders and an array of feeOrders. Returns a subset of the feeOrders that has enough ZRX (taking into account + * on-chain balances, allowances, and partial fills) in order to fill the takerFees required by signedOrders plus a + * slippageBufferAmount. Iterates from first feeOrder to last. Sort the feeOrders by ascending rate in order to get the subset of + * feeOrders that will cost the least ETH. + * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as + * the makerAsset and WETH as the takerAsset. + * @param remainingFillableMakerAssetAmounts An array of BigNumbers corresponding to the signedOrders parameter. + * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups + * for these values. + * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as + * the makerAsset and WETH as the takerAsset. + * @param remainingFillableFeeAmounts An array of BigNumbers corresponding to the signedFeeOrders parameter. + * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups + * for these values. + * @param slippageBufferAmount An additional amount of fee to be covered by the result in case of trade collisions or partial fills. + * @return Resulting orders and remaining fee amount that could not be covered by the input. + */ + findFeeOrdersThatCoverFeesForTargetOrders( + signedOrders: SignedOrder[], + remainingFillableMakerAssetAmounts: BigNumber[], + signedFeeOrders: SignedOrder[], + remainingFillableFeeAmounts: BigNumber[], + slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, + ): { resultOrders: SignedOrder[]; remainingFeeAmount: BigNumber } { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + _.forEach(remainingFillableMakerAssetAmounts, (amount, index) => + assert.isValidBaseUnitAmount(`remainingFillableMakerAssetAmount[${index}]`, amount), + ); + assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema); + _.forEach(remainingFillableFeeAmounts, (amount, index) => + assert.isValidBaseUnitAmount(`remainingFillableFeeAmounts[${index}]`, amount), + ); + assert.isValidBaseUnitAmount('slippageBufferAmount', slippageBufferAmount); + assert.assert( + signedOrders.length === remainingFillableMakerAssetAmounts.length, + 'Expected signedOrders.length to equal remainingFillableMakerAssetAmounts.length', + ); + assert.assert( + signedOrders.length === remainingFillableMakerAssetAmounts.length, + 'Expected signedFeeOrders.length to equal remainingFillableFeeAmounts.length', + ); + // calculate total amount of ZRX needed to fill signedOrders + const totalFeeAmount = _.reduce( + signedOrders, + (accFees, order, index) => { + const makerAssetAmountAvailable = remainingFillableMakerAssetAmounts[index]; + const feeToFillMakerAssetAmountAvailable = makerAssetAmountAvailable + .mul(order.takerFee) + .div(order.makerAssetAmount); + return accFees.plus(feeToFillMakerAssetAmountAvailable); + }, + constants.ZERO_AMOUNT, + ); + const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( + signedFeeOrders, + remainingFillableFeeAmounts, + totalFeeAmount, + slippageBufferAmount, + ); + return { + resultOrders, + remainingFeeAmount: remainingFillAmount, + }; + // TODO: add more orders here to cover rounding + // https://github.com/0xProject/0x-protocol-specification/blob/master/v2/forwarding-contract-specification.md#over-buying-zrx + }, +}; diff --git a/packages/order-utils/src/order_factory.ts b/packages/order-utils/src/order_factory.ts index 803cb82b1..14727fd97 100644 --- a/packages/order-utils/src/order_factory.ts +++ b/packages/order-utils/src/order_factory.ts @@ -1,49 +1,63 @@ -import { ECSignature, SignedOrder } from '@0xproject/types'; +import { ECSignature, Order, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Provider } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; +import { constants } from './constants'; import { orderHashUtils } from './order_hash'; import { generatePseudoRandomSalt } from './salt'; import { ecSignOrderHashAsync } from './signature_utils'; -import { MessagePrefixType } from './types'; +import { CreateOrderOpts, MessagePrefixType } from './types'; export const orderFactory = { - async createSignedOrderAsync( - provider: Provider, + createOrder( makerAddress: string, - takerAddress: string, - senderAddress: string, - makerFee: BigNumber, - takerFee: BigNumber, makerAssetAmount: BigNumber, makerAssetData: string, takerAssetAmount: BigNumber, takerAssetData: string, exchangeAddress: string, - feeRecipientAddress: string, - expirationTimeSecondsIfExists?: BigNumber, - ): Promise<SignedOrder> { - const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite - const expirationTimeSeconds = _.isUndefined(expirationTimeSecondsIfExists) - ? defaultExpirationUnixTimestampSec - : expirationTimeSecondsIfExists; + createOrderOpts: CreateOrderOpts = generateDefaultCreateOrderOpts(), + ): Order { + const defaultCreateOrderOpts = generateDefaultCreateOrderOpts(); const order = { makerAddress, - takerAddress, - senderAddress, - makerFee, - takerFee, makerAssetAmount, takerAssetAmount, makerAssetData, takerAssetData, - salt: generatePseudoRandomSalt(), exchangeAddress, - feeRecipientAddress, - expirationTimeSeconds, + takerAddress: createOrderOpts.takerAddress || defaultCreateOrderOpts.takerAddress, + senderAddress: createOrderOpts.senderAddress || defaultCreateOrderOpts.senderAddress, + makerFee: createOrderOpts.makerFee || defaultCreateOrderOpts.makerFee, + takerFee: createOrderOpts.takerFee || defaultCreateOrderOpts.takerFee, + feeRecipientAddress: createOrderOpts.feeRecipientAddress || defaultCreateOrderOpts.feeRecipientAddress, + salt: createOrderOpts.salt || defaultCreateOrderOpts.salt, + expirationTimeSeconds: + createOrderOpts.expirationTimeSeconds || defaultCreateOrderOpts.expirationTimeSeconds, }; + return order; + }, + async createSignedOrderAsync( + provider: Provider, + makerAddress: string, + makerAssetAmount: BigNumber, + makerAssetData: string, + takerAssetAmount: BigNumber, + takerAssetData: string, + exchangeAddress: string, + createOrderOpts?: CreateOrderOpts, + ): Promise<SignedOrder> { + const order = orderFactory.createOrder( + makerAddress, + makerAssetAmount, + makerAssetData, + takerAssetAmount, + takerAssetData, + exchangeAddress, + createOrderOpts, + ); const orderHash = orderHashUtils.getOrderHashHex(order); const messagePrefixOpts = { prefixType: MessagePrefixType.EthSign, @@ -56,6 +70,26 @@ export const orderFactory = { }, }; +function generateDefaultCreateOrderOpts(): { + takerAddress: string; + senderAddress: string; + makerFee: BigNumber; + takerFee: BigNumber; + feeRecipientAddress: string; + salt: BigNumber; + expirationTimeSeconds: BigNumber; +} { + return { + takerAddress: constants.NULL_ADDRESS, + senderAddress: constants.NULL_ADDRESS, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + feeRecipientAddress: constants.NULL_ADDRESS, + salt: generatePseudoRandomSalt(), + expirationTimeSeconds: constants.INFINITE_TIMESTAMP_SEC, + }; +} + function getVRSHexString(ecSignature: ECSignature): string { const ETH_SIGN_SIGNATURE_TYPE = '03'; const vrs = `${intToHex(ecSignature.v)}${ethUtil.stripHexPrefix(ecSignature.r)}${ethUtil.stripHexPrefix( diff --git a/packages/order-utils/src/types.ts b/packages/order-utils/src/types.ts index b08e74e71..f44e94349 100644 --- a/packages/order-utils/src/types.ts +++ b/packages/order-utils/src/types.ts @@ -1,3 +1,5 @@ +import { BigNumber } from '@0xproject/utils'; + export enum OrderError { InvalidSignature = 'INVALID_SIGNATURE', } @@ -51,3 +53,13 @@ export enum EIP712Types { String = 'string', Uint256 = 'uint256', } + +export interface CreateOrderOpts { + takerAddress?: string; + senderAddress?: string; + makerFee?: BigNumber; + takerFee?: BigNumber; + feeRecipientAddress?: string; + salt?: BigNumber; + expirationTimeSeconds?: BigNumber; +} |