From 9164d58dc7c5b8df74dfc0db9b11359145415056 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Tue, 19 Mar 2019 16:45:11 +0100 Subject: [PATCH 01/23] Simulate maker transfer in order validation --- packages/abi-gen-wrappers/package.json | 2 +- .../src/generated-wrappers/i_asset_proxy.ts | 534 ++++++++++++++++++ packages/abi-gen-wrappers/src/index.ts | 1 + .../artifacts/IAssetProxy.json | 185 ++++++ packages/contract-artifacts/src/index.ts | 2 + packages/contract-artifacts/tsconfig.json | 1 + .../src/contract_wrappers/exchange_wrapper.ts | 70 ++- .../test/exchange_wrapper_test.ts | 3 + packages/order-utils/src/index.ts | 2 + .../src/order_calculation_utils.ts | 110 ++++ packages/order-utils/src/types.ts | 5 + 11 files changed, 912 insertions(+), 3 deletions(-) create mode 100644 packages/abi-gen-wrappers/src/generated-wrappers/i_asset_proxy.ts create mode 100644 packages/contract-artifacts/artifacts/IAssetProxy.json create mode 100644 packages/order-utils/src/order_calculation_utils.ts diff --git a/packages/abi-gen-wrappers/package.json b/packages/abi-gen-wrappers/package.json index 527540a1d5..e21122fe1d 100644 --- a/packages/abi-gen-wrappers/package.json +++ b/packages/abi-gen-wrappers/package.json @@ -18,7 +18,7 @@ "generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../../node_modules/@0x/abi-gen-templates/contract.handlebars --partials '../../node_modules/@0x/abi-gen-templates/partials/**/*.handlebars' --output src/generated-wrappers --backend ethers" }, "config": { - "abis": "../contract-artifacts/artifacts/@(AssetProxyOwner|DutchAuction|DummyERC20Token|DummyERC721Token|ERC20Proxy|ERC20Token|ERC721Proxy|ERC721Token|Exchange|Forwarder|IValidator|IWallet|MultiAssetProxy|OrderValidator|WETH9|ZRXToken|Coordinator|CoordinatorRegistry).json" + "abis": "../contract-artifacts/artifacts/@(AssetProxyOwner|DutchAuction|DummyERC20Token|DummyERC721Token|ERC20Proxy|ERC20Token|ERC721Proxy|ERC721Token|Exchange|Forwarder|IAssetProxy|IValidator|IWallet|MultiAssetProxy|OrderValidator|WETH9|ZRXToken|Coordinator|CoordinatorRegistry).json" }, "repository": { "type": "git", diff --git a/packages/abi-gen-wrappers/src/generated-wrappers/i_asset_proxy.ts b/packages/abi-gen-wrappers/src/generated-wrappers/i_asset_proxy.ts new file mode 100644 index 0000000000..08a1c454db --- /dev/null +++ b/packages/abi-gen-wrappers/src/generated-wrappers/i_asset_proxy.ts @@ -0,0 +1,534 @@ +// tslint:disable:no-consecutive-blank-lines ordered-imports align trailing-comma whitespace class-name +// tslint:disable:no-unused-variable +// tslint:disable:no-unbound-method +import { BaseContract } from '@0x/base-contract'; +import { BlockParam, BlockParamLiteral, CallData, ContractAbi, ContractArtifact, DecodedLogArgs, MethodAbi, TxData, TxDataPayable, SupportedProvider } from 'ethereum-types'; +import { BigNumber, classUtils, logUtils, providerUtils } from '@0x/utils'; +import { SimpleContractArtifact } from '@0x/types'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as ethers from 'ethers'; +import * as _ from 'lodash'; +// tslint:enable:no-unused-variable + + +/* istanbul ignore next */ +// tslint:disable:no-parameter-reassignment +// tslint:disable-next-line:class-name +export class IAssetProxyContract extends BaseContract { + public addAuthorizedAddress = { + async sendTransactionAsync( + target: string, + txData: Partial = {}, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('addAuthorizedAddress(address)', [target + ]); + const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...txData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + self.addAuthorizedAddress.estimateGasAsync.bind( + self, + target + ), + ); + const txHash = await self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); + return txHash; + }, + async estimateGasAsync( + target: string, + txData: Partial = {}, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('addAuthorizedAddress(address)', [target + ]); + const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...txData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + ); + const gas = await self._web3Wrapper.estimateGasAsync(txDataWithDefaults); + return gas; + }, + getABIEncodedTransactionData( + target: string, + ): string { + const self = this as any as IAssetProxyContract; + const abiEncodedTransactionData = self._strictEncodeArguments('addAuthorizedAddress(address)', [target + ]); + return abiEncodedTransactionData; + }, + async callAsync( + target: string, + callData: Partial = {}, + defaultBlock?: BlockParam, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('addAuthorizedAddress(address)', [target + ]); + const callDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...callData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + ); + const rawCallResult = await self._web3Wrapper.callAsync(callDataWithDefaults, defaultBlock); + BaseContract._throwIfRevertWithReasonCallResult(rawCallResult); + const abiEncoder = self._lookupAbiEncoder('addAuthorizedAddress(address)'); + // tslint:disable boolean-naming + const result = abiEncoder.strictDecodeReturnValue(rawCallResult); + // tslint:enable boolean-naming + return result; + }, + }; + public removeAuthorizedAddress = { + async sendTransactionAsync( + target: string, + txData: Partial = {}, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('removeAuthorizedAddress(address)', [target + ]); + const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...txData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + self.removeAuthorizedAddress.estimateGasAsync.bind( + self, + target + ), + ); + const txHash = await self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); + return txHash; + }, + async estimateGasAsync( + target: string, + txData: Partial = {}, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('removeAuthorizedAddress(address)', [target + ]); + const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...txData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + ); + const gas = await self._web3Wrapper.estimateGasAsync(txDataWithDefaults); + return gas; + }, + getABIEncodedTransactionData( + target: string, + ): string { + const self = this as any as IAssetProxyContract; + const abiEncodedTransactionData = self._strictEncodeArguments('removeAuthorizedAddress(address)', [target + ]); + return abiEncodedTransactionData; + }, + async callAsync( + target: string, + callData: Partial = {}, + defaultBlock?: BlockParam, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('removeAuthorizedAddress(address)', [target + ]); + const callDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...callData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + ); + const rawCallResult = await self._web3Wrapper.callAsync(callDataWithDefaults, defaultBlock); + BaseContract._throwIfRevertWithReasonCallResult(rawCallResult); + const abiEncoder = self._lookupAbiEncoder('removeAuthorizedAddress(address)'); + // tslint:disable boolean-naming + const result = abiEncoder.strictDecodeReturnValue(rawCallResult); + // tslint:enable boolean-naming + return result; + }, + }; + public removeAuthorizedAddressAtIndex = { + async sendTransactionAsync( + target: string, + index: BigNumber, + txData: Partial = {}, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('removeAuthorizedAddressAtIndex(address,uint256)', [target, + index + ]); + const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...txData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + self.removeAuthorizedAddressAtIndex.estimateGasAsync.bind( + self, + target, + index + ), + ); + const txHash = await self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); + return txHash; + }, + async estimateGasAsync( + target: string, + index: BigNumber, + txData: Partial = {}, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('removeAuthorizedAddressAtIndex(address,uint256)', [target, + index + ]); + const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...txData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + ); + const gas = await self._web3Wrapper.estimateGasAsync(txDataWithDefaults); + return gas; + }, + getABIEncodedTransactionData( + target: string, + index: BigNumber, + ): string { + const self = this as any as IAssetProxyContract; + const abiEncodedTransactionData = self._strictEncodeArguments('removeAuthorizedAddressAtIndex(address,uint256)', [target, + index + ]); + return abiEncodedTransactionData; + }, + async callAsync( + target: string, + index: BigNumber, + callData: Partial = {}, + defaultBlock?: BlockParam, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('removeAuthorizedAddressAtIndex(address,uint256)', [target, + index + ]); + const callDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...callData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + ); + const rawCallResult = await self._web3Wrapper.callAsync(callDataWithDefaults, defaultBlock); + BaseContract._throwIfRevertWithReasonCallResult(rawCallResult); + const abiEncoder = self._lookupAbiEncoder('removeAuthorizedAddressAtIndex(address,uint256)'); + // tslint:disable boolean-naming + const result = abiEncoder.strictDecodeReturnValue(rawCallResult); + // tslint:enable boolean-naming + return result; + }, + }; + public transferFrom = { + async sendTransactionAsync( + assetData: string, + from: string, + to: string, + amount: BigNumber, + txData: Partial = {}, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('transferFrom(bytes,address,address,uint256)', [assetData, + from, + to, + amount + ]); + const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...txData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + self.transferFrom.estimateGasAsync.bind( + self, + assetData, + from, + to, + amount + ), + ); + const txHash = await self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); + return txHash; + }, + async estimateGasAsync( + assetData: string, + from: string, + to: string, + amount: BigNumber, + txData: Partial = {}, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('transferFrom(bytes,address,address,uint256)', [assetData, + from, + to, + amount + ]); + const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...txData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + ); + const gas = await self._web3Wrapper.estimateGasAsync(txDataWithDefaults); + return gas; + }, + getABIEncodedTransactionData( + assetData: string, + from: string, + to: string, + amount: BigNumber, + ): string { + const self = this as any as IAssetProxyContract; + const abiEncodedTransactionData = self._strictEncodeArguments('transferFrom(bytes,address,address,uint256)', [assetData, + from, + to, + amount + ]); + return abiEncodedTransactionData; + }, + async callAsync( + assetData: string, + from: string, + to: string, + amount: BigNumber, + callData: Partial = {}, + defaultBlock?: BlockParam, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('transferFrom(bytes,address,address,uint256)', [assetData, + from, + to, + amount + ]); + const callDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...callData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + ); + const rawCallResult = await self._web3Wrapper.callAsync(callDataWithDefaults, defaultBlock); + BaseContract._throwIfRevertWithReasonCallResult(rawCallResult); + const abiEncoder = self._lookupAbiEncoder('transferFrom(bytes,address,address,uint256)'); + // tslint:disable boolean-naming + const result = abiEncoder.strictDecodeReturnValue(rawCallResult); + // tslint:enable boolean-naming + return result; + }, + }; + public getProxyId = { + async callAsync( + callData: Partial = {}, + defaultBlock?: BlockParam, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('getProxyId()', []); + const callDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...callData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + ); + const rawCallResult = await self._web3Wrapper.callAsync(callDataWithDefaults, defaultBlock); + BaseContract._throwIfRevertWithReasonCallResult(rawCallResult); + const abiEncoder = self._lookupAbiEncoder('getProxyId()'); + // tslint:disable boolean-naming + const result = abiEncoder.strictDecodeReturnValue(rawCallResult); + // tslint:enable boolean-naming + return result; + }, + }; + public getAuthorizedAddresses = { + async callAsync( + callData: Partial = {}, + defaultBlock?: BlockParam, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('getAuthorizedAddresses()', []); + const callDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...callData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + ); + const rawCallResult = await self._web3Wrapper.callAsync(callDataWithDefaults, defaultBlock); + BaseContract._throwIfRevertWithReasonCallResult(rawCallResult); + const abiEncoder = self._lookupAbiEncoder('getAuthorizedAddresses()'); + // tslint:disable boolean-naming + const result = abiEncoder.strictDecodeReturnValue(rawCallResult); + // tslint:enable boolean-naming + return result; + }, + }; + public transferOwnership = { + async sendTransactionAsync( + newOwner: string, + txData: Partial = {}, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('transferOwnership(address)', [newOwner + ]); + const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...txData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + self.transferOwnership.estimateGasAsync.bind( + self, + newOwner + ), + ); + const txHash = await self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); + return txHash; + }, + async estimateGasAsync( + newOwner: string, + txData: Partial = {}, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('transferOwnership(address)', [newOwner + ]); + const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...txData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + ); + const gas = await self._web3Wrapper.estimateGasAsync(txDataWithDefaults); + return gas; + }, + getABIEncodedTransactionData( + newOwner: string, + ): string { + const self = this as any as IAssetProxyContract; + const abiEncodedTransactionData = self._strictEncodeArguments('transferOwnership(address)', [newOwner + ]); + return abiEncodedTransactionData; + }, + async callAsync( + newOwner: string, + callData: Partial = {}, + defaultBlock?: BlockParam, + ): Promise { + const self = this as any as IAssetProxyContract; + const encodedData = self._strictEncodeArguments('transferOwnership(address)', [newOwner + ]); + const callDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...callData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + ); + const rawCallResult = await self._web3Wrapper.callAsync(callDataWithDefaults, defaultBlock); + BaseContract._throwIfRevertWithReasonCallResult(rawCallResult); + const abiEncoder = self._lookupAbiEncoder('transferOwnership(address)'); + // tslint:disable boolean-naming + const result = abiEncoder.strictDecodeReturnValue(rawCallResult); + // tslint:enable boolean-naming + return result; + }, + }; + public static async deployFrom0xArtifactAsync( + artifact: ContractArtifact | SimpleContractArtifact, + supportedProvider: SupportedProvider, + txDefaults: Partial, + ): Promise { + if (_.isUndefined(artifact.compilerOutput)) { + throw new Error('Compiler output not found in the artifact file'); + } + const provider = providerUtils.standardizeOrThrow(supportedProvider); + const bytecode = artifact.compilerOutput.evm.bytecode.object; + const abi = artifact.compilerOutput.abi; + return IAssetProxyContract.deployAsync(bytecode, abi, provider, txDefaults, ); + } + public static async deployAsync( + bytecode: string, + abi: ContractAbi, + supportedProvider: SupportedProvider, + txDefaults: Partial, + ): Promise { + const provider = providerUtils.standardizeOrThrow(supportedProvider); + const constructorAbi = BaseContract._lookupConstructorAbi(abi); + [] = BaseContract._formatABIDataItemList( + constructorAbi.inputs, + [], + BaseContract._bigNumberToString, + ); + const iface = new ethers.utils.Interface(abi); + const deployInfo = iface.deployFunction; + const txData = deployInfo.encode(bytecode, []); + const web3Wrapper = new Web3Wrapper(provider); + const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + {data: txData}, + txDefaults, + web3Wrapper.estimateGasAsync.bind(web3Wrapper), + ); + const txHash = await web3Wrapper.sendTransactionAsync(txDataWithDefaults); + logUtils.log(`transactionHash: ${txHash}`); + const txReceipt = await web3Wrapper.awaitTransactionSuccessAsync(txHash); + logUtils.log(`IAssetProxy successfully deployed at ${txReceipt.contractAddress}`); + const contractInstance = new IAssetProxyContract(abi, txReceipt.contractAddress as string, provider, txDefaults); + contractInstance.constructorArgs = []; + return contractInstance; + } + constructor(abi: ContractAbi, address: string, supportedProvider: SupportedProvider, txDefaults?: Partial) { + super('IAssetProxy', abi, address, supportedProvider, txDefaults); + classUtils.bindAll(this, ['_abiEncoderByFunctionSignature', 'address', 'abi', '_web3Wrapper']); + } +} // tslint:disable:max-file-line-count +// tslint:enable:no-unbound-method diff --git a/packages/abi-gen-wrappers/src/index.ts b/packages/abi-gen-wrappers/src/index.ts index 54a41c37c7..00a3cf87c7 100644 --- a/packages/abi-gen-wrappers/src/index.ts +++ b/packages/abi-gen-wrappers/src/index.ts @@ -8,6 +8,7 @@ export * from './generated-wrappers/erc721_proxy'; export * from './generated-wrappers/erc721_token'; export * from './generated-wrappers/exchange'; export * from './generated-wrappers/forwarder'; +export * from './generated-wrappers/i_asset_proxy'; export * from './generated-wrappers/i_validator'; export * from './generated-wrappers/i_wallet'; export * from './generated-wrappers/multi_asset_proxy'; diff --git a/packages/contract-artifacts/artifacts/IAssetProxy.json b/packages/contract-artifacts/artifacts/IAssetProxy.json new file mode 100644 index 0000000000..547bdc938d --- /dev/null +++ b/packages/contract-artifacts/artifacts/IAssetProxy.json @@ -0,0 +1,185 @@ +{ + "schemaVersion": "2.0.0", + "contractName": "IAssetProxy", + "compilerOutput": { + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "addAuthorizedAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "removeAuthorizedAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + }, + { + "name": "index", + "type": "uint256" + } + ], + "name": "removeAuthorizedAddressAtIndex", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "assetData", + "type": "bytes" + }, + { + "name": "from", + "type": "address" + }, + { + "name": "to", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getProxyId", + "outputs": [ + { + "name": "", + "type": "bytes4" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getAuthorizedAddresses", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "evm": { + "bytecode": { + "linkReferences": {}, + "object": "0x", + "opcodes": "", + "sourceMap": "" + }, + "deployedBytecode": { + "linkReferences": {}, + "object": "0x", + "opcodes": "", + "sourceMap": "" + } + } + }, + "sources": { + "src/interfaces/IAssetProxy.sol": { + "id": 2 + }, + "src/interfaces/IAuthorizable.sol": { + "id": 3 + }, + "@0x/contracts-utils/contracts/src/interfaces/IOwnable.sol": { + "id": 6 + } + }, + "sourceCodes": { + "src/interfaces/IAssetProxy.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"./IAuthorizable.sol\";\n\n\ncontract IAssetProxy is\n IAuthorizable\n{\n /// @dev Transfers assets. Either succeeds or throws.\n /// @param assetData Byte array encoded for the respective asset proxy.\n /// @param from Address to transfer asset from.\n /// @param to Address to transfer asset to.\n /// @param amount Amount of asset to transfer.\n function transferFrom(\n bytes assetData,\n address from,\n address to,\n uint256 amount\n )\n external;\n \n /// @dev Gets the proxy id associated with the proxy address.\n /// @return Proxy id.\n function getProxyId()\n external\n pure\n returns (bytes4);\n}\n", + "src/interfaces/IAuthorizable.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-utils/contracts/src/interfaces/IOwnable.sol\";\n\n\ncontract IAuthorizable is\n IOwnable\n{\n /// @dev Authorizes an address.\n /// @param target Address to authorize.\n function addAuthorizedAddress(address target)\n external;\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n function removeAuthorizedAddress(address target)\n external;\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n /// @param index Index of target in authorities array.\n function removeAuthorizedAddressAtIndex(\n address target,\n uint256 index\n )\n external;\n \n /// @dev Gets all authorized addresses.\n /// @return Array of authorized addresses.\n function getAuthorizedAddresses()\n external\n view\n returns (address[] memory);\n}\n", + "@0x/contracts-utils/contracts/src/interfaces/IOwnable.sol": "pragma solidity ^0.4.24;\n\n\ncontract IOwnable {\n\n function transferOwnership(address newOwner)\n public;\n}\n" + }, + "sourceTreeHashHex": "0x4fe33d3b00ae85e5c1d5478eeb1bf17fdc637fa0501dc1ff6fd1f871f87c83ca", + "compiler": { + "name": "solc", + "version": "0.4.25+commit.59dbf8f1.Linux.g++", + "settings": { + "optimizer": { + "enabled": true, + "runs": 1000000, + "details": { + "yul": true, + "deduplicate": true, + "cse": true, + "constantOptimizer": true + } + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + }, + "evmVersion": "byzantium", + "remappings": [ + "@0x/contracts-utils=/Users/jacob/projects/ethdev/0x/0x.js/contracts/asset-proxy/node_modules/@0x/contracts-utils" + ] + } + }, + "networks": {} +} \ No newline at end of file diff --git a/packages/contract-artifacts/src/index.ts b/packages/contract-artifacts/src/index.ts index 6b751222c1..7d7fc3487b 100644 --- a/packages/contract-artifacts/src/index.ts +++ b/packages/contract-artifacts/src/index.ts @@ -10,6 +10,7 @@ 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 IAssetProxy from '../artifacts/IAssetProxy.json'; import * as IValidator from '../artifacts/IValidator.json'; import * as IWallet from '../artifacts/IWallet.json'; import * as MultiAssetProxy from '../artifacts/MultiAssetProxy.json'; @@ -28,6 +29,7 @@ export { ERC721Token, Exchange, Forwarder, + IAssetProxy, IValidator, IWallet, MultiAssetProxy, diff --git a/packages/contract-artifacts/tsconfig.json b/packages/contract-artifacts/tsconfig.json index 022463e771..c10e3b2fe4 100644 --- a/packages/contract-artifacts/tsconfig.json +++ b/packages/contract-artifacts/tsconfig.json @@ -17,6 +17,7 @@ "./artifacts/ERC721Token.json", "./artifacts/Exchange.json", "./artifacts/Forwarder.json", + "./artifacts/IAssetProxy.json", "./artifacts/IValidator.json", "./artifacts/IWallet.json", "./artifacts/MultiAssetProxy.json", diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index 49193f8164..46b0e3f96d 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -1,10 +1,12 @@ -import { ExchangeContract, ExchangeEventArgs, ExchangeEvents } from '@0x/abi-gen-wrappers'; -import { Exchange } from '@0x/contract-artifacts'; +import { ExchangeContract, ExchangeEventArgs, ExchangeEvents, IAssetProxyContract } from '@0x/abi-gen-wrappers'; +import { Exchange, IAssetProxy } from '@0x/contract-artifacts'; import { schemas } from '@0x/json-schemas'; import { assetDataUtils, BalanceAndProxyAllowanceLazyStore, ExchangeTransferSimulator, + orderCalculationUtils, + orderHashUtils, OrderValidationUtils, } from '@0x/order-utils'; import { AssetProxyId, Order, SignedOrder } from '@0x/types'; @@ -1165,6 +1167,70 @@ export class ExchangeWrapper extends ContractWrapper { this.getZRXAssetData(), expectedFillTakerTokenAmountIfExists, ); + const filledTakerAmount = await this.getFilledTakerAssetAmountAsync( + orderHashUtils.getOrderHashHex(signedOrder), + ); + const makerAssetBalance = await balanceAllowanceStore.getBalanceAsync( + signedOrder.makerAssetData, + signedOrder.makerAddress, + ); + const makerAssetAllowance = await balanceAllowanceStore.getProxyAllowanceAsync( + signedOrder.makerAssetData, + signedOrder.makerAddress, + ); + const makerZRXBalance = await balanceAllowanceStore.getBalanceAsync( + this.getZRXAssetData(), + signedOrder.makerAddress, + ); + const makerZRXAllowance = await balanceAllowanceStore.getProxyAllowanceAsync( + this.getZRXAssetData(), + signedOrder.makerAddress, + ); + const remainingFillableTakerAssetAmount = orderCalculationUtils.calculateRemainingFillableTakerAssetAmount( + signedOrder, + filledTakerAmount, + { balance: makerAssetBalance, allowance: makerAssetAllowance }, + { balance: makerZRXBalance, allowance: makerZRXAllowance }, + ); + + await this.validateMakerTransferThrowIfInvalidAsync(signedOrder, remainingFillableTakerAssetAmount); + } + /** + * Validate the transfer from the Maker to the Taker. This is simulated on chain + * via an eth_call. If this call fails the asset is unlikely to be transferrable. + * @param signedOrder SignedOrder of interest + * @param fillTakerAssetAmount Amount we'd like to fill the order for + * @param takerAddress The taker of the order, defaults to signedOrder.takerAddress + */ + public async validateMakerTransferThrowIfInvalidAsync( + signedOrder: SignedOrder, + fillTakerAssetAmount: BigNumber, + takerAddress?: string, + ): Promise { + const toAddress = _.isUndefined(takerAddress) ? signedOrder.takerAddress : takerAddress; + const exchangeInstance = await this._getExchangeContractAsync(); + const makerAssetData = signedOrder.makerAssetData; + const makerAssetDataProxyId = assetDataUtils.decodeAssetProxyId(signedOrder.makerAssetData); + const assetProxyAddress = await exchangeInstance.assetProxies.callAsync(makerAssetDataProxyId); + const assetProxy = new IAssetProxyContract( + IAssetProxy.compilerOutput.abi, + assetProxyAddress, + this._web3Wrapper.getProvider(), + ); + const makerTransferAmount = orderCalculationUtils.getMakerFillAmount(signedOrder, fillTakerAssetAmount); + + const result = await assetProxy.transferFrom.callAsync( + makerAssetData, + signedOrder.makerAddress, + toAddress, + makerTransferAmount, + { + from: this.address, + }, + ); + if (result !== undefined) { + throw new Error('Unknown error occured during maker transfer simulation'); + } } /** * Validate a call to FillOrder and throw if it wouldn't succeed diff --git a/packages/contract-wrappers/test/exchange_wrapper_test.ts b/packages/contract-wrappers/test/exchange_wrapper_test.ts index acd30495bd..30e6caadbf 100644 --- a/packages/contract-wrappers/test/exchange_wrapper_test.ts +++ b/packages/contract-wrappers/test/exchange_wrapper_test.ts @@ -294,6 +294,9 @@ describe('ExchangeWrapper', () => { contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrderWithInvalidSignature), ).to.eventually.to.be.rejectedWith(RevertReason.InvalidOrderSignature); }); + it.only('should validate the order', async () => { + await contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrder); + }); }); describe('#isValidSignature', () => { it('should check if the signature is valid', async () => { diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts index 8bb1bd671e..f297065183 100644 --- a/packages/order-utils/src/index.ts +++ b/packages/order-utils/src/index.ts @@ -7,6 +7,7 @@ export { transactionHashUtils } from './transaction_hash'; export { rateUtils } from './rate_utils'; export { sortingUtils } from './sorting_utils'; export { orderParsingUtils } from './parsing_utils'; +export { orderCalculationUtils } from './order_calculation_utils'; export { OrderStateUtils } from './order_state_utils'; export { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher'; @@ -74,4 +75,5 @@ export { FindOrdersThatCoverMakerAssetFillAmountOpts, FeeOrdersAndRemainingFeeAmount, OrdersAndRemainingFillAmount, + BalanceAndAllowance, } from './types'; diff --git a/packages/order-utils/src/order_calculation_utils.ts b/packages/order-utils/src/order_calculation_utils.ts new file mode 100644 index 0000000000..19dd425b22 --- /dev/null +++ b/packages/order-utils/src/order_calculation_utils.ts @@ -0,0 +1,110 @@ +import { Order } from '@0x/types'; +import { BigNumber } from '@0x/utils'; + +import { constants } from './constants'; +import { BalanceAndAllowance } from './types'; + +export const orderCalculationUtils = { + getTakerFillAmount(order: Order, makerFillAmount: BigNumber): BigNumber { + // Round up because exchange rate favors Maker + const takerFillAmount = makerFillAmount + .multipliedBy(order.takerAssetAmount) + .div(order.makerAssetAmount) + .integerValue(BigNumber.ROUND_CEIL); + return takerFillAmount; + }, + // given a desired amount of takerAsset to fill, calculate how much makerAsset will be filled + getMakerFillAmount(order: Order, takerFillAmount: BigNumber): BigNumber { + // Round down because exchange rate favors Maker + const makerFillAmount = takerFillAmount + .multipliedBy(order.makerAssetAmount) + .div(order.takerAssetAmount) + .integerValue(BigNumber.ROUND_FLOOR); + return makerFillAmount; + }, + /** + * Calculates the remaining fillable amount for an order given: + * order filled amount + * asset balance/allowance (maker/taker) + * ZRX fee balance/allowance (maker/taker) + * Taker values are checked if specified in the order. For example, if the maker does not + * have sufficient ZRX allowance to pay the fee, then this function will return the maximum + * amount that can be filled given the maker's ZRX allowance + * @param order The order + * @param takerAssetFilledAmount The amount currently filled on the order + * @param makerAssetBalanceAllowance The makerAsset balance and allowance of the maker + * @param makerZRXBalanceAllowance The ZRX balance and allowance of the maker + * @param takerAssetBalanceAllowance The takerAsset balance and allowance of the taker + * @param takerZRXBalanceAllowance The ZRX balance and allowance of the taker + */ + calculateRemainingFillableTakerAssetAmount( + order: Order, + takerAssetFilledAmount: BigNumber, + makerAssetBalanceAllowance: BalanceAndAllowance, + makerZRXBalanceAllowance: BalanceAndAllowance, + takerAssetBalanceAllowance?: BalanceAndAllowance, + takerZRXBalanceAllowance?: BalanceAndAllowance, + ): BigNumber { + const minSet = []; + + // Calculate min of balance & allowance of taker's takerAsset + if (order.takerAddress !== constants.NULL_ADDRESS) { + if (takerAssetBalanceAllowance && takerZRXBalanceAllowance) { + const maxTakerAssetFillAmountGivenTakerConstraints = BigNumber.min( + takerAssetBalanceAllowance.balance, + takerAssetBalanceAllowance.allowance, + ); + minSet.push( + maxTakerAssetFillAmountGivenTakerConstraints, + takerAssetBalanceAllowance.balance, + takerAssetBalanceAllowance.allowance, + ); + } + } + + // Calculate min of balance & allowance of maker's makerAsset -> translate into takerAsset amount + const maxMakerAssetFillAmount = BigNumber.min( + makerAssetBalanceAllowance.balance, + makerAssetBalanceAllowance.allowance, + ); + const maxTakerAssetFillAmountGivenMakerConstraints = orderCalculationUtils.getTakerFillAmount( + order, + maxMakerAssetFillAmount, + ); + minSet.push(maxTakerAssetFillAmountGivenMakerConstraints); + + // Calculate min of balance & allowance of taker's ZRX -> translate into takerAsset amount + if (!order.takerFee.eq(0) && order.takerAddress !== constants.NULL_ADDRESS) { + if (takerAssetBalanceAllowance && takerZRXBalanceAllowance) { + const takerZRXAvailable = BigNumber.min( + takerZRXBalanceAllowance.balance, + takerZRXBalanceAllowance.allowance, + ); + const maxTakerAssetFillAmountGivenTakerZRXConstraints = takerZRXAvailable + .multipliedBy(order.takerAssetAmount) + .div(order.takerFee) + .integerValue(BigNumber.ROUND_CEIL); // Should this round to ciel or floor? + minSet.push(maxTakerAssetFillAmountGivenTakerZRXConstraints); + } + } + + // Calculate min of balance & allowance of maker's ZRX -> translate into takerAsset amount + if (!order.makerFee.eq(0)) { + const makerZRXAvailable = BigNumber.min( + makerZRXBalanceAllowance.balance, + makerZRXBalanceAllowance.allowance, + ); + const maxTakerAssetFillAmountGivenMakerZRXConstraints = makerZRXAvailable + .multipliedBy(order.takerAssetAmount) + .div(order.makerFee) + .integerValue(BigNumber.ROUND_CEIL); // Should this round to ciel or floor? + minSet.push(maxTakerAssetFillAmountGivenMakerZRXConstraints); + } + + const remainingTakerAssetFillAmount = order.takerAssetAmount.minus(takerAssetFilledAmount); + minSet.push(remainingTakerAssetFillAmount); + + const maxTakerAssetFillAmount = BigNumber.min(...minSet); + return maxTakerAssetFillAmount; + }, +}; diff --git a/packages/order-utils/src/types.ts b/packages/order-utils/src/types.ts index 55ec553dbd..bf1169e5a8 100644 --- a/packages/order-utils/src/types.ts +++ b/packages/order-utils/src/types.ts @@ -64,3 +64,8 @@ export interface OrdersAndRemainingFillAmount { ordersRemainingFillableMakerAssetAmounts: BigNumber[]; remainingFillAmount: BigNumber; } + +export interface BalanceAndAllowance { + balance: BigNumber; + allowance: BigNumber; +} From b16446877e867ce0776367b15aac2b64dd132aae Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Tue, 19 Mar 2019 16:46:54 +0100 Subject: [PATCH 02/23] only --- 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 30e6caadbf..686d364dd1 100644 --- a/packages/contract-wrappers/test/exchange_wrapper_test.ts +++ b/packages/contract-wrappers/test/exchange_wrapper_test.ts @@ -294,7 +294,7 @@ describe('ExchangeWrapper', () => { contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrderWithInvalidSignature), ).to.eventually.to.be.rejectedWith(RevertReason.InvalidOrderSignature); }); - it.only('should validate the order', async () => { + it('should validate the order', async () => { await contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrder); }); }); From 957f8c56a1b084d14918388c65bb394ced3068df Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Wed, 20 Mar 2019 15:24:14 +0100 Subject: [PATCH 03/23] validateRemainingOrderAmountIsFillable --- .../src/contract_wrappers/exchange_wrapper.ts | 71 +++++++++++-------- packages/contract-wrappers/src/types.ts | 7 +- .../test/exchange_wrapper_test.ts | 22 +++++- 3 files changed, 67 insertions(+), 33 deletions(-) diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index 46b0e3f96d..eac46051de 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -1158,42 +1158,55 @@ export class ExchangeWrapper extends ContractWrapper { const balanceAllowanceStore = new BalanceAndProxyAllowanceLazyStore(balanceAllowanceFetcher); const exchangeTradeSimulator = new ExchangeTransferSimulator(balanceAllowanceStore); - const expectedFillTakerTokenAmountIfExists = opts.expectedFillTakerTokenAmount; const filledCancelledFetcher = new OrderFilledCancelledFetcher(this, BlockParamLiteral.Latest); + + let fillableTakerAssetAmount; + const shouldValidateRemainingOrderAmountIsFillable = _.isUndefined(opts.validateRemainingOrderAmountIsFillable) + ? true + : opts.validateRemainingOrderAmountIsFillable; + const filledTakerTokenAmount = await this.getFilledTakerAssetAmountAsync( + orderHashUtils.getOrderHashHex(signedOrder), + ); + if (opts.expectedFillTakerTokenAmount) { + // If the caller has specified a taker fill amount, we use this for all validation + fillableTakerAssetAmount = opts.expectedFillTakerTokenAmount; + } else if (shouldValidateRemainingOrderAmountIsFillable) { + // Historically if a fill amount was not specified we would default to the amount + // left on the order. + fillableTakerAssetAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount); + } else { + const makerAssetBalance = await balanceAllowanceStore.getBalanceAsync( + signedOrder.makerAssetData, + signedOrder.makerAddress, + ); + const makerAssetAllowance = await balanceAllowanceStore.getProxyAllowanceAsync( + signedOrder.makerAssetData, + signedOrder.makerAddress, + ); + const makerZRXBalance = await balanceAllowanceStore.getBalanceAsync( + this.getZRXAssetData(), + signedOrder.makerAddress, + ); + const makerZRXAllowance = await balanceAllowanceStore.getProxyAllowanceAsync( + this.getZRXAssetData(), + signedOrder.makerAddress, + ); + fillableTakerAssetAmount = orderCalculationUtils.calculateRemainingFillableTakerAssetAmount( + signedOrder, + filledTakerTokenAmount, + { balance: makerAssetBalance, allowance: makerAssetAllowance }, + { balance: makerZRXBalance, allowance: makerZRXAllowance }, + ); + } + const orderValidationUtils = new OrderValidationUtils(filledCancelledFetcher, this._web3Wrapper.getProvider()); await orderValidationUtils.validateOrderFillableOrThrowAsync( exchangeTradeSimulator, signedOrder, this.getZRXAssetData(), - expectedFillTakerTokenAmountIfExists, - ); - const filledTakerAmount = await this.getFilledTakerAssetAmountAsync( - orderHashUtils.getOrderHashHex(signedOrder), - ); - const makerAssetBalance = await balanceAllowanceStore.getBalanceAsync( - signedOrder.makerAssetData, - signedOrder.makerAddress, - ); - const makerAssetAllowance = await balanceAllowanceStore.getProxyAllowanceAsync( - signedOrder.makerAssetData, - signedOrder.makerAddress, - ); - const makerZRXBalance = await balanceAllowanceStore.getBalanceAsync( - this.getZRXAssetData(), - signedOrder.makerAddress, + fillableTakerAssetAmount, ); - const makerZRXAllowance = await balanceAllowanceStore.getProxyAllowanceAsync( - this.getZRXAssetData(), - signedOrder.makerAddress, - ); - const remainingFillableTakerAssetAmount = orderCalculationUtils.calculateRemainingFillableTakerAssetAmount( - signedOrder, - filledTakerAmount, - { balance: makerAssetBalance, allowance: makerAssetAllowance }, - { balance: makerZRXBalance, allowance: makerZRXAllowance }, - ); - - await this.validateMakerTransferThrowIfInvalidAsync(signedOrder, remainingFillableTakerAssetAmount); + await this.validateMakerTransferThrowIfInvalidAsync(signedOrder, fillableTakerAssetAmount); } /** * Validate the transfer from the Maker to the Taker. This is simulated on chain diff --git a/packages/contract-wrappers/src/types.ts b/packages/contract-wrappers/src/types.ts index 29cf8b2c45..e147280787 100644 --- a/packages/contract-wrappers/src/types.ts +++ b/packages/contract-wrappers/src/types.ts @@ -122,11 +122,14 @@ export interface ContractWrappersConfig { /** * expectedFillTakerTokenAmount: If specified, the validation method will ensure that the * supplied order maker has a sufficient allowance/balance to fill this amount of the order's - * takerTokenAmount. If not specified, the validation method ensures that the maker has a sufficient - * allowance/balance to fill the entire remaining order amount. + * takerTokenAmount. + * validateRemainingOrderAmountIsFillable: The validation method ensures that the maker has a sufficient + * allowance/balance to fill the entire remaining order amount. This is the default. If neither options are + * specified, the balances and allowances are checked to determine the order is fillable by any amount. */ export interface ValidateOrderFillableOpts { expectedFillTakerTokenAmount?: BigNumber; + validateRemainingOrderAmountIsFillable?: boolean; } /** diff --git a/packages/contract-wrappers/test/exchange_wrapper_test.ts b/packages/contract-wrappers/test/exchange_wrapper_test.ts index 686d364dd1..b172b0ab9f 100644 --- a/packages/contract-wrappers/test/exchange_wrapper_test.ts +++ b/packages/contract-wrappers/test/exchange_wrapper_test.ts @@ -282,7 +282,7 @@ describe('ExchangeWrapper', () => { expect(ordersInfo[1].orderHash).to.be.equal(anotherOrderHash); }); }); - describe('#validateOrderFillableOrThrowAsync', () => { + describe.only('#validateOrderFillableOrThrowAsync', () => { it('should throw if signature is invalid', async () => { const signedOrderWithInvalidSignature = { ...signedOrder, @@ -294,9 +294,27 @@ describe('ExchangeWrapper', () => { contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrderWithInvalidSignature), ).to.eventually.to.be.rejectedWith(RevertReason.InvalidOrderSignature); }); - it('should validate the order', async () => { + it('should validate the order with the current balances and allowances for the maker', async () => { + await contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrder, { + validateRemainingOrderAmountIsFillable: false, + }); + }); + it('should validate the order with remaining fillable amount for the order', async () => { await contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrder); }); + it('should validate the order with specified amount', async () => { + await contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrder, { + expectedFillTakerTokenAmount: signedOrder.takerAssetAmount, + }); + }); + it('should throw if the amount is greater than the allowance/balance', async () => { + expect( + contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrder, { + // tslint:disable-next-line:custom-no-magic-numbers + expectedFillTakerTokenAmount: new BigNumber(2).pow(256).minus(1), + }), + ).to.eventually.to.be.rejected(); + }); }); describe('#isValidSignature', () => { it('should check if the signature is valid', async () => { From 18c613a611415c5a6c49f10a7edb138bf1a7b3be Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Wed, 20 Mar 2019 15:31:04 +0100 Subject: [PATCH 04/23] Update comments --- .../src/contract_wrappers/exchange_wrapper.ts | 3 ++- packages/contract-wrappers/test/exchange_wrapper_test.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index eac46051de..7b4a436d77 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -1141,7 +1141,8 @@ export class ExchangeWrapper extends ContractWrapper { * Validate if the supplied order is fillable, and throw if it isn't * @param signedOrder SignedOrder of interest * @param opts ValidateOrderFillableOpts options (e.g expectedFillTakerTokenAmount. - * If it isn't supplied, we check if the order is fillable for a non-zero amount) + * If it isn't supplied, we check if the order is fillable for the remaining amount. + * To check if the order is fillable for any amount set validateRemainingOrderAmountIsFillable to false.) */ public async validateOrderFillableOrThrowAsync( signedOrder: SignedOrder, diff --git a/packages/contract-wrappers/test/exchange_wrapper_test.ts b/packages/contract-wrappers/test/exchange_wrapper_test.ts index b172b0ab9f..10d19e7bfa 100644 --- a/packages/contract-wrappers/test/exchange_wrapper_test.ts +++ b/packages/contract-wrappers/test/exchange_wrapper_test.ts @@ -282,7 +282,7 @@ describe('ExchangeWrapper', () => { expect(ordersInfo[1].orderHash).to.be.equal(anotherOrderHash); }); }); - describe.only('#validateOrderFillableOrThrowAsync', () => { + describe('#validateOrderFillableOrThrowAsync', () => { it('should throw if signature is invalid', async () => { const signedOrderWithInvalidSignature = { ...signedOrder, From 8b70762e34a82fcb24d464d3a8ca801116f2ddc4 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Wed, 20 Mar 2019 17:17:42 +0100 Subject: [PATCH 05/23] Add a Test for an Untransferrable ERC20 token --- contracts/erc20/compiler.json | 3 +- .../test/UntransferrableDummyERC20Token.sol | 76 ++++ .../UntransferrableDummyERC20Token.ts | 409 ++++++++++++++++++ .../test/exchange_wrapper_test.ts | 25 ++ 4 files changed, 512 insertions(+), 1 deletion(-) create mode 100644 contracts/erc20/contracts/test/UntransferrableDummyERC20Token.sol create mode 100644 packages/contract-wrappers/test/artifacts/UntransferrableDummyERC20Token.ts diff --git a/contracts/erc20/compiler.json b/contracts/erc20/compiler.json index c84caf47f1..576bff024b 100644 --- a/contracts/erc20/compiler.json +++ b/contracts/erc20/compiler.json @@ -32,6 +32,7 @@ "src/interfaces/IEtherToken.sol", "test/DummyERC20Token.sol", "test/DummyMultipleReturnERC20Token.sol", - "test/DummyNoReturnERC20Token.sol" + "test/DummyNoReturnERC20Token.sol", + "test/UntransferrableDummyERC20Token.sol" ] } diff --git a/contracts/erc20/contracts/test/UntransferrableDummyERC20Token.sol b/contracts/erc20/contracts/test/UntransferrableDummyERC20Token.sol new file mode 100644 index 0000000000..4507b1b200 --- /dev/null +++ b/contracts/erc20/contracts/test/UntransferrableDummyERC20Token.sol @@ -0,0 +1,76 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.5; + +import "./DummyERC20Token.sol"; + + +// solhint-disable no-empty-blocks +contract UntransferrableDummyERC20Token is + DummyERC20Token +{ + bool internal _paused; + + constructor ( + string memory _name, + string memory _symbol, + uint256 _decimals, + uint256 _totalSupply + ) + public + DummyERC20Token( + _name, + _symbol, + _decimals, + _totalSupply + ) + {} + + // /// @dev send `value` token to `to` from `msg.sender` + // /// @param _to The address of the recipient + // /// @param _value The amount of token to be transferred + // function transfer(address _to, uint256 _value) + // external + // returns (bool) + // { + // require( + // false, + // "TRANSFER_DISABLED" + // ); + // } + + /// @dev send `value` token to `to` from `from` on the condition it is approved by `from` + /// @param _from The address of the sender + /// @param _to The address of the recipient + /// @param _value The amount of token to be transferred + function transferFrom( + address _from, + address _to, + uint256 _value + ) + external + returns (bool) + { + require( + false, + "TRANSFER_DISABLED" + ); + } +} + diff --git a/packages/contract-wrappers/test/artifacts/UntransferrableDummyERC20Token.ts b/packages/contract-wrappers/test/artifacts/UntransferrableDummyERC20Token.ts new file mode 100644 index 0000000000..c1a472f0c2 --- /dev/null +++ b/packages/contract-wrappers/test/artifacts/UntransferrableDummyERC20Token.ts @@ -0,0 +1,409 @@ +export const UntransferrableDummyERC20Token = { + schemaVersion: '2.0.0', + contractName: 'UntransferrableDummyERC20Token', + compilerOutput: { + abi: [ + { + constant: true, + inputs: [], + name: 'name', + outputs: [ + { + name: '', + type: 'string', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_spender', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + name: 'approve', + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'totalSupply', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_from', + type: 'address', + }, + { + name: '_to', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + name: 'transferFrom', + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'decimals', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'owner', + outputs: [ + { + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'symbol', + outputs: [ + { + name: '', + type: 'string', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_value', + type: 'uint256', + }, + ], + name: 'mint', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_to', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + name: 'transfer', + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address', + }, + { + name: '_spender', + type: 'address', + }, + ], + name: 'allowance', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_target', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + name: 'setBalance', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: 'newOwner', + type: 'address', + }, + ], + name: 'transferOwnership', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'MAX_MINT_AMOUNT', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + name: '_name', + type: 'string', + }, + { + name: '_symbol', + type: 'string', + }, + { + name: '_decimals', + type: 'uint256', + }, + { + name: '_totalSupply', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: '_from', + type: 'address', + }, + { + indexed: true, + name: '_to', + type: 'address', + }, + { + indexed: false, + name: '_value', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: '_owner', + type: 'address', + }, + { + indexed: true, + name: '_spender', + type: 'address', + }, + { + indexed: false, + name: '_value', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + ], + evm: { + bytecode: { + linkReferences: {}, + object: + '0x60806040523480156200001157600080fd5b5060405162000e2738038062000e27833981018060405260808110156200003757600080fd5b8101908080516401000000008111156200005057600080fd5b820160208101848111156200006457600080fd5b81516401000000008111828201871017156200007f57600080fd5b505092919060200180516401000000008111156200009c57600080fd5b82016020810184811115620000b057600080fd5b8151640100000000811182820187101715620000cb57600080fd5b505060208083015160409093015160008054600160a060020a03191633179055865192955092935085918591859185916200010c9160049187019062000146565b5082516200012290600590602086019062000146565b506006919091553360009081526001602052604090205550620001eb945050505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200018957805160ff1916838001178555620001b9565b82800160010185558215620001b9579182015b82811115620001b95782518255916020019190600101906200019c565b50620001c7929150620001cb565b5090565b620001e891905b80821115620001c75760008155600101620001d2565b90565b610c2c80620001fb6000396000f3fe608060405234801561001057600080fd5b5060043610610107576000357c01000000000000000000000000000000000000000000000000000000009004806395d89b41116100a9578063dd62ed3e11610083578063dd62ed3e146102ff578063e30443bc1461033a578063f2fde38b14610373578063fa9b7018146103a657610107565b806395d89b411461029f578063a0712d68146102a7578063a9059cbb146102c657610107565b806323b872dd116100e557806323b872dd146101f0578063313ce5671461023357806370a082311461023b5780638da5cb5b1461026e57610107565b806306fdde031461010c578063095ea7b31461018957806318160ddd146101d6575b600080fd5b6101146103ae565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561014e578181015183820152602001610136565b50505050905090810190601f16801561017b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101c26004803603604081101561019f57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813516906020013561045a565b604080519115158252519081900360200190f35b6101de6104cd565b60408051918252519081900360200190f35b6101c26004803603606081101561020657600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013590911690604001356104d3565b6101de61053c565b6101de6004803603602081101561025157600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16610542565b61027661056a565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b610114610586565b6102c4600480360360208110156102bd57600080fd5b50356105ff565b005b6101c2600480360360408110156102dc57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610685565b6101de6004803603604081101561031557600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516610814565b6102c46004803603604081101561035057600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813516906020013561084c565b6102c46004803603602081101561038957600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16610960565b6101de610a47565b6004805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156104525780601f1061042757610100808354040283529160200191610452565b820191906000526020600020905b81548152906001019060200180831161043557829003601f168201915b505050505081565b33600081815260026020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a350600192915050565b60035490565b6000604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f5452414e534645525f44495341424c4544000000000000000000000000000000604482015290519081900360640190fd5b60065481565b73ffffffffffffffffffffffffffffffffffffffff1660009081526001602052604090205490565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b6005805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156104525780601f1061042757610100808354040283529160200191610452565b69021e19e0c9bab240000081111561067857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f56414c55455f544f4f5f4c415247450000000000000000000000000000000000604482015290519081900360640190fd5b6106823382610a55565b50565b3360009081526001602052604081205482111561070357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f45524332305f494e53554646494349454e545f42414c414e4345000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8316600090815260016020526040902054828101101561079957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f55494e543235365f4f564552464c4f5700000000000000000000000000000000604482015290519081900360640190fd5b3360008181526001602090815260408083208054879003905573ffffffffffffffffffffffffffffffffffffffff871680845292819020805487019055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a350600192915050565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260026020908152604080832093909416825291909152205490565b60005473ffffffffffffffffffffffffffffffffffffffff1633146108d257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff82166000908152600160205260409020548082101561091d576109156003546109108385610b0e565b610b0e565b600355610936565b61093260035461092d8484610b0e565b610b85565b6003555b5073ffffffffffffffffffffffffffffffffffffffff909116600090815260016020526040902055565b60005473ffffffffffffffffffffffffffffffffffffffff1633146109e657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811615610682576000805473ffffffffffffffffffffffffffffffffffffffff83167fffffffffffffffffffffffff000000000000000000000000000000000000000090911617905550565b69021e19e0c9bab240000081565b73ffffffffffffffffffffffffffffffffffffffff8216600090815260016020526040902054610a86908290610b85565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260016020526040902055600354610ab99082610b85565b60035560408051828152905173ffffffffffffffffffffffffffffffffffffffff8416916000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a35050565b600082821115610b7f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f55494e543235365f554e444552464c4f57000000000000000000000000000000604482015290519081900360640190fd5b50900390565b600082820183811015610bf957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f55494e543235365f4f564552464c4f5700000000000000000000000000000000604482015290519081900360640190fd5b939250505056fea165627a7a723058204cebdef97829ef903902bc1f15ea8041119e2f2735acfe65aeda451b3f21c4f10029', + }, + }, + }, + sources: { + 'test/UntransferrableDummyERC20Token.sol': { + id: 5, + }, + 'test/DummyERC20Token.sol': { + id: 4, + }, + '@0x/contracts-utils/contracts/src/Ownable.sol': { + id: 6, + }, + '@0x/contracts-utils/contracts/src/interfaces/IOwnable.sol': { + id: 8, + }, + 'src/MintableERC20Token.sol': { + id: 1, + }, + '@0x/contracts-utils/contracts/src/SafeMath.sol': { + id: 7, + }, + 'src/UnlimitedAllowanceERC20Token.sol': { + id: 2, + }, + 'src/ERC20Token.sol': { + id: 0, + }, + 'src/interfaces/IERC20Token.sol': { + id: 3, + }, + }, + sourceCodes: { + 'test/UntransferrableDummyERC20Token.sol': + '/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.5.5;\n\nimport "./DummyERC20Token.sol";\n\n\n// solhint-disable no-empty-blocks\ncontract UntransferrableDummyERC20Token is\n DummyERC20Token\n{\n bool internal _paused;\n\n constructor (\n string memory _name,\n string memory _symbol,\n uint256 _decimals,\n uint256 _totalSupply\n )\n public\n DummyERC20Token(\n _name,\n _symbol,\n _decimals,\n _totalSupply\n )\n {}\n\n /// @dev send `value` token to `to` from `msg.sender`\n /// @param _to The address of the recipient\n /// @param _value The amount of token to be transferred\n function transfer(address _to, uint256 _value)\n external\n returns (bool)\n {\n require(\n false,\n "TRANSFER_DISABLED"\n );\n }\n\n /// @dev send `value` token to `to` from `from` on the condition it is approved by `from`\n /// @param _from The address of the sender\n /// @param _to The address of the recipient\n /// @param _value The amount of token to be transferred\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n )\n external\n returns (bool)\n {\n require(\n false,\n "TRANSFER_DISABLED"\n );\n }\n}\n\n', + 'test/DummyERC20Token.sol': + '/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.5.5;\n\nimport "@0x/contracts-utils/contracts/src/Ownable.sol";\nimport "../src/MintableERC20Token.sol";\n\n\ncontract DummyERC20Token is \n Ownable,\n MintableERC20Token\n{\n string public name;\n string public symbol;\n uint256 public decimals;\n uint256 public constant MAX_MINT_AMOUNT = 10000000000000000000000;\n\n constructor (\n string memory _name,\n string memory _symbol,\n uint256 _decimals,\n uint256 _totalSupply\n )\n public\n {\n name = _name;\n symbol = _symbol;\n decimals = _decimals;\n _totalSupply = _totalSupply;\n balances[msg.sender] = _totalSupply;\n }\n\n /// @dev Sets the balance of target address\n /// @param _target Address or which balance will be updated\n /// @param _value New balance of target address\n function setBalance(address _target, uint256 _value)\n external\n onlyOwner\n {\n uint256 currBalance = balances[_target];\n if (_value < currBalance) {\n _totalSupply = safeSub(_totalSupply, safeSub(currBalance, _value));\n } else {\n _totalSupply = safeAdd(_totalSupply, safeSub(_value, currBalance));\n }\n balances[_target] = _value;\n }\n\n /// @dev Mints new tokens for sender\n /// @param _value Amount of tokens to mint\n function mint(uint256 _value)\n external\n {\n require(\n _value <= MAX_MINT_AMOUNT,\n "VALUE_TOO_LARGE"\n );\n\n _mint(msg.sender, _value);\n }\n}\n', + '@0x/contracts-utils/contracts/src/Ownable.sol': + 'pragma solidity ^0.5.5;\n\nimport "./interfaces/IOwnable.sol";\n\n\ncontract Ownable is\n IOwnable\n{\n address public owner;\n\n constructor ()\n public\n {\n owner = msg.sender;\n }\n\n modifier onlyOwner() {\n require(\n msg.sender == owner,\n "ONLY_CONTRACT_OWNER"\n );\n _;\n }\n\n function transferOwnership(address newOwner)\n public\n onlyOwner\n {\n if (newOwner != address(0)) {\n owner = newOwner;\n }\n }\n}\n', + '@0x/contracts-utils/contracts/src/interfaces/IOwnable.sol': + 'pragma solidity ^0.5.5;\n\n\ncontract IOwnable {\n\n function transferOwnership(address newOwner)\n public;\n}\n', + 'src/MintableERC20Token.sol': + '/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.5.5;\n\nimport "@0x/contracts-utils/contracts/src/SafeMath.sol";\nimport "./UnlimitedAllowanceERC20Token.sol";\n\n\ncontract MintableERC20Token is \n SafeMath,\n UnlimitedAllowanceERC20Token\n{\n /// @dev Mints new tokens\n /// @param _to Address of the beneficiary that will own the minted token\n /// @param _value Amount of tokens to mint\n function _mint(address _to, uint256 _value)\n internal\n {\n balances[_to] = safeAdd(_value, balances[_to]);\n _totalSupply = safeAdd(_totalSupply, _value);\n\n emit Transfer(\n address(0),\n _to,\n _value\n );\n }\n\n /// @dev Mints new tokens\n /// @param _owner Owner of tokens that will be burned\n /// @param _value Amount of tokens to burn\n function _burn(address _owner, uint256 _value)\n internal\n {\n balances[_owner] = safeSub(balances[_owner], _value);\n _totalSupply = safeSub(_totalSupply, _value);\n\n emit Transfer(\n _owner,\n address(0),\n _value\n );\n }\n}\n', + '@0x/contracts-utils/contracts/src/SafeMath.sol': + 'pragma solidity ^0.5.5;\n\n\ncontract SafeMath {\n\n function safeMul(uint256 a, uint256 b)\n internal\n pure\n returns (uint256)\n {\n if (a == 0) {\n return 0;\n }\n uint256 c = a * b;\n require(\n c / a == b,\n "UINT256_OVERFLOW"\n );\n return c;\n }\n\n function safeDiv(uint256 a, uint256 b)\n internal\n pure\n returns (uint256)\n {\n uint256 c = a / b;\n return c;\n }\n\n function safeSub(uint256 a, uint256 b)\n internal\n pure\n returns (uint256)\n {\n require(\n b <= a,\n "UINT256_UNDERFLOW"\n );\n return a - b;\n }\n\n function safeAdd(uint256 a, uint256 b)\n internal\n pure\n returns (uint256)\n {\n uint256 c = a + b;\n require(\n c >= a,\n "UINT256_OVERFLOW"\n );\n return c;\n }\n\n function max64(uint64 a, uint64 b)\n internal\n pure\n returns (uint256)\n {\n return a >= b ? a : b;\n }\n\n function min64(uint64 a, uint64 b)\n internal\n pure\n returns (uint256)\n {\n return a < b ? a : b;\n }\n\n function max256(uint256 a, uint256 b)\n internal\n pure\n returns (uint256)\n {\n return a >= b ? a : b;\n }\n\n function min256(uint256 a, uint256 b)\n internal\n pure\n returns (uint256)\n {\n return a < b ? a : b;\n }\n}\n', + 'src/UnlimitedAllowanceERC20Token.sol': + '/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.5.5;\n\nimport "./ERC20Token.sol";\n\n\ncontract UnlimitedAllowanceERC20Token is\n ERC20Token\n{\n uint256 constant internal MAX_UINT = 2**256 - 1;\n\n /// @dev ERC20 transferFrom, modified such that an allowance of MAX_UINT represents an unlimited allowance. See https://github.com/ethereum/EIPs/issues/717\n /// @param _from Address to transfer from.\n /// @param _to Address to transfer to.\n /// @param _value Amount to transfer.\n /// @return Success of transfer.\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n )\n external\n returns (bool)\n {\n uint256 allowance = allowed[_from][msg.sender];\n require(\n balances[_from] >= _value,\n "ERC20_INSUFFICIENT_BALANCE"\n );\n require(\n allowance >= _value,\n "ERC20_INSUFFICIENT_ALLOWANCE"\n );\n require(\n balances[_to] + _value >= balances[_to],\n "UINT256_OVERFLOW"\n );\n\n balances[_to] += _value;\n balances[_from] -= _value;\n if (allowance < MAX_UINT) {\n allowed[_from][msg.sender] -= _value;\n }\n\n emit Transfer(\n _from,\n _to,\n _value\n );\n\n return true;\n }\n}\n', + 'src/ERC20Token.sol': + '/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.5.5;\n\nimport "./interfaces/IERC20Token.sol";\n\n\ncontract ERC20Token is\n IERC20Token\n{\n mapping (address => uint256) internal balances;\n mapping (address => mapping (address => uint256)) internal allowed;\n\n uint256 internal _totalSupply;\n\n /// @dev send `value` token to `to` from `msg.sender`\n /// @param _to The address of the recipient\n /// @param _value The amount of token to be transferred\n /// @return True if transfer was successful\n function transfer(address _to, uint256 _value)\n external\n returns (bool)\n {\n require(\n balances[msg.sender] >= _value,\n "ERC20_INSUFFICIENT_BALANCE"\n );\n require(\n balances[_to] + _value >= balances[_to],\n "UINT256_OVERFLOW"\n );\n\n balances[msg.sender] -= _value;\n balances[_to] += _value;\n\n emit Transfer(\n msg.sender,\n _to,\n _value\n );\n\n return true;\n }\n\n /// @dev send `value` token to `to` from `from` on the condition it is approved by `from`\n /// @param _from The address of the sender\n /// @param _to The address of the recipient\n /// @param _value The amount of token to be transferred\n /// @return True if transfer was successful\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n )\n external\n returns (bool)\n {\n require(\n balances[_from] >= _value,\n "ERC20_INSUFFICIENT_BALANCE"\n );\n require(\n allowed[_from][msg.sender] >= _value,\n "ERC20_INSUFFICIENT_ALLOWANCE"\n );\n require(\n balances[_to] + _value >= balances[_to],\n "UINT256_OVERFLOW"\n );\n\n balances[_to] += _value;\n balances[_from] -= _value;\n allowed[_from][msg.sender] -= _value;\n \n emit Transfer(\n _from,\n _to,\n _value\n );\n \n return true;\n }\n\n /// @dev `msg.sender` approves `_spender` to spend `_value` tokens\n /// @param _spender The address of the account able to transfer the tokens\n /// @param _value The amount of wei to be approved for transfer\n /// @return Always true if the call has enough gas to complete execution\n function approve(address _spender, uint256 _value)\n external\n returns (bool)\n {\n allowed[msg.sender][_spender] = _value;\n emit Approval(\n msg.sender,\n _spender,\n _value\n );\n return true;\n }\n\n /// @dev Query total supply of token\n /// @return Total supply of token\n function totalSupply()\n external\n view\n returns (uint256)\n {\n return _totalSupply;\n }\n\n /// @dev Query the balance of owner\n /// @param _owner The address from which the balance will be retrieved\n /// @return Balance of owner\n function balanceOf(address _owner)\n external\n view\n returns (uint256)\n {\n return balances[_owner];\n }\n\n /// @param _owner The address of the account owning tokens\n /// @param _spender The address of the account able to transfer the tokens\n /// @return Amount of remaining tokens allowed to spent\n function allowance(address _owner, address _spender)\n external\n view\n returns (uint256)\n {\n return allowed[_owner][_spender];\n }\n}\n', + 'src/interfaces/IERC20Token.sol': + '/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.5.5;\n\n\ncontract IERC20Token {\n\n // solhint-disable no-simple-event-func-name\n event Transfer(\n address indexed _from,\n address indexed _to,\n uint256 _value\n );\n\n event Approval(\n address indexed _owner,\n address indexed _spender,\n uint256 _value\n );\n\n /// @dev send `value` token to `to` from `msg.sender`\n /// @param _to The address of the recipient\n /// @param _value The amount of token to be transferred\n /// @return True if transfer was successful\n function transfer(address _to, uint256 _value)\n external\n returns (bool);\n\n /// @dev send `value` token to `to` from `from` on the condition it is approved by `from`\n /// @param _from The address of the sender\n /// @param _to The address of the recipient\n /// @param _value The amount of token to be transferred\n /// @return True if transfer was successful\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n )\n external\n returns (bool);\n \n /// @dev `msg.sender` approves `_spender` to spend `_value` tokens\n /// @param _spender The address of the account able to transfer the tokens\n /// @param _value The amount of wei to be approved for transfer\n /// @return Always true if the call has enough gas to complete execution\n function approve(address _spender, uint256 _value)\n external\n returns (bool);\n\n /// @dev Query total supply of token\n /// @return Total supply of token\n function totalSupply()\n external\n view\n returns (uint256);\n \n /// @param _owner The address from which the balance will be retrieved\n /// @return Balance of owner\n function balanceOf(address _owner)\n external\n view\n returns (uint256);\n\n /// @param _owner The address of the account owning tokens\n /// @param _spender The address of the account able to transfer the tokens\n /// @return Amount of remaining tokens allowed to spent\n function allowance(address _owner, address _spender)\n external\n view\n returns (uint256);\n}\n', + }, + sourceTreeHashHex: '0x1fe42c8f253c28a74058cb82ccc63d561a91271e813cb576ee4f2a43175b0f3f', + compiler: { + name: 'solc', + version: '0.5.6+commit.b259423e.Linux.g++', + settings: { + optimizer: { + enabled: true, + runs: 1000000, + details: { + yul: true, + deduplicate: true, + cse: true, + constantOptimizer: true, + }, + }, + outputSelection: { + '*': { + '*': [ + 'abi', + 'evm.bytecode.object', + 'evm.bytecode.sourceMap', + 'evm.deployedBytecode.object', + 'evm.deployedBytecode.sourceMap', + ], + }, + }, + evmVersion: 'byzantium', + remappings: [ + '@0x/contracts-utils=/Users/jacob/projects/ethdev/0x/0x.js/contracts/erc20/node_modules/@0x/contracts-utils', + ], + }, + }, + networks: {}, +}; diff --git a/packages/contract-wrappers/test/exchange_wrapper_test.ts b/packages/contract-wrappers/test/exchange_wrapper_test.ts index 10d19e7bfa..113e90f72a 100644 --- a/packages/contract-wrappers/test/exchange_wrapper_test.ts +++ b/packages/contract-wrappers/test/exchange_wrapper_test.ts @@ -1,3 +1,4 @@ +import { DummyERC20TokenContract } from '@0x/abi-gen-wrappers'; import { BlockchainLifecycle, callbackErrorReporter } from '@0x/dev-utils'; import { FillScenarios } from '@0x/fill-scenarios'; import { assetDataUtils, orderHashUtils, signatureUtils } from '@0x/order-utils'; @@ -10,6 +11,7 @@ import 'mocha'; import { ContractWrappers, ExchangeCancelEventArgs, ExchangeEvents, ExchangeFillEventArgs, OrderStatus } from '../src'; import { DecodedLogEvent } from '../src/types'; +import { UntransferrableDummyERC20Token } from './artifacts/UntransferrableDummyERC20Token'; import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; import { migrateOnceAsync } from './utils/migrate'; @@ -315,6 +317,29 @@ describe('ExchangeWrapper', () => { }), ).to.eventually.to.be.rejected(); }); + it('should throw when the ERC20 token has transfer restrictions', async () => { + const untransferrableToken = await DummyERC20TokenContract.deployFrom0xArtifactAsync( + UntransferrableDummyERC20Token, + provider, + { from: userAddresses[0] }, + 'UntransferrableToken', + 'UTT', + new BigNumber(constants.ZRX_DECIMALS), + // tslint:disable-next-line:custom-no-magic-numbers + new BigNumber(2).pow(20).minus(1), + ); + const untransferrableMakerAssetData = assetDataUtils.encodeERC20AssetData(untransferrableToken.address); + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + untransferrableMakerAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + expect( + contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrder), + ).to.eventually.to.be.rejectedWith(RevertReason.TransferFailed); + }); }); describe('#isValidSignature', () => { it('should check if the signature is valid', async () => { From 38ac2e80edef6b2f45b6dfcc9f71c3b51e1167f8 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Wed, 20 Mar 2019 17:19:17 +0100 Subject: [PATCH 06/23] Remove unused code --- .../test/UntransferrableDummyERC20Token.sol | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/contracts/erc20/contracts/test/UntransferrableDummyERC20Token.sol b/contracts/erc20/contracts/test/UntransferrableDummyERC20Token.sol index 4507b1b200..c9f8df4a11 100644 --- a/contracts/erc20/contracts/test/UntransferrableDummyERC20Token.sol +++ b/contracts/erc20/contracts/test/UntransferrableDummyERC20Token.sol @@ -42,19 +42,6 @@ contract UntransferrableDummyERC20Token is ) {} - // /// @dev send `value` token to `to` from `msg.sender` - // /// @param _to The address of the recipient - // /// @param _value The amount of token to be transferred - // function transfer(address _to, uint256 _value) - // external - // returns (bool) - // { - // require( - // false, - // "TRANSFER_DISABLED" - // ); - // } - /// @dev send `value` token to `to` from `from` on the condition it is approved by `from` /// @param _from The address of the sender /// @param _to The address of the recipient From ee8d40a66eac4aa491addcee0e628c50dd110dc4 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 21 Mar 2019 12:05:23 +0100 Subject: [PATCH 07/23] Add IAssetProxy to python --- .../artifacts/IAssetProxy.json | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 python-packages/contract_artifacts/src/zero_ex/contract_artifacts/artifacts/IAssetProxy.json diff --git a/python-packages/contract_artifacts/src/zero_ex/contract_artifacts/artifacts/IAssetProxy.json b/python-packages/contract_artifacts/src/zero_ex/contract_artifacts/artifacts/IAssetProxy.json new file mode 100644 index 0000000000..547bdc938d --- /dev/null +++ b/python-packages/contract_artifacts/src/zero_ex/contract_artifacts/artifacts/IAssetProxy.json @@ -0,0 +1,185 @@ +{ + "schemaVersion": "2.0.0", + "contractName": "IAssetProxy", + "compilerOutput": { + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "addAuthorizedAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "removeAuthorizedAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + }, + { + "name": "index", + "type": "uint256" + } + ], + "name": "removeAuthorizedAddressAtIndex", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "assetData", + "type": "bytes" + }, + { + "name": "from", + "type": "address" + }, + { + "name": "to", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getProxyId", + "outputs": [ + { + "name": "", + "type": "bytes4" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getAuthorizedAddresses", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "evm": { + "bytecode": { + "linkReferences": {}, + "object": "0x", + "opcodes": "", + "sourceMap": "" + }, + "deployedBytecode": { + "linkReferences": {}, + "object": "0x", + "opcodes": "", + "sourceMap": "" + } + } + }, + "sources": { + "src/interfaces/IAssetProxy.sol": { + "id": 2 + }, + "src/interfaces/IAuthorizable.sol": { + "id": 3 + }, + "@0x/contracts-utils/contracts/src/interfaces/IOwnable.sol": { + "id": 6 + } + }, + "sourceCodes": { + "src/interfaces/IAssetProxy.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"./IAuthorizable.sol\";\n\n\ncontract IAssetProxy is\n IAuthorizable\n{\n /// @dev Transfers assets. Either succeeds or throws.\n /// @param assetData Byte array encoded for the respective asset proxy.\n /// @param from Address to transfer asset from.\n /// @param to Address to transfer asset to.\n /// @param amount Amount of asset to transfer.\n function transferFrom(\n bytes assetData,\n address from,\n address to,\n uint256 amount\n )\n external;\n \n /// @dev Gets the proxy id associated with the proxy address.\n /// @return Proxy id.\n function getProxyId()\n external\n pure\n returns (bytes4);\n}\n", + "src/interfaces/IAuthorizable.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-utils/contracts/src/interfaces/IOwnable.sol\";\n\n\ncontract IAuthorizable is\n IOwnable\n{\n /// @dev Authorizes an address.\n /// @param target Address to authorize.\n function addAuthorizedAddress(address target)\n external;\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n function removeAuthorizedAddress(address target)\n external;\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n /// @param index Index of target in authorities array.\n function removeAuthorizedAddressAtIndex(\n address target,\n uint256 index\n )\n external;\n \n /// @dev Gets all authorized addresses.\n /// @return Array of authorized addresses.\n function getAuthorizedAddresses()\n external\n view\n returns (address[] memory);\n}\n", + "@0x/contracts-utils/contracts/src/interfaces/IOwnable.sol": "pragma solidity ^0.4.24;\n\n\ncontract IOwnable {\n\n function transferOwnership(address newOwner)\n public;\n}\n" + }, + "sourceTreeHashHex": "0x4fe33d3b00ae85e5c1d5478eeb1bf17fdc637fa0501dc1ff6fd1f871f87c83ca", + "compiler": { + "name": "solc", + "version": "0.4.25+commit.59dbf8f1.Linux.g++", + "settings": { + "optimizer": { + "enabled": true, + "runs": 1000000, + "details": { + "yul": true, + "deduplicate": true, + "cse": true, + "constantOptimizer": true + } + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + }, + "evmVersion": "byzantium", + "remappings": [ + "@0x/contracts-utils=/Users/jacob/projects/ethdev/0x/0x.js/contracts/asset-proxy/node_modules/@0x/contracts-utils" + ] + } + }, + "networks": {} +} \ No newline at end of file From 91ec65da1b6f9bf36fcc4c5615c352bd769c48fc Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 21 Mar 2019 14:11:36 +0100 Subject: [PATCH 08/23] Await in tests to prevent clash in before blocks --- .../test/exchange_wrapper_test.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/contract-wrappers/test/exchange_wrapper_test.ts b/packages/contract-wrappers/test/exchange_wrapper_test.ts index 113e90f72a..2fc8f131b7 100644 --- a/packages/contract-wrappers/test/exchange_wrapper_test.ts +++ b/packages/contract-wrappers/test/exchange_wrapper_test.ts @@ -22,7 +22,7 @@ chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -describe('ExchangeWrapper', () => { +describe.only('ExchangeWrapper', () => { let contractWrappers: ContractWrappers; let userAddresses: string[]; let zrxTokenAddress: string; @@ -329,16 +329,26 @@ describe('ExchangeWrapper', () => { new BigNumber(2).pow(20).minus(1), ); const untransferrableMakerAssetData = assetDataUtils.encodeERC20AssetData(untransferrableToken.address); - signedOrder = await fillScenarios.createFillableSignedOrderAsync( + const invalidSignedOrder = await fillScenarios.createFillableSignedOrderAsync( untransferrableMakerAssetData, takerAssetData, makerAddress, takerAddress, fillableAmount, ); - expect( - contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrder), - ).to.eventually.to.be.rejectedWith(RevertReason.TransferFailed); + await web3Wrapper.awaitTransactionSuccessAsync( + await contractWrappers.erc20Token.setProxyAllowanceAsync( + untransferrableToken.address, + makerAddress, + signedOrder.makerAssetAmount, + ), + ); + try { + await contractWrappers.exchange.validateOrderFillableOrThrowAsync(invalidSignedOrder); + expect(true).to.be.false(); // never hit + } catch (e) { + expect(e.message).to.include('TRANSFER_FAILED'); + } }); }); describe('#isValidSignature', () => { From a34d5b29e891a4961b7883a6b43be0a5b0aeffad Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 21 Mar 2019 14:17:01 +0100 Subject: [PATCH 09/23] Return eventually rejectedWith --- .../contract-wrappers/test/exchange_wrapper_test.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/contract-wrappers/test/exchange_wrapper_test.ts b/packages/contract-wrappers/test/exchange_wrapper_test.ts index 2fc8f131b7..bb5930dc03 100644 --- a/packages/contract-wrappers/test/exchange_wrapper_test.ts +++ b/packages/contract-wrappers/test/exchange_wrapper_test.ts @@ -292,7 +292,7 @@ describe.only('ExchangeWrapper', () => { '0x1b61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403', }; - expect( + return expect( contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrderWithInvalidSignature), ).to.eventually.to.be.rejectedWith(RevertReason.InvalidOrderSignature); }); @@ -310,7 +310,7 @@ describe.only('ExchangeWrapper', () => { }); }); it('should throw if the amount is greater than the allowance/balance', async () => { - expect( + return expect( contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrder, { // tslint:disable-next-line:custom-no-magic-numbers expectedFillTakerTokenAmount: new BigNumber(2).pow(256).minus(1), @@ -343,12 +343,9 @@ describe.only('ExchangeWrapper', () => { signedOrder.makerAssetAmount, ), ); - try { - await contractWrappers.exchange.validateOrderFillableOrThrowAsync(invalidSignedOrder); - expect(true).to.be.false(); // never hit - } catch (e) { - expect(e.message).to.include('TRANSFER_FAILED'); - } + return expect( + contractWrappers.exchange.validateOrderFillableOrThrowAsync(invalidSignedOrder), + ).to.eventually.to.be.rejectedWith('TRANSFER_FAILED'); }); }); describe('#isValidSignature', () => { From e57567287759e2120f3e8091f724ba8bc679ea3a Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 21 Mar 2019 14:22:26 +0100 Subject: [PATCH 10/23] Update CHANGELOGs --- contracts/erc20/CHANGELOG.json | 4 ++++ packages/abi-gen-wrappers/CHANGELOG.json | 9 +++++++++ packages/contract-artifacts/CHANGELOG.json | 9 +++++++++ packages/contract-wrappers/CHANGELOG.json | 9 +++++++++ packages/order-utils/CHANGELOG.json | 9 +++++++++ 5 files changed, 40 insertions(+) diff --git a/contracts/erc20/CHANGELOG.json b/contracts/erc20/CHANGELOG.json index 812564a559..05ed5303e1 100644 --- a/contracts/erc20/CHANGELOG.json +++ b/contracts/erc20/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Run Web3ProviderEngine without excess block polling", "pr": 1695 + }, + { + "note": "Added UntransferrableDummyERC20Token", + "pr": 1714 } ], "timestamp": 1553183790 diff --git a/packages/abi-gen-wrappers/CHANGELOG.json b/packages/abi-gen-wrappers/CHANGELOG.json index f85a71cd29..7184de7134 100644 --- a/packages/abi-gen-wrappers/CHANGELOG.json +++ b/packages/abi-gen-wrappers/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "4.2.0", + "changes": [ + { + "note": "Added IAssetProxy wrapper", + "pr": 1714 + } + ] + }, { "version": "4.1.0", "changes": [ diff --git a/packages/contract-artifacts/CHANGELOG.json b/packages/contract-artifacts/CHANGELOG.json index 1fbfe90604..e3c83aa8d9 100644 --- a/packages/contract-artifacts/CHANGELOG.json +++ b/packages/contract-artifacts/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "1.5.0", + "changes": [ + { + "note": "Added artifact for `IAssetProxy`", + "pr": 1714 + } + ] + }, { "version": "1.4.0", "changes": [ diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index 5ab5d9bc58..aeadd419d5 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "8.1.0", + "changes": [ + { + "note": "Added a simulation for `exchange.validateOrderFillableOrThrowAsync` that simulates maker transfer", + "pr": 1714 + } + ] + }, { "timestamp": 1553183790, "version": "8.0.5", diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index d7511d2faa..ab85d41574 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "7.2.0", + "changes": [ + { + "note": "Added `orderCalculationUtils`", + "pr": 1714 + } + ] + }, { "timestamp": 1553183790, "version": "7.1.1", From a5f06c577d626605fc4460d0007cf51dc7a46713 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 21 Mar 2019 16:15:38 +0100 Subject: [PATCH 11/23] Remove unused pause --- .../erc20/contracts/test/UntransferrableDummyERC20Token.sol | 2 -- packages/contract-wrappers/test/exchange_wrapper_test.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/erc20/contracts/test/UntransferrableDummyERC20Token.sol b/contracts/erc20/contracts/test/UntransferrableDummyERC20Token.sol index c9f8df4a11..e1e3b3b68d 100644 --- a/contracts/erc20/contracts/test/UntransferrableDummyERC20Token.sol +++ b/contracts/erc20/contracts/test/UntransferrableDummyERC20Token.sol @@ -25,8 +25,6 @@ import "./DummyERC20Token.sol"; contract UntransferrableDummyERC20Token is DummyERC20Token { - bool internal _paused; - constructor ( string memory _name, string memory _symbol, diff --git a/packages/contract-wrappers/test/exchange_wrapper_test.ts b/packages/contract-wrappers/test/exchange_wrapper_test.ts index bb5930dc03..5eddd5000e 100644 --- a/packages/contract-wrappers/test/exchange_wrapper_test.ts +++ b/packages/contract-wrappers/test/exchange_wrapper_test.ts @@ -22,7 +22,7 @@ chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -describe.only('ExchangeWrapper', () => { +describe('ExchangeWrapper', () => { let contractWrappers: ContractWrappers; let userAddresses: string[]; let zrxTokenAddress: string; From fa679974246fb37c3f039772c9f99422130070cf Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 21 Mar 2019 17:17:29 +0100 Subject: [PATCH 12/23] Move order_utils from asset-buyer to order-utils package --- packages/asset-buyer/CHANGELOG.json | 9 ++ .../standard_relayer_api_order_provider.ts | 4 +- .../src/utils/buy_quote_calculator.ts | 10 +-- .../src/utils/calculate_liquidity.ts | 8 +- .../order_provider_response_processor.ts | 14 +-- packages/asset-buyer/src/utils/order_utils.ts | 74 ---------------- .../src/contract_wrappers/exchange_wrapper.ts | 2 +- .../test/exchange_wrapper_test.ts | 33 +++++++ .../src/order_calculation_utils.ts | 87 +++++++++++++++++-- 9 files changed, 144 insertions(+), 97 deletions(-) delete mode 100644 packages/asset-buyer/src/utils/order_utils.ts diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json index a4800c1d79..277658d1cb 100644 --- a/packages/asset-buyer/CHANGELOG.json +++ b/packages/asset-buyer/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "6.1.0", + "changes": [ + { + "note": "Moves order_utils into `@0x/order-utils` package as `orderCalculationUtils`", + "pr": 1714 + } + ] + }, { "timestamp": 1553183790, "version": "6.0.5", diff --git a/packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts b/packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts index 813c9923b4..4eda756977 100644 --- a/packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts +++ b/packages/asset-buyer/src/order_providers/standard_relayer_api_order_provider.ts @@ -1,4 +1,5 @@ import { HttpClient } from '@0x/connect'; +import { orderCalculationUtils } from '@0x/order-utils'; import { APIOrder, AssetPairsResponse, OrderbookResponse } from '@0x/types'; import * as _ from 'lodash'; @@ -10,7 +11,6 @@ import { SignedOrderWithRemainingFillableMakerAssetAmount, } from '../types'; import { assert } from '../utils/assert'; -import { orderUtils } from '../utils/order_utils'; export class StandardRelayerAPIOrderProvider implements OrderProvider { public readonly apiUrl: string; @@ -31,7 +31,7 @@ export class StandardRelayerAPIOrderProvider implements OrderProvider { 'remainingTakerAssetAmount', order.takerAssetAmount, ); - const remainingFillableMakerAssetAmount = orderUtils.getRemainingMakerAmount( + const remainingFillableMakerAssetAmount = orderCalculationUtils.getMakerFillAmount( order, remainingFillableTakerAssetAmount, ); diff --git a/packages/asset-buyer/src/utils/buy_quote_calculator.ts b/packages/asset-buyer/src/utils/buy_quote_calculator.ts index 1258410945..68dec0e611 100644 --- a/packages/asset-buyer/src/utils/buy_quote_calculator.ts +++ b/packages/asset-buyer/src/utils/buy_quote_calculator.ts @@ -1,4 +1,4 @@ -import { marketUtils, SignedOrder } from '@0x/order-utils'; +import { marketUtils, orderCalculationUtils, SignedOrder } from '@0x/order-utils'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; @@ -6,8 +6,6 @@ import { constants } from '../constants'; import { InsufficientAssetLiquidityError } from '../errors'; import { AssetBuyerError, BuyQuote, BuyQuoteInfo, OrdersAndFillableAmounts } from '../types'; -import { orderUtils } from './order_utils'; - // Calculates a buy quote for orders that have WETH as the takerAsset export const buyQuoteCalculator = { calculate( @@ -166,7 +164,7 @@ function findEthAmountNeededToBuyZrx( const { totalEthAmount, remainingZrxBuyAmount } = acc; const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index]; const makerFillAmount = BigNumber.min(remainingZrxBuyAmount, remainingFillableMakerAssetAmount); - const [takerFillAmount, adjustedMakerFillAmount] = orderUtils.getTakerFillAmountForFeeOrder( + const [takerFillAmount, adjustedMakerFillAmount] = orderCalculationUtils.getTakerFillAmountForFeeOrder( order, makerFillAmount, ); @@ -200,8 +198,8 @@ function findEthAndZrxAmountNeededToBuyAsset( const { totalEthAmount, totalZrxAmount, remainingAssetBuyAmount } = acc; const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index]; const makerFillAmount = BigNumber.min(acc.remainingAssetBuyAmount, remainingFillableMakerAssetAmount); - const takerFillAmount = orderUtils.getTakerFillAmount(order, makerFillAmount); - const takerFeeAmount = orderUtils.getTakerFeeAmount(order, takerFillAmount); + const takerFillAmount = orderCalculationUtils.getTakerFillAmount(order, makerFillAmount); + const takerFeeAmount = orderCalculationUtils.getTakerFeeAmount(order, takerFillAmount); return { totalEthAmount: totalEthAmount.plus(takerFillAmount), totalZrxAmount: totalZrxAmount.plus(takerFeeAmount), diff --git a/packages/asset-buyer/src/utils/calculate_liquidity.ts b/packages/asset-buyer/src/utils/calculate_liquidity.ts index a8d165b4b9..1b40e27dba 100644 --- a/packages/asset-buyer/src/utils/calculate_liquidity.ts +++ b/packages/asset-buyer/src/utils/calculate_liquidity.ts @@ -1,9 +1,8 @@ +import { orderCalculationUtils } from '@0x/order-utils'; import { BigNumber } from '@0x/utils'; import { LiquidityForAssetData, OrdersAndFillableAmounts } from '../types'; -import { orderUtils } from './order_utils'; - export const calculateLiquidity = (ordersAndFillableAmounts: OrdersAndFillableAmounts): LiquidityForAssetData => { const { orders, remainingFillableMakerAssetAmounts } = ordersAndFillableAmounts; const liquidityInBigNumbers = orders.reduce( @@ -14,7 +13,10 @@ export const calculateLiquidity = (ordersAndFillableAmounts: OrdersAndFillableAm } const tokensAvailableForCurrentOrder = availableMakerAssetAmount; - const ethValueAvailableForCurrentOrder = orderUtils.getTakerFillAmount(order, availableMakerAssetAmount); + const ethValueAvailableForCurrentOrder = orderCalculationUtils.getTakerFillAmount( + order, + availableMakerAssetAmount, + ); return { tokensAvailableInBaseUnits: acc.tokensAvailableInBaseUnits.plus(tokensAvailableForCurrentOrder), ethValueAvailableInWei: acc.ethValueAvailableInWei.plus(ethValueAvailableForCurrentOrder), diff --git a/packages/asset-buyer/src/utils/order_provider_response_processor.ts b/packages/asset-buyer/src/utils/order_provider_response_processor.ts index f08cd6150d..2dd0484990 100644 --- a/packages/asset-buyer/src/utils/order_provider_response_processor.ts +++ b/packages/asset-buyer/src/utils/order_provider_response_processor.ts @@ -1,5 +1,5 @@ import { OrderAndTraderInfo, OrderStatus, OrderValidatorWrapper } from '@0x/contract-wrappers'; -import { sortingUtils } from '@0x/order-utils'; +import { orderCalculationUtils, sortingUtils } from '@0x/order-utils'; import { RemainingFillableCalculator } from '@0x/order-utils/lib/src/remaining_fillable_calculator'; import { SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; @@ -14,8 +14,6 @@ import { SignedOrderWithRemainingFillableMakerAssetAmount, } from '../types'; -import { orderUtils } from './order_utils'; - export const orderProviderResponseProcessor = { throwIfInvalidResponse(response: OrderProviderResponse, request: OrderProviderRequest): void { const { makerAssetData, takerAssetData } = request; @@ -83,7 +81,10 @@ function filterOutExpiredAndNonOpenOrders( expiryBufferSeconds: number, ): SignedOrderWithRemainingFillableMakerAssetAmount[] { const result = _.filter(orders, order => { - return orderUtils.isOpenOrder(order) && !orderUtils.willOrderExpire(order, expiryBufferSeconds); + return ( + orderCalculationUtils.isOpenOrder(order) && + !orderCalculationUtils.willOrderExpire(order, expiryBufferSeconds) + ); }); return result; } @@ -112,7 +113,10 @@ function getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain( const transferrableAssetAmount = BigNumber.min(traderInfo.makerAllowance, traderInfo.makerBalance); const transferrableFeeAssetAmount = BigNumber.min(traderInfo.makerZrxAllowance, traderInfo.makerZrxBalance); const remainingTakerAssetAmount = order.takerAssetAmount.minus(orderInfo.orderTakerAssetFilledAmount); - const remainingMakerAssetAmount = orderUtils.getRemainingMakerAmount(order, remainingTakerAssetAmount); + const remainingMakerAssetAmount = orderCalculationUtils.getMakerFillAmount( + order, + remainingTakerAssetAmount, + ); const remainingFillableCalculator = new RemainingFillableCalculator( order.makerFee, order.makerAssetAmount, diff --git a/packages/asset-buyer/src/utils/order_utils.ts b/packages/asset-buyer/src/utils/order_utils.ts deleted file mode 100644 index 3ea3cafd33..0000000000 --- a/packages/asset-buyer/src/utils/order_utils.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; - -import { constants } from '../constants'; - -export const orderUtils = { - isOrderExpired(order: SignedOrder): boolean { - return orderUtils.willOrderExpire(order, 0); - }, - willOrderExpire(order: SignedOrder, secondsFromNow: number): boolean { - const millisecondsInSecond = 1000; - const currentUnixTimestampSec = new BigNumber(Date.now() / millisecondsInSecond).integerValue(); - return order.expirationTimeSeconds.isLessThan(currentUnixTimestampSec.plus(secondsFromNow)); - }, - isOpenOrder(order: SignedOrder): boolean { - return order.takerAddress === constants.NULL_ADDRESS; - }, - // given a remaining amount of takerAsset, calculate how much makerAsset is available - getRemainingMakerAmount(order: SignedOrder, remainingTakerAmount: BigNumber): BigNumber { - const remainingMakerAmount = remainingTakerAmount - .times(order.makerAssetAmount) - .div(order.takerAssetAmount) - .integerValue(BigNumber.ROUND_FLOOR); - return remainingMakerAmount; - }, - // given a desired amount of makerAsset, calculate how much takerAsset is required to fill that amount - getTakerFillAmount(order: SignedOrder, makerFillAmount: BigNumber): BigNumber { - // Round up because exchange rate favors Maker - const takerFillAmount = makerFillAmount - .multipliedBy(order.takerAssetAmount) - .div(order.makerAssetAmount) - .integerValue(BigNumber.ROUND_CEIL); - return takerFillAmount; - }, - // given a desired amount of takerAsset to fill, calculate how much fee is required by the taker to fill that amount - getTakerFeeAmount(order: SignedOrder, takerFillAmount: BigNumber): BigNumber { - // Round down because Taker fee rate favors Taker - const takerFeeAmount = takerFillAmount - .multipliedBy(order.takerFee) - .div(order.takerAssetAmount) - .integerValue(BigNumber.ROUND_FLOOR); - return takerFeeAmount; - }, - // given a desired amount of takerAsset to fill, calculate how much makerAsset will be filled - getMakerFillAmount(order: SignedOrder, takerFillAmount: BigNumber): BigNumber { - // Round down because exchange rate favors Maker - const makerFillAmount = takerFillAmount - .multipliedBy(order.makerAssetAmount) - .div(order.takerAssetAmount) - .integerValue(BigNumber.ROUND_FLOOR); - return makerFillAmount; - }, - // given a desired amount of makerAsset, calculate how much fee is required by the maker to fill that amount - getMakerFeeAmount(order: SignedOrder, makerFillAmount: BigNumber): BigNumber { - // Round down because Maker fee rate favors Maker - const makerFeeAmount = makerFillAmount - .multipliedBy(order.makerFee) - .div(order.makerAssetAmount) - .integerValue(BigNumber.ROUND_FLOOR); - return makerFeeAmount; - }, - // given a desired amount of ZRX from a fee order, calculate how much takerAsset is required to fill that amount - // also calculate how much ZRX needs to be bought in order fill the desired amount + takerFee - getTakerFillAmountForFeeOrder(order: SignedOrder, makerFillAmount: BigNumber): [BigNumber, BigNumber] { - // For each unit of TakerAsset we buy (MakerAsset - TakerFee) - const adjustedTakerFillAmount = makerFillAmount - .multipliedBy(order.takerAssetAmount) - .div(order.makerAssetAmount.minus(order.takerFee)) - .integerValue(BigNumber.ROUND_CEIL); - // The amount that we buy will be greater than makerFillAmount, since we buy some amount for fees. - const adjustedMakerFillAmount = orderUtils.getMakerFillAmount(order, adjustedTakerFillAmount); - return [adjustedTakerFillAmount, adjustedMakerFillAmount]; - }, -}; diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index 7b4a436d77..48946e6191 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -1211,7 +1211,7 @@ export class ExchangeWrapper extends ContractWrapper { } /** * Validate the transfer from the Maker to the Taker. This is simulated on chain - * via an eth_call. If this call fails the asset is unlikely to be transferrable. + * via an eth_call. If this call fails the asset is not transferrable. * @param signedOrder SignedOrder of interest * @param fillTakerAssetAmount Amount we'd like to fill the order for * @param takerAddress The taker of the order, defaults to signedOrder.takerAddress diff --git a/packages/contract-wrappers/test/exchange_wrapper_test.ts b/packages/contract-wrappers/test/exchange_wrapper_test.ts index 5eddd5000e..29d3bbfa3c 100644 --- a/packages/contract-wrappers/test/exchange_wrapper_test.ts +++ b/packages/contract-wrappers/test/exchange_wrapper_test.ts @@ -317,6 +317,39 @@ describe('ExchangeWrapper', () => { }), ).to.eventually.to.be.rejected(); }); + it('should throw when the maker does not have enough balance for the remaining order amount', async () => { + const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress); + // Change maker balance to have less than the order amount + const remainingBalance = makerBalance.minus(signedOrder.makerAssetAmount.minus(1)); + await web3Wrapper.awaitTransactionSuccessAsync( + await contractWrappers.erc20Token.transferAsync( + makerTokenAddress, + makerAddress, + constants.NULL_ADDRESS, + remainingBalance, + ), + ); + return expect( + contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrder), + ).to.eventually.to.be.rejected(); + }); + it('should validate the order when remaining order amount has some fillable amount', async () => { + const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress); + // Change maker balance to have less than the order amount + const remainingBalance = makerBalance.minus(signedOrder.makerAssetAmount.minus(1)); + await web3Wrapper.awaitTransactionSuccessAsync( + await contractWrappers.erc20Token.transferAsync( + makerTokenAddress, + makerAddress, + constants.NULL_ADDRESS, + remainingBalance, + ), + ); + // An amount is still transferrable + await contractWrappers.exchange.validateOrderFillableOrThrowAsync(signedOrder, { + validateRemainingOrderAmountIsFillable: false, + }); + }); it('should throw when the ERC20 token has transfer restrictions', async () => { const untransferrableToken = await DummyERC20TokenContract.deployFrom0xArtifactAsync( UntransferrableDummyERC20Token, diff --git a/packages/order-utils/src/order_calculation_utils.ts b/packages/order-utils/src/order_calculation_utils.ts index 19dd425b22..bba8855369 100644 --- a/packages/order-utils/src/order_calculation_utils.ts +++ b/packages/order-utils/src/order_calculation_utils.ts @@ -5,6 +5,48 @@ import { constants } from './constants'; import { BalanceAndAllowance } from './types'; export const orderCalculationUtils = { + /** + * Determines if the order is expired given the current time + * @param order The order for expiry calculation + */ + isOrderExpired(order: Order): boolean { + return orderCalculationUtils.willOrderExpire(order, 0); + }, + /** + * Calculates if the order will expire in the future. + * @param order The order for expiry calculation + * @param secondsFromNow The amount of seconds from current time + */ + willOrderExpire(order: Order, secondsFromNow: number): boolean { + const millisecondsInSecond = 1000; + const currentUnixTimestampSec = new BigNumber(Date.now() / millisecondsInSecond).integerValue(); + return order.expirationTimeSeconds.isLessThan(currentUnixTimestampSec.plus(secondsFromNow)); + }, + /** + * Determines if the order is open and fillable by any taker. + * @param order The order + */ + isOpenOrder(order: Order): boolean { + return order.takerAddress === constants.NULL_ADDRESS; + }, + /** + * Given an amount of taker asset, calculate the the amount of maker asset + * @param order The order + * @param makerFillAmount the amount of taker asset + */ + getMakerFillAmount(order: Order, takerFillAmount: BigNumber): BigNumber { + // Round down because exchange rate favors Maker + const makerFillAmount = takerFillAmount + .multipliedBy(order.makerAssetAmount) + .div(order.takerAssetAmount) + .integerValue(BigNumber.ROUND_FLOOR); + return makerFillAmount; + }, + /** + * Given an amount of maker asset, calculate the equivalent amount in taker asset + * @param order The order + * @param makerFillAmount the amount of maker asset + */ getTakerFillAmount(order: Order, makerFillAmount: BigNumber): BigNumber { // Round up because exchange rate favors Maker const takerFillAmount = makerFillAmount @@ -13,14 +55,47 @@ export const orderCalculationUtils = { .integerValue(BigNumber.ROUND_CEIL); return takerFillAmount; }, - // given a desired amount of takerAsset to fill, calculate how much makerAsset will be filled - getMakerFillAmount(order: Order, takerFillAmount: BigNumber): BigNumber { - // Round down because exchange rate favors Maker - const makerFillAmount = takerFillAmount - .multipliedBy(order.makerAssetAmount) + /** + * Given an amount of taker asset, calculate the fee amount required for the taker + * @param order The order + * @param takerFillAmount the amount of taker asset + */ + getTakerFeeAmount(order: Order, takerFillAmount: BigNumber): BigNumber { + // Round down because Taker fee rate favors Taker + const takerFeeAmount = takerFillAmount + .multipliedBy(order.takerFee) .div(order.takerAssetAmount) .integerValue(BigNumber.ROUND_FLOOR); - return makerFillAmount; + return takerFeeAmount; + }, + /** + * Given an amount of maker asset, calculate the fee amount required for the maker + * @param order The order + * @param makerFillAmount the amount of maker asset + */ + getMakerFeeAmount(order: Order, makerFillAmount: BigNumber): BigNumber { + // Round down because Maker fee rate favors Maker + const makerFeeAmount = makerFillAmount + .multipliedBy(order.makerFee) + .div(order.makerAssetAmount) + .integerValue(BigNumber.ROUND_FLOOR); + return makerFeeAmount; + }, + /** + * Given a desired amount of ZRX from a fee order, calculate the amount of taker asset required to fill. + * Also calculate how much ZRX needs to be purchased in order to fill the desired amount plus the taker fee amount + * @param order The order + * @param makerFillAmount the amount of maker asset + */ + getTakerFillAmountForFeeOrder(order: Order, makerFillAmount: BigNumber): [BigNumber, BigNumber] { + // For each unit of TakerAsset we buy (MakerAsset - TakerFee) + const adjustedTakerFillAmount = makerFillAmount + .multipliedBy(order.takerAssetAmount) + .div(order.makerAssetAmount.minus(order.takerFee)) + .integerValue(BigNumber.ROUND_CEIL); + // The amount that we buy will be greater than makerFillAmount, since we buy some amount for fees. + const adjustedMakerFillAmount = orderCalculationUtils.getMakerFillAmount(order, adjustedTakerFillAmount); + return [adjustedTakerFillAmount, adjustedMakerFillAmount]; }, /** * Calculates the remaining fillable amount for an order given: From f31a141d78c7735d5970f08e8de6dd680285a8be Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 21 Mar 2019 17:20:23 +0100 Subject: [PATCH 13/23] Update packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts Co-Authored-By: dekz --- .../contract-wrappers/src/contract_wrappers/exchange_wrapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index 48946e6191..dcb1e5a154 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -1142,7 +1142,7 @@ export class ExchangeWrapper extends ContractWrapper { * @param signedOrder SignedOrder of interest * @param opts ValidateOrderFillableOpts options (e.g expectedFillTakerTokenAmount. * If it isn't supplied, we check if the order is fillable for the remaining amount. - * To check if the order is fillable for any amount set validateRemainingOrderAmountIsFillable to false.) + * To check if the order is fillable for a non-zero amount, set `validateRemainingOrderAmountIsFillable` to false.) */ public async validateOrderFillableOrThrowAsync( signedOrder: SignedOrder, From 86a9a892d259ec8477363f3a6e1ba16d4aa015cc Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 21 Mar 2019 17:21:27 +0100 Subject: [PATCH 14/23] Update packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts Co-Authored-By: dekz --- .../contract-wrappers/src/contract_wrappers/exchange_wrapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index dcb1e5a154..bfb19c6c6c 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -1210,7 +1210,7 @@ export class ExchangeWrapper extends ContractWrapper { await this.validateMakerTransferThrowIfInvalidAsync(signedOrder, fillableTakerAssetAmount); } /** - * Validate the transfer from the Maker to the Taker. This is simulated on chain + * Validate the transfer from the Maker to the Taker. This is simulated on-chain * via an eth_call. If this call fails the asset is not transferrable. * @param signedOrder SignedOrder of interest * @param fillTakerAssetAmount Amount we'd like to fill the order for From a017122c4438667d57222645ab847fdbd435f135 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 21 Mar 2019 17:21:42 +0100 Subject: [PATCH 15/23] Update packages/contract-wrappers/src/types.ts Co-Authored-By: dekz --- packages/contract-wrappers/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contract-wrappers/src/types.ts b/packages/contract-wrappers/src/types.ts index e147280787..d3ee92eaa9 100644 --- a/packages/contract-wrappers/src/types.ts +++ b/packages/contract-wrappers/src/types.ts @@ -125,7 +125,7 @@ export interface ContractWrappersConfig { * takerTokenAmount. * validateRemainingOrderAmountIsFillable: The validation method ensures that the maker has a sufficient * allowance/balance to fill the entire remaining order amount. This is the default. If neither options are - * specified, the balances and allowances are checked to determine the order is fillable by any amount. + * specified, the balances and allowances are checked to determine the order is fillable for a non-zero amount. We call such orders "partially fillable orders". */ export interface ValidateOrderFillableOpts { expectedFillTakerTokenAmount?: BigNumber; From 8b8dc7ac785728ba961c54b78c6ff889bf8e43f7 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 21 Mar 2019 17:28:53 +0100 Subject: [PATCH 16/23] Update jsdoc --- .../contract-wrappers/src/contract_wrappers/exchange_wrapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index bfb19c6c6c..302c62dd56 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -1211,7 +1211,7 @@ export class ExchangeWrapper extends ContractWrapper { } /** * Validate the transfer from the Maker to the Taker. This is simulated on-chain - * via an eth_call. If this call fails the asset is not transferrable. + * via an eth_call. If this call fails, the asset is currently nontransferable. * @param signedOrder SignedOrder of interest * @param fillTakerAssetAmount Amount we'd like to fill the order for * @param takerAddress The taker of the order, defaults to signedOrder.takerAddress From 0bf46bfcb5528881ff711ba052615f6f58366839 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 22 Mar 2019 15:59:57 +0100 Subject: [PATCH 17/23] Re-use order relevant state --- .../src/contract_wrappers/exchange_wrapper.ts | 34 ++------ .../src/order_calculation_utils.ts | 85 ------------------- 2 files changed, 8 insertions(+), 111 deletions(-) diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index 302c62dd56..5fbb827e81 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -7,6 +7,7 @@ import { ExchangeTransferSimulator, orderCalculationUtils, orderHashUtils, + OrderStateUtils, OrderValidationUtils, } from '@0x/order-utils'; import { AssetProxyId, Order, SignedOrder } from '@0x/types'; @@ -1158,46 +1159,27 @@ export class ExchangeWrapper extends ContractWrapper { ); const balanceAllowanceStore = new BalanceAndProxyAllowanceLazyStore(balanceAllowanceFetcher); const exchangeTradeSimulator = new ExchangeTransferSimulator(balanceAllowanceStore); - const filledCancelledFetcher = new OrderFilledCancelledFetcher(this, BlockParamLiteral.Latest); let fillableTakerAssetAmount; const shouldValidateRemainingOrderAmountIsFillable = _.isUndefined(opts.validateRemainingOrderAmountIsFillable) ? true : opts.validateRemainingOrderAmountIsFillable; - const filledTakerTokenAmount = await this.getFilledTakerAssetAmountAsync( - orderHashUtils.getOrderHashHex(signedOrder), - ); if (opts.expectedFillTakerTokenAmount) { // If the caller has specified a taker fill amount, we use this for all validation fillableTakerAssetAmount = opts.expectedFillTakerTokenAmount; } else if (shouldValidateRemainingOrderAmountIsFillable) { // Historically if a fill amount was not specified we would default to the amount // left on the order. + const filledTakerTokenAmount = await this.getFilledTakerAssetAmountAsync( + orderHashUtils.getOrderHashHex(signedOrder), + ); fillableTakerAssetAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount); } else { - const makerAssetBalance = await balanceAllowanceStore.getBalanceAsync( - signedOrder.makerAssetData, - signedOrder.makerAddress, - ); - const makerAssetAllowance = await balanceAllowanceStore.getProxyAllowanceAsync( - signedOrder.makerAssetData, - signedOrder.makerAddress, - ); - const makerZRXBalance = await balanceAllowanceStore.getBalanceAsync( - this.getZRXAssetData(), - signedOrder.makerAddress, - ); - const makerZRXAllowance = await balanceAllowanceStore.getProxyAllowanceAsync( - this.getZRXAssetData(), - signedOrder.makerAddress, - ); - fillableTakerAssetAmount = orderCalculationUtils.calculateRemainingFillableTakerAssetAmount( - signedOrder, - filledTakerTokenAmount, - { balance: makerAssetBalance, allowance: makerAssetAllowance }, - { balance: makerZRXBalance, allowance: makerZRXAllowance }, - ); + const orderStateUtils = new OrderStateUtils(balanceAllowanceStore, filledCancelledFetcher); + // Calculate the taker amount fillable given the maker balance and allowance + const orderRelevantState = await orderStateUtils.getOpenOrderRelevantStateAsync(signedOrder); + fillableTakerAssetAmount = orderRelevantState.remainingFillableTakerAssetAmount; } const orderValidationUtils = new OrderValidationUtils(filledCancelledFetcher, this._web3Wrapper.getProvider()); diff --git a/packages/order-utils/src/order_calculation_utils.ts b/packages/order-utils/src/order_calculation_utils.ts index bba8855369..b1d491c991 100644 --- a/packages/order-utils/src/order_calculation_utils.ts +++ b/packages/order-utils/src/order_calculation_utils.ts @@ -97,89 +97,4 @@ export const orderCalculationUtils = { const adjustedMakerFillAmount = orderCalculationUtils.getMakerFillAmount(order, adjustedTakerFillAmount); return [adjustedTakerFillAmount, adjustedMakerFillAmount]; }, - /** - * Calculates the remaining fillable amount for an order given: - * order filled amount - * asset balance/allowance (maker/taker) - * ZRX fee balance/allowance (maker/taker) - * Taker values are checked if specified in the order. For example, if the maker does not - * have sufficient ZRX allowance to pay the fee, then this function will return the maximum - * amount that can be filled given the maker's ZRX allowance - * @param order The order - * @param takerAssetFilledAmount The amount currently filled on the order - * @param makerAssetBalanceAllowance The makerAsset balance and allowance of the maker - * @param makerZRXBalanceAllowance The ZRX balance and allowance of the maker - * @param takerAssetBalanceAllowance The takerAsset balance and allowance of the taker - * @param takerZRXBalanceAllowance The ZRX balance and allowance of the taker - */ - calculateRemainingFillableTakerAssetAmount( - order: Order, - takerAssetFilledAmount: BigNumber, - makerAssetBalanceAllowance: BalanceAndAllowance, - makerZRXBalanceAllowance: BalanceAndAllowance, - takerAssetBalanceAllowance?: BalanceAndAllowance, - takerZRXBalanceAllowance?: BalanceAndAllowance, - ): BigNumber { - const minSet = []; - - // Calculate min of balance & allowance of taker's takerAsset - if (order.takerAddress !== constants.NULL_ADDRESS) { - if (takerAssetBalanceAllowance && takerZRXBalanceAllowance) { - const maxTakerAssetFillAmountGivenTakerConstraints = BigNumber.min( - takerAssetBalanceAllowance.balance, - takerAssetBalanceAllowance.allowance, - ); - minSet.push( - maxTakerAssetFillAmountGivenTakerConstraints, - takerAssetBalanceAllowance.balance, - takerAssetBalanceAllowance.allowance, - ); - } - } - - // Calculate min of balance & allowance of maker's makerAsset -> translate into takerAsset amount - const maxMakerAssetFillAmount = BigNumber.min( - makerAssetBalanceAllowance.balance, - makerAssetBalanceAllowance.allowance, - ); - const maxTakerAssetFillAmountGivenMakerConstraints = orderCalculationUtils.getTakerFillAmount( - order, - maxMakerAssetFillAmount, - ); - minSet.push(maxTakerAssetFillAmountGivenMakerConstraints); - - // Calculate min of balance & allowance of taker's ZRX -> translate into takerAsset amount - if (!order.takerFee.eq(0) && order.takerAddress !== constants.NULL_ADDRESS) { - if (takerAssetBalanceAllowance && takerZRXBalanceAllowance) { - const takerZRXAvailable = BigNumber.min( - takerZRXBalanceAllowance.balance, - takerZRXBalanceAllowance.allowance, - ); - const maxTakerAssetFillAmountGivenTakerZRXConstraints = takerZRXAvailable - .multipliedBy(order.takerAssetAmount) - .div(order.takerFee) - .integerValue(BigNumber.ROUND_CEIL); // Should this round to ciel or floor? - minSet.push(maxTakerAssetFillAmountGivenTakerZRXConstraints); - } - } - - // Calculate min of balance & allowance of maker's ZRX -> translate into takerAsset amount - if (!order.makerFee.eq(0)) { - const makerZRXAvailable = BigNumber.min( - makerZRXBalanceAllowance.balance, - makerZRXBalanceAllowance.allowance, - ); - const maxTakerAssetFillAmountGivenMakerZRXConstraints = makerZRXAvailable - .multipliedBy(order.takerAssetAmount) - .div(order.makerFee) - .integerValue(BigNumber.ROUND_CEIL); // Should this round to ciel or floor? - minSet.push(maxTakerAssetFillAmountGivenMakerZRXConstraints); - } - - const remainingTakerAssetFillAmount = order.takerAssetAmount.minus(takerAssetFilledAmount); - minSet.push(remainingTakerAssetFillAmount); - - const maxTakerAssetFillAmount = BigNumber.min(...minSet); - return maxTakerAssetFillAmount; - }, }; From 6b5ef10467feffb4fe0ea2f57f63c0b08d183852 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 22 Mar 2019 16:26:43 +0100 Subject: [PATCH 18/23] Remove unused imports --- packages/order-utils/src/index.ts | 1 - packages/order-utils/src/order_calculation_utils.ts | 1 - packages/order-utils/src/types.ts | 5 ----- 3 files changed, 7 deletions(-) diff --git a/packages/order-utils/src/index.ts b/packages/order-utils/src/index.ts index f297065183..3d90e7043b 100644 --- a/packages/order-utils/src/index.ts +++ b/packages/order-utils/src/index.ts @@ -75,5 +75,4 @@ export { FindOrdersThatCoverMakerAssetFillAmountOpts, FeeOrdersAndRemainingFeeAmount, OrdersAndRemainingFillAmount, - BalanceAndAllowance, } from './types'; diff --git a/packages/order-utils/src/order_calculation_utils.ts b/packages/order-utils/src/order_calculation_utils.ts index b1d491c991..d6818830f7 100644 --- a/packages/order-utils/src/order_calculation_utils.ts +++ b/packages/order-utils/src/order_calculation_utils.ts @@ -2,7 +2,6 @@ import { Order } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { constants } from './constants'; -import { BalanceAndAllowance } from './types'; export const orderCalculationUtils = { /** diff --git a/packages/order-utils/src/types.ts b/packages/order-utils/src/types.ts index bf1169e5a8..55ec553dbd 100644 --- a/packages/order-utils/src/types.ts +++ b/packages/order-utils/src/types.ts @@ -64,8 +64,3 @@ export interface OrdersAndRemainingFillAmount { ordersRemainingFillableMakerAssetAmounts: BigNumber[]; remainingFillAmount: BigNumber; } - -export interface BalanceAndAllowance { - balance: BigNumber; - allowance: BigNumber; -} From 85a7efbd6197bd52a366218d85515a23db7afa13 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 22 Mar 2019 16:51:43 +0100 Subject: [PATCH 19/23] Change to accept maker amount --- packages/asset-buyer/CHANGELOG.json | 2 +- .../src/contract_wrappers/exchange_wrapper.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json index 277658d1cb..a688bb8eb1 100644 --- a/packages/asset-buyer/CHANGELOG.json +++ b/packages/asset-buyer/CHANGELOG.json @@ -3,7 +3,7 @@ "version": "6.1.0", "changes": [ { - "note": "Moves order_utils into `@0x/order-utils` package as `orderCalculationUtils`", + "note": "Moved order_utils into `@0x/order-utils` package as `orderCalculationUtils`", "pr": 1714 } ] diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index 5fbb827e81..e773cfada6 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -1189,18 +1189,19 @@ export class ExchangeWrapper extends ContractWrapper { this.getZRXAssetData(), fillableTakerAssetAmount, ); - await this.validateMakerTransferThrowIfInvalidAsync(signedOrder, fillableTakerAssetAmount); + const makerTransferAmount = orderCalculationUtils.getMakerFillAmount(signedOrder, fillableTakerAssetAmount); + await this.validateMakerTransferThrowIfInvalidAsync(signedOrder, makerTransferAmount); } /** - * Validate the transfer from the Maker to the Taker. This is simulated on-chain + * Validate the transfer from the maker to the taker. This is simulated on-chain * via an eth_call. If this call fails, the asset is currently nontransferable. * @param signedOrder SignedOrder of interest - * @param fillTakerAssetAmount Amount we'd like to fill the order for - * @param takerAddress The taker of the order, defaults to signedOrder.takerAddress + * @param makerAssetAmount Amount to transfer from the maker + * @param takerAddress The address to transfer to, defaults to signedOrder.takerAddress */ public async validateMakerTransferThrowIfInvalidAsync( signedOrder: SignedOrder, - fillTakerAssetAmount: BigNumber, + makerAssetAmount: BigNumber, takerAddress?: string, ): Promise { const toAddress = _.isUndefined(takerAddress) ? signedOrder.takerAddress : takerAddress; @@ -1213,19 +1214,18 @@ export class ExchangeWrapper extends ContractWrapper { assetProxyAddress, this._web3Wrapper.getProvider(), ); - const makerTransferAmount = orderCalculationUtils.getMakerFillAmount(signedOrder, fillTakerAssetAmount); const result = await assetProxy.transferFrom.callAsync( makerAssetData, signedOrder.makerAddress, toAddress, - makerTransferAmount, + makerAssetAmount, { from: this.address, }, ); if (result !== undefined) { - throw new Error('Unknown error occured during maker transfer simulation'); + throw new Error(`Error during maker transfer simulation: ${result}`); } } /** From 6c8d4dcc1ecf040bdce02a9d578481c282fe0e6a Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Tue, 26 Mar 2019 09:52:39 +0100 Subject: [PATCH 20/23] Allow simulation taker address to be specified --- .../src/contract_wrappers/exchange_wrapper.ts | 9 ++++++--- packages/contract-wrappers/src/types.ts | 15 +++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts index e773cfada6..d001e08602 100644 --- a/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/contract-wrappers/src/contract_wrappers/exchange_wrapper.ts @@ -1169,8 +1169,7 @@ export class ExchangeWrapper extends ContractWrapper { // If the caller has specified a taker fill amount, we use this for all validation fillableTakerAssetAmount = opts.expectedFillTakerTokenAmount; } else if (shouldValidateRemainingOrderAmountIsFillable) { - // Historically if a fill amount was not specified we would default to the amount - // left on the order. + // Default behaviour is to validate the amount left on the order. const filledTakerTokenAmount = await this.getFilledTakerAssetAmountAsync( orderHashUtils.getOrderHashHex(signedOrder), ); @@ -1190,7 +1189,11 @@ export class ExchangeWrapper extends ContractWrapper { fillableTakerAssetAmount, ); const makerTransferAmount = orderCalculationUtils.getMakerFillAmount(signedOrder, fillableTakerAssetAmount); - await this.validateMakerTransferThrowIfInvalidAsync(signedOrder, makerTransferAmount); + await this.validateMakerTransferThrowIfInvalidAsync( + signedOrder, + makerTransferAmount, + opts.simulationTakerAddress, + ); } /** * Validate the transfer from the maker to the taker. This is simulated on-chain diff --git a/packages/contract-wrappers/src/types.ts b/packages/contract-wrappers/src/types.ts index d3ee92eaa9..955b59713e 100644 --- a/packages/contract-wrappers/src/types.ts +++ b/packages/contract-wrappers/src/types.ts @@ -120,16 +120,19 @@ export interface ContractWrappersConfig { } /** - * expectedFillTakerTokenAmount: If specified, the validation method will ensure that the - * supplied order maker has a sufficient allowance/balance to fill this amount of the order's - * takerTokenAmount. - * validateRemainingOrderAmountIsFillable: The validation method ensures that the maker has a sufficient - * allowance/balance to fill the entire remaining order amount. This is the default. If neither options are - * specified, the balances and allowances are checked to determine the order is fillable for a non-zero amount. We call such orders "partially fillable orders". + * expectedFillTakerTokenAmount: If specified, the validation method will ensure that the supplied order maker has a sufficient + * allowance/balance to fill this amount of the order's takerTokenAmount. + * validateRemainingOrderAmountIsFillable: The validation method ensures that the maker has a sufficient allowance/balance to fill + * the entire remaining order amount. This is the default. If neither options are specified, + * the balances and allowances are checked to determine the order is fillable for a + * non-zero amount. We call such orders "partially fillable orders". + * simulationTakerAddress: During the maker transfer simulation validation, tokens are sent from the maker to the simulationTakerAddress. This defaults + * to the takerAddress specified in the order. Some tokens prevent transfer to the NULL address so this address can be specified. */ export interface ValidateOrderFillableOpts { expectedFillTakerTokenAmount?: BigNumber; validateRemainingOrderAmountIsFillable?: boolean; + simulationTakerAddress?: string; } /** From 50835e317f43f428b66b51c012005df70f946e47 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Tue, 26 Mar 2019 11:42:51 +0100 Subject: [PATCH 21/23] Update CHANGELOG --- contracts/erc20/CHANGELOG.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/contracts/erc20/CHANGELOG.json b/contracts/erc20/CHANGELOG.json index 05ed5303e1..1e43197f55 100644 --- a/contracts/erc20/CHANGELOG.json +++ b/contracts/erc20/CHANGELOG.json @@ -1,14 +1,19 @@ [ + { + "version": "2.2.0", + "changes": [ + { + "note": "Added UntransferrableDummyERC20Token", + "pr": 1714 + } + ] + }, { "version": "2.1.0", "changes": [ { "note": "Run Web3ProviderEngine without excess block polling", "pr": 1695 - }, - { - "note": "Added UntransferrableDummyERC20Token", - "pr": 1714 } ], "timestamp": 1553183790 From 1f2214f89168bd3a3867acca84474e480728cc66 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 28 Mar 2019 14:19:33 +0100 Subject: [PATCH 22/23] Major bump as this change could break existing orderbooks --- packages/contract-wrappers/CHANGELOG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index aeadd419d5..ca670ed464 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,6 +1,6 @@ [ { - "version": "8.1.0", + "version": "9.0.0", "changes": [ { "note": "Added a simulation for `exchange.validateOrderFillableOrThrowAsync` that simulates maker transfer", From d8bfc92cc5d30f495a069aafce9da5cc7fc38a54 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 28 Mar 2019 14:49:22 +0100 Subject: [PATCH 23/23] Update documentation and changelog --- packages/contract-wrappers/CHANGELOG.json | 6 +++++- packages/contract-wrappers/src/types.ts | 17 ++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index ca670ed464..179e897b41 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -3,7 +3,11 @@ "version": "9.0.0", "changes": [ { - "note": "Added a simulation for `exchange.validateOrderFillableOrThrowAsync` that simulates maker transfer", + "note": "Added a simulation to transfer from maker to taker during `exchange.validateOrderFillableOrThrowAsync`", + "pr": 1714 + }, + { + "note": "Added additional properties to `ValidateOrderFillableOpts`. An order can now be validated to fill a non-zero amount by specifying `validateRemainingOrderAmountIsFillable` as `false`. The default `true` will continue to validate the entire remaining balance is fillable.", "pr": 1714 } ] diff --git a/packages/contract-wrappers/src/types.ts b/packages/contract-wrappers/src/types.ts index 955b59713e..624b9ac301 100644 --- a/packages/contract-wrappers/src/types.ts +++ b/packages/contract-wrappers/src/types.ts @@ -120,14 +120,17 @@ export interface ContractWrappersConfig { } /** - * expectedFillTakerTokenAmount: If specified, the validation method will ensure that the supplied order maker has a sufficient + * `expectedFillTakerTokenAmount`: If specified, the validation method will ensure that the supplied order maker has a sufficient * allowance/balance to fill this amount of the order's takerTokenAmount. - * validateRemainingOrderAmountIsFillable: The validation method ensures that the maker has a sufficient allowance/balance to fill - * the entire remaining order amount. This is the default. If neither options are specified, - * the balances and allowances are checked to determine the order is fillable for a - * non-zero amount. We call such orders "partially fillable orders". - * simulationTakerAddress: During the maker transfer simulation validation, tokens are sent from the maker to the simulationTakerAddress. This defaults - * to the takerAddress specified in the order. Some tokens prevent transfer to the NULL address so this address can be specified. + * + * `validateRemainingOrderAmountIsFillable`: The validation method ensures that the maker has sufficient allowance/balance to fill + * the entire remaining order amount. If this option is set to false, the balances + * and allowances are calculated to determine the order is fillable for a + * non-zero amount (some value less than or equal to the order remaining amount). + * We call such orders "partially fillable orders". Default is `true`. + * + * `simulationTakerAddress`: During the maker transfer simulation, tokens are sent from the maker to the `simulationTakerAddress`. This defaults + * to the `takerAddress` specified in the order. Some tokens prevent transfer to the NULL address so this address can be specified. */ export interface ValidateOrderFillableOpts { expectedFillTakerTokenAmount?: BigNumber;