From a7238d0fdb302d7062f3f63c3119910286f992c5 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 18 Jul 2018 16:21:24 -0700 Subject: Implement initial forwarder wrapper --- packages/contract-wrappers/package.json | 4 +- packages/contract-wrappers/src/artifacts.ts | 2 + .../contract-wrappers/src/contract_wrappers.ts | 7 + .../src/contract_wrappers/forwarder_wrapper.ts | 170 +++++++++++++++++++++ packages/contract-wrappers/src/index.ts | 1 + packages/contract-wrappers/src/types.ts | 2 + packages/contract-wrappers/src/utils/constants.ts | 1 + .../test/forwarder_wrapper_test.ts | 57 +++++++ 8 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts create mode 100644 packages/contract-wrappers/test/forwarder_wrapper_test.ts diff --git a/packages/contract-wrappers/package.json b/packages/contract-wrappers/package.json index ed0278caa..f27afaba9 100644 --- a/packages/contract-wrappers/package.json +++ b/packages/contract-wrappers/package.json @@ -14,7 +14,7 @@ "watch_without_deps": "yarn pre_build && tsc -w", "build": "yarn pre_build && tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts", "pre_build": "run-s update_artifacts_v2_beta update_artifacts_v2 generate_contract_wrappers copy_artifacts", - "generate_contract_wrappers": "abi-gen --abis 'src/artifacts/@(Exchange|DummyERC20Token|DummyERC721Token|ZRXToken|ERC20Token|ERC721Token|WETH9|ERC20Proxy|ERC721Proxy).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers", + "generate_contract_wrappers": "abi-gen --abis 'src/artifacts/@(Exchange|DummyERC20Token|DummyERC721Token|ZRXToken|ERC20Token|ERC721Token|WETH9|ERC20Proxy|ERC721Proxy|Forwarder).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers", "lint": "tslint --project . --exclude **/src/contract_wrappers/**/* --exclude **/lib/**/*", "test:circleci": "run-s test:coverage", "test": "yarn run_mocha", @@ -29,7 +29,7 @@ "manual:postpublish": "yarn build; node ./scripts/postpublish.js" }, "config": { - "contracts_v2_beta": "Exchange ERC20Proxy ERC20Token ERC721Proxy ERC721Token WETH9 ZRXToken", + "contracts_v2_beta": "Exchange ERC20Proxy ERC20Token ERC721Proxy ERC721Token WETH9 ZRXToken Forwarder", "contracts_v2": "DummyERC20Token DummyERC721Token" }, "repository": { diff --git a/packages/contract-wrappers/src/artifacts.ts b/packages/contract-wrappers/src/artifacts.ts index 742d0e1b2..2481b311a 100644 --- a/packages/contract-wrappers/src/artifacts.ts +++ b/packages/contract-wrappers/src/artifacts.ts @@ -7,6 +7,7 @@ import * as ERC20Token from './artifacts/ERC20Token.json'; import * as ERC721Proxy from './artifacts/ERC721Proxy.json'; import * as ERC721Token from './artifacts/ERC721Token.json'; import * as Exchange from './artifacts/Exchange.json'; +import * as Forwarder from './artifacts/Forwarder.json'; import * as EtherToken from './artifacts/WETH9.json'; import * as ZRXToken from './artifacts/ZRXToken.json'; @@ -20,4 +21,5 @@ export const artifacts = { EtherToken: (EtherToken as any) as ContractArtifact, ERC20Proxy: (ERC20Proxy as any) as ContractArtifact, ERC721Proxy: (ERC721Proxy as any) as ContractArtifact, + Forwarder: (Forwarder as any) as ContractArtifact, }; diff --git a/packages/contract-wrappers/src/contract_wrappers.ts b/packages/contract-wrappers/src/contract_wrappers.ts index 8010242c5..76aefbdcf 100644 --- a/packages/contract-wrappers/src/contract_wrappers.ts +++ b/packages/contract-wrappers/src/contract_wrappers.ts @@ -11,6 +11,7 @@ import { ERC721ProxyWrapper } from './contract_wrappers/erc721_proxy_wrapper'; import { ERC721TokenWrapper } from './contract_wrappers/erc721_token_wrapper'; import { EtherTokenWrapper } from './contract_wrappers/ether_token_wrapper'; import { ExchangeWrapper } from './contract_wrappers/exchange_wrapper'; +import { ForwarderWrapper } from './contract_wrappers/forwarder_wrapper'; import { ContractWrappersConfigSchema } from './schemas/contract_wrappers_config_schema'; import { contractWrappersPrivateNetworkConfigSchema } from './schemas/contract_wrappers_private_network_config_schema'; import { contractWrappersPublicNetworkConfigSchema } from './schemas/contract_wrappers_public_network_config_schema'; @@ -47,6 +48,11 @@ export class ContractWrappers { * erc721Proxy smart contract. */ public erc721Proxy: ERC721ProxyWrapper; + /** + * An instance of the ForwarderWrapper class containing methods for interacting with any Forwarder smart contract. + */ + public forwarder: ForwarderWrapper; + private _web3Wrapper: Web3Wrapper; /** * Instantiates a new ContractWrappers instance. @@ -104,6 +110,7 @@ export class ContractWrappers { config.zrxContractAddress, blockPollingIntervalMs, ); + this.forwarder = new ForwarderWrapper(this._web3Wrapper, config.networkId, config.forwarderContractAddress); } /** * Sets a new web3 provider for 0x.js. Updating the provider will stop all diff --git a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts new file mode 100644 index 000000000..56533fb78 --- /dev/null +++ b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts @@ -0,0 +1,170 @@ +import { schemas } from '@0xproject/json-schemas'; +import { AssetProxyId, SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { ContractAbi } from 'ethereum-types'; +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 { assert } from '../utils/assert'; +import { constants } from '../utils/constants'; + +import { ContractWrapper } from './contract_wrapper'; +import { ForwarderContract } from './generated/forwarder'; + +/** + * This class includes the functionality related to interacting with the Forwarder contract. + */ +export class ForwarderWrapper extends ContractWrapper { + public abi: ContractAbi = artifacts.Forwarder.compilerOutput.abi; + private _forwarderContractIfExists?: ForwarderContract; + private _contractAddressIfExists?: string; + constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { + super(web3Wrapper, networkId); + this._contractAddressIfExists = contractAddressIfExists; + } + /** + * Purchases as much of orders' makerAssets as possible by selling up to 95% of transaction's ETH value. + * 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 + * @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. + * @return Transaction hash. + */ + public async marketSellOrdersWithEthAsync( + signedOrders: SignedOrder[], + takerAddress: string, + ethAmount: BigNumber, + signedFeeOrders: SignedOrder[] = [], + feePercentage: BigNumber = constants.ZERO_AMOUNT, + feeRecipientAddress: string = constants.NULL_ADDRESS, + txOpts: TransactionOpts = {}, + ): Promise { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + assert.isBigNumber('ethAmount', ethAmount); + assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema); + assert.isBigNumber('feePercentage', feePercentage); + assert.isETHAddressHex('feeRecipientAddress', feeRecipientAddress); + assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); + const normalizedTakerAddress = takerAddress.toLowerCase(); + const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase(); + const ForwarderContractInstance = await this._getForwarderContractAsync(); + const txHash = await ForwarderContractInstance.marketSellOrdersWithEth.sendTransactionAsync( + signedOrders, + _.map(signedOrders, order => order.signature), + signedFeeOrders, + _.map(signedFeeOrders, order => order.signature), + feePercentage, + feeRecipientAddress, + { + value: ethAmount, + from: normalizedTakerAddress, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }, + ); + return txHash; + } + /** + * 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 + * @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. + * @return Transaction hash. + */ + public async marketBuyOrdersWithEthAsync( + signedOrders: SignedOrder[], + makerAssetFillAmount: BigNumber, + takerAddress: string, + ethAmount: BigNumber, + signedFeeOrders: SignedOrder[] = [], + feePercentage: BigNumber = constants.ZERO_AMOUNT, + feeRecipientAddress: string = constants.NULL_ADDRESS, + txOpts: TransactionOpts = {}, + ): Promise { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + assert.isBigNumber('ethAmount', ethAmount); + assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema); + assert.isBigNumber('feePercentage', feePercentage); + assert.isETHAddressHex('feeRecipientAddress', feeRecipientAddress); + assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); + const normalizedTakerAddress = takerAddress.toLowerCase(); + const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase(); + const ForwarderContractInstance = await this._getForwarderContractAsync(); + const txHash = await ForwarderContractInstance.marketBuyOrdersWithEth.sendTransactionAsync( + signedOrders, + makerAssetFillAmount, + _.map(signedOrders, order => order.signature), + signedFeeOrders, + _.map(signedFeeOrders, order => order.signature), + feePercentage, + feeRecipientAddress, + { + value: ethAmount, + from: normalizedTakerAddress, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Retrieves the Ethereum address of the Forwarder contract deployed on the network + * that the user-passed web3 provider is connected to. + * @returns The Ethereum address of the Forwarder contract being used. + */ + public getContractAddress(): string { + const contractAddress = this._getContractAddress(artifacts.Forwarder, this._contractAddressIfExists); + return contractAddress; + } + // HACK: We don't want this method to be visible to the other units within that package but not to the end user. + // TS doesn't give that possibility and therefore we make it private and access it over an any cast. Because of that tslint sees it as unused. + // tslint:disable-next-line:no-unused-variable + private _invalidateContractInstance(): void { + delete this._forwarderContractIfExists; + } + private async _getForwarderContractAsync(): Promise { + if (!_.isUndefined(this._forwarderContractIfExists)) { + return this._forwarderContractIfExists; + } + const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( + artifacts.Forwarder, + this._contractAddressIfExists, + ); + const contractInstance = new ForwarderContract( + abi, + address, + this._web3Wrapper.getProvider(), + this._web3Wrapper.getContractDefaults(), + ); + this._forwarderContractIfExists = contractInstance; + return this._forwarderContractIfExists; + } +} diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts index e5485d7a6..1986e0004 100644 --- a/packages/contract-wrappers/src/index.ts +++ b/packages/contract-wrappers/src/index.ts @@ -5,6 +5,7 @@ export { EtherTokenWrapper } from './contract_wrappers/ether_token_wrapper'; export { ExchangeWrapper } from './contract_wrappers/exchange_wrapper'; export { ERC20ProxyWrapper } from './contract_wrappers/erc20_proxy_wrapper'; export { ERC721ProxyWrapper } from './contract_wrappers/erc721_proxy_wrapper'; +export { ForwarderWrapper } from './contract_wrappers/forwarder_wrapper'; export { ContractWrappersError, diff --git a/packages/contract-wrappers/src/types.ts b/packages/contract-wrappers/src/types.ts index f9d7a6b9f..887d09c80 100644 --- a/packages/contract-wrappers/src/types.ts +++ b/packages/contract-wrappers/src/types.ts @@ -109,6 +109,7 @@ export type SyncMethod = (...args: any[]) => any; * zrxContractAddress: The address of the ZRX contract to use * erc20ProxyContractAddress: The address of the erc20 token transfer proxy contract to use * erc721ProxyContractAddress: The address of the erc721 token transfer proxy contract to use + * forwarderContractAddress: The address of the forwarder contract to use * orderWatcherConfig: All the configs related to the orderWatcher * blockPollingIntervalMs: The interval to use for block polling in event watching methods (defaults to 1000) */ @@ -119,6 +120,7 @@ export interface ContractWrappersConfig { zrxContractAddress?: string; erc20ProxyContractAddress?: string; erc721ProxyContractAddress?: string; + forwarderContractAddress?: string; blockPollingIntervalMs?: number; } diff --git a/packages/contract-wrappers/src/utils/constants.ts b/packages/contract-wrappers/src/utils/constants.ts index 039475b7f..d436efefc 100644 --- a/packages/contract-wrappers/src/utils/constants.ts +++ b/packages/contract-wrappers/src/utils/constants.ts @@ -10,4 +10,5 @@ export const constants = { // tslint:disable-next-line:custom-no-magic-numbers UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1), DEFAULT_BLOCK_POLLING_INTERVAL: 1000, + ZERO_AMOUNT: new BigNumber(0), }; diff --git a/packages/contract-wrappers/test/forwarder_wrapper_test.ts b/packages/contract-wrappers/test/forwarder_wrapper_test.ts new file mode 100644 index 000000000..61a21a0d7 --- /dev/null +++ b/packages/contract-wrappers/test/forwarder_wrapper_test.ts @@ -0,0 +1,57 @@ +import { BlockchainLifecycle, callbackErrorReporter } from '@0xproject/dev-utils'; +import { FillScenarios } from '@0xproject/fill-scenarios'; +import { assetDataUtils, orderHashUtils } from '@0xproject/order-utils'; +import { DoneCallback, SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; +import { BlockParamLiteral } from 'ethereum-types'; +import 'mocha'; + +import { + ContractWrappers, + DecodedLogEvent, + ExchangeCancelEventArgs, + ExchangeEvents, + ExchangeFillEventArgs, + OrderStatus, +} from '../src'; + +import { chaiSetup } from './utils/chai_setup'; +import { constants } from './utils/constants'; +import { tokenUtils } from './utils/token_utils'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('ForwarderWrapper', () => { + let contractWrappers: ContractWrappers; + let forwarderContractAddress: string; + let userAddresses: string[]; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + blockPollingIntervalMs: 0, + }; + before(async () => { + await blockchainLifecycle.startAsync(); + contractWrappers = new ContractWrappers(provider, config); + forwarderContractAddress = contractWrappers.exchange.getContractAddress(); + userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + // describe('#fillOrderAsync', () => { + // it('should fill a valid order', async () => { + // // txHash = await contractWrappers.exchange.fillOrderAsync(signedOrder, takerTokenFillAmount, takerAddress); + // // await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); + // }); + // }); +}); -- cgit From 045751a430c512d94bf7e515d7531bac68dc2179 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 30 Jul 2018 17:43:02 -0700 Subject: Add getOrdersInfo to exchange_wrapper --- .../src/contract_wrappers/exchange_wrapper.ts | 22 +++++++++++++++++++++- packages/contract-wrappers/src/types.ts | 4 ++-- .../test/exchange_wrapper_test.ts | 9 +++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index 3e7619228..48bd00f90 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -869,15 +869,35 @@ export class ExchangeWrapper extends ContractWrapper { */ @decorators.asyncZeroExErrorHandler public async getOrderInfoAsync(order: Order | SignedOrder, methodOpts: MethodOpts = {}): Promise { + assert.doesConformToSchema('order', order, schemas.orderSchema); if (!_.isUndefined(methodOpts)) { assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema); } const exchangeInstance = await this._getExchangeContractAsync(); - const txData = {}; const orderInfo = await exchangeInstance.getOrderInfo.callAsync(order, txData, methodOpts.defaultBlock); return orderInfo; } + /** + * Get order info for multiple orders + * @param orders Orders + * @param methodOpts Optional arguments this method accepts. + * @returns Array of Order infos + */ + @decorators.asyncZeroExErrorHandler + public async getOrdersInfoAsync( + orders: Array, + methodOpts: MethodOpts = {}, + ): Promise { + assert.doesConformToSchema('orders', orders, schemas.ordersSchema); + if (!_.isUndefined(methodOpts)) { + assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema); + } + const exchangeInstance = await this._getExchangeContractAsync(); + const txData = {}; + const ordersInfo = await exchangeInstance.getOrdersInfo.callAsync(orders, txData, methodOpts.defaultBlock); + return ordersInfo; + } /** * Cancel a given order. * @param order An object that conforms to the Order or SignedOrder interface. The order you would like to cancel. diff --git a/packages/contract-wrappers/src/types.ts b/packages/contract-wrappers/src/types.ts index 887d09c80..2b3cdc591 100644 --- a/packages/contract-wrappers/src/types.ts +++ b/packages/contract-wrappers/src/types.ts @@ -174,13 +174,13 @@ export enum TransferType { export type OnOrderStateChangeCallback = (err: Error | null, orderState?: OrderState) => void; export interface OrderInfo { - orderStatus: number; + orderStatus: OrderStatus; orderHash: string; orderTakerAssetFilledAmount: BigNumber; } export enum OrderStatus { - INVALID, + INVALID = 0, INVALID_MAKER_ASSET_AMOUNT, INVALID_TAKER_ASSET_AMOUNT, FILLABLE, diff --git a/packages/contract-wrappers/test/exchange_wrapper_test.ts b/packages/contract-wrappers/test/exchange_wrapper_test.ts index dca212f65..e468187f2 100644 --- a/packages/contract-wrappers/test/exchange_wrapper_test.ts +++ b/packages/contract-wrappers/test/exchange_wrapper_test.ts @@ -277,6 +277,15 @@ describe('ExchangeWrapper', () => { expect(orderInfo.orderHash).to.be.equal(orderHash); }); }); + describe('#getOrdersInfoAsync', () => { + it('should get the orders info', async () => { + const ordersInfo = await contractWrappers.exchange.getOrdersInfoAsync([signedOrder, anotherSignedOrder]); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + expect(ordersInfo[0].orderHash).to.be.equal(orderHash); + const anotherOrderHash = orderHashUtils.getOrderHashHex(anotherSignedOrder); + expect(ordersInfo[1].orderHash).to.be.equal(anotherOrderHash); + }); + }); describe('#isValidSignature', () => { it('should check if the signature is valid', async () => { const orderHash = orderHashUtils.getOrderHashHex(signedOrder); -- cgit From 44498f2263ccfec805a6815a1ab673e2dd423e8d Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 30 Jul 2018 17:51:02 -0700 Subject: Fix spelling error in exchange wrapper tests --- packages/contract-wrappers/test/exchange_wrapper_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contract-wrappers/test/exchange_wrapper_test.ts b/packages/contract-wrappers/test/exchange_wrapper_test.ts index e468187f2..fa3b49eb9 100644 --- a/packages/contract-wrappers/test/exchange_wrapper_test.ts +++ b/packages/contract-wrappers/test/exchange_wrapper_test.ts @@ -304,7 +304,7 @@ describe('ExchangeWrapper', () => { }); }); describe('#isAllowedValidatorAsync', () => { - it('should check if the validator is alllowed', async () => { + it('should check if the validator is allowed', async () => { const signerAddress = makerAddress; const validatorAddress = constants.NULL_ADDRESS; const isAllowed = await contractWrappers.exchange.isAllowedValidatorAsync(signerAddress, validatorAddress); -- cgit From bc93ff0cb5c846fcb534f90b93e54bba0747f562 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 30 Jul 2018 19:43:44 -0700 Subject: Write initial test for forwarder_wrapper --- .../src/contract_wrappers/forwarder_wrapper.ts | 8 +-- .../test/forwarder_wrapper_test.ts | 81 ++++++++++++++++++---- 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts index 56533fb78..db1e5390a 100644 --- a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts @@ -62,8 +62,8 @@ export class ForwarderWrapper extends ContractWrapper { assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); const normalizedTakerAddress = takerAddress.toLowerCase(); const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase(); - const ForwarderContractInstance = await this._getForwarderContractAsync(); - const txHash = await ForwarderContractInstance.marketSellOrdersWithEth.sendTransactionAsync( + const forwarderContractInstance = await this._getForwarderContractAsync(); + const txHash = await forwarderContractInstance.marketSellOrdersWithEth.sendTransactionAsync( signedOrders, _.map(signedOrders, order => order.signature), signedFeeOrders, @@ -117,8 +117,8 @@ export class ForwarderWrapper extends ContractWrapper { assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); const normalizedTakerAddress = takerAddress.toLowerCase(); const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase(); - const ForwarderContractInstance = await this._getForwarderContractAsync(); - const txHash = await ForwarderContractInstance.marketBuyOrdersWithEth.sendTransactionAsync( + const forwarderContractInstance = await this._getForwarderContractAsync(); + const txHash = await forwarderContractInstance.marketBuyOrdersWithEth.sendTransactionAsync( signedOrders, makerAssetFillAmount, _.map(signedOrders, order => order.signature), diff --git a/packages/contract-wrappers/test/forwarder_wrapper_test.ts b/packages/contract-wrappers/test/forwarder_wrapper_test.ts index 61a21a0d7..0fb695b9e 100644 --- a/packages/contract-wrappers/test/forwarder_wrapper_test.ts +++ b/packages/contract-wrappers/test/forwarder_wrapper_test.ts @@ -26,18 +26,65 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); describe('ForwarderWrapper', () => { - let contractWrappers: ContractWrappers; - let forwarderContractAddress: string; - let userAddresses: string[]; - const config = { + const contractWrappersConfig = { networkId: constants.TESTRPC_NETWORK_ID, blockPollingIntervalMs: 0, }; + const fillableAmount = new BigNumber(5); + const takerTokenFillAmount = new BigNumber(5); + let contractWrappers: ContractWrappers; + let fillScenarios: FillScenarios; + let forwarderContractAddress: string; + let exchangeContractAddress: string; + let zrxTokenAddress: string; + let userAddresses: string[]; + let coinbase: string; + let makerAddress: string; + let takerAddress: string; + let feeRecipient: string; + let anotherMakerAddress: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let makerAssetData: string; + let takerAssetData: string; + let signedOrder: SignedOrder; + let anotherSignedOrder: SignedOrder; before(async () => { await blockchainLifecycle.startAsync(); - contractWrappers = new ContractWrappers(provider, config); - forwarderContractAddress = contractWrappers.exchange.getContractAddress(); + contractWrappers = new ContractWrappers(provider, contractWrappersConfig); + forwarderContractAddress = contractWrappers.forwarder.getContractAddress(); + exchangeContractAddress = contractWrappers.exchange.getContractAddress(); userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + zrxTokenAddress = tokenUtils.getProtocolTokenAddress(); + fillScenarios = new FillScenarios( + provider, + userAddresses, + zrxTokenAddress, + exchangeContractAddress, + contractWrappers.erc20Proxy.getContractAddress(), + contractWrappers.erc721Proxy.getContractAddress(), + ); + [coinbase, makerAddress, takerAddress, feeRecipient, anotherMakerAddress] = userAddresses; + [makerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + takerTokenAddress = tokenUtils.getWethTokenAddress(); + [makerAssetData, takerAssetData] = [ + assetDataUtils.encodeERC20AssetData(makerTokenAddress), + assetDataUtils.encodeERC20AssetData(takerTokenAddress), + ]; + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + constants.NULL_ADDRESS, + fillableAmount, + ); + anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + constants.NULL_ADDRESS, + fillableAmount, + ); }); after(async () => { await blockchainLifecycle.revertAsync(); @@ -48,10 +95,20 @@ describe('ForwarderWrapper', () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - // describe('#fillOrderAsync', () => { - // it('should fill a valid order', async () => { - // // txHash = await contractWrappers.exchange.fillOrderAsync(signedOrder, takerTokenFillAmount, takerAddress); - // // await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); - // }); - // }); + describe('#marketBuyOrdersWithEthAsync', () => { + it('should market buy orders with eth', async () => { + const signedOrders = [signedOrder, anotherSignedOrder]; + const makerAssetFillAmount = signedOrder.makerAssetAmount.plus(anotherSignedOrder.makerAssetAmount); + const txHash = await contractWrappers.forwarder.marketBuyOrdersWithEthAsync( + signedOrders, + makerAssetFillAmount, + takerAddress, + makerAssetFillAmount, + ); + await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); + const ordersInfo = await contractWrappers.exchange.getOrdersInfoAsync([signedOrder, anotherSignedOrder]); + expect(ordersInfo[0].orderStatus).to.be.equal(OrderStatus.FULLY_FILLED); + expect(ordersInfo[1].orderStatus).to.be.equal(OrderStatus.FULLY_FILLED); + }); + }); }); -- cgit From 8ed3d59f969c2f07e34739c5a08c69de583cef88 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 30 Jul 2018 22:14:07 -0700 Subject: Add more assertions --- .../contract-wrappers/src/contract_wrappers.ts | 7 ++- .../src/contract_wrappers/forwarder_wrapper.ts | 41 ++++++++++++++- packages/contract-wrappers/src/utils/assert.ts | 60 ++++++++++++++++++++-- 3 files changed, 102 insertions(+), 6 deletions(-) diff --git a/packages/contract-wrappers/src/contract_wrappers.ts b/packages/contract-wrappers/src/contract_wrappers.ts index 76aefbdcf..4277a0746 100644 --- a/packages/contract-wrappers/src/contract_wrappers.ts +++ b/packages/contract-wrappers/src/contract_wrappers.ts @@ -110,7 +110,12 @@ export class ContractWrappers { config.zrxContractAddress, blockPollingIntervalMs, ); - this.forwarder = new ForwarderWrapper(this._web3Wrapper, config.networkId, config.forwarderContractAddress); + this.forwarder = new ForwarderWrapper( + this._web3Wrapper, + config.networkId, + config.forwarderContractAddress, + config.zrxContractAddress, + ); } /** * Sets a new web3 provider for 0x.js. Updating the provider will stop all diff --git a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts index db1e5390a..beb2d1c81 100644 --- a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts @@ -22,9 +22,16 @@ export class ForwarderWrapper extends ContractWrapper { public abi: ContractAbi = artifacts.Forwarder.compilerOutput.abi; private _forwarderContractIfExists?: ForwarderContract; private _contractAddressIfExists?: string; - constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { + private _zrxContractAddressIfExists?: string; + constructor( + web3Wrapper: Web3Wrapper, + networkId: number, + contractAddressIfExists?: string, + zrxContractAddressIfExists?: string, + ) { super(web3Wrapper, networkId); this._contractAddressIfExists = contractAddressIfExists; + this._zrxContractAddressIfExists = zrxContractAddressIfExists; } /** * Purchases as much of orders' makerAssets as possible by selling up to 95% of transaction's ETH value. @@ -53,6 +60,7 @@ export class ForwarderWrapper extends ContractWrapper { feeRecipientAddress: string = constants.NULL_ADDRESS, txOpts: TransactionOpts = {}, ): Promise { + // type assertions assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); assert.isBigNumber('ethAmount', ethAmount); @@ -60,6 +68,13 @@ export class ForwarderWrapper extends ContractWrapper { assert.isBigNumber('feePercentage', feePercentage); assert.isETHAddressHex('feeRecipientAddress', feeRecipientAddress); assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); + // other assertions + assert.ordersCanBeUsedForForwarderContract(signedOrders, this.getEtherTokenAddress()); + assert.feeOrdersCanBeUsedForForwarderContract( + signedFeeOrders, + this.getZRXTokenAddress(), + this.getEtherTokenAddress(), + ); const normalizedTakerAddress = takerAddress.toLowerCase(); const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase(); const forwarderContractInstance = await this._getForwarderContractAsync(); @@ -107,6 +122,7 @@ export class ForwarderWrapper extends ContractWrapper { feeRecipientAddress: string = constants.NULL_ADDRESS, txOpts: TransactionOpts = {}, ): Promise { + // type assertions assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount); await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); @@ -115,6 +131,13 @@ export class ForwarderWrapper extends ContractWrapper { assert.isBigNumber('feePercentage', feePercentage); assert.isETHAddressHex('feeRecipientAddress', feeRecipientAddress); assert.doesConformToSchema('txOpts', txOpts, txOptsSchema); + // other assertions + assert.ordersCanBeUsedForForwarderContract(signedOrders, this.getEtherTokenAddress()); + assert.feeOrdersCanBeUsedForForwarderContract( + signedFeeOrders, + this.getZRXTokenAddress(), + this.getEtherTokenAddress(), + ); const normalizedTakerAddress = takerAddress.toLowerCase(); const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase(); const forwarderContractInstance = await this._getForwarderContractAsync(); @@ -144,6 +167,22 @@ export class ForwarderWrapper extends ContractWrapper { const contractAddress = this._getContractAddress(artifacts.Forwarder, this._contractAddressIfExists); return contractAddress; } + /** + * Returns the ZRX token address used by the forwarder contract. + * @return Address of ZRX token + */ + public getZRXTokenAddress(): string { + const contractAddress = this._getContractAddress(artifacts.ZRXToken, this._zrxContractAddressIfExists); + return contractAddress; + } + /** + * Returns the Ether token address used by the forwarder contract. + * @return Address of Ether token + */ + public getEtherTokenAddress(): string { + const contractAddress = this._getContractAddress(artifacts.EtherToken); + return contractAddress; + } // HACK: We don't want this method to be visible to the other units within that package but not to the end user. // TS doesn't give that possibility and therefore we make it private and access it over an any cast. Because of that tslint sees it as unused. // tslint:disable-next-line:no-unused-variable diff --git a/packages/contract-wrappers/src/utils/assert.ts b/packages/contract-wrappers/src/utils/assert.ts index 842b16fa0..183642170 100644 --- a/packages/contract-wrappers/src/utils/assert.ts +++ b/packages/contract-wrappers/src/utils/assert.ts @@ -1,11 +1,14 @@ import { assert as sharedAssert } from '@0xproject/assert'; // HACK: We need those two unused imports because they're actually used by sharedAssert which gets injected here import { Schema } from '@0xproject/json-schemas'; // tslint:disable-line:no-unused-variable -import { isValidSignatureAsync } from '@0xproject/order-utils'; -import { ECSignature } from '@0xproject/types'; // tslint:disable-line:no-unused-variable +import { assetDataUtils, isValidSignatureAsync } from '@0xproject/order-utils'; +import { ECSignature, Order } from '@0xproject/types'; // tslint:disable-line:no-unused-variable import { BigNumber } from '@0xproject/utils'; // tslint:disable-line:no-unused-variable import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Provider } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { constants } from './constants'; export const assert = { ...sharedAssert, @@ -16,12 +19,12 @@ export const assert = { signerAddress: string, ): Promise { const isValid = await isValidSignatureAsync(provider, orderHash, signature, signerAddress); - this.assert(isValid, `Expected order with hash '${orderHash}' to have a valid signature`); + sharedAssert.assert(isValid, `Expected order with hash '${orderHash}' to have a valid signature`); }, isValidSubscriptionToken(variableName: string, subscriptionToken: string): void { const uuidRegex = new RegExp('^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$'); const isValid = uuidRegex.test(subscriptionToken); - this.assert(isValid, `Expected ${variableName} to be a valid subscription token`); + sharedAssert.assert(isValid, `Expected ${variableName} to be a valid subscription token`); }, async isSenderAddressAsync( variableName: string, @@ -35,4 +38,53 @@ export const assert = { `Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`, ); }, + ordersCanBeUsedForForwarderContract(orders: Order[], etherTokenAddress: string): void { + sharedAssert.assert(!_.isEmpty(orders), 'Expected at least 1 signed order. Found no orders'); + assert.ordersHaveAtMostOneUniqueValueForProperty(orders, 'makerAssetData'); + assert.allTakerAssetDatasAreErc20Token(orders, etherTokenAddress); + assert.allTakerAddressesAreNull(orders); + }, + feeOrdersCanBeUsedForForwarderContract(orders: Order[], zrxTokenAddress: string, etherTokenAddress: string): void { + if (!_.isEmpty(orders)) { + assert.allMakerAssetDatasAreErc20Token(orders, zrxTokenAddress); + assert.allTakerAssetDatasAreErc20Token(orders, etherTokenAddress); + } + }, + allTakerAddressesAreNull(orders: Order[]): void { + assert.ordersHaveAtMostOneUniqueValueForProperty(orders, 'takerAddress', constants.NULL_ADDRESS); + }, + allMakerAssetDatasAreErc20Token(orders: Order[], tokenAddress: string): void { + assert.ordersHaveAtMostOneUniqueValueForProperty( + orders, + 'makerAssetData', + assetDataUtils.encodeERC20AssetData(tokenAddress), + ); + }, + allTakerAssetDatasAreErc20Token(orders: Order[], tokenAddress: string): void { + assert.ordersHaveAtMostOneUniqueValueForProperty( + orders, + 'takerAssetData', + assetDataUtils.encodeERC20AssetData(tokenAddress), + ); + }, + /* + * Asserts that all the orders have the same value for the provided propertyName + * If the value parameter is provided, this asserts that all orders have the prope + */ + ordersHaveAtMostOneUniqueValueForProperty(orders: Order[], propertyName: string, value?: any): void { + const allValues = _.map(orders, order => _.get(order, propertyName)); + sharedAssert.hasAtMostOneUniqueValue( + allValues, + `Expected all orders to have the same ${propertyName} field. Found the following ${propertyName} values: ${JSON.stringify( + allValues, + )}`, + ); + if (!_.isUndefined(value)) { + const firstValue = _.head(allValues); + sharedAssert.assert( + firstValue === value, + `Expected all orders to have a ${propertyName} field with value: ${value}. Found: ${firstValue}`, + ); + } + }, }; -- cgit From 5d44a67e62eb47ba4a8664e83ed46568df5eb78f Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Tue, 31 Jul 2018 00:10:58 -0700 Subject: Update forwarder_wrapper_test --- .../contract-wrappers/test/forwarder_wrapper_test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/contract-wrappers/test/forwarder_wrapper_test.ts b/packages/contract-wrappers/test/forwarder_wrapper_test.ts index 0fb695b9e..3f3b40e0b 100644 --- a/packages/contract-wrappers/test/forwarder_wrapper_test.ts +++ b/packages/contract-wrappers/test/forwarder_wrapper_test.ts @@ -111,4 +111,20 @@ describe('ForwarderWrapper', () => { expect(ordersInfo[1].orderStatus).to.be.equal(OrderStatus.FULLY_FILLED); }); }); + describe('#marketSellOrdersWithEthAsync', () => { + it('should market sell orders with eth', async () => { + const signedOrders = [signedOrder, anotherSignedOrder]; + const makerAssetFillAmount = signedOrder.makerAssetAmount.plus(anotherSignedOrder.makerAssetAmount); + const txHash = await contractWrappers.forwarder.marketSellOrdersWithEthAsync( + signedOrders, + takerAddress, + makerAssetFillAmount, + ); + await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); + const ordersInfo = await contractWrappers.exchange.getOrdersInfoAsync([signedOrder, anotherSignedOrder]); + expect(ordersInfo[0].orderStatus).to.be.equal(OrderStatus.FULLY_FILLED); + 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 + }); + }); }); -- cgit From ca1f926d6d137f9523a9765c047430ec39d45d86 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Tue, 31 Jul 2018 00:26:53 -0700 Subject: Clarify ethAmount is in wei --- packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts index beb2d1c81..90cfbe2af 100644 --- a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts @@ -42,7 +42,7 @@ export class ForwarderWrapper extends ContractWrapper { * 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 + * @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. @@ -103,7 +103,7 @@ export class ForwarderWrapper extends ContractWrapper { * @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 + * @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. -- cgit From 9f7f61085c1a6989b79df575beb0b5d8f2b3652d Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 1 Aug 2018 15:29:47 -0700 Subject: Update contract-wrappers CHANGELOG.json --- packages/contract-wrappers/CHANGELOG.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index d9eef089f..8fa31052c 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "1.1.0-rc.2", + "changes": [ + { + "note": "Add ForwarderWrapper", + "pr": 934 + } + ] + }, { "version": "1.0.1-rc.2", "changes": [ -- cgit From 4f006fdc5c3cc24bb5d008f4c363d5225fe6728a Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 1 Aug 2018 14:27:17 -0700 Subject: Create marketBuyOrdersOptimizations --- .../src/contract_wrappers/forwarder_wrapper.ts | 27 ++++++++++---- packages/contract-wrappers/src/utils/constants.ts | 1 + .../src/utils/market_orders_optimization_utils.ts | 43 ++++++++++++++++++++++ 3 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 packages/contract-wrappers/src/utils/market_orders_optimization_utils.ts diff --git a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts index 90cfbe2af..7d51889cf 100644 --- a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts @@ -11,6 +11,7 @@ import { txOptsSchema } from '../schemas/tx_opts_schema'; import { TransactionOpts } from '../types'; import { assert } from '../utils/assert'; import { constants } from '../utils/constants'; +import { marketOrdersOptimizationUtils } from '../utils/market_orders_optimization_utils'; import { ContractWrapper } from './contract_wrapper'; import { ForwarderContract } from './generated/forwarder'; @@ -75,14 +76,19 @@ export class ForwarderWrapper extends ContractWrapper { this.getZRXTokenAddress(), this.getEtherTokenAddress(), ); + // lowercase input addresses const normalizedTakerAddress = takerAddress.toLowerCase(); const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase(); + // optimize orders + const optimizedMarketOrders = marketOrdersOptimizationUtils.optimizeMarketOrders(signedOrders); + const optimizedFeeOrders = marketOrdersOptimizationUtils.optimizeFeeOrders(signedFeeOrders); + // send transaction const forwarderContractInstance = await this._getForwarderContractAsync(); const txHash = await forwarderContractInstance.marketSellOrdersWithEth.sendTransactionAsync( - signedOrders, - _.map(signedOrders, order => order.signature), - signedFeeOrders, - _.map(signedFeeOrders, order => order.signature), + optimizedMarketOrders, + _.map(optimizedMarketOrders, order => order.signature), + optimizedFeeOrders, + _.map(optimizedFeeOrders, order => order.signature), feePercentage, feeRecipientAddress, { @@ -138,15 +144,20 @@ export class ForwarderWrapper extends ContractWrapper { this.getZRXTokenAddress(), this.getEtherTokenAddress(), ); + // lowercase input addresses const normalizedTakerAddress = takerAddress.toLowerCase(); const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase(); + // optimize orders + const optimizedMarketOrders = marketOrdersOptimizationUtils.optimizeMarketOrders(signedOrders); + const optimizedFeeOrders = marketOrdersOptimizationUtils.optimizeFeeOrders(signedFeeOrders); + // send transaction const forwarderContractInstance = await this._getForwarderContractAsync(); const txHash = await forwarderContractInstance.marketBuyOrdersWithEth.sendTransactionAsync( - signedOrders, + optimizedMarketOrders, makerAssetFillAmount, - _.map(signedOrders, order => order.signature), - signedFeeOrders, - _.map(signedFeeOrders, order => order.signature), + _.map(optimizedMarketOrders, order => order.signature), + optimizedFeeOrders, + _.map(optimizedFeeOrders, order => order.signature), feePercentage, feeRecipientAddress, { diff --git a/packages/contract-wrappers/src/utils/constants.ts b/packages/contract-wrappers/src/utils/constants.ts index d436efefc..2df11538c 100644 --- a/packages/contract-wrappers/src/utils/constants.ts +++ b/packages/contract-wrappers/src/utils/constants.ts @@ -2,6 +2,7 @@ import { BigNumber } from '@0xproject/utils'; export const constants = { NULL_ADDRESS: '0x0000000000000000000000000000000000000000', + NULL_BYTES: '0x', TESTRPC_NETWORK_ID: 50, INVALID_JUMP_PATTERN: 'invalid JUMP at', REVERT: 'revert', diff --git a/packages/contract-wrappers/src/utils/market_orders_optimization_utils.ts b/packages/contract-wrappers/src/utils/market_orders_optimization_utils.ts new file mode 100644 index 000000000..0041f1a64 --- /dev/null +++ b/packages/contract-wrappers/src/utils/market_orders_optimization_utils.ts @@ -0,0 +1,43 @@ +import { SignedOrder } from '@0xproject/types'; +import * as _ from 'lodash'; + +import { constants } from './constants'; + +export const marketOrdersOptimizationUtils = { + /** + * Takes an array of orders and outputs an array of equivalent orders where all takerAssetData are '0x' and + * all makerAssetData are '0x' except for that of the first order, which retains its original value + * @param orders An array of SignedOrder objects + * @returns optimized orders + */ + optimizeMarketOrders(orders: SignedOrder[]): SignedOrder[] { + const optimizedOrders = _.map(orders, (order, index) => { + const makerAssetData = index === 0 ? order.makerAssetData : constants.NULL_BYTES; + const takerAssetData = constants.NULL_BYTES; + return { + ...order, + makerAssetData, + takerAssetData, + }; + }); + return optimizedOrders; + }, + /** + * Takes an array of orders and outputs an array of equivalent orders where all takerAssetData are '0x' and + * all makerAssetData are '0x' + * @param orders An array of SignedOrder objects + * @returns optimized orders + */ + optimizeFeeOrders(orders: SignedOrder[]): SignedOrder[] { + const optimizedOrders = _.map(orders, order => { + const makerAssetData = constants.NULL_BYTES; + const takerAssetData = constants.NULL_BYTES; + return { + ...order, + makerAssetData, + takerAssetData, + }; + }); + return optimizedOrders; + }, +}; -- cgit From 7c864b81e0d958560e098ebd8bd241385e4aadff Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 1 Aug 2018 15:08:17 -0700 Subject: Add createOrder with no signing to orderFactory --- packages/order-utils/CHANGELOG.json | 8 ++++++ packages/order-utils/src/constants.ts | 1 + packages/order-utils/src/order_factory.ts | 42 ++++++++++++++++++++++++++----- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index a399f5ea1..7d9ca0a53 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -1,4 +1,12 @@ [ + { + "version": "1.1.0-rc.2", + "changes": [ + { + "note": "Added a synchronous `createOrder` method in `orderFactory`" + } + ] + }, { "version": "1.0.1-rc.2", "changes": [ diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts index bb7482184..92eb89d70 100644 --- a/packages/order-utils/src/constants.ts +++ b/packages/order-utils/src/constants.ts @@ -10,4 +10,5 @@ export const constants = { ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH: 53, SELECTOR_LENGTH: 4, BASE_16: 16, + INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite }; diff --git a/packages/order-utils/src/order_factory.ts b/packages/order-utils/src/order_factory.ts index 803cb82b1..4be7a1913 100644 --- a/packages/order-utils/src/order_factory.ts +++ b/packages/order-utils/src/order_factory.ts @@ -1,17 +1,17 @@ -import { ECSignature, SignedOrder } from '@0xproject/types'; +import { ECSignature, Order, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Provider } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; +import { constants } from './constants'; import { orderHashUtils } from './order_hash'; import { generatePseudoRandomSalt } from './salt'; import { ecSignOrderHashAsync } from './signature_utils'; import { MessagePrefixType } from './types'; export const orderFactory = { - async createSignedOrderAsync( - provider: Provider, + createOrder( makerAddress: string, takerAddress: string, senderAddress: string, @@ -24,10 +24,9 @@ export const orderFactory = { exchangeAddress: string, feeRecipientAddress: string, expirationTimeSecondsIfExists?: BigNumber, - ): Promise { - const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite + ): Order { const expirationTimeSeconds = _.isUndefined(expirationTimeSecondsIfExists) - ? defaultExpirationUnixTimestampSec + ? constants.INFINITE_TIMESTAMP_SEC : expirationTimeSecondsIfExists; const order = { makerAddress, @@ -44,6 +43,37 @@ export const orderFactory = { feeRecipientAddress, expirationTimeSeconds, }; + return order; + }, + async createSignedOrderAsync( + provider: Provider, + makerAddress: string, + takerAddress: string, + senderAddress: string, + makerFee: BigNumber, + takerFee: BigNumber, + makerAssetAmount: BigNumber, + makerAssetData: string, + takerAssetAmount: BigNumber, + takerAssetData: string, + exchangeAddress: string, + feeRecipientAddress: string, + expirationTimeSecondsIfExists?: BigNumber, + ): Promise { + const order = orderFactory.createOrder( + makerAddress, + takerAddress, + senderAddress, + makerFee, + takerFee, + makerAssetAmount, + makerAssetData, + takerAssetAmount, + takerAssetData, + exchangeAddress, + feeRecipientAddress, + expirationTimeSecondsIfExists, + ); const orderHash = orderHashUtils.getOrderHashHex(order); const messagePrefixOpts = { prefixType: MessagePrefixType.EthSign, -- cgit From 30c6fe08ec5f67fa3679c0043a24ffc420974964 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 1 Aug 2018 15:08:34 -0700 Subject: Add tests --- .../src/utils/market_orders_optimization_utils.ts | 37 ++++++------ .../test/market_orders_optimization_utils_test.ts | 66 ++++++++++++++++++++++ 2 files changed, 85 insertions(+), 18 deletions(-) create mode 100644 packages/contract-wrappers/test/market_orders_optimization_utils_test.ts diff --git a/packages/contract-wrappers/src/utils/market_orders_optimization_utils.ts b/packages/contract-wrappers/src/utils/market_orders_optimization_utils.ts index 0041f1a64..e35b2eadc 100644 --- a/packages/contract-wrappers/src/utils/market_orders_optimization_utils.ts +++ b/packages/contract-wrappers/src/utils/market_orders_optimization_utils.ts @@ -11,15 +11,12 @@ export const marketOrdersOptimizationUtils = { * @returns optimized orders */ optimizeMarketOrders(orders: SignedOrder[]): SignedOrder[] { - const optimizedOrders = _.map(orders, (order, index) => { - const makerAssetData = index === 0 ? order.makerAssetData : constants.NULL_BYTES; - const takerAssetData = constants.NULL_BYTES; - return { - ...order, - makerAssetData, - takerAssetData, - }; - }); + const optimizedOrders = _.map(orders, (order, index) => + transformOrder(order, { + makerAssetData: index === 0 ? order.makerAssetData : constants.NULL_BYTES, + takerAssetData: constants.NULL_BYTES, + }), + ); return optimizedOrders; }, /** @@ -29,15 +26,19 @@ export const marketOrdersOptimizationUtils = { * @returns optimized orders */ optimizeFeeOrders(orders: SignedOrder[]): SignedOrder[] { - const optimizedOrders = _.map(orders, order => { - const makerAssetData = constants.NULL_BYTES; - const takerAssetData = constants.NULL_BYTES; - return { - ...order, - makerAssetData, - takerAssetData, - }; - }); + const optimizedOrders = _.map(orders, (order, index) => + transformOrder(order, { + makerAssetData: constants.NULL_BYTES, + takerAssetData: constants.NULL_BYTES, + }), + ); return optimizedOrders; }, }; + +const transformOrder = (order: SignedOrder, partialOrder: Partial) => { + return { + ...order, + ...partialOrder, + }; +}; diff --git a/packages/contract-wrappers/test/market_orders_optimization_utils_test.ts b/packages/contract-wrappers/test/market_orders_optimization_utils_test.ts new file mode 100644 index 000000000..742294df2 --- /dev/null +++ b/packages/contract-wrappers/test/market_orders_optimization_utils_test.ts @@ -0,0 +1,66 @@ +import { orderFactory } from '@0xproject/order-utils'; +import * as chai from 'chai'; +import * as _ from 'lodash'; +import 'mocha'; + +import { constants } from '../src/utils/constants'; +import { marketOrdersOptimizationUtils } from '../src/utils/market_orders_optimization_utils'; + +import { chaiSetup } from './utils/chai_setup'; +import { assert } from '../src/utils/assert'; +import { NULL_BYTES } from '@0xproject/utils'; + +chaiSetup.configure(); +const expect = chai.expect; + +// utility for generating a set of order objects with mostly NULL values +// except for a specified makerAssetData and takerAssetData +const FAKE_ORDERS_COUNT = 5; +const generateFakeOrders = (makerAssetData: string, takerAssetData: string) => + _.map(_.range(FAKE_ORDERS_COUNT), index => { + const order = orderFactory.createOrder( + constants.NULL_ADDRESS, + constants.NULL_ADDRESS, + constants.NULL_ADDRESS, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + makerAssetData, + constants.ZERO_AMOUNT, + takerAssetData, + constants.NULL_ADDRESS, + constants.NULL_ADDRESS, + ); + return { + ...order, + signature: 'dummy signature', + }; + }); + +describe('marketOrdersOptimizationUtils', () => { + const fakeMakerAssetData = 'fakeMakerAssetData'; + const fakeTakerAssetData = 'fakeTakerAssetData'; + const orders = generateFakeOrders(fakeMakerAssetData, fakeTakerAssetData); + describe('#optimizeMarketOrders', () => { + it('should make makerAssetData `0x` unless first order', () => { + const optimizedOrders = marketOrdersOptimizationUtils.optimizeMarketOrders(orders); + expect(optimizedOrders[0].makerAssetData).to.equal(fakeMakerAssetData); + const ordersWithoutHead = _.slice(optimizedOrders, 1); + _.forEach(ordersWithoutHead, order => expect(order.makerAssetData).to.equal(constants.NULL_BYTES)); + }); + it('should make all takerAssetData `0x`', () => { + const optimizedOrders = marketOrdersOptimizationUtils.optimizeMarketOrders(orders); + _.forEach(optimizedOrders, order => expect(order.takerAssetData).to.equal(constants.NULL_BYTES)); + }); + }); + describe('#optimizeFeeOrders', () => { + it('should make all makerAssetData `0x`', () => { + const optimizedOrders = marketOrdersOptimizationUtils.optimizeFeeOrders(orders); + _.forEach(optimizedOrders, order => expect(order.makerAssetData).to.equal(constants.NULL_BYTES)); + }); + it('should make all takerAssetData `0x`', () => { + const optimizedOrders = marketOrdersOptimizationUtils.optimizeFeeOrders(orders); + _.forEach(optimizedOrders, order => expect(order.takerAssetData).to.equal(constants.NULL_BYTES)); + }); + }); +}); -- cgit From 6e74896620137b462b78a9493278a30efd016c44 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 1 Aug 2018 20:49:10 -0700 Subject: CHANGELOG --- packages/contract-wrappers/CHANGELOG.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index 8fa31052c..cf10751d4 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Add ForwarderWrapper", "pr": 934 + }, + { + "note": "Optimize orders in ForwarderWrapper", + "pr": 935 } ] }, -- cgit From c3e6be7956adf4fabcc8dfffe081515562a1dde0 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 2 Aug 2018 15:53:02 -0700 Subject: Add missing PR numbers --- packages/contract-wrappers/CHANGELOG.json | 2 +- packages/order-utils/CHANGELOG.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index cf10751d4..d95c99a25 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -8,7 +8,7 @@ }, { "note": "Optimize orders in ForwarderWrapper", - "pr": 935 + "pr": 936 } ] }, diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index 7d9ca0a53..ddfa690fe 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -3,7 +3,8 @@ "version": "1.1.0-rc.2", "changes": [ { - "note": "Added a synchronous `createOrder` method in `orderFactory`" + "note": "Added a synchronous `createOrder` method in `orderFactory`", + "pr": 935 } ] }, -- cgit From 82092ab50a73e15e167f387380cd75ac52581e88 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 2 Aug 2018 16:02:58 -0700 Subject: Rename to calldata utils --- .../src/contract_wrappers/forwarder_wrapper.ts | 10 ++-- .../src/utils/calldata_optimization_utils.ts | 44 +++++++++++++++ .../src/utils/market_orders_optimization_utils.ts | 44 --------------- .../test/calldata_optimization_utils_test.ts | 66 ++++++++++++++++++++++ .../test/market_orders_optimization_utils_test.ts | 66 ---------------------- 5 files changed, 115 insertions(+), 115 deletions(-) create mode 100644 packages/contract-wrappers/src/utils/calldata_optimization_utils.ts delete mode 100644 packages/contract-wrappers/src/utils/market_orders_optimization_utils.ts create mode 100644 packages/contract-wrappers/test/calldata_optimization_utils_test.ts delete mode 100644 packages/contract-wrappers/test/market_orders_optimization_utils_test.ts diff --git a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts index 7d51889cf..13ef0fe01 100644 --- a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts @@ -10,8 +10,8 @@ import { orderTxOptsSchema } from '../schemas/order_tx_opts_schema'; import { txOptsSchema } from '../schemas/tx_opts_schema'; import { TransactionOpts } from '../types'; import { assert } from '../utils/assert'; +import { calldataOptimizationUtils } from '../utils/calldata_optimization_utils'; import { constants } from '../utils/constants'; -import { marketOrdersOptimizationUtils } from '../utils/market_orders_optimization_utils'; import { ContractWrapper } from './contract_wrapper'; import { ForwarderContract } from './generated/forwarder'; @@ -80,8 +80,8 @@ export class ForwarderWrapper extends ContractWrapper { const normalizedTakerAddress = takerAddress.toLowerCase(); const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase(); // optimize orders - const optimizedMarketOrders = marketOrdersOptimizationUtils.optimizeMarketOrders(signedOrders); - const optimizedFeeOrders = marketOrdersOptimizationUtils.optimizeFeeOrders(signedFeeOrders); + const optimizedMarketOrders = calldataOptimizationUtils.optimizeForwarderOrders(signedOrders); + const optimizedFeeOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(signedFeeOrders); // send transaction const forwarderContractInstance = await this._getForwarderContractAsync(); const txHash = await forwarderContractInstance.marketSellOrdersWithEth.sendTransactionAsync( @@ -148,8 +148,8 @@ export class ForwarderWrapper extends ContractWrapper { const normalizedTakerAddress = takerAddress.toLowerCase(); const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase(); // optimize orders - const optimizedMarketOrders = marketOrdersOptimizationUtils.optimizeMarketOrders(signedOrders); - const optimizedFeeOrders = marketOrdersOptimizationUtils.optimizeFeeOrders(signedFeeOrders); + const optimizedMarketOrders = calldataOptimizationUtils.optimizeForwarderOrders(signedOrders); + const optimizedFeeOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(signedFeeOrders); // send transaction const forwarderContractInstance = await this._getForwarderContractAsync(); const txHash = await forwarderContractInstance.marketBuyOrdersWithEth.sendTransactionAsync( diff --git a/packages/contract-wrappers/src/utils/calldata_optimization_utils.ts b/packages/contract-wrappers/src/utils/calldata_optimization_utils.ts new file mode 100644 index 000000000..3172cf531 --- /dev/null +++ b/packages/contract-wrappers/src/utils/calldata_optimization_utils.ts @@ -0,0 +1,44 @@ +import { SignedOrder } from '@0xproject/types'; +import * as _ from 'lodash'; + +import { constants } from './constants'; + +export const calldataOptimizationUtils = { + /** + * Takes an array of orders and outputs an array of equivalent orders where all takerAssetData are '0x' and + * all makerAssetData are '0x' except for that of the first order, which retains its original value + * @param orders An array of SignedOrder objects + * @returns optimized orders + */ + optimizeForwarderOrders(orders: SignedOrder[]): SignedOrder[] { + const optimizedOrders = _.map(orders, (order, index) => + transformOrder(order, { + makerAssetData: index === 0 ? order.makerAssetData : constants.NULL_BYTES, + takerAssetData: constants.NULL_BYTES, + }), + ); + return optimizedOrders; + }, + /** + * Takes an array of orders and outputs an array of equivalent orders where all takerAssetData are '0x' and + * all makerAssetData are '0x' + * @param orders An array of SignedOrder objects + * @returns optimized orders + */ + optimizeForwarderFeeOrders(orders: SignedOrder[]): SignedOrder[] { + const optimizedOrders = _.map(orders, (order, index) => + transformOrder(order, { + makerAssetData: constants.NULL_BYTES, + takerAssetData: constants.NULL_BYTES, + }), + ); + return optimizedOrders; + }, +}; + +const transformOrder = (order: SignedOrder, partialOrder: Partial) => { + return { + ...order, + ...partialOrder, + }; +}; diff --git a/packages/contract-wrappers/src/utils/market_orders_optimization_utils.ts b/packages/contract-wrappers/src/utils/market_orders_optimization_utils.ts deleted file mode 100644 index e35b2eadc..000000000 --- a/packages/contract-wrappers/src/utils/market_orders_optimization_utils.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { SignedOrder } from '@0xproject/types'; -import * as _ from 'lodash'; - -import { constants } from './constants'; - -export const marketOrdersOptimizationUtils = { - /** - * Takes an array of orders and outputs an array of equivalent orders where all takerAssetData are '0x' and - * all makerAssetData are '0x' except for that of the first order, which retains its original value - * @param orders An array of SignedOrder objects - * @returns optimized orders - */ - optimizeMarketOrders(orders: SignedOrder[]): SignedOrder[] { - const optimizedOrders = _.map(orders, (order, index) => - transformOrder(order, { - makerAssetData: index === 0 ? order.makerAssetData : constants.NULL_BYTES, - takerAssetData: constants.NULL_BYTES, - }), - ); - return optimizedOrders; - }, - /** - * Takes an array of orders and outputs an array of equivalent orders where all takerAssetData are '0x' and - * all makerAssetData are '0x' - * @param orders An array of SignedOrder objects - * @returns optimized orders - */ - optimizeFeeOrders(orders: SignedOrder[]): SignedOrder[] { - const optimizedOrders = _.map(orders, (order, index) => - transformOrder(order, { - makerAssetData: constants.NULL_BYTES, - takerAssetData: constants.NULL_BYTES, - }), - ); - return optimizedOrders; - }, -}; - -const transformOrder = (order: SignedOrder, partialOrder: Partial) => { - return { - ...order, - ...partialOrder, - }; -}; diff --git a/packages/contract-wrappers/test/calldata_optimization_utils_test.ts b/packages/contract-wrappers/test/calldata_optimization_utils_test.ts new file mode 100644 index 000000000..107d913ba --- /dev/null +++ b/packages/contract-wrappers/test/calldata_optimization_utils_test.ts @@ -0,0 +1,66 @@ +import { orderFactory } from '@0xproject/order-utils'; +import * as chai from 'chai'; +import * as _ from 'lodash'; +import 'mocha'; + +import { constants } from '../src/utils/constants'; +import { calldataOptimizationUtils } from '../src/utils/calldata_optimization_utils'; + +import { chaiSetup } from './utils/chai_setup'; +import { assert } from '../src/utils/assert'; +import { NULL_BYTES } from '@0xproject/utils'; + +chaiSetup.configure(); +const expect = chai.expect; + +// utility for generating a set of order objects with mostly NULL values +// except for a specified makerAssetData and takerAssetData +const FAKE_ORDERS_COUNT = 5; +const generateFakeOrders = (makerAssetData: string, takerAssetData: string) => + _.map(_.range(FAKE_ORDERS_COUNT), index => { + const order = orderFactory.createOrder( + constants.NULL_ADDRESS, + constants.NULL_ADDRESS, + constants.NULL_ADDRESS, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + makerAssetData, + constants.ZERO_AMOUNT, + takerAssetData, + constants.NULL_ADDRESS, + constants.NULL_ADDRESS, + ); + return { + ...order, + signature: 'dummy signature', + }; + }); + +describe('calldataOptimizationUtils', () => { + const fakeMakerAssetData = 'fakeMakerAssetData'; + const fakeTakerAssetData = 'fakeTakerAssetData'; + const orders = generateFakeOrders(fakeMakerAssetData, fakeTakerAssetData); + describe('#optimizeForwarderOrders', () => { + it('should make makerAssetData `0x` unless first order', () => { + const optimizedOrders = calldataOptimizationUtils.optimizeForwarderOrders(orders); + expect(optimizedOrders[0].makerAssetData).to.equal(fakeMakerAssetData); + const ordersWithoutHead = _.slice(optimizedOrders, 1); + _.forEach(ordersWithoutHead, order => expect(order.makerAssetData).to.equal(constants.NULL_BYTES)); + }); + it('should make all takerAssetData `0x`', () => { + const optimizedOrders = calldataOptimizationUtils.optimizeForwarderOrders(orders); + _.forEach(optimizedOrders, order => expect(order.takerAssetData).to.equal(constants.NULL_BYTES)); + }); + }); + describe('#optimizeForwarderFeeOrders', () => { + it('should make all makerAssetData `0x`', () => { + const optimizedOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(orders); + _.forEach(optimizedOrders, order => expect(order.makerAssetData).to.equal(constants.NULL_BYTES)); + }); + it('should make all takerAssetData `0x`', () => { + const optimizedOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(orders); + _.forEach(optimizedOrders, order => expect(order.takerAssetData).to.equal(constants.NULL_BYTES)); + }); + }); +}); diff --git a/packages/contract-wrappers/test/market_orders_optimization_utils_test.ts b/packages/contract-wrappers/test/market_orders_optimization_utils_test.ts deleted file mode 100644 index 742294df2..000000000 --- a/packages/contract-wrappers/test/market_orders_optimization_utils_test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { orderFactory } from '@0xproject/order-utils'; -import * as chai from 'chai'; -import * as _ from 'lodash'; -import 'mocha'; - -import { constants } from '../src/utils/constants'; -import { marketOrdersOptimizationUtils } from '../src/utils/market_orders_optimization_utils'; - -import { chaiSetup } from './utils/chai_setup'; -import { assert } from '../src/utils/assert'; -import { NULL_BYTES } from '@0xproject/utils'; - -chaiSetup.configure(); -const expect = chai.expect; - -// utility for generating a set of order objects with mostly NULL values -// except for a specified makerAssetData and takerAssetData -const FAKE_ORDERS_COUNT = 5; -const generateFakeOrders = (makerAssetData: string, takerAssetData: string) => - _.map(_.range(FAKE_ORDERS_COUNT), index => { - const order = orderFactory.createOrder( - constants.NULL_ADDRESS, - constants.NULL_ADDRESS, - constants.NULL_ADDRESS, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - makerAssetData, - constants.ZERO_AMOUNT, - takerAssetData, - constants.NULL_ADDRESS, - constants.NULL_ADDRESS, - ); - return { - ...order, - signature: 'dummy signature', - }; - }); - -describe('marketOrdersOptimizationUtils', () => { - const fakeMakerAssetData = 'fakeMakerAssetData'; - const fakeTakerAssetData = 'fakeTakerAssetData'; - const orders = generateFakeOrders(fakeMakerAssetData, fakeTakerAssetData); - describe('#optimizeMarketOrders', () => { - it('should make makerAssetData `0x` unless first order', () => { - const optimizedOrders = marketOrdersOptimizationUtils.optimizeMarketOrders(orders); - expect(optimizedOrders[0].makerAssetData).to.equal(fakeMakerAssetData); - const ordersWithoutHead = _.slice(optimizedOrders, 1); - _.forEach(ordersWithoutHead, order => expect(order.makerAssetData).to.equal(constants.NULL_BYTES)); - }); - it('should make all takerAssetData `0x`', () => { - const optimizedOrders = marketOrdersOptimizationUtils.optimizeMarketOrders(orders); - _.forEach(optimizedOrders, order => expect(order.takerAssetData).to.equal(constants.NULL_BYTES)); - }); - }); - describe('#optimizeFeeOrders', () => { - it('should make all makerAssetData `0x`', () => { - const optimizedOrders = marketOrdersOptimizationUtils.optimizeFeeOrders(orders); - _.forEach(optimizedOrders, order => expect(order.makerAssetData).to.equal(constants.NULL_BYTES)); - }); - it('should make all takerAssetData `0x`', () => { - const optimizedOrders = marketOrdersOptimizationUtils.optimizeFeeOrders(orders); - _.forEach(optimizedOrders, order => expect(order.takerAssetData).to.equal(constants.NULL_BYTES)); - }); - }); -}); -- cgit From 4f381ca1d9b6f8ecc232d0481d86f8ba695f7601 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Fri, 3 Aug 2018 15:47:19 -0400 Subject: Update orderFactory interface --- .../test/calldata_optimization_utils_test.ts | 10 ++---- packages/fill-scenarios/src/fill_scenarios.ts | 8 ++--- packages/order-utils/src/constants.ts | 1 + packages/order-utils/src/order_factory.ts | 42 +++++++++++----------- 4 files changed, 28 insertions(+), 33 deletions(-) diff --git a/packages/contract-wrappers/test/calldata_optimization_utils_test.ts b/packages/contract-wrappers/test/calldata_optimization_utils_test.ts index 107d913ba..a4cea772f 100644 --- a/packages/contract-wrappers/test/calldata_optimization_utils_test.ts +++ b/packages/contract-wrappers/test/calldata_optimization_utils_test.ts @@ -3,12 +3,11 @@ import * as chai from 'chai'; import * as _ from 'lodash'; import 'mocha'; -import { constants } from '../src/utils/constants'; +import { assert } from '../src/utils/assert'; import { calldataOptimizationUtils } from '../src/utils/calldata_optimization_utils'; +import { constants } from '../src/utils/constants'; import { chaiSetup } from './utils/chai_setup'; -import { assert } from '../src/utils/assert'; -import { NULL_BYTES } from '@0xproject/utils'; chaiSetup.configure(); const expect = chai.expect; @@ -20,16 +19,11 @@ const generateFakeOrders = (makerAssetData: string, takerAssetData: string) => _.map(_.range(FAKE_ORDERS_COUNT), index => { const order = orderFactory.createOrder( constants.NULL_ADDRESS, - constants.NULL_ADDRESS, - constants.NULL_ADDRESS, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, makerAssetData, constants.ZERO_AMOUNT, takerAssetData, constants.NULL_ADDRESS, - constants.NULL_ADDRESS, ); return { ...order, diff --git a/packages/fill-scenarios/src/fill_scenarios.ts b/packages/fill-scenarios/src/fill_scenarios.ts index 8f2766e24..f35094560 100644 --- a/packages/fill-scenarios/src/fill_scenarios.ts +++ b/packages/fill-scenarios/src/fill_scenarios.ts @@ -194,15 +194,15 @@ export class FillScenarios { const signedOrder = await orderFactory.createSignedOrderAsync( this._web3Wrapper.getProvider(), makerAddress, - takerAddress, - senderAddress, - makerFee, - takerFee, makerFillableAmount, makerAssetData, takerFillableAmount, takerAssetData, this._exchangeAddress, + takerAddress, + senderAddress, + makerFee, + takerFee, feeRecepientAddress, expirationTimeSeconds, ); diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts index 92eb89d70..ea3f8b932 100644 --- a/packages/order-utils/src/constants.ts +++ b/packages/order-utils/src/constants.ts @@ -11,4 +11,5 @@ export const constants = { SELECTOR_LENGTH: 4, BASE_16: 16, INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite + ZERO_AMOUNT: new BigNumber(0), }; diff --git a/packages/order-utils/src/order_factory.ts b/packages/order-utils/src/order_factory.ts index 4be7a1913..444e5a0b2 100644 --- a/packages/order-utils/src/order_factory.ts +++ b/packages/order-utils/src/order_factory.ts @@ -13,21 +13,19 @@ import { MessagePrefixType } from './types'; export const orderFactory = { createOrder( makerAddress: string, - takerAddress: string, - senderAddress: string, - makerFee: BigNumber, - takerFee: BigNumber, makerAssetAmount: BigNumber, makerAssetData: string, takerAssetAmount: BigNumber, takerAssetData: string, exchangeAddress: string, - feeRecipientAddress: string, - expirationTimeSecondsIfExists?: BigNumber, + takerAddress: string = constants.NULL_ADDRESS, + senderAddress: string = constants.NULL_ADDRESS, + makerFee: BigNumber = constants.ZERO_AMOUNT, + takerFee: BigNumber = constants.ZERO_AMOUNT, + feeRecipientAddress: string = constants.NULL_ADDRESS, + salt: BigNumber = generatePseudoRandomSalt(), + expirationTimeSeconds: BigNumber = constants.INFINITE_TIMESTAMP_SEC, ): Order { - const expirationTimeSeconds = _.isUndefined(expirationTimeSecondsIfExists) - ? constants.INFINITE_TIMESTAMP_SEC - : expirationTimeSecondsIfExists; const order = { makerAddress, takerAddress, @@ -38,7 +36,7 @@ export const orderFactory = { takerAssetAmount, makerAssetData, takerAssetData, - salt: generatePseudoRandomSalt(), + salt, exchangeAddress, feeRecipientAddress, expirationTimeSeconds, @@ -48,31 +46,33 @@ export const orderFactory = { async createSignedOrderAsync( provider: Provider, makerAddress: string, - takerAddress: string, - senderAddress: string, - makerFee: BigNumber, - takerFee: BigNumber, makerAssetAmount: BigNumber, makerAssetData: string, takerAssetAmount: BigNumber, takerAssetData: string, exchangeAddress: string, - feeRecipientAddress: string, - expirationTimeSecondsIfExists?: BigNumber, + takerAddress?: string, + senderAddress?: string, + makerFee?: BigNumber, + takerFee?: BigNumber, + feeRecipientAddress?: string, + salt?: BigNumber, + expirationTimeSeconds?: BigNumber, ): Promise { const order = orderFactory.createOrder( makerAddress, - takerAddress, - senderAddress, - makerFee, - takerFee, makerAssetAmount, makerAssetData, takerAssetAmount, takerAssetData, exchangeAddress, + takerAddress, + senderAddress, + makerFee, + takerFee, feeRecipientAddress, - expirationTimeSecondsIfExists, + salt, + expirationTimeSeconds, ); const orderHash = orderHashUtils.getOrderHashHex(order); const messagePrefixOpts = { -- cgit From d00ee5df0d861e15e258e6747bec4af3284205b2 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Fri, 3 Aug 2018 15:58:17 -0400 Subject: Fix CHANGELOGs --- packages/contract-wrappers/CHANGELOG.json | 2 +- packages/fill-scenarios/CHANGELOG.json | 9 +++++++++ packages/order-utils/CHANGELOG.json | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index d95c99a25..02e1acf91 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,6 +1,6 @@ [ { - "version": "1.1.0-rc.2", + "version": "1.0.1-rc.3", "changes": [ { "note": "Add ForwarderWrapper", diff --git a/packages/fill-scenarios/CHANGELOG.json b/packages/fill-scenarios/CHANGELOG.json index 954803462..af1f79cf9 100644 --- a/packages/fill-scenarios/CHANGELOG.json +++ b/packages/fill-scenarios/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "1.0.1-rc.3", + "changes": [ + { + "note": "Updated to use latest orderFactory interface", + "pr": 936 + } + ] + }, { "version": "1.0.1-rc.2", "changes": [ diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index ddfa690fe..cac29bf6b 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -1,10 +1,10 @@ [ { - "version": "1.1.0-rc.2", + "version": "1.0.1-rc.3", "changes": [ { "note": "Added a synchronous `createOrder` method in `orderFactory`", - "pr": 935 + "pr": 936 } ] }, -- cgit From 3865a081a0f8ea48be736adebb8dcdb4b7d80b04 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Fri, 3 Aug 2018 16:46:55 -0400 Subject: Prettier --- packages/sol-resolver/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/sol-resolver/CHANGELOG.md b/packages/sol-resolver/CHANGELOG.md index 8ff6ce6ed..5d2ee154a 100644 --- a/packages/sol-resolver/CHANGELOG.md +++ b/packages/sol-resolver/CHANGELOG.md @@ -5,7 +5,6 @@ Edit the package's CHANGELOG.json file only. CHANGELOG - ## v1.0.4 - _July 26, 2018_ * Dependencies updated -- cgit From 47673ba4bb2932051cb810bd0012c208665eb277 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Sun, 5 Aug 2018 16:51:53 -0400 Subject: Update createFactory to accept one createOrderOpts param to encompass all optional params --- packages/fill-scenarios/CHANGELOG.json | 3 +- packages/fill-scenarios/src/fill_scenarios.ts | 24 ++++----- packages/order-utils/CHANGELOG.json | 3 +- packages/order-utils/src/order_factory.ts | 70 ++++++++++++++++----------- 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/packages/fill-scenarios/CHANGELOG.json b/packages/fill-scenarios/CHANGELOG.json index af1f79cf9..04e076203 100644 --- a/packages/fill-scenarios/CHANGELOG.json +++ b/packages/fill-scenarios/CHANGELOG.json @@ -3,7 +3,8 @@ "version": "1.0.1-rc.3", "changes": [ { - "note": "Updated to use latest orderFactory interface", + "note": + "Updated to use latest orderFactory interface, fixed `feeRecipient` spelling error in public interface", "pr": 936 } ] diff --git a/packages/fill-scenarios/src/fill_scenarios.ts b/packages/fill-scenarios/src/fill_scenarios.ts index f35094560..1a1adb326 100644 --- a/packages/fill-scenarios/src/fill_scenarios.ts +++ b/packages/fill-scenarios/src/fill_scenarios.ts @@ -61,7 +61,7 @@ export class FillScenarios { makerAddress: string, takerAddress: string, fillableAmount: BigNumber, - feeRecepientAddress: string, + feeRecipientAddress: string, expirationTimeSeconds?: BigNumber, ): Promise { return this._createAsymmetricFillableSignedOrderWithFeesAsync( @@ -73,7 +73,7 @@ export class FillScenarios { takerAddress, fillableAmount, fillableAmount, - feeRecepientAddress, + feeRecipientAddress, expirationTimeSeconds, ); } @@ -88,7 +88,7 @@ export class FillScenarios { ): Promise { const makerFee = new BigNumber(0); const takerFee = new BigNumber(0); - const feeRecepientAddress = constants.NULL_ADDRESS; + const feeRecipientAddress = constants.NULL_ADDRESS; return this._createAsymmetricFillableSignedOrderWithFeesAsync( makerAssetData, takerAssetData, @@ -98,7 +98,7 @@ export class FillScenarios { takerAddress, makerFillableAmount, takerFillableAmount, - feeRecepientAddress, + feeRecipientAddress, expirationTimeSeconds, ); } @@ -148,7 +148,7 @@ export class FillScenarios { takerAddress: string, makerFillableAmount: BigNumber, takerFillableAmount: BigNumber, - feeRecepientAddress: string, + feeRecipientAddress: string, expirationTimeSeconds?: BigNumber, ): Promise { const decodedMakerAssetData = assetDataUtils.decodeAssetDataOrThrow(makerAssetData); @@ -199,12 +199,14 @@ export class FillScenarios { takerFillableAmount, takerAssetData, this._exchangeAddress, - takerAddress, - senderAddress, - makerFee, - takerFee, - feeRecepientAddress, - expirationTimeSeconds, + { + takerAddress, + senderAddress, + makerFee, + takerFee, + feeRecipientAddress, + expirationTimeSeconds, + }, ); return signedOrder; } diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index cac29bf6b..70a75854a 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -3,7 +3,8 @@ "version": "1.0.1-rc.3", "changes": [ { - "note": "Added a synchronous `createOrder` method in `orderFactory`", + "note": + "Added a synchronous `createOrder` method in `orderFactory`, updated public interfaces to support some optional parameters", "pr": 936 } ] diff --git a/packages/order-utils/src/order_factory.ts b/packages/order-utils/src/order_factory.ts index 444e5a0b2..5901d38c3 100644 --- a/packages/order-utils/src/order_factory.ts +++ b/packages/order-utils/src/order_factory.ts @@ -10,6 +10,16 @@ import { generatePseudoRandomSalt } from './salt'; import { ecSignOrderHashAsync } from './signature_utils'; import { MessagePrefixType } from './types'; +export interface CreateOrderOpts { + takerAddress?: string; + senderAddress?: string; + makerFee?: BigNumber; + takerFee?: BigNumber; + feeRecipientAddress?: string; + salt?: BigNumber; + expirationTimeSeconds?: BigNumber; +} + export const orderFactory = { createOrder( makerAddress: string, @@ -18,28 +28,24 @@ export const orderFactory = { takerAssetAmount: BigNumber, takerAssetData: string, exchangeAddress: string, - takerAddress: string = constants.NULL_ADDRESS, - senderAddress: string = constants.NULL_ADDRESS, - makerFee: BigNumber = constants.ZERO_AMOUNT, - takerFee: BigNumber = constants.ZERO_AMOUNT, - feeRecipientAddress: string = constants.NULL_ADDRESS, - salt: BigNumber = generatePseudoRandomSalt(), - expirationTimeSeconds: BigNumber = constants.INFINITE_TIMESTAMP_SEC, + createOrderOpts: CreateOrderOpts = generateDefaultCreateOrderOpts(), ): Order { + const defaultCreateOrderOpts = generateDefaultCreateOrderOpts(); const order = { makerAddress, - takerAddress, - senderAddress, - makerFee, - takerFee, makerAssetAmount, takerAssetAmount, makerAssetData, takerAssetData, - salt, exchangeAddress, - feeRecipientAddress, - expirationTimeSeconds, + takerAddress: createOrderOpts.takerAddress || defaultCreateOrderOpts.takerAddress, + senderAddress: createOrderOpts.senderAddress || defaultCreateOrderOpts.senderAddress, + makerFee: createOrderOpts.makerFee || defaultCreateOrderOpts.makerFee, + takerFee: createOrderOpts.takerFee || defaultCreateOrderOpts.takerFee, + feeRecipientAddress: createOrderOpts.feeRecipientAddress || defaultCreateOrderOpts.feeRecipientAddress, + salt: createOrderOpts.salt || defaultCreateOrderOpts.salt, + expirationTimeSeconds: + createOrderOpts.expirationTimeSeconds || defaultCreateOrderOpts.expirationTimeSeconds, }; return order; }, @@ -51,13 +57,7 @@ export const orderFactory = { takerAssetAmount: BigNumber, takerAssetData: string, exchangeAddress: string, - takerAddress?: string, - senderAddress?: string, - makerFee?: BigNumber, - takerFee?: BigNumber, - feeRecipientAddress?: string, - salt?: BigNumber, - expirationTimeSeconds?: BigNumber, + createOrderOpts?: CreateOrderOpts, ): Promise { const order = orderFactory.createOrder( makerAddress, @@ -66,13 +66,7 @@ export const orderFactory = { takerAssetAmount, takerAssetData, exchangeAddress, - takerAddress, - senderAddress, - makerFee, - takerFee, - feeRecipientAddress, - salt, - expirationTimeSeconds, + createOrderOpts, ); const orderHash = orderHashUtils.getOrderHashHex(order); const messagePrefixOpts = { @@ -86,6 +80,26 @@ export const orderFactory = { }, }; +function generateDefaultCreateOrderOpts(): { + takerAddress: string; + senderAddress: string; + makerFee: BigNumber; + takerFee: BigNumber; + feeRecipientAddress: string; + salt: BigNumber; + expirationTimeSeconds: BigNumber; +} { + return { + takerAddress: constants.NULL_ADDRESS, + senderAddress: constants.NULL_ADDRESS, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + feeRecipientAddress: constants.NULL_ADDRESS, + salt: generatePseudoRandomSalt(), + expirationTimeSeconds: constants.INFINITE_TIMESTAMP_SEC, + }; +} + function getVRSHexString(ecSignature: ECSignature): string { const ETH_SIGN_SIGNATURE_TYPE = '03'; const vrs = `${intToHex(ecSignature.v)}${ethUtil.stripHexPrefix(ecSignature.r)}${ethUtil.stripHexPrefix( -- cgit From 3cb955c136bf47b5f40cdbc44bcc4d19ec6d6453 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Sun, 5 Aug 2018 17:15:58 -0400 Subject: Move CreateOrderOpts into shared types --- packages/order-utils/src/index.ts | 10 +++++++++- packages/order-utils/src/order_factory.ts | 12 +----------- packages/order-utils/src/types.ts | 12 ++++++++++++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts index 76be63bb8..129eb0a3d 100644 --- a/packages/order-utils/src/index.ts +++ b/packages/order-utils/src/index.ts @@ -13,7 +13,15 @@ export { orderFactory } from './order_factory'; export { constants } from './constants'; export { crypto } from './crypto'; export { generatePseudoRandomSalt } from './salt'; -export { OrderError, MessagePrefixType, MessagePrefixOpts, EIP712Parameter, EIP712Schema, EIP712Types } from './types'; +export { + CreateOrderOpts, + OrderError, + MessagePrefixType, + MessagePrefixOpts, + EIP712Parameter, + EIP712Schema, + EIP712Types, +} from './types'; export { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher'; export { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher'; export { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store'; diff --git a/packages/order-utils/src/order_factory.ts b/packages/order-utils/src/order_factory.ts index 5901d38c3..14727fd97 100644 --- a/packages/order-utils/src/order_factory.ts +++ b/packages/order-utils/src/order_factory.ts @@ -8,17 +8,7 @@ import { constants } from './constants'; import { orderHashUtils } from './order_hash'; import { generatePseudoRandomSalt } from './salt'; import { ecSignOrderHashAsync } from './signature_utils'; -import { MessagePrefixType } from './types'; - -export interface CreateOrderOpts { - takerAddress?: string; - senderAddress?: string; - makerFee?: BigNumber; - takerFee?: BigNumber; - feeRecipientAddress?: string; - salt?: BigNumber; - expirationTimeSeconds?: BigNumber; -} +import { CreateOrderOpts, MessagePrefixType } from './types'; export const orderFactory = { createOrder( diff --git a/packages/order-utils/src/types.ts b/packages/order-utils/src/types.ts index b08e74e71..f44e94349 100644 --- a/packages/order-utils/src/types.ts +++ b/packages/order-utils/src/types.ts @@ -1,3 +1,5 @@ +import { BigNumber } from '@0xproject/utils'; + export enum OrderError { InvalidSignature = 'INVALID_SIGNATURE', } @@ -51,3 +53,13 @@ export enum EIP712Types { String = 'string', Uint256 = 'uint256', } + +export interface CreateOrderOpts { + takerAddress?: string; + senderAddress?: string; + makerFee?: BigNumber; + takerFee?: BigNumber; + feeRecipientAddress?: string; + salt?: BigNumber; + expirationTimeSeconds?: BigNumber; +} -- cgit From 1c06380ef50ae401670d5de74ef2e32f972177ca Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Wed, 1 Aug 2018 23:14:24 -0700 Subject: Add findOrdersThatCoverMakerAssetFillAmount static method on ForwarderWrapper --- .../src/contract_wrappers/forwarder_wrapper.ts | 45 +++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts index 13ef0fe01..5e879b3a8 100644 --- a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts @@ -1,5 +1,5 @@ import { schemas } from '@0xproject/json-schemas'; -import { AssetProxyId, SignedOrder } from '@0xproject/types'; +import { AssetProxyId, OrderRelevantState, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { ContractAbi } from 'ethereum-types'; @@ -24,6 +24,49 @@ export class ForwarderWrapper extends ContractWrapper { private _forwarderContractIfExists?: ForwarderContract; private _contractAddressIfExists?: string; private _zrxContractAddressIfExists?: string; + /** + * Takes an array of orders and returns a subset of those orders that has enough makerAssetAmount (taking into account on-chain balances, + * allowances, and partial fills) in order to fill the input makerAssetFillAmount plus slippageBufferAmount. Iterates from first order to last. + * Sort the input by rate in order to get the subset of orders that will cost the least ETH. + * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify the same makerAsset. + * All orders should specify WETH as the takerAsset. + * @param orderStates An array of objects corresponding to the signedOrders parameter that each contain on-chain state + * relevant to that order. + * @param makerAssetFillAmount The amount of makerAsset desired to be filled. + * @param slippageBufferAmount An additional amount makerAsset to be covered by the result in case of trade collisions or partial fills. + * @return Resulting orders and remaining fill amount that could not be covered by the input. + */ + public static findOrdersThatCoverMakerAssetFillAmount( + signedOrders: SignedOrder[], + orderStates: OrderRelevantState[], + makerAssetFillAmount: BigNumber, + slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, + ): { resultOrders: SignedOrder[]; remainingFillAmount: BigNumber } { + // type assertions + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount); + assert.isBigNumber('slippageBufferAmount', slippageBufferAmount); + // calculate total amount of makerAsset needed to fill + const totalFillAmount = makerAssetFillAmount.plus(slippageBufferAmount); + // iterate through the signedOrders input from left to right until we have enough makerAsset to fill totalFillAmount + const result = _.reduce( + signedOrders, + ({ resultOrders, remainingFillAmount }, order, index) => { + if (remainingFillAmount.lessThanOrEqualTo(constants.ZERO_AMOUNT)) { + return { resultOrders, remainingFillAmount }; + } else { + const orderState = orderStates[index]; + const orderRemainingFillableMakerAssetAmount = orderState.remainingFillableMakerAssetAmount; + return { + resultOrders: _.concat(resultOrders, order), + remainingFillAmount: remainingFillAmount.minus(orderState.remainingFillableMakerAssetAmount), + }; + } + }, + { resultOrders: [] as SignedOrder[], remainingFillAmount: totalFillAmount }, + ); + return result; + } constructor( web3Wrapper: Web3Wrapper, networkId: number, -- cgit From a016747c36e0bc2c182e3d2b565ca63fb95e2b1b Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 2 Aug 2018 10:55:09 -0700 Subject: Add findFeeOrdersThatCoverFeesForTargetOrders to ForwarderWrapper --- .../src/contract_wrappers/forwarder_wrapper.ts | 65 ++++++++++++++++++++-- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts index 5e879b3a8..c0961b3a3 100644 --- a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts @@ -27,7 +27,7 @@ export class ForwarderWrapper extends ContractWrapper { /** * Takes an array of orders and returns a subset of those orders that has enough makerAssetAmount (taking into account on-chain balances, * allowances, and partial fills) in order to fill the input makerAssetFillAmount plus slippageBufferAmount. Iterates from first order to last. - * Sort the input by rate in order to get the subset of orders that will cost the least ETH. + * Sort the input by ascending rate in order to get the subset of orders that will cost the least ETH. * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify the same makerAsset. * All orders should specify WETH as the takerAsset. * @param orderStates An array of objects corresponding to the signedOrders parameter that each contain on-chain state @@ -46,20 +46,20 @@ export class ForwarderWrapper extends ContractWrapper { assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount); assert.isBigNumber('slippageBufferAmount', slippageBufferAmount); - // calculate total amount of makerAsset needed to fill + // calculate total amount of makerAsset needed to be filled const totalFillAmount = makerAssetFillAmount.plus(slippageBufferAmount); // iterate through the signedOrders input from left to right until we have enough makerAsset to fill totalFillAmount const result = _.reduce( signedOrders, ({ resultOrders, remainingFillAmount }, order, index) => { if (remainingFillAmount.lessThanOrEqualTo(constants.ZERO_AMOUNT)) { - return { resultOrders, remainingFillAmount }; + return { resultOrders, remainingFillAmount: constants.ZERO_AMOUNT }; } else { const orderState = orderStates[index]; - const orderRemainingFillableMakerAssetAmount = orderState.remainingFillableMakerAssetAmount; + const makerAssetAmountAvailable = ForwarderWrapper._getMakerAssetAmountAvailable(orderState); return { resultOrders: _.concat(resultOrders, order), - remainingFillAmount: remainingFillAmount.minus(orderState.remainingFillableMakerAssetAmount), + remainingFillAmount: remainingFillAmount.minus(makerAssetAmountAvailable), }; } }, @@ -67,6 +67,61 @@ export class ForwarderWrapper extends ContractWrapper { ); return result; } + /** + * Takes an array of orders and an array of feeOrders. Returns a subset of the feeOrders that has enough ZRX (taking into account + * on-chain balances, allowances, and partial fills) in order to fill the takerFees required by signedOrders plus a + * slippageBufferAmount. Iterates from first feeOrder to last. Sort the feeOrders by ascending rate in order to get the subset of + * feeOrders that will cost the least ETH. + * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as + * the makerAsset and WETH as the takerAsset. + * @param orderStates An array of objects corresponding to the signedOrders parameter that each contain on-chain state + * relevant to that order. + * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as + * the makerAsset and WETH as the takerAsset. + * @param feeOrderStates An array of objects corresponding to the signedOrders parameter that each contain on-chain state + * relevant to that order. + * @param makerAssetFillAmount The amount of makerAsset desired to be filled. + * @param slippageBufferAmount An additional amount makerAsset to be covered by the result in case of trade collisions or partial fills. + * @return Resulting orders and remaining fill amount that could not be covered by the input. + */ + public static findFeeOrdersThatCoverFeesForTargetOrders( + signedOrders: SignedOrder[], + orderStates: OrderRelevantState[], + signedFeeOrders: SignedOrder[], + feeOrderStates: OrderRelevantState[], + slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, + ): { resultOrders: SignedOrder[]; remainingFillAmount: BigNumber } { + // type assertions + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema); + assert.isBigNumber('slippageBufferAmount', slippageBufferAmount); + // calculate total amount of ZRX needed to fill signedOrders + const totalFeeAmount = _.reduce( + signedOrders, + (accFees, order, index) => { + const orderState = orderStates[index]; + const makerAssetAmountAvailable = ForwarderWrapper._getMakerAssetAmountAvailable(orderState); + const feeToFillMakerAssetAmountAvailable = makerAssetAmountAvailable + .div(order.makerAssetAmount) + .mul(order.takerFee); + return feeToFillMakerAssetAmountAvailable; + }, + constants.ZERO_AMOUNT, + ); + return ForwarderWrapper.findOrdersThatCoverMakerAssetFillAmount( + signedFeeOrders, + feeOrderStates, + totalFeeAmount, + slippageBufferAmount, + ); + } + private static _getMakerAssetAmountAvailable(orderState: OrderRelevantState): BigNumber { + return BigNumber.min( + orderState.makerBalance, + orderState.remainingFillableMakerAssetAmount, + orderState.makerProxyAllowance, + ); + } constructor( web3Wrapper: Web3Wrapper, networkId: number, -- cgit From d9933237a0be069d84944e4d4f1b3dffe6bb7643 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 2 Aug 2018 11:21:05 -0700 Subject: Move helper functions into order-utils --- .../src/contract_wrappers/forwarder_wrapper.ts | 100 +------------------ packages/order-utils/src/constants.ts | 2 +- packages/order-utils/src/market_utils.ts | 109 +++++++++++++++++++++ 3 files changed, 111 insertions(+), 100 deletions(-) create mode 100644 packages/order-utils/src/market_utils.ts diff --git a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts index c0961b3a3..13ef0fe01 100644 --- a/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/forwarder_wrapper.ts @@ -1,5 +1,5 @@ import { schemas } from '@0xproject/json-schemas'; -import { AssetProxyId, OrderRelevantState, SignedOrder } from '@0xproject/types'; +import { AssetProxyId, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { ContractAbi } from 'ethereum-types'; @@ -24,104 +24,6 @@ export class ForwarderWrapper extends ContractWrapper { private _forwarderContractIfExists?: ForwarderContract; private _contractAddressIfExists?: string; private _zrxContractAddressIfExists?: string; - /** - * Takes an array of orders and returns a subset of those orders that has enough makerAssetAmount (taking into account on-chain balances, - * allowances, and partial fills) in order to fill the input makerAssetFillAmount plus slippageBufferAmount. Iterates from first order to last. - * Sort the input by ascending rate in order to get the subset of orders that will cost the least ETH. - * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify the same makerAsset. - * All orders should specify WETH as the takerAsset. - * @param orderStates An array of objects corresponding to the signedOrders parameter that each contain on-chain state - * relevant to that order. - * @param makerAssetFillAmount The amount of makerAsset desired to be filled. - * @param slippageBufferAmount An additional amount makerAsset to be covered by the result in case of trade collisions or partial fills. - * @return Resulting orders and remaining fill amount that could not be covered by the input. - */ - public static findOrdersThatCoverMakerAssetFillAmount( - signedOrders: SignedOrder[], - orderStates: OrderRelevantState[], - makerAssetFillAmount: BigNumber, - slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, - ): { resultOrders: SignedOrder[]; remainingFillAmount: BigNumber } { - // type assertions - assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); - assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount); - assert.isBigNumber('slippageBufferAmount', slippageBufferAmount); - // calculate total amount of makerAsset needed to be filled - const totalFillAmount = makerAssetFillAmount.plus(slippageBufferAmount); - // iterate through the signedOrders input from left to right until we have enough makerAsset to fill totalFillAmount - const result = _.reduce( - signedOrders, - ({ resultOrders, remainingFillAmount }, order, index) => { - if (remainingFillAmount.lessThanOrEqualTo(constants.ZERO_AMOUNT)) { - return { resultOrders, remainingFillAmount: constants.ZERO_AMOUNT }; - } else { - const orderState = orderStates[index]; - const makerAssetAmountAvailable = ForwarderWrapper._getMakerAssetAmountAvailable(orderState); - return { - resultOrders: _.concat(resultOrders, order), - remainingFillAmount: remainingFillAmount.minus(makerAssetAmountAvailable), - }; - } - }, - { resultOrders: [] as SignedOrder[], remainingFillAmount: totalFillAmount }, - ); - return result; - } - /** - * Takes an array of orders and an array of feeOrders. Returns a subset of the feeOrders that has enough ZRX (taking into account - * on-chain balances, allowances, and partial fills) in order to fill the takerFees required by signedOrders plus a - * slippageBufferAmount. Iterates from first feeOrder to last. Sort the feeOrders by ascending rate in order to get the subset of - * feeOrders that will cost the least ETH. - * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as - * the makerAsset and WETH as the takerAsset. - * @param orderStates An array of objects corresponding to the signedOrders parameter that each contain on-chain state - * relevant to that order. - * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as - * the makerAsset and WETH as the takerAsset. - * @param feeOrderStates An array of objects corresponding to the signedOrders parameter that each contain on-chain state - * relevant to that order. - * @param makerAssetFillAmount The amount of makerAsset desired to be filled. - * @param slippageBufferAmount An additional amount makerAsset to be covered by the result in case of trade collisions or partial fills. - * @return Resulting orders and remaining fill amount that could not be covered by the input. - */ - public static findFeeOrdersThatCoverFeesForTargetOrders( - signedOrders: SignedOrder[], - orderStates: OrderRelevantState[], - signedFeeOrders: SignedOrder[], - feeOrderStates: OrderRelevantState[], - slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, - ): { resultOrders: SignedOrder[]; remainingFillAmount: BigNumber } { - // type assertions - assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); - assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema); - assert.isBigNumber('slippageBufferAmount', slippageBufferAmount); - // calculate total amount of ZRX needed to fill signedOrders - const totalFeeAmount = _.reduce( - signedOrders, - (accFees, order, index) => { - const orderState = orderStates[index]; - const makerAssetAmountAvailable = ForwarderWrapper._getMakerAssetAmountAvailable(orderState); - const feeToFillMakerAssetAmountAvailable = makerAssetAmountAvailable - .div(order.makerAssetAmount) - .mul(order.takerFee); - return feeToFillMakerAssetAmountAvailable; - }, - constants.ZERO_AMOUNT, - ); - return ForwarderWrapper.findOrdersThatCoverMakerAssetFillAmount( - signedFeeOrders, - feeOrderStates, - totalFeeAmount, - slippageBufferAmount, - ); - } - private static _getMakerAssetAmountAvailable(orderState: OrderRelevantState): BigNumber { - return BigNumber.min( - orderState.makerBalance, - orderState.remainingFillableMakerAssetAmount, - orderState.makerProxyAllowance, - ); - } constructor( web3Wrapper: Web3Wrapper, networkId: number, diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts index ea3f8b932..b18546a6c 100644 --- a/packages/order-utils/src/constants.ts +++ b/packages/order-utils/src/constants.ts @@ -10,6 +10,6 @@ export const constants = { ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH: 53, SELECTOR_LENGTH: 4, BASE_16: 16, - INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite + INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite, ZERO_AMOUNT: new BigNumber(0), }; diff --git a/packages/order-utils/src/market_utils.ts b/packages/order-utils/src/market_utils.ts new file mode 100644 index 000000000..4ddcc6ec8 --- /dev/null +++ b/packages/order-utils/src/market_utils.ts @@ -0,0 +1,109 @@ +import { schemas } from '@0xproject/json-schemas'; +import { OrderRelevantState, SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import * as _ from 'lodash'; + +import { assert } from './assert'; +import { constants } from './constants'; + +export const marketUtils = { + /** + * Takes an array of orders and returns a subset of those orders that has enough makerAssetAmount (taking into account on-chain balances, + * allowances, and partial fills) in order to fill the input makerAssetFillAmount plus slippageBufferAmount. Iterates from first order to last. + * Sort the input by ascending rate in order to get the subset of orders that will cost the least ETH. + * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify the same makerAsset. + * All orders should specify WETH as the takerAsset. + * @param orderStates An array of objects corresponding to the signedOrders parameter that each contain on-chain state + * relevant to that order. + * @param makerAssetFillAmount The amount of makerAsset desired to be filled. + * @param slippageBufferAmount An additional amount makerAsset to be covered by the result in case of trade collisions or partial fills. + * @return Resulting orders and remaining fill amount that could not be covered by the input. + */ + findOrdersThatCoverMakerAssetFillAmount( + signedOrders: SignedOrder[], + orderStates: OrderRelevantState[], + makerAssetFillAmount: BigNumber, + slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, + ): { resultOrders: SignedOrder[]; remainingFillAmount: BigNumber } { + // type assertions + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount); + assert.isBigNumber('slippageBufferAmount', slippageBufferAmount); + // calculate total amount of makerAsset needed to be filled + const totalFillAmount = makerAssetFillAmount.plus(slippageBufferAmount); + // iterate through the signedOrders input from left to right until we have enough makerAsset to fill totalFillAmount + const result = _.reduce( + signedOrders, + ({ resultOrders, remainingFillAmount }, order, index) => { + if (remainingFillAmount.lessThanOrEqualTo(constants.ZERO_AMOUNT)) { + return { resultOrders, remainingFillAmount: constants.ZERO_AMOUNT }; + } else { + const orderState = orderStates[index]; + const makerAssetAmountAvailable = getMakerAssetAmountAvailable(orderState); + return { + resultOrders: _.concat(resultOrders, order), + remainingFillAmount: remainingFillAmount.minus(makerAssetAmountAvailable), + }; + } + }, + { resultOrders: [] as SignedOrder[], remainingFillAmount: totalFillAmount }, + ); + return result; + }, + /** + * Takes an array of orders and an array of feeOrders. Returns a subset of the feeOrders that has enough ZRX (taking into account + * on-chain balances, allowances, and partial fills) in order to fill the takerFees required by signedOrders plus a + * slippageBufferAmount. Iterates from first feeOrder to last. Sort the feeOrders by ascending rate in order to get the subset of + * feeOrders that will cost the least ETH. + * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as + * the makerAsset and WETH as the takerAsset. + * @param orderStates An array of objects corresponding to the signedOrders parameter that each contain on-chain state + * relevant to that order. + * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as + * the makerAsset and WETH as the takerAsset. + * @param feeOrderStates An array of objects corresponding to the signedOrders parameter that each contain on-chain state + * relevant to that order. + * @param makerAssetFillAmount The amount of makerAsset desired to be filled. + * @param slippageBufferAmount An additional amount makerAsset to be covered by the result in case of trade collisions or partial fills. + * @return Resulting orders and remaining fill amount that could not be covered by the input. + */ + findFeeOrdersThatCoverFeesForTargetOrders( + signedOrders: SignedOrder[], + orderStates: OrderRelevantState[], + signedFeeOrders: SignedOrder[], + feeOrderStates: OrderRelevantState[], + slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, + ): { resultOrders: SignedOrder[]; remainingFillAmount: BigNumber } { + // type assertions + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema); + assert.isBigNumber('slippageBufferAmount', slippageBufferAmount); + // calculate total amount of ZRX needed to fill signedOrders + const totalFeeAmount = _.reduce( + signedOrders, + (accFees, order, index) => { + const orderState = orderStates[index]; + const makerAssetAmountAvailable = getMakerAssetAmountAvailable(orderState); + const feeToFillMakerAssetAmountAvailable = makerAssetAmountAvailable + .div(order.makerAssetAmount) + .mul(order.takerFee); + return accFees.plus(feeToFillMakerAssetAmountAvailable); + }, + constants.ZERO_AMOUNT, + ); + return marketUtils.findOrdersThatCoverMakerAssetFillAmount( + signedFeeOrders, + feeOrderStates, + totalFeeAmount, + slippageBufferAmount, + ); + }, +}; + +const getMakerAssetAmountAvailable = (orderState: OrderRelevantState) => { + return BigNumber.min( + orderState.makerBalance, + orderState.remainingFillableMakerAssetAmount, + orderState.makerProxyAllowance, + ); +}; -- cgit From e5d65b585a2b0a159f50320eaf5bfec05a869478 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 2 Aug 2018 12:51:12 -0700 Subject: Update hex schema to match 0x --- packages/json-schemas/schemas/basic_type_schemas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/json-schemas/schemas/basic_type_schemas.ts b/packages/json-schemas/schemas/basic_type_schemas.ts index 7565df9e0..301f9ec73 100644 --- a/packages/json-schemas/schemas/basic_type_schemas.ts +++ b/packages/json-schemas/schemas/basic_type_schemas.ts @@ -7,7 +7,7 @@ export const addressSchema = { export const hexSchema = { id: '/Hex', type: 'string', - pattern: '^0x([0-9a-f][0-9a-f])+$', + pattern: '^0x(([0-9a-f][0-9a-f])+)?$', }; export const numberSchema = { -- cgit From 09c0fc94fc91134acfdee1017d7a50e2047b019b Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 2 Aug 2018 13:22:04 -0700 Subject: Implement first round of tests for findOrdersThatCoverMakerAssetFillAmount --- packages/order-utils/src/constants.ts | 1 + packages/order-utils/src/index.ts | 1 + packages/order-utils/src/market_utils.ts | 23 ++-- packages/order-utils/test/market_utils_test.ts | 146 +++++++++++++++++++++ .../order-utils/test/utils/test_order_factory.ts | 63 +++++++++ 5 files changed, 222 insertions(+), 12 deletions(-) create mode 100644 packages/order-utils/test/market_utils_test.ts create mode 100644 packages/order-utils/test/utils/test_order_factory.ts diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts index b18546a6c..5137ff499 100644 --- a/packages/order-utils/src/constants.ts +++ b/packages/order-utils/src/constants.ts @@ -2,6 +2,7 @@ import { BigNumber } from '@0xproject/utils'; export const constants = { NULL_ADDRESS: '0x0000000000000000000000000000000000000000', + NULL_BYTES: '0x', // tslint:disable-next-line:custom-no-magic-numbers UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1), TESTRPC_NETWORK_ID: 50, diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts index 129eb0a3d..858f500c6 100644 --- a/packages/order-utils/src/index.ts +++ b/packages/order-utils/src/index.ts @@ -32,3 +32,4 @@ export { assetDataUtils } from './asset_data_utils'; export { EIP712Utils } from './eip712_utils'; export { OrderValidationUtils } from './order_validation_utils'; export { ExchangeTransferSimulator } from './exchange_transfer_simulator'; +export { marketUtils } from './market_utils'; diff --git a/packages/order-utils/src/market_utils.ts b/packages/order-utils/src/market_utils.ts index 4ddcc6ec8..710eddf8a 100644 --- a/packages/order-utils/src/market_utils.ts +++ b/packages/order-utils/src/market_utils.ts @@ -39,10 +39,17 @@ export const marketUtils = { return { resultOrders, remainingFillAmount: constants.ZERO_AMOUNT }; } else { const orderState = orderStates[index]; - const makerAssetAmountAvailable = getMakerAssetAmountAvailable(orderState); + const makerAssetAmountAvailable = orderState.remainingFillableMakerAssetAmount; + // 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: _.concat(resultOrders, order), - remainingFillAmount: remainingFillAmount.minus(makerAssetAmountAvailable), + resultOrders: makerAssetAmountAvailable.gt(constants.ZERO_AMOUNT) + ? _.concat(resultOrders, order) + : resultOrders, + remainingFillAmount: BigNumber.max( + constants.ZERO_AMOUNT, + remainingFillAmount.minus(makerAssetAmountAvailable), + ), }; } }, @@ -83,7 +90,7 @@ export const marketUtils = { signedOrders, (accFees, order, index) => { const orderState = orderStates[index]; - const makerAssetAmountAvailable = getMakerAssetAmountAvailable(orderState); + const makerAssetAmountAvailable = orderState.remainingFillableMakerAssetAmount; const feeToFillMakerAssetAmountAvailable = makerAssetAmountAvailable .div(order.makerAssetAmount) .mul(order.takerFee); @@ -99,11 +106,3 @@ export const marketUtils = { ); }, }; - -const getMakerAssetAmountAvailable = (orderState: OrderRelevantState) => { - return BigNumber.min( - orderState.makerBalance, - orderState.remainingFillableMakerAssetAmount, - orderState.makerProxyAllowance, - ); -}; diff --git a/packages/order-utils/test/market_utils_test.ts b/packages/order-utils/test/market_utils_test.ts new file mode 100644 index 000000000..93779d035 --- /dev/null +++ b/packages/order-utils/test/market_utils_test.ts @@ -0,0 +1,146 @@ +import { OrderRelevantState, SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; +import * as _ from 'lodash'; +import 'mocha'; + +import { constants, marketUtils, orderFactory } from '../src'; + +import { chaiSetup } from './utils/chai_setup'; +import { testOrderFactory } from './utils/test_order_factory'; + +chaiSetup.configure(); +const expect = chai.expect; + +// tslint:disable: no-unused-expression +describe('marketUtils', () => { + describe.only('#findOrdersThatCoverMakerAssetFillAmount', () => { + describe('no orders', () => { + it('returns empty and unchanged remainingFillAmount', async () => { + const fillAmount = new BigNumber(10); + const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( + [], + [], + fillAmount, + ); + expect(resultOrders).to.be.empty; + expect(remainingFillAmount).to.be.bignumber.equal(fillAmount); + }); + }); + describe('orders are all completely fillable', () => { + // generate three signed orders each with 10 units of makerAsset, 30 total + const testOrderCount = 3; + const makerAssetAmount = new BigNumber(10); + const inputOrders = testOrderFactory.generateTestSignedOrders( + { + makerAssetAmount, + }, + testOrderCount, + ); + // generate order states that cover the required fill amount + const inputOrderStates = testOrderFactory.generateTestOrderRelevantStates( + { + remainingFillableMakerAssetAmount: makerAssetAmount, + }, + testOrderCount, + ); + it('returns input orders and zero remainingFillAmount when input exactly matches requested fill amount', async () => { + // try to fill 30 units of makerAsset + const fillAmount = new BigNumber(30); + const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( + inputOrders, + inputOrderStates, + fillAmount, + ); + expect(resultOrders).to.be.deep.equal(inputOrders); + expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); + }); + it('returns input orders and zero remainingFillAmount when input has more than requested fill amount', async () => { + // try to fill 25 units of makerAsset + const fillAmount = new BigNumber(25); + const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( + inputOrders, + inputOrderStates, + fillAmount, + ); + expect(resultOrders).to.be.deep.equal(inputOrders); + expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); + }); + it('returns input orders and non-zero remainingFillAmount when input has less than requested fill amount', async () => { + // try to fill 35 units of makerAsset + const fillAmount = new BigNumber(35); + const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( + inputOrders, + inputOrderStates, + fillAmount, + ); + expect(resultOrders).to.be.deep.equal(inputOrders); + expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(5)); + }); + it('returns first order and zero remainingFillAmount when requested fill amount is exactly covered by the first order', async () => { + // try to fill 10 units of makerAsset + const fillAmount = new BigNumber(10); + const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( + inputOrders, + inputOrderStates, + fillAmount, + ); + expect(resultOrders).to.be.deep.equal([inputOrders[0]]); + expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); + }); + it('returns first two orders and zero remainingFillAmount when requested fill amount is over covered by the first two order', async () => { + // try to fill 15 units of makerAsset + const fillAmount = new BigNumber(15); + const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( + inputOrders, + inputOrderStates, + fillAmount, + ); + expect(resultOrders).to.be.deep.equal([inputOrders[0], inputOrders[1]]); + expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); + }); + }); + describe('orders are partially fillable', () => { + // generate three signed orders each with 10 units of makerAsset, 30 total + const testOrderCount = 3; + const makerAssetAmount = new BigNumber(10); + const inputOrders = testOrderFactory.generateTestSignedOrders( + { + makerAssetAmount, + }, + testOrderCount, + ); + // generate order states that cover different scenarios + // 1. order is completely filled already + // 2. order is partially fillable + // 3. order is completely fillable + const partialOrderStates: Array> = [ + { + remainingFillableMakerAssetAmount: constants.ZERO_AMOUNT, + }, + { + remainingFillableMakerAssetAmount: new BigNumber(5), + }, + { + remainingFillableMakerAssetAmount: makerAssetAmount, + }, + ]; + const inputOrderStates: OrderRelevantState[] = _.map( + partialOrderStates, + testOrderFactory.generateTestOrderRelevantState, + ); + it('returns last 2 orders and non-zero remainingFillAmount when trying to fill original makerAssetAmounts', async () => { + // try to fill 30 units of makerAsset + const fillAmount = new BigNumber(30); + const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( + inputOrders, + inputOrderStates, + fillAmount, + ); + expect(resultOrders).to.be.deep.equal([inputOrders[1], inputOrders[2]]); + expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(15)); + }); + }); + }); + describe('#findFeeOrdersThatCoverFeesForTargetOrders', () => {}); +}); diff --git a/packages/order-utils/test/utils/test_order_factory.ts b/packages/order-utils/test/utils/test_order_factory.ts new file mode 100644 index 000000000..2c5d8cf61 --- /dev/null +++ b/packages/order-utils/test/utils/test_order_factory.ts @@ -0,0 +1,63 @@ +import { Order, OrderRelevantState, SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import * as _ from 'lodash'; + +import { constants, orderFactory } from '../../src'; + +const BASE_TEST_ORDER: Order = orderFactory.createOrder( + constants.NULL_ADDRESS, + constants.NULL_ADDRESS, + constants.NULL_ADDRESS, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + constants.NULL_BYTES, + constants.ZERO_AMOUNT, + constants.NULL_BYTES, + constants.NULL_ADDRESS, + constants.NULL_ADDRESS, +); +const BASE_TEST_SIGNED_ORDER: SignedOrder = { + ...BASE_TEST_ORDER, + signature: constants.NULL_BYTES, +}; +const BASE_TEST_ORDER_RELEVANT_STATE: OrderRelevantState = { + makerBalance: constants.ZERO_AMOUNT, + makerProxyAllowance: constants.ZERO_AMOUNT, + makerFeeBalance: constants.ZERO_AMOUNT, + makerFeeProxyAllowance: constants.ZERO_AMOUNT, + filledTakerAssetAmount: constants.ZERO_AMOUNT, + remainingFillableMakerAssetAmount: constants.ZERO_AMOUNT, + remainingFillableTakerAssetAmount: constants.ZERO_AMOUNT, +}; + +export const testOrderFactory = { + generateTestSignedOrder(partialOrder: Partial): SignedOrder { + return transformObject(BASE_TEST_SIGNED_ORDER, partialOrder); + }, + generateTestSignedOrders(partialOrder: Partial, numOrders: number): SignedOrder[] { + const baseTestOrders = generateArrayOfInput(BASE_TEST_SIGNED_ORDER, numOrders); + return transformObjects(baseTestOrders, partialOrder); + }, + generateTestOrderRelevantState(partialOrderRelevantState: Partial): OrderRelevantState { + return transformObject(BASE_TEST_ORDER_RELEVANT_STATE, partialOrderRelevantState); + }, + generateTestOrderRelevantStates( + partialOrderRelevantState: Partial, + numOrderStates: number, + ): OrderRelevantState[] { + const baseTestOrderStates = generateArrayOfInput(BASE_TEST_ORDER_RELEVANT_STATE, numOrderStates); + return transformObjects(baseTestOrderStates, partialOrderRelevantState); + }, +}; + +function generateArrayOfInput(input: T, rangeLength: number): T[] { + return _.map(_.range(rangeLength), () => input); +} +function transformObject(input: T, transformation: Partial): T { + const copy = _.cloneDeep(input); + return _.assign(copy, transformation); +} +function transformObjects(inputs: T[], transformation: Partial): T[] { + return _.map(inputs, input => transformObject(input, transformation)); +} -- cgit From bc5f8e52de9dfe920ce1d0e6b44c90a5a5826cbe Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Thu, 2 Aug 2018 15:47:29 -0700 Subject: Change orderStates param name to remaingFillableMakerAssetAmounts --- packages/order-utils/src/market_utils.ts | 85 ++++++++++++++-------- packages/order-utils/test/market_utils_test.ts | 43 +++-------- .../order-utils/test/utils/test_order_factory.ts | 32 +------- 3 files changed, 69 insertions(+), 91 deletions(-) diff --git a/packages/order-utils/src/market_utils.ts b/packages/order-utils/src/market_utils.ts index 710eddf8a..d66448a0b 100644 --- a/packages/order-utils/src/market_utils.ts +++ b/packages/order-utils/src/market_utils.ts @@ -1,5 +1,5 @@ import { schemas } from '@0xproject/json-schemas'; -import { OrderRelevantState, SignedOrder } from '@0xproject/types'; +import { SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; @@ -11,24 +11,33 @@ export const marketUtils = { * Takes an array of orders and returns a subset of those orders that has enough makerAssetAmount (taking into account on-chain balances, * allowances, and partial fills) in order to fill the input makerAssetFillAmount plus slippageBufferAmount. Iterates from first order to last. * Sort the input by ascending rate in order to get the subset of orders that will cost the least ETH. - * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify the same makerAsset. - * All orders should specify WETH as the takerAsset. - * @param orderStates An array of objects corresponding to the signedOrders parameter that each contain on-chain state - * relevant to that order. - * @param makerAssetFillAmount The amount of makerAsset desired to be filled. - * @param slippageBufferAmount An additional amount makerAsset to be covered by the result in case of trade collisions or partial fills. + * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify the same makerAsset. + * All orders should specify WETH as the takerAsset. + * @param remainingFillableMakerAssetAmounts An array of BigNumbers corresponding to the signedOrders parameter. + * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups + * for these values. + * @param makerAssetFillAmount The amount of makerAsset desired to be filled. + * @param slippageBufferAmount An additional amount makerAsset to be covered by the result in case of trade collisions or partial fills. * @return Resulting orders and remaining fill amount that could not be covered by the input. */ findOrdersThatCoverMakerAssetFillAmount( signedOrders: SignedOrder[], - orderStates: OrderRelevantState[], + remainingFillableMakerAssetAmounts: BigNumber[], makerAssetFillAmount: BigNumber, slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, ): { resultOrders: SignedOrder[]; remainingFillAmount: BigNumber } { // type assertions assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); - assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount); - assert.isBigNumber('slippageBufferAmount', slippageBufferAmount); + _.forEach(remainingFillableMakerAssetAmounts, (amount, index) => + assert.isValidBaseUnitAmount(`remainingFillableMakerAssetAmount[${index}]`, amount), + ); + assert.isValidBaseUnitAmount('makerAssetFillAmount', makerAssetFillAmount); + assert.isValidBaseUnitAmount('slippageBufferAmount', slippageBufferAmount); + // other assertions + assert.assert( + signedOrders.length === remainingFillableMakerAssetAmounts.length, + 'Expected signedOrders.length to equal remainingFillableMakerAssetAmounts.length', + ); // calculate total amount of makerAsset needed to be filled const totalFillAmount = makerAssetFillAmount.plus(slippageBufferAmount); // iterate through the signedOrders input from left to right until we have enough makerAsset to fill totalFillAmount @@ -38,8 +47,7 @@ export const marketUtils = { if (remainingFillAmount.lessThanOrEqualTo(constants.ZERO_AMOUNT)) { return { resultOrders, remainingFillAmount: constants.ZERO_AMOUNT }; } else { - const orderState = orderStates[index]; - const makerAssetAmountAvailable = orderState.remainingFillableMakerAssetAmount; + const makerAssetAmountAvailable = remainingFillableMakerAssetAmounts[index]; // if there is no makerAssetAmountAvailable do not append order to resultOrders // if we have exceeded the total amount we want to fill set remainingFillAmount to 0 return { @@ -62,47 +70,64 @@ export const marketUtils = { * on-chain balances, allowances, and partial fills) in order to fill the takerFees required by signedOrders plus a * slippageBufferAmount. Iterates from first feeOrder to last. Sort the feeOrders by ascending rate in order to get the subset of * feeOrders that will cost the least ETH. - * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as - * the makerAsset and WETH as the takerAsset. - * @param orderStates An array of objects corresponding to the signedOrders parameter that each contain on-chain state - * relevant to that order. - * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as - * the makerAsset and WETH as the takerAsset. - * @param feeOrderStates An array of objects corresponding to the signedOrders parameter that each contain on-chain state - * relevant to that order. - * @param makerAssetFillAmount The amount of makerAsset desired to be filled. - * @param slippageBufferAmount An additional amount makerAsset to be covered by the result in case of trade collisions or partial fills. + * @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as + * the makerAsset and WETH as the takerAsset. + * @param remainingFillableMakerAssetAmounts An array of BigNumbers corresponding to the signedOrders parameter. + * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups + * for these values. + * @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as + * the makerAsset and WETH as the takerAsset. + * @param remainingFillableFeeAmounts An array of BigNumbers corresponding to the signedFeeOrders parameter. + * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups + * for these values. + * @param slippageBufferAmount An additional amount makerAsset to be covered by the result in case of trade collisions or partial fills. * @return Resulting orders and remaining fill amount that could not be covered by the input. */ findFeeOrdersThatCoverFeesForTargetOrders( signedOrders: SignedOrder[], - orderStates: OrderRelevantState[], + remainingFillableMakerAssetAmounts: BigNumber[], signedFeeOrders: SignedOrder[], - feeOrderStates: OrderRelevantState[], + remainingFillableFeeAmounts: BigNumber[], slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, ): { resultOrders: SignedOrder[]; remainingFillAmount: BigNumber } { // type assertions assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + _.forEach(remainingFillableMakerAssetAmounts, (amount, index) => + assert.isValidBaseUnitAmount(`remainingFillableMakerAssetAmount[${index}]`, amount), + ); assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema); - assert.isBigNumber('slippageBufferAmount', slippageBufferAmount); + _.forEach(remainingFillableFeeAmounts, (amount, index) => + assert.isValidBaseUnitAmount(`remainingFillableFeeAmounts[${index}]`, amount), + ); + assert.isValidBaseUnitAmount('slippageBufferAmount', slippageBufferAmount); + // other assertions + assert.assert( + signedOrders.length === remainingFillableMakerAssetAmounts.length, + 'Expected signedOrders.length to equal remainingFillableMakerAssetAmounts.length', + ); + assert.assert( + signedOrders.length === remainingFillableMakerAssetAmounts.length, + 'Expected signedFeeOrders.length to equal remainingFillableFeeAmounts.length', + ); // calculate total amount of ZRX needed to fill signedOrders const totalFeeAmount = _.reduce( signedOrders, (accFees, order, index) => { - const orderState = orderStates[index]; - const makerAssetAmountAvailable = orderState.remainingFillableMakerAssetAmount; + const makerAssetAmountAvailable = remainingFillableMakerAssetAmounts[index]; const feeToFillMakerAssetAmountAvailable = makerAssetAmountAvailable - .div(order.makerAssetAmount) - .mul(order.takerFee); + .mul(order.takerFee) + .div(order.makerAssetAmount); return accFees.plus(feeToFillMakerAssetAmountAvailable); }, constants.ZERO_AMOUNT, ); return marketUtils.findOrdersThatCoverMakerAssetFillAmount( signedFeeOrders, - feeOrderStates, + remainingFillableFeeAmounts, totalFeeAmount, slippageBufferAmount, ); + // 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/test/market_utils_test.ts b/packages/order-utils/test/market_utils_test.ts index 93779d035..ac3fb9b93 100644 --- a/packages/order-utils/test/market_utils_test.ts +++ b/packages/order-utils/test/market_utils_test.ts @@ -1,10 +1,8 @@ -import { OrderRelevantState, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; -import * as _ from 'lodash'; import 'mocha'; -import { constants, marketUtils, orderFactory } from '../src'; +import { constants, marketUtils } from '../src'; import { chaiSetup } from './utils/chai_setup'; import { testOrderFactory } from './utils/test_order_factory'; @@ -37,19 +35,14 @@ describe('marketUtils', () => { }, testOrderCount, ); - // generate order states that cover the required fill amount - const inputOrderStates = testOrderFactory.generateTestOrderRelevantStates( - { - remainingFillableMakerAssetAmount: makerAssetAmount, - }, - testOrderCount, - ); + // generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount + const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount]; it('returns input orders and zero remainingFillAmount when input exactly matches requested fill amount', async () => { // try to fill 30 units of makerAsset const fillAmount = new BigNumber(30); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - inputOrderStates, + remainingFillableMakerAssetAmounts, fillAmount, ); expect(resultOrders).to.be.deep.equal(inputOrders); @@ -60,7 +53,7 @@ describe('marketUtils', () => { const fillAmount = new BigNumber(25); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - inputOrderStates, + remainingFillableMakerAssetAmounts, fillAmount, ); expect(resultOrders).to.be.deep.equal(inputOrders); @@ -71,7 +64,7 @@ describe('marketUtils', () => { const fillAmount = new BigNumber(35); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - inputOrderStates, + remainingFillableMakerAssetAmounts, fillAmount, ); expect(resultOrders).to.be.deep.equal(inputOrders); @@ -82,7 +75,7 @@ describe('marketUtils', () => { const fillAmount = new BigNumber(10); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - inputOrderStates, + remainingFillableMakerAssetAmounts, fillAmount, ); expect(resultOrders).to.be.deep.equal([inputOrders[0]]); @@ -93,7 +86,7 @@ describe('marketUtils', () => { const fillAmount = new BigNumber(15); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - inputOrderStates, + remainingFillableMakerAssetAmounts, fillAmount, ); expect(resultOrders).to.be.deep.equal([inputOrders[0], inputOrders[1]]); @@ -110,31 +103,17 @@ describe('marketUtils', () => { }, testOrderCount, ); - // generate order states that cover different scenarios + // generate remainingFillableMakerAssetAmounts that cover different partial fill scenarios // 1. order is completely filled already // 2. order is partially fillable // 3. order is completely fillable - const partialOrderStates: Array> = [ - { - remainingFillableMakerAssetAmount: constants.ZERO_AMOUNT, - }, - { - remainingFillableMakerAssetAmount: new BigNumber(5), - }, - { - remainingFillableMakerAssetAmount: makerAssetAmount, - }, - ]; - const inputOrderStates: OrderRelevantState[] = _.map( - partialOrderStates, - testOrderFactory.generateTestOrderRelevantState, - ); + const remainingFillableMakerAssetAmounts = [constants.ZERO_AMOUNT, new BigNumber(5), makerAssetAmount]; it('returns last 2 orders and non-zero remainingFillAmount when trying to fill original makerAssetAmounts', async () => { // try to fill 30 units of makerAsset const fillAmount = new BigNumber(30); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, - inputOrderStates, + remainingFillableMakerAssetAmounts, fillAmount, ); expect(resultOrders).to.be.deep.equal([inputOrders[1], inputOrders[2]]); diff --git a/packages/order-utils/test/utils/test_order_factory.ts b/packages/order-utils/test/utils/test_order_factory.ts index 2c5d8cf61..611e777ea 100644 --- a/packages/order-utils/test/utils/test_order_factory.ts +++ b/packages/order-utils/test/utils/test_order_factory.ts @@ -1,5 +1,4 @@ -import { Order, OrderRelevantState, SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; +import { Order, SignedOrder } from '@0xproject/types'; import * as _ from 'lodash'; import { constants, orderFactory } from '../../src'; @@ -21,43 +20,18 @@ const BASE_TEST_SIGNED_ORDER: SignedOrder = { ...BASE_TEST_ORDER, signature: constants.NULL_BYTES, }; -const BASE_TEST_ORDER_RELEVANT_STATE: OrderRelevantState = { - makerBalance: constants.ZERO_AMOUNT, - makerProxyAllowance: constants.ZERO_AMOUNT, - makerFeeBalance: constants.ZERO_AMOUNT, - makerFeeProxyAllowance: constants.ZERO_AMOUNT, - filledTakerAssetAmount: constants.ZERO_AMOUNT, - remainingFillableMakerAssetAmount: constants.ZERO_AMOUNT, - remainingFillableTakerAssetAmount: constants.ZERO_AMOUNT, -}; export const testOrderFactory = { generateTestSignedOrder(partialOrder: Partial): SignedOrder { return transformObject(BASE_TEST_SIGNED_ORDER, partialOrder); }, generateTestSignedOrders(partialOrder: Partial, numOrders: number): SignedOrder[] { - const baseTestOrders = generateArrayOfInput(BASE_TEST_SIGNED_ORDER, numOrders); - return transformObjects(baseTestOrders, partialOrder); - }, - generateTestOrderRelevantState(partialOrderRelevantState: Partial): OrderRelevantState { - return transformObject(BASE_TEST_ORDER_RELEVANT_STATE, partialOrderRelevantState); - }, - generateTestOrderRelevantStates( - partialOrderRelevantState: Partial, - numOrderStates: number, - ): OrderRelevantState[] { - const baseTestOrderStates = generateArrayOfInput(BASE_TEST_ORDER_RELEVANT_STATE, numOrderStates); - return transformObjects(baseTestOrderStates, partialOrderRelevantState); + const baseTestOrders = _.map(_.range(numOrders), () => BASE_TEST_SIGNED_ORDER); + return _.map(baseTestOrders, order => transformObject(order, partialOrder)); }, }; -function generateArrayOfInput(input: T, rangeLength: number): T[] { - return _.map(_.range(rangeLength), () => input); -} function transformObject(input: T, transformation: Partial): T { const copy = _.cloneDeep(input); return _.assign(copy, transformation); } -function transformObjects(inputs: T[], transformation: Partial): T[] { - return _.map(inputs, input => transformObject(input, transformation)); -} -- cgit From 8382161f7553539ed6f436be88df8672b00bf35e Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Sun, 5 Aug 2018 20:48:56 -0400 Subject: Add tests for findFeeOrdersThatCoverFeesForTargetOrders --- packages/order-utils/src/constants.ts | 2 +- packages/order-utils/src/market_utils.ts | 14 +- packages/order-utils/test/market_utils_test.ts | 162 ++++++++++++++++++++- .../order-utils/test/utils/test_order_factory.ts | 7 +- 4 files changed, 165 insertions(+), 20 deletions(-) diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts index 5137ff499..c23578c20 100644 --- a/packages/order-utils/src/constants.ts +++ b/packages/order-utils/src/constants.ts @@ -11,6 +11,6 @@ export const constants = { ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH: 53, SELECTOR_LENGTH: 4, BASE_16: 16, - INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite, + INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite ZERO_AMOUNT: new BigNumber(0), }; diff --git a/packages/order-utils/src/market_utils.ts b/packages/order-utils/src/market_utils.ts index d66448a0b..94b5be4eb 100644 --- a/packages/order-utils/src/market_utils.ts +++ b/packages/order-utils/src/market_utils.ts @@ -17,7 +17,7 @@ export const marketUtils = { * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups * for these values. * @param makerAssetFillAmount The amount of makerAsset desired to be filled. - * @param slippageBufferAmount An additional amount makerAsset to be covered by the result in case of trade collisions or partial fills. + * @param slippageBufferAmount An additional amount of makerAsset to be covered by the result in case of trade collisions or partial fills. * @return Resulting orders and remaining fill amount that could not be covered by the input. */ findOrdersThatCoverMakerAssetFillAmount( @@ -80,8 +80,8 @@ export const marketUtils = { * @param remainingFillableFeeAmounts An array of BigNumbers corresponding to the signedFeeOrders parameter. * You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups * for these values. - * @param slippageBufferAmount An additional amount makerAsset to be covered by the result in case of trade collisions or partial fills. - * @return Resulting orders and remaining fill amount that could not be covered by the input. + * @param slippageBufferAmount An additional amount of fee to be covered by the result in case of trade collisions or partial fills. + * @return Resulting orders and remaining fee amount that could not be covered by the input. */ findFeeOrdersThatCoverFeesForTargetOrders( signedOrders: SignedOrder[], @@ -89,7 +89,7 @@ export const marketUtils = { signedFeeOrders: SignedOrder[], remainingFillableFeeAmounts: BigNumber[], slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, - ): { resultOrders: SignedOrder[]; remainingFillAmount: BigNumber } { + ): { resultOrders: SignedOrder[]; remainingFeeAmount: BigNumber } { // type assertions assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); _.forEach(remainingFillableMakerAssetAmounts, (amount, index) => @@ -121,12 +121,16 @@ export const marketUtils = { }, constants.ZERO_AMOUNT, ); - return marketUtils.findOrdersThatCoverMakerAssetFillAmount( + const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( signedFeeOrders, remainingFillableFeeAmounts, totalFeeAmount, slippageBufferAmount, ); + return { + resultOrders, + remainingFeeAmount: remainingFillAmount, + }; // TODO: add more orders here to cover rounding // https://github.com/0xProject/0x-protocol-specification/blob/master/v2/forwarding-contract-specification.md#over-buying-zrx }, diff --git a/packages/order-utils/test/market_utils_test.ts b/packages/order-utils/test/market_utils_test.ts index ac3fb9b93..03f86c581 100644 --- a/packages/order-utils/test/market_utils_test.ts +++ b/packages/order-utils/test/market_utils_test.ts @@ -12,7 +12,7 @@ const expect = chai.expect; // tslint:disable: no-unused-expression describe('marketUtils', () => { - describe.only('#findOrdersThatCoverMakerAssetFillAmount', () => { + describe('#findOrdersThatCoverMakerAssetFillAmount', () => { describe('no orders', () => { it('returns empty and unchanged remainingFillAmount', async () => { const fillAmount = new BigNumber(10); @@ -25,15 +25,14 @@ describe('marketUtils', () => { expect(remainingFillAmount).to.be.bignumber.equal(fillAmount); }); }); - describe('orders are all completely fillable', () => { + describe('orders are completely fillable', () => { // generate three signed orders each with 10 units of makerAsset, 30 total - const testOrderCount = 3; const makerAssetAmount = new BigNumber(10); const inputOrders = testOrderFactory.generateTestSignedOrders( { makerAssetAmount, }, - testOrderCount, + 3, ); // generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount]; @@ -95,20 +94,19 @@ describe('marketUtils', () => { }); describe('orders are partially fillable', () => { // generate three signed orders each with 10 units of makerAsset, 30 total - const testOrderCount = 3; const makerAssetAmount = new BigNumber(10); const inputOrders = testOrderFactory.generateTestSignedOrders( { makerAssetAmount, }, - testOrderCount, + 3, ); // generate remainingFillableMakerAssetAmounts that cover different partial fill scenarios // 1. order is completely filled already // 2. order is partially fillable // 3. order is completely fillable const remainingFillableMakerAssetAmounts = [constants.ZERO_AMOUNT, new BigNumber(5), makerAssetAmount]; - it('returns last 2 orders and non-zero remainingFillAmount when trying to fill original makerAssetAmounts', async () => { + it('returns last two orders and non-zero remainingFillAmount when trying to fill original makerAssetAmounts', async () => { // try to fill 30 units of makerAsset const fillAmount = new BigNumber(30); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( @@ -121,5 +119,153 @@ describe('marketUtils', () => { }); }); }); - describe('#findFeeOrdersThatCoverFeesForTargetOrders', () => {}); + describe('#findFeeOrdersThatCoverFeesForTargetOrders', () => { + // generate three signed fee orders each with 10 units of ZRX, 30 total + const zrxAmount = new BigNumber(10); + const inputFeeOrders = testOrderFactory.generateTestSignedOrders( + { + makerAssetAmount: zrxAmount, + }, + 3, + ); + // generate remainingFillableFeeAmounts that equal the zrxAmount + const remainingFillableFeeAmounts = [zrxAmount, zrxAmount, zrxAmount]; + describe('no target orders', () => { + it('returns empty and zero remainingFeeAmount', async () => { + const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( + [], + [], + inputFeeOrders, + remainingFillableFeeAmounts, + ); + expect(resultOrders).to.be.empty; + expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); + }); + }); + describe('no fee orders', () => { + // generate three signed orders each with 10 units of makerAsset, 30 total + // each signed order requires 10 units of takerFee + const makerAssetAmount = new BigNumber(10); + const takerFee = new BigNumber(10); + const inputOrders = testOrderFactory.generateTestSignedOrders( + { + makerAssetAmount, + takerFee, + }, + 3, + ); + // generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount + const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount]; + it('returns empty and non-zero remainingFeeAmount', async () => { + const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( + inputOrders, + remainingFillableMakerAssetAmounts, + [], + [], + ); + expect(resultOrders).to.be.empty; + expect(remainingFeeAmount).to.be.bignumber.equal(new BigNumber(30)); + }); + }); + describe('target orders have no fees', () => { + // generate three signed orders each with 10 units of makerAsset, 30 total + const makerAssetAmount = new BigNumber(10); + const inputOrders = testOrderFactory.generateTestSignedOrders( + { + makerAssetAmount, + }, + 3, + ); + // generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount + const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount]; + it('returns empty and zero remainingFeeAmount', async () => { + const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( + inputOrders, + remainingFillableMakerAssetAmounts, + inputFeeOrders, + remainingFillableFeeAmounts, + ); + expect(resultOrders).to.be.empty; + expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); + }); + }); + describe('target orders require fees and are completely fillable', () => { + // generate three signed orders each with 10 units of makerAsset, 30 total + // each signed order requires 10 units of takerFee + const makerAssetAmount = new BigNumber(10); + const takerFee = new BigNumber(10); + const inputOrders = testOrderFactory.generateTestSignedOrders( + { + makerAssetAmount, + takerFee, + }, + 3, + ); + // generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount + const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount]; + it('returns input fee orders and zero remainingFeeAmount', async () => { + const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( + inputOrders, + remainingFillableMakerAssetAmounts, + inputFeeOrders, + remainingFillableFeeAmounts, + ); + expect(resultOrders).to.be.deep.equal(inputFeeOrders); + expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); + }); + }); + describe('target orders require fees and are partially fillable', () => { + // generate three signed orders each with 10 units of makerAsset, 30 total + // each signed order requires 10 units of takerFee + const makerAssetAmount = new BigNumber(10); + const takerFee = new BigNumber(10); + const inputOrders = testOrderFactory.generateTestSignedOrders( + { + makerAssetAmount, + takerFee, + }, + 3, + ); + // generate remainingFillableMakerAssetAmounts that cover different partial fill scenarios + // 1. order is completely filled already + // 2. order is partially fillable + // 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( + inputOrders, + remainingFillableMakerAssetAmounts, + inputFeeOrders, + remainingFillableFeeAmounts, + ); + expect(resultOrders).to.be.deep.equal([inputFeeOrders[0], inputFeeOrders[1]]); + expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); + }); + }); + describe('target orders require more fees than available', () => { + // generate three signed orders each with 10 units of makerAsset, 30 total + // each signed order requires 20 units of takerFee + const makerAssetAmount = new BigNumber(10); + const takerFee = new BigNumber(20); + const inputOrders = testOrderFactory.generateTestSignedOrders( + { + makerAssetAmount, + takerFee, + }, + 3, + ); + // generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount + const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount]; + it('returns input fee orders and non-zero remainingFeeAmount', async () => { + const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders( + inputOrders, + remainingFillableMakerAssetAmounts, + inputFeeOrders, + remainingFillableFeeAmounts, + ); + expect(resultOrders).to.be.deep.equal(inputFeeOrders); + expect(remainingFeeAmount).to.be.bignumber.equal(new BigNumber(30)); + }); + }); + }); }); diff --git a/packages/order-utils/test/utils/test_order_factory.ts b/packages/order-utils/test/utils/test_order_factory.ts index 611e777ea..75dc6f1f2 100644 --- a/packages/order-utils/test/utils/test_order_factory.ts +++ b/packages/order-utils/test/utils/test_order_factory.ts @@ -5,14 +5,9 @@ import { constants, orderFactory } from '../../src'; const BASE_TEST_ORDER: Order = orderFactory.createOrder( constants.NULL_ADDRESS, - constants.NULL_ADDRESS, - constants.NULL_ADDRESS, - constants.ZERO_AMOUNT, constants.ZERO_AMOUNT, + constants.NULL_ADDRESS, constants.ZERO_AMOUNT, - constants.NULL_BYTES, - constants.ZERO_AMOUNT, - constants.NULL_BYTES, constants.NULL_ADDRESS, constants.NULL_ADDRESS, ); -- cgit From 7d0bec9b2a9960390b0b3b19e5ed4d84a679669b Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Sun, 5 Aug 2018 20:54:29 -0400 Subject: Add some test cases that stress slippageBufferAmount param --- packages/order-utils/test/market_utils_test.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/order-utils/test/market_utils_test.ts b/packages/order-utils/test/market_utils_test.ts index 03f86c581..21c0a4802 100644 --- a/packages/order-utils/test/market_utils_test.ts +++ b/packages/order-utils/test/market_utils_test.ts @@ -37,34 +37,43 @@ describe('marketUtils', () => { // generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount]; it('returns input orders and zero remainingFillAmount when input exactly matches requested fill amount', async () => { - // try to fill 30 units of makerAsset - const fillAmount = new BigNumber(30); + // try to fill 20 units of makerAsset + // include 10 units of slippageBufferAmount + const fillAmount = new BigNumber(20); + const slippageBufferAmount = new BigNumber(10); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, remainingFillableMakerAssetAmounts, fillAmount, + slippageBufferAmount, ); expect(resultOrders).to.be.deep.equal(inputOrders); expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); }); it('returns input orders and zero remainingFillAmount when input has more than requested fill amount', async () => { - // try to fill 25 units of makerAsset - const fillAmount = new BigNumber(25); + // try to fill 15 units of makerAsset + // include 10 units of slippageBufferAmount + const fillAmount = new BigNumber(15); + const slippageBufferAmount = new BigNumber(10); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, remainingFillableMakerAssetAmounts, fillAmount, + slippageBufferAmount, ); expect(resultOrders).to.be.deep.equal(inputOrders); expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); }); it('returns input orders and non-zero remainingFillAmount when input has less than requested fill amount', async () => { - // try to fill 35 units of makerAsset - const fillAmount = new BigNumber(35); + // try to fill 30 units of makerAsset + // include 5 units of slippageBufferAmount + const fillAmount = new BigNumber(30); + const slippageBufferAmount = new BigNumber(5); const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount( inputOrders, remainingFillableMakerAssetAmounts, fillAmount, + slippageBufferAmount, ); expect(resultOrders).to.be.deep.equal(inputOrders); expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(5)); -- cgit From 2273798df9c5b625a678fefaa2f49e7e1cb99d0f Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Sun, 5 Aug 2018 20:58:57 -0400 Subject: Update CHANGELOGs --- packages/json-schemas/CHANGELOG.json | 9 +++++++++ packages/order-utils/CHANGELOG.json | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/packages/json-schemas/CHANGELOG.json b/packages/json-schemas/CHANGELOG.json index 31da6a7f7..33cf126e3 100644 --- a/packages/json-schemas/CHANGELOG.json +++ b/packages/json-schemas/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "1.0.1-rc.4", + "changes": [ + { + "note": "Change hexSchema to match `0x`", + "pr": 937 + } + ] + }, { "version": "1.0.1-rc.3", "changes": [ diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index 70a75854a..776bd67ec 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -6,6 +6,10 @@ "note": "Added a synchronous `createOrder` method in `orderFactory`, updated public interfaces to support some optional parameters", "pr": 936 + }, + { + "note": "Added marketUtils", + "pr": 937 } ] }, -- cgit From 0bc775cdb8617fea73c87eaff015bf3d2dfadb42 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Sun, 5 Aug 2018 21:02:10 -0400 Subject: Remove 0x test case from hexSchema test --- packages/json-schemas/test/schema_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/json-schemas/test/schema_test.ts b/packages/json-schemas/test/schema_test.ts index d202b5643..f84553df1 100644 --- a/packages/json-schemas/test/schema_test.ts +++ b/packages/json-schemas/test/schema_test.ts @@ -89,7 +89,7 @@ describe('Schema', () => { validateAgainstSchema(testCases, hexSchema); }); it('should fail for invalid hex string', () => { - const testCases = ['0x', '0', '0xzzzzzzB11a196601eD2ce54B665CaFEca0347D42']; + const testCases = ['0', '0xzzzzzzB11a196601eD2ce54B665CaFEca0347D42']; const shouldFail = true; validateAgainstSchema(testCases, hexSchema, shouldFail); }); -- cgit From 35201af4b1aa64a0961de0d13ce9c5bac65ddbf8 Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 6 Aug 2018 16:35:49 -0400 Subject: Remove assertion comments --- packages/order-utils/src/market_utils.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/order-utils/src/market_utils.ts b/packages/order-utils/src/market_utils.ts index 94b5be4eb..681059ddf 100644 --- a/packages/order-utils/src/market_utils.ts +++ b/packages/order-utils/src/market_utils.ts @@ -26,14 +26,12 @@ export const marketUtils = { makerAssetFillAmount: BigNumber, slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, ): { resultOrders: SignedOrder[]; remainingFillAmount: BigNumber } { - // type assertions assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); _.forEach(remainingFillableMakerAssetAmounts, (amount, index) => assert.isValidBaseUnitAmount(`remainingFillableMakerAssetAmount[${index}]`, amount), ); assert.isValidBaseUnitAmount('makerAssetFillAmount', makerAssetFillAmount); assert.isValidBaseUnitAmount('slippageBufferAmount', slippageBufferAmount); - // other assertions assert.assert( signedOrders.length === remainingFillableMakerAssetAmounts.length, 'Expected signedOrders.length to equal remainingFillableMakerAssetAmounts.length', @@ -90,7 +88,6 @@ export const marketUtils = { remainingFillableFeeAmounts: BigNumber[], slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT, ): { resultOrders: SignedOrder[]; remainingFeeAmount: BigNumber } { - // type assertions assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); _.forEach(remainingFillableMakerAssetAmounts, (amount, index) => assert.isValidBaseUnitAmount(`remainingFillableMakerAssetAmount[${index}]`, amount), @@ -100,7 +97,6 @@ export const marketUtils = { assert.isValidBaseUnitAmount(`remainingFillableFeeAmounts[${index}]`, amount), ); assert.isValidBaseUnitAmount('slippageBufferAmount', slippageBufferAmount); - // other assertions assert.assert( signedOrders.length === remainingFillableMakerAssetAmounts.length, 'Expected signedOrders.length to equal remainingFillableMakerAssetAmounts.length', -- cgit From 3d6cf503645386734bb552e09df2c6709e2ed45c Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Sun, 29 Jul 2018 21:47:21 -0700 Subject: Fix comments, styling, and optimize hashOrder --- .../2.0.0/protocol/Exchange/MixinTransactions.sol | 4 +-- .../src/2.0.0/protocol/Exchange/libs/LibEIP712.sol | 15 ++++++--- .../src/2.0.0/protocol/Exchange/libs/LibOrder.sol | 39 ++++++++++++---------- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinTransactions.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinTransactions.sol index 88d2da7d7..0814638db 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/MixinTransactions.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinTransactions.sol @@ -123,10 +123,10 @@ contract MixinTransactions is bytes32 dataHash = keccak256(data); // Assembly for more efficiently computing: - // keccak256(abi.encode( + // keccak256(abi.encodePacked( // EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH, // salt, - // signerAddress, + // bytes32(signerAddress), // keccak256(data) // )); diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibEIP712.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibEIP712.sol index 1fc41dafd..c9e45189d 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibEIP712.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibEIP712.sol @@ -30,7 +30,7 @@ contract LibEIP712 { string constant internal EIP712_DOMAIN_VERSION = "2"; // Hash of the EIP712 Domain Separator Schema - bytes32 public constant EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked( + bytes32 constant internal EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked( "EIP712Domain(", "string name,", "string version,", @@ -45,11 +45,11 @@ contract LibEIP712 { constructor () public { - EIP712_DOMAIN_HASH = keccak256(abi.encode( + EIP712_DOMAIN_HASH = keccak256(abi.encodePacked( EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH, keccak256(bytes(EIP712_DOMAIN_NAME)), keccak256(bytes(EIP712_DOMAIN_VERSION)), - address(this) + bytes32(address(this)) )); } @@ -59,8 +59,13 @@ contract LibEIP712 { function hashEIP712Message(bytes32 hashStruct) internal view - returns (bytes32) + returns (bytes32 result) { - return keccak256(abi.encodePacked(EIP191_HEADER, EIP712_DOMAIN_HASH, hashStruct)); + result = keccak256(abi.encodePacked( + EIP191_HEADER, + EIP712_DOMAIN_HASH, + hashStruct + )); + return result; } } diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibOrder.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibOrder.sol index 4031ff26b..68f4f5f1b 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibOrder.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibOrder.sol @@ -103,11 +103,12 @@ contract LibOrder is bytes32 takerAssetDataHash = keccak256(order.takerAssetData); // Assembly for more efficiently computing: - // keccak256(abi.encode( - // order.makerAddress, - // order.takerAddress, - // order.feeRecipientAddress, - // order.senderAddress, + // keccak256(abi.encodePacked( + // EIP712_ORDER_SCHEMA_HASH, + // bytes32(order.makerAddress), + // bytes32(order.takerAddress), + // bytes32(order.feeRecipientAddress), + // bytes32(order.senderAddress), // order.makerAssetAmount, // order.takerAssetAmount, // order.makerFee, @@ -119,24 +120,26 @@ contract LibOrder is // )); assembly { + // Calculate memory addresses that will be swapped out before hashing + let pos1 := sub(order, 32) + let pos2 := add(order, 320) + let pos3 := add(order, 352) + // Backup - // solhint-disable-next-line space-after-comma - let temp1 := mload(sub(order, 32)) - let temp2 := mload(add(order, 320)) - let temp3 := mload(add(order, 352)) + let temp1 := mload(pos1) + let temp2 := mload(pos2) + let temp3 := mload(pos3) // Hash in place - // solhint-disable-next-line space-after-comma - mstore(sub(order, 32), schemaHash) - mstore(add(order, 320), makerAssetDataHash) - mstore(add(order, 352), takerAssetDataHash) - result := keccak256(sub(order, 32), 416) + mstore(pos1, schemaHash) + mstore(pos2, makerAssetDataHash) + mstore(pos3, takerAssetDataHash) + result := keccak256(pos1, 416) // Restore - // solhint-disable-next-line space-after-comma - mstore(sub(order, 32), temp1) - mstore(add(order, 320), temp2) - mstore(add(order, 352), temp3) + mstore(pos1, temp1) + mstore(pos2, temp2) + mstore(pos3, temp3) } return result; } -- cgit From 149c07dfd2ef2e2102d66ebbdaf1268a1938f4af Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Tue, 7 Aug 2018 16:27:02 -0700 Subject: Use asm for hashEIP712Message, increment free memory pointer after asm hashing functions --- .../2.0.0/protocol/Exchange/MixinTransactions.sol | 12 +++++++---- .../src/2.0.0/protocol/Exchange/libs/LibEIP712.sol | 25 +++++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinTransactions.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinTransactions.sol index 0814638db..b5de1a5de 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/MixinTransactions.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinTransactions.sol @@ -131,11 +131,15 @@ contract MixinTransactions is // )); assembly { + // Load free memory pointer let memPtr := mload(64) - mstore(memPtr, schemaHash) - mstore(add(memPtr, 32), salt) - mstore(add(memPtr, 64), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff)) - mstore(add(memPtr, 96), dataHash) + + mstore(memPtr, schemaHash) // hash of schema + mstore(add(memPtr, 32), salt) // salt + mstore(add(memPtr, 64), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff)) // signerAddress + mstore(add(memPtr, 96), dataHash) // hash of data + + // Compute hash result := keccak256(memPtr, 128) } diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibEIP712.sol b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibEIP712.sol index c9e45189d..b02f7632e 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibEIP712.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/libs/LibEIP712.sol @@ -61,11 +61,26 @@ contract LibEIP712 { view returns (bytes32 result) { - result = keccak256(abi.encodePacked( - EIP191_HEADER, - EIP712_DOMAIN_HASH, - hashStruct - )); + bytes32 eip712DomainHash = EIP712_DOMAIN_HASH; + + // Assembly for more efficient computing: + // keccak256(abi.encodePacked( + // EIP191_HEADER, + // EIP712_DOMAIN_HASH, + // hashStruct + // )); + + assembly { + // Load free memory pointer + let memPtr := mload(64) + + mstore(memPtr, 0x1901000000000000000000000000000000000000000000000000000000000000) // EIP191 header + mstore(add(memPtr, 2), eip712DomainHash) // EIP712 domain hash + mstore(add(memPtr, 34), hashStruct) // Hash of struct + + // Compute hash + result := keccak256(memPtr, 66) + } return result; } } -- cgit From 6e2e658162a5a128b722ba105f92fa5267c4bd62 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Wed, 8 Aug 2018 11:27:38 -0700 Subject: Update TypeScript to version 2.9.2 --- packages/0x.js/package.json | 2 +- packages/abi-gen/package.json | 2 +- packages/assert/package.json | 2 +- packages/base-contract/package.json | 2 +- packages/connect/package.json | 2 +- packages/contract-wrappers/package.json | 2 +- packages/contracts/package.json | 2 +- packages/contracts/test/exchange/match_orders.ts | 23 ++++++-- packages/dev-utils/package.json | 2 +- packages/ethereum-types/package.json | 2 +- packages/fill-scenarios/package.json | 2 +- packages/json-schemas/package.json | 2 +- packages/metacoin/package.json | 2 +- packages/migrations/package.json | 2 +- packages/monorepo-scripts/package.json | 2 +- packages/order-utils/package.json | 2 +- packages/order-watcher/package.json | 2 +- packages/react-docs-example/package.json | 2 +- packages/react-docs/package.json | 2 +- packages/react-shared/package.json | 2 +- packages/sol-compiler/package.json | 2 +- packages/sol-cov/package.json | 2 +- packages/sol-resolver/package.json | 2 +- packages/sra-report/package.json | 2 +- packages/subproviders/package.json | 2 +- packages/testnet-faucets/package.json | 2 +- packages/tslint-config/package.json | 2 +- packages/types/package.json | 2 +- packages/utils/package.json | 2 +- packages/web3-wrapper/package.json | 2 +- packages/website/package.json | 2 +- yarn.lock | 71 ++---------------------- 32 files changed, 53 insertions(+), 101 deletions(-) diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index 3b92752e1..7d3fa92c3 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -94,7 +94,7 @@ "source-map-support": "^0.5.0", "tslint": "5.11.0", "typedoc": "0xProject/typedoc", - "typescript": "2.7.1", + "typescript": "2.9.2", "webpack": "^3.1.0" }, "dependencies": { diff --git a/packages/abi-gen/package.json b/packages/abi-gen/package.json index 2732cdb64..393428771 100644 --- a/packages/abi-gen/package.json +++ b/packages/abi-gen/package.json @@ -63,7 +63,7 @@ "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "publishConfig": { "access": "public" diff --git a/packages/assert/package.json b/packages/assert/package.json index 27fd51923..f95190ad6 100644 --- a/packages/assert/package.json +++ b/packages/assert/package.json @@ -44,7 +44,7 @@ "nyc": "^11.0.1", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "dependencies": { "@0xproject/json-schemas": "^1.0.1-rc.3", diff --git a/packages/base-contract/package.json b/packages/base-contract/package.json index 7ac629bbf..851b81262 100644 --- a/packages/base-contract/package.json +++ b/packages/base-contract/package.json @@ -40,7 +40,7 @@ "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "dependencies": { "@0xproject/typescript-typings": "^1.0.3", diff --git a/packages/connect/package.json b/packages/connect/package.json index 57bd27c5e..d2f18c410 100644 --- a/packages/connect/package.json +++ b/packages/connect/package.json @@ -83,7 +83,7 @@ "shx": "^0.2.2", "tslint": "5.11.0", "typedoc": "~0.8.0", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "publishConfig": { "access": "public" diff --git a/packages/contract-wrappers/package.json b/packages/contract-wrappers/package.json index f27afaba9..64de5b0c2 100644 --- a/packages/contract-wrappers/package.json +++ b/packages/contract-wrappers/package.json @@ -68,7 +68,7 @@ "sinon": "^4.0.0", "source-map-support": "^0.5.0", "tslint": "5.11.0", - "typescript": "2.7.1", + "typescript": "2.9.2", "web3-provider-engine": "14.0.6" }, "dependencies": { diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 014210d33..1f5c15674 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -68,7 +68,7 @@ "solc": "^0.4.24", "solhint": "^1.2.1", "tslint": "5.11.0", - "typescript": "2.7.1", + "typescript": "2.9.2", "yargs": "^10.0.3" }, "dependencies": { diff --git a/packages/contracts/test/exchange/match_orders.ts b/packages/contracts/test/exchange/match_orders.ts index 440097562..46b3569bd 100644 --- a/packages/contracts/test/exchange/match_orders.ts +++ b/packages/contracts/test/exchange/match_orders.ts @@ -69,13 +69,22 @@ describe('matchOrders', () => { before(async () => { // Create accounts const accounts = await web3Wrapper.getAvailableAddressesAsync(); + // Hack(albrow): Both Prettier and TSLint insert a trailing comma below + // but that is invalid syntax as of TypeScript version >= 2.8. We don't + // have the right fine-grained configuration options in TSLint, + // Prettier, or TypeScript, to reconcile this, so we will just have to + // wait for them to sort it out. We disable TSLint and Prettier for + // this part of the code for now. This occurs several times in this + // file. See https://github.com/prettier/prettier/issues/4624. + // prettier-ignore const usedAddresses = ([ owner, makerAddressLeft, makerAddressRight, takerAddress, feeRecipientAddressLeft, - feeRecipientAddressRight, + // tslint:disable-next-line:trailing-comma + feeRecipientAddressRight ] = _.slice(accounts, 0, 6)); // Create wrappers erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); @@ -201,9 +210,11 @@ describe('matchOrders', () => { // Match signedOrderLeft with signedOrderRight let newERC20BalancesByOwner: ERC20BalancesByOwner; let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner; + // prettier-ignore [ newERC20BalancesByOwner, - newERC721TokenIdsByOwner, + // tslint:disable-next-line:trailing-comma + newERC721TokenIdsByOwner ] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, @@ -306,9 +317,11 @@ describe('matchOrders', () => { // Match orders let newERC20BalancesByOwner: ERC20BalancesByOwner; let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner; + // prettier-ignore [ newERC20BalancesByOwner, - newERC721TokenIdsByOwner, + // tslint:disable-next-line:trailing-comma + newERC721TokenIdsByOwner ] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, @@ -374,9 +387,11 @@ describe('matchOrders', () => { // Match orders let newERC20BalancesByOwner: ERC20BalancesByOwner; let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner; + // prettier-ignore [ newERC20BalancesByOwner, - newERC721TokenIdsByOwner, + // tslint:disable-next-line:trailing-comma + newERC721TokenIdsByOwner ] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 2c7c53194..66f6472f1 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -42,7 +42,7 @@ "nyc": "^11.0.1", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "dependencies": { "@0xproject/subproviders": "^1.0.4", diff --git a/packages/ethereum-types/package.json b/packages/ethereum-types/package.json index 7ed99d419..03e70a778 100644 --- a/packages/ethereum-types/package.json +++ b/packages/ethereum-types/package.json @@ -41,7 +41,7 @@ "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "dependencies": { "@types/node": "^8.0.53", diff --git a/packages/fill-scenarios/package.json b/packages/fill-scenarios/package.json index 281575107..f7e1e1ec4 100644 --- a/packages/fill-scenarios/package.json +++ b/packages/fill-scenarios/package.json @@ -38,7 +38,7 @@ "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "dependencies": { "@0xproject/base-contract": "^1.0.4", diff --git a/packages/json-schemas/package.json b/packages/json-schemas/package.json index 4793fc0d5..6f0376d1b 100644 --- a/packages/json-schemas/package.json +++ b/packages/json-schemas/package.json @@ -70,7 +70,7 @@ "shx": "^0.2.2", "tslint": "5.11.0", "typedoc": "0xProject/typedoc", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "publishConfig": { "access": "public" diff --git a/packages/metacoin/package.json b/packages/metacoin/package.json index bab14bd5b..890d14fba 100644 --- a/packages/metacoin/package.json +++ b/packages/metacoin/package.json @@ -56,6 +56,6 @@ "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.7.1" + "typescript": "2.9.2" } } diff --git a/packages/migrations/package.json b/packages/migrations/package.json index 0a1186f6a..c4d14eaee 100644 --- a/packages/migrations/package.json +++ b/packages/migrations/package.json @@ -49,7 +49,7 @@ "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.7.1", + "typescript": "2.9.2", "yargs": "^10.0.3" }, "dependencies": { diff --git a/packages/monorepo-scripts/package.json b/packages/monorepo-scripts/package.json index 128bdcff5..c849c01ba 100644 --- a/packages/monorepo-scripts/package.json +++ b/packages/monorepo-scripts/package.json @@ -39,7 +39,7 @@ "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "dependencies": { "@lerna/batch-packages": "^3.0.0-beta.18", diff --git a/packages/order-utils/package.json b/packages/order-utils/package.json index cab917a82..7880b9352 100644 --- a/packages/order-utils/package.json +++ b/packages/order-utils/package.json @@ -70,7 +70,7 @@ "sinon": "^4.0.0", "tslint": "5.11.0", "typedoc": "0xProject/typedoc", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "dependencies": { "@0xproject/assert": "^1.0.4", diff --git a/packages/order-watcher/package.json b/packages/order-watcher/package.json index e4226f017..c000b4fec 100644 --- a/packages/order-watcher/package.json +++ b/packages/order-watcher/package.json @@ -67,7 +67,7 @@ "sinon": "^4.0.0", "source-map-support": "^0.5.0", "tslint": "5.11.0", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "dependencies": { "@0xproject/assert": "^1.0.4", diff --git a/packages/react-docs-example/package.json b/packages/react-docs-example/package.json index ca7a85b76..4eb109b3e 100644 --- a/packages/react-docs-example/package.json +++ b/packages/react-docs-example/package.json @@ -45,7 +45,7 @@ "source-map-loader": "^0.2.3", "style-loader": "^0.20.2", "tslint": "^5.9.1", - "typescript": "2.7.1", + "typescript": "2.9.2", "webpack": "^3.11.0", "webpack-dev-server": "^2.11.1" }, diff --git a/packages/react-docs/package.json b/packages/react-docs/package.json index 1028f7fd6..44044e54d 100644 --- a/packages/react-docs/package.json +++ b/packages/react-docs/package.json @@ -33,7 +33,7 @@ "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "^5.9.1", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "dependencies": { "@0xproject/react-shared": "^1.0.5", diff --git a/packages/react-shared/package.json b/packages/react-shared/package.json index bb9211752..839bfccc5 100644 --- a/packages/react-shared/package.json +++ b/packages/react-shared/package.json @@ -32,7 +32,7 @@ "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "^5.9.1", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "dependencies": { "@types/is-mobile": "0.3.0", diff --git a/packages/sol-compiler/package.json b/packages/sol-compiler/package.json index c03477544..c31d180c2 100644 --- a/packages/sol-compiler/package.json +++ b/packages/sol-compiler/package.json @@ -71,7 +71,7 @@ "tslint": "5.11.0", "typedoc": "0xProject/typedoc", "types-bn": "^0.0.1", - "typescript": "2.7.1", + "typescript": "2.9.2", "web3-typescript-typings": "^0.10.2", "zeppelin-solidity": "1.8.0" }, diff --git a/packages/sol-cov/package.json b/packages/sol-cov/package.json index ee87543db..41758b30d 100644 --- a/packages/sol-cov/package.json +++ b/packages/sol-cov/package.json @@ -89,7 +89,7 @@ "sinon": "^4.0.0", "tslint": "5.11.0", "typedoc": "0xProject/typedoc", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "publishConfig": { "access": "public" diff --git a/packages/sol-resolver/package.json b/packages/sol-resolver/package.json index dd5915237..618f78eec 100644 --- a/packages/sol-resolver/package.json +++ b/packages/sol-resolver/package.json @@ -30,7 +30,7 @@ "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "dependencies": { "@0xproject/types": "^1.0.1-rc.3", diff --git a/packages/sra-report/package.json b/packages/sra-report/package.json index 57e91a93c..55063a7f0 100644 --- a/packages/sra-report/package.json +++ b/packages/sra-report/package.json @@ -66,7 +66,7 @@ "nyc": "^11.0.1", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "publishConfig": { "access": "public" diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index 5e0153765..d59326b6f 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -84,7 +84,7 @@ "sinon": "^4.0.0", "tslint": "5.11.0", "typedoc": "0xProject/typedoc", - "typescript": "2.7.1", + "typescript": "2.9.2", "webpack": "^3.1.0" }, "optionalDependencies": { diff --git a/packages/testnet-faucets/package.json b/packages/testnet-faucets/package.json index 18306311e..3b68c06ee 100644 --- a/packages/testnet-faucets/package.json +++ b/packages/testnet-faucets/package.json @@ -43,7 +43,7 @@ "shx": "^0.2.2", "source-map-loader": "^0.1.6", "tslint": "5.11.0", - "typescript": "2.7.1", + "typescript": "2.9.2", "webpack": "^3.1.0", "webpack-node-externals": "^1.6.0" } diff --git a/packages/tslint-config/package.json b/packages/tslint-config/package.json index f6fdb3649..040db472a 100644 --- a/packages/tslint-config/package.json +++ b/packages/tslint-config/package.json @@ -39,7 +39,7 @@ "copyfiles": "^1.2.0", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "dependencies": { "lodash": "^4.17.4", diff --git a/packages/types/package.json b/packages/types/package.json index d2fefa136..e42c44630 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -30,7 +30,7 @@ "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "dependencies": { "@types/node": "^8.0.53", diff --git a/packages/utils/package.json b/packages/utils/package.json index b1a0d8eb9..ee150cb0e 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -32,7 +32,7 @@ "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "dependencies": { "@0xproject/types": "^1.0.1-rc.3", diff --git a/packages/web3-wrapper/package.json b/packages/web3-wrapper/package.json index d0f55c905..300382c7f 100644 --- a/packages/web3-wrapper/package.json +++ b/packages/web3-wrapper/package.json @@ -61,7 +61,7 @@ "shx": "^0.2.2", "tslint": "5.11.0", "typedoc": "0xProject/typedoc", - "typescript": "2.7.1" + "typescript": "2.9.2" }, "dependencies": { "@0xproject/assert": "^1.0.4", diff --git a/packages/website/package.json b/packages/website/package.json index 13f1f5372..4a19fed6d 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -98,7 +98,7 @@ "style-loader": "0.13.x", "tslint": "5.11.0", "tslint-config-0xproject": "^0.0.2", - "typescript": "2.7.1", + "typescript": "2.9.2", "uglifyjs-webpack-plugin": "^1.2.5", "webpack": "^3.1.0", "webpack-dev-middleware": "^1.10.0", diff --git a/yarn.lock b/yarn.lock index 10db1da42..90a254354 100644 --- a/yarn.lock +++ b/yarn.lock @@ -565,37 +565,6 @@ lodash "4.17.10" uuid "3.2.1" -"@0xproject/contract-wrappers@^1.0.1-rc.2": - version "1.0.1-rc.1" - dependencies: - "@0xproject/assert" "^1.0.3" - "@0xproject/base-contract" "^1.0.3" - "@0xproject/fill-scenarios" "^1.0.1-rc.1" - "@0xproject/json-schemas" "^1.0.1-rc.2" - "@0xproject/order-utils" "^1.0.1-rc.1" - "@0xproject/types" "^1.0.1-rc.2" - "@0xproject/typescript-typings" "^1.0.3" - "@0xproject/utils" "^1.0.3" - "@0xproject/web3-wrapper" "^1.1.1" - ethereum-types "^1.0.3" - ethereumjs-blockstream "5.0.0" - ethereumjs-util "^5.1.1" - ethers "3.0.22" - js-sha3 "^0.7.0" - lodash "^4.17.4" - uuid "^3.1.0" - -"@0xproject/dev-utils@^1.0.3": - version "1.0.2" - dependencies: - "@0xproject/subproviders" "^1.0.3" - "@0xproject/types" "^1.0.1-rc.2" - "@0xproject/typescript-typings" "^1.0.3" - "@0xproject/utils" "^1.0.3" - "@0xproject/web3-wrapper" "^1.1.1" - ethereum-types "^1.0.3" - lodash "^4.17.4" - "@0xproject/fill-scenarios@^0.0.4": version "0.0.4" resolved "https://registry.yarnpkg.com/@0xproject/fill-scenarios/-/fill-scenarios-0.0.4.tgz#4d23c75abda7e9f117b698c0b8b142af07e0c69e" @@ -653,23 +622,6 @@ jsonschema "1.2.2" lodash.values "4.3.0" -"@0xproject/migrations@^1.0.3": - version "1.0.2" - dependencies: - "@0xproject/base-contract" "^1.0.3" - "@0xproject/order-utils" "^1.0.1-rc.1" - "@0xproject/sol-compiler" "^1.0.3" - "@0xproject/subproviders" "^1.0.3" - "@0xproject/typescript-typings" "^1.0.3" - "@0xproject/utils" "^1.0.3" - "@0xproject/web3-wrapper" "^1.1.1" - "@ledgerhq/hw-app-eth" "^4.3.0" - ethereum-types "^1.0.3" - ethers "3.0.22" - lodash "^4.17.4" - optionalDependencies: - "@ledgerhq/hw-transport-node-hid" "^4.3.0" - "@0xproject/order-utils@^0.0.7": version "0.0.7" resolved "https://registry.yarnpkg.com/@0xproject/order-utils/-/order-utils-0.0.7.tgz#eaa465782ea5745bdad54e1a851533172d993b7c" @@ -718,25 +670,6 @@ ethereumjs-util "5.1.5" lodash "4.17.10" -"@0xproject/order-utils@^1.0.1-rc.2": - version "1.0.1-rc.1" - dependencies: - "@0xproject/assert" "^1.0.3" - "@0xproject/base-contract" "^1.0.3" - "@0xproject/json-schemas" "^1.0.1-rc.2" - "@0xproject/sol-compiler" "^1.0.3" - "@0xproject/types" "^1.0.1-rc.2" - "@0xproject/typescript-typings" "^1.0.3" - "@0xproject/utils" "^1.0.3" - "@0xproject/web3-wrapper" "^1.1.1" - "@types/node" "^8.0.53" - bn.js "^4.11.8" - ethereum-types "^1.0.3" - ethereumjs-abi "0.6.5" - ethereumjs-util "^5.1.1" - ethers "3.0.22" - lodash "^4.17.4" - "@0xproject/order-watcher@^0.0.7": version "0.0.7" resolved "https://registry.yarnpkg.com/@0xproject/order-watcher/-/order-watcher-0.0.7.tgz#fbe019aa33447781096b5d562e7a3a4ec91a1da2" @@ -13146,6 +13079,10 @@ typescript@2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.1.tgz#bb3682c2c791ac90e7c6210b26478a8da085c359" +typescript@2.9.2: + version "2.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" + typewise-core@^1.2, typewise-core@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/typewise-core/-/typewise-core-1.2.0.tgz#97eb91805c7f55d2f941748fa50d315d991ef195" -- cgit From 68fb1bf37622e244f7dbce276a7c3355e4ec88eb Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Wed, 8 Aug 2018 13:58:29 -0700 Subject: fix comments and styling for MixinSignatureValidator --- .../src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol index ac7382715..44de54817 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol @@ -96,14 +96,15 @@ contract MixinSignatureValidator is "LENGTH_GREATER_THAN_0_REQUIRED" ); - // Ensure signature is supported + // Pop last byte off of signature byte array. uint8 signatureTypeRaw = uint8(signature.popLastByte()); + + // Ensure signature is supported require( signatureTypeRaw < uint8(SignatureType.NSignatureTypes), "SIGNATURE_UNSUPPORTED" ); - // Pop last byte off of signature byte array. SignatureType signatureType = SignatureType(signatureTypeRaw); // Variables are not scoped in Solidity. @@ -141,7 +142,12 @@ contract MixinSignatureValidator is v = uint8(signature[0]); r = signature.readBytes32(1); s = signature.readBytes32(33); - recovered = ecrecover(hash, v, r, s); + recovered = ecrecover( + hash, + v, + r, + s + ); isValid = signerAddress == recovered; return isValid; @@ -197,7 +203,6 @@ contract MixinSignatureValidator is // | 0x14 + x | 1 | Signature type is always "\x06" | } else if (signatureType == SignatureType.Validator) { // Pop last 20 bytes off of signature byte array. - address validatorAddress = signature.popLast20Bytes(); // Ensure signer has approved validator. -- cgit From 797fd38e00e48abddc03be214984f81bf7b1c29c Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Wed, 8 Aug 2018 14:01:12 -0700 Subject: feat(monorepo-scripts): Add confirmation prompt before publishing --- packages/monorepo-scripts/src/publish.ts | 22 +++++++---- yarn.lock | 67 -------------------------------- 2 files changed, 14 insertions(+), 75 deletions(-) diff --git a/packages/monorepo-scripts/src/publish.ts b/packages/monorepo-scripts/src/publish.ts index 5992131db..6ff0c9bef 100644 --- a/packages/monorepo-scripts/src/publish.ts +++ b/packages/monorepo-scripts/src/publish.ts @@ -31,12 +31,25 @@ const packageNameToWebsitePath: { [name: string]: string } = { 'ethereum-types': 'ethereum-types', }; +async function confirmAsync(message: string): Promise { + prompt.start(); + const result = await promisify(prompt.get)([message]); + const didConfirm = result[message] === 'y'; + if (!didConfirm) { + utils.log('Publish process aborted.'); + process.exit(0); + } +} + (async () => { // Fetch public, updated Lerna packages const shouldIncludePrivate = true; const allUpdatedPackages = await utils.getUpdatedPackagesAsync(shouldIncludePrivate); if (!configs.IS_LOCAL_PUBLISH) { + await confirmAsync( + 'THIS IS NOT A TEST PUBLISH! You are about to publish one or more packages to npm. Are you sure you want to continue? (y/n)', + ); await confirmDocPagesRenderAsync(allUpdatedPackages); } @@ -107,14 +120,7 @@ package.ts. Please add an entry for it and try again.`, opn(link); }); - prompt.start(); - const message = 'Do all the doc pages render properly? (yn)'; - const result = await promisify(prompt.get)([message]); - const didConfirm = result[message] === 'y'; - if (!didConfirm) { - utils.log('Publish process aborted.'); - process.exit(0); - } + await confirmAsync('Do all the doc pages render properly? (y/n)'); } async function pushChangelogsToGithubAsync(): Promise { diff --git a/yarn.lock b/yarn.lock index 10db1da42..84f690011 100644 --- a/yarn.lock +++ b/yarn.lock @@ -565,37 +565,6 @@ lodash "4.17.10" uuid "3.2.1" -"@0xproject/contract-wrappers@^1.0.1-rc.2": - version "1.0.1-rc.1" - dependencies: - "@0xproject/assert" "^1.0.3" - "@0xproject/base-contract" "^1.0.3" - "@0xproject/fill-scenarios" "^1.0.1-rc.1" - "@0xproject/json-schemas" "^1.0.1-rc.2" - "@0xproject/order-utils" "^1.0.1-rc.1" - "@0xproject/types" "^1.0.1-rc.2" - "@0xproject/typescript-typings" "^1.0.3" - "@0xproject/utils" "^1.0.3" - "@0xproject/web3-wrapper" "^1.1.1" - ethereum-types "^1.0.3" - ethereumjs-blockstream "5.0.0" - ethereumjs-util "^5.1.1" - ethers "3.0.22" - js-sha3 "^0.7.0" - lodash "^4.17.4" - uuid "^3.1.0" - -"@0xproject/dev-utils@^1.0.3": - version "1.0.2" - dependencies: - "@0xproject/subproviders" "^1.0.3" - "@0xproject/types" "^1.0.1-rc.2" - "@0xproject/typescript-typings" "^1.0.3" - "@0xproject/utils" "^1.0.3" - "@0xproject/web3-wrapper" "^1.1.1" - ethereum-types "^1.0.3" - lodash "^4.17.4" - "@0xproject/fill-scenarios@^0.0.4": version "0.0.4" resolved "https://registry.yarnpkg.com/@0xproject/fill-scenarios/-/fill-scenarios-0.0.4.tgz#4d23c75abda7e9f117b698c0b8b142af07e0c69e" @@ -653,23 +622,6 @@ jsonschema "1.2.2" lodash.values "4.3.0" -"@0xproject/migrations@^1.0.3": - version "1.0.2" - dependencies: - "@0xproject/base-contract" "^1.0.3" - "@0xproject/order-utils" "^1.0.1-rc.1" - "@0xproject/sol-compiler" "^1.0.3" - "@0xproject/subproviders" "^1.0.3" - "@0xproject/typescript-typings" "^1.0.3" - "@0xproject/utils" "^1.0.3" - "@0xproject/web3-wrapper" "^1.1.1" - "@ledgerhq/hw-app-eth" "^4.3.0" - ethereum-types "^1.0.3" - ethers "3.0.22" - lodash "^4.17.4" - optionalDependencies: - "@ledgerhq/hw-transport-node-hid" "^4.3.0" - "@0xproject/order-utils@^0.0.7": version "0.0.7" resolved "https://registry.yarnpkg.com/@0xproject/order-utils/-/order-utils-0.0.7.tgz#eaa465782ea5745bdad54e1a851533172d993b7c" @@ -718,25 +670,6 @@ ethereumjs-util "5.1.5" lodash "4.17.10" -"@0xproject/order-utils@^1.0.1-rc.2": - version "1.0.1-rc.1" - dependencies: - "@0xproject/assert" "^1.0.3" - "@0xproject/base-contract" "^1.0.3" - "@0xproject/json-schemas" "^1.0.1-rc.2" - "@0xproject/sol-compiler" "^1.0.3" - "@0xproject/types" "^1.0.1-rc.2" - "@0xproject/typescript-typings" "^1.0.3" - "@0xproject/utils" "^1.0.3" - "@0xproject/web3-wrapper" "^1.1.1" - "@types/node" "^8.0.53" - bn.js "^4.11.8" - ethereum-types "^1.0.3" - ethereumjs-abi "0.6.5" - ethereumjs-util "^5.1.1" - ethers "3.0.22" - lodash "^4.17.4" - "@0xproject/order-watcher@^0.0.7": version "0.0.7" resolved "https://registry.yarnpkg.com/@0xproject/order-watcher/-/order-watcher-0.0.7.tgz#fbe019aa33447781096b5d562e7a3a4ec91a1da2" -- cgit From 5ccf41c56693aa45988001260b68fdad2124b12c Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Wed, 8 Aug 2018 14:01:57 -0700 Subject: fix(monorepo-scripts): Fix typo in git tag command --- packages/monorepo-scripts/src/utils/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/monorepo-scripts/src/utils/utils.ts b/packages/monorepo-scripts/src/utils/utils.ts index d9bae3ea9..26ac801bd 100644 --- a/packages/monorepo-scripts/src/utils/utils.ts +++ b/packages/monorepo-scripts/src/utils/utils.ts @@ -117,7 +117,7 @@ export const utils = { return tags; }, async getLocalGitTagsAsync(): Promise { - const result = await execAsync(`git tags`, { + const result = await execAsync(`git tag`, { cwd: constants.monorepoRootPath, }); const tagsString = result.stdout; -- cgit From 6a5965d73bb542634631d7af76c150795d899744 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Thu, 26 Jul 2018 14:11:03 -0700 Subject: Add strictArgumentEncodingCheck to BaseContract and use it in contract templates --- packages/0x.js/test/0x.js_test.ts | 3 +- packages/base-contract/src/index.ts | 17 +++ packages/base-contract/test/base_contract_test.ts | 110 +++++++++++++++ .../partials/callAsync.handlebars | 1 + packages/contract_templates/partials/tx.handlebars | 1 + .../contracts/test/exchange/signature_validator.ts | 6 +- packages/order-utils/test/signature_utils_test.ts | 3 +- packages/utils/package.json | 10 +- packages/utils/src/abi_utils.ts | 153 +++++++++++++++++++++ packages/utils/test/abi_utils_test.ts | 19 +++ packages/utils/tsconfig.json | 5 +- 11 files changed, 319 insertions(+), 9 deletions(-) create mode 100644 packages/base-contract/test/base_contract_test.ts create mode 100644 packages/utils/test/abi_utils_test.ts diff --git a/packages/0x.js/test/0x.js_test.ts b/packages/0x.js/test/0x.js_test.ts index b4baecb1e..2c1cb2c74 100644 --- a/packages/0x.js/test/0x.js_test.ts +++ b/packages/0x.js/test/0x.js_test.ts @@ -48,8 +48,9 @@ describe('ZeroEx library', () => { const ethSignSignature = '0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403'; const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; + const bytes32Zeros = '0x0000000000000000000000000000000000000000000000000000000000000000'; it("should return false if the data doesn't pertain to the signature & address", async () => { - return expect((zeroEx.exchange as any).isValidSignatureAsync('0x0', address, ethSignSignature)).to.become( + return expect((zeroEx.exchange as any).isValidSignatureAsync(bytes32Zeros, address, ethSignSignature)).to.become( false, ); }); diff --git a/packages/base-contract/src/index.ts b/packages/base-contract/src/index.ts index a240fb8b6..e354f109e 100644 --- a/packages/base-contract/src/index.ts +++ b/packages/base-contract/src/index.ts @@ -82,6 +82,23 @@ export class BaseContract { } return txDataWithDefaults; } + // Throws if the given arguments cannot be safely/correctly encoded based on + // the given inputAbi. An argument may not be considered safely encodeable + // if it overflows the corresponding Solidity type, there is a bug in the + // encoder, or the encoder performs unsafe type coercion. + public static strictArgumentEncodingCheck(inputAbi: DataItem[], args: any[]): void { + const coder = (ethers as any).utils.AbiCoder.defaultCoder; + const params = abiUtils.parseEthersParams(inputAbi); + const rawEncoded = coder.encode(params.names, params.types, args); + const rawDecoded = coder.decode(params.names, params.types, rawEncoded); + for (let i = 0; i < rawDecoded.length; i++) { + const original = args[i]; + const decoded = rawDecoded[i]; + if (!abiUtils.isAbiDataEqual(params.names[i], params.types[i], original, decoded)) { + throw new Error(`Cannot safely encode argument: ${params.names[i]} (${original}) of type ${params.types[i]}. (Possible type overflow or other encoding error)`); + } + } + } protected _lookupEthersInterface(functionSignature: string): ethers.Interface { const ethersInterface = this._ethersInterfacesByFunctionSignature[functionSignature]; if (_.isUndefined(ethersInterface)) { diff --git a/packages/base-contract/test/base_contract_test.ts b/packages/base-contract/test/base_contract_test.ts new file mode 100644 index 000000000..57aacdc8a --- /dev/null +++ b/packages/base-contract/test/base_contract_test.ts @@ -0,0 +1,110 @@ +import * as chai from 'chai'; +import 'mocha'; + +import { BaseContract } from '../src'; + +const { expect } = chai; + +describe('BaseContract', () => { + describe('strictArgumentEncodingCheck', () => { + it('works for simple types', () => { + BaseContract.strictArgumentEncodingCheck([{ name: 'to', type: 'address' }], ['0xe834ec434daba538cd1b9fe1582052b880bd7e63']); + }); + it('works for array types', () => { + const inputAbi = [{ + name: 'takerAssetFillAmounts', + type: 'uint256[]', + }]; + const args = [ + [ + '9000000000000000000', + '79000000000000000000', + '979000000000000000000', + '7979000000000000000000', + ], + ]; + BaseContract.strictArgumentEncodingCheck(inputAbi, args); + }); + it('works for tuple/struct types', () => { + const inputAbi = [ + { + components: [ + { + 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', + }, + ], + name: 'order', + type: 'tuple', + }, + ]; + const args = [ + { + makerAddress: '0x6ecbe1db9ef729cbe972c83fb886247691fb6beb', + takerAddress: '0x0000000000000000000000000000000000000000', + feeRecipientAddress: '0xe834ec434daba538cd1b9fe1582052b880bd7e63', + senderAddress: '0x0000000000000000000000000000000000000000', + makerAssetAmount: '0', + takerAssetAmount: '200000000000000000000', + makerFee: '1000000000000000000', + takerFee: '1000000000000000000', + expirationTimeSeconds: '1532563026', + salt: '59342956082154660870994022243365949771115859664887449740907298019908621891376', + makerAssetData: '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48', + takerAssetData: '0xf47261b00000000000000000000000001d7022f5b17d2f8b695918fb48fa1089c9f85401', + }, + ]; + BaseContract.strictArgumentEncodingCheck(inputAbi, args); + }); + it('throws for integer overflows', () => { + expect(() => BaseContract.strictArgumentEncodingCheck([{ name: 'amount', type: 'uint8' }], ['256'])).to.throw(); + }); + it('throws for fixed byte array overflows', () => { + expect(() => BaseContract.strictArgumentEncodingCheck([{ name: 'hash', type: 'bytes8' }], ['0x001122334455667788'])).to.throw(); + }); + }); +}); diff --git a/packages/contract_templates/partials/callAsync.handlebars b/packages/contract_templates/partials/callAsync.handlebars index fcaae57c6..94752691d 100644 --- a/packages/contract_templates/partials/callAsync.handlebars +++ b/packages/contract_templates/partials/callAsync.handlebars @@ -7,6 +7,7 @@ async callAsync( const functionSignature = '{{this.functionSignature}}'; const inputAbi = self._lookupAbi(functionSignature).inputs; [{{> params inputs=inputs}}] = BaseContract._formatABIDataItemList(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self)); + BaseContract.strictArgumentEncodingCheck(inputAbi, [{{> params inputs=inputs}}]); const ethersFunction = self._lookupEthersInterface(functionSignature).functions.{{this.name}}( {{> params inputs=inputs}} ) as ethers.CallDescription; diff --git a/packages/contract_templates/partials/tx.handlebars b/packages/contract_templates/partials/tx.handlebars index e297d05e6..4340d662e 100644 --- a/packages/contract_templates/partials/tx.handlebars +++ b/packages/contract_templates/partials/tx.handlebars @@ -11,6 +11,7 @@ public {{this.tsName}} = { const self = this as any as {{contractName}}Contract; const inputAbi = self._lookupAbi('{{this.functionSignature}}').inputs; [{{> params inputs=inputs}}] = BaseContract._formatABIDataItemList(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self)); + BaseContract.strictArgumentEncodingCheck(inputAbi, [{{> params inputs=inputs}}]); const encodedData = self._lookupEthersInterface('{{this.functionSignature}}').functions.{{this.name}}( {{> params inputs=inputs}} ).data; diff --git a/packages/contracts/test/exchange/signature_validator.ts b/packages/contracts/test/exchange/signature_validator.ts index f2bb42c75..bef0547bd 100644 --- a/packages/contracts/test/exchange/signature_validator.ts +++ b/packages/contracts/test/exchange/signature_validator.ts @@ -113,7 +113,7 @@ describe('MixinSignatureValidator', () => { it('should revert when signature type is unsupported', async () => { const unsupportedSignatureType = SignatureType.NSignatureTypes; - const unsupportedSignatureHex = `0x${unsupportedSignatureType}`; + const unsupportedSignatureHex = '0x' + Buffer.from([unsupportedSignatureType]).toString('hex'); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); return expectContractCallFailed( signatureValidator.publicIsValidSignature.callAsync( @@ -126,7 +126,7 @@ describe('MixinSignatureValidator', () => { }); it('should revert when SignatureType=Illegal', async () => { - const unsupportedSignatureHex = `0x${SignatureType.Illegal}`; + const unsupportedSignatureHex = '0x' + Buffer.from([SignatureType.Illegal]).toString('hex'); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); return expectContractCallFailed( signatureValidator.publicIsValidSignature.callAsync( @@ -139,7 +139,7 @@ describe('MixinSignatureValidator', () => { }); it('should return false when SignatureType=Invalid and signature has a length of zero', async () => { - const signatureHex = `0x${SignatureType.Invalid}`; + const signatureHex = '0x' + Buffer.from([SignatureType.Invalid]).toString('hex'); const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( orderHashHex, diff --git a/packages/order-utils/test/signature_utils_test.ts b/packages/order-utils/test/signature_utils_test.ts index 5714f9671..baae2b414 100644 --- a/packages/order-utils/test/signature_utils_test.ts +++ b/packages/order-utils/test/signature_utils_test.ts @@ -22,7 +22,8 @@ describe('Signature utils', () => { let address = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; it("should return false if the data doesn't pertain to the signature & address", async () => { - expect(await isValidSignatureAsync(provider, '0x0', ethSignSignature, address)).to.be.false(); + const bytes32Zeros = '0x0000000000000000000000000000000000000000000000000000000000000000'; + expect(await 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'; diff --git a/packages/utils/package.json b/packages/utils/package.json index ee150cb0e..46c1d05d0 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -5,12 +5,13 @@ "node": ">=6.12" }, "description": "0x TS utils", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", "scripts": { "watch_without_deps": "tsc -w", "build": "tsc && copyfiles -u 2 './lib/monorepo_scripts/**/*' ./scripts", "clean": "shx rm -rf lib scripts", + "test": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --bail --exit", "lint": "tslint --project .", "manual:postpublish": "yarn build; node ./scripts/postpublish.js" }, @@ -27,12 +28,15 @@ "@0xproject/monorepo-scripts": "^1.0.4", "@0xproject/tslint-config": "^1.0.4", "@types/lodash": "4.14.104", + "@types/mocha": "^2.2.42", "copyfiles": "^1.2.0", "make-promises-safe": "^1.1.0", "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.9.2" + "typescript": "2.9.2", + "chai": "^4.0.1", + "mocha": "^4.0.1" }, "dependencies": { "@0xproject/types": "^1.0.1-rc.3", diff --git a/packages/utils/src/abi_utils.ts b/packages/utils/src/abi_utils.ts index 421dd405c..16aa72afd 100644 --- a/packages/utils/src/abi_utils.ts +++ b/packages/utils/src/abi_utils.ts @@ -1,7 +1,160 @@ import { AbiDefinition, AbiType, ContractAbi, DataItem, MethodAbi } from 'ethereum-types'; import * as _ from 'lodash'; +import { BigNumber } from './configured_bignumber'; + +export type EthersParamName = null | string | EthersNestedParamName; + +export interface EthersNestedParamName { + name: string | null; + names: EthersParamName[]; +} + +// Note(albrow): This function is unexported in ethers.js. Copying it here for +// now. +// Source: https://github.com/ethers-io/ethers.js/blob/884593ab76004a808bf8097e9753fb5f8dcc3067/contracts/interface.js#L30 +function parseEthersParams(params: DataItem[]): { names: EthersParamName[]; types: string[] } { + const names: EthersParamName[] = []; + const types: string[] = []; + + params.forEach((param: DataItem) => { + if (param.components != null) { + let suffix = ''; + const arrayBracket = param.type.indexOf('['); + if (arrayBracket >= 0) { suffix = param.type.substring(arrayBracket); } + + const result = parseEthersParams(param.components); + names.push({ name: (param.name || null), names: result.names }); + types.push('tuple(' + result.types.join(',') + ')' + suffix); + } else { + names.push(param.name || null); + types.push(param.type); + } + }); + + return { + names, + types, + }; +} + +// returns true if x is equal to y and false otherwise. Performs some minimal +// type conversion and data massaging for x and y, depending on type. name and +// type should typically be derived from parseEthersParams. +function isAbiDataEqual(name: EthersParamName, type: string, x: any, y: any): boolean { + if (_.isUndefined(x) && _.isUndefined(y)) { + return true; + } else if (_.isUndefined(x) && !_.isUndefined(y)) { + return false; + } else if (!_.isUndefined(x) && _.isUndefined(y)) { + return false; + } + if (_.endsWith(type, '[]')) { + // For array types, we iterate through the elements and check each one + // individually. Strangely, name does not need to be changed in this + // case. + if (x.length !== y.length) { + return false; + } + const newType = _.trimEnd(type, '[]'); + for (let i = 0; i < x.length; i++) { + if (!isAbiDataEqual(name, newType, x[i], y[i])) { + return false; + } + } + return true; + } + if (_.startsWith(type, 'tuple(')) { + if (_.isString(name)) { + throw new Error('Internal error: type was tuple but names was a string'); + } else if (_.isNull(name)) { + throw new Error('Internal error: type was tuple but names was a null'); + } + // For tuples, we iterate through the underlying values and check each + // one individually. + const types = splitTupleTypes(type); + if (types.length !== name.names.length) { + throw new Error(`Internal error: parameter types/names length mismatch (${types.length} != ${name.names.length})`); + } + for (let i = 0; i < types.length; i++) { + // For tuples, name is an object with a names property that is an + // array. As an example, for orders, name looks like: + // + // { + // name: 'orders', + // names: [ + // 'makerAddress', + // // ... + // 'takerAssetData' + // ] + // } + // + const nestedName = _.isString(name.names[i]) ? name.names[i] as string : (name.names[i] as EthersNestedParamName).name as string; + if (!isAbiDataEqual(name.names[i], types[i], x[nestedName], y[nestedName])) { + return false; + } + } + return true; + } else if (type === 'address' || type === 'bytes') { + // HACK(albrow): ethers.js sometimes changes the case of addresses/bytes + // when decoding/encoding. To account for that, we convert to lowercase + // before comparing. + return _.isEqual(_.toLower(x), _.toLower(y)); + } else if (_.startsWith(type, 'uint') || _.startsWith(type, 'int')) { + return new BigNumber(x).eq(new BigNumber(y)); + } + return _.isEqual(x, y); +} + +// splitTupleTypes splits a tuple type string (of the form `tuple(X)` where X is +// any other type or list of types) into its component types. It works with +// nested tuples, so, e.g., `tuple(tuple(uint256,address),bytes32)` will yield: +// `['tuple(uint256,address)', 'bytes32']`. It expects exactly one tuple type as +// an argument (not an array). +function splitTupleTypes(type: string): string[] { + if (_.endsWith(type, '[]')) { + throw new Error('Internal error: array types are not supported'); + } else if (!_.startsWith(type, 'tuple(')) { + throw new Error('Internal error: expected tuple type but got non-tuple type: ' + type); + } + // Trim the outtermost tuple(). + const trimmedType = type.substring('tuple('.length, type.length - 1); + const types: string[] = []; + let currToken = ''; + let parenCount = 0; + // Tokenize the type string while keeping track of parentheses. + for (const char of trimmedType) { + switch (char) { + case '(': + parenCount += 1; + currToken += char; + break; + case ')': + parenCount -= 1; + currToken += char; + break; + case ',': + if (parenCount === 0) { + types.push(currToken); + currToken = ''; + break; + } else { + currToken += char; + break; + } + default: + currToken += char; + break; + } + } + types.push(currToken); + return types; +} + export const abiUtils = { + parseEthersParams, + isAbiDataEqual, + splitTupleTypes, parseFunctionParam(param: DataItem): string { if (param.type === 'tuple') { // Parse out tuple types into {type_1, type_2, ..., type_N} diff --git a/packages/utils/test/abi_utils_test.ts b/packages/utils/test/abi_utils_test.ts new file mode 100644 index 000000000..0ebee64c4 --- /dev/null +++ b/packages/utils/test/abi_utils_test.ts @@ -0,0 +1,19 @@ +import * as chai from 'chai'; +import 'mocha'; + +import { abiUtils } from '../src'; + +const expect = chai.expect; + +describe('abiUtils', () => { + describe('splitTupleTypes', () => { + it('handles basic types', () => { + const got = abiUtils.splitTupleTypes('tuple(bytes,uint256,address)'); + expect(got).to.deep.equal(['bytes', 'uint256', 'address']); + }); + it('handles nested tuple types', () => { + const got = abiUtils.splitTupleTypes('tuple(tuple(bytes,uint256),address)'); + expect(got).to.deep.equal(['tuple(bytes,uint256)', 'address']); + }); + }); +}); diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json index c56d255d5..852708eba 100644 --- a/packages/utils/tsconfig.json +++ b/packages/utils/tsconfig.json @@ -3,5 +3,8 @@ "compilerOptions": { "outDir": "lib" }, - "include": ["./src/**/*"] + "include": [ + "src/**/*", + "test/**/*" + ] } -- cgit From 6a6739ebbec291b61226c047fde7b3d0bb4a7250 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Thu, 26 Jul 2018 14:32:52 -0700 Subject: Apply prettier --- packages/0x.js/test/0x.js_test.ts | 6 ++--- packages/base-contract/src/index.ts | 6 ++++- packages/base-contract/test/base_contract_test.ts | 30 +++++++++++++---------- packages/utils/src/abi_utils.ts | 14 ++++++++--- packages/utils/tsconfig.json | 5 +--- 5 files changed, 36 insertions(+), 25 deletions(-) diff --git a/packages/0x.js/test/0x.js_test.ts b/packages/0x.js/test/0x.js_test.ts index 2c1cb2c74..be2a94482 100644 --- a/packages/0x.js/test/0x.js_test.ts +++ b/packages/0x.js/test/0x.js_test.ts @@ -50,9 +50,9 @@ describe('ZeroEx library', () => { const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; const bytes32Zeros = '0x0000000000000000000000000000000000000000000000000000000000000000'; it("should return false if the data doesn't pertain to the signature & address", async () => { - return expect((zeroEx.exchange as any).isValidSignatureAsync(bytes32Zeros, address, ethSignSignature)).to.become( - false, - ); + return expect( + (zeroEx.exchange as any).isValidSignatureAsync(bytes32Zeros, address, ethSignSignature), + ).to.become(false); }); it("should return false if the address doesn't pertain to the signature & data", async () => { const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42'; diff --git a/packages/base-contract/src/index.ts b/packages/base-contract/src/index.ts index e354f109e..31d2e4019 100644 --- a/packages/base-contract/src/index.ts +++ b/packages/base-contract/src/index.ts @@ -95,7 +95,11 @@ export class BaseContract { const original = args[i]; const decoded = rawDecoded[i]; if (!abiUtils.isAbiDataEqual(params.names[i], params.types[i], original, decoded)) { - throw new Error(`Cannot safely encode argument: ${params.names[i]} (${original}) of type ${params.types[i]}. (Possible type overflow or other encoding error)`); + throw new Error( + `Cannot safely encode argument: ${params.names[i]} (${original}) of type ${ + params.types[i] + }. (Possible type overflow or other encoding error)`, + ); } } } diff --git a/packages/base-contract/test/base_contract_test.ts b/packages/base-contract/test/base_contract_test.ts index 57aacdc8a..2c31d1f11 100644 --- a/packages/base-contract/test/base_contract_test.ts +++ b/packages/base-contract/test/base_contract_test.ts @@ -8,20 +8,20 @@ const { expect } = chai; describe('BaseContract', () => { describe('strictArgumentEncodingCheck', () => { it('works for simple types', () => { - BaseContract.strictArgumentEncodingCheck([{ name: 'to', type: 'address' }], ['0xe834ec434daba538cd1b9fe1582052b880bd7e63']); + BaseContract.strictArgumentEncodingCheck( + [{ name: 'to', type: 'address' }], + ['0xe834ec434daba538cd1b9fe1582052b880bd7e63'], + ); }); it('works for array types', () => { - const inputAbi = [{ - name: 'takerAssetFillAmounts', - type: 'uint256[]', - }]; + const inputAbi = [ + { + name: 'takerAssetFillAmounts', + type: 'uint256[]', + }, + ]; const args = [ - [ - '9000000000000000000', - '79000000000000000000', - '979000000000000000000', - '7979000000000000000000', - ], + ['9000000000000000000', '79000000000000000000', '979000000000000000000', '7979000000000000000000'], ]; BaseContract.strictArgumentEncodingCheck(inputAbi, args); }); @@ -101,10 +101,14 @@ describe('BaseContract', () => { BaseContract.strictArgumentEncodingCheck(inputAbi, args); }); it('throws for integer overflows', () => { - expect(() => BaseContract.strictArgumentEncodingCheck([{ name: 'amount', type: 'uint8' }], ['256'])).to.throw(); + expect(() => + BaseContract.strictArgumentEncodingCheck([{ name: 'amount', type: 'uint8' }], ['256']), + ).to.throw(); }); it('throws for fixed byte array overflows', () => { - expect(() => BaseContract.strictArgumentEncodingCheck([{ name: 'hash', type: 'bytes8' }], ['0x001122334455667788'])).to.throw(); + expect(() => + BaseContract.strictArgumentEncodingCheck([{ name: 'hash', type: 'bytes8' }], ['0x001122334455667788']), + ).to.throw(); }); }); }); diff --git a/packages/utils/src/abi_utils.ts b/packages/utils/src/abi_utils.ts index 16aa72afd..874e0b2da 100644 --- a/packages/utils/src/abi_utils.ts +++ b/packages/utils/src/abi_utils.ts @@ -21,10 +21,12 @@ function parseEthersParams(params: DataItem[]): { names: EthersParamName[]; type if (param.components != null) { let suffix = ''; const arrayBracket = param.type.indexOf('['); - if (arrayBracket >= 0) { suffix = param.type.substring(arrayBracket); } + if (arrayBracket >= 0) { + suffix = param.type.substring(arrayBracket); + } const result = parseEthersParams(param.components); - names.push({ name: (param.name || null), names: result.names }); + names.push({ name: param.name || null, names: result.names }); types.push('tuple(' + result.types.join(',') + ')' + suffix); } else { names.push(param.name || null); @@ -74,7 +76,9 @@ function isAbiDataEqual(name: EthersParamName, type: string, x: any, y: any): bo // one individually. const types = splitTupleTypes(type); if (types.length !== name.names.length) { - throw new Error(`Internal error: parameter types/names length mismatch (${types.length} != ${name.names.length})`); + throw new Error( + `Internal error: parameter types/names length mismatch (${types.length} != ${name.names.length})`, + ); } for (let i = 0; i < types.length; i++) { // For tuples, name is an object with a names property that is an @@ -89,7 +93,9 @@ function isAbiDataEqual(name: EthersParamName, type: string, x: any, y: any): bo // ] // } // - const nestedName = _.isString(name.names[i]) ? name.names[i] as string : (name.names[i] as EthersNestedParamName).name as string; + const nestedName = _.isString(name.names[i]) + ? (name.names[i] as string) + : ((name.names[i] as EthersNestedParamName).name as string); if (!isAbiDataEqual(name.names[i], types[i], x[nestedName], y[nestedName])) { return false; } diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json index 852708eba..8b4cd47a2 100644 --- a/packages/utils/tsconfig.json +++ b/packages/utils/tsconfig.json @@ -3,8 +3,5 @@ "compilerOptions": { "outDir": "lib" }, - "include": [ - "src/**/*", - "test/**/*" - ] + "include": ["src/**/*", "test/**/*"] } -- cgit From 52e094addcecc6136c9582a51dd52b8f44f769f7 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Mon, 6 Aug 2018 16:58:46 -0700 Subject: Move some ethers-related types to typescript-typings/ethers --- packages/base-contract/src/index.ts | 2 +- packages/typescript-typings/types/ethers/index.d.ts | 18 ++++++++++++++++++ packages/utils/src/abi_utils.ts | 18 ++++++------------ 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/base-contract/src/index.ts b/packages/base-contract/src/index.ts index 31d2e4019..12f974445 100644 --- a/packages/base-contract/src/index.ts +++ b/packages/base-contract/src/index.ts @@ -87,7 +87,7 @@ export class BaseContract { // if it overflows the corresponding Solidity type, there is a bug in the // encoder, or the encoder performs unsafe type coercion. public static strictArgumentEncodingCheck(inputAbi: DataItem[], args: any[]): void { - const coder = (ethers as any).utils.AbiCoder.defaultCoder; + const coder = ethers.utils.AbiCoder.defaultCoder; const params = abiUtils.parseEthersParams(inputAbi); const rawEncoded = coder.encode(params.names, params.types, args); const rawDecoded = coder.decode(params.names, params.types, rawEncoded); diff --git a/packages/typescript-typings/types/ethers/index.d.ts b/packages/typescript-typings/types/ethers/index.d.ts index f869196e0..58bc1e8a9 100644 --- a/packages/typescript-typings/types/ethers/index.d.ts +++ b/packages/typescript-typings/types/ethers/index.d.ts @@ -34,4 +34,22 @@ declare module 'ethers' { const enum errors { INVALID_ARGUMENT = 'INVALID_ARGUMENT', } + + export type ParamName = null | string | NestedParamName; + + export interface NestedParamName { + name: string | null; + names: ParamName[]; + } + + export const utils: { + AbiCoder: { + defaultCoder: AbiCoder; + }; + }; + + export interface AbiCoder { + encode: (names?: ParamName[], types: string[], args: any[]) => string; + decode: (names?: ParamName[], types: string[], data: string) => any; + } } diff --git a/packages/utils/src/abi_utils.ts b/packages/utils/src/abi_utils.ts index 874e0b2da..fc64a2a89 100644 --- a/packages/utils/src/abi_utils.ts +++ b/packages/utils/src/abi_utils.ts @@ -1,20 +1,14 @@ import { AbiDefinition, AbiType, ContractAbi, DataItem, MethodAbi } from 'ethereum-types'; +import * as ethers from 'ethers'; import * as _ from 'lodash'; import { BigNumber } from './configured_bignumber'; -export type EthersParamName = null | string | EthersNestedParamName; - -export interface EthersNestedParamName { - name: string | null; - names: EthersParamName[]; -} - // Note(albrow): This function is unexported in ethers.js. Copying it here for // now. // Source: https://github.com/ethers-io/ethers.js/blob/884593ab76004a808bf8097e9753fb5f8dcc3067/contracts/interface.js#L30 -function parseEthersParams(params: DataItem[]): { names: EthersParamName[]; types: string[] } { - const names: EthersParamName[] = []; +function parseEthersParams(params: DataItem[]): { names: ethers.ParamName[]; types: string[] } { + const names: ethers.ParamName[] = []; const types: string[] = []; params.forEach((param: DataItem) => { @@ -43,7 +37,7 @@ function parseEthersParams(params: DataItem[]): { names: EthersParamName[]; type // returns true if x is equal to y and false otherwise. Performs some minimal // type conversion and data massaging for x and y, depending on type. name and // type should typically be derived from parseEthersParams. -function isAbiDataEqual(name: EthersParamName, type: string, x: any, y: any): boolean { +function isAbiDataEqual(name: ethers.ParamName, type: string, x: any, y: any): boolean { if (_.isUndefined(x) && _.isUndefined(y)) { return true; } else if (_.isUndefined(x) && !_.isUndefined(y)) { @@ -70,7 +64,7 @@ function isAbiDataEqual(name: EthersParamName, type: string, x: any, y: any): bo if (_.isString(name)) { throw new Error('Internal error: type was tuple but names was a string'); } else if (_.isNull(name)) { - throw new Error('Internal error: type was tuple but names was a null'); + throw new Error('Internal error: type was tuple but names was null'); } // For tuples, we iterate through the underlying values and check each // one individually. @@ -95,7 +89,7 @@ function isAbiDataEqual(name: EthersParamName, type: string, x: any, y: any): bo // const nestedName = _.isString(name.names[i]) ? (name.names[i] as string) - : ((name.names[i] as EthersNestedParamName).name as string); + : ((name.names[i] as ethers.NestedParamName).name as string); if (!isAbiDataEqual(name.names[i], types[i], x[nestedName], y[nestedName])) { return false; } -- cgit From 09af23f950b6143cc3aec3c5843963e70d2a5b5c Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Tue, 7 Aug 2018 11:01:41 -0700 Subject: Update CHANGELOG.json for base-contract --- packages/base-contract/CHANGELOG.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/base-contract/CHANGELOG.json b/packages/base-contract/CHANGELOG.json index 9dddde18a..b041e13da 100644 --- a/packages/base-contract/CHANGELOG.json +++ b/packages/base-contract/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "2.0.0-rc.1", + "changes": [ + { + "pr": 915, + "note": "Added strict encoding/decoding checks for sendTransaction and call" + } + ] + }, { "timestamp": 1532619515, "version": "1.0.4", -- cgit From 44b01f2069c10e84fc7fd55dc24767dfe877321c Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Wed, 8 Aug 2018 14:52:05 -0700 Subject: Update ethers typings for TypeScript 2.9.2 --- packages/typescript-typings/types/ethers/index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/typescript-typings/types/ethers/index.d.ts b/packages/typescript-typings/types/ethers/index.d.ts index 58bc1e8a9..875563ba2 100644 --- a/packages/typescript-typings/types/ethers/index.d.ts +++ b/packages/typescript-typings/types/ethers/index.d.ts @@ -49,7 +49,7 @@ declare module 'ethers' { }; export interface AbiCoder { - encode: (names?: ParamName[], types: string[], args: any[]) => string; - decode: (names?: ParamName[], types: string[], data: string) => any; + encode: (names: ParamName[] | string[], types: string[] | any[], args: any[] | undefined) => string; + decode: (names: ParamName[] | string[], types: string[] | string, data: string | undefined) => any; } } -- cgit From 44d909c0c789a5b3fba2393746593d39c0ed1df7 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Wed, 8 Aug 2018 14:54:35 -0700 Subject: Update CHANGELOG.json for contract-wrappers --- packages/contract-wrappers/CHANGELOG.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index 02e1acf91..fb077ce73 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -2,6 +2,10 @@ { "version": "1.0.1-rc.3", "changes": [ + { + "pr": 915, + "note": "Added strict encoding/decoding checks for sendTransaction and call" + }, { "note": "Add ForwarderWrapper", "pr": 934 -- cgit From 2a85f79040117cbcf58a03fb66dbd67f83b257d3 Mon Sep 17 00:00:00 2001 From: fragosti Date: Wed, 8 Aug 2018 15:42:09 -0700 Subject: Change amir picture --- packages/website/public/images/team/amir.jpeg | Bin 21098 -> 0 bytes packages/website/public/images/team/amir.png | Bin 0 -> 116488 bytes packages/website/ts/pages/about/about.tsx | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 packages/website/public/images/team/amir.jpeg create mode 100644 packages/website/public/images/team/amir.png diff --git a/packages/website/public/images/team/amir.jpeg b/packages/website/public/images/team/amir.jpeg deleted file mode 100644 index 7ee16263a..000000000 Binary files a/packages/website/public/images/team/amir.jpeg and /dev/null differ diff --git a/packages/website/public/images/team/amir.png b/packages/website/public/images/team/amir.png new file mode 100644 index 000000000..2bb795d50 Binary files /dev/null and b/packages/website/public/images/team/amir.png differ diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx index b9bc906bd..1574f65e0 100644 --- a/packages/website/ts/pages/about/about.tsx +++ b/packages/website/ts/pages/about/about.tsx @@ -27,7 +27,7 @@ const teamRow1: ProfileInfo[] = [ title: 'Co-founder & CTO', description: `Smart contract R&D. Previously fixed income trader at DRW. \ Finance at University of Illinois, Urbana-Champaign.`, - image: '/images/team/amir.jpeg', + image: '/images/team/amir.png', linkedIn: 'https://www.linkedin.com/in/abandeali1/', github: 'https://github.com/abandeali1', medium: 'https://medium.com/@abandeali1', -- cgit From 762bbe9bcd79ca5b832859cc20ffa4c83603cbcb Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Wed, 8 Aug 2018 16:44:52 -0700 Subject: Update remaining CHANGELOG.json files --- packages/0x.js/CHANGELOG.json | 8 ++++++++ packages/fill-scenarios/CHANGELOG.json | 3 +++ packages/order-utils/CHANGELOG.json | 3 +++ packages/order-watcher/CHANGELOG.json | 8 ++++++++ 4 files changed, 22 insertions(+) diff --git a/packages/0x.js/CHANGELOG.json b/packages/0x.js/CHANGELOG.json index 4f39a9b8c..913b7a76e 100644 --- a/packages/0x.js/CHANGELOG.json +++ b/packages/0x.js/CHANGELOG.json @@ -1,4 +1,12 @@ [ + { + "version": "1.0.1-rc.3", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "1.0.1-rc.2", "changes": [ diff --git a/packages/fill-scenarios/CHANGELOG.json b/packages/fill-scenarios/CHANGELOG.json index 04e076203..1c3864da2 100644 --- a/packages/fill-scenarios/CHANGELOG.json +++ b/packages/fill-scenarios/CHANGELOG.json @@ -6,6 +6,9 @@ "note": "Updated to use latest orderFactory interface, fixed `feeRecipient` spelling error in public interface", "pr": 936 + }, + { + "note": "Dependencies updated" } ] }, diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index 776bd67ec..fa82976ad 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -10,6 +10,9 @@ { "note": "Added marketUtils", "pr": 937 + }, + { + "note": "Dependencies updated" } ] }, diff --git a/packages/order-watcher/CHANGELOG.json b/packages/order-watcher/CHANGELOG.json index 08c1f7f58..0c9ef7a8a 100644 --- a/packages/order-watcher/CHANGELOG.json +++ b/packages/order-watcher/CHANGELOG.json @@ -1,4 +1,12 @@ [ + { + "version": "1.0.1-rc.3", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "1.0.1-rc.2", "changes": [ -- cgit From ca7d8a8940df23e56fb034878939112bc359cb2f Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Wed, 8 Aug 2018 16:47:36 -0700 Subject: Update CI config and package.json to run @0xproject/utils tests on CI --- .circleci/config.yml | 1 + packages/utils/package.json | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b93f62fa7..43e542a86 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -80,6 +80,7 @@ jobs: - run: yarn wsrun test:circleci @0xproject/sra-report - run: yarn wsrun test:circleci @0xproject/subproviders - run: yarn wsrun test:circleci @0xproject/web3-wrapper + - run: yarn wsrun test:circleci @0xproject/utils - save_cache: key: coverage-0xjs-{{ .Environment.CIRCLE_SHA1 }} paths: diff --git a/packages/utils/package.json b/packages/utils/package.json index 46c1d05d0..ee5fc264f 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -11,7 +11,11 @@ "watch_without_deps": "tsc -w", "build": "tsc && copyfiles -u 2 './lib/monorepo_scripts/**/*' ./scripts", "clean": "shx rm -rf lib scripts", - "test": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --bail --exit", + "test": "yarn run_mocha", + "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", "lint": "tslint --project .", "manual:postpublish": "yarn build; node ./scripts/postpublish.js" }, -- cgit From 5b7774f9d00a0f80601e6ff4ed0920c6a150a350 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Wed, 8 Aug 2018 17:33:20 -0700 Subject: Add packages/coverage/.gitkeep file --- packages/utils/coverage/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/utils/coverage/.gitkeep diff --git a/packages/utils/coverage/.gitkeep b/packages/utils/coverage/.gitkeep new file mode 100644 index 000000000..e69de29bb -- cgit From c4c37cafa0d8a77bfdc01b1cc111ba0101e86c8b Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Wed, 8 Aug 2018 17:58:04 -0700 Subject: Update comment about ethers checksummed address behavior --- packages/utils/src/abi_utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/utils/src/abi_utils.ts b/packages/utils/src/abi_utils.ts index fc64a2a89..c9b70966c 100644 --- a/packages/utils/src/abi_utils.ts +++ b/packages/utils/src/abi_utils.ts @@ -96,9 +96,9 @@ function isAbiDataEqual(name: ethers.ParamName, type: string, x: any, y: any): b } return true; } else if (type === 'address' || type === 'bytes') { - // HACK(albrow): ethers.js sometimes changes the case of addresses/bytes - // when decoding/encoding. To account for that, we convert to lowercase - // before comparing. + // HACK(albrow): ethers.js returns the checksummed address even when + // initially passed in a non-checksummed address. To account for that, + // we convert to lowercase before comparing. return _.isEqual(_.toLower(x), _.toLower(y)); } else if (_.startsWith(type, 'uint') || _.startsWith(type, 'int')) { return new BigNumber(x).eq(new BigNumber(y)); -- cgit From 68605ca261330acaf83daaf355170fbda20ad02f Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 9 Aug 2018 17:00:05 +0200 Subject: Import marshaller directly --- packages/subproviders/src/subproviders/signer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/subproviders/src/subproviders/signer.ts b/packages/subproviders/src/subproviders/signer.ts index 8c4e89caf..d5fd86897 100644 --- a/packages/subproviders/src/subproviders/signer.ts +++ b/packages/subproviders/src/subproviders/signer.ts @@ -1,5 +1,4 @@ -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import { marshaller } from '@0xproject/web3-wrapper/lib/src/marshaller'; +import { marshaller, Web3Wrapper } from '@0xproject/web3-wrapper'; import { JSONRPCRequestPayload, Provider } from 'ethereum-types'; import { Callback, ErrorCallback } from '../types'; -- cgit From d44ff6a91582ed2b4dc25059d52556c4f9c6e163 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 9 Aug 2018 17:02:13 +0200 Subject: Add @return comments --- packages/web3-wrapper/src/marshaller.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/web3-wrapper/src/marshaller.ts b/packages/web3-wrapper/src/marshaller.ts index 7d254b49e..572a322d6 100644 --- a/packages/web3-wrapper/src/marshaller.ts +++ b/packages/web3-wrapper/src/marshaller.ts @@ -32,6 +32,7 @@ export const marshaller = { /** * Unmarshall block without transaction data * @param blockWithHexValues block to unmarshall + * @return unmarshalled block without transaction data */ unmarshalIntoBlockWithoutTransactionData( blockWithHexValues: BlockWithoutTransactionDataRPC, @@ -51,6 +52,7 @@ export const marshaller = { /** * Unmarshall block with transaction data * @param blockWithHexValues block to unmarshall + * @return unmarshalled block with transaction data */ unmarshalIntoBlockWithTransactionData(blockWithHexValues: BlockWithTransactionDataRPC): BlockWithTransactionData { const block = { @@ -73,6 +75,7 @@ export const marshaller = { /** * Unmarshall transaction * @param txRpc transaction to unmarshall + * @return unmarshalled transaction */ unmarshalTransaction(txRpc: TransactionRPC): Transaction { const tx = { @@ -91,6 +94,7 @@ export const marshaller = { /** * Unmarshall transaction data * @param txDataRpc transaction data to unmarshall + * @return unmarshalled transaction data */ unmarshalTxData(txDataRpc: TxDataRPC): TxData { if (_.isUndefined(txDataRpc.from)) { @@ -108,6 +112,7 @@ export const marshaller = { /** * Marshall transaction data * @param txData transaction data to marshall + * @return marshalled transaction data */ marshalTxData(txData: Partial): Partial { if (_.isUndefined(txData.from)) { @@ -133,6 +138,7 @@ export const marshaller = { /** * Marshall call data * @param callData call data to marshall + * @return marshalled call data */ marshalCallData(callData: Partial): Partial { const callTxDataBase = { @@ -149,6 +155,7 @@ export const marshaller = { /** * Marshall address * @param address address to marshall + * @return marshalled address */ marshalAddress(address: string): string { if (addressUtils.isAddress(address)) { @@ -159,6 +166,7 @@ export const marshaller = { /** * Marshall block param * @param blockParam block param to marshall + * @return marshalled block param */ marshalBlockParam(blockParam: BlockParam | string | number | undefined): string | undefined { if (_.isUndefined(blockParam)) { @@ -170,6 +178,7 @@ export const marshaller = { /** * Unmarshall log * @param rawLog log to unmarshall + * @return unmarshalled log */ unmarshalLog(rawLog: RawLogEntry): LogEntry { const formattedLog = { -- cgit