diff options
Diffstat (limited to 'packages/order-utils')
50 files changed, 1443 insertions, 781 deletions
diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index 86f0da65a..22af2412b 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -1,5 +1,112 @@ [ { + "version": "2.0.0", + "changes": [ + { + "note": + "Added `ecSignOrderAsync` to first sign an order using `eth_signTypedData` and fallback to `eth_sign`.", + "pr": 1102 + }, + { + "note": "Added `ecSignTypedDataOrderAsync` to sign an order exclusively using `eth_signTypedData`.", + "pr": 1102 + }, + { + "note": "Rename `ecSignOrderHashAsync` to `ecSignHashAsync` removing `SignerType` parameter.", + "pr": 1102 + }, + { + "note": "Use `AssetData` union type for function return values.", + "pr": 1131 + } + ], + "timestamp": 1539871071 + }, + { + "version": "1.0.7", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1538693146 + }, + { + "version": "1.0.6", + "changes": [ + { + "note": + "Add signerAddress normalization to `isValidECSignature` to avoid `invalid address recovery` error if caller supplies a checksummed address", + "pr": 1096 + } + ], + "timestamp": 1538157789 + }, + { + "timestamp": 1537907159, + "version": "1.0.5", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1537875740, + "version": "1.0.4", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1537541580, + "version": "1.0.3", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "1.0.2", + "changes": [ + { + "note": "Drastically reduce the bundle size by removing unused parts of included contract artifacts." + } + ], + "timestamp": 1537369748 + }, + { + "version": "1.0.1", + "changes": [ + { + "note": "Export `orderParsingUtils`", + "pr": 1044 + } + ], + "timestamp": 1536142250 + }, + { + "version": "1.0.1-rc.6", + "changes": [ + { + "note": "Fix missing `BlockParamLiteral` type import issue" + } + ], + "timestamp": 1535377027 + }, + { + "version": "1.0.1-rc.5", + "changes": [ + { + "note": "Remove Caller and Trezor SignatureTypes", + "pr": 1015 + } + ] + }, + { "version": "1.0.1-rc.4", "changes": [ { @@ -14,8 +121,33 @@ "note": "Update marketUtils api such that all optional parameters are bundled into one optional param and more defaults are provided", "pr": 954 + }, + { + "note": + "Instead of exporting signature util methods individually, they are now exported as `signatureUtils`", + "pr": 924 + }, + { + "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 } - ] + ], + "timestamp": 1535133899 }, { "version": "1.0.1-rc.3", diff --git a/packages/order-utils/CHANGELOG.md b/packages/order-utils/CHANGELOG.md index 0df2a6a75..afd5c4adc 100644 --- a/packages/order-utils/CHANGELOG.md +++ b/packages/order-utils/CHANGELOG.md @@ -5,7 +5,61 @@ Edit the package's CHANGELOG.json file only. CHANGELOG -## v1.0.1-rc.3 - _August 13, 2018_ +## v2.0.0 - _October 18, 2018_ + + * Added `ecSignOrderAsync` to first sign an order using `eth_signTypedData` and fallback to `eth_sign`. (#1102) + * Added `ecSignTypedDataOrderAsync` to sign an order exclusively using `eth_signTypedData`. (#1102) + * Rename `ecSignOrderHashAsync` to `ecSignHashAsync` removing `SignerType` parameter. (#1102) + * Use `AssetData` union type for function return values. (#1131) + +## v1.0.7 - _October 4, 2018_ + + * Dependencies updated + +## v1.0.6 - _September 28, 2018_ + + * Add signerAddress normalization to `isValidECSignature` to avoid `invalid address recovery` error if caller supplies a checksummed address (#1096) + +## v1.0.5 - _September 25, 2018_ + + * Dependencies updated + +## v1.0.4 - _September 25, 2018_ + + * Dependencies updated + +## v1.0.3 - _September 21, 2018_ + + * Dependencies updated + +## v1.0.2 - _September 19, 2018_ + + * Drastically reduce the bundle size by removing unused parts of included contract artifacts. + +## v1.0.1 - _September 5, 2018_ + + * Export `orderParsingUtils` (#1044) + +## v1.0.1-rc.6 - _August 27, 2018_ + + * Fix missing `BlockParamLiteral` type import issue + +## v1.0.1-rc.5 - _Invalid date_ + + * Remove Caller and Trezor SignatureTypes (#1015) + +## v1.0.1-rc.4 - _August 24, 2018_ + + * Remove rounding error being thrown when maker amount is very small (#959) + * Added rateUtils and sortingUtils (#953) + * Update marketUtils api such that all optional parameters are bundled into one optional param and more defaults are provided (#954) + * Instead of exporting signature util methods individually, they are now exported as `signatureUtils` (#924) + * 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` (#924) + * Rename `resultOrders` to `resultFeeOrders` for object returned by `findFeeOrdersThatCoverFeesForTargetOrders` in `marketUtils` api (#997) + * Make `sortFeeOrdersByFeeAdjustedRate` in `sortingUtils` generic (#997) + * Update `findFeeOrdersThatCoverFeesForTargetOrders` to round the the nearest integer when calculating required fees (#997) + +## v1.0.1-rc.3 - _August 14, 2018_ * Update ecSignOrderHashAsync to return signature string with signature type byte. Removes messagePrefixOpts. (#914) * Added a synchronous `createOrder` method in `orderFactory`, updated public interfaces to support some optional parameters (#936) diff --git a/packages/order-utils/README.md b/packages/order-utils/README.md index 33df1d4b0..4a8287098 100644 --- a/packages/order-utils/README.md +++ b/packages/order-utils/README.md @@ -1,4 +1,4 @@ -## @0xproject/order-utils +## @0x/order-utils 0x order-related utilities for those developing on top of 0x protocol. @@ -7,14 +7,14 @@ ## Installation ```bash -yarn add @0xproject/order-utils +yarn add @0x/order-utils ``` 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"], } ``` @@ -43,13 +43,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/order-utils yarn build +PKG=@0x/order-utils yarn build ``` Or continuously rebuild on change: ```bash -PKG=@0xproject/order-utils yarn watch +PKG=@0x/order-utils yarn watch ``` ### Clean diff --git a/packages/order-utils/package.json b/packages/order-utils/package.json index b94f59e29..a6a84b940 100644 --- a/packages/order-utils/package.json +++ b/packages/order-utils/package.json @@ -1,6 +1,6 @@ { - "name": "@0xproject/order-utils", - "version": "1.0.1-rc.3", + "name": "@0x/order-utils", + "version": "2.0.0", "engines": { "node": ">=6.12" }, @@ -8,37 +8,21 @@ "main": "lib/src/index.js", "types": "lib/src/index.d.ts", "scripts": { - "watch_without_deps": "yarn pre_build && tsc -w", - "build": "run-s pre_build transpile copy_monorepo_scripts", - "pre_build": "run-s update_artifacts_v2_beta generate_contract_wrappers", - "transpile": "tsc", - "copy_monorepo_scripts": "copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts", - "generate_contract_wrappers": "abi-gen --abis 'lib/src/artifacts/@(Exchange|IWallet|IValidator|DummyERC20Token|ERC20Proxy|ERC20Token).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/generated_contract_wrappers --backend ethers", - "update_artifacts_v2_beta": "for i in ${npm_package_config_contracts_v2_beta}; do copyfiles -u 4 ../migrations/artifacts/2.0.0-beta-testnet/$i.json lib/src/artifacts; done;", + "build": "yarn tsc -b", + "build:ci": "yarn build", "test": "yarn run_mocha", "rebuild_and_test": "run-s build test", "test:circleci": "yarn test:coverage", "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --bail --exit", "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", - "clean": "shx rm -rf lib scripts lib/src/artifacts src/generated_contract_wrappers", - "lint": "tslint --project . --exclude **/src/generated_contract_wrappers/**/*", - "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" + "clean": "shx rm -rf lib generated_docs", + "lint": "tslint --format stylish --project .", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" }, "config": { - "contracts_v2_beta": "IWallet IValidator Exchange ERC20Proxy ERC20Token DummyERC20Token", "postpublish": { - "docPublishConfigs": { - "extraFileIncludes": [ - "../types/src/index.ts", - "../ethereum-types/src/index.ts" - ], - "s3BucketPath": "s3://doc-jsons/order-utils/", - "s3StagingBucketPath": "s3://staging-doc-jsons/order-utils/" - } + "assets": [] } }, "license": "Apache-2.0", @@ -51,15 +35,13 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/order-utils/README.md", "devDependencies": { - "@0xproject/dev-utils": "^1.0.4", - "@0xproject/monorepo-scripts": "^1.0.5", - "@0xproject/tslint-config": "^1.0.5", + "@0x/dev-utils": "^1.0.13", + "@0x/tslint-config": "^1.0.9", "@types/bn.js": "^4.11.0", "@types/lodash": "4.14.104", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", "chai-bignumber": "^2.0.1", - "copyfiles": "^2.0.0", "dirty-chai": "^2.0.1", "make-promises-safe": "^1.1.0", "mocha": "^4.1.0", @@ -67,24 +49,25 @@ "shx": "^0.2.2", "sinon": "^4.0.0", "tslint": "5.11.0", - "typedoc": "0xProject/typedoc", + "typedoc": "0.13.0", "typescript": "3.0.1" }, "dependencies": { - "@0xproject/assert": "^1.0.5", - "@0xproject/base-contract": "^2.0.0-rc.1", - "@0xproject/json-schemas": "^1.0.1-rc.4", - "@0xproject/sol-compiler": "^1.0.5", - "@0xproject/types": "^1.0.1-rc.4", - "@0xproject/typescript-typings": "^1.0.4", - "@0xproject/utils": "^1.0.5", - "@0xproject/web3-wrapper": "^1.2.0", - "@types/node": "^8.0.53", + "@0x/abi-gen-wrappers": "^1.0.1", + "@0x/assert": "^1.0.14", + "@0x/base-contract": "^3.0.2", + "@0x/contract-artifacts": "^1.0.1", + "@0x/json-schemas": "^2.0.0", + "@0x/types": "^1.2.0", + "@0x/typescript-typings": "^3.0.3", + "@0x/utils": "^2.0.3", + "@0x/web3-wrapper": "^3.1.0", + "@types/node": "*", "bn.js": "^4.11.8", - "ethereum-types": "^1.0.4", + "ethereum-types": "^1.1.1", "ethereumjs-abi": "0.6.5", "ethereumjs-util": "^5.1.1", - "ethers": "3.0.22", + "ethers": "~4.0.4", "lodash": "^4.17.5" }, "publishConfig": { diff --git a/packages/order-utils/src/abstract/abstract_balance_and_proxy_allowance_fetcher.ts b/packages/order-utils/src/abstract/abstract_balance_and_proxy_allowance_fetcher.ts index b2760d98e..13fbf1736 100644 --- a/packages/order-utils/src/abstract/abstract_balance_and_proxy_allowance_fetcher.ts +++ b/packages/order-utils/src/abstract/abstract_balance_and_proxy_allowance_fetcher.ts @@ -1,6 +1,23 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from '@0x/utils'; +/** + * An abstract class to be implemented in order to use OrderStateUtils. The class that + * implements this interface must be capable of fetching the balance and proxyAllowance + * for an Ethereum address and assetData + */ export abstract class AbstractBalanceAndProxyAllowanceFetcher { + /** + * Get balance of assetData for userAddress + * @param assetData AssetData for which to fetch the balance + * @param userAddress Ethereum address for which to fetch the balance + * @return Balance amount in base units + */ public abstract async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber>; + /** + * Get the 0x asset proxy allowance of assetData for userAddress + * @param assetData AssetData for which to fetch the allowance + * @param userAddress Ethereum address for which to fetch the allowance + * @return Allowance amount in base units + */ public abstract async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber>; } diff --git a/packages/order-utils/src/abstract/abstract_balance_and_proxy_allowance_lazy_store.ts b/packages/order-utils/src/abstract/abstract_balance_and_proxy_allowance_lazy_store.ts index 38e08b7fe..0a73e92bd 100644 --- a/packages/order-utils/src/abstract/abstract_balance_and_proxy_allowance_lazy_store.ts +++ b/packages/order-utils/src/abstract/abstract_balance_and_proxy_allowance_lazy_store.ts @@ -1,4 +1,4 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from '@0x/utils'; export abstract class AbstractBalanceAndProxyAllowanceLazyStore { public abstract async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber>; diff --git a/packages/order-utils/src/abstract/abstract_order_filled_cancelled_fetcher.ts b/packages/order-utils/src/abstract/abstract_order_filled_cancelled_fetcher.ts index 865ea4e43..de096b7d9 100644 --- a/packages/order-utils/src/abstract/abstract_order_filled_cancelled_fetcher.ts +++ b/packages/order-utils/src/abstract/abstract_order_filled_cancelled_fetcher.ts @@ -1,7 +1,22 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from '@0x/utils'; +/** + * An abstract class to be implemented in order to use OrderStateUtils. The class that + * implements this interface must be capable of fetching the amount filled of an order + * and whether it's been cancelled. + */ export abstract class AbstractOrderFilledCancelledFetcher { + /** + * Get the amount of the order's takerToken amount already filled + * @param orderHash OrderHash of order we are interested in + * @return FilledTakerAmount + */ public abstract async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber>; + /** + * Whether an order is cancelled + * @param orderHash OrderHash of order we are interested in + * @return Whether or not the order is cancelled + */ public abstract async isOrderCancelledAsync(orderHash: string): Promise<boolean>; public abstract getZRXAssetData(): string; } diff --git a/packages/order-utils/src/abstract/abstract_order_filled_cancelled_lazy_store.ts b/packages/order-utils/src/abstract/abstract_order_filled_cancelled_lazy_store.ts index 617bcb224..d9e66db06 100644 --- a/packages/order-utils/src/abstract/abstract_order_filled_cancelled_lazy_store.ts +++ b/packages/order-utils/src/abstract/abstract_order_filled_cancelled_lazy_store.ts @@ -1,4 +1,4 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from '@0x/utils'; export abstract class AbstractOrderFilledCancelledLazyStore { public abstract async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber>; diff --git a/packages/order-utils/src/artifacts.ts b/packages/order-utils/src/artifacts.ts deleted file mode 100644 index 3d2d1e953..000000000 --- a/packages/order-utils/src/artifacts.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ContractArtifact } from '@0xproject/sol-compiler'; - -import * as DummyERC20Token from './artifacts/DummyERC20Token.json'; -import * as ERC20Proxy from './artifacts/ERC20Proxy.json'; -import * as Exchange from './artifacts/Exchange.json'; -import * as IValidator from './artifacts/IValidator.json'; -import * as IWallet from './artifacts/IWallet.json'; -export const artifacts = { - ERC20Proxy: (ERC20Proxy as any) as ContractArtifact, - DummyERC20Token: (DummyERC20Token as any) as ContractArtifact, - Exchange: (Exchange as any) as ContractArtifact, - IWallet: (IWallet as any) as ContractArtifact, - IValidator: (IValidator as any) as ContractArtifact, -}; diff --git a/packages/order-utils/src/assert.ts b/packages/order-utils/src/assert.ts index f8db7ac63..2f73f58c4 100644 --- a/packages/order-utils/src/assert.ts +++ b/packages/order-utils/src/assert.ts @@ -1,10 +1,10 @@ -import { assert as sharedAssert } from '@0xproject/assert'; +import { assert as sharedAssert } from '@0x/assert'; // HACK: We need those two unused imports because they're actually used by sharedAssert which gets injected here // tslint:disable:no-unused-variable -import { Schema } from '@0xproject/json-schemas'; -import { ECSignature, SignatureType } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { Schema } from '@0x/json-schemas'; +import { ECSignature, SignatureType } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; // tslint:enable:no-unused-variable import * as _ from 'lodash'; diff --git a/packages/order-utils/src/asset_data_utils.ts b/packages/order-utils/src/asset_data_utils.ts index 0c0b59548..9bbef3a23 100644 --- a/packages/order-utils/src/asset_data_utils.ts +++ b/packages/order-utils/src/asset_data_utils.ts @@ -1,5 +1,5 @@ -import { AssetProxyId, ERC20AssetData, ERC721AssetData } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +import { AssetData, AssetProxyId, ERC20AssetData, ERC721AssetData } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import ethAbi = require('ethereumjs-abi'); import ethUtil = require('ethereumjs-util'); @@ -112,7 +112,7 @@ export const assetDataUtils = { * @param assetData Hex encoded assetData string to decode * @return Either a ERC20 or ERC721 assetData object */ - decodeAssetDataOrThrow(assetData: string): ERC20AssetData | ERC721AssetData { + decodeAssetDataOrThrow(assetData: string): AssetData { const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData); switch (assetProxyId) { case AssetProxyId.ERC20: diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts index c23578c20..10029dcc3 100644 --- a/packages/order-utils/src/constants.ts +++ b/packages/order-utils/src/constants.ts @@ -1,4 +1,4 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from '@0x/utils'; export const constants = { NULL_ADDRESS: '0x0000000000000000000000000000000000000000', @@ -13,4 +13,39 @@ export const constants = { BASE_16: 16, INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite ZERO_AMOUNT: new BigNumber(0), + EIP712_DOMAIN_NAME: '0x Protocol', + EIP712_DOMAIN_VERSION: '2', + EIP712_DOMAIN_SCHEMA: { + name: 'EIP712Domain', + parameters: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'verifyingContract', type: 'address' }, + ], + }, + EIP712_ORDER_SCHEMA: { + name: 'Order', + parameters: [ + { name: 'makerAddress', type: 'address' }, + { name: 'takerAddress', type: 'address' }, + { name: 'feeRecipientAddress', type: 'address' }, + { name: 'senderAddress', type: 'address' }, + { name: 'makerAssetAmount', type: 'uint256' }, + { name: 'takerAssetAmount', type: 'uint256' }, + { name: 'makerFee', type: 'uint256' }, + { name: 'takerFee', type: 'uint256' }, + { name: 'expirationTimeSeconds', type: 'uint256' }, + { name: 'salt', type: 'uint256' }, + { name: 'makerAssetData', type: 'bytes' }, + { name: 'takerAssetData', type: 'bytes' }, + ], + }, + EIP712_ZEROEX_TRANSACTION_SCHEMA: { + name: 'ZeroExTransaction', + parameters: [ + { name: 'salt', type: 'uint256' }, + { name: 'signerAddress', type: 'address' }, + { name: 'data', type: 'bytes' }, + ], + }, }; diff --git a/packages/order-utils/src/eip712_utils.ts b/packages/order-utils/src/eip712_utils.ts index 2594e6d6d..385fda989 100644 --- a/packages/order-utils/src/eip712_utils.ts +++ b/packages/order-utils/src/eip712_utils.ts @@ -1,93 +1,83 @@ -import ethUtil = require('ethereumjs-util'); +import { assert } from '@0x/assert'; +import { schemas } from '@0x/json-schemas'; +import { EIP712Object, EIP712TypedData, EIP712Types, Order, ZeroExTransaction } from '@0x/types'; import * as _ from 'lodash'; -import { crypto } from './crypto'; -import { EIP712Schema, EIP712Types } from './types'; +import { constants } from './constants'; -const EIP191_PREFIX = '\x19\x01'; -const EIP712_DOMAIN_NAME = '0x Protocol'; -const EIP712_DOMAIN_VERSION = '2'; -const EIP712_VALUE_LENGTH = 32; - -const EIP712_DOMAIN_SCHEMA: EIP712Schema = { - name: 'EIP712Domain', - parameters: [ - { name: 'name', type: EIP712Types.String }, - { name: 'version', type: EIP712Types.String }, - { name: 'verifyingContract', type: EIP712Types.Address }, - ], -}; - -export const EIP712Utils = { +export const eip712Utils = { /** - * Compiles the EIP712Schema and returns the hash of the schema. - * @param schema The EIP712 schema. - * @return The hash of the compiled schema + * Creates a EIP712TypedData object specific to the 0x protocol for use with signTypedData. + * @param primaryType The primary type found in message + * @param types The additional types for the data in message + * @param message The contents of the message + * @param exchangeAddress The address of the exchange contract + * @return A typed data object */ - compileSchema(schema: EIP712Schema): Buffer { - const eip712Schema = EIP712Utils._encodeType(schema); - const eip712SchemaHashBuffer = crypto.solSHA3([eip712Schema]); - return eip712SchemaHashBuffer; + createTypedData: ( + primaryType: string, + types: EIP712Types, + message: EIP712Object, + exchangeAddress: string, + ): EIP712TypedData => { + assert.isETHAddressHex('exchangeAddress', exchangeAddress); + assert.isString('primaryType', primaryType); + const typedData = { + types: { + EIP712Domain: constants.EIP712_DOMAIN_SCHEMA.parameters, + ...types, + }, + domain: { + name: constants.EIP712_DOMAIN_NAME, + version: constants.EIP712_DOMAIN_VERSION, + verifyingContract: exchangeAddress, + }, + message, + primaryType, + }; + assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedDataSchema); + return typedData; }, /** - * Merges the EIP712 hash of a struct with the DomainSeparator for 0x v2. - * @param hashStruct the EIP712 hash of a struct - * @param contractAddress the exchange contract address - * @return The hash of an EIP712 message with domain separator prefixed + * Creates an Order EIP712TypedData object for use with signTypedData. + * @param Order the order + * @return A typed data object */ - createEIP712Message(hashStruct: Buffer, contractAddress: string): Buffer { - const domainSeparatorHashBuffer = EIP712Utils._getDomainSeparatorHashBuffer(contractAddress); - const messageBuff = crypto.solSHA3([EIP191_PREFIX, domainSeparatorHashBuffer, hashStruct]); - return messageBuff; - }, - pad32Address(address: string): Buffer { - const addressBuffer = ethUtil.toBuffer(address); - const addressPadded = EIP712Utils.pad32Buffer(addressBuffer); - return addressPadded; - }, - pad32Buffer(buffer: Buffer): Buffer { - const bufferPadded = ethUtil.setLengthLeft(buffer, EIP712_VALUE_LENGTH); - return bufferPadded; - }, - _getDomainSeparatorSchemaBuffer(): Buffer { - return EIP712Utils.compileSchema(EIP712_DOMAIN_SCHEMA); - }, - _getDomainSeparatorHashBuffer(exchangeAddress: string): Buffer { - const domainSeparatorSchemaBuffer = EIP712Utils._getDomainSeparatorSchemaBuffer(); - const encodedData = EIP712Utils._encodeData(EIP712_DOMAIN_SCHEMA, { - name: EIP712_DOMAIN_NAME, - version: EIP712_DOMAIN_VERSION, - verifyingContract: exchangeAddress, + createOrderTypedData: (order: Order): EIP712TypedData => { + assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]); + const normalizedOrder = _.mapValues(order, value => { + return !_.isString(value) ? value.toString() : value; }); - const domainSeparatorHashBuff2 = crypto.solSHA3([domainSeparatorSchemaBuffer, ...encodedData]); - return domainSeparatorHashBuff2; - }, - _encodeType(schema: EIP712Schema): string { - const namedTypes = _.map(schema.parameters, ({ name, type }) => `${type} ${name}`); - const namedTypesJoined = namedTypes.join(','); - const encodedType = `${schema.name}(${namedTypesJoined})`; - return encodedType; - }, - _encodeData(schema: EIP712Schema, data: { [key: string]: any }): any { - const encodedValues = []; - for (const parameter of schema.parameters) { - const value = data[parameter.name]; - if (parameter.type === EIP712Types.String || parameter.type === EIP712Types.Bytes) { - encodedValues.push(crypto.solSHA3([ethUtil.toBuffer(value)])); - } else if (parameter.type === EIP712Types.Uint256) { - encodedValues.push(value); - } else if (parameter.type === EIP712Types.Address) { - encodedValues.push(EIP712Utils.pad32Address(value)); - } else { - throw new Error(`Unable to encode ${parameter.type}`); - } - } - return encodedValues; + const typedData = eip712Utils.createTypedData( + constants.EIP712_ORDER_SCHEMA.name, + { Order: constants.EIP712_ORDER_SCHEMA.parameters }, + normalizedOrder, + order.exchangeAddress, + ); + return typedData; }, - structHash(schema: EIP712Schema, data: { [key: string]: any }): Buffer { - const encodedData = EIP712Utils._encodeData(schema, data); - const schemaHash = EIP712Utils.compileSchema(schema); - const hashBuffer = crypto.solSHA3([schemaHash, ...encodedData]); - return hashBuffer; + /** + * Creates an ExecuteTransaction EIP712TypedData object for use with signTypedData and + * 0x Exchange executeTransaction. + * @param ZeroExTransaction the 0x transaction + * @param exchangeAddress The address of the exchange contract + * @return A typed data object + */ + createZeroExTransactionTypedData: ( + zeroExTransaction: ZeroExTransaction, + exchangeAddress: string, + ): EIP712TypedData => { + assert.isETHAddressHex('exchangeAddress', exchangeAddress); + assert.doesConformToSchema('zeroExTransaction', zeroExTransaction, schemas.zeroExTransactionSchema); + const normalizedTransaction = _.mapValues(zeroExTransaction, value => { + return !_.isString(value) ? value.toString() : value; + }); + const typedData = eip712Utils.createTypedData( + constants.EIP712_ZEROEX_TRANSACTION_SCHEMA.name, + { ZeroExTransaction: constants.EIP712_ZEROEX_TRANSACTION_SCHEMA.parameters }, + normalizedTransaction, + exchangeAddress, + ); + return typedData; }, }; diff --git a/packages/order-utils/src/exchange_transfer_simulator.ts b/packages/order-utils/src/exchange_transfer_simulator.ts index c3a4f9c2a..7a38b35df 100644 --- a/packages/order-utils/src/exchange_transfer_simulator.ts +++ b/packages/order-utils/src/exchange_transfer_simulator.ts @@ -1,5 +1,5 @@ -import { ExchangeContractErrs } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +import { ExchangeContractErrs } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import { AbstractBalanceAndProxyAllowanceLazyStore } from './abstract/abstract_balance_and_proxy_allowance_lazy_store'; import { constants } from './constants'; @@ -33,6 +33,10 @@ const ERR_MSG_MAPPING = { }, }; +/** + * An exchange transfer simulator which simulates asset transfers exactly how the + * 0x exchange contract would do them. + */ export class ExchangeTransferSimulator { private readonly _store: AbstractBalanceAndProxyAllowanceLazyStore; private static _throwValidationError( @@ -43,6 +47,11 @@ export class ExchangeTransferSimulator { const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType]; throw new Error(errMsg); } + /** + * Instantiate a ExchangeTransferSimulator + * @param store A class that implements AbstractBalanceAndProxyAllowanceLazyStore + * @return an instance of ExchangeTransferSimulator + */ constructor(store: AbstractBalanceAndProxyAllowanceLazyStore) { this._store = store; } diff --git a/packages/order-utils/src/globals.d.ts b/packages/order-utils/src/globals.d.ts deleted file mode 100644 index 94e63a32d..000000000 --- a/packages/order-utils/src/globals.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -declare module '*.json' { - const json: any; - /* tslint:disable */ - export default json; - /* tslint:enable */ -} diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts index 2b1c92973..e70d43efb 100644 --- a/packages/order-utils/src/index.ts +++ b/packages/order-utils/src/index.ts @@ -1,29 +1,60 @@ export { orderHashUtils } from './order_hash'; -export { - isValidSignatureAsync, - isValidPresignedSignatureAsync, - isValidWalletSignatureAsync, - isValidValidatorSignatureAsync, - isValidECSignature, - ecSignOrderHashAsync, - addSignedMessagePrefix, - parseECSignature, -} from './signature_utils'; -export { orderFactory } from './order_factory'; -export { constants } from './constants'; -export { crypto } from './crypto'; +export { signatureUtils } from './signature_utils'; export { generatePseudoRandomSalt } from './salt'; -export { CreateOrderOpts, OrderError, 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'; -export { OrderFilledCancelledLazyStore } from './store/order_filled_cancelled_lazy_store'; -export { RemainingFillableCalculator } from './remaining_fillable_calculator'; -export { OrderStateUtils } from './order_state_utils'; 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'; export { rateUtils } from './rate_utils'; export { sortingUtils } from './sorting_utils'; +export { orderParsingUtils } from './parsing_utils'; + +export { OrderStateUtils } from './order_state_utils'; +export { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher'; +export { AbstractBalanceAndProxyAllowanceLazyStore } from './abstract/abstract_balance_and_proxy_allowance_lazy_store'; +export { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher'; +export { AbstractOrderFilledCancelledLazyStore } from './abstract/abstract_order_filled_cancelled_lazy_store'; + +export { OrderValidationUtils } from './order_validation_utils'; +export { ExchangeTransferSimulator } from './exchange_transfer_simulator'; +export { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store'; +export { OrderFilledCancelledLazyStore } from './store/order_filled_cancelled_lazy_store'; + +export { eip712Utils } from './eip712_utils'; + +export { + Provider, + JSONRPCRequestPayload, + JSONRPCErrorCallback, + JSONRPCResponsePayload, + JSONRPCResponseError, +} from 'ethereum-types'; + +export { + SignedOrder, + Order, + OrderRelevantState, + OrderState, + ECSignature, + AssetData, + ERC20AssetData, + ERC721AssetData, + AssetProxyId, + SignatureType, + OrderStateValid, + OrderStateInvalid, + ExchangeContractErrs, + EIP712Parameter, + EIP712TypedData, + EIP712Types, + EIP712Object, + EIP712ObjectValue, + ZeroExTransaction, +} from '@0x/types'; +export { + OrderError, + TradeSide, + TransferType, + FindFeeOrdersThatCoverFeesForTargetOrdersOpts, + FindOrdersThatCoverMakerAssetFillAmountOpts, + FeeOrdersAndRemainingFeeAmount, + OrdersAndRemainingFillAmount, +} from './types'; diff --git a/packages/order-utils/src/market_utils.ts b/packages/order-utils/src/market_utils.ts index a0a827546..fa32f1413 100644 --- a/packages/order-utils/src/market_utils.ts +++ b/packages/order-utils/src/market_utils.ts @@ -1,11 +1,16 @@ -import { schemas } from '@0xproject/json-schemas'; -import { Order } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +import { schemas } from '@0x/json-schemas'; +import { Order } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { assert } from './assert'; import { constants } from './constants'; -import { FindFeeOrdersThatCoverFeesForTargetOrdersOpts, FindOrdersThatCoverMakerAssetFillAmountOpts } from './types'; +import { + FeeOrdersAndRemainingFeeAmount, + FindFeeOrdersThatCoverFeesForTargetOrdersOpts, + FindOrdersThatCoverMakerAssetFillAmountOpts, + OrdersAndRemainingFillAmount, +} from './types'; export const marketUtils = { /** @@ -22,7 +27,7 @@ export const marketUtils = { orders: T[], makerAssetFillAmount: BigNumber, opts?: FindOrdersThatCoverMakerAssetFillAmountOpts, - ): { resultOrders: T[]; remainingFillAmount: BigNumber } { + ): OrdersAndRemainingFillAmount<T> { assert.doesConformToSchema('orders', orders, schemas.ordersSchema); assert.isValidBaseUnitAmount('makerAssetFillAmount', makerAssetFillAmount); // try to get remainingFillableMakerAssetAmounts from opts, if it's not there, use makerAssetAmount values from orders @@ -46,17 +51,23 @@ export const marketUtils = { // iterate through the orders input from left to right until we have enough makerAsset to fill totalFillAmount const result = _.reduce( orders, - ({ resultOrders, remainingFillAmount }, order, index) => { + ({ resultOrders, remainingFillAmount, ordersRemainingFillableMakerAssetAmounts }, order, index) => { if (remainingFillAmount.lessThanOrEqualTo(constants.ZERO_AMOUNT)) { - return { resultOrders, remainingFillAmount: constants.ZERO_AMOUNT }; + return { + resultOrders, + remainingFillAmount: constants.ZERO_AMOUNT, + ordersRemainingFillableMakerAssetAmounts, + }; } else { const makerAssetAmountAvailable = remainingFillableMakerAssetAmounts[index]; + const shouldIncludeOrder = makerAssetAmountAvailable.gt(constants.ZERO_AMOUNT); // 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, + resultOrders: shouldIncludeOrder ? _.concat(resultOrders, order) : resultOrders, + ordersRemainingFillableMakerAssetAmounts: shouldIncludeOrder + ? _.concat(ordersRemainingFillableMakerAssetAmounts, makerAssetAmountAvailable) + : ordersRemainingFillableMakerAssetAmounts, remainingFillAmount: BigNumber.max( constants.ZERO_AMOUNT, remainingFillAmount.minus(makerAssetAmountAvailable), @@ -64,7 +75,11 @@ export const marketUtils = { }; } }, - { resultOrders: [] as T[], remainingFillAmount: totalFillAmount }, + { + resultOrders: [] as T[], + remainingFillAmount: totalFillAmount, + ordersRemainingFillableMakerAssetAmounts: [] as BigNumber[], + }, ); return result; }, @@ -84,7 +99,7 @@ export const marketUtils = { orders: T[], feeOrders: T[], opts?: FindFeeOrdersThatCoverFeesForTargetOrdersOpts, - ): { resultOrders: T[]; remainingFeeAmount: BigNumber } { + ): FeeOrdersAndRemainingFeeAmount<T> { assert.doesConformToSchema('orders', orders, schemas.ordersSchema); assert.doesConformToSchema('feeOrders', feeOrders, schemas.ordersSchema); // try to get remainingFillableMakerAssetAmounts from opts, if it's not there, use makerAssetAmount values from orders @@ -123,22 +138,23 @@ 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, ); - const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( - feeOrders, - totalFeeAmount, - { - remainingFillableMakerAssetAmounts: remainingFillableFeeAmounts, - slippageBufferAmount, - }, - ); - return { + const { resultOrders, + remainingFillAmount, + ordersRemainingFillableMakerAssetAmounts, + } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(feeOrders, totalFeeAmount, { + remainingFillableMakerAssetAmounts: remainingFillableFeeAmounts, + slippageBufferAmount, + }); + return { + resultFeeOrders: resultOrders, remainingFeeAmount: remainingFillAmount, + feeOrdersRemainingFillableMakerAssetAmounts: ordersRemainingFillableMakerAssetAmounts, }; // 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/monorepo_scripts/postpublish.ts b/packages/order-utils/src/monorepo_scripts/postpublish.ts deleted file mode 100644 index dcb99d0f7..000000000 --- a/packages/order-utils/src/monorepo_scripts/postpublish.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { postpublishUtils } from '@0xproject/monorepo-scripts'; - -import * as packageJSON from '../package.json'; -import * as tsConfigJSON from '../tsconfig.json'; - -const cwd = `${__dirname}/..`; -// tslint:disable-next-line:no-floating-promises -postpublishUtils.runAsync(packageJSON, tsConfigJSON, cwd); diff --git a/packages/order-utils/src/monorepo_scripts/stage_docs.ts b/packages/order-utils/src/monorepo_scripts/stage_docs.ts deleted file mode 100644 index e732ac8eb..000000000 --- a/packages/order-utils/src/monorepo_scripts/stage_docs.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { postpublishUtils } from '@0xproject/monorepo-scripts'; - -import * as packageJSON from '../package.json'; -import * as tsConfigJSON from '../tsconfig.json'; - -const cwd = `${__dirname}/..`; -// tslint:disable-next-line:no-floating-promises -postpublishUtils.publishDocsToStagingAsync(packageJSON, tsConfigJSON, cwd); diff --git a/packages/order-utils/src/order_factory.ts b/packages/order-utils/src/order_factory.ts index 4a6f3924b..f7b855bfb 100644 --- a/packages/order-utils/src/order_factory.ts +++ b/packages/order-utils/src/order_factory.ts @@ -1,15 +1,28 @@ -import { Order, SignedOrder, SignerType } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +import { Order, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import { Provider } from 'ethereum-types'; import * as _ from 'lodash'; import { constants } from './constants'; import { orderHashUtils } from './order_hash'; import { generatePseudoRandomSalt } from './salt'; -import { ecSignOrderHashAsync } from './signature_utils'; +import { signatureUtils } from './signature_utils'; import { CreateOrderOpts } from './types'; - export const orderFactory = { + createOrderFromPartial(partialOrder: Partial<Order>): Order { + const defaultOrder = generateEmptyOrder(); + return { + ...defaultOrder, + ...partialOrder, + }; + }, + createSignedOrderFromPartial(partialSignedOrder: Partial<SignedOrder>): SignedOrder { + const defaultOrder = generateEmptySignedOrder(); + return { + ...defaultOrder, + ...partialSignedOrder, + }; + }, createOrder( makerAddress: string, makerAssetAmount: BigNumber, @@ -58,12 +71,36 @@ export const orderFactory = { createOrderOpts, ); const orderHash = orderHashUtils.getOrderHashHex(order); - const signature = await ecSignOrderHashAsync(provider, orderHash, makerAddress, SignerType.Default); + const signature = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress); const signedOrder: SignedOrder = _.assign(order, { signature }); return signedOrder; }, }; +function generateEmptySignedOrder(): SignedOrder { + return { + ...generateEmptyOrder(), + signature: constants.NULL_BYTES, + }; +} +function generateEmptyOrder(): Order { + return { + senderAddress: constants.NULL_ADDRESS, + makerAddress: constants.NULL_ADDRESS, + takerAddress: constants.NULL_ADDRESS, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + makerAssetAmount: constants.ZERO_AMOUNT, + takerAssetAmount: constants.ZERO_AMOUNT, + makerAssetData: constants.NULL_BYTES, + takerAssetData: constants.NULL_BYTES, + salt: generatePseudoRandomSalt(), + exchangeAddress: constants.NULL_ADDRESS, + feeRecipientAddress: constants.NULL_ADDRESS, + expirationTimeSeconds: constants.INFINITE_TIMESTAMP_SEC, + }; +} + function generateDefaultCreateOrderOpts(): { takerAddress: string; senderAddress: string; diff --git a/packages/order-utils/src/order_hash.ts b/packages/order-utils/src/order_hash.ts index 54c500653..c8e9be71e 100644 --- a/packages/order-utils/src/order_hash.ts +++ b/packages/order-utils/src/order_hash.ts @@ -1,31 +1,13 @@ -import { schemas, SchemaValidator } from '@0xproject/json-schemas'; -import { Order, SignedOrder } from '@0xproject/types'; +import { schemas, SchemaValidator } from '@0x/json-schemas'; +import { Order, SignedOrder } from '@0x/types'; +import { signTypedDataUtils } from '@0x/utils'; import * as _ from 'lodash'; import { assert } from './assert'; -import { EIP712Utils } from './eip712_utils'; -import { EIP712Schema, EIP712Types } from './types'; +import { eip712Utils } from './eip712_utils'; const INVALID_TAKER_FORMAT = 'instance.takerAddress is not of a type(s) string'; -const EIP712_ORDER_SCHEMA: EIP712Schema = { - name: 'Order', - parameters: [ - { name: 'makerAddress', type: EIP712Types.Address }, - { name: 'takerAddress', type: EIP712Types.Address }, - { name: 'feeRecipientAddress', type: EIP712Types.Address }, - { name: 'senderAddress', type: EIP712Types.Address }, - { name: 'makerAssetAmount', type: EIP712Types.Uint256 }, - { name: 'takerAssetAmount', type: EIP712Types.Uint256 }, - { name: 'makerFee', type: EIP712Types.Uint256 }, - { name: 'takerFee', type: EIP712Types.Uint256 }, - { name: 'expirationTimeSeconds', type: EIP712Types.Uint256 }, - { name: 'salt', type: EIP712Types.Uint256 }, - { name: 'makerAssetData', type: EIP712Types.Bytes }, - { name: 'takerAssetData', type: EIP712Types.Bytes }, - ], -}; - export const orderHashUtils = { /** * Checks if the supplied hex encoded order hash is valid. @@ -45,7 +27,7 @@ export const orderHashUtils = { /** * Computes the orderHash for a supplied order. * @param order An object that conforms to the Order or SignedOrder interface definitions. - * @return The resulting orderHash from hashing the supplied order. + * @return Hex encoded string orderHash from hashing the supplied order. */ getOrderHashHex(order: SignedOrder | Order): string { try { @@ -64,16 +46,13 @@ export const orderHashUtils = { return orderHashHex; }, /** - * Computes the orderHash for a supplied order and returns it as a Buffer + * Computes the orderHash for a supplied order * @param order An object that conforms to the Order or SignedOrder interface definitions. - * @return The resulting orderHash from hashing the supplied order as a Buffer + * @return A Buffer containing the resulting orderHash from hashing the supplied order */ getOrderHashBuffer(order: SignedOrder | Order): Buffer { - const orderParamsHashBuff = EIP712Utils.structHash(EIP712_ORDER_SCHEMA, order); - const orderHashBuff = EIP712Utils.createEIP712Message(orderParamsHashBuff, order.exchangeAddress); + const typedData = eip712Utils.createOrderTypedData(order); + const orderHashBuff = signTypedDataUtils.generateTypedDataHash(typedData); return orderHashBuff; }, - _getOrderSchemaBuffer(): Buffer { - return EIP712Utils.compileSchema(EIP712_ORDER_SCHEMA); - }, }; diff --git a/packages/order-utils/src/order_state_utils.ts b/packages/order-utils/src/order_state_utils.ts index 18fc18bf6..159aeeb09 100644 --- a/packages/order-utils/src/order_state_utils.ts +++ b/packages/order-utils/src/order_state_utils.ts @@ -5,8 +5,8 @@ import { OrderStateInvalid, OrderStateValid, SignedOrder, -} from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +} from '@0x/types'; +import { BigNumber } from '@0x/utils'; import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher'; import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher'; @@ -81,7 +81,7 @@ export class OrderStateUtils { const remainingTakerAssetAmount = signedOrder.takerAssetAmount.minus( sidedOrderRelevantState.filledTakerAssetAmount, ); - const isRoundingError = OrderValidationUtils.isRoundingError( + const isRoundingError = OrderValidationUtils.isRoundingErrorFloor( remainingTakerAssetAmount, signedOrder.takerAssetAmount, signedOrder.makerAssetAmount, @@ -91,6 +91,14 @@ export class OrderStateUtils { } return { isValid: true }; } + /** + * Instantiate OrderStateUtils + * @param balanceAndProxyAllowanceFetcher A class that is capable of fetching balances + * and proxyAllowances for Ethereum addresses. It must implement AbstractBalanceAndProxyAllowanceFetcher + * @param orderFilledCancelledFetcher A class that is capable of fetching whether an order + * is cancelled and how much of it has been filled. It must implement AbstractOrderFilledCancelledFetcher + * @return Instance of OrderStateUtils + */ constructor( balanceAndProxyAllowanceFetcher: AbstractBalanceAndProxyAllowanceFetcher, orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher, @@ -98,7 +106,15 @@ export class OrderStateUtils { this._balanceAndProxyAllowanceFetcher = balanceAndProxyAllowanceFetcher; this._orderFilledCancelledFetcher = orderFilledCancelledFetcher; } - public async getOpenOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> { + /** + * Get the orderState for an "open" order (i.e where takerAddress=NULL_ADDRESS) + * This method will only check the maker's balance/allowance to calculate the + * OrderState. + * @param signedOrder The order of interest + * @return State relevant to the signedOrder, as well as whether the signedOrder is "valid". + * Validity is defined as a non-zero amount of the order can still be filled. + */ + public async getOpenOrderStateAsync(signedOrder: SignedOrder, transactionHash?: string): Promise<OrderState> { const orderRelevantState = await this.getOpenOrderRelevantStateAsync(signedOrder); const orderHash = orderHashUtils.getOrderHashHex(signedOrder); const isOrderCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(orderHash); @@ -118,6 +134,7 @@ export class OrderStateUtils { isValid: true, orderHash, orderRelevantState, + transactionHash, }; return orderState; } else { @@ -125,10 +142,16 @@ export class OrderStateUtils { isValid: false, orderHash, error: orderValidationResult.error, + transactionHash, }; return orderState; } } + /** + * Get state relevant to an order (i.e makerBalance, makerAllowance, filledTakerAssetAmount, etc... + * @param signedOrder Order of interest + * @return An instance of OrderRelevantState + */ public async getOpenOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> { const isMaker = true; const sidedOrderRelevantState = await this._getSidedOrderRelevantStateAsync( @@ -151,6 +174,12 @@ export class OrderStateUtils { }; return orderRelevantState; } + /** + * Get the max amount of the supplied order's takerAmount that could still be filled + * @param signedOrder Order of interest + * @param takerAddress Hypothetical taker of the order + * @return fillableTakerAssetAmount + */ public async getMaxFillableTakerAssetAmountAsync( signedOrder: SignedOrder, takerAddress: string, @@ -164,7 +193,7 @@ export class OrderStateUtils { ); const remainingFillableTakerAssetAmountGivenMakersStatus = signedOrder.makerAssetAmount.eq(0) ? new BigNumber(0) - : utils.getPartialAmount( + : utils.getPartialAmountFloor( orderRelevantMakerState.remainingFillableAssetAmount, signedOrder.makerAssetAmount, signedOrder.takerAssetAmount, @@ -183,32 +212,6 @@ export class OrderStateUtils { return fillableTakerAssetAmount; } - public async getMaxFillableTakerAssetAmountForFailingOrderAsync( - signedOrder: SignedOrder, - takerAddress: string, - ): Promise<BigNumber> { - // Get min of taker balance & allowance - const takerAssetBalanceOfTaker = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync( - signedOrder.takerAssetData, - takerAddress, - ); - const takerAssetAllowanceOfTaker = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync( - signedOrder.takerAssetData, - takerAddress, - ); - const minTakerAssetAmount = BigNumber.min([takerAssetBalanceOfTaker, takerAssetAllowanceOfTaker]); - - // get remainingFillAmount - const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - const filledTakerAssetAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash); - const remainingFillTakerAssetAmount = signedOrder.takerAssetAmount.minus(filledTakerAssetAmount); - - if (minTakerAssetAmount.gte(remainingFillTakerAssetAmount)) { - return remainingFillTakerAssetAmount; - } else { - return minTakerAssetAmount; - } - } private async _getSidedOrderRelevantStateAsync( isMakerSide: boolean, signedOrder: SignedOrder, diff --git a/packages/order-utils/src/order_validation_utils.ts b/packages/order-utils/src/order_validation_utils.ts index 67d747081..a40069f63 100644 --- a/packages/order-utils/src/order_validation_utils.ts +++ b/packages/order-utils/src/order_validation_utils.ts @@ -1,5 +1,5 @@ -import { RevertReason, SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +import { RevertReason, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import { Provider } from 'ethereum-types'; import * as _ from 'lodash'; @@ -9,12 +9,22 @@ import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_f import { constants } from './constants'; import { ExchangeTransferSimulator } from './exchange_transfer_simulator'; import { orderHashUtils } from './order_hash'; -import { isValidSignatureAsync } from './signature_utils'; +import { signatureUtils } from './signature_utils'; import { utils } from './utils'; +/** + * A utility class for validating orders + */ export class OrderValidationUtils { private readonly _orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher; - public static isRoundingError(numerator: BigNumber, denominator: BigNumber, target: BigNumber): boolean { + /** + * A Typescript implementation mirroring the implementation of isRoundingError in the + * Exchange smart contract + * @param numerator Numerator value. When used to check an order, pass in `takerAssetFilledAmount` + * @param denominator Denominator value. When used to check an order, pass in `order.takerAssetAmount` + * @param target Target value. When used to check an order, pass in `order.makerAssetAmount` + */ + public static isRoundingErrorFloor(numerator: BigNumber, denominator: BigNumber, target: BigNumber): boolean { // Solidity's mulmod() in JS // Source: https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#mathematical-and-cryptographic-functions if (denominator.eq(0)) { @@ -31,6 +41,15 @@ export class OrderValidationUtils { const isError = errPercentageTimes1000000.gt(1000); return isError; } + /** + * Validate that the maker & taker have sufficient balances/allowances + * to fill the supplied order to the fillTakerAssetAmount amount + * @param exchangeTradeEmulator ExchangeTradeEmulator to use + * @param signedOrder SignedOrder to test + * @param fillTakerAssetAmount Amount of takerAsset to fill the signedOrder + * @param senderAddress Sender of the fillOrder tx + * @param zrxAssetData AssetData for the ZRX token + */ public static async validateFillOrderBalancesAllowancesThrowIfInvalidAsync( exchangeTradeEmulator: ExchangeTransferSimulator, signedOrder: SignedOrder, @@ -39,7 +58,7 @@ export class OrderValidationUtils { zrxAssetData: string, ): Promise<void> { try { - const fillMakerTokenAmount = utils.getPartialAmount( + const fillMakerTokenAmount = utils.getPartialAmountFloor( fillTakerAssetAmount, signedOrder.takerAssetAmount, signedOrder.makerAssetAmount, @@ -60,7 +79,7 @@ export class OrderValidationUtils { TradeSide.Taker, TransferType.Trade, ); - const makerFeeAmount = utils.getPartialAmount( + const makerFeeAmount = utils.getPartialAmountFloor( fillTakerAssetAmount, signedOrder.takerAssetAmount, signedOrder.makerFee, @@ -73,7 +92,7 @@ export class OrderValidationUtils { TradeSide.Maker, TransferType.Fee, ); - const takerFeeAmount = utils.getPartialAmount( + const takerFeeAmount = utils.getPartialAmountFloor( fillTakerAssetAmount, signedOrder.takerAssetAmount, signedOrder.takerFee, @@ -104,9 +123,22 @@ export class OrderValidationUtils { throw new Error(RevertReason.OrderUnfillable); } } + /** + * Instantiate OrderValidationUtils + * @param orderFilledCancelledFetcher A module that implements the AbstractOrderFilledCancelledFetcher + * @return An instance of OrderValidationUtils + */ constructor(orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher) { this._orderFilledCancelledFetcher = orderFilledCancelledFetcher; } + /** + * Validate if the supplied order is fillable, and throw if it isn't + * @param exchangeTradeEmulator ExchangeTradeEmulator instance + * @param signedOrder SignedOrder of interest + * @param zrxAssetData ZRX assetData + * @param expectedFillTakerTokenAmount If supplied, this call will make sure this amount is fillable. + * If it isn't supplied, we check if the order is fillable for a non-zero amount + */ public async validateOrderFillableOrThrowAsync( exchangeTradeEmulator: ExchangeTransferSimulator, signedOrder: SignedOrder, @@ -132,6 +164,15 @@ export class OrderValidationUtils { zrxAssetData, ); } + /** + * Validate a call to FillOrder and throw if it wouldn't succeed + * @param exchangeTradeEmulator ExchangeTradeEmulator to use + * @param provider Web3 provider to use for JSON RPC requests + * @param signedOrder SignedOrder of interest + * @param fillTakerAssetAmount Amount we'd like to fill the order for + * @param takerAddress The taker of the order + * @param zrxAssetData ZRX asset data + */ public async validateFillOrderThrowIfInvalidAsync( exchangeTradeEmulator: ExchangeTransferSimulator, provider: Provider, @@ -147,7 +188,7 @@ export class OrderValidationUtils { throw new Error(RevertReason.InvalidTakerAmount); } const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - const isValid = await isValidSignatureAsync( + const isValid = await signatureUtils.isValidSignatureAsync( provider, orderHash, signedOrder.signature, @@ -177,7 +218,7 @@ export class OrderValidationUtils { zrxAssetData, ); - const wouldRoundingErrorOccur = OrderValidationUtils.isRoundingError( + const wouldRoundingErrorOccur = OrderValidationUtils.isRoundingErrorFloor( desiredFillTakerTokenAmount, signedOrder.takerAssetAmount, signedOrder.makerAssetAmount, @@ -187,6 +228,15 @@ export class OrderValidationUtils { } return filledTakerTokenAmount; } + /** + * Validate a call to fillOrKillOrder and throw if it would fail + * @param exchangeTradeEmulator ExchangeTradeEmulator to use + * @param provider Web3 provider to use for JSON RPC requests + * @param signedOrder SignedOrder of interest + * @param fillTakerAssetAmount Amount we'd like to fill the order for + * @param takerAddress The taker of the order + * @param zrxAssetData ZRX asset data + */ public async validateFillOrKillOrderThrowIfInvalidAsync( exchangeTradeEmulator: ExchangeTransferSimulator, provider: Provider, diff --git a/packages/order-utils/src/parsing_utils.ts b/packages/order-utils/src/parsing_utils.ts new file mode 100644 index 000000000..98c6899fe --- /dev/null +++ b/packages/order-utils/src/parsing_utils.ts @@ -0,0 +1,27 @@ +import { BigNumber } from '@0x/utils'; +import * as _ from 'lodash'; + +export const orderParsingUtils = { + convertStringsFieldsToBigNumbers(obj: any, fields: string[]): any { + const result = _.assign({}, obj); + _.each(fields, field => { + _.update(result, field, (value: string) => { + if (_.isUndefined(value)) { + throw new Error(`Could not find field '${field}' while converting string fields to BigNumber.`); + } + return new BigNumber(value); + }); + }); + return result; + }, + convertOrderStringFieldsToBigNumber(order: any): any { + return orderParsingUtils.convertStringsFieldsToBigNumbers(order, [ + 'makerAssetAmount', + 'takerAssetAmount', + 'makerFee', + 'takerFee', + 'expirationTimeSeconds', + 'salt', + ]); + }, +}; diff --git a/packages/order-utils/src/rate_utils.ts b/packages/order-utils/src/rate_utils.ts index c9ca72c59..416e00c67 100644 --- a/packages/order-utils/src/rate_utils.ts +++ b/packages/order-utils/src/rate_utils.ts @@ -1,6 +1,6 @@ -import { schemas } from '@0xproject/json-schemas'; -import { Order } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +import { schemas } from '@0x/json-schemas'; +import { Order } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import { assert } from './assert'; import { constants } from './constants'; diff --git a/packages/order-utils/src/remaining_fillable_calculator.ts b/packages/order-utils/src/remaining_fillable_calculator.ts index 7022aa979..052eafa1d 100644 --- a/packages/order-utils/src/remaining_fillable_calculator.ts +++ b/packages/order-utils/src/remaining_fillable_calculator.ts @@ -1,4 +1,4 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from '@0x/utils'; export class RemainingFillableCalculator { private readonly _isTraderAssetZRX: boolean; diff --git a/packages/order-utils/src/salt.ts b/packages/order-utils/src/salt.ts index 90a4197c0..ff47ab5d2 100644 --- a/packages/order-utils/src/salt.ts +++ b/packages/order-utils/src/salt.ts @@ -1,4 +1,4 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from '@0x/utils'; const MAX_DIGITS_IN_UNSIGNED_256_INT = 78; diff --git a/packages/order-utils/src/signature_utils.ts b/packages/order-utils/src/signature_utils.ts index 870aef2ed..96d90e21a 100644 --- a/packages/order-utils/src/signature_utils.ts +++ b/packages/order-utils/src/signature_utils.ts @@ -1,346 +1,375 @@ -import { schemas } from '@0xproject/json-schemas'; -import { ECSignature, SignatureType, SignerType, ValidatorSignature } from '@0xproject/types'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { ExchangeContract, IValidatorContract, IWalletContract } from '@0x/abi-gen-wrappers'; +import * as artifacts from '@0x/contract-artifacts'; +import { schemas } from '@0x/json-schemas'; +import { ECSignature, Order, SignatureType, SignedOrder, ValidatorSignature } from '@0x/types'; +import { Web3Wrapper } from '@0x/web3-wrapper'; import { Provider } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { artifacts } from './artifacts'; import { assert } from './assert'; -import { ExchangeContract } from './generated_contract_wrappers/exchange'; -import { IValidatorContract } from './generated_contract_wrappers/i_validator'; -import { IWalletContract } from './generated_contract_wrappers/i_wallet'; +import { eip712Utils } from './eip712_utils'; +import { orderHashUtils } from './order_hash'; import { OrderError } from './types'; import { utils } from './utils'; -/** - * Verifies that the provided signature is valid according to the 0x Protocol smart contracts - * @param data The hex encoded data signed by the supplied signature. - * @param signature A hex encoded 0x Protocol signature made up of: [TypeSpecificData][SignatureType]. - * E.g [vrs][SignatureType.EIP712] - * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. - * @return Whether the signature is valid for the supplied signerAddress and data. - */ -export async function isValidSignatureAsync( - provider: Provider, - data: string, - signature: string, - signerAddress: string, -): Promise<boolean> { - assert.isWeb3Provider('provider', provider); - assert.isHexString('data', data); - assert.isHexString('signature', signature); - assert.isETHAddressHex('signerAddress', signerAddress); - const signatureTypeIndexIfExists = utils.getSignatureTypeIndexIfExists(signature); - if (_.isUndefined(signatureTypeIndexIfExists)) { - throw new Error(`Unrecognized signatureType in signature: ${signature}`); - } +export const signatureUtils = { + /** + * Verifies that the provided signature is valid according to the 0x Protocol smart contracts + * @param data The hex encoded data signed by the supplied signature. + * @param signature A hex encoded 0x Protocol signature made up of: [TypeSpecificData][SignatureType]. + * E.g [vrs][SignatureType.EIP712] + * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. + * @return Whether the signature is valid for the supplied signerAddress and data. + */ + async isValidSignatureAsync( + provider: Provider, + data: string, + signature: string, + signerAddress: string, + ): Promise<boolean> { + assert.isWeb3Provider('provider', provider); + assert.isHexString('data', data); + assert.isHexString('signature', signature); + assert.isETHAddressHex('signerAddress', signerAddress); + const signatureTypeIndexIfExists = utils.getSignatureTypeIndexIfExists(signature); + if (_.isUndefined(signatureTypeIndexIfExists)) { + throw new Error(`Unrecognized signatureType in signature: ${signature}`); + } - switch (signatureTypeIndexIfExists) { - case SignatureType.Illegal: - case SignatureType.Invalid: - return false; + switch (signatureTypeIndexIfExists) { + case SignatureType.Illegal: + case SignatureType.Invalid: + return false; - case SignatureType.EIP712: { - const ecSignature = parseECSignature(signature); - return isValidECSignature(data, ecSignature, signerAddress); - } + case SignatureType.EIP712: { + const ecSignature = signatureUtils.parseECSignature(signature); + return signatureUtils.isValidECSignature(data, ecSignature, signerAddress); + } - case SignatureType.EthSign: { - const ecSignature = parseECSignature(signature); - const prefixedMessageHex = addSignedMessagePrefix(data, SignerType.Default); - return isValidECSignature(prefixedMessageHex, ecSignature, signerAddress); - } + case SignatureType.EthSign: { + const ecSignature = signatureUtils.parseECSignature(signature); + const prefixedMessageHex = signatureUtils.addSignedMessagePrefix(data); + return signatureUtils.isValidECSignature(prefixedMessageHex, ecSignature, signerAddress); + } - case SignatureType.Caller: - // HACK: We currently do not "validate" the caller signature type. - // It can only be validated during Exchange contract execution. - throw new Error('Caller signature type cannot be validated off-chain'); + case SignatureType.Wallet: { + const isValid = await signatureUtils.isValidWalletSignatureAsync( + provider, + data, + signature, + signerAddress, + ); + return isValid; + } - case SignatureType.Wallet: { - const isValid = await isValidWalletSignatureAsync(provider, data, signature, signerAddress); - return isValid; - } + case SignatureType.Validator: { + const isValid = await signatureUtils.isValidValidatorSignatureAsync( + provider, + data, + signature, + signerAddress, + ); + return isValid; + } - case SignatureType.Validator: { - const isValid = await isValidValidatorSignatureAsync(provider, data, signature, signerAddress); - return isValid; - } + case SignatureType.PreSigned: { + return signatureUtils.isValidPresignedSignatureAsync(provider, data, signerAddress); + } - case SignatureType.PreSigned: { - return isValidPresignedSignatureAsync(provider, data, signerAddress); + default: + throw new Error(`Unhandled SignatureType: ${signatureTypeIndexIfExists}`); } - - case SignatureType.Trezor: { - const prefixedMessageHex = addSignedMessagePrefix(data, SignerType.Trezor); - const ecSignature = parseECSignature(signature); - return isValidECSignature(prefixedMessageHex, ecSignature, signerAddress); + }, + /** + * Verifies that the provided presigned signature is valid according to the 0x Protocol smart contracts + * @param provider Web3 provider to use for all JSON RPC requests + * @param data The hex encoded data signed by the supplied signature + * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. + * @return Whether the data was preSigned by the supplied signerAddress + */ + async isValidPresignedSignatureAsync(provider: Provider, data: string, signerAddress: string): Promise<boolean> { + assert.isWeb3Provider('provider', provider); + assert.isHexString('data', data); + assert.isETHAddressHex('signerAddress', signerAddress); + const exchangeContract = new ExchangeContract(artifacts.Exchange.compilerOutput.abi, signerAddress, provider); + const isValid = await exchangeContract.preSigned.callAsync(data, signerAddress); + return isValid; + }, + /** + * Verifies that the provided wallet signature is valid according to the 0x Protocol smart contracts + * @param provider Web3 provider to use for all JSON RPC requests + * @param data The hex encoded data signed by the supplied signature. + * @param signature A hex encoded presigned 0x Protocol signature made up of: [SignatureType.Presigned] + * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. + * @return Whether the data was preSigned by the supplied signerAddress. + */ + async isValidWalletSignatureAsync( + provider: Provider, + data: string, + signature: string, + signerAddress: string, + ): Promise<boolean> { + assert.isWeb3Provider('provider', provider); + assert.isHexString('data', data); + assert.isHexString('signature', signature); + assert.isETHAddressHex('signerAddress', signerAddress); + // tslint:disable-next-line:custom-no-magic-numbers + const signatureWithoutType = signature.slice(-2); + const walletContract = new IWalletContract(artifacts.IWallet.compilerOutput.abi, signerAddress, provider); + const isValid = await walletContract.isValidSignature.callAsync(data, signatureWithoutType); + return isValid; + }, + /** + * Verifies that the provided validator signature is valid according to the 0x Protocol smart contracts + * @param provider Web3 provider to use for all JSON RPC requests + * @param data The hex encoded data signed by the supplied signature. + * @param signature A hex encoded presigned 0x Protocol signature made up of: [SignatureType.Presigned] + * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. + * @return Whether the data was preSigned by the supplied signerAddress. + */ + async isValidValidatorSignatureAsync( + provider: Provider, + data: string, + signature: string, + signerAddress: string, + ): Promise<boolean> { + assert.isWeb3Provider('provider', provider); + assert.isHexString('data', data); + assert.isHexString('signature', signature); + assert.isETHAddressHex('signerAddress', signerAddress); + const validatorSignature = parseValidatorSignature(signature); + const exchangeContract = new ExchangeContract(artifacts.Exchange.compilerOutput.abi, signerAddress, provider); + const isValidatorApproved = await exchangeContract.allowedValidators.callAsync( + signerAddress, + validatorSignature.validatorAddress, + ); + if (!isValidatorApproved) { + throw new Error( + `Validator ${validatorSignature.validatorAddress} was not pre-approved by ${signerAddress}.`, + ); } - default: - throw new Error(`Unhandled SignatureType: ${signatureTypeIndexIfExists}`); - } -} - -/** - * Verifies that the provided presigned signature is valid according to the 0x Protocol smart contracts - * @param data The hex encoded data signed by the supplied signature. - * @param signature A hex encoded presigned 0x Protocol signature made up of: [SignatureType.Presigned] - * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. - * @return Whether the data was preSigned by the supplied signerAddress. - */ -export async function isValidPresignedSignatureAsync( - provider: Provider, - data: string, - signerAddress: string, -): Promise<boolean> { - assert.isWeb3Provider('provider', provider); - assert.isHexString('data', data); - assert.isETHAddressHex('signerAddress', signerAddress); - const exchangeContract = new ExchangeContract(artifacts.Exchange.compilerOutput.abi, signerAddress, provider); - const isValid = await exchangeContract.preSigned.callAsync(data, signerAddress); - return isValid; -} - -/** - * Verifies that the provided wallet signature is valid according to the 0x Protocol smart contracts - * @param data The hex encoded data signed by the supplied signature. - * @param signature A hex encoded presigned 0x Protocol signature made up of: [SignatureType.Presigned] - * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. - * @return Whether the data was preSigned by the supplied signerAddress. - */ -export async function isValidWalletSignatureAsync( - provider: Provider, - data: string, - signature: string, - signerAddress: string, -): Promise<boolean> { - assert.isWeb3Provider('provider', provider); - assert.isHexString('data', data); - assert.isHexString('signature', signature); - assert.isETHAddressHex('signerAddress', signerAddress); - // tslint:disable-next-line:custom-no-magic-numbers - const signatureWithoutType = signature.slice(-2); - const walletContract = new IWalletContract(artifacts.IWallet.compilerOutput.abi, signerAddress, provider); - const isValid = await walletContract.isValidSignature.callAsync(data, signatureWithoutType); - return isValid; -} - -/** - * Verifies that the provided validator signature is valid according to the 0x Protocol smart contracts - * @param data The hex encoded data signed by the supplied signature. - * @param signature A hex encoded presigned 0x Protocol signature made up of: [SignatureType.Presigned] - * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. - * @return Whether the data was preSigned by the supplied signerAddress. - */ -export async function isValidValidatorSignatureAsync( - provider: Provider, - data: string, - signature: string, - signerAddress: string, -): Promise<boolean> { - assert.isWeb3Provider('provider', provider); - assert.isHexString('data', data); - assert.isHexString('signature', signature); - assert.isETHAddressHex('signerAddress', signerAddress); - const validatorSignature = parseValidatorSignature(signature); - const exchangeContract = new ExchangeContract(artifacts.Exchange.compilerOutput.abi, signerAddress, provider); - const isValidatorApproved = await exchangeContract.allowedValidators.callAsync( - signerAddress, - validatorSignature.validatorAddress, - ); - if (!isValidatorApproved) { - throw new Error(`Validator ${validatorSignature.validatorAddress} was not pre-approved by ${signerAddress}.`); - } - - const validatorContract = new IValidatorContract(artifacts.IValidator.compilerOutput.abi, signerAddress, provider); - const isValid = await validatorContract.isValidSignature.callAsync( - data, - signerAddress, - validatorSignature.signature, - ); - return isValid; -} - -/** - * Checks if the supplied elliptic curve signature corresponds to signing `data` with - * the private key corresponding to `signerAddress` - * @param data The hex encoded data signed by the supplied signature. - * @param signature An object containing the elliptic curve signature parameters. - * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. - * @return Whether the ECSignature is valid. - */ -export function isValidECSignature(data: string, signature: ECSignature, signerAddress: string): boolean { - assert.isHexString('data', data); - assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema); - assert.isETHAddressHex('signerAddress', signerAddress); - - const msgHashBuff = ethUtil.toBuffer(data); - try { - const pubKey = ethUtil.ecrecover( - msgHashBuff, - signature.v, - ethUtil.toBuffer(signature.r), - ethUtil.toBuffer(signature.s), + const validatorContract = new IValidatorContract( + artifacts.IValidator.compilerOutput.abi, + signerAddress, + provider, ); - const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey)); - return retrievedAddress === signerAddress; - } catch (err) { - return false; - } -} - -/** - * Signs an orderHash and returns it's elliptic curve signature and signature type. - * This method currently supports TestRPC, Geth and Parity above and below V1.6.6 - * @param orderHash Hex encoded orderHash to sign. - * @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address - * must be available via the Provider supplied to 0x.js. - * @param signerType Different signers add/require different prefixes to be prepended to the message being signed. - * Since we cannot know ahead of time which signer you are using, you must supply a SignerType. - * @return A hex encoded string containing the Elliptic curve signature generated by signing the orderHash and the Signature Type. - */ -export async function ecSignOrderHashAsync( - provider: Provider, - orderHash: string, - signerAddress: string, - signerType: SignerType, -): Promise<string> { - assert.isWeb3Provider('provider', provider); - assert.isHexString('orderHash', orderHash); - assert.isETHAddressHex('signerAddress', signerAddress); - const web3Wrapper = new Web3Wrapper(provider); - await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); - const normalizedSignerAddress = signerAddress.toLowerCase(); - - let msgHashHex = orderHash; - const prefixedMsgHashHex = addSignedMessagePrefix(orderHash, signerType); - // Metamask incorrectly implements eth_sign and does not prefix the message as per the spec - // Source: https://github.com/MetaMask/metamask-extension/commit/a9d36860bec424dcee8db043d3e7da6a5ff5672e - if (signerType === SignerType.Metamask) { - msgHashHex = prefixedMsgHashHex; - } - const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHashHex); + const isValid = await validatorContract.isValidSignature.callAsync( + data, + signerAddress, + validatorSignature.signature, + ); + return isValid; + }, + /** + * Checks if the supplied elliptic curve signature corresponds to signing `data` with + * the private key corresponding to `signerAddress` + * @param data The hex encoded data signed by the supplied signature. + * @param signature An object containing the elliptic curve signature parameters. + * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. + * @return Whether the ECSignature is valid. + */ + isValidECSignature(data: string, signature: ECSignature, signerAddress: string): boolean { + assert.isHexString('data', data); + assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema); + assert.isETHAddressHex('signerAddress', signerAddress); + const normalizedSignerAddress = signerAddress.toLowerCase(); - // HACK: There is no consensus on whether the signatureHex string should be formatted as - // v + r + s OR r + s + v, and different clients (even different versions of the same client) - // return the signature params in different orders. In order to support all client implementations, - // we parse the signature in both ways, and evaluate if either one is a valid signature. - // r + s + v is the most prevalent format from eth_sign, so we attempt this first. - // tslint:disable-next-line:custom-no-magic-numbers - const validVParamValues = [27, 28]; - const ecSignatureRSV = parseSignatureHexAsRSV(signature); - if (_.includes(validVParamValues, ecSignatureRSV.v)) { - const isValidRSVSignature = isValidECSignature(prefixedMsgHashHex, ecSignatureRSV, normalizedSignerAddress); - if (isValidRSVSignature) { - const convertedSignatureHex = convertECSignatureToSignatureHex(ecSignatureRSV, signerType); - return convertedSignatureHex; + const msgHashBuff = ethUtil.toBuffer(data); + try { + const pubKey = ethUtil.ecrecover( + msgHashBuff, + signature.v, + ethUtil.toBuffer(signature.r), + ethUtil.toBuffer(signature.s), + ); + const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey)); + const normalizedRetrievedAddress = retrievedAddress.toLowerCase(); + return normalizedRetrievedAddress === normalizedSignerAddress; + } catch (err) { + return false; } - } - const ecSignatureVRS = parseSignatureHexAsVRS(signature); - if (_.includes(validVParamValues, ecSignatureVRS.v)) { - const isValidVRSSignature = isValidECSignature(prefixedMsgHashHex, ecSignatureVRS, normalizedSignerAddress); - if (isValidVRSSignature) { - const convertedSignatureHex = convertECSignatureToSignatureHex(ecSignatureVRS, signerType); - return convertedSignatureHex; + }, + /** + * Signs an order and returns a SignedOrder. First `eth_signTypedData` is requested + * then a fallback to `eth_sign` if not available on the supplied provider. + * @param order The Order to sign. + * @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address + * must be available via the supplied Provider. + * @return A SignedOrder containing the order and Elliptic curve signature with Signature Type. + */ + async ecSignOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> { + assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]); + try { + const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress); + return signedOrder; + } catch (err) { + // HACK: We are unable to handle specific errors thrown since provider is not an object + // under our control. It could be Metamask Web3, Ethers, or any general RPC provider. + // We check for a user denying the signature request in a way that supports Metamask and + // Coinbase Wallet. Unfortunately for signers with a different error message, + // they will receive two signature requests. + if (err.message.includes('User denied message signature')) { + throw err; + } + const orderHash = orderHashUtils.getOrderHashHex(order); + const signatureHex = await signatureUtils.ecSignHashAsync(provider, orderHash, signerAddress); + const signedOrder = { + ...order, + signature: signatureHex, + }; + return signedOrder; } - } + }, + /** + * Signs an order using `eth_signTypedData` and returns a SignedOrder. + * @param order The Order to sign. + * @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address + * must be available via the supplied Provider. + * @return A SignedOrder containing the order and Elliptic curve signature with Signature Type. + */ + async ecSignTypedDataOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> { + assert.isWeb3Provider('provider', provider); + assert.isETHAddressHex('signerAddress', signerAddress); + assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]); + const web3Wrapper = new Web3Wrapper(provider); + await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); + const normalizedSignerAddress = signerAddress.toLowerCase(); + const typedData = eip712Utils.createOrderTypedData(order); + try { + const signature = await web3Wrapper.signTypedDataAsync(normalizedSignerAddress, typedData); + const ecSignatureRSV = parseSignatureHexAsRSV(signature); + const signatureBuffer = Buffer.concat([ + ethUtil.toBuffer(ecSignatureRSV.v), + ethUtil.toBuffer(ecSignatureRSV.r), + ethUtil.toBuffer(ecSignatureRSV.s), + ethUtil.toBuffer(SignatureType.EIP712), + ]); + const signatureHex = `0x${signatureBuffer.toString('hex')}`; + return { + ...order, + signature: signatureHex, + }; + } catch (err) { + // Detect if Metamask to transition users to the MetamaskSubprovider + if ((provider as any).isMetaMask) { + throw new Error(OrderError.InvalidMetamaskSigner); + } else { + throw err; + } + } + }, + /** + * Signs a hash using `eth_sign` and returns its elliptic curve signature and signature type. + * @param msgHash Hex encoded message to sign. + * @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address + * must be available via the supplied Provider. + * @return A hex encoded string containing the Elliptic curve signature generated by signing the msgHash and the Signature Type. + */ + async ecSignHashAsync(provider: Provider, msgHash: string, signerAddress: string): Promise<string> { + assert.isWeb3Provider('provider', provider); + assert.isHexString('msgHash', msgHash); + assert.isETHAddressHex('signerAddress', signerAddress); + const web3Wrapper = new Web3Wrapper(provider); + await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); + const normalizedSignerAddress = signerAddress.toLowerCase(); + const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHash); + const prefixedMsgHashHex = signatureUtils.addSignedMessagePrefix(msgHash); - throw new Error(OrderError.InvalidSignature); -} -/** - * Combines ECSignature with V,R,S and the relevant signature type for use in 0x protocol - * @param ecSignature The ECSignature of the signed data - * @param signerType The SignerType of the signed data - * @return Hex encoded string of signature (v,r,s) with Signature Type - */ -export function convertECSignatureToSignatureHex(ecSignature: ECSignature, signerType: SignerType): string { - const signatureBuffer = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ethUtil.toBuffer(ecSignature.r), - ethUtil.toBuffer(ecSignature.s), - ]); - const signatureHex = `0x${signatureBuffer.toString('hex')}`; - let signatureType; - switch (signerType) { - case SignerType.Metamask: - case SignerType.Ledger: - case SignerType.Default: { - signatureType = SignatureType.EthSign; - break; + // HACK: There is no consensus on whether the signatureHex string should be formatted as + // v + r + s OR r + s + v, and different clients (even different versions of the same client) + // return the signature params in different orders. In order to support all client implementations, + // we parse the signature in both ways, and evaluate if either one is a valid signature. + // r + s + v is the most prevalent format from eth_sign, so we attempt this first. + // tslint:disable-next-line:custom-no-magic-numbers + const validVParamValues = [27, 28]; + const ecSignatureRSV = parseSignatureHexAsRSV(signature); + if (_.includes(validVParamValues, ecSignatureRSV.v)) { + const isValidRSVSignature = signatureUtils.isValidECSignature( + prefixedMsgHashHex, + ecSignatureRSV, + normalizedSignerAddress, + ); + if (isValidRSVSignature) { + const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(ecSignatureRSV); + return convertedSignatureHex; + } } - case SignerType.Trezor: { - signatureType = SignatureType.Trezor; - break; + const ecSignatureVRS = parseSignatureHexAsVRS(signature); + if (_.includes(validVParamValues, ecSignatureVRS.v)) { + const isValidVRSSignature = signatureUtils.isValidECSignature( + prefixedMsgHashHex, + ecSignatureVRS, + normalizedSignerAddress, + ); + if (isValidVRSSignature) { + const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(ecSignatureVRS); + return convertedSignatureHex; + } } - default: - throw new Error(`Unrecognized SignerType: ${signerType}`); - } - const signatureWithType = convertToSignatureWithType(signatureHex, signatureType); - return signatureWithType; -} -/** - * Combines the signature proof and the Signature Type. - * @param signature The hex encoded signature proof - * @param signatureType The signature type, i.e EthSign, Trezor, Wallet etc. - * @return Hex encoded string of signature proof with Signature Type - */ -export function convertToSignatureWithType(signature: string, signatureType: SignatureType): string { - const signatureBuffer = Buffer.concat([ethUtil.toBuffer(signature), ethUtil.toBuffer(signatureType)]); - const signatureHex = `0x${signatureBuffer.toString('hex')}`; - return signatureHex; -} -/** - * Adds the relevant prefix to the message being signed. - * @param message Message to sign - * @param signerType The type of message prefix to add for a given SignerType. Different signers expect - * specific message prefixes. - * @return Prefixed message - */ -export function addSignedMessagePrefix(message: string, signerType: SignerType = SignerType.Default): string { - assert.isString('message', message); - assert.doesBelongToStringEnum('signerType', signerType, SignerType); - switch (signerType) { - case SignerType.Metamask: - case SignerType.Ledger: - case SignerType.Default: { - const msgBuff = ethUtil.toBuffer(message); - const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff); - const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff); - return prefixedMsgHex; - } - case SignerType.Trezor: { - const msgBuff = ethUtil.toBuffer(message); - const prefixedMsgBuff = hashTrezorPersonalMessage(msgBuff); - const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff); - return prefixedMsgHex; + // Detect if Metamask to transition users to the MetamaskSubprovider + if ((provider as any).isMetaMask) { + throw new Error(OrderError.InvalidMetamaskSigner); + } else { + throw new Error(OrderError.InvalidSignature); } - default: - throw new Error(`Unrecognized SignerType: ${signerType}`); - } -} + }, + /** + * Combines ECSignature with V,R,S and the EthSign signature type for use in 0x protocol + * @param ecSignature The ECSignature of the signed data + * @return Hex encoded string of signature (v,r,s) with Signature Type + */ + convertECSignatureToSignatureHex(ecSignature: ECSignature): string { + const signatureBuffer = Buffer.concat([ + ethUtil.toBuffer(ecSignature.v), + ethUtil.toBuffer(ecSignature.r), + ethUtil.toBuffer(ecSignature.s), + ]); + const signatureHex = `0x${signatureBuffer.toString('hex')}`; + const signatureWithType = signatureUtils.convertToSignatureWithType(signatureHex, SignatureType.EthSign); + return signatureWithType; + }, + /** + * Combines the signature proof and the Signature Type. + * @param signature The hex encoded signature proof + * @param signatureType The signature type, i.e EthSign, Wallet etc. + * @return Hex encoded string of signature proof with Signature Type + */ + convertToSignatureWithType(signature: string, signatureType: SignatureType): string { + const signatureBuffer = Buffer.concat([ethUtil.toBuffer(signature), ethUtil.toBuffer(signatureType)]); + const signatureHex = `0x${signatureBuffer.toString('hex')}`; + return signatureHex; + }, + /** + * Adds the relevant prefix to the message being signed. + * @param message Message to sign + * @return Prefixed message + */ + addSignedMessagePrefix(message: string): string { + assert.isString('message', message); + const msgBuff = ethUtil.toBuffer(message); + const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff); + const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff); + return prefixedMsgHex; + }, + /** + * Parse a 0x protocol hex-encoded signature string into its ECSignature components + * @param signature A hex encoded ecSignature 0x Protocol signature + * @return An ECSignature object with r,s,v parameters + */ + parseECSignature(signature: string): ECSignature { + assert.isHexString('signature', signature); + const ecSignatureTypes = [SignatureType.EthSign, SignatureType.EIP712]; + assert.isOneOfExpectedSignatureTypes(signature, ecSignatureTypes); -/** - * Parse a 0x protocol hex-encoded signature string into it's ECSignature components - * @param signature A hex encoded ecSignature 0x Protocol signature - * @return An ECSignature object with r,s,v parameters - */ -export function parseECSignature(signature: string): ECSignature { - assert.isHexString('signature', signature); - const ecSignatureTypes = [SignatureType.EthSign, SignatureType.EIP712, SignatureType.Trezor]; - assert.isOneOfExpectedSignatureTypes(signature, ecSignatureTypes); + // tslint:disable-next-line:custom-no-magic-numbers + const vrsHex = signature.slice(0, -2); + const ecSignature = parseSignatureHexAsVRS(vrsHex); - // tslint:disable-next-line:custom-no-magic-numbers - const vrsHex = signature.slice(0, -2); - const ecSignature = parseSignatureHexAsVRS(vrsHex); - - return ecSignature; -} - -function hashTrezorPersonalMessage(message: Buffer): Buffer { - const prefix = ethUtil.toBuffer('\x19Ethereum Signed Message:\n' + String.fromCharCode(message.byteLength)); - return ethUtil.sha3(Buffer.concat([prefix, message])); -} + return ecSignature; + }, +}; function parseValidatorSignature(signature: string): ValidatorSignature { assert.isOneOfExpectedSignatureTypes(signature, [SignatureType.Validator]); diff --git a/packages/order-utils/src/sorting_utils.ts b/packages/order-utils/src/sorting_utils.ts index 8811bcaf8..1de24264f 100644 --- a/packages/order-utils/src/sorting_utils.ts +++ b/packages/order-utils/src/sorting_utils.ts @@ -1,6 +1,6 @@ -import { schemas } from '@0xproject/json-schemas'; -import { Order } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +import { schemas } from '@0x/json-schemas'; +import { Order } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { assert } from './assert'; @@ -32,7 +32,7 @@ export const sortingUtils = { * the makerAsset and WETH as the takerAsset. * @return The input orders sorted by rate in ascending order */ - sortFeeOrdersByFeeAdjustedRate(feeOrders: Order[]): Order[] { + sortFeeOrdersByFeeAdjustedRate<T extends Order>(feeOrders: T[]): T[] { assert.doesConformToSchema('feeOrders', feeOrders, schemas.ordersSchema); const rateCalculator = rateUtils.getFeeAdjustedRateOfFeeOrder.bind(rateUtils); const sortedOrders = sortOrders(feeOrders, rateCalculator); diff --git a/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts b/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts index 5a2c1d7ff..f42a76d0c 100644 --- a/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts +++ b/packages/order-utils/src/store/balance_and_proxy_allowance_lazy_store.ts @@ -1,5 +1,5 @@ -import { AssetProxyId } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +import { AssetProxyId } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { AbstractBalanceAndProxyAllowanceFetcher } from '../abstract/abstract_balance_and_proxy_allowance_fetcher'; @@ -21,11 +21,21 @@ export class BalanceAndProxyAllowanceLazyStore implements AbstractBalanceAndProx [userAddress: string]: BigNumber; }; }; + /** + * Instantiates a BalanceAndProxyAllowanceLazyStore + * @param balanceAndProxyAllowanceFetcher Class the implements the AbstractBalanceAndProxyAllowanceFetcher + * @return Instance of BalanceAndProxyAllowanceLazyStore + */ constructor(balanceAndProxyAllowanceFetcher: AbstractBalanceAndProxyAllowanceFetcher) { this._balanceAndProxyAllowanceFetcher = balanceAndProxyAllowanceFetcher; this._balance = {}; this._proxyAllowance = {}; } + /** + * Get a users balance of an asset + * @param assetData AssetData of interest + * @param userAddress Ethereum address of interest + */ public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { if (_.isUndefined(this._balance[assetData]) || _.isUndefined(this._balance[assetData][userAddress])) { const balance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(assetData, userAddress); @@ -34,12 +44,22 @@ export class BalanceAndProxyAllowanceLazyStore implements AbstractBalanceAndProx const cachedBalance = this._balance[assetData][userAddress]; return cachedBalance; } + /** + * Set the balance of an asset for a user + * @param assetData AssetData of interest + * @param userAddress Ethereum address of interest + */ public setBalance(assetData: string, userAddress: string, balance: BigNumber): void { if (_.isUndefined(this._balance[assetData])) { this._balance[assetData] = {}; } this._balance[assetData][userAddress] = balance; } + /** + * Clear the balance of an asset for a user + * @param assetData AssetData of interest + * @param userAddress Ethereum address of interest + */ public deleteBalance(assetData: string, userAddress: string): void { if (!_.isUndefined(this._balance[assetData])) { delete this._balance[assetData][userAddress]; @@ -48,6 +68,11 @@ export class BalanceAndProxyAllowanceLazyStore implements AbstractBalanceAndProx } } } + /** + * Get the 0x asset proxy allowance + * @param assetData AssetData of interest + * @param userAddress Ethereum address of interest + */ public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { if ( _.isUndefined(this._proxyAllowance[assetData]) || @@ -62,12 +87,22 @@ export class BalanceAndProxyAllowanceLazyStore implements AbstractBalanceAndProx const cachedProxyAllowance = this._proxyAllowance[assetData][userAddress]; return cachedProxyAllowance; } + /** + * Set the 0x asset proxy allowance + * @param assetData AssetData of interest + * @param userAddress Ethereum address of interest + */ public setProxyAllowance(assetData: string, userAddress: string, proxyAllowance: BigNumber): void { if (_.isUndefined(this._proxyAllowance[assetData])) { this._proxyAllowance[assetData] = {}; } this._proxyAllowance[assetData][userAddress] = proxyAllowance; } + /** + * Clear the 0x asset proxy allowance + * @param assetData AssetData of interest + * @param userAddress Ethereum address of interest + */ public deleteProxyAllowance(assetData: string, userAddress: string): void { if (!_.isUndefined(this._proxyAllowance[assetData])) { delete this._proxyAllowance[assetData][userAddress]; @@ -76,6 +111,11 @@ export class BalanceAndProxyAllowanceLazyStore implements AbstractBalanceAndProx } } } + /** + * Clear all ERC721 0x proxy allowances a user has on all items of a specific ERC721 contract + * @param tokenAddress ERc721 token address + * @param userAddress Owner Ethereum address + */ public deleteAllERC721ProxyAllowance(tokenAddress: string, userAddress: string): void { for (const assetData in this._proxyAllowance) { if (this._proxyAllowance.hasOwnProperty(assetData)) { @@ -90,6 +130,9 @@ export class BalanceAndProxyAllowanceLazyStore implements AbstractBalanceAndProx } } } + /** + * Delete all balances & allowances + */ public deleteAll(): void { this._balance = {}; this._proxyAllowance = {}; diff --git a/packages/order-utils/src/store/order_filled_cancelled_lazy_store.ts b/packages/order-utils/src/store/order_filled_cancelled_lazy_store.ts index 336c6d0ba..1d84ffdaa 100644 --- a/packages/order-utils/src/store/order_filled_cancelled_lazy_store.ts +++ b/packages/order-utils/src/store/order_filled_cancelled_lazy_store.ts @@ -1,4 +1,4 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { AbstractOrderFilledCancelledFetcher } from '../abstract/abstract_order_filled_cancelled_fetcher'; @@ -15,11 +15,21 @@ export class OrderFilledCancelledLazyStore implements AbstractOrderFilledCancell private _isCancelled: { [orderHash: string]: boolean; }; + /** + * Instantiate a OrderFilledCancelledLazyStore + * @param orderFilledCancelledFetcher Class instance that implements the AbstractOrderFilledCancelledFetcher + * @returns An instance of OrderFilledCancelledLazyStore + */ constructor(orderFilledCancelledFetcher: AbstractOrderFilledCancelledFetcher) { this._orderFilledCancelledFetcher = orderFilledCancelledFetcher; this._filledTakerAmount = {}; this._isCancelled = {}; } + /** + * Get the filledTakerAssetAmount of an order + * @param orderHash OrderHash from order of interest + * @return filledTakerAssetAmount + */ public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> { if (_.isUndefined(this._filledTakerAmount[orderHash])) { const filledTakerAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash); @@ -28,12 +38,26 @@ export class OrderFilledCancelledLazyStore implements AbstractOrderFilledCancell const cachedFilledTakerAmount = this._filledTakerAmount[orderHash]; return cachedFilledTakerAmount; } + /** + * Set the filledTakerAssetAmount of an order + * @param orderHash OrderHash from order of interest + * @param filledTakerAmount Desired filledTakerAssetAmount + */ public setFilledTakerAmount(orderHash: string, filledTakerAmount: BigNumber): void { this._filledTakerAmount[orderHash] = filledTakerAmount; } + /** + * Clear the filledTakerAssetAmount of an order + * @param orderHash OrderHash from order of interest + */ public deleteFilledTakerAmount(orderHash: string): void { delete this._filledTakerAmount[orderHash]; } + /** + * Check if an order has been cancelled + * @param orderHash OrderHash from order of interest + * @return Whether the order has been cancelled + */ public async getIsCancelledAsync(orderHash: string): Promise<boolean> { if (_.isUndefined(this._isCancelled[orderHash])) { const isCancelled = await this._orderFilledCancelledFetcher.isOrderCancelledAsync(orderHash); @@ -42,22 +66,43 @@ export class OrderFilledCancelledLazyStore implements AbstractOrderFilledCancell const cachedIsCancelled = this._isCancelled[orderHash]; // tslint:disable-line:boolean-naming return cachedIsCancelled; } + /** + * Set whether an order has been cancelled or not + * @param orderHash OrderHash from order of interest + * @param isCancelled Whether this order should be cancelled or not + */ public setIsCancelled(orderHash: string, isCancelled: boolean): void { this._isCancelled[orderHash] = isCancelled; } + /** + * Clear whether the order has been cancelled if already set + * @param orderHash OrderHash from order of interest + */ public deleteIsCancelled(orderHash: string): void { delete this._isCancelled[orderHash]; } + /** + * Clear all filled/cancelled state + */ public deleteAll(): void { this.deleteAllFilled(); this.deleteAllIsCancelled(); } + /** + * Clear all cancelled state + */ public deleteAllIsCancelled(): void { this._isCancelled = {}; } + /** + * Clear all filled state + */ public deleteAllFilled(): void { this._filledTakerAmount = {}; } + /** + * Get the ZRX assetData + */ public getZRXAssetData(): string { const zrxAssetData = this._orderFilledCancelledFetcher.getZRXAssetData(); return zrxAssetData; diff --git a/packages/order-utils/src/types.ts b/packages/order-utils/src/types.ts index 2e9c79d80..55ec553db 100644 --- a/packages/order-utils/src/types.ts +++ b/packages/order-utils/src/types.ts @@ -1,7 +1,8 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from '@0x/utils'; export enum OrderError { InvalidSignature = 'INVALID_SIGNATURE', + InvalidMetamaskSigner = "MetaMask provider must be wrapped in a MetamaskSubprovider (from the '@0x/subproviders' package) in order to work with this method.", } export enum TradeSide { @@ -14,24 +15,6 @@ export enum TransferType { Fee = 'fee', } -export interface EIP712Parameter { - name: string; - type: EIP712Types; -} - -export interface EIP712Schema { - name: string; - parameters: EIP712Parameter[]; -} - -export enum EIP712Types { - Address = 'address', - Bytes = 'bytes', - Bytes32 = 'bytes32', - String = 'string', - Uint256 = 'uint256', -} - export interface CreateOrderOpts { takerAddress?: string; senderAddress?: string; @@ -44,7 +27,7 @@ export interface CreateOrderOpts { /** * remainingFillableMakerAssetAmount: An array of BigNumbers corresponding to the `orders` parameter. - * You can use `OrderStateUtils` `@0xproject/order-utils` to perform blockchain lookups for these values. + * You can use `OrderStateUtils` `@0x/order-utils` to perform blockchain lookups for these values. * Defaults to `makerAssetAmount` values from the orders param. * slippageBufferAmount: An additional amount of makerAsset to be covered by the result in case of trade collisions or partial fills. * Defaults to 0 @@ -56,10 +39,10 @@ export interface FindOrdersThatCoverMakerAssetFillAmountOpts { /** * remainingFillableMakerAssetAmount: An array of BigNumbers corresponding to the `orders` parameter. - * You can use `OrderStateUtils` `@0xproject/order-utils` to perform blockchain lookups for these values. + * You can use `OrderStateUtils` `@0x/order-utils` to perform blockchain lookups for these values. * Defaults to `makerAssetAmount` values from the orders param. * remainingFillableFeeAmounts: An array of BigNumbers corresponding to the feeOrders parameter. - * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups for these values. + * You can use OrderStateUtils @0x/order-utils to perform blockchain lookups for these values. * Defaults to `makerAssetAmount` values from the feeOrders param. * slippageBufferAmount: An additional amount of fee to be covered by the result in case of trade collisions or partial fills. * Defaults to 0 @@ -69,3 +52,15 @@ export interface FindFeeOrdersThatCoverFeesForTargetOrdersOpts { remainingFillableFeeAmounts?: BigNumber[]; slippageBufferAmount?: BigNumber; } + +export interface FeeOrdersAndRemainingFeeAmount<T> { + resultFeeOrders: T[]; + feeOrdersRemainingFillableMakerAssetAmounts: BigNumber[]; + remainingFeeAmount: BigNumber; +} + +export interface OrdersAndRemainingFillAmount<T> { + resultOrders: T[]; + ordersRemainingFillableMakerAssetAmounts: BigNumber[]; + remainingFillAmount: BigNumber; +} diff --git a/packages/order-utils/src/utils.ts b/packages/order-utils/src/utils.ts index 7aaaf0609..6b2261001 100644 --- a/packages/order-utils/src/utils.ts +++ b/packages/order-utils/src/utils.ts @@ -1,4 +1,4 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from '@0x/utils'; export const utils = { getSignatureTypeIndexIfExists(signature: string): number { @@ -12,7 +12,7 @@ export const utils = { const milisecondsInSecond = 1000; return new BigNumber(Date.now() / milisecondsInSecond).round(); }, - getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { + getPartialAmountFloor(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { const fillMakerTokenAmount = numerator .mul(target) .div(denominator) diff --git a/packages/order-utils/test/asset_data_utils_test.ts b/packages/order-utils/test/asset_data_utils_test.ts new file mode 100644 index 000000000..f175b7a38 --- /dev/null +++ b/packages/order-utils/test/asset_data_utils_test.ts @@ -0,0 +1,50 @@ +import * as chai from 'chai'; + +import { ERC20AssetData, ERC721AssetData } from '@0x/types'; +import { BigNumber } from '@0x/utils'; + +import { assetDataUtils } from '../src/asset_data_utils'; + +import { chaiSetup } from './utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +const KNOWN_ENCODINGS = [ + { + address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48', + assetData: '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48', + }, + { + address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48', + tokenId: new BigNumber(1), + assetData: + '0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001', + }, +]; + +const ERC20_ASSET_PROXY_ID = '0xf47261b0'; +const ERC721_ASSET_PROXY_ID = '0x02571792'; + +describe('assetDataUtils', () => { + it('should encode ERC20', () => { + const assetData = assetDataUtils.encodeERC20AssetData(KNOWN_ENCODINGS[0].address); + expect(assetData).to.equal(KNOWN_ENCODINGS[0].assetData); + }); + it('should decode ERC20', () => { + const assetData: ERC20AssetData = assetDataUtils.decodeERC20AssetData(KNOWN_ENCODINGS[0].assetData); + expect(assetData.tokenAddress).to.equal(KNOWN_ENCODINGS[0].address); + expect(assetData.assetProxyId).to.equal(ERC20_ASSET_PROXY_ID); + }); + it('should encode ERC721', () => { + const assetData = assetDataUtils.encodeERC721AssetData(KNOWN_ENCODINGS[1].address, KNOWN_ENCODINGS[1] + .tokenId as BigNumber); + expect(assetData).to.equal(KNOWN_ENCODINGS[1].assetData); + }); + it('should decode ERC721', () => { + const assetData: ERC721AssetData = assetDataUtils.decodeERC721AssetData(KNOWN_ENCODINGS[1].assetData); + expect(assetData.tokenAddress).to.equal(KNOWN_ENCODINGS[1].address); + expect(assetData.assetProxyId).to.equal(ERC721_ASSET_PROXY_ID); + expect(assetData.tokenId).to.be.bignumber.equal(KNOWN_ENCODINGS[1].tokenId); + }); +}); diff --git a/packages/order-utils/test/eip712_utils_test.ts b/packages/order-utils/test/eip712_utils_test.ts new file mode 100644 index 000000000..a54e49958 --- /dev/null +++ b/packages/order-utils/test/eip712_utils_test.ts @@ -0,0 +1,44 @@ +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; +import 'mocha'; + +import { constants } from '../src/constants'; +import { eip712Utils } from '../src/eip712_utils'; + +import { chaiSetup } from './utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe('EIP712 Utils', () => { + describe('createTypedData', () => { + it('adds in the EIP712DomainSeparator', () => { + const primaryType = 'Test'; + const typedData = eip712Utils.createTypedData( + primaryType, + { Test: [{ name: 'testValue', type: 'uint256' }] }, + { testValue: '1' }, + constants.NULL_ADDRESS, + ); + expect(typedData.domain).to.not.be.undefined(); + expect(typedData.types.EIP712Domain).to.not.be.undefined(); + const domainObject = typedData.domain; + expect(domainObject.name).to.eq(constants.EIP712_DOMAIN_NAME); + expect(typedData.primaryType).to.eq(primaryType); + }); + }); + describe('createTypedData', () => { + it('adds in the EIP712DomainSeparator', () => { + const typedData = eip712Utils.createZeroExTransactionTypedData( + { + salt: new BigNumber('0'), + data: constants.NULL_BYTES, + signerAddress: constants.NULL_ADDRESS, + }, + constants.NULL_ADDRESS, + ); + expect(typedData.primaryType).to.eq(constants.EIP712_ZEROEX_TRANSACTION_SCHEMA.name); + expect(typedData.types.EIP712Domain).to.not.be.undefined(); + }); + }); +}); diff --git a/packages/order-utils/test/exchange_transfer_simulator_test.ts b/packages/order-utils/test/exchange_transfer_simulator_test.ts index f5c18cdb9..c26eb1907 100644 --- a/packages/order-utils/test/exchange_transfer_simulator_test.ts +++ b/packages/order-utils/test/exchange_transfer_simulator_test.ts @@ -1,15 +1,13 @@ -import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils'; -import { ExchangeContractErrs } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +import { DummyERC20TokenContract, ERC20ProxyContract, ERC20TokenContract } from '@0x/abi-gen-wrappers'; +import * as artifacts from '@0x/contract-artifacts'; +import { BlockchainLifecycle, devConstants } from '@0x/dev-utils'; +import { ExchangeContractErrs } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import * as chai from 'chai'; -import { artifacts } from '../src/artifacts'; import { assetDataUtils } from '../src/asset_data_utils'; import { constants } from '../src/constants'; import { ExchangeTransferSimulator } from '../src/exchange_transfer_simulator'; -import { DummyERC20TokenContract } from '../src/generated_contract_wrappers/dummy_erc20_token'; -import { ERC20ProxyContract } from '../src/generated_contract_wrappers/erc20_proxy'; -import { ERC20TokenContract } from '../src/generated_contract_wrappers/erc20_token'; import { BalanceAndProxyAllowanceLazyStore } from '../src/store/balance_and_proxy_allowance_lazy_store'; import { TradeSide, TransferType } from '../src/types'; diff --git a/packages/order-utils/test/market_utils_test.ts b/packages/order-utils/test/market_utils_test.ts index 109420a02..42ea195bb 100644 --- a/packages/order-utils/test/market_utils_test.ts +++ b/packages/order-utils/test/market_utils_test.ts @@ -1,8 +1,9 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from '@0x/utils'; import * as chai from 'chai'; import 'mocha'; -import { constants, marketUtils } from '../src'; +import { marketUtils } from '../src'; +import { constants } from '../src/constants'; import { chaiSetup } from './utils/chai_setup'; import { testOrderFactory } from './utils/test_order_factory'; @@ -139,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); }); }); @@ -162,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)); }); }); @@ -183,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); }); }); @@ -204,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); }); }); @@ -230,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); }); }); @@ -254,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)); }); }); diff --git a/packages/order-utils/test/order_hash_test.ts b/packages/order-utils/test/order_hash_test.ts index 0a6be83d0..a85d4c81a 100644 --- a/packages/order-utils/test/order_hash_test.ts +++ b/packages/order-utils/test/order_hash_test.ts @@ -1,9 +1,11 @@ -import { Order } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +import { Order } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import * as chai from 'chai'; import 'mocha'; -import { constants, orderHashUtils } from '../src'; +import { orderHashUtils } from '../src'; + +import { constants } from '../src/constants'; import { chaiSetup } from './utils/chai_setup'; @@ -33,6 +35,20 @@ describe('Order hashing', () => { const orderHash = orderHashUtils.getOrderHashHex(order); expect(orderHash).to.be.equal(expectedOrderHash); }); + it('calculates the order hash if amounts are strings', async () => { + // It's common for developers using javascript to provide the amounts + // as strings. Since we eventually toString() the BigNumber + // before encoding we should result in the same orderHash in this scenario + // tslint:disable-next-line:no-unnecessary-type-assertion + const orderHash = orderHashUtils.getOrderHashHex({ + ...order, + makerAssetAmount: '0', + takerAssetAmount: '0', + makerFee: '0', + takerFee: '0', + } as any); + expect(orderHash).to.be.equal(expectedOrderHash); + }); it('throws a readable error message if taker format is invalid', async () => { const orderWithInvalidtakerFormat = { ...order, diff --git a/packages/order-utils/test/order_state_utils_test.ts b/packages/order-utils/test/order_state_utils_test.ts index 91ef23b69..39c4c362f 100644 --- a/packages/order-utils/test/order_state_utils_test.ts +++ b/packages/order-utils/test/order_state_utils_test.ts @@ -1,4 +1,4 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from '@0x/utils'; import * as chai from 'chai'; import 'mocha'; @@ -120,5 +120,25 @@ describe('OrderStateUtils', () => { const orderState = await orderStateUtils.getOpenOrderStateAsync(signedOrder); expect(orderState.isValid).to.eq(false); }); + it('should include the transactionHash in orderState if supplied in method invocation', async () => { + const makerAssetAmount = new BigNumber(10); + const takerAssetAmount = new BigNumber(10000000000000000); + const takerBalance = takerAssetAmount; + const orderFilledAmount = new BigNumber(0); + const mockBalanceFetcher = buildMockBalanceFetcher(takerBalance); + const mockOrderFilledFetcher = buildMockOrderFilledFetcher(orderFilledAmount); + const [signedOrder] = testOrderFactory.generateTestSignedOrders( + { + makerAssetAmount, + takerAssetAmount, + }, + 1, + ); + + const orderStateUtils = new OrderStateUtils(mockBalanceFetcher, mockOrderFilledFetcher); + const transactionHash = '0xdeadbeef'; + const orderState = await orderStateUtils.getOpenOrderStateAsync(signedOrder, transactionHash); + expect(orderState.transactionHash).to.eq(transactionHash); + }); }); }); diff --git a/packages/order-utils/test/order_validation_utils_test.ts b/packages/order-utils/test/order_validation_utils_test.ts index d3ff867d7..d4d12a6a7 100644 --- a/packages/order-utils/test/order_validation_utils_test.ts +++ b/packages/order-utils/test/order_validation_utils_test.ts @@ -1,4 +1,4 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from '@0x/utils'; import * as chai from 'chai'; import 'mocha'; @@ -16,7 +16,7 @@ describe('OrderValidationUtils', () => { const denominator = new BigNumber(999); const target = new BigNumber(50); // rounding error = ((20*50/999) - floor(20*50/999)) / (20*50/999) = 0.1% - const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target); + const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); expect(isRoundingError).to.be.false(); }); @@ -25,7 +25,7 @@ describe('OrderValidationUtils', () => { const denominator = new BigNumber(9991); const target = new BigNumber(500); // rounding error = ((20*500/9991) - floor(20*500/9991)) / (20*500/9991) = 0.09% - const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target); + const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); expect(isRoundingError).to.be.false(); }); @@ -34,7 +34,7 @@ describe('OrderValidationUtils', () => { const denominator = new BigNumber(9989); const target = new BigNumber(500); // rounding error = ((20*500/9989) - floor(20*500/9989)) / (20*500/9989) = 0.011% - const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target); + const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); expect(isRoundingError).to.be.true(); }); @@ -43,7 +43,7 @@ describe('OrderValidationUtils', () => { const denominator = new BigNumber(7); const target = new BigNumber(10); // rounding error = ((3*10/7) - floor(3*10/7)) / (3*10/7) = 6.67% - const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target); + const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); expect(isRoundingError).to.be.true(); }); @@ -52,7 +52,7 @@ describe('OrderValidationUtils', () => { const denominator = new BigNumber(2); const target = new BigNumber(10); - const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target); + const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); expect(isRoundingError).to.be.false(); }); @@ -63,7 +63,7 @@ describe('OrderValidationUtils', () => { const target = new BigNumber(105762562); // rounding error = ((76564*105762562/676373677) - floor(76564*105762562/676373677)) / // (76564*105762562/676373677) = 0.0007% - const isRoundingError = OrderValidationUtils.isRoundingError(numerator, denominator, target); + const isRoundingError = OrderValidationUtils.isRoundingErrorFloor(numerator, denominator, target); expect(isRoundingError).to.be.false(); }); }); diff --git a/packages/order-utils/test/rate_utils_test.ts b/packages/order-utils/test/rate_utils_test.ts index 2e299c209..b13878bb5 100644 --- a/packages/order-utils/test/rate_utils_test.ts +++ b/packages/order-utils/test/rate_utils_test.ts @@ -1,4 +1,4 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from '@0x/utils'; import * as chai from 'chai'; import 'mocha'; diff --git a/packages/order-utils/test/remaining_fillable_calculator_test.ts b/packages/order-utils/test/remaining_fillable_calculator_test.ts index a5a3b7fc6..c55a76def 100644 --- a/packages/order-utils/test/remaining_fillable_calculator_test.ts +++ b/packages/order-utils/test/remaining_fillable_calculator_test.ts @@ -1,6 +1,6 @@ -import { SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; import 'mocha'; diff --git a/packages/order-utils/test/signature_utils_test.ts b/packages/order-utils/test/signature_utils_test.ts index a25d2afd6..84e5559a9 100644 --- a/packages/order-utils/test/signature_utils_test.ts +++ b/packages/order-utils/test/signature_utils_test.ts @@ -1,13 +1,14 @@ -import { SignerType } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +import { Order, SignatureType } from '@0x/types'; +import { BigNumber } from '@0x/utils'; import * as chai from 'chai'; import { JSONRPCErrorCallback, JSONRPCRequestPayload } from 'ethereum-types'; +import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; import 'mocha'; -import * as Sinon from 'sinon'; -import { ecSignOrderHashAsync, generatePseudoRandomSalt } from '../src'; -import { convertECSignatureToSignatureHex, isValidECSignature, isValidSignatureAsync } from '../src/signature_utils'; +import { generatePseudoRandomSalt, orderHashUtils } from '../src'; +import { constants } from '../src/constants'; +import { signatureUtils } from '../src/signature_utils'; import { chaiSetup } from './utils/chai_setup'; import { provider, web3Wrapper } from './utils/web3_wrapper'; @@ -16,7 +17,29 @@ chaiSetup.configure(); const expect = chai.expect; describe('Signature utils', () => { - describe('#isValidSignature', () => { + let makerAddress: string; + const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48'; + let order: Order; + before(async () => { + const availableAddreses = await web3Wrapper.getAvailableAddressesAsync(); + makerAddress = availableAddreses[0]; + order = { + makerAddress, + takerAddress: constants.NULL_ADDRESS, + senderAddress: constants.NULL_ADDRESS, + feeRecipientAddress: constants.NULL_ADDRESS, + makerAssetData: constants.NULL_ADDRESS, + takerAssetData: constants.NULL_ADDRESS, + exchangeAddress: fakeExchangeContractAddress, + salt: new BigNumber(0), + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + makerAssetAmount: new BigNumber(0), + takerAssetAmount: new BigNumber(0), + expirationTimeSeconds: new BigNumber(0), + }; + }); + describe('#isValidSignatureAsync', () => { let dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; const ethSignSignature = '0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403'; @@ -24,12 +47,14 @@ describe('Signature utils', () => { it("should return false if the data doesn't pertain to the signature & address", async () => { const bytes32Zeros = '0x0000000000000000000000000000000000000000000000000000000000000000'; - expect(await isValidSignatureAsync(provider, bytes32Zeros, ethSignSignature, address)).to.be.false(); + expect( + await signatureUtils.isValidSignatureAsync(provider, bytes32Zeros, ethSignSignature, address), + ).to.be.false(); }); it("should return false if the address doesn't pertain to the signature & data", async () => { const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42'; expect( - await isValidSignatureAsync(provider, dataHex, ethSignSignature, validUnrelatedAddress), + await signatureUtils.isValidSignatureAsync(provider, dataHex, ethSignSignature, validUnrelatedAddress), ).to.be.false(); }); it("should return false if the signature doesn't pertain to the dataHex & address", async () => { @@ -37,18 +62,27 @@ describe('Signature utils', () => { // tslint:disable-next-line:custom-no-magic-numbers signatureArray[5] = 'C'; // V = 28, instead of 27 const wrongSignature = signatureArray.join(''); - expect(await isValidSignatureAsync(provider, dataHex, wrongSignature, address)).to.be.false(); + expect( + await signatureUtils.isValidSignatureAsync(provider, dataHex, wrongSignature, address), + ).to.be.false(); }); it('should throw if signatureType is invalid', () => { const signatureArray = ethSignSignature.split(''); signatureArray[3] = '9'; // SignatureType w/ index 9 doesn't exist const signatureWithInvalidType = signatureArray.join(''); - expect(isValidSignatureAsync(provider, dataHex, signatureWithInvalidType, address)).to.be.rejected(); + expect( + signatureUtils.isValidSignatureAsync(provider, dataHex, signatureWithInvalidType, address), + ).to.be.rejected(); }); it('should return true for a valid Ecrecover (EthSign) signature', async () => { - const isValidSignatureLocal = await isValidSignatureAsync(provider, dataHex, ethSignSignature, address); + const isValidSignatureLocal = await signatureUtils.isValidSignatureAsync( + provider, + dataHex, + ethSignSignature, + address, + ); expect(isValidSignatureLocal).to.be.true(); }); @@ -57,16 +91,12 @@ describe('Signature utils', () => { address = '0x6ecbe1db9ef729cbe972c83fb886247691fb6beb'; const eip712Signature = '0x1bdde07aac4bf12c12ddbb155919c43eba4146a2cfcf904a862950dbebe332554c6674975603eb5a4eaf8fd7f2e06350267e5b36cda9851a89f8bb49fe2fc9afe202'; - const isValidSignatureLocal = await isValidSignatureAsync(provider, dataHex, eip712Signature, address); - expect(isValidSignatureLocal).to.be.true(); - }); - - it('should return true for a valid Trezor signature', async () => { - dataHex = '0xd0d994e31c88f33fd8a572552a70ed339de579e5ba49ee1d17cc978bbe1cdd21'; - address = '0x6ecbe1db9ef729cbe972c83fb886247691fb6beb'; - const trezorSignature = - '0x1ce4760660e6495b5ae6723087bea073b3a99ce98ea81fdf00c240279c010e63d05b87bc34c4d67d4776e8d5aeb023a67484f4eaf0fd353b40893e5101e845cd9908'; - const isValidSignatureLocal = await isValidSignatureAsync(provider, dataHex, trezorSignature, address); + const isValidSignatureLocal = await signatureUtils.isValidSignatureAsync( + provider, + dataHex, + eip712Signature, + address, + ); expect(isValidSignatureLocal).to.be.true(); }); }); @@ -80,18 +110,18 @@ describe('Signature utils', () => { const address = '0x0e5cb767cce09a7f3ca594df118aa519be5e2b5a'; it("should return false if the data doesn't pertain to the signature & address", async () => { - expect(isValidECSignature('0x0', signature, address)).to.be.false(); + expect(signatureUtils.isValidECSignature('0x0', signature, address)).to.be.false(); }); it("should return false if the address doesn't pertain to the signature & data", async () => { const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42'; - expect(isValidECSignature(data, signature, validUnrelatedAddress)).to.be.false(); + expect(signatureUtils.isValidECSignature(data, signature, validUnrelatedAddress)).to.be.false(); }); it("should return false if the signature doesn't pertain to the data & address", async () => { const wrongSignature = _.assign({}, signature, { v: 28 }); - expect(isValidECSignature(data, wrongSignature, address)).to.be.false(); + expect(signatureUtils.isValidECSignature(data, wrongSignature, address)).to.be.false(); }); it('should return true if the signature does pertain to the data & address', async () => { - const isValidSignatureLocal = isValidECSignature(data, signature, address); + const isValidSignatureLocal = signatureUtils.isValidECSignature(data, signature, address); expect(isValidSignatureLocal).to.be.true(); }); }); @@ -108,23 +138,64 @@ describe('Signature utils', () => { expect(salt.lessThan(twoPow256)).to.be.true(); }); }); - describe('#ecSignOrderHashAsync', () => { - let stubs: Sinon.SinonStub[] = []; - let makerAddress: string; + describe('#ecSignOrderAsync', () => { + it('should default to eth_sign if eth_signTypedData is unavailable', async () => { + const expectedSignature = + '0x1c3582f06356a1314dbf1c0e534c4d8e92e59b056ee607a7ff5a825f5f2cc5e6151c5cc7fdd420f5608e4d5bef108e42ad90c7a4b408caef32e24374cf387b0d7603'; + + const fakeProvider = { + async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> { + if (payload.method === 'eth_signTypedData') { + callback(new Error('Internal RPC Error')); + } else if (payload.method === 'eth_sign') { + const [address, message] = payload.params; + const signature = await web3Wrapper.signMessageAsync(address, message); + callback(null, { + id: 42, + jsonrpc: '2.0', + result: signature, + }); + } else { + callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] }); + } + }, + }; + const signedOrder = await signatureUtils.ecSignOrderAsync(fakeProvider, order, makerAddress); + expect(signedOrder.signature).to.equal(expectedSignature); + }); + it('should throw if the user denies the signing request', async () => { + const fakeProvider = { + async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> { + if (payload.method === 'eth_signTypedData') { + callback(new Error('User denied message signature')); + } else if (payload.method === 'eth_sign') { + const [address, message] = payload.params; + const signature = await web3Wrapper.signMessageAsync(address, message); + callback(null, { + id: 42, + jsonrpc: '2.0', + result: signature, + }); + } else { + callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] }); + } + }, + }; + expect(signatureUtils.ecSignOrderAsync(fakeProvider, order, makerAddress)).to.to.be.rejectedWith( + 'User denied message signature', + ); + }); + }); + describe('#ecSignHashAsync', () => { before(async () => { const availableAddreses = await web3Wrapper.getAvailableAddressesAsync(); makerAddress = availableAddreses[0]; }); - afterEach(() => { - // clean up any stubs after the test has completed - _.each(stubs, s => s.restore()); - stubs = []; - }); - it('Should return the correct Signature', async () => { + it('should return the correct Signature', async () => { const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; const expectedSignature = '0x1b61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403'; - const ecSignature = await ecSignOrderHashAsync(provider, orderHash, makerAddress, SignerType.Default); + const ecSignature = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress); expect(ecSignature).to.equal(expectedSignature); }); it('should return the correct Signature for signatureHex concatenated as R + S + V', async () => { @@ -150,7 +221,7 @@ describe('Signature utils', () => { } }, }; - const ecSignature = await ecSignOrderHashAsync(fakeProvider, orderHash, makerAddress, SignerType.Default); + const ecSignature = await signatureUtils.ecSignHashAsync(fakeProvider, orderHash, makerAddress); expect(ecSignature).to.equal(expectedSignature); }); it('should return the correct Signature for signatureHex concatenated as V + R + S', async () => { @@ -173,44 +244,68 @@ describe('Signature utils', () => { }, }; - const ecSignature = await ecSignOrderHashAsync(fakeProvider, orderHash, makerAddress, SignerType.Default); + const ecSignature = await signatureUtils.ecSignHashAsync(fakeProvider, orderHash, makerAddress); expect(ecSignature).to.equal(expectedSignature); }); - // Note this is due to a bug in Metamask where it does not prefix before signing, this is a known issue and is to be fixed in the future - // Source: https://github.com/MetaMask/metamask-extension/commit/a9d36860bec424dcee8db043d3e7da6a5ff5672e - it('should receive a payload modified with a prefix when Metamask is SignerType', async () => { + it('should return a valid signature', async () => { const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004'; - const orderHashPrefixed = '0xae70f31d26096291aa681b26cb7574563956221d0b4213631e1ef9df675d4cba'; + const ecSignature = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress); + + const isValidSignature = await signatureUtils.isValidSignatureAsync( + provider, + orderHash, + ecSignature, + makerAddress, + ); + expect(isValidSignature).to.be.true(); + }); + }); + describe('#ecSignTypedDataOrderAsync', () => { + it('should result in the same signature as signing the order hash without an ethereum message prefix', async () => { + // Note: Since order hash is an EIP712 hash the result of a valid EIP712 signature + // of order hash is the same as signing the order without the Ethereum Message prefix. + const orderHashHex = orderHashUtils.getOrderHashHex(order); + const sig = ethUtil.ecsign( + ethUtil.toBuffer(orderHashHex), + Buffer.from('F2F48EE19680706196E2E339E5DA3491186E0C4C5030670656B0E0164837257D', 'hex'), + ); + const signatureBuffer = Buffer.concat([ + ethUtil.toBuffer(sig.v), + ethUtil.toBuffer(sig.r), + ethUtil.toBuffer(sig.s), + ethUtil.toBuffer(SignatureType.EIP712), + ]); + const signatureHex = `0x${signatureBuffer.toString('hex')}`; + const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(provider, order, makerAddress); + const isValidSignature = await signatureUtils.isValidSignatureAsync( + provider, + orderHashHex, + signedOrder.signature, + makerAddress, + ); + expect(signatureHex).to.eq(signedOrder.signature); + expect(isValidSignature).to.eq(true); + }); + it('should return the correct Signature for signatureHex concatenated as R + S + V', async () => { const expectedSignature = - '0x1b117902c86dfb95fe0d1badd983ee166ad259b27acb220174cbb4460d872871137feabdfe76e05924b484789f79af4ee7fa29ec006cedce1bbf369320d034e10b03'; - // Generated from a MM eth_sign request from 0x5409ed021d9299bf6814279a6a1411a7e866a631 signing 0xae70f31d26096291aa681b26cb7574563956221d0b4213631e1ef9df675d4cba - const metamaskSignature = - '0x117902c86dfb95fe0d1badd983ee166ad259b27acb220174cbb4460d872871137feabdfe76e05924b484789f79af4ee7fa29ec006cedce1bbf369320d034e10b1b'; + '0x1cd472c439833774b55d248c31b6585f21aea1b9363ebb4ec58549e46b62eb5a6f696f5781f62de008ee7f77650ef940d99c97ec1dee67b3f5cea1bbfdfeb2eba602'; const fakeProvider = { async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> { - if (payload.method === 'eth_sign') { - const [, message] = payload.params; - expect(message).to.equal(orderHashPrefixed); + if (payload.method === 'eth_signTypedData') { + const [address, typedData] = payload.params; + const signature = await web3Wrapper.signTypedDataAsync(address, typedData); callback(null, { id: 42, jsonrpc: '2.0', - result: metamaskSignature, + result: signature, }); } else { callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] }); } }, }; - - const ecSignature = await ecSignOrderHashAsync(fakeProvider, orderHash, makerAddress, SignerType.Metamask); - expect(ecSignature).to.equal(expectedSignature); - }); - it('should return a valid signature', async () => { - const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004'; - const ecSignature = await ecSignOrderHashAsync(provider, orderHash, makerAddress, SignerType.Default); - - const isValidSignature = await isValidSignatureAsync(provider, orderHash, ecSignature, makerAddress); - expect(isValidSignature).to.be.true(); + const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(fakeProvider, order, makerAddress); + expect(signedOrder.signature).to.equal(expectedSignature); }); }); describe('#convertECSignatureToSignatureHex', () => { @@ -219,35 +314,11 @@ describe('Signature utils', () => { r: '0xaca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d64393', s: '0x46b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf2', }; - it('should concatenate v,r,s and append the Trezor signature type', async () => { - const expectedSignatureWithSignatureType = - '0x1baca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf208'; - const signatureWithSignatureType = convertECSignatureToSignatureHex(ecSignature, SignerType.Trezor); - expect(signatureWithSignatureType).to.equal(expectedSignatureWithSignatureType); - }); - it('should concatenate v,r,s and append the EthSign signature type when SignerType is Default', async () => { + it('should concatenate v,r,s and append the EthSign signature type', async () => { const expectedSignatureWithSignatureType = '0x1baca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf203'; - const signatureWithSignatureType = convertECSignatureToSignatureHex(ecSignature, SignerType.Default); + const signatureWithSignatureType = signatureUtils.convertECSignatureToSignatureHex(ecSignature); expect(signatureWithSignatureType).to.equal(expectedSignatureWithSignatureType); }); - it('should concatenate v,r,s and append the EthSign signature type when SignerType is Ledger', async () => { - const expectedSignatureWithSignatureType = - '0x1baca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf203'; - const signatureWithSignatureType = convertECSignatureToSignatureHex(ecSignature, SignerType.Ledger); - expect(signatureWithSignatureType).to.equal(expectedSignatureWithSignatureType); - }); - it('should concatenate v,r,s and append the EthSign signature type when SignerType is Metamask', async () => { - const expectedSignatureWithSignatureType = - '0x1baca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf203'; - const signatureWithSignatureType = convertECSignatureToSignatureHex(ecSignature, SignerType.Metamask); - expect(signatureWithSignatureType).to.equal(expectedSignatureWithSignatureType); - }); - it('should throw if the SignerType is invalid', async () => { - const expectedMessage = 'Unrecognized SignerType: INVALID_SIGNER'; - expect(() => convertECSignatureToSignatureHex(ecSignature, 'INVALID_SIGNER' as SignerType)).to.throw( - expectedMessage, - ); - }); }); }); diff --git a/packages/order-utils/test/sorting_utils_test.ts b/packages/order-utils/test/sorting_utils_test.ts index 016432505..0b8757496 100644 --- a/packages/order-utils/test/sorting_utils_test.ts +++ b/packages/order-utils/test/sorting_utils_test.ts @@ -1,4 +1,4 @@ -import { BigNumber } from '@0xproject/utils'; +import { BigNumber } from '@0x/utils'; import * as chai from 'chai'; import 'mocha'; diff --git a/packages/order-utils/test/utils/simple_erc20_balance_and_proxy_allowance_fetcher.ts b/packages/order-utils/test/utils/simple_erc20_balance_and_proxy_allowance_fetcher.ts index 279a5c0db..d3ea8b456 100644 --- a/packages/order-utils/test/utils/simple_erc20_balance_and_proxy_allowance_fetcher.ts +++ b/packages/order-utils/test/utils/simple_erc20_balance_and_proxy_allowance_fetcher.ts @@ -1,9 +1,8 @@ -import { BigNumber } from '@0xproject/utils'; +import { ERC20TokenContract } from '@0x/abi-gen-wrappers'; +import { BigNumber } from '@0x/utils'; import { AbstractBalanceAndProxyAllowanceFetcher } from '../../src/abstract/abstract_balance_and_proxy_allowance_fetcher'; -import { ERC20TokenContract } from '../../src/generated_contract_wrappers/erc20_token'; - export class SimpleERC20BalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher { private readonly _erc20TokenContract: ERC20TokenContract; private readonly _erc20ProxyAddress: string; diff --git a/packages/order-utils/test/utils/test_order_factory.ts b/packages/order-utils/test/utils/test_order_factory.ts index 75dc6f1f2..145332674 100644 --- a/packages/order-utils/test/utils/test_order_factory.ts +++ b/packages/order-utils/test/utils/test_order_factory.ts @@ -1,7 +1,8 @@ -import { Order, SignedOrder } from '@0xproject/types'; +import { Order, SignedOrder } from '@0x/types'; import * as _ from 'lodash'; -import { constants, orderFactory } from '../../src'; +import { constants } from '../../src/constants'; +import { orderFactory } from '../../src/order_factory'; const BASE_TEST_ORDER: Order = orderFactory.createOrder( constants.NULL_ADDRESS, diff --git a/packages/order-utils/test/utils/web3_wrapper.ts b/packages/order-utils/test/utils/web3_wrapper.ts index ab801fa7f..accfcb7fe 100644 --- a/packages/order-utils/test/utils/web3_wrapper.ts +++ b/packages/order-utils/test/utils/web3_wrapper.ts @@ -1,5 +1,5 @@ -import { web3Factory } from '@0xproject/dev-utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { web3Factory } from '@0x/dev-utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; import { Provider } from 'ethereum-types'; const provider: Provider = web3Factory.getRpcProvider({ shouldUseInProcessGanache: true }); diff --git a/packages/order-utils/tsconfig.json b/packages/order-utils/tsconfig.json index 8b4cd47a2..718e623c7 100644 --- a/packages/order-utils/tsconfig.json +++ b/packages/order-utils/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../../tsconfig", "compilerOptions": { - "outDir": "lib" + "outDir": "lib", + "rootDir": "." }, "include": ["src/**/*", "test/**/*"] } diff --git a/packages/order-utils/tslint.json b/packages/order-utils/tslint.json index ffaefe83a..dd9053357 100644 --- a/packages/order-utils/tslint.json +++ b/packages/order-utils/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0x/tslint-config"] } diff --git a/packages/order-utils/typedoc-tsconfig.json b/packages/order-utils/typedoc-tsconfig.json new file mode 100644 index 000000000..b9c6b36f3 --- /dev/null +++ b/packages/order-utils/typedoc-tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../typedoc-tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["src/**/*", "test/**/*"] +} |