diff options
28 files changed, 593 insertions, 44 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml index 3c87e1ff2..876b861d3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -162,6 +162,90 @@ jobs: key: coverage-web3-wrapper-{{ .Environment.CIRCLE_SHA1 }} paths: - ~/repo/packages/web3-wrapper/coverage/lcov.info + test-python: + working_directory: ~/repo + docker: + - image: circleci/python + steps: + - checkout + - run: sudo chown -R circleci:circleci /usr/local/bin + - run: sudo chown -R circleci:circleci /usr/local/lib/python3.7/site-packages + - restore_cache: + key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} + - run: + command: | + cd python-packages/order_utils + python -m ensurepip + python -m pip install -e .[dev] + - save_cache: + key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} + paths: + - "/usr/local/bin" + - "/usr/local/lib/python3.7/site-packages" + - ".eggs" + - ".mypy_cache" + - ".pytest_cache" + - ".tox" + - run: + command: | + cd python-packages/order_utils + coverage run setup.py test + - save_cache: + key: coverage-python-order-utils-{{ .Environment.CIRCLE_SHA1 }} + paths: + - ~/repo/python-packages/order_utils/.coverage + test-rest-python: + working_directory: ~/repo + docker: + - image: circleci/python + steps: + - checkout + - run: sudo chown -R circleci:circleci /usr/local/bin + - run: sudo chown -R circleci:circleci /usr/local/lib/python3.7/site-packages + - restore_cache: + key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} + - run: + command: | + cd python-packages/order_utils + python -m ensurepip + python -m pip install -e .[dev] + - save_cache: + key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} + paths: + - "/usr/local/bin" + - "/usr/local/lib/python3.7/site-packages" + - ".eggs" + - ".mypy_cache" + - ".pytest_cache" + - ".tox" + - run: + command: | + cd python-packages/order_utils + tox + static-tests-python: + working_directory: ~/repo + docker: + - image: circleci/python + steps: + - checkout + - run: sudo chown -R circleci:circleci /usr/local/bin + - run: sudo chown -R circleci:circleci /usr/local/lib/python3.7/site-packages + - restore_cache: + key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} + - run: + command: | + cd python-packages/order_utils + python -m ensurepip + python -m pip install -e .[dev] + - save_cache: + key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} + paths: + - "/usr/local/bin" + - "/usr/local/lib/python3.7/site-packages" + - run: + command: | + cd python-packages/order_utils + python setup.py lint static-tests: working_directory: ~/repo docker: @@ -233,6 +317,9 @@ jobs: - restore_cache: keys: - coverage-contracts-{{ .Environment.CIRCLE_SHA1 }} + - restore_cache: + keys: + - coverage-python-order-utils-{{ .Environment.CIRCLE_SHA1 }} - run: yarn report_coverage workflows: version: 2 @@ -263,3 +350,8 @@ workflows: - submit-coverage: requires: - test-rest + - test-python + - test-python + - static-tests-python + # skip python tox run for now, as we don't yet have multiple test environments to support. + #- test-rest-python diff --git a/.gitignore b/.gitignore index 1ae1b412d..6143dedb0 100644 --- a/.gitignore +++ b/.gitignore @@ -109,3 +109,12 @@ packages/sol-compiler/solc_bin/ # Monorepo scripts packages/*/scripts/ + +# python stuff +.eggs +.mypy_cache +.tox +python-packages/*/build +__pycache__ +python-packages/*/src/*.egg-info +python-packages/*/.coverage diff --git a/CODEOWNERS b/CODEOWNERS index 9f9ba666e..e2a8d93cd 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -5,4 +5,6 @@ # https://git-scm.com/docs/gitignore#_pattern_format # Website -packages/website/ @BMillman19 @fragosti +packages/asset-buyer/ @BMillman19 @fragosti @steveklebanoff +packages/instant/ @BMillman19 @fragosti @steveklebanoff +packages/website/ @BMillman19 @fragosti @fabioberger @steveklebanoff diff --git a/package.json b/package.json index 920afaf29..e6ae0ebde 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "ganache": "ganache-cli -p 8545 --networkId 50 -m \"${npm_package_config_mnemonic}\"", "prettier": "prettier --write '**/*.{ts,tsx,json,md}' --config .prettierrc", "prettier:ci": "prettier --list-different '**/*.{ts,tsx,json,md}' --config .prettierrc", - "report_coverage": "lcov-result-merger 'packages/*/coverage/lcov.info' | coveralls", + "report_coverage": "lcov-result-merger './{packages/*/coverage/lcov.info,python-packages/*/.coverage}' | coveralls", "test:installation": "node ./packages/monorepo-scripts/lib/test_installation.js", "test:installation:local": "IS_LOCAL_PUBLISH=true node ./packages/monorepo-scripts/lib/test_installation.js", "test:publish:circleci:comment": "HACK(albrow) We need an automated way to login to npm and echo+sleep piped to stdin was the only way I could find to do it.", diff --git a/packages/0x.js/src/index.ts b/packages/0x.js/src/index.ts index 6eb1fd8ee..ce3f616aa 100644 --- a/packages/0x.js/src/index.ts +++ b/packages/0x.js/src/index.ts @@ -78,6 +78,7 @@ export { ERC721AssetData, SignatureType, OrderRelevantState, + Stats, } from '@0xproject/types'; export { diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index a96cb3a59..0770b6c0d 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,5 +1,13 @@ [ { + "version": "2.1.0", + "changes": [ + { + "note": "Add optional validation to the forwarder wrapper methods" + } + ] + }, + { "version": "2.0.2", "changes": [ { diff --git a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts index 906222731..c19edf188 100644 --- a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts @@ -8,10 +8,11 @@ import * as _ from 'lodash'; import { artifacts } from '../artifacts'; import { orderTxOptsSchema } from '../schemas/order_tx_opts_schema'; import { txOptsSchema } from '../schemas/tx_opts_schema'; -import { TransactionOpts } from '../types'; +import { OrderTransactionOpts } from '../types'; import { assert } from '../utils/assert'; import { calldataOptimizationUtils } from '../utils/calldata_optimization_utils'; import { constants } from '../utils/constants'; +import { decorators } from '../utils/decorators'; import { utils } from '../utils/utils'; import { ContractWrapper } from './contract_wrapper'; @@ -40,19 +41,20 @@ export class ForwarderWrapper extends ContractWrapper { * Any ZRX required to pay fees for primary orders will automatically be purchased by this contract. * 5% of ETH value is reserved for paying fees to order feeRecipients (in ZRX) and forwarding contract feeRecipient (in ETH). * Any ETH not spent will be refunded to sender. - * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders must specify the same makerAsset. - * All orders must specify WETH as the takerAsset - * @param takerAddress The user Ethereum address who would like to fill this order. Must be available via the supplied - * Provider provided at instantiation. - * @param ethAmount The amount of eth to send with the transaction (in wei). - * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders must specify ZRX as makerAsset and WETH as takerAsset. - * Used to purchase ZRX for primary order fees. - * @param feePercentage The percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. - * Defaults to 0. - * @param feeRecipientAddress The address that will receive ETH when signedFeeOrders are filled. - * @param txOpts Transaction parameters. + * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders must specify the same makerAsset. + * All orders must specify WETH as the takerAsset + * @param takerAddress The user Ethereum address who would like to fill this order. Must be available via the supplied + * Provider provided at instantiation. + * @param ethAmount The amount of eth to send with the transaction (in wei). + * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders must specify ZRX as makerAsset and WETH as takerAsset. + * Used to purchase ZRX for primary order fees. + * @param feePercentage The percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. + * Defaults to 0. + * @param feeRecipientAddress The address that will receive ETH when signedFeeOrders are filled. + * @param orderTransactionOpts Transaction parameters. * @return Transaction hash. */ + @decorators.asyncZeroExErrorHandler public async marketSellOrdersWithEthAsync( signedOrders: SignedOrder[], takerAddress: string, @@ -60,7 +62,7 @@ export class ForwarderWrapper extends ContractWrapper { signedFeeOrders: SignedOrder[] = [], feePercentage: number = 0, feeRecipientAddress: string = constants.NULL_ADDRESS, - txOpts: TransactionOpts = {}, + orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true }, ): Promise<string> { // type assertions assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); @@ -69,7 +71,7 @@ export class ForwarderWrapper extends ContractWrapper { assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema); assert.isNumber('feePercentage', feePercentage); assert.isETHAddressHex('feeRecipientAddress', feeRecipientAddress); - assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); + assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]); // other assertions assert.ordersCanBeUsedForForwarderContract(signedOrders, this.getEtherTokenAddress()); assert.feeOrdersCanBeUsedForForwarderContract( @@ -85,20 +87,41 @@ export class ForwarderWrapper extends ContractWrapper { // optimize orders const optimizedMarketOrders = calldataOptimizationUtils.optimizeForwarderOrders(signedOrders); const optimizedFeeOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(signedFeeOrders); - // send transaction + // compile signatures + const signatures = _.map(optimizedMarketOrders, order => order.signature); + const feeSignatures = _.map(optimizedFeeOrders, order => order.signature); + // get contract const forwarderContractInstance = await this._getForwarderContractAsync(); + // validate transaction + if (orderTransactionOpts.shouldValidate) { + await forwarderContractInstance.marketSellOrdersWithEth.callAsync( + optimizedMarketOrders, + signatures, + optimizedFeeOrders, + feeSignatures, + formattedFeePercentage, + feeRecipientAddress, + { + value: ethAmount, + from: normalizedTakerAddress, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + } + // send transaction const txHash = await forwarderContractInstance.marketSellOrdersWithEth.sendTransactionAsync( optimizedMarketOrders, - _.map(optimizedMarketOrders, order => order.signature), + signatures, optimizedFeeOrders, - _.map(optimizedFeeOrders, order => order.signature), + feeSignatures, formattedFeePercentage, feeRecipientAddress, { value: ethAmount, from: normalizedTakerAddress, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, }, ); return txHash; @@ -107,20 +130,21 @@ export class ForwarderWrapper extends ContractWrapper { * Attempt to purchase makerAssetFillAmount of makerAsset by selling ethAmount provided with transaction. * Any ZRX required to pay fees for primary orders will automatically be purchased by the contract. * Any ETH not spent will be refunded to sender. - * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders must specify the same makerAsset. - * All orders must specify WETH as the takerAsset - * @param makerAssetFillAmount The amount of the order (in taker asset baseUnits) that you wish to fill. - * @param takerAddress The user Ethereum address who would like to fill this order. Must be available via the supplied - * Provider provided at instantiation. - * @param ethAmount The amount of eth to send with the transaction (in wei). - * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders must specify ZRX as makerAsset and WETH as takerAsset. - * Used to purchase ZRX for primary order fees. - * @param feePercentage The percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. - * Defaults to 0. - * @param feeRecipientAddress The address that will receive ETH when signedFeeOrders are filled. - * @param txOpts Transaction parameters. + * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders must specify the same makerAsset. + * All orders must specify WETH as the takerAsset + * @param makerAssetFillAmount The amount of the order (in taker asset baseUnits) that you wish to fill. + * @param takerAddress The user Ethereum address who would like to fill this order. Must be available via the supplied + * Provider provided at instantiation. + * @param ethAmount The amount of eth to send with the transaction (in wei). + * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders must specify ZRX as makerAsset and WETH as takerAsset. + * Used to purchase ZRX for primary order fees. + * @param feePercentage The percentage of WETH sold that will payed as fee to forwarding contract feeRecipient. + * Defaults to 0. + * @param feeRecipientAddress The address that will receive ETH when signedFeeOrders are filled. + * @param orderTransactionOpts Transaction parameters. * @return Transaction hash. */ + @decorators.asyncZeroExErrorHandler public async marketBuyOrdersWithEthAsync( signedOrders: SignedOrder[], makerAssetFillAmount: BigNumber, @@ -129,7 +153,7 @@ export class ForwarderWrapper extends ContractWrapper { signedFeeOrders: SignedOrder[] = [], feePercentage: number = 0, feeRecipientAddress: string = constants.NULL_ADDRESS, - txOpts: TransactionOpts = {}, + orderTransactionOpts: OrderTransactionOpts = { shouldValidate: true }, ): Promise<string> { // type assertions assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); @@ -139,7 +163,7 @@ export class ForwarderWrapper extends ContractWrapper { assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema); assert.isNumber('feePercentage', feePercentage); assert.isETHAddressHex('feeRecipientAddress', feeRecipientAddress); - assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); + assert.doesConformToSchema('orderTransactionOpts', orderTransactionOpts, orderTxOptsSchema, [txOptsSchema]); // other assertions assert.ordersCanBeUsedForForwarderContract(signedOrders, this.getEtherTokenAddress()); assert.feeOrdersCanBeUsedForForwarderContract( @@ -155,21 +179,43 @@ export class ForwarderWrapper extends ContractWrapper { // optimize orders const optimizedMarketOrders = calldataOptimizationUtils.optimizeForwarderOrders(signedOrders); const optimizedFeeOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(signedFeeOrders); - // send transaction + // compile signatures + const signatures = _.map(optimizedMarketOrders, order => order.signature); + const feeSignatures = _.map(optimizedFeeOrders, order => order.signature); + // get contract const forwarderContractInstance = await this._getForwarderContractAsync(); + // validate transaction + if (orderTransactionOpts.shouldValidate) { + await forwarderContractInstance.marketBuyOrdersWithEth.callAsync( + optimizedMarketOrders, + makerAssetFillAmount, + signatures, + optimizedFeeOrders, + feeSignatures, + formattedFeePercentage, + feeRecipientAddress, + { + value: ethAmount, + from: normalizedTakerAddress, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + } + // send transaction const txHash = await forwarderContractInstance.marketBuyOrdersWithEth.sendTransactionAsync( optimizedMarketOrders, makerAssetFillAmount, - _.map(optimizedMarketOrders, order => order.signature), + signatures, optimizedFeeOrders, - _.map(optimizedFeeOrders, order => order.signature), + feeSignatures, formattedFeePercentage, feeRecipientAddress, { value: ethAmount, from: normalizedTakerAddress, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, }, ); return txHash; diff --git a/packages/contract-wrappers/test/forwarder_wrapper_test.ts b/packages/contract-wrappers/test/forwarder_wrapper_test.ts index f77b47337..4329e8770 100644 --- a/packages/contract-wrappers/test/forwarder_wrapper_test.ts +++ b/packages/contract-wrappers/test/forwarder_wrapper_test.ts @@ -17,6 +17,7 @@ chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +// tslint:disable:custom-no-magic-numbers describe('ForwarderWrapper', () => { const contractWrappersConfig = { networkId: constants.TESTRPC_NETWORK_ID, @@ -99,6 +100,25 @@ describe('ForwarderWrapper', () => { expect(ordersInfo[0].orderStatus).to.be.equal(OrderStatus.FULLY_FILLED); expect(ordersInfo[1].orderStatus).to.be.equal(OrderStatus.FULLY_FILLED); }); + it('should throw when invalid transaction and shouldValidate is true', async () => { + const signedOrders = [signedOrder]; + // request more makerAsset than what is available + const makerAssetFillAmount = signedOrder.makerAssetAmount.plus(100); + return expect( + contractWrappers.forwarder.marketBuyOrdersWithEthAsync( + signedOrders, + makerAssetFillAmount, + takerAddress, + makerAssetFillAmount, + [], + 0, + constants.NULL_ADDRESS, + { + shouldValidate: true, + }, + ), + ).to.be.rejectedWith('COMPLETE_FILL_FAILED'); + }); }); describe('#marketSellOrdersWithEthAsync', () => { it('should market sell orders with eth', async () => { @@ -115,5 +135,33 @@ describe('ForwarderWrapper', () => { expect(ordersInfo[1].orderStatus).to.be.equal(OrderStatus.FILLABLE); expect(ordersInfo[1].orderTakerAssetFilledAmount).to.be.bignumber.equal(new BigNumber(4)); // only 95% of ETH is sold }); + it('should throw when invalid transaction and shouldValidate is true', async () => { + // create an order with fees, we try to fill it but we do not provide enough ETH to cover the fees + const signedOrderWithFee = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerAssetData, + takerAssetData, + constants.ZERO_AMOUNT, + new BigNumber(100), + makerAddress, + constants.NULL_ADDRESS, + fillableAmount, + constants.NULL_ADDRESS, + ); + const signedOrders = [signedOrderWithFee]; + const makerAssetFillAmount = signedOrder.makerAssetAmount; + return expect( + contractWrappers.forwarder.marketSellOrdersWithEthAsync( + signedOrders, + takerAddress, + makerAssetFillAmount, + [], + 0, + constants.NULL_ADDRESS, + { + shouldValidate: true, + }, + ), + ).to.be.rejectedWith('COMPLETE_FILL_FAILED'); + }); }); }); diff --git a/packages/order-watcher/CHANGELOG.json b/packages/order-watcher/CHANGELOG.json index ce56e492c..feebb9d69 100644 --- a/packages/order-watcher/CHANGELOG.json +++ b/packages/order-watcher/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "version": "2.1.2", + "changes": [ + { + "note": "Added getStats function and returns a Stats object", + "pr": 1118 + } + ] + }, + { "version": "2.1.1", "changes": [ { diff --git a/packages/order-watcher/src/index.ts b/packages/order-watcher/src/index.ts index d2f91eab1..8280c73a4 100644 --- a/packages/order-watcher/src/index.ts +++ b/packages/order-watcher/src/index.ts @@ -7,6 +7,7 @@ export { OrderState, ExchangeContractErrs, OrderRelevantState, + Stats, } from '@0xproject/types'; export { OnOrderStateChangeCallback, OrderWatcherConfig } from './types'; diff --git a/packages/order-watcher/src/order_watcher/order_watcher.ts b/packages/order-watcher/src/order_watcher/order_watcher.ts index f9a63efe3..eb37bd617 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher.ts @@ -30,7 +30,7 @@ import { orderHashUtils, OrderStateUtils, } from '@0xproject/order-utils'; -import { AssetProxyId, ExchangeContractErrs, OrderState, SignedOrder } from '@0xproject/types'; +import { AssetProxyId, ExchangeContractErrs, OrderState, SignedOrder, Stats } from '@0xproject/types'; import { errorUtils, intervalUtils } from '@0xproject/utils'; import { BlockParamLiteral, LogEntryEvent, LogWithDecodedArgs, Provider } from 'ethereum-types'; import * as _ from 'lodash'; @@ -213,6 +213,14 @@ export class OrderWatcher { this._expirationWatcher.unsubscribe(); intervalUtils.clearAsyncExcludingInterval(this._cleanupJobIntervalIdIfExists); } + /** + * Gets statistics of the OrderWatcher Instance. + */ + public getStats(): Stats { + return { + orderCount: _.size(this._orderByOrderHash), + }; + } private async _cleanupAsync(): Promise<void> { for (const orderHash of _.keys(this._orderByOrderHash)) { this._cleanupOrderRelatedState(orderHash); diff --git a/packages/order-watcher/test/order_watcher_test.ts b/packages/order-watcher/test/order_watcher_test.ts index 60d9069e8..4545f84bf 100644 --- a/packages/order-watcher/test/order_watcher_test.ts +++ b/packages/order-watcher/test/order_watcher_test.ts @@ -140,6 +140,23 @@ describe('OrderWatcher', () => { expect(() => orderWatcher.subscribe(_.noop.bind(_))).to.throw(OrderWatcherError.SubscriptionAlreadyPresent); }); }); + describe('#getStats', async () => { + it('orderCount should increment and decrement with order additions and removals', async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + expect(orderWatcher.getStats().orderCount).to.be.eq(0); + await orderWatcher.addOrderAsync(signedOrder); + expect(orderWatcher.getStats().orderCount).to.be.eq(1); + orderWatcher.removeOrder(orderHash); + expect(orderWatcher.getStats().orderCount).to.be.eq(0); + }); + }); describe('tests with cleanup', async () => { afterEach(async () => { orderWatcher.unsubscribe(); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 6bc966ba1..d33048b61 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -620,3 +620,7 @@ export interface EIP712TypedData { message: EIP712Object; primaryType: string; } + +export interface Stats { + orderCount: number; +} diff --git a/packages/website/package.json b/packages/website/package.json index 82109d95f..8f69460d6 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -59,6 +59,7 @@ "react-typist": "^2.0.4", "redux": "^3.6.0", "redux-devtools-extension": "^2.13.2", + "rollbar": "^2.4.7", "semver-sort": "0.0.4", "styled-components": "^3.3.0", "thenby": "^1.2.3", diff --git a/packages/website/ts/utils/error_reporter.ts b/packages/website/ts/utils/error_reporter.ts index 6008fffed..bec92e829 100644 --- a/packages/website/ts/utils/error_reporter.ts +++ b/packages/website/ts/utils/error_reporter.ts @@ -1,4 +1,5 @@ import { logUtils } from '@0xproject/utils'; +import Rollbar = require('rollbar'); import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; @@ -36,8 +37,8 @@ const rollbarConfig = { 'SecurityError (DOM Exception 18)', ], }; -import Rollbar = require('../../public/js/rollbar.umd.min.js'); -const rollbar = Rollbar.init(rollbarConfig); + +const rollbar = new Rollbar(rollbarConfig); export const errorReporter = { report(err: Error): void { diff --git a/python-packages/order_utils/package.json b/python-packages/order_utils/package.json new file mode 100644 index 000000000..125ca71e0 --- /dev/null +++ b/python-packages/order_utils/package.json @@ -0,0 +1,17 @@ +{ + "comment": "this file exists as an entry point to building this project, specifically for humans that are familiar with yarn and already have it installed. this file is not used in any automation or CI.", + "scripts": { + "install": "pip install -e .[dev]", + "build": "python setup.py build && yarn build:docs", + "build:docs": "python setup.py build_sphinx && sphinx-apidoc -o build/docs/api src", + "test:comment": "test in local environment. to test in all environments, use test:all", + "test": "python setup.py test", + "test:all": "tox", + "test:coverage": "coverage run setup.py test && coveralls", + "lint": "python setup.py lint", + "clean": "python setup.py clean" + }, + "dependencies:comment": "managed in setup.py", + "devDependencies:comment": "managed in setup.py", + "license": "Apache-2.0" +} diff --git a/python-packages/order_utils/setup.py b/python-packages/order_utils/setup.py new file mode 100644 index 000000000..a76d724aa --- /dev/null +++ b/python-packages/order_utils/setup.py @@ -0,0 +1,128 @@ +"""setuptools module for order_utils package.""" + +import subprocess # nosec +from shutil import rmtree +from os import path, remove, walk + +from distutils.command.clean import clean # type: ignore +from setuptools import setup # type: ignore +import setuptools.command.build_py # type: ignore +from setuptools.command.test import test as TestCommand # type: ignore + + +class TestCommandExtension(TestCommand): + """Run pytest tests.""" + + def run_tests(self): + """Invoke pytest.""" + import pytest # type: ignore + + pytest.main() + + +# pylint: disable=too-many-ancestors +class LintCommand(setuptools.command.build_py.build_py): + """Custom setuptools command class for running linters.""" + + def run(self): + """Run linter shell commands.""" + lint_commands = [ + # formatter: + "black --line-length 79 --check --diff src test setup.py".split(), + # style guide checker (formerly pep8): + "pycodestyle src test setup.py".split(), + # docstring style checker: + "pydocstyle src test setup.py".split(), + # static type checker: + "mypy src setup.py".split(), + # security issue checker: + "bandit -r src ./setup.py".split(), + # general linter: + "pylint src test setup.py".split(), + # pylint takes relatively long to run, so it runs last, to enable + # fast failures. + ] + for lint_command in lint_commands: + print( + "Running lint command `", " ".join(lint_command).strip(), "`" + ) + subprocess.check_call(lint_command) # nosec + + +class CleanCommandExtension(clean): + """Custom command to do custom cleanup.""" + + def run(self): + """Run the regular clean, followed by our custom commands.""" + super().run() + rmtree("build", ignore_errors=True) + rmtree(".mypy_cache", ignore_errors=True) + rmtree(".tox", ignore_errors=True) + rmtree(".pytest_cache", ignore_errors=True) + rmtree("src/order_utils.egg-info", ignore_errors=True) + # delete all .pyc files + for root, _, files in walk("."): + for file in files: + (_, extension) = path.splitext(file) + if extension == ".pyc": + remove(path.join(root, file)) + + +setup( + name="order_utils", + version="1.0.0", + description="Order utilities for 0x applications", + author="F. Eugene Aumson", + cmdclass={ + "clean": CleanCommandExtension, + "lint": LintCommand, + "test": TestCommandExtension, + }, + include_package_data=True, + install_requires=["web3"], + extras_require={ + "dev": [ + "bandit", + "black", + "coverage", + "coveralls", + "mypy", + "pycodestyle", + "pydocstyle", + "pylint", + "pytest", + "sphinx", + "tox", + ] + }, + python_requires=">=3.6, <4", + package_data={"zero_ex.order_utils": ["py.typed"]}, + package_dir={"": "src"}, + license="Apache 2.0", + keywords=( + "ethereum cryptocurrency 0x decentralized blockchain dex exchange" + ), + packages=["zero_ex.order_utils"], + classifiers=[ + "Development Status :: 1 - Planning", + "Intended Audience :: Developers", + "Intended Audience :: Financial and Insurance Industry", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Topic :: Office/Business :: Financial", + "Topic :: Software Development :: Libraries", + "Topic :: Utilities", + ], + zip_safe=False, + command_options={ + "build_sphinx": { + "source_dir": ("setup.py", "src"), + "build_dir": ("setup.py", "build/docs"), + } + }, +) diff --git a/python-packages/order_utils/src/conf.py b/python-packages/order_utils/src/conf.py new file mode 100644 index 000000000..f3f15967c --- /dev/null +++ b/python-packages/order_utils/src/conf.py @@ -0,0 +1,50 @@ +"""Configuration file for the Sphinx documentation builder.""" + +# Reference: http://www.sphinx-doc.org/en/master/config + +# pylint: disable=invalid-name +# because these variables are not named in upper case, as globals should be. + +project = "order_utils.py" +# pylint: disable=redefined-builtin +copyright = "2018, ZeroEx, Intl." +author = "F. Eugene Aumson" +version = "" # The short X.Y version +release = "" # The full version, including alpha/beta/rc tags + +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.coverage", + "sphinx.ext.viewcode", +] + +templates_path = ["doc_templates"] + +source_suffix = ".rst" +# eg: source_suffix = [".rst", ".md"] + +master_doc = "index" # The master toctree document. + +language = None + +exclude_patterns = [] # type: ignore + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = None + +html_theme = "alabaster" + +html_static_path = ["doc_static"] +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". + +# Output file base name for HTML help builder. +htmlhelp_basename = "order_utilspydoc" + +# -- Extension configuration: + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {"https://docs.python.org/": None} diff --git a/python-packages/order_utils/src/index.rst b/python-packages/order_utils/src/index.rst new file mode 100644 index 000000000..cbc4c8409 --- /dev/null +++ b/python-packages/order_utils/src/index.rst @@ -0,0 +1,22 @@ +.. source for the sphinx-generated build/docs/web/index.html + +order_utils.py +============== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + +.. automodule:: zero_ex.order_utils + :members: + +.. automodule:: zero_ex.order_utils.signature_utils + :members: + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/python-packages/order_utils/src/zero_ex/__init__.py b/python-packages/order_utils/src/zero_ex/__init__.py new file mode 100644 index 000000000..c3ed1562a --- /dev/null +++ b/python-packages/order_utils/src/zero_ex/__init__.py @@ -0,0 +1 @@ +"""0x Python API.""" diff --git a/python-packages/order_utils/src/zero_ex/order_utils/__init__.py b/python-packages/order_utils/src/zero_ex/order_utils/__init__.py new file mode 100644 index 000000000..f014af0f6 --- /dev/null +++ b/python-packages/order_utils/src/zero_ex/order_utils/__init__.py @@ -0,0 +1 @@ +"""Order utilities for 0x applications.""" diff --git a/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py b/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py new file mode 100644 index 000000000..7f4697106 --- /dev/null +++ b/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py @@ -0,0 +1,13 @@ +"""Signature utilities.""" + + +def ec_sign_order_hash(): + """Signs an orderHash. + + Returns its elliptic curve signature and signature type. This method + currently supports TestRPC, Geth, and Parity above and below v1.6.6. + + >>> ec_sign_order_hash() + 'stub return value' + """ + return "stub return value" diff --git a/python-packages/order_utils/src/zero_ex/py.typed b/python-packages/order_utils/src/zero_ex/py.typed new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/python-packages/order_utils/src/zero_ex/py.typed diff --git a/python-packages/order_utils/test/__init__.py b/python-packages/order_utils/test/__init__.py new file mode 100644 index 000000000..ec5b114aa --- /dev/null +++ b/python-packages/order_utils/test/__init__.py @@ -0,0 +1 @@ +"""Tests of zero_x.order_utils.""" diff --git a/python-packages/order_utils/test/test_doctest.py b/python-packages/order_utils/test/test_doctest.py new file mode 100644 index 000000000..a0e61f84a --- /dev/null +++ b/python-packages/order_utils/test/test_doctest.py @@ -0,0 +1,10 @@ +"""Exercise doctests for order_utils module.""" + +from doctest import testmod +from zero_ex.order_utils import signature_utils + + +def test_doctest(): + """Invoke doctest on the module.""" + (failure_count, _) = testmod(signature_utils) + assert failure_count == 0 diff --git a/python-packages/order_utils/test/test_signature_utils.py b/python-packages/order_utils/test/test_signature_utils.py new file mode 100644 index 000000000..7e830f9f8 --- /dev/null +++ b/python-packages/order_utils/test/test_signature_utils.py @@ -0,0 +1,8 @@ +"""Tests of 0x.order_utils.signature_utils.*.""" + +from zero_ex.order_utils.signature_utils import ec_sign_order_hash + + +def test_ec_sign_order_hash(): + """Test the signing of order hashes.""" + assert ec_sign_order_hash() == "stub return value" diff --git a/python-packages/order_utils/tox.ini b/python-packages/order_utils/tox.ini new file mode 100644 index 000000000..1cce32b5f --- /dev/null +++ b/python-packages/order_utils/tox.ini @@ -0,0 +1,12 @@ +# tox (https://tox.readthedocs.io/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = py37 + +[testenv] +commands = + pip install -e .[dev] + python setup.py test @@ -3867,6 +3867,10 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" +console-polyfill@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/console-polyfill/-/console-polyfill-0.3.0.tgz#84900902a18c47a5eba932be75fa44d23e8af861" + constant-case@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-2.0.0.tgz#4175764d389d3fa9c8ecd29186ed6005243b6a46" @@ -5062,6 +5066,12 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +error-stack-parser@1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-1.3.3.tgz#fada6e3a9cd2b0e080e6d6fc751418649734f35c" + dependencies: + stackframe "^0.3.1" + es-abstract@^1.10.0, es-abstract@^1.5.1, es-abstract@^1.6.1: version "1.12.0" resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" @@ -7936,6 +7946,10 @@ is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" +is_js@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/is_js/-/is_js-0.9.0.tgz#0ab94540502ba7afa24c856aa985561669e9c52d" + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -12641,6 +12655,12 @@ request-ip@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/request-ip/-/request-ip-1.2.3.tgz#66988f0e22406ec4af630d19b573fe4b447c3b49" +request-ip@~2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/request-ip/-/request-ip-2.0.2.tgz#deeae6d4af21768497db8cd05fa37143f8f1257e" + dependencies: + is_js "^0.9.0" + request-promise-core@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6" @@ -12937,6 +12957,21 @@ rollbar@^0.6.5: optionalDependencies: decache "^3.0.5" +rollbar@^2.4.7: + version "2.4.7" + resolved "https://registry.yarnpkg.com/rollbar/-/rollbar-2.4.7.tgz#9b1de1a0fab6b6e63fcfcd322c26081a1d8242e8" + dependencies: + async "~1.2.1" + console-polyfill "0.3.0" + debug "2.6.9" + error-stack-parser "1.3.3" + json-stringify-safe "~5.0.0" + lru-cache "~2.2.1" + request-ip "~2.0.1" + uuid "3.0.x" + optionalDependencies: + decache "^3.0.5" + rst-selector-parser@^2.2.3: version "2.2.3" resolved "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" @@ -13791,6 +13826,10 @@ stack-utils@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620" +stackframe@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-0.3.1.tgz#33aa84f1177a5548c8935533cbfeb3420975f5a4" + state-toggle@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.0.tgz#d20f9a616bb4f0c3b98b91922d25b640aa2bc425" @@ -15213,7 +15252,7 @@ uuid@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.1.tgz#c2a30dedb3e535d72ccf82e343941a50ba8533ac" -uuid@3.0.1, uuid@~3.0.0: +uuid@3.0.1, uuid@3.0.x, uuid@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" |