diff options
Diffstat (limited to 'packages/0x.js')
63 files changed, 10080 insertions, 10083 deletions
diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index 10d923501..67d6452b5 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -1,97 +1,94 @@ { - "name": "0x.js", - "version": "0.30.2", - "description": "A javascript library for interacting with the 0x protocol", - "keywords": [ - "0x.js", - "0xproject", - "ethereum", - "tokens", - "exchange" - ], - "main": "lib/src/index.js", - "types": "lib/src/index.d.ts", - "scripts": { - "prebuild": "run-s clean generate_contract_wrappers", - "build": "run-p build:umd:prod build:commonjs; exit 0;", - "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR", - "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json", - "generate_contract_wrappers": "node ../abi-gen/lib/index.js --abiGlob 'src/artifacts/@(Exchange|Token|TokenTransferProxy|EtherToken|TokenRegistry|DummyToken).json' --templates contract_templates --output src/contract_wrappers/generated", - "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", - "test:circleci": "run-s test:coverage report_test_coverage", - "test": "run-s clean test:commonjs", - "test:coverage": "nyc npm run test --all", - "report_test_coverage": "nyc report --reporter=text-lcov | coveralls", - "update_contracts": "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../0x.js/src/artifacts; done;", - "clean": "shx rm -rf _bundles lib test_temp", - "build:umd:prod": "NODE_ENV=production webpack", - "build:commonjs": "tsc && copyfiles -u 2 './src/artifacts/**/*.json' ./lib/src/artifacts;", - "test:commonjs": "run-s build:commonjs run_mocha", - "run_mocha": "mocha lib/test/**/*_test.js --timeout 10000 --bail --exit" - }, - "config": { - "artifacts": "TokenTransferProxy Exchange TokenRegistry Token EtherToken" - }, - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x.js" - }, - "license": "Apache-2.0", - "engines": { - "node": ">=6.0.0" - }, - "devDependencies": { - "@0xproject/abi-gen": "^0.1.4", - "@0xproject/dev-utils": "^0.0.7", - "@0xproject/tslint-config": "^0.4.4", - "@types/bintrees": "^1.0.2", - "@types/jsonschema": "^1.1.1", - "@types/lodash": "^4.14.86", - "@types/mocha": "^2.2.42", - "@types/node": "^8.0.53", - "@types/sinon": "^2.2.2", - "@types/uuid": "^3.4.2", - "awesome-typescript-loader": "^3.1.3", - "chai": "^4.0.1", - "chai-as-promised": "^7.1.0", - "chai-as-promised-typescript-typings": "^0.0.6", - "chai-bignumber": "^2.0.1", - "chai-typescript-typings": "^0.0.2", - "copyfiles": "^1.2.0", - "coveralls": "^3.0.0", - "dirty-chai": "^2.0.1", - "json-loader": "^0.5.4", - "mocha": "^4.0.1", - "npm-run-all": "^4.1.2", - "nyc": "^11.0.1", - "opn-cli": "^3.1.0", - "request": "^2.81.0", - "request-promise-native": "^1.0.4", - "shx": "^0.2.2", - "sinon": "^4.0.0", - "source-map-support": "^0.5.0", - "truffle-hdwallet-provider": "^0.0.3", - "tslint": "5.8.0", - "typedoc": "~0.8.0", - "typescript": "~2.6.1", - "web3-provider-engine": "^13.0.1", - "web3-typescript-typings": "^0.9.6", - "webpack": "^3.1.0" - }, - "dependencies": { - "@0xproject/assert": "^0.0.13", - "@0xproject/json-schemas": "^0.7.5", - "@0xproject/types": "^0.1.6", - "@0xproject/utils": "^0.2.2", - "@0xproject/web3-wrapper": "^0.1.7", - "bintrees": "^1.0.2", - "bn.js": "^4.11.8", - "ethereumjs-abi": "^0.6.4", - "ethereumjs-blockstream": "^2.0.6", - "ethereumjs-util": "^5.1.1", - "js-sha3": "^0.6.1", - "lodash": "^4.17.4", - "uuid": "^3.1.0", - "web3": "^0.20.0" - } + "name": "0x.js", + "version": "0.30.2", + "description": "A javascript library for interacting with the 0x protocol", + "keywords": ["0x.js", "0xproject", "ethereum", "tokens", "exchange"], + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", + "scripts": { + "prebuild": "run-s clean generate_contract_wrappers", + "build": "run-p build:umd:prod build:commonjs; exit 0;", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR", + "upload_docs_json": + "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json", + "generate_contract_wrappers": + "node ../abi-gen/lib/index.js --abiGlob 'src/artifacts/@(Exchange|Token|TokenTransferProxy|EtherToken|TokenRegistry|DummyToken).json' --templates contract_templates --output src/contract_wrappers/generated", + "lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'", + "test:circleci": "run-s test:coverage report_test_coverage", + "test": "run-s clean test:commonjs", + "test:coverage": "nyc npm run test --all", + "report_test_coverage": "nyc report --reporter=text-lcov | coveralls", + "update_contracts": + "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../0x.js/src/artifacts; done;", + "clean": "shx rm -rf _bundles lib test_temp", + "build:umd:prod": "NODE_ENV=production webpack", + "build:commonjs": "tsc && copyfiles -u 2 './src/artifacts/**/*.json' ./lib/src/artifacts;", + "test:commonjs": "run-s build:commonjs run_mocha", + "run_mocha": "mocha lib/test/**/*_test.js --timeout 10000 --bail --exit" + }, + "config": { + "artifacts": "TokenTransferProxy Exchange TokenRegistry Token EtherToken" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x.js" + }, + "license": "Apache-2.0", + "engines": { + "node": ">=6.0.0" + }, + "devDependencies": { + "@0xproject/abi-gen": "^0.1.4", + "@0xproject/dev-utils": "^0.0.7", + "@0xproject/tslint-config": "^0.4.4", + "@types/bintrees": "^1.0.2", + "@types/jsonschema": "^1.1.1", + "@types/lodash": "^4.14.86", + "@types/mocha": "^2.2.42", + "@types/node": "^8.0.53", + "@types/sinon": "^2.2.2", + "@types/uuid": "^3.4.2", + "awesome-typescript-loader": "^3.1.3", + "chai": "^4.0.1", + "chai-as-promised": "^7.1.0", + "chai-as-promised-typescript-typings": "^0.0.6", + "chai-bignumber": "^2.0.1", + "chai-typescript-typings": "^0.0.2", + "copyfiles": "^1.2.0", + "coveralls": "^3.0.0", + "dirty-chai": "^2.0.1", + "json-loader": "^0.5.4", + "mocha": "^4.0.1", + "npm-run-all": "^4.1.2", + "nyc": "^11.0.1", + "opn-cli": "^3.1.0", + "request": "^2.81.0", + "request-promise-native": "^1.0.4", + "shx": "^0.2.2", + "sinon": "^4.0.0", + "source-map-support": "^0.5.0", + "truffle-hdwallet-provider": "^0.0.3", + "tslint": "5.8.0", + "typedoc": "~0.8.0", + "typescript": "~2.6.1", + "web3-provider-engine": "^13.0.1", + "web3-typescript-typings": "^0.9.6", + "webpack": "^3.1.0" + }, + "dependencies": { + "@0xproject/assert": "^0.0.13", + "@0xproject/json-schemas": "^0.7.5", + "@0xproject/types": "^0.1.6", + "@0xproject/utils": "^0.2.2", + "@0xproject/web3-wrapper": "^0.1.7", + "bintrees": "^1.0.2", + "bn.js": "^4.11.8", + "ethereumjs-abi": "^0.6.4", + "ethereumjs-blockstream": "^2.0.6", + "ethereumjs-util": "^5.1.1", + "js-sha3": "^0.6.1", + "lodash": "^4.17.4", + "uuid": "^3.1.0", + "web3": "^0.20.0" + } } diff --git a/packages/0x.js/src/0x.ts b/packages/0x.js/src/0x.ts index f8a484c5d..dec26e3eb 100644 --- a/packages/0x.js/src/0x.ts +++ b/packages/0x.js/src/0x.ts @@ -13,13 +13,13 @@ import { TokenWrapper } from './contract_wrappers/token_wrapper'; import { OrderStateWatcher } from './order_watcher/order_state_watcher'; import { zeroExConfigSchema } from './schemas/zero_ex_config_schema'; import { - ECSignature, - Order, - SignedOrder, - TransactionReceiptWithDecodedLogs, - Web3Provider, - ZeroExConfig, - ZeroExError, + ECSignature, + Order, + SignedOrder, + TransactionReceiptWithDecodedLogs, + Web3Provider, + ZeroExConfig, + ZeroExError, } from './types'; import { AbiDecoder } from './utils/abi_decoder'; import { assert } from './utils/assert'; @@ -33,318 +33,318 @@ import { utils } from './utils/utils'; * and all calls to the library should be made through a ZeroEx instance. */ export class ZeroEx { - /** - * When creating an order without a specified taker or feeRecipient you must supply the Solidity - * address null type (as opposed to Javascripts `null`, `undefined` or empty string). We expose - * this constant for your convenience. - */ - public static NULL_ADDRESS = constants.NULL_ADDRESS; + /** + * When creating an order without a specified taker or feeRecipient you must supply the Solidity + * address null type (as opposed to Javascripts `null`, `undefined` or empty string). We expose + * this constant for your convenience. + */ + public static NULL_ADDRESS = constants.NULL_ADDRESS; - /** - * An instance of the ExchangeWrapper class containing methods for interacting with the 0x Exchange smart contract. - */ - public exchange: ExchangeWrapper; - /** - * An instance of the TokenRegistryWrapper class containing methods for interacting with the 0x - * TokenRegistry smart contract. - */ - public tokenRegistry: TokenRegistryWrapper; - /** - * An instance of the TokenWrapper class containing methods for interacting with any ERC20 token smart contract. - */ - public token: TokenWrapper; - /** - * An instance of the EtherTokenWrapper class containing methods for interacting with the - * wrapped ETH ERC20 token smart contract. - */ - public etherToken: EtherTokenWrapper; - /** - * An instance of the TokenTransferProxyWrapper class containing methods for interacting with the - * tokenTransferProxy smart contract. - */ - public proxy: TokenTransferProxyWrapper; - /** - * An instance of the OrderStateWatcher class containing methods for watching a set of orders for relevant - * blockchain state changes. - */ - public orderStateWatcher: OrderStateWatcher; - private _web3Wrapper: Web3Wrapper; - private _abiDecoder: AbiDecoder; - /** - * Verifies that the elliptic curve signature `signature` was generated - * by signing `data` with the private key corresponding to the `signerAddress` address. - * @param data The hex encoded data signed by the supplied signature. - * @param signature An object containing the elliptic curve signature parameters. - * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. - * @return Whether the signature is valid for the supplied signerAddress and data. - */ - public static isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean { - assert.isHexString('data', data); - assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema); - assert.isETHAddressHex('signerAddress', signerAddress); + /** + * An instance of the ExchangeWrapper class containing methods for interacting with the 0x Exchange smart contract. + */ + public exchange: ExchangeWrapper; + /** + * An instance of the TokenRegistryWrapper class containing methods for interacting with the 0x + * TokenRegistry smart contract. + */ + public tokenRegistry: TokenRegistryWrapper; + /** + * An instance of the TokenWrapper class containing methods for interacting with any ERC20 token smart contract. + */ + public token: TokenWrapper; + /** + * An instance of the EtherTokenWrapper class containing methods for interacting with the + * wrapped ETH ERC20 token smart contract. + */ + public etherToken: EtherTokenWrapper; + /** + * An instance of the TokenTransferProxyWrapper class containing methods for interacting with the + * tokenTransferProxy smart contract. + */ + public proxy: TokenTransferProxyWrapper; + /** + * An instance of the OrderStateWatcher class containing methods for watching a set of orders for relevant + * blockchain state changes. + */ + public orderStateWatcher: OrderStateWatcher; + private _web3Wrapper: Web3Wrapper; + private _abiDecoder: AbiDecoder; + /** + * Verifies that the elliptic curve signature `signature` was generated + * by signing `data` with the private key corresponding to the `signerAddress` address. + * @param data The hex encoded data signed by the supplied signature. + * @param signature An object containing the elliptic curve signature parameters. + * @param signerAddress The hex encoded address that signed the data, producing the supplied signature. + * @return Whether the signature is valid for the supplied signerAddress and data. + */ + public static isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean { + assert.isHexString('data', data); + assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema); + assert.isETHAddressHex('signerAddress', signerAddress); - const isValidSignature = signatureUtils.isValidSignature(data, signature, signerAddress); - return isValidSignature; - } - /** - * Generates a pseudo-random 256-bit salt. - * The salt can be included in an 0x order, ensuring that the order generates a unique orderHash - * and will not collide with other outstanding orders that are identical in all other parameters. - * @return A pseudo-random 256-bit number that can be used as a salt. - */ - public static generatePseudoRandomSalt(): BigNumber { - // BigNumber.random returns a pseudo-random number between 0 & 1 with a passed in number of decimal places. - // Source: https://mikemcl.github.io/bignumber.js/#random - const randomNumber = BigNumber.random(constants.MAX_DIGITS_IN_UNSIGNED_256_INT); - const factor = new BigNumber(10).pow(constants.MAX_DIGITS_IN_UNSIGNED_256_INT - 1); - const salt = randomNumber.times(factor).round(); - return salt; - } - /** - * Checks if the supplied hex encoded order hash is valid. - * Note: Valid means it has the expected format, not that an order with the orderHash exists. - * Use this method when processing orderHashes submitted as user input. - * @param orderHash Hex encoded orderHash. - * @return Whether the supplied orderHash has the expected format. - */ - public static isValidOrderHash(orderHash: string): boolean { - // Since this method can be called to check if any arbitrary string conforms to an orderHash's - // format, we only assert that we were indeed passed a string. - assert.isString('orderHash', orderHash); - const schemaValidator = new SchemaValidator(); - const isValidOrderHash = schemaValidator.validate(orderHash, schemas.orderHashSchema).valid; - return isValidOrderHash; - } - /** - * A unit amount is defined as the amount of a token above the specified decimal places (integer part). - * E.g: If a currency has 18 decimal places, 1e18 or one quintillion of the currency is equivalent - * to 1 unit. - * @param amount The amount in baseUnits that you would like converted to units. - * @param decimals The number of decimal places the unit amount has. - * @return The amount in units. - */ - public static toUnitAmount(amount: BigNumber, decimals: number): BigNumber { - assert.isValidBaseUnitAmount('amount', amount); - assert.isNumber('decimals', decimals); + const isValidSignature = signatureUtils.isValidSignature(data, signature, signerAddress); + return isValidSignature; + } + /** + * Generates a pseudo-random 256-bit salt. + * The salt can be included in an 0x order, ensuring that the order generates a unique orderHash + * and will not collide with other outstanding orders that are identical in all other parameters. + * @return A pseudo-random 256-bit number that can be used as a salt. + */ + public static generatePseudoRandomSalt(): BigNumber { + // BigNumber.random returns a pseudo-random number between 0 & 1 with a passed in number of decimal places. + // Source: https://mikemcl.github.io/bignumber.js/#random + const randomNumber = BigNumber.random(constants.MAX_DIGITS_IN_UNSIGNED_256_INT); + const factor = new BigNumber(10).pow(constants.MAX_DIGITS_IN_UNSIGNED_256_INT - 1); + const salt = randomNumber.times(factor).round(); + return salt; + } + /** + * Checks if the supplied hex encoded order hash is valid. + * Note: Valid means it has the expected format, not that an order with the orderHash exists. + * Use this method when processing orderHashes submitted as user input. + * @param orderHash Hex encoded orderHash. + * @return Whether the supplied orderHash has the expected format. + */ + public static isValidOrderHash(orderHash: string): boolean { + // Since this method can be called to check if any arbitrary string conforms to an orderHash's + // format, we only assert that we were indeed passed a string. + assert.isString('orderHash', orderHash); + const schemaValidator = new SchemaValidator(); + const isValidOrderHash = schemaValidator.validate(orderHash, schemas.orderHashSchema).valid; + return isValidOrderHash; + } + /** + * A unit amount is defined as the amount of a token above the specified decimal places (integer part). + * E.g: If a currency has 18 decimal places, 1e18 or one quintillion of the currency is equivalent + * to 1 unit. + * @param amount The amount in baseUnits that you would like converted to units. + * @param decimals The number of decimal places the unit amount has. + * @return The amount in units. + */ + public static toUnitAmount(amount: BigNumber, decimals: number): BigNumber { + assert.isValidBaseUnitAmount('amount', amount); + assert.isNumber('decimals', decimals); - const aUnit = new BigNumber(10).pow(decimals); - const unit = amount.div(aUnit); - return unit; - } - /** - * A baseUnit is defined as the smallest denomination of a token. An amount expressed in baseUnits - * is the amount expressed in the smallest denomination. - * E.g: 1 unit of a token with 18 decimal places is expressed in baseUnits as 1000000000000000000 - * @param amount The amount of units that you would like converted to baseUnits. - * @param decimals The number of decimal places the unit amount has. - * @return The amount in baseUnits. - */ - public static toBaseUnitAmount(amount: BigNumber, decimals: number): BigNumber { - assert.isBigNumber('amount', amount); - assert.isNumber('decimals', decimals); + const aUnit = new BigNumber(10).pow(decimals); + const unit = amount.div(aUnit); + return unit; + } + /** + * A baseUnit is defined as the smallest denomination of a token. An amount expressed in baseUnits + * is the amount expressed in the smallest denomination. + * E.g: 1 unit of a token with 18 decimal places is expressed in baseUnits as 1000000000000000000 + * @param amount The amount of units that you would like converted to baseUnits. + * @param decimals The number of decimal places the unit amount has. + * @return The amount in baseUnits. + */ + public static toBaseUnitAmount(amount: BigNumber, decimals: number): BigNumber { + assert.isBigNumber('amount', amount); + assert.isNumber('decimals', decimals); - const unit = new BigNumber(10).pow(decimals); - const baseUnitAmount = amount.times(unit); - const hasDecimals = baseUnitAmount.decimalPlaces() !== 0; - if (hasDecimals) { - throw new Error(`Invalid unit amount: ${amount.toString()} - Too many decimal places`); - } - return baseUnitAmount; - } - /** - * Computes the orderHash for a supplied order. - * @param order An object that conforms to the Order or SignedOrder interface definitions. - * @return The resulting orderHash from hashing the supplied order. - */ - @decorators.syncZeroExErrorHandler - public static getOrderHashHex(order: Order | SignedOrder): string { - assert.doesConformToSchema('order', order, schemas.orderSchema); - const orderHashHex = utils.getOrderHashHex(order); - return orderHashHex; - } - /** - * Instantiates a new ZeroEx instance that provides the public interface to the 0x.js library. - * @param provider The Web3.js Provider instance you would like the 0x.js library to use for interacting with - * the Ethereum network. - * @param config The configuration object. Look up the type for the description. - * @return An instance of the 0x.js ZeroEx class. - */ - constructor(provider: Web3Provider, config: ZeroExConfig) { - assert.isWeb3Provider('provider', provider); - assert.doesConformToSchema('config', config, zeroExConfigSchema); - const artifactJSONs = _.values(artifacts); - const abiArrays = _.map(artifactJSONs, artifact => artifact.abi); - this._abiDecoder = new AbiDecoder(abiArrays); - const defaults = { - gasPrice: config.gasPrice, - }; - this._web3Wrapper = new Web3Wrapper(provider, defaults); - this.proxy = new TokenTransferProxyWrapper( - this._web3Wrapper, - config.networkId, - config.tokenTransferProxyContractAddress, - ); - this.token = new TokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.proxy); - this.exchange = new ExchangeWrapper( - this._web3Wrapper, - config.networkId, - this._abiDecoder, - this.token, - config.exchangeContractAddress, - config.zrxContractAddress, - ); - this.tokenRegistry = new TokenRegistryWrapper( - this._web3Wrapper, - config.networkId, - config.tokenRegistryContractAddress, - ); - this.etherToken = new EtherTokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.token); - this.orderStateWatcher = new OrderStateWatcher( - this._web3Wrapper, - this._abiDecoder, - this.token, - this.exchange, - config.orderWatcherConfig, - ); - } - /** - * Sets a new web3 provider for 0x.js. Updating the provider will stop all - * subscriptions so you will need to re-subscribe to all events relevant to your app after this call. - * @param provider The Web3Provider you would like the 0x.js library to use from now on. - * @param networkId The id of the network your provider is connected to - */ - public setProvider(provider: Web3Provider, networkId: number): void { - this._web3Wrapper.setProvider(provider); - (this.exchange as any)._invalidateContractInstances(); - (this.exchange as any)._setNetworkId(networkId); - (this.tokenRegistry as any)._invalidateContractInstance(); - (this.tokenRegistry as any)._setNetworkId(networkId); - (this.token as any)._invalidateContractInstances(); - (this.token as any)._setNetworkId(networkId); - (this.proxy as any)._invalidateContractInstance(); - (this.proxy as any)._setNetworkId(networkId); - (this.etherToken as any)._invalidateContractInstance(); - (this.etherToken as any)._setNetworkId(networkId); - } - /** - * Get user Ethereum addresses available through the supplied web3 provider available for sending transactions. - * @return An array of available user Ethereum addresses. - */ - public async getAvailableAddressesAsync(): Promise<string[]> { - const availableAddresses = await this._web3Wrapper.getAvailableAddressesAsync(); - return availableAddresses; - } - /** - * Signs an orderHash and returns it's elliptic curve signature. - * This method currently supports TestRPC, Geth and Parity above and below V1.6.6 - * @param orderHash Hex encoded orderHash to sign. - * @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address - * must be available via the Web3.Provider supplied to 0x.js. - * @param shouldAddPersonalMessagePrefix Some signers add the personal message prefix `\x19Ethereum Signed Message` - * themselves (e.g Parity Signer, Ledger, TestRPC) and others expect it to already be done by the client - * (e.g Metamask). Depending on which signer this request is going to, decide on whether to add the prefix - * before sending the request. - * @return An object containing the Elliptic curve signature parameters generated by signing the orderHash. - */ - public async signOrderHashAsync( - orderHash: string, - signerAddress: string, - shouldAddPersonalMessagePrefix: boolean, - ): Promise<ECSignature> { - assert.isHexString('orderHash', orderHash); - await assert.isSenderAddressAsync('signerAddress', signerAddress, this._web3Wrapper); + const unit = new BigNumber(10).pow(decimals); + const baseUnitAmount = amount.times(unit); + const hasDecimals = baseUnitAmount.decimalPlaces() !== 0; + if (hasDecimals) { + throw new Error(`Invalid unit amount: ${amount.toString()} - Too many decimal places`); + } + return baseUnitAmount; + } + /** + * Computes the orderHash for a supplied order. + * @param order An object that conforms to the Order or SignedOrder interface definitions. + * @return The resulting orderHash from hashing the supplied order. + */ + @decorators.syncZeroExErrorHandler + public static getOrderHashHex(order: Order | SignedOrder): string { + assert.doesConformToSchema('order', order, schemas.orderSchema); + const orderHashHex = utils.getOrderHashHex(order); + return orderHashHex; + } + /** + * Instantiates a new ZeroEx instance that provides the public interface to the 0x.js library. + * @param provider The Web3.js Provider instance you would like the 0x.js library to use for interacting with + * the Ethereum network. + * @param config The configuration object. Look up the type for the description. + * @return An instance of the 0x.js ZeroEx class. + */ + constructor(provider: Web3Provider, config: ZeroExConfig) { + assert.isWeb3Provider('provider', provider); + assert.doesConformToSchema('config', config, zeroExConfigSchema); + const artifactJSONs = _.values(artifacts); + const abiArrays = _.map(artifactJSONs, artifact => artifact.abi); + this._abiDecoder = new AbiDecoder(abiArrays); + const defaults = { + gasPrice: config.gasPrice, + }; + this._web3Wrapper = new Web3Wrapper(provider, defaults); + this.proxy = new TokenTransferProxyWrapper( + this._web3Wrapper, + config.networkId, + config.tokenTransferProxyContractAddress, + ); + this.token = new TokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.proxy); + this.exchange = new ExchangeWrapper( + this._web3Wrapper, + config.networkId, + this._abiDecoder, + this.token, + config.exchangeContractAddress, + config.zrxContractAddress, + ); + this.tokenRegistry = new TokenRegistryWrapper( + this._web3Wrapper, + config.networkId, + config.tokenRegistryContractAddress, + ); + this.etherToken = new EtherTokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.token); + this.orderStateWatcher = new OrderStateWatcher( + this._web3Wrapper, + this._abiDecoder, + this.token, + this.exchange, + config.orderWatcherConfig, + ); + } + /** + * Sets a new web3 provider for 0x.js. Updating the provider will stop all + * subscriptions so you will need to re-subscribe to all events relevant to your app after this call. + * @param provider The Web3Provider you would like the 0x.js library to use from now on. + * @param networkId The id of the network your provider is connected to + */ + public setProvider(provider: Web3Provider, networkId: number): void { + this._web3Wrapper.setProvider(provider); + (this.exchange as any)._invalidateContractInstances(); + (this.exchange as any)._setNetworkId(networkId); + (this.tokenRegistry as any)._invalidateContractInstance(); + (this.tokenRegistry as any)._setNetworkId(networkId); + (this.token as any)._invalidateContractInstances(); + (this.token as any)._setNetworkId(networkId); + (this.proxy as any)._invalidateContractInstance(); + (this.proxy as any)._setNetworkId(networkId); + (this.etherToken as any)._invalidateContractInstance(); + (this.etherToken as any)._setNetworkId(networkId); + } + /** + * Get user Ethereum addresses available through the supplied web3 provider available for sending transactions. + * @return An array of available user Ethereum addresses. + */ + public async getAvailableAddressesAsync(): Promise<string[]> { + const availableAddresses = await this._web3Wrapper.getAvailableAddressesAsync(); + return availableAddresses; + } + /** + * Signs an orderHash and returns it's elliptic curve signature. + * This method currently supports TestRPC, Geth and Parity above and below V1.6.6 + * @param orderHash Hex encoded orderHash to sign. + * @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address + * must be available via the Web3.Provider supplied to 0x.js. + * @param shouldAddPersonalMessagePrefix Some signers add the personal message prefix `\x19Ethereum Signed Message` + * themselves (e.g Parity Signer, Ledger, TestRPC) and others expect it to already be done by the client + * (e.g Metamask). Depending on which signer this request is going to, decide on whether to add the prefix + * before sending the request. + * @return An object containing the Elliptic curve signature parameters generated by signing the orderHash. + */ + public async signOrderHashAsync( + orderHash: string, + signerAddress: string, + shouldAddPersonalMessagePrefix: boolean, + ): Promise<ECSignature> { + assert.isHexString('orderHash', orderHash); + await assert.isSenderAddressAsync('signerAddress', signerAddress, this._web3Wrapper); - let msgHashHex = orderHash; - if (shouldAddPersonalMessagePrefix) { - const orderHashBuff = ethUtil.toBuffer(orderHash); - const msgHashBuff = ethUtil.hashPersonalMessage(orderHashBuff); - msgHashHex = ethUtil.bufferToHex(msgHashBuff); - } + let msgHashHex = orderHash; + if (shouldAddPersonalMessagePrefix) { + const orderHashBuff = ethUtil.toBuffer(orderHash); + const msgHashBuff = ethUtil.hashPersonalMessage(orderHashBuff); + msgHashHex = ethUtil.bufferToHex(msgHashBuff); + } - const signature = await this._web3Wrapper.signTransactionAsync(signerAddress, msgHashHex); + const signature = await this._web3Wrapper.signTransactionAsync(signerAddress, msgHashHex); - // HACK: There is no consensus on whether the signatureHex string should be formatted as - // v + r + s OR r + s + v, and different clients (even different versions of the same client) - // return the signature params in different orders. In order to support all client implementations, - // we parse the signature in both ways, and evaluate if either one is a valid signature. - const validVParamValues = [27, 28]; - const ecSignatureVRS = signatureUtils.parseSignatureHexAsVRS(signature); - if (_.includes(validVParamValues, ecSignatureVRS.v)) { - const isValidVRSSignature = ZeroEx.isValidSignature(orderHash, ecSignatureVRS, signerAddress); - if (isValidVRSSignature) { - return ecSignatureVRS; - } - } + // HACK: There is no consensus on whether the signatureHex string should be formatted as + // v + r + s OR r + s + v, and different clients (even different versions of the same client) + // return the signature params in different orders. In order to support all client implementations, + // we parse the signature in both ways, and evaluate if either one is a valid signature. + const validVParamValues = [27, 28]; + const ecSignatureVRS = signatureUtils.parseSignatureHexAsVRS(signature); + if (_.includes(validVParamValues, ecSignatureVRS.v)) { + const isValidVRSSignature = ZeroEx.isValidSignature(orderHash, ecSignatureVRS, signerAddress); + if (isValidVRSSignature) { + return ecSignatureVRS; + } + } - const ecSignatureRSV = signatureUtils.parseSignatureHexAsRSV(signature); - if (_.includes(validVParamValues, ecSignatureRSV.v)) { - const isValidRSVSignature = ZeroEx.isValidSignature(orderHash, ecSignatureRSV, signerAddress); - if (isValidRSVSignature) { - return ecSignatureRSV; - } - } + const ecSignatureRSV = signatureUtils.parseSignatureHexAsRSV(signature); + if (_.includes(validVParamValues, ecSignatureRSV.v)) { + const isValidRSVSignature = ZeroEx.isValidSignature(orderHash, ecSignatureRSV, signerAddress); + if (isValidRSVSignature) { + return ecSignatureRSV; + } + } - throw new Error(ZeroExError.InvalidSignature); - } - /** - * Waits for a transaction to be mined and returns the transaction receipt. - * @param txHash Transaction hash - * @param pollingIntervalMs How often (in ms) should we check if the transaction is mined. - * @param timeoutMs How long (in ms) to poll for transaction mined until aborting. - * @return Transaction receipt with decoded log args. - */ - public async awaitTransactionMinedAsync( - txHash: string, - pollingIntervalMs = 1000, - timeoutMs?: number, - ): Promise<TransactionReceiptWithDecodedLogs> { - let timeoutExceeded = false; - if (timeoutMs) { - setTimeout(() => (timeoutExceeded = true), timeoutMs); - } + throw new Error(ZeroExError.InvalidSignature); + } + /** + * Waits for a transaction to be mined and returns the transaction receipt. + * @param txHash Transaction hash + * @param pollingIntervalMs How often (in ms) should we check if the transaction is mined. + * @param timeoutMs How long (in ms) to poll for transaction mined until aborting. + * @return Transaction receipt with decoded log args. + */ + public async awaitTransactionMinedAsync( + txHash: string, + pollingIntervalMs = 1000, + timeoutMs?: number, + ): Promise<TransactionReceiptWithDecodedLogs> { + let timeoutExceeded = false; + if (timeoutMs) { + setTimeout(() => (timeoutExceeded = true), timeoutMs); + } - const txReceiptPromise = new Promise( - (resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => { - const intervalId = intervalUtils.setAsyncExcludingInterval( - async () => { - if (timeoutExceeded) { - intervalUtils.clearAsyncExcludingInterval(intervalId); - return reject(ZeroExError.TransactionMiningTimeout); - } + const txReceiptPromise = new Promise( + (resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => { + const intervalId = intervalUtils.setAsyncExcludingInterval( + async () => { + if (timeoutExceeded) { + intervalUtils.clearAsyncExcludingInterval(intervalId); + return reject(ZeroExError.TransactionMiningTimeout); + } - const transactionReceipt = await this._web3Wrapper.getTransactionReceiptAsync(txHash); - if (!_.isNull(transactionReceipt)) { - intervalUtils.clearAsyncExcludingInterval(intervalId); - const logsWithDecodedArgs = _.map( - transactionReceipt.logs, - this._abiDecoder.tryToDecodeLogOrNoop.bind(this._abiDecoder), - ); - const transactionReceiptWithDecodedLogArgs: TransactionReceiptWithDecodedLogs = { - ...transactionReceipt, - logs: logsWithDecodedArgs, - }; - resolve(transactionReceiptWithDecodedLogArgs); - } - }, - pollingIntervalMs, - (err: Error) => { - intervalUtils.clearAsyncExcludingInterval(intervalId); - reject(err); - }, - ); - }, - ); + const transactionReceipt = await this._web3Wrapper.getTransactionReceiptAsync(txHash); + if (!_.isNull(transactionReceipt)) { + intervalUtils.clearAsyncExcludingInterval(intervalId); + const logsWithDecodedArgs = _.map( + transactionReceipt.logs, + this._abiDecoder.tryToDecodeLogOrNoop.bind(this._abiDecoder), + ); + const transactionReceiptWithDecodedLogArgs: TransactionReceiptWithDecodedLogs = { + ...transactionReceipt, + logs: logsWithDecodedArgs, + }; + resolve(transactionReceiptWithDecodedLogArgs); + } + }, + pollingIntervalMs, + (err: Error) => { + intervalUtils.clearAsyncExcludingInterval(intervalId); + reject(err); + }, + ); + }, + ); - return txReceiptPromise; - } - /* + return txReceiptPromise; + } + /* * HACK: `TokenWrapper` needs a token transfer proxy address. `TokenTransferProxy` address is fetched from * an `ExchangeWrapper`. `ExchangeWrapper` needs `TokenWrapper` to validate orders, creating a dependency cycle. * In order to break this - we create this function here and pass it as a parameter to the `TokenWrapper` * and `ProxyWrapper`. */ - private async _getTokenTransferProxyAddressAsync(): Promise<string> { - const tokenTransferProxyAddress = await (this.exchange as any)._getTokenTransferProxyAddressAsync(); - return tokenTransferProxyAddress; - } + private async _getTokenTransferProxyAddressAsync(): Promise<string> { + const tokenTransferProxyAddress = await (this.exchange as any)._getTokenTransferProxyAddressAsync(); + return tokenTransferProxyAddress; + } } diff --git a/packages/0x.js/src/artifacts.ts b/packages/0x.js/src/artifacts.ts index cbacd7d56..7d4bd6757 100644 --- a/packages/0x.js/src/artifacts.ts +++ b/packages/0x.js/src/artifacts.ts @@ -8,11 +8,11 @@ import * as ZRXArtifact from './artifacts/ZRX.json'; import { Artifact } from './types'; export const artifacts = { - ZRXArtifact: (ZRXArtifact as any) as Artifact, - DummyTokenArtifact: (DummyTokenArtifact as any) as Artifact, - TokenArtifact: (TokenArtifact as any) as Artifact, - ExchangeArtifact: (ExchangeArtifact as any) as Artifact, - EtherTokenArtifact: (EtherTokenArtifact as any) as Artifact, - TokenRegistryArtifact: (TokenRegistryArtifact as any) as Artifact, - TokenTransferProxyArtifact: (TokenTransferProxyArtifact as any) as Artifact, + ZRXArtifact: (ZRXArtifact as any) as Artifact, + DummyTokenArtifact: (DummyTokenArtifact as any) as Artifact, + TokenArtifact: (TokenArtifact as any) as Artifact, + ExchangeArtifact: (ExchangeArtifact as any) as Artifact, + EtherTokenArtifact: (EtherTokenArtifact as any) as Artifact, + TokenRegistryArtifact: (TokenRegistryArtifact as any) as Artifact, + TokenTransferProxyArtifact: (TokenTransferProxyArtifact as any) as Artifact, }; diff --git a/packages/0x.js/src/artifacts/DummyToken.json b/packages/0x.js/src/artifacts/DummyToken.json index f64a8cd3d..f96d63c21 100644 --- a/packages/0x.js/src/artifacts/DummyToken.json +++ b/packages/0x.js/src/artifacts/DummyToken.json @@ -1,22 +1,22 @@ { - "contract_name": "DummyToken", - "abi": [ - { - "constant": false, - "inputs": [ - { - "name": "_target", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "setBalance", - "outputs": [], - "payable": false, - "type": "function" - } - ] + "contract_name": "DummyToken", + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "_target", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "setBalance", + "outputs": [], + "payable": false, + "type": "function" + } + ] } diff --git a/packages/0x.js/src/artifacts/EtherToken.json b/packages/0x.js/src/artifacts/EtherToken.json index 26cca57cd..ca5f9c035 100644 --- a/packages/0x.js/src/artifacts/EtherToken.json +++ b/packages/0x.js/src/artifacts/EtherToken.json @@ -1,287 +1,287 @@ { - "contract_name": "EtherToken", - "abi": [ - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "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, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "amount", - "type": "uint256" - } - ], - "name": "withdraw", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "name": "", - "type": "uint8" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "deposit", - "outputs": [], - "payable": true, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - }, - { - "name": "_spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "payable": true, - "type": "fallback" - }, - { - "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" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_owner", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Deposit", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_owner", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Withdrawal", - "type": "event" - } - ], - "networks": { - "1": { - "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "3": { - "address": "0xc00fd9820cd2898cc4c054b7bf142de637ad129a" - }, - "4": { - "address": "0xc778417e063141139fce010982780140aa0cd5ab" - }, - "42": { - "address": "0x653e49e301e508a13237c0ddc98ae7d4cd2667a1" - }, - "50": { - "address": "0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c" - } - } + "contract_name": "EtherToken", + "abi": [ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "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, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "deposit", + "outputs": [], + "payable": true, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "payable": true, + "type": "fallback" + }, + { + "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" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + } + ], + "networks": { + "1": { + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "3": { + "address": "0xc00fd9820cd2898cc4c054b7bf142de637ad129a" + }, + "4": { + "address": "0xc778417e063141139fce010982780140aa0cd5ab" + }, + "42": { + "address": "0x653e49e301e508a13237c0ddc98ae7d4cd2667a1" + }, + "50": { + "address": "0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c" + } + } } diff --git a/packages/0x.js/src/artifacts/Exchange.json b/packages/0x.js/src/artifacts/Exchange.json index af8db7360..44bb41d4d 100644 --- a/packages/0x.js/src/artifacts/Exchange.json +++ b/packages/0x.js/src/artifacts/Exchange.json @@ -1,610 +1,610 @@ { - "contract_name": "Exchange", - "abi": [ - { - "constant": true, - "inputs": [ - { - "name": "numerator", - "type": "uint256" - }, - { - "name": "denominator", - "type": "uint256" - }, - { - "name": "target", - "type": "uint256" - } - ], - "name": "isRoundingError", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "name": "filled", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "name": "cancelled", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5][]" - }, - { - "name": "orderValues", - "type": "uint256[6][]" - }, - { - "name": "fillTakerTokenAmount", - "type": "uint256" - }, - { - "name": "shouldThrowOnInsufficientBalanceOrAllowance", - "type": "bool" - }, - { - "name": "v", - "type": "uint8[]" - }, - { - "name": "r", - "type": "bytes32[]" - }, - { - "name": "s", - "type": "bytes32[]" - } - ], - "name": "fillOrdersUpTo", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5]" - }, - { - "name": "orderValues", - "type": "uint256[6]" - }, - { - "name": "cancelTakerTokenAmount", - "type": "uint256" - } - ], - "name": "cancelOrder", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "ZRX_TOKEN_CONTRACT", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5][]" - }, - { - "name": "orderValues", - "type": "uint256[6][]" - }, - { - "name": "fillTakerTokenAmounts", - "type": "uint256[]" - }, - { - "name": "v", - "type": "uint8[]" - }, - { - "name": "r", - "type": "bytes32[]" - }, - { - "name": "s", - "type": "bytes32[]" - } - ], - "name": "batchFillOrKillOrders", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5]" - }, - { - "name": "orderValues", - "type": "uint256[6]" - }, - { - "name": "fillTakerTokenAmount", - "type": "uint256" - }, - { - "name": "v", - "type": "uint8" - }, - { - "name": "r", - "type": "bytes32" - }, - { - "name": "s", - "type": "bytes32" - } - ], - "name": "fillOrKillOrder", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "orderHash", - "type": "bytes32" - } - ], - "name": "getUnavailableTakerTokenAmount", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "signer", - "type": "address" - }, - { - "name": "hash", - "type": "bytes32" - }, - { - "name": "v", - "type": "uint8" - }, - { - "name": "r", - "type": "bytes32" - }, - { - "name": "s", - "type": "bytes32" - } - ], - "name": "isValidSignature", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "numerator", - "type": "uint256" - }, - { - "name": "denominator", - "type": "uint256" - }, - { - "name": "target", - "type": "uint256" - } - ], - "name": "getPartialAmount", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "TOKEN_TRANSFER_PROXY_CONTRACT", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5][]" - }, - { - "name": "orderValues", - "type": "uint256[6][]" - }, - { - "name": "fillTakerTokenAmounts", - "type": "uint256[]" - }, - { - "name": "shouldThrowOnInsufficientBalanceOrAllowance", - "type": "bool" - }, - { - "name": "v", - "type": "uint8[]" - }, - { - "name": "r", - "type": "bytes32[]" - }, - { - "name": "s", - "type": "bytes32[]" - } - ], - "name": "batchFillOrders", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5][]" - }, - { - "name": "orderValues", - "type": "uint256[6][]" - }, - { - "name": "cancelTakerTokenAmounts", - "type": "uint256[]" - } - ], - "name": "batchCancelOrders", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5]" - }, - { - "name": "orderValues", - "type": "uint256[6]" - }, - { - "name": "fillTakerTokenAmount", - "type": "uint256" - }, - { - "name": "shouldThrowOnInsufficientBalanceOrAllowance", - "type": "bool" - }, - { - "name": "v", - "type": "uint8" - }, - { - "name": "r", - "type": "bytes32" - }, - { - "name": "s", - "type": "bytes32" - } - ], - "name": "fillOrder", - "outputs": [ - { - "name": "filledTakerTokenAmount", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5]" - }, - { - "name": "orderValues", - "type": "uint256[6]" - } - ], - "name": "getOrderHash", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "EXTERNAL_QUERY_GAS_LIMIT", - "outputs": [ - { - "name": "", - "type": "uint16" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "VERSION", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "type": "function" - }, - { - "inputs": [ - { - "name": "_zrxToken", - "type": "address" - }, - { - "name": "_tokenTransferProxy", - "type": "address" - } - ], - "payable": false, - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "maker", - "type": "address" - }, - { - "indexed": false, - "name": "taker", - "type": "address" - }, - { - "indexed": true, - "name": "feeRecipient", - "type": "address" - }, - { - "indexed": false, - "name": "makerToken", - "type": "address" - }, - { - "indexed": false, - "name": "takerToken", - "type": "address" - }, - { - "indexed": false, - "name": "filledMakerTokenAmount", - "type": "uint256" - }, - { - "indexed": false, - "name": "filledTakerTokenAmount", - "type": "uint256" - }, - { - "indexed": false, - "name": "paidMakerFee", - "type": "uint256" - }, - { - "indexed": false, - "name": "paidTakerFee", - "type": "uint256" - }, - { - "indexed": true, - "name": "tokens", - "type": "bytes32" - }, - { - "indexed": false, - "name": "orderHash", - "type": "bytes32" - } - ], - "name": "LogFill", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "maker", - "type": "address" - }, - { - "indexed": true, - "name": "feeRecipient", - "type": "address" - }, - { - "indexed": false, - "name": "makerToken", - "type": "address" - }, - { - "indexed": false, - "name": "takerToken", - "type": "address" - }, - { - "indexed": false, - "name": "cancelledMakerTokenAmount", - "type": "uint256" - }, - { - "indexed": false, - "name": "cancelledTakerTokenAmount", - "type": "uint256" - }, - { - "indexed": true, - "name": "tokens", - "type": "bytes32" - }, - { - "indexed": false, - "name": "orderHash", - "type": "bytes32" - } - ], - "name": "LogCancel", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "errorId", - "type": "uint8" - }, - { - "indexed": true, - "name": "orderHash", - "type": "bytes32" - } - ], - "name": "LogError", - "type": "event" - } - ], - "networks": { - "1": { - "address": "0x12459c951127e0c374ff9105dda097662a027093" - }, - "3": { - "address": "0x479cc461fecd078f766ecc58533d6f69580cf3ac" - }, - "4": { - "address": "0x1d16ef40fac01cec8adac2ac49427b9384192c05" - }, - "42": { - "address": "0x90fe2af704b34e0224bf2299c838e04d4dcf1364" - }, - "50": { - "address": "0x48bacb9266a570d521063ef5dd96e61686dbe788" - } - } + "contract_name": "Exchange", + "abi": [ + { + "constant": true, + "inputs": [ + { + "name": "numerator", + "type": "uint256" + }, + { + "name": "denominator", + "type": "uint256" + }, + { + "name": "target", + "type": "uint256" + } + ], + "name": "isRoundingError", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "name": "filled", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "name": "cancelled", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5][]" + }, + { + "name": "orderValues", + "type": "uint256[6][]" + }, + { + "name": "fillTakerTokenAmount", + "type": "uint256" + }, + { + "name": "shouldThrowOnInsufficientBalanceOrAllowance", + "type": "bool" + }, + { + "name": "v", + "type": "uint8[]" + }, + { + "name": "r", + "type": "bytes32[]" + }, + { + "name": "s", + "type": "bytes32[]" + } + ], + "name": "fillOrdersUpTo", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5]" + }, + { + "name": "orderValues", + "type": "uint256[6]" + }, + { + "name": "cancelTakerTokenAmount", + "type": "uint256" + } + ], + "name": "cancelOrder", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ZRX_TOKEN_CONTRACT", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5][]" + }, + { + "name": "orderValues", + "type": "uint256[6][]" + }, + { + "name": "fillTakerTokenAmounts", + "type": "uint256[]" + }, + { + "name": "v", + "type": "uint8[]" + }, + { + "name": "r", + "type": "bytes32[]" + }, + { + "name": "s", + "type": "bytes32[]" + } + ], + "name": "batchFillOrKillOrders", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5]" + }, + { + "name": "orderValues", + "type": "uint256[6]" + }, + { + "name": "fillTakerTokenAmount", + "type": "uint256" + }, + { + "name": "v", + "type": "uint8" + }, + { + "name": "r", + "type": "bytes32" + }, + { + "name": "s", + "type": "bytes32" + } + ], + "name": "fillOrKillOrder", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "orderHash", + "type": "bytes32" + } + ], + "name": "getUnavailableTakerTokenAmount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "signer", + "type": "address" + }, + { + "name": "hash", + "type": "bytes32" + }, + { + "name": "v", + "type": "uint8" + }, + { + "name": "r", + "type": "bytes32" + }, + { + "name": "s", + "type": "bytes32" + } + ], + "name": "isValidSignature", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "numerator", + "type": "uint256" + }, + { + "name": "denominator", + "type": "uint256" + }, + { + "name": "target", + "type": "uint256" + } + ], + "name": "getPartialAmount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "TOKEN_TRANSFER_PROXY_CONTRACT", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5][]" + }, + { + "name": "orderValues", + "type": "uint256[6][]" + }, + { + "name": "fillTakerTokenAmounts", + "type": "uint256[]" + }, + { + "name": "shouldThrowOnInsufficientBalanceOrAllowance", + "type": "bool" + }, + { + "name": "v", + "type": "uint8[]" + }, + { + "name": "r", + "type": "bytes32[]" + }, + { + "name": "s", + "type": "bytes32[]" + } + ], + "name": "batchFillOrders", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5][]" + }, + { + "name": "orderValues", + "type": "uint256[6][]" + }, + { + "name": "cancelTakerTokenAmounts", + "type": "uint256[]" + } + ], + "name": "batchCancelOrders", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5]" + }, + { + "name": "orderValues", + "type": "uint256[6]" + }, + { + "name": "fillTakerTokenAmount", + "type": "uint256" + }, + { + "name": "shouldThrowOnInsufficientBalanceOrAllowance", + "type": "bool" + }, + { + "name": "v", + "type": "uint8" + }, + { + "name": "r", + "type": "bytes32" + }, + { + "name": "s", + "type": "bytes32" + } + ], + "name": "fillOrder", + "outputs": [ + { + "name": "filledTakerTokenAmount", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "orderAddresses", + "type": "address[5]" + }, + { + "name": "orderValues", + "type": "uint256[6]" + } + ], + "name": "getOrderHash", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "EXTERNAL_QUERY_GAS_LIMIT", + "outputs": [ + { + "name": "", + "type": "uint16" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "type": "function" + }, + { + "inputs": [ + { + "name": "_zrxToken", + "type": "address" + }, + { + "name": "_tokenTransferProxy", + "type": "address" + } + ], + "payable": false, + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "maker", + "type": "address" + }, + { + "indexed": false, + "name": "taker", + "type": "address" + }, + { + "indexed": true, + "name": "feeRecipient", + "type": "address" + }, + { + "indexed": false, + "name": "makerToken", + "type": "address" + }, + { + "indexed": false, + "name": "takerToken", + "type": "address" + }, + { + "indexed": false, + "name": "filledMakerTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "filledTakerTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "paidMakerFee", + "type": "uint256" + }, + { + "indexed": false, + "name": "paidTakerFee", + "type": "uint256" + }, + { + "indexed": true, + "name": "tokens", + "type": "bytes32" + }, + { + "indexed": false, + "name": "orderHash", + "type": "bytes32" + } + ], + "name": "LogFill", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "maker", + "type": "address" + }, + { + "indexed": true, + "name": "feeRecipient", + "type": "address" + }, + { + "indexed": false, + "name": "makerToken", + "type": "address" + }, + { + "indexed": false, + "name": "takerToken", + "type": "address" + }, + { + "indexed": false, + "name": "cancelledMakerTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "cancelledTakerTokenAmount", + "type": "uint256" + }, + { + "indexed": true, + "name": "tokens", + "type": "bytes32" + }, + { + "indexed": false, + "name": "orderHash", + "type": "bytes32" + } + ], + "name": "LogCancel", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "errorId", + "type": "uint8" + }, + { + "indexed": true, + "name": "orderHash", + "type": "bytes32" + } + ], + "name": "LogError", + "type": "event" + } + ], + "networks": { + "1": { + "address": "0x12459c951127e0c374ff9105dda097662a027093" + }, + "3": { + "address": "0x479cc461fecd078f766ecc58533d6f69580cf3ac" + }, + "4": { + "address": "0x1d16ef40fac01cec8adac2ac49427b9384192c05" + }, + "42": { + "address": "0x90fe2af704b34e0224bf2299c838e04d4dcf1364" + }, + "50": { + "address": "0x48bacb9266a570d521063ef5dd96e61686dbe788" + } + } } diff --git a/packages/0x.js/src/artifacts/Token.json b/packages/0x.js/src/artifacts/Token.json index 3b5a86ae0..bd4208275 100644 --- a/packages/0x.js/src/artifacts/Token.json +++ b/packages/0x.js/src/artifacts/Token.json @@ -1,172 +1,172 @@ { - "contract_name": "Token", - "abi": [ - { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "name": "success", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "name": "supply", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_from", - "type": "address" - }, - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "success", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "balance", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "name": "success", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - }, - { - "name": "_spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "name": "remaining", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "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" - } - ] + "contract_name": "Token", + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "supply", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "remaining", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "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" + } + ] } diff --git a/packages/0x.js/src/artifacts/TokenRegistry.json b/packages/0x.js/src/artifacts/TokenRegistry.json index 0f583628c..490ea0c34 100644 --- a/packages/0x.js/src/artifacts/TokenRegistry.json +++ b/packages/0x.js/src/artifacts/TokenRegistry.json @@ -1,547 +1,547 @@ { - "contract_name": "TokenRegistry", - "abi": [ - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_index", - "type": "uint256" - } - ], - "name": "removeToken", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_name", - "type": "string" - } - ], - "name": "getTokenAddressByName", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_symbol", - "type": "string" - } - ], - "name": "getTokenAddressBySymbol", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_swarmHash", - "type": "bytes" - } - ], - "name": "setTokenSwarmHash", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_token", - "type": "address" - } - ], - "name": "getTokenMetaData", - "outputs": [ - { - "name": "", - "type": "address" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "uint8" - }, - { - "name": "", - "type": "bytes" - }, - { - "name": "", - "type": "bytes" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "owner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_name", - "type": "string" - }, - { - "name": "_symbol", - "type": "string" - }, - { - "name": "_decimals", - "type": "uint8" - }, - { - "name": "_ipfsHash", - "type": "bytes" - }, - { - "name": "_swarmHash", - "type": "bytes" - } - ], - "name": "addToken", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_name", - "type": "string" - } - ], - "name": "setTokenName", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "tokens", - "outputs": [ - { - "name": "token", - "type": "address" - }, - { - "name": "name", - "type": "string" - }, - { - "name": "symbol", - "type": "string" - }, - { - "name": "decimals", - "type": "uint8" - }, - { - "name": "ipfsHash", - "type": "bytes" - }, - { - "name": "swarmHash", - "type": "bytes" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "tokenAddresses", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_name", - "type": "string" - } - ], - "name": "getTokenByName", - "outputs": [ - { - "name": "", - "type": "address" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "uint8" - }, - { - "name": "", - "type": "bytes" - }, - { - "name": "", - "type": "bytes" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getTokenAddresses", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_ipfsHash", - "type": "bytes" - } - ], - "name": "setTokenIpfsHash", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_symbol", - "type": "string" - } - ], - "name": "getTokenBySymbol", - "outputs": [ - { - "name": "", - "type": "address" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "uint8" - }, - { - "name": "", - "type": "bytes" - }, - { - "name": "", - "type": "bytes" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_symbol", - "type": "string" - } - ], - "name": "setTokenSymbol", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "name", - "type": "string" - }, - { - "indexed": false, - "name": "symbol", - "type": "string" - }, - { - "indexed": false, - "name": "decimals", - "type": "uint8" - }, - { - "indexed": false, - "name": "ipfsHash", - "type": "bytes" - }, - { - "indexed": false, - "name": "swarmHash", - "type": "bytes" - } - ], - "name": "LogAddToken", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "name", - "type": "string" - }, - { - "indexed": false, - "name": "symbol", - "type": "string" - }, - { - "indexed": false, - "name": "decimals", - "type": "uint8" - }, - { - "indexed": false, - "name": "ipfsHash", - "type": "bytes" - }, - { - "indexed": false, - "name": "swarmHash", - "type": "bytes" - } - ], - "name": "LogRemoveToken", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "oldName", - "type": "string" - }, - { - "indexed": false, - "name": "newName", - "type": "string" - } - ], - "name": "LogTokenNameChange", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "oldSymbol", - "type": "string" - }, - { - "indexed": false, - "name": "newSymbol", - "type": "string" - } - ], - "name": "LogTokenSymbolChange", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "oldIpfsHash", - "type": "bytes" - }, - { - "indexed": false, - "name": "newIpfsHash", - "type": "bytes" - } - ], - "name": "LogTokenIpfsHashChange", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "oldSwarmHash", - "type": "bytes" - }, - { - "indexed": false, - "name": "newSwarmHash", - "type": "bytes" - } - ], - "name": "LogTokenSwarmHashChange", - "type": "event" - } - ], - "networks": { - "1": { - "address": "0x926a74c5c36adf004c87399e65f75628b0f98d2c" - }, - "3": { - "address": "0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed" - }, - "4": { - "address": "0x4e9aad8184de8833365fea970cd9149372fdf1e6" - }, - "42": { - "address": "0xf18e504561f4347bea557f3d4558f559dddbae7f" - }, - "50": { - "address": "0x0b1ba0af832d7c05fd64161e0db78e85978e8082" - } - } + "contract_name": "TokenRegistry", + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_index", + "type": "uint256" + } + ], + "name": "removeToken", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_name", + "type": "string" + } + ], + "name": "getTokenAddressByName", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_symbol", + "type": "string" + } + ], + "name": "getTokenAddressBySymbol", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_swarmHash", + "type": "bytes" + } + ], + "name": "setTokenSwarmHash", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_token", + "type": "address" + } + ], + "name": "getTokenMetaData", + "outputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "string" + }, + { + "name": "", + "type": "string" + }, + { + "name": "", + "type": "uint8" + }, + { + "name": "", + "type": "bytes" + }, + { + "name": "", + "type": "bytes" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_name", + "type": "string" + }, + { + "name": "_symbol", + "type": "string" + }, + { + "name": "_decimals", + "type": "uint8" + }, + { + "name": "_ipfsHash", + "type": "bytes" + }, + { + "name": "_swarmHash", + "type": "bytes" + } + ], + "name": "addToken", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_name", + "type": "string" + } + ], + "name": "setTokenName", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "tokens", + "outputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "symbol", + "type": "string" + }, + { + "name": "decimals", + "type": "uint8" + }, + { + "name": "ipfsHash", + "type": "bytes" + }, + { + "name": "swarmHash", + "type": "bytes" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "tokenAddresses", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_name", + "type": "string" + } + ], + "name": "getTokenByName", + "outputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "string" + }, + { + "name": "", + "type": "string" + }, + { + "name": "", + "type": "uint8" + }, + { + "name": "", + "type": "bytes" + }, + { + "name": "", + "type": "bytes" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTokenAddresses", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_ipfsHash", + "type": "bytes" + } + ], + "name": "setTokenIpfsHash", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_symbol", + "type": "string" + } + ], + "name": "getTokenBySymbol", + "outputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "string" + }, + { + "name": "", + "type": "string" + }, + { + "name": "", + "type": "uint8" + }, + { + "name": "", + "type": "bytes" + }, + { + "name": "", + "type": "bytes" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_token", + "type": "address" + }, + { + "name": "_symbol", + "type": "string" + } + ], + "name": "setTokenSymbol", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "name", + "type": "string" + }, + { + "indexed": false, + "name": "symbol", + "type": "string" + }, + { + "indexed": false, + "name": "decimals", + "type": "uint8" + }, + { + "indexed": false, + "name": "ipfsHash", + "type": "bytes" + }, + { + "indexed": false, + "name": "swarmHash", + "type": "bytes" + } + ], + "name": "LogAddToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "name", + "type": "string" + }, + { + "indexed": false, + "name": "symbol", + "type": "string" + }, + { + "indexed": false, + "name": "decimals", + "type": "uint8" + }, + { + "indexed": false, + "name": "ipfsHash", + "type": "bytes" + }, + { + "indexed": false, + "name": "swarmHash", + "type": "bytes" + } + ], + "name": "LogRemoveToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "oldName", + "type": "string" + }, + { + "indexed": false, + "name": "newName", + "type": "string" + } + ], + "name": "LogTokenNameChange", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "oldSymbol", + "type": "string" + }, + { + "indexed": false, + "name": "newSymbol", + "type": "string" + } + ], + "name": "LogTokenSymbolChange", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "oldIpfsHash", + "type": "bytes" + }, + { + "indexed": false, + "name": "newIpfsHash", + "type": "bytes" + } + ], + "name": "LogTokenIpfsHashChange", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "oldSwarmHash", + "type": "bytes" + }, + { + "indexed": false, + "name": "newSwarmHash", + "type": "bytes" + } + ], + "name": "LogTokenSwarmHashChange", + "type": "event" + } + ], + "networks": { + "1": { + "address": "0x926a74c5c36adf004c87399e65f75628b0f98d2c" + }, + "3": { + "address": "0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed" + }, + "4": { + "address": "0x4e9aad8184de8833365fea970cd9149372fdf1e6" + }, + "42": { + "address": "0xf18e504561f4347bea557f3d4558f559dddbae7f" + }, + "50": { + "address": "0x0b1ba0af832d7c05fd64161e0db78e85978e8082" + } + } } diff --git a/packages/0x.js/src/artifacts/TokenTransferProxy.json b/packages/0x.js/src/artifacts/TokenTransferProxy.json index 8cf551ddb..5e45e4903 100644 --- a/packages/0x.js/src/artifacts/TokenTransferProxy.json +++ b/packages/0x.js/src/artifacts/TokenTransferProxy.json @@ -1,187 +1,187 @@ { - "contract_name": "TokenTransferProxy", - "abi": [ - { - "constant": false, - "inputs": [ - { - "name": "token", - "type": "address" - }, - { - "name": "from", - "type": "address" - }, - { - "name": "to", - "type": "address" - }, - { - "name": "value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "target", - "type": "address" - } - ], - "name": "addAuthorizedAddress", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "authorities", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "target", - "type": "address" - } - ], - "name": "removeAuthorizedAddress", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "owner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "authorized", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getAuthorizedAddresses", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "target", - "type": "address" - }, - { - "indexed": true, - "name": "caller", - "type": "address" - } - ], - "name": "LogAuthorizedAddressAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "target", - "type": "address" - }, - { - "indexed": true, - "name": "caller", - "type": "address" - } - ], - "name": "LogAuthorizedAddressRemoved", - "type": "event" - } - ], - "networks": { - "1": { - "address": "0x8da0d80f5007ef1e431dd2127178d224e32c2ef4" - }, - "3": { - "address": "0x4e9aad8184de8833365fea970cd9149372fdf1e6" - }, - "4": { - "address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d" - }, - "42": { - "address": "0x087eed4bc1ee3de49befbd66c662b434b15d49d4" - }, - "50": { - "address": "0x1dc4c1cefef38a777b15aa20260a54e584b16c48" - } - } + "contract_name": "TokenTransferProxy", + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "token", + "type": "address" + }, + { + "name": "from", + "type": "address" + }, + { + "name": "to", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "addAuthorizedAddress", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "authorities", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "removeAuthorizedAddress", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "authorized", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getAuthorizedAddresses", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "target", + "type": "address" + }, + { + "indexed": true, + "name": "caller", + "type": "address" + } + ], + "name": "LogAuthorizedAddressAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "target", + "type": "address" + }, + { + "indexed": true, + "name": "caller", + "type": "address" + } + ], + "name": "LogAuthorizedAddressRemoved", + "type": "event" + } + ], + "networks": { + "1": { + "address": "0x8da0d80f5007ef1e431dd2127178d224e32c2ef4" + }, + "3": { + "address": "0x4e9aad8184de8833365fea970cd9149372fdf1e6" + }, + "4": { + "address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d" + }, + "42": { + "address": "0x087eed4bc1ee3de49befbd66c662b434b15d49d4" + }, + "50": { + "address": "0x1dc4c1cefef38a777b15aa20260a54e584b16c48" + } + } } diff --git a/packages/0x.js/src/artifacts/ZRX.json b/packages/0x.js/src/artifacts/ZRX.json index e40b8f268..44f478ab4 100644 --- a/packages/0x.js/src/artifacts/ZRX.json +++ b/packages/0x.js/src/artifacts/ZRX.json @@ -1,20 +1,20 @@ { - "contract_name": "ZRX", - "networks": { - "1": { - "address": "0xe41d2489571d322189246dafa5ebde1f4699f498" - }, - "3": { - "address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d" - }, - "4": { - "address": "0x00f58d6d585f84b2d7267940cede30ce2fe6eae8" - }, - "42": { - "address": "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570" - }, - "50": { - "address": "0x1d7022f5b17d2f8b695918fb48fa1089c9f85401" - } - } + "contract_name": "ZRX", + "networks": { + "1": { + "address": "0xe41d2489571d322189246dafa5ebde1f4699f498" + }, + "3": { + "address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d" + }, + "4": { + "address": "0x00f58d6d585f84b2d7267940cede30ce2fe6eae8" + }, + "42": { + "address": "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570" + }, + "50": { + "address": "0x1d7022f5b17d2f8b695918fb48fa1089c9f85401" + } + } } diff --git a/packages/0x.js/src/contract_wrappers/contract_wrapper.ts b/packages/0x.js/src/contract_wrappers/contract_wrapper.ts index 27551c01d..d1b753f45 100644 --- a/packages/0x.js/src/contract_wrappers/contract_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/contract_wrapper.ts @@ -5,203 +5,203 @@ import * as _ from 'lodash'; import * as Web3 from 'web3'; import { - Artifact, - BlockParamLiteral, - BlockRange, - ContractEventArgs, - ContractEvents, - EventCallback, - IndexedFilterValues, - InternalZeroExError, - LogWithDecodedArgs, - RawLog, - ZeroExError, + Artifact, + BlockParamLiteral, + BlockRange, + ContractEventArgs, + ContractEvents, + EventCallback, + IndexedFilterValues, + InternalZeroExError, + LogWithDecodedArgs, + RawLog, + ZeroExError, } from '../types'; import { AbiDecoder } from '../utils/abi_decoder'; import { constants } from '../utils/constants'; import { filterUtils } from '../utils/filter_utils'; const CONTRACT_NAME_TO_NOT_FOUND_ERROR: { - [contractName: string]: ZeroExError; + [contractName: string]: ZeroExError; } = { - ZRX: ZeroExError.ZRXContractDoesNotExist, - EtherToken: ZeroExError.EtherTokenContractDoesNotExist, - Token: ZeroExError.TokenContractDoesNotExist, - TokenRegistry: ZeroExError.TokenRegistryContractDoesNotExist, - TokenTransferProxy: ZeroExError.TokenTransferProxyContractDoesNotExist, - Exchange: ZeroExError.ExchangeContractDoesNotExist, + ZRX: ZeroExError.ZRXContractDoesNotExist, + EtherToken: ZeroExError.EtherTokenContractDoesNotExist, + Token: ZeroExError.TokenContractDoesNotExist, + TokenRegistry: ZeroExError.TokenRegistryContractDoesNotExist, + TokenTransferProxy: ZeroExError.TokenTransferProxyContractDoesNotExist, + Exchange: ZeroExError.ExchangeContractDoesNotExist, }; export class ContractWrapper { - protected _web3Wrapper: Web3Wrapper; - private _networkId: number; - private _abiDecoder?: AbiDecoder; - private _blockAndLogStreamerIfExists: BlockAndLogStreamer | undefined; - private _blockAndLogStreamInterval: NodeJS.Timer; - private _filters: { [filterToken: string]: Web3.FilterObject }; - private _filterCallbacks: { - [filterToken: string]: EventCallback<ContractEventArgs>; - }; - private _onLogAddedSubscriptionToken: string | undefined; - private _onLogRemovedSubscriptionToken: string | undefined; - constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder?: AbiDecoder) { - this._web3Wrapper = web3Wrapper; - this._networkId = networkId; - this._abiDecoder = abiDecoder; - this._filters = {}; - this._filterCallbacks = {}; - this._blockAndLogStreamerIfExists = undefined; - this._onLogAddedSubscriptionToken = undefined; - this._onLogRemovedSubscriptionToken = undefined; - } - protected unsubscribeAll(): void { - const filterTokens = _.keys(this._filterCallbacks); - _.each(filterTokens, filterToken => { - this._unsubscribe(filterToken); - }); - } - protected _unsubscribe(filterToken: string, err?: Error): void { - if (_.isUndefined(this._filters[filterToken])) { - throw new Error(ZeroExError.SubscriptionNotFound); - } - if (!_.isUndefined(err)) { - const callback = this._filterCallbacks[filterToken]; - callback(err, undefined); - } - delete this._filters[filterToken]; - delete this._filterCallbacks[filterToken]; - if (_.isEmpty(this._filters)) { - this._stopBlockAndLogStream(); - } - } - protected _subscribe<ArgsType extends ContractEventArgs>( - address: string, - eventName: ContractEvents, - indexFilterValues: IndexedFilterValues, - abi: Web3.ContractAbi, - callback: EventCallback<ArgsType>, - ): string { - const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi); - if (_.isUndefined(this._blockAndLogStreamerIfExists)) { - this._startBlockAndLogStream(); - } - const filterToken = filterUtils.generateUUID(); - this._filters[filterToken] = filter; - this._filterCallbacks[filterToken] = callback as EventCallback<ContractEventArgs>; - return filterToken; - } - protected async _getLogsAsync<ArgsType extends ContractEventArgs>( - address: string, - eventName: ContractEvents, - blockRange: BlockRange, - indexFilterValues: IndexedFilterValues, - abi: Web3.ContractAbi, - ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { - const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi, blockRange); - const logs = await this._web3Wrapper.getLogsAsync(filter); - const logsWithDecodedArguments = _.map(logs, this._tryToDecodeLogOrNoop.bind(this)); - return logsWithDecodedArguments; - } - protected _tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>( - log: Web3.LogEntry, - ): LogWithDecodedArgs<ArgsType> | RawLog { - if (_.isUndefined(this._abiDecoder)) { - throw new Error(InternalZeroExError.NoAbiDecoder); - } - const logWithDecodedArgs = this._abiDecoder.tryToDecodeLogOrNoop(log); - return logWithDecodedArgs; - } - protected async _instantiateContractIfExistsAsync( - artifact: Artifact, - addressIfExists?: string, - ): Promise<Web3.ContractInstance> { - let contractAddress: string; - if (_.isUndefined(addressIfExists)) { - if (_.isUndefined(artifact.networks[this._networkId])) { - throw new Error(ZeroExError.ContractNotDeployedOnNetwork); - } - contractAddress = artifact.networks[this._networkId].address.toLowerCase(); - } else { - contractAddress = addressIfExists; - } - const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(contractAddress); - if (!doesContractExist) { - throw new Error(CONTRACT_NAME_TO_NOT_FOUND_ERROR[artifact.contract_name]); - } - const contractInstance = this._web3Wrapper.getContractInstance(artifact.abi, contractAddress); - return contractInstance; - } - protected _getContractAddress(artifact: Artifact, addressIfExists?: string): string { - if (_.isUndefined(addressIfExists)) { - const contractAddress = artifact.networks[this._networkId].address; - if (_.isUndefined(contractAddress)) { - throw new Error(ZeroExError.ExchangeContractDoesNotExist); - } - return contractAddress; - } else { - return addressIfExists; - } - } - private _onLogStateChanged<ArgsType extends ContractEventArgs>(isRemoved: boolean, log: Web3.LogEntry): void { - _.forEach(this._filters, (filter: Web3.FilterObject, filterToken: string) => { - if (filterUtils.matchesFilter(log, filter)) { - const decodedLog = this._tryToDecodeLogOrNoop(log) as LogWithDecodedArgs<ArgsType>; - const logEvent = { - log: decodedLog, - isRemoved, - }; - this._filterCallbacks[filterToken](null, logEvent); - } - }); - } - private _startBlockAndLogStream(): void { - if (!_.isUndefined(this._blockAndLogStreamerIfExists)) { - throw new Error(ZeroExError.SubscriptionAlreadyPresent); - } - this._blockAndLogStreamerIfExists = new BlockAndLogStreamer( - this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper), - this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper), - ); - const catchAllLogFilter = {}; - this._blockAndLogStreamerIfExists.addLogFilter(catchAllLogFilter); - this._blockAndLogStreamInterval = intervalUtils.setAsyncExcludingInterval( - this._reconcileBlockAsync.bind(this), - constants.DEFAULT_BLOCK_POLLING_INTERVAL, - this._onReconcileBlockError.bind(this), - ); - let isRemoved = false; - this._onLogAddedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogAdded( - this._onLogStateChanged.bind(this, isRemoved), - ); - isRemoved = true; - this._onLogRemovedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogRemoved( - this._onLogStateChanged.bind(this, isRemoved), - ); - } - private _onReconcileBlockError(err: Error): void { - const filterTokens = _.keys(this._filterCallbacks); - _.each(filterTokens, filterToken => { - this._unsubscribe(filterToken, err); - }); - } - private _setNetworkId(networkId: number): void { - this._networkId = networkId; - } - private _stopBlockAndLogStream(): void { - if (_.isUndefined(this._blockAndLogStreamerIfExists)) { - throw new Error(ZeroExError.SubscriptionNotFound); - } - this._blockAndLogStreamerIfExists.unsubscribeFromOnLogAdded(this._onLogAddedSubscriptionToken as string); - this._blockAndLogStreamerIfExists.unsubscribeFromOnLogRemoved(this._onLogRemovedSubscriptionToken as string); - intervalUtils.clearAsyncExcludingInterval(this._blockAndLogStreamInterval); - delete this._blockAndLogStreamerIfExists; - } - private async _reconcileBlockAsync(): Promise<void> { - const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest); - // We need to coerce to Block type cause Web3.Block includes types for mempool blocks - if (!_.isUndefined(this._blockAndLogStreamerIfExists)) { - // If we clear the interval while fetching the block - this._blockAndLogStreamer will be undefined - await this._blockAndLogStreamerIfExists.reconcileNewBlock((latestBlock as any) as Block); - } - } + protected _web3Wrapper: Web3Wrapper; + private _networkId: number; + private _abiDecoder?: AbiDecoder; + private _blockAndLogStreamerIfExists: BlockAndLogStreamer | undefined; + private _blockAndLogStreamInterval: NodeJS.Timer; + private _filters: { [filterToken: string]: Web3.FilterObject }; + private _filterCallbacks: { + [filterToken: string]: EventCallback<ContractEventArgs>; + }; + private _onLogAddedSubscriptionToken: string | undefined; + private _onLogRemovedSubscriptionToken: string | undefined; + constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder?: AbiDecoder) { + this._web3Wrapper = web3Wrapper; + this._networkId = networkId; + this._abiDecoder = abiDecoder; + this._filters = {}; + this._filterCallbacks = {}; + this._blockAndLogStreamerIfExists = undefined; + this._onLogAddedSubscriptionToken = undefined; + this._onLogRemovedSubscriptionToken = undefined; + } + protected unsubscribeAll(): void { + const filterTokens = _.keys(this._filterCallbacks); + _.each(filterTokens, filterToken => { + this._unsubscribe(filterToken); + }); + } + protected _unsubscribe(filterToken: string, err?: Error): void { + if (_.isUndefined(this._filters[filterToken])) { + throw new Error(ZeroExError.SubscriptionNotFound); + } + if (!_.isUndefined(err)) { + const callback = this._filterCallbacks[filterToken]; + callback(err, undefined); + } + delete this._filters[filterToken]; + delete this._filterCallbacks[filterToken]; + if (_.isEmpty(this._filters)) { + this._stopBlockAndLogStream(); + } + } + protected _subscribe<ArgsType extends ContractEventArgs>( + address: string, + eventName: ContractEvents, + indexFilterValues: IndexedFilterValues, + abi: Web3.ContractAbi, + callback: EventCallback<ArgsType>, + ): string { + const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi); + if (_.isUndefined(this._blockAndLogStreamerIfExists)) { + this._startBlockAndLogStream(); + } + const filterToken = filterUtils.generateUUID(); + this._filters[filterToken] = filter; + this._filterCallbacks[filterToken] = callback as EventCallback<ContractEventArgs>; + return filterToken; + } + protected async _getLogsAsync<ArgsType extends ContractEventArgs>( + address: string, + eventName: ContractEvents, + blockRange: BlockRange, + indexFilterValues: IndexedFilterValues, + abi: Web3.ContractAbi, + ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { + const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi, blockRange); + const logs = await this._web3Wrapper.getLogsAsync(filter); + const logsWithDecodedArguments = _.map(logs, this._tryToDecodeLogOrNoop.bind(this)); + return logsWithDecodedArguments; + } + protected _tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>( + log: Web3.LogEntry, + ): LogWithDecodedArgs<ArgsType> | RawLog { + if (_.isUndefined(this._abiDecoder)) { + throw new Error(InternalZeroExError.NoAbiDecoder); + } + const logWithDecodedArgs = this._abiDecoder.tryToDecodeLogOrNoop(log); + return logWithDecodedArgs; + } + protected async _instantiateContractIfExistsAsync( + artifact: Artifact, + addressIfExists?: string, + ): Promise<Web3.ContractInstance> { + let contractAddress: string; + if (_.isUndefined(addressIfExists)) { + if (_.isUndefined(artifact.networks[this._networkId])) { + throw new Error(ZeroExError.ContractNotDeployedOnNetwork); + } + contractAddress = artifact.networks[this._networkId].address.toLowerCase(); + } else { + contractAddress = addressIfExists; + } + const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(contractAddress); + if (!doesContractExist) { + throw new Error(CONTRACT_NAME_TO_NOT_FOUND_ERROR[artifact.contract_name]); + } + const contractInstance = this._web3Wrapper.getContractInstance(artifact.abi, contractAddress); + return contractInstance; + } + protected _getContractAddress(artifact: Artifact, addressIfExists?: string): string { + if (_.isUndefined(addressIfExists)) { + const contractAddress = artifact.networks[this._networkId].address; + if (_.isUndefined(contractAddress)) { + throw new Error(ZeroExError.ExchangeContractDoesNotExist); + } + return contractAddress; + } else { + return addressIfExists; + } + } + private _onLogStateChanged<ArgsType extends ContractEventArgs>(isRemoved: boolean, log: Web3.LogEntry): void { + _.forEach(this._filters, (filter: Web3.FilterObject, filterToken: string) => { + if (filterUtils.matchesFilter(log, filter)) { + const decodedLog = this._tryToDecodeLogOrNoop(log) as LogWithDecodedArgs<ArgsType>; + const logEvent = { + log: decodedLog, + isRemoved, + }; + this._filterCallbacks[filterToken](null, logEvent); + } + }); + } + private _startBlockAndLogStream(): void { + if (!_.isUndefined(this._blockAndLogStreamerIfExists)) { + throw new Error(ZeroExError.SubscriptionAlreadyPresent); + } + this._blockAndLogStreamerIfExists = new BlockAndLogStreamer( + this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper), + this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper), + ); + const catchAllLogFilter = {}; + this._blockAndLogStreamerIfExists.addLogFilter(catchAllLogFilter); + this._blockAndLogStreamInterval = intervalUtils.setAsyncExcludingInterval( + this._reconcileBlockAsync.bind(this), + constants.DEFAULT_BLOCK_POLLING_INTERVAL, + this._onReconcileBlockError.bind(this), + ); + let isRemoved = false; + this._onLogAddedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogAdded( + this._onLogStateChanged.bind(this, isRemoved), + ); + isRemoved = true; + this._onLogRemovedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogRemoved( + this._onLogStateChanged.bind(this, isRemoved), + ); + } + private _onReconcileBlockError(err: Error): void { + const filterTokens = _.keys(this._filterCallbacks); + _.each(filterTokens, filterToken => { + this._unsubscribe(filterToken, err); + }); + } + private _setNetworkId(networkId: number): void { + this._networkId = networkId; + } + private _stopBlockAndLogStream(): void { + if (_.isUndefined(this._blockAndLogStreamerIfExists)) { + throw new Error(ZeroExError.SubscriptionNotFound); + } + this._blockAndLogStreamerIfExists.unsubscribeFromOnLogAdded(this._onLogAddedSubscriptionToken as string); + this._blockAndLogStreamerIfExists.unsubscribeFromOnLogRemoved(this._onLogRemovedSubscriptionToken as string); + intervalUtils.clearAsyncExcludingInterval(this._blockAndLogStreamInterval); + delete this._blockAndLogStreamerIfExists; + } + private async _reconcileBlockAsync(): Promise<void> { + const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest); + // We need to coerce to Block type cause Web3.Block includes types for mempool blocks + if (!_.isUndefined(this._blockAndLogStreamerIfExists)) { + // If we clear the interval while fetching the block - this._blockAndLogStreamer will be undefined + await this._blockAndLogStreamerIfExists.reconcileNewBlock((latestBlock as any) as Block); + } + } } diff --git a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts index b03571636..d412971be 100644 --- a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts @@ -5,14 +5,14 @@ import * as _ from 'lodash'; import { artifacts } from '../artifacts'; import { - BlockRange, - EtherTokenContractEventArgs, - EtherTokenEvents, - EventCallback, - IndexedFilterValues, - LogWithDecodedArgs, - TransactionOpts, - ZeroExError, + BlockRange, + EtherTokenContractEventArgs, + EtherTokenEvents, + EventCallback, + IndexedFilterValues, + LogWithDecodedArgs, + TransactionOpts, + ZeroExError, } from '../types'; import { AbiDecoder } from '../utils/abi_decoder'; import { assert } from '../utils/assert'; @@ -26,159 +26,159 @@ import { TokenWrapper } from './token_wrapper'; * The caller can convert ETH into the equivalent number of wrapped ETH ERC20 tokens and back. */ export class EtherTokenWrapper extends ContractWrapper { - private _etherTokenContractsByAddress: { - [address: string]: EtherTokenContract; - } = {}; - private _tokenWrapper: TokenWrapper; - constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder: AbiDecoder, tokenWrapper: TokenWrapper) { - super(web3Wrapper, networkId, abiDecoder); - this._tokenWrapper = tokenWrapper; - } - /** - * Deposit ETH into the Wrapped ETH smart contract and issues the equivalent number of wrapped ETH tokens - * to the depositor address. These wrapped ETH tokens can be used in 0x trades and are redeemable for 1-to-1 - * for ETH. - * @param etherTokenAddress EtherToken address you wish to deposit into. - * @param amountInWei Amount of ETH in Wei the caller wishes to deposit. - * @param depositor The hex encoded user Ethereum address that would like to make the deposit. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async depositAsync( - etherTokenAddress: string, - amountInWei: BigNumber, - depositor: string, - txOpts: TransactionOpts = {}, - ): Promise<string> { - assert.isValidBaseUnitAmount('amountInWei', amountInWei); - await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper); + private _etherTokenContractsByAddress: { + [address: string]: EtherTokenContract; + } = {}; + private _tokenWrapper: TokenWrapper; + constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder: AbiDecoder, tokenWrapper: TokenWrapper) { + super(web3Wrapper, networkId, abiDecoder); + this._tokenWrapper = tokenWrapper; + } + /** + * Deposit ETH into the Wrapped ETH smart contract and issues the equivalent number of wrapped ETH tokens + * to the depositor address. These wrapped ETH tokens can be used in 0x trades and are redeemable for 1-to-1 + * for ETH. + * @param etherTokenAddress EtherToken address you wish to deposit into. + * @param amountInWei Amount of ETH in Wei the caller wishes to deposit. + * @param depositor The hex encoded user Ethereum address that would like to make the deposit. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async depositAsync( + etherTokenAddress: string, + amountInWei: BigNumber, + depositor: string, + txOpts: TransactionOpts = {}, + ): Promise<string> { + assert.isValidBaseUnitAmount('amountInWei', amountInWei); + await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper); - const ethBalanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(depositor); - assert.assert(ethBalanceInWei.gte(amountInWei), ZeroExError.InsufficientEthBalanceForDeposit); + const ethBalanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(depositor); + assert.assert(ethBalanceInWei.gte(amountInWei), ZeroExError.InsufficientEthBalanceForDeposit); - const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress); - const txHash = await wethContract.deposit.sendTransactionAsync({ - from: depositor, - value: amountInWei, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, - }); - return txHash; - } - /** - * Withdraw ETH to the withdrawer's address from the wrapped ETH smart contract in exchange for the - * equivalent number of wrapped ETH tokens. - * @param etherTokenAddress EtherToken address you wish to withdraw from. - * @param amountInWei Amount of ETH in Wei the caller wishes to withdraw. - * @param withdrawer The hex encoded user Ethereum address that would like to make the withdrawl. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async withdrawAsync( - etherTokenAddress: string, - amountInWei: BigNumber, - withdrawer: string, - txOpts: TransactionOpts = {}, - ): Promise<string> { - assert.isValidBaseUnitAmount('amountInWei', amountInWei); - await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper); + const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress); + const txHash = await wethContract.deposit.sendTransactionAsync({ + from: depositor, + value: amountInWei, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }); + return txHash; + } + /** + * Withdraw ETH to the withdrawer's address from the wrapped ETH smart contract in exchange for the + * equivalent number of wrapped ETH tokens. + * @param etherTokenAddress EtherToken address you wish to withdraw from. + * @param amountInWei Amount of ETH in Wei the caller wishes to withdraw. + * @param withdrawer The hex encoded user Ethereum address that would like to make the withdrawl. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async withdrawAsync( + etherTokenAddress: string, + amountInWei: BigNumber, + withdrawer: string, + txOpts: TransactionOpts = {}, + ): Promise<string> { + assert.isValidBaseUnitAmount('amountInWei', amountInWei); + await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper); - const WETHBalanceInBaseUnits = await this._tokenWrapper.getBalanceAsync(etherTokenAddress, withdrawer); - assert.assert(WETHBalanceInBaseUnits.gte(amountInWei), ZeroExError.InsufficientWEthBalanceForWithdrawal); + const WETHBalanceInBaseUnits = await this._tokenWrapper.getBalanceAsync(etherTokenAddress, withdrawer); + assert.assert(WETHBalanceInBaseUnits.gte(amountInWei), ZeroExError.InsufficientWEthBalanceForWithdrawal); - const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress); - const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, { - from: withdrawer, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, - }); - return txHash; - } - /** - * Gets historical logs without creating a subscription - * @param etherTokenAddress An address of the ether token that emmited the logs. - * @param eventName The ether token contract event you would like to subscribe to. - * @param blockRange Block range to get logs from. - * @param indexFilterValues An object where the keys are indexed args returned by the event and - * the value is the value you are interested in. E.g `{_owner: aUserAddressHex}` - * @return Array of logs that match the parameters - */ - public async getLogsAsync<ArgsType extends EtherTokenContractEventArgs>( - etherTokenAddress: string, - eventName: EtherTokenEvents, - blockRange: BlockRange, - indexFilterValues: IndexedFilterValues, - ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { - assert.isETHAddressHex('etherTokenAddress', etherTokenAddress); - assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents); - assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - const logs = await this._getLogsAsync<ArgsType>( - etherTokenAddress, - eventName, - blockRange, - indexFilterValues, - artifacts.EtherTokenArtifact.abi, - ); - return logs; - } - /** - * Subscribe to an event type emitted by the Token contract. - * @param etherTokenAddress The hex encoded address where the ether token is deployed. - * @param eventName The ether token contract event you would like to subscribe to. - * @param indexFilterValues An object where the keys are indexed args returned by the event and - * the value is the value you are interested in. E.g `{_owner: aUserAddressHex}` - * @param callback Callback that gets called when a log is added/removed - * @return Subscription token used later to unsubscribe - */ - public subscribe<ArgsType extends EtherTokenContractEventArgs>( - etherTokenAddress: string, - eventName: EtherTokenEvents, - indexFilterValues: IndexedFilterValues, - callback: EventCallback<ArgsType>, - ): string { - assert.isETHAddressHex('etherTokenAddress', etherTokenAddress); - assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - assert.isFunction('callback', callback); - const subscriptionToken = this._subscribe<ArgsType>( - etherTokenAddress, - eventName, - indexFilterValues, - artifacts.EtherTokenArtifact.abi, - callback, - ); - return subscriptionToken; - } - /** - * Cancel a subscription - * @param subscriptionToken Subscription token returned by `subscribe()` - */ - public unsubscribe(subscriptionToken: string): void { - this._unsubscribe(subscriptionToken); - } - /** - * Cancels all existing subscriptions - */ - public unsubscribeAll(): void { - super.unsubscribeAll(); - } - private _invalidateContractInstance(): void { - this.unsubscribeAll(); - this._etherTokenContractsByAddress = {}; - } - private async _getEtherTokenContractAsync(etherTokenAddress: string): Promise<EtherTokenContract> { - let etherTokenContract = this._etherTokenContractsByAddress[etherTokenAddress]; - if (!_.isUndefined(etherTokenContract)) { - return etherTokenContract; - } - const web3ContractInstance = await this._instantiateContractIfExistsAsync( - artifacts.EtherTokenArtifact, - etherTokenAddress, - ); - const contractInstance = new EtherTokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); - etherTokenContract = contractInstance; - this._etherTokenContractsByAddress[etherTokenAddress] = etherTokenContract; - return etherTokenContract; - } + const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress); + const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, { + from: withdrawer, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }); + return txHash; + } + /** + * Gets historical logs without creating a subscription + * @param etherTokenAddress An address of the ether token that emmited the logs. + * @param eventName The ether token contract event you would like to subscribe to. + * @param blockRange Block range to get logs from. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{_owner: aUserAddressHex}` + * @return Array of logs that match the parameters + */ + public async getLogsAsync<ArgsType extends EtherTokenContractEventArgs>( + etherTokenAddress: string, + eventName: EtherTokenEvents, + blockRange: BlockRange, + indexFilterValues: IndexedFilterValues, + ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { + assert.isETHAddressHex('etherTokenAddress', etherTokenAddress); + assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents); + assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + const logs = await this._getLogsAsync<ArgsType>( + etherTokenAddress, + eventName, + blockRange, + indexFilterValues, + artifacts.EtherTokenArtifact.abi, + ); + return logs; + } + /** + * Subscribe to an event type emitted by the Token contract. + * @param etherTokenAddress The hex encoded address where the ether token is deployed. + * @param eventName The ether token contract event you would like to subscribe to. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{_owner: aUserAddressHex}` + * @param callback Callback that gets called when a log is added/removed + * @return Subscription token used later to unsubscribe + */ + public subscribe<ArgsType extends EtherTokenContractEventArgs>( + etherTokenAddress: string, + eventName: EtherTokenEvents, + indexFilterValues: IndexedFilterValues, + callback: EventCallback<ArgsType>, + ): string { + assert.isETHAddressHex('etherTokenAddress', etherTokenAddress); + assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + assert.isFunction('callback', callback); + const subscriptionToken = this._subscribe<ArgsType>( + etherTokenAddress, + eventName, + indexFilterValues, + artifacts.EtherTokenArtifact.abi, + callback, + ); + return subscriptionToken; + } + /** + * Cancel a subscription + * @param subscriptionToken Subscription token returned by `subscribe()` + */ + public unsubscribe(subscriptionToken: string): void { + this._unsubscribe(subscriptionToken); + } + /** + * Cancels all existing subscriptions + */ + public unsubscribeAll(): void { + super.unsubscribeAll(); + } + private _invalidateContractInstance(): void { + this.unsubscribeAll(); + this._etherTokenContractsByAddress = {}; + } + private async _getEtherTokenContractAsync(etherTokenAddress: string): Promise<EtherTokenContract> { + let etherTokenContract = this._etherTokenContractsByAddress[etherTokenAddress]; + if (!_.isUndefined(etherTokenContract)) { + return etherTokenContract; + } + const web3ContractInstance = await this._instantiateContractIfExistsAsync( + artifacts.EtherTokenArtifact, + etherTokenAddress, + ); + const contractInstance = new EtherTokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); + etherTokenContract = contractInstance; + this._etherTokenContractsByAddress[etherTokenAddress] = etherTokenContract; + return etherTokenContract; + } } diff --git a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts index 2b6117729..668893d45 100644 --- a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts @@ -6,27 +6,27 @@ import * as Web3 from 'web3'; import { artifacts } from '../artifacts'; import { - BlockParamLiteral, - BlockRange, - DecodedLogArgs, - ECSignature, - EventCallback, - ExchangeContractErrCodes, - ExchangeContractErrs, - ExchangeContractEventArgs, - ExchangeEvents, - IndexedFilterValues, - LogErrorContractEventArgs, - LogWithDecodedArgs, - MethodOpts, - Order, - OrderAddresses, - OrderCancellationRequest, - OrderFillRequest, - OrderTransactionOpts, - OrderValues, - SignedOrder, - ValidateOrderFillableOpts, + BlockParamLiteral, + BlockRange, + DecodedLogArgs, + ECSignature, + EventCallback, + ExchangeContractErrCodes, + ExchangeContractErrs, + ExchangeContractEventArgs, + ExchangeEvents, + IndexedFilterValues, + LogErrorContractEventArgs, + LogWithDecodedArgs, + MethodOpts, + Order, + OrderAddresses, + OrderCancellationRequest, + OrderFillRequest, + OrderTransactionOpts, + OrderValues, + SignedOrder, + ValidateOrderFillableOpts, } from '../types'; import { AbiDecoder } from '../utils/abi_decoder'; import { assert } from '../utils/assert'; @@ -42,7 +42,7 @@ import { TokenWrapper } from './token_wrapper'; const SHOULD_VALIDATE_BY_DEFAULT = true; interface ExchangeContractErrCodesToMsgs { - [exchangeContractErrCodes: number]: string; + [exchangeContractErrCodes: number]: string; } /** @@ -50,864 +50,864 @@ interface ExchangeContractErrCodesToMsgs { * events of the 0x Exchange smart contract. */ export class ExchangeWrapper extends ContractWrapper { - private _exchangeContractIfExists?: ExchangeContract; - private _orderValidationUtils: OrderValidationUtils; - private _tokenWrapper: TokenWrapper; - private _exchangeContractErrCodesToMsg: ExchangeContractErrCodesToMsgs = { - [ExchangeContractErrCodes.ERROR_FILL_EXPIRED]: ExchangeContractErrs.OrderFillExpired, - [ExchangeContractErrCodes.ERROR_CANCEL_EXPIRED]: ExchangeContractErrs.OrderFillExpired, - [ExchangeContractErrCodes.ERROR_FILL_NO_VALUE]: ExchangeContractErrs.OrderRemainingFillAmountZero, - [ExchangeContractErrCodes.ERROR_CANCEL_NO_VALUE]: ExchangeContractErrs.OrderRemainingFillAmountZero, - [ExchangeContractErrCodes.ERROR_FILL_TRUNCATION]: ExchangeContractErrs.OrderFillRoundingError, - [ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.FillBalanceAllowanceError, - }; - private _contractAddressIfExists?: string; - private _zrxContractAddressIfExists?: string; - private static _getOrderAddressesAndValues(order: Order): [OrderAddresses, OrderValues] { - const orderAddresses: OrderAddresses = [ - order.maker, - order.taker, - order.makerTokenAddress, - order.takerTokenAddress, - order.feeRecipient, - ]; - const orderValues: OrderValues = [ - order.makerTokenAmount, - order.takerTokenAmount, - order.makerFee, - order.takerFee, - order.expirationUnixTimestampSec, - order.salt, - ]; - return [orderAddresses, orderValues]; - } - constructor( - web3Wrapper: Web3Wrapper, - networkId: number, - abiDecoder: AbiDecoder, - tokenWrapper: TokenWrapper, - contractAddressIfExists?: string, - zrxContractAddressIfExists?: string, - ) { - super(web3Wrapper, networkId, abiDecoder); - this._tokenWrapper = tokenWrapper; - this._orderValidationUtils = new OrderValidationUtils(this); - this._contractAddressIfExists = contractAddressIfExists; - this._zrxContractAddressIfExists = zrxContractAddressIfExists; - } - /** - * Returns the unavailable takerAmount of an order. Unavailable amount is defined as the total - * amount that has been filled or cancelled. The remaining takerAmount can be calculated by - * subtracting the unavailable amount from the total order takerAmount. - * @param orderHash The hex encoded orderHash for which you would like to retrieve the - * unavailable takerAmount. - * @param methodOpts Optional arguments this method accepts. - * @return The amount of the order (in taker tokens) that has either been filled or cancelled. - */ - public async getUnavailableTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> { - assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); + private _exchangeContractIfExists?: ExchangeContract; + private _orderValidationUtils: OrderValidationUtils; + private _tokenWrapper: TokenWrapper; + private _exchangeContractErrCodesToMsg: ExchangeContractErrCodesToMsgs = { + [ExchangeContractErrCodes.ERROR_FILL_EXPIRED]: ExchangeContractErrs.OrderFillExpired, + [ExchangeContractErrCodes.ERROR_CANCEL_EXPIRED]: ExchangeContractErrs.OrderFillExpired, + [ExchangeContractErrCodes.ERROR_FILL_NO_VALUE]: ExchangeContractErrs.OrderRemainingFillAmountZero, + [ExchangeContractErrCodes.ERROR_CANCEL_NO_VALUE]: ExchangeContractErrs.OrderRemainingFillAmountZero, + [ExchangeContractErrCodes.ERROR_FILL_TRUNCATION]: ExchangeContractErrs.OrderFillRoundingError, + [ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.FillBalanceAllowanceError, + }; + private _contractAddressIfExists?: string; + private _zrxContractAddressIfExists?: string; + private static _getOrderAddressesAndValues(order: Order): [OrderAddresses, OrderValues] { + const orderAddresses: OrderAddresses = [ + order.maker, + order.taker, + order.makerTokenAddress, + order.takerTokenAddress, + order.feeRecipient, + ]; + const orderValues: OrderValues = [ + order.makerTokenAmount, + order.takerTokenAmount, + order.makerFee, + order.takerFee, + order.expirationUnixTimestampSec, + order.salt, + ]; + return [orderAddresses, orderValues]; + } + constructor( + web3Wrapper: Web3Wrapper, + networkId: number, + abiDecoder: AbiDecoder, + tokenWrapper: TokenWrapper, + contractAddressIfExists?: string, + zrxContractAddressIfExists?: string, + ) { + super(web3Wrapper, networkId, abiDecoder); + this._tokenWrapper = tokenWrapper; + this._orderValidationUtils = new OrderValidationUtils(this); + this._contractAddressIfExists = contractAddressIfExists; + this._zrxContractAddressIfExists = zrxContractAddressIfExists; + } + /** + * Returns the unavailable takerAmount of an order. Unavailable amount is defined as the total + * amount that has been filled or cancelled. The remaining takerAmount can be calculated by + * subtracting the unavailable amount from the total order takerAmount. + * @param orderHash The hex encoded orderHash for which you would like to retrieve the + * unavailable takerAmount. + * @param methodOpts Optional arguments this method accepts. + * @return The amount of the order (in taker tokens) that has either been filled or cancelled. + */ + public async getUnavailableTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> { + assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); - const exchangeContract = await this._getExchangeContractAsync(); - const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - let unavailableTakerTokenAmount = await exchangeContract.getUnavailableTakerTokenAmount.callAsync( - orderHash, - defaultBlock, - ); - // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber - unavailableTakerTokenAmount = new BigNumber(unavailableTakerTokenAmount); - return unavailableTakerTokenAmount; - } - /** - * Retrieve the takerAmount of an order that has already been filled. - * @param orderHash The hex encoded orderHash for which you would like to retrieve the filled takerAmount. - * @param methodOpts Optional arguments this method accepts. - * @return The amount of the order (in taker tokens) that has already been filled. - */ - public async getFilledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> { - assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); + const exchangeContract = await this._getExchangeContractAsync(); + const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; + let unavailableTakerTokenAmount = await exchangeContract.getUnavailableTakerTokenAmount.callAsync( + orderHash, + defaultBlock, + ); + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber + unavailableTakerTokenAmount = new BigNumber(unavailableTakerTokenAmount); + return unavailableTakerTokenAmount; + } + /** + * Retrieve the takerAmount of an order that has already been filled. + * @param orderHash The hex encoded orderHash for which you would like to retrieve the filled takerAmount. + * @param methodOpts Optional arguments this method accepts. + * @return The amount of the order (in taker tokens) that has already been filled. + */ + public async getFilledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> { + assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); - const exchangeContract = await this._getExchangeContractAsync(); - const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - let fillAmountInBaseUnits = await exchangeContract.filled.callAsync(orderHash, defaultBlock); - // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber - fillAmountInBaseUnits = new BigNumber(fillAmountInBaseUnits); - return fillAmountInBaseUnits; - } - /** - * Retrieve the takerAmount of an order that has been cancelled. - * @param orderHash The hex encoded orderHash for which you would like to retrieve the - * cancelled takerAmount. - * @param methodOpts Optional arguments this method accepts. - * @return The amount of the order (in taker tokens) that has been cancelled. - */ - public async getCancelledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> { - assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); + const exchangeContract = await this._getExchangeContractAsync(); + const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; + let fillAmountInBaseUnits = await exchangeContract.filled.callAsync(orderHash, defaultBlock); + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber + fillAmountInBaseUnits = new BigNumber(fillAmountInBaseUnits); + return fillAmountInBaseUnits; + } + /** + * Retrieve the takerAmount of an order that has been cancelled. + * @param orderHash The hex encoded orderHash for which you would like to retrieve the + * cancelled takerAmount. + * @param methodOpts Optional arguments this method accepts. + * @return The amount of the order (in taker tokens) that has been cancelled. + */ + public async getCancelledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> { + assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); - const exchangeContract = await this._getExchangeContractAsync(); - const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - let cancelledAmountInBaseUnits = await exchangeContract.cancelled.callAsync(orderHash, defaultBlock); - // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber - cancelledAmountInBaseUnits = new BigNumber(cancelledAmountInBaseUnits); - return cancelledAmountInBaseUnits; - } - /** - * Fills a signed order with an amount denominated in baseUnits of the taker token. - * Since the order in which transactions are included in the next block is indeterminate, race-conditions - * could arise where a users balance or allowance changes before the fillOrder executes. Because of this, - * we allow you to specify `shouldThrowOnInsufficientBalanceOrAllowance`. - * If false, the smart contract will not throw if the parties - * do not have sufficient balances/allowances, preserving gas costs. Setting it to true forgoes this check - * and causes the smart contract to throw (using all the gas supplied) instead. - * @param signedOrder An object that conforms to the SignedOrder interface. - * @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that - * you wish to fill. - * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw - * if upon execution the tokens cannot be transferred. - * @param takerAddress The user Ethereum address who would like to fill this order. - * Must be available via the supplied Web3.Provider - * passed to 0x.js. - * @param orderTransactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async fillOrderAsync( - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - shouldThrowOnInsufficientBalanceOrAllowance: boolean, - takerAddress: string, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const exchangeContract = await this._getExchangeContractAsync(); + const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; + let cancelledAmountInBaseUnits = await exchangeContract.cancelled.callAsync(orderHash, defaultBlock); + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber + cancelledAmountInBaseUnits = new BigNumber(cancelledAmountInBaseUnits); + return cancelledAmountInBaseUnits; + } + /** + * Fills a signed order with an amount denominated in baseUnits of the taker token. + * Since the order in which transactions are included in the next block is indeterminate, race-conditions + * could arise where a users balance or allowance changes before the fillOrder executes. Because of this, + * we allow you to specify `shouldThrowOnInsufficientBalanceOrAllowance`. + * If false, the smart contract will not throw if the parties + * do not have sufficient balances/allowances, preserving gas costs. Setting it to true forgoes this check + * and causes the smart contract to throw (using all the gas supplied) instead. + * @param signedOrder An object that conforms to the SignedOrder interface. + * @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that + * you wish to fill. + * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw + * if upon execution the tokens cannot be transferred. + * @param takerAddress The user Ethereum address who would like to fill this order. + * Must be available via the supplied Web3.Provider + * passed to 0x.js. + * @param orderTransactionOpts Optional arguments this method accepts. + * @return Transaction hash. + */ + @decorators.asyncZeroExErrorHandler + public async fillOrderAsync( + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + shouldThrowOnInsufficientBalanceOrAllowance: boolean, + takerAddress: string, + orderTransactionOpts: OrderTransactionOpts = {}, + ): Promise<string> { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); + assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const exchangeInstance = await this._getExchangeContractAsync(); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - takerAddress, - zrxTokenAddress, - ); - } + const exchangeInstance = await this._getExchangeContractAsync(); + const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) + ? SHOULD_VALIDATE_BY_DEFAULT + : orderTransactionOpts.shouldValidate; + if (shouldValidate) { + const zrxTokenAddress = this.getZRXTokenAddress(); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + signedOrder, + fillTakerTokenAmount, + takerAddress, + zrxTokenAddress, + ); + } - const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder); + const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder); - const txHash: string = await exchangeInstance.fillOrder.sendTransactionAsync( - orderAddresses, - orderValues, - fillTakerTokenAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - signedOrder.ecSignature.v, - signedOrder.ecSignature.r, - signedOrder.ecSignature.s, - { - from: takerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Sequentially and atomically fills signedOrders up to the specified takerTokenFillAmount. - * If the fill amount is reached - it succeeds and does not fill the rest of the orders. - * If fill amount is not reached - it fills as much of the fill amount as possible and succeeds. - * @param signedOrders The array of signedOrders that you would like to fill until - * takerTokenFillAmount is reached. - * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. - * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw if - * upon execution any of the tokens cannot be transferred. - * If set to false, the call will continue to fill subsequent - * signedOrders even when some cannot be filled. - * @param takerAddress The user Ethereum address who would like to fill these - * orders. Must be available via the supplied Web3.Provider - * passed to 0x.js. - * @param orderTransactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async fillOrdersUpToAsync( - signedOrders: SignedOrder[], - fillTakerTokenAmount: BigNumber, - shouldThrowOnInsufficientBalanceOrAllowance: boolean, - takerAddress: string, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); - const takerTokenAddresses = _.map(signedOrders, signedOrder => signedOrder.takerTokenAddress); - assert.hasAtMostOneUniqueValue( - takerTokenAddresses, - ExchangeContractErrs.MultipleTakerTokensInFillUpToDisallowed, - ); - const exchangeContractAddresses = _.map(signedOrders, signedOrder => signedOrder.exchangeContractAddress); - assert.hasAtMostOneUniqueValue( - exchangeContractAddresses, - ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, - ); - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const txHash: string = await exchangeInstance.fillOrder.sendTransactionAsync( + orderAddresses, + orderValues, + fillTakerTokenAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + signedOrder.ecSignature.v, + signedOrder.ecSignature.r, + signedOrder.ecSignature.s, + { + from: takerAddress, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Sequentially and atomically fills signedOrders up to the specified takerTokenFillAmount. + * If the fill amount is reached - it succeeds and does not fill the rest of the orders. + * If fill amount is not reached - it fills as much of the fill amount as possible and succeeds. + * @param signedOrders The array of signedOrders that you would like to fill until + * takerTokenFillAmount is reached. + * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. + * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw if + * upon execution any of the tokens cannot be transferred. + * If set to false, the call will continue to fill subsequent + * signedOrders even when some cannot be filled. + * @param takerAddress The user Ethereum address who would like to fill these + * orders. Must be available via the supplied Web3.Provider + * passed to 0x.js. + * @param orderTransactionOpts Optional arguments this method accepts. + * @return Transaction hash. + */ + @decorators.asyncZeroExErrorHandler + public async fillOrdersUpToAsync( + signedOrders: SignedOrder[], + fillTakerTokenAmount: BigNumber, + shouldThrowOnInsufficientBalanceOrAllowance: boolean, + takerAddress: string, + orderTransactionOpts: OrderTransactionOpts = {}, + ): Promise<string> { + assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema); + const takerTokenAddresses = _.map(signedOrders, signedOrder => signedOrder.takerTokenAddress); + assert.hasAtMostOneUniqueValue( + takerTokenAddresses, + ExchangeContractErrs.MultipleTakerTokensInFillUpToDisallowed, + ); + const exchangeContractAddresses = _.map(signedOrders, signedOrder => signedOrder.exchangeContractAddress); + assert.hasAtMostOneUniqueValue( + exchangeContractAddresses, + ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, + ); + assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); + assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - let filledTakerTokenAmount = new BigNumber(0); - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - for (const signedOrder of signedOrders) { - const singleFilledTakerTokenAmount = await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount.minus(filledTakerTokenAmount), - takerAddress, - zrxTokenAddress, - ); - filledTakerTokenAmount = filledTakerTokenAmount.plus(singleFilledTakerTokenAmount); - } - } + const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) + ? SHOULD_VALIDATE_BY_DEFAULT + : orderTransactionOpts.shouldValidate; + if (shouldValidate) { + let filledTakerTokenAmount = new BigNumber(0); + const zrxTokenAddress = this.getZRXTokenAddress(); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + for (const signedOrder of signedOrders) { + const singleFilledTakerTokenAmount = await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + signedOrder, + fillTakerTokenAmount.minus(filledTakerTokenAmount), + takerAddress, + zrxTokenAddress, + ); + filledTakerTokenAmount = filledTakerTokenAmount.plus(singleFilledTakerTokenAmount); + } + } - if (_.isEmpty(signedOrders)) { - throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - } + if (_.isEmpty(signedOrders)) { + throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); + } - const orderAddressesValuesAndSignatureArray = _.map(signedOrders, signedOrder => { - return [ - ...ExchangeWrapper._getOrderAddressesAndValues(signedOrder), - signedOrder.ecSignature.v, - signedOrder.ecSignature.r, - signedOrder.ecSignature.s, - ]; - }); - // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( - const [orderAddressesArray, orderValuesArray, vArray, rArray, sArray] = _.unzip<any>( - orderAddressesValuesAndSignatureArray, - ); + const orderAddressesValuesAndSignatureArray = _.map(signedOrders, signedOrder => { + return [ + ...ExchangeWrapper._getOrderAddressesAndValues(signedOrder), + signedOrder.ecSignature.v, + signedOrder.ecSignature.r, + signedOrder.ecSignature.s, + ]; + }); + // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( + const [orderAddressesArray, orderValuesArray, vArray, rArray, sArray] = _.unzip<any>( + orderAddressesValuesAndSignatureArray, + ); - const exchangeInstance = await this._getExchangeContractAsync(); - const txHash = await exchangeInstance.fillOrdersUpTo.sendTransactionAsync( - orderAddressesArray, - orderValuesArray, - fillTakerTokenAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - vArray, - rArray, - sArray, - { - from: takerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Batch version of fillOrderAsync. - * Executes multiple fills atomically in a single transaction. - * If shouldThrowOnInsufficientBalanceOrAllowance is set to false, it will continue filling subsequent orders even - * when earlier ones fail. - * When shouldThrowOnInsufficientBalanceOrAllowance is set to true, if any fill fails, the entire batch fails. - * @param orderFillRequests An array of objects that conform to the - * OrderFillRequest interface. - * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw - * if upon execution any of the tokens cannot be - * transferred. If set to false, the call will continue to - * fill subsequent signedOrders even when some - * cannot be filled. - * @param takerAddress The user Ethereum address who would like to fill - * these orders. Must be available via the supplied - * Web3.Provider passed to 0x.js. - * @param orderTransactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async batchFillOrdersAsync( - orderFillRequests: OrderFillRequest[], - shouldThrowOnInsufficientBalanceOrAllowance: boolean, - takerAddress: string, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema); - const exchangeContractAddresses = _.map( - orderFillRequests, - orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress, - ); - assert.hasAtMostOneUniqueValue( - exchangeContractAddresses, - ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, - ); - assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - for (const orderFillRequest of orderFillRequests) { - await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - orderFillRequest.signedOrder, - orderFillRequest.takerTokenFillAmount, - takerAddress, - zrxTokenAddress, - ); - } - } - if (_.isEmpty(orderFillRequests)) { - throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - } + const exchangeInstance = await this._getExchangeContractAsync(); + const txHash = await exchangeInstance.fillOrdersUpTo.sendTransactionAsync( + orderAddressesArray, + orderValuesArray, + fillTakerTokenAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + vArray, + rArray, + sArray, + { + from: takerAddress, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Batch version of fillOrderAsync. + * Executes multiple fills atomically in a single transaction. + * If shouldThrowOnInsufficientBalanceOrAllowance is set to false, it will continue filling subsequent orders even + * when earlier ones fail. + * When shouldThrowOnInsufficientBalanceOrAllowance is set to true, if any fill fails, the entire batch fails. + * @param orderFillRequests An array of objects that conform to the + * OrderFillRequest interface. + * @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw + * if upon execution any of the tokens cannot be + * transferred. If set to false, the call will continue to + * fill subsequent signedOrders even when some + * cannot be filled. + * @param takerAddress The user Ethereum address who would like to fill + * these orders. Must be available via the supplied + * Web3.Provider passed to 0x.js. + * @param orderTransactionOpts Optional arguments this method accepts. + * @return Transaction hash. + */ + @decorators.asyncZeroExErrorHandler + public async batchFillOrdersAsync( + orderFillRequests: OrderFillRequest[], + shouldThrowOnInsufficientBalanceOrAllowance: boolean, + takerAddress: string, + orderTransactionOpts: OrderTransactionOpts = {}, + ): Promise<string> { + assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema); + const exchangeContractAddresses = _.map( + orderFillRequests, + orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress, + ); + assert.hasAtMostOneUniqueValue( + exchangeContractAddresses, + ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, + ); + assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) + ? SHOULD_VALIDATE_BY_DEFAULT + : orderTransactionOpts.shouldValidate; + if (shouldValidate) { + const zrxTokenAddress = this.getZRXTokenAddress(); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + for (const orderFillRequest of orderFillRequests) { + await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + orderFillRequest.signedOrder, + orderFillRequest.takerTokenFillAmount, + takerAddress, + zrxTokenAddress, + ); + } + } + if (_.isEmpty(orderFillRequests)) { + throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); + } - const orderAddressesValuesAmountsAndSignatureArray = _.map(orderFillRequests, orderFillRequest => { - return [ - ...ExchangeWrapper._getOrderAddressesAndValues(orderFillRequest.signedOrder), - orderFillRequest.takerTokenFillAmount, - orderFillRequest.signedOrder.ecSignature.v, - orderFillRequest.signedOrder.ecSignature.r, - orderFillRequest.signedOrder.ecSignature.s, - ]; - }); - // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( - const [orderAddressesArray, orderValuesArray, fillTakerTokenAmounts, vArray, rArray, sArray] = _.unzip<any>( - orderAddressesValuesAmountsAndSignatureArray, - ); + const orderAddressesValuesAmountsAndSignatureArray = _.map(orderFillRequests, orderFillRequest => { + return [ + ...ExchangeWrapper._getOrderAddressesAndValues(orderFillRequest.signedOrder), + orderFillRequest.takerTokenFillAmount, + orderFillRequest.signedOrder.ecSignature.v, + orderFillRequest.signedOrder.ecSignature.r, + orderFillRequest.signedOrder.ecSignature.s, + ]; + }); + // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( + const [orderAddressesArray, orderValuesArray, fillTakerTokenAmounts, vArray, rArray, sArray] = _.unzip<any>( + orderAddressesValuesAmountsAndSignatureArray, + ); - const exchangeInstance = await this._getExchangeContractAsync(); - const txHash = await exchangeInstance.batchFillOrders.sendTransactionAsync( - orderAddressesArray, - orderValuesArray, - fillTakerTokenAmounts, - shouldThrowOnInsufficientBalanceOrAllowance, - vArray, - rArray, - sArray, - { - from: takerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Attempts to fill a specific amount of an order. If the entire amount specified cannot be filled, - * the fill order is abandoned. - * @param signedOrder An object that conforms to the SignedOrder interface. The - * signedOrder you wish to fill. - * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. - * @param takerAddress The user Ethereum address who would like to fill this order. - * Must be available via the supplied Web3.Provider passed to 0x.js. - * @param orderTransactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async fillOrKillOrderAsync( - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const exchangeInstance = await this._getExchangeContractAsync(); + const txHash = await exchangeInstance.batchFillOrders.sendTransactionAsync( + orderAddressesArray, + orderValuesArray, + fillTakerTokenAmounts, + shouldThrowOnInsufficientBalanceOrAllowance, + vArray, + rArray, + sArray, + { + from: takerAddress, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Attempts to fill a specific amount of an order. If the entire amount specified cannot be filled, + * the fill order is abandoned. + * @param signedOrder An object that conforms to the SignedOrder interface. The + * signedOrder you wish to fill. + * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. + * @param takerAddress The user Ethereum address who would like to fill this order. + * Must be available via the supplied Web3.Provider passed to 0x.js. + * @param orderTransactionOpts Optional arguments this method accepts. + * @return Transaction hash. + */ + @decorators.asyncZeroExErrorHandler + public async fillOrKillOrderAsync( + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + takerAddress: string, + orderTransactionOpts: OrderTransactionOpts = {}, + ): Promise<string> { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const exchangeInstance = await this._getExchangeContractAsync(); + const exchangeInstance = await this._getExchangeContractAsync(); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - takerAddress, - zrxTokenAddress, - ); - } + const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) + ? SHOULD_VALIDATE_BY_DEFAULT + : orderTransactionOpts.shouldValidate; + if (shouldValidate) { + const zrxTokenAddress = this.getZRXTokenAddress(); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + signedOrder, + fillTakerTokenAmount, + takerAddress, + zrxTokenAddress, + ); + } - const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder); - const txHash = await exchangeInstance.fillOrKillOrder.sendTransactionAsync( - orderAddresses, - orderValues, - fillTakerTokenAmount, - signedOrder.ecSignature.v, - signedOrder.ecSignature.r, - signedOrder.ecSignature.s, - { - from: takerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Batch version of fillOrKill. Allows a taker to specify a batch of orders that will either be atomically - * filled (each to the specified fillAmount) or aborted. - * @param orderFillRequests An array of objects that conform to the OrderFillRequest interface. - * @param takerAddress The user Ethereum address who would like to fill there orders. - * Must be available via the supplied Web3.Provider passed to 0x.js. - * @param orderTransactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async batchFillOrKillAsync( - orderFillRequests: OrderFillRequest[], - takerAddress: string, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema); - const exchangeContractAddresses = _.map( - orderFillRequests, - orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress, - ); - assert.hasAtMostOneUniqueValue( - exchangeContractAddresses, - ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, - ); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - if (_.isEmpty(orderFillRequests)) { - throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - } - const exchangeInstance = await this._getExchangeContractAsync(); + const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder); + const txHash = await exchangeInstance.fillOrKillOrder.sendTransactionAsync( + orderAddresses, + orderValues, + fillTakerTokenAmount, + signedOrder.ecSignature.v, + signedOrder.ecSignature.r, + signedOrder.ecSignature.s, + { + from: takerAddress, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Batch version of fillOrKill. Allows a taker to specify a batch of orders that will either be atomically + * filled (each to the specified fillAmount) or aborted. + * @param orderFillRequests An array of objects that conform to the OrderFillRequest interface. + * @param takerAddress The user Ethereum address who would like to fill there orders. + * Must be available via the supplied Web3.Provider passed to 0x.js. + * @param orderTransactionOpts Optional arguments this method accepts. + * @return Transaction hash. + */ + @decorators.asyncZeroExErrorHandler + public async batchFillOrKillAsync( + orderFillRequests: OrderFillRequest[], + takerAddress: string, + orderTransactionOpts: OrderTransactionOpts = {}, + ): Promise<string> { + assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema); + const exchangeContractAddresses = _.map( + orderFillRequests, + orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress, + ); + assert.hasAtMostOneUniqueValue( + exchangeContractAddresses, + ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, + ); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + if (_.isEmpty(orderFillRequests)) { + throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); + } + const exchangeInstance = await this._getExchangeContractAsync(); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - for (const orderFillRequest of orderFillRequests) { - await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - orderFillRequest.signedOrder, - orderFillRequest.takerTokenFillAmount, - takerAddress, - zrxTokenAddress, - ); - } - } + const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) + ? SHOULD_VALIDATE_BY_DEFAULT + : orderTransactionOpts.shouldValidate; + if (shouldValidate) { + const zrxTokenAddress = this.getZRXTokenAddress(); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + for (const orderFillRequest of orderFillRequests) { + await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + orderFillRequest.signedOrder, + orderFillRequest.takerTokenFillAmount, + takerAddress, + zrxTokenAddress, + ); + } + } - const orderAddressesValuesAndTakerTokenFillAmounts = _.map(orderFillRequests, request => { - return [ - ...ExchangeWrapper._getOrderAddressesAndValues(request.signedOrder), - request.takerTokenFillAmount, - request.signedOrder.ecSignature.v, - request.signedOrder.ecSignature.r, - request.signedOrder.ecSignature.s, - ]; - }); + const orderAddressesValuesAndTakerTokenFillAmounts = _.map(orderFillRequests, request => { + return [ + ...ExchangeWrapper._getOrderAddressesAndValues(request.signedOrder), + request.takerTokenFillAmount, + request.signedOrder.ecSignature.v, + request.signedOrder.ecSignature.r, + request.signedOrder.ecSignature.s, + ]; + }); - // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( - const [orderAddresses, orderValues, fillTakerTokenAmounts, vParams, rParams, sParams] = _.unzip<any>( - orderAddressesValuesAndTakerTokenFillAmounts, - ); - const txHash = await exchangeInstance.batchFillOrKillOrders.sendTransactionAsync( - orderAddresses, - orderValues, - fillTakerTokenAmounts, - vParams, - rParams, - sParams, - { - from: takerAddress, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Cancel a given fill amount of an order. Cancellations are cumulative. - * @param order An object that conforms to the Order or SignedOrder interface. - * The order you would like to cancel. - * @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel. - * @param transactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async cancelOrderAsync( - order: Order | SignedOrder, - cancelTakerTokenAmount: BigNumber, - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema('order', order, schemas.orderSchema); - assert.isValidBaseUnitAmount('takerTokenCancelAmount', cancelTakerTokenAmount); - await assert.isSenderAddressAsync('order.maker', order.maker, this._web3Wrapper); + // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( + const [orderAddresses, orderValues, fillTakerTokenAmounts, vParams, rParams, sParams] = _.unzip<any>( + orderAddressesValuesAndTakerTokenFillAmounts, + ); + const txHash = await exchangeInstance.batchFillOrKillOrders.sendTransactionAsync( + orderAddresses, + orderValues, + fillTakerTokenAmounts, + vParams, + rParams, + sParams, + { + from: takerAddress, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Cancel a given fill amount of an order. Cancellations are cumulative. + * @param order An object that conforms to the Order or SignedOrder interface. + * The order you would like to cancel. + * @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel. + * @param transactionOpts Optional arguments this method accepts. + * @return Transaction hash. + */ + @decorators.asyncZeroExErrorHandler + public async cancelOrderAsync( + order: Order | SignedOrder, + cancelTakerTokenAmount: BigNumber, + orderTransactionOpts: OrderTransactionOpts = {}, + ): Promise<string> { + assert.doesConformToSchema('order', order, schemas.orderSchema); + assert.isValidBaseUnitAmount('takerTokenCancelAmount', cancelTakerTokenAmount); + await assert.isSenderAddressAsync('order.maker', order.maker, this._web3Wrapper); - const exchangeInstance = await this._getExchangeContractAsync(); + const exchangeInstance = await this._getExchangeContractAsync(); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const orderHash = utils.getOrderHashHex(order); - const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); - OrderValidationUtils.validateCancelOrderThrowIfInvalid( - order, - cancelTakerTokenAmount, - unavailableTakerTokenAmount, - ); - } + const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) + ? SHOULD_VALIDATE_BY_DEFAULT + : orderTransactionOpts.shouldValidate; + if (shouldValidate) { + const orderHash = utils.getOrderHashHex(order); + const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); + OrderValidationUtils.validateCancelOrderThrowIfInvalid( + order, + cancelTakerTokenAmount, + unavailableTakerTokenAmount, + ); + } - const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order); - const txHash = await exchangeInstance.cancelOrder.sendTransactionAsync( - orderAddresses, - orderValues, - cancelTakerTokenAmount, - { - from: order.maker, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Batch version of cancelOrderAsync. Atomically cancels multiple orders in a single transaction. - * All orders must be from the same maker. - * @param orderCancellationRequests An array of objects that conform to the OrderCancellationRequest - * interface. - * @param transactionOpts Optional arguments this method accepts. - * @return Transaction hash. - */ - @decorators.asyncZeroExErrorHandler - public async batchCancelOrdersAsync( - orderCancellationRequests: OrderCancellationRequest[], - orderTransactionOpts: OrderTransactionOpts = {}, - ): Promise<string> { - assert.doesConformToSchema( - 'orderCancellationRequests', - orderCancellationRequests, - schemas.orderCancellationRequestsSchema, - ); - const exchangeContractAddresses = _.map( - orderCancellationRequests, - orderCancellationRequest => orderCancellationRequest.order.exchangeContractAddress, - ); - assert.hasAtMostOneUniqueValue( - exchangeContractAddresses, - ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, - ); - const makers = _.map(orderCancellationRequests, cancellationRequest => cancellationRequest.order.maker); - assert.hasAtMostOneUniqueValue(makers, ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed); - const maker = makers[0]; - await assert.isSenderAddressAsync('maker', maker, this._web3Wrapper); - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - for (const orderCancellationRequest of orderCancellationRequests) { - const orderHash = utils.getOrderHashHex(orderCancellationRequest.order); - const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); - OrderValidationUtils.validateCancelOrderThrowIfInvalid( - orderCancellationRequest.order, - orderCancellationRequest.takerTokenCancelAmount, - unavailableTakerTokenAmount, - ); - } - } - if (_.isEmpty(orderCancellationRequests)) { - throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - } - const exchangeInstance = await this._getExchangeContractAsync(); - const orderAddressesValuesAndTakerTokenCancelAmounts = _.map(orderCancellationRequests, cancellationRequest => { - return [ - ...ExchangeWrapper._getOrderAddressesAndValues(cancellationRequest.order), - cancellationRequest.takerTokenCancelAmount, - ]; - }); - // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( - const [orderAddresses, orderValues, cancelTakerTokenAmounts] = _.unzip<any>( - orderAddressesValuesAndTakerTokenCancelAmounts, - ); - const txHash = await exchangeInstance.batchCancelOrders.sendTransactionAsync( - orderAddresses, - orderValues, - cancelTakerTokenAmounts, - { - from: maker, - gas: orderTransactionOpts.gasLimit, - gasPrice: orderTransactionOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Subscribe to an event type emitted by the Exchange contract. - * @param eventName The exchange contract event you would like to subscribe to. - * @param indexFilterValues An object where the keys are indexed args returned by the event and - * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` - * @param callback Callback that gets called when a log is added/removed - * @return Subscription token used later to unsubscribe - */ - public subscribe<ArgsType extends ExchangeContractEventArgs>( - eventName: ExchangeEvents, - indexFilterValues: IndexedFilterValues, - callback: EventCallback<ArgsType>, - ): string { - assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - assert.isFunction('callback', callback); - const exchangeContractAddress = this.getContractAddress(); - const subscriptionToken = this._subscribe<ArgsType>( - exchangeContractAddress, - eventName, - indexFilterValues, - artifacts.ExchangeArtifact.abi, - callback, - ); - return subscriptionToken; - } - /** - * Cancel a subscription - * @param subscriptionToken Subscription token returned by `subscribe()` - */ - public unsubscribe(subscriptionToken: string): void { - this._unsubscribe(subscriptionToken); - } - /** - * Cancels all existing subscriptions - */ - public unsubscribeAll(): void { - super.unsubscribeAll(); - } - /** - * Gets historical logs without creating a subscription - * @param eventName The exchange contract event you would like to subscribe to. - * @param blockRange Block range to get logs from. - * @param indexFilterValues An object where the keys are indexed args returned by the event and - * the value is the value you are interested in. E.g `{_from: aUserAddressHex}` - * @return Array of logs that match the parameters - */ - public async getLogsAsync<ArgsType extends ExchangeContractEventArgs>( - eventName: ExchangeEvents, - blockRange: BlockRange, - indexFilterValues: IndexedFilterValues, - ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { - assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents); - assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - const exchangeContractAddress = this.getContractAddress(); - const logs = await this._getLogsAsync<ArgsType>( - exchangeContractAddress, - eventName, - blockRange, - indexFilterValues, - artifacts.ExchangeArtifact.abi, - ); - return logs; - } - /** - * Retrieves the Ethereum address of the Exchange contract deployed on the network - * that the user-passed web3 provider is connected to. - * @returns The Ethereum address of the Exchange contract being used. - */ - public getContractAddress(): string { - const contractAddress = this._getContractAddress(artifacts.ExchangeArtifact, this._contractAddressIfExists); - return contractAddress; - } - /** - * Checks if order is still fillable and throws an error otherwise. Useful for orderbook - * pruning where you want to remove stale orders without knowing who the taker will be. - * @param signedOrder An object that conforms to the SignedOrder interface. The - * signedOrder you wish to validate. - * @param opts An object that conforms to the ValidateOrderFillableOpts - * interface. Allows specifying a specific fillTakerTokenAmount - * to validate for. - */ - public async validateOrderFillableOrThrowAsync( - signedOrder: SignedOrder, - opts?: ValidateOrderFillableOpts, - ): Promise<void> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - const zrxTokenAddress = this.getZRXTokenAddress(); - const expectedFillTakerTokenAmount = !_.isUndefined(opts) ? opts.expectedFillTakerTokenAmount : undefined; - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - await this._orderValidationUtils.validateOrderFillableOrThrowAsync( - exchangeTradeEmulator, - signedOrder, - zrxTokenAddress, - expectedFillTakerTokenAmount, - ); - } - /** - * Checks if order fill will succeed and throws an error otherwise. - * @param signedOrder An object that conforms to the SignedOrder interface. The - * signedOrder you wish to fill. - * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. - * @param takerAddress The user Ethereum address who would like to fill this order. - * Must be available via the supplied Web3.Provider passed to 0x.js. - */ - public async validateFillOrderThrowIfInvalidAsync( - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - ): Promise<void> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - takerAddress, - zrxTokenAddress, - ); - } - /** - * Checks if cancelling a given order will succeed and throws an informative error if it won't. - * @param order An object that conforms to the Order or SignedOrder interface. - * The order you would like to cancel. - * @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel. - */ - public async validateCancelOrderThrowIfInvalidAsync( - order: Order, - cancelTakerTokenAmount: BigNumber, - ): Promise<void> { - assert.doesConformToSchema('order', order, schemas.orderSchema); - assert.isValidBaseUnitAmount('cancelTakerTokenAmount', cancelTakerTokenAmount); - const orderHash = utils.getOrderHashHex(order); - const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); - OrderValidationUtils.validateCancelOrderThrowIfInvalid( - order, - cancelTakerTokenAmount, - unavailableTakerTokenAmount, - ); - } - /** - * Checks if calling fillOrKill on a given order will succeed and throws an informative error if it won't. - * @param signedOrder An object that conforms to the SignedOrder interface. The - * signedOrder you wish to fill. - * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. - * @param takerAddress The user Ethereum address who would like to fill this order. - * Must be available via the supplied Web3.Provider passed to 0x.js. - */ - public async validateFillOrKillOrderThrowIfInvalidAsync( - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - ): Promise<void> { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - takerAddress, - zrxTokenAddress, - ); - } - /** - * Checks if rounding error will be > 0.1% when computing makerTokenAmount by doing: - * `(fillTakerTokenAmount * makerTokenAmount) / takerTokenAmount`. - * 0x Protocol does not accept any trades that result in large rounding errors. This means that tokens with few or - * no decimals can only be filled in quantities and ratios that avoid large rounding errors. - * @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that you wish to fill. - * @param takerTokenAmount The order size on the taker side - * @param makerTokenAmount The order size on the maker side - */ - public async isRoundingErrorAsync( - fillTakerTokenAmount: BigNumber, - takerTokenAmount: BigNumber, - makerTokenAmount: BigNumber, - ): Promise<boolean> { - assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); - assert.isValidBaseUnitAmount('takerTokenAmount', takerTokenAmount); - assert.isValidBaseUnitAmount('makerTokenAmount', makerTokenAmount); - const exchangeInstance = await this._getExchangeContractAsync(); - const isRoundingError = await exchangeInstance.isRoundingError.callAsync( - fillTakerTokenAmount, - takerTokenAmount, - makerTokenAmount, - ); - return isRoundingError; - } - /** - * Checks if logs contain LogError, which is emmited by Exchange contract on transaction failure. - * @param logs Transaction logs as returned by `zeroEx.awaitTransactionMinedAsync` - */ - public throwLogErrorsAsErrors(logs: Array<LogWithDecodedArgs<DecodedLogArgs> | Web3.LogEntry>): void { - const errLog = _.find(logs, { - event: ExchangeEvents.LogError, - }) as LogWithDecodedArgs<LogErrorContractEventArgs> | undefined; - if (!_.isUndefined(errLog)) { - const logArgs = errLog.args; - const errCode = logArgs.errorId.toNumber(); - const errMessage = this._exchangeContractErrCodesToMsg[errCode]; - throw new Error(errMessage); - } - } - /** - * Returns the ZRX token address used by the exchange contract. - * @return Address of ZRX token - */ - public getZRXTokenAddress(): string { - const contractAddress = this._getContractAddress(artifacts.ZRXArtifact, this._zrxContractAddressIfExists); - return contractAddress; - } - private _invalidateContractInstances(): void { - this.unsubscribeAll(); - delete this._exchangeContractIfExists; - } - private async _isValidSignatureUsingContractCallAsync( - dataHex: string, - ecSignature: ECSignature, - signerAddressHex: string, - ): Promise<boolean> { - assert.isHexString('dataHex', dataHex); - assert.doesConformToSchema('ecSignature', ecSignature, schemas.ecSignatureSchema); - assert.isETHAddressHex('signerAddressHex', signerAddressHex); + const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order); + const txHash = await exchangeInstance.cancelOrder.sendTransactionAsync( + orderAddresses, + orderValues, + cancelTakerTokenAmount, + { + from: order.maker, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Batch version of cancelOrderAsync. Atomically cancels multiple orders in a single transaction. + * All orders must be from the same maker. + * @param orderCancellationRequests An array of objects that conform to the OrderCancellationRequest + * interface. + * @param transactionOpts Optional arguments this method accepts. + * @return Transaction hash. + */ + @decorators.asyncZeroExErrorHandler + public async batchCancelOrdersAsync( + orderCancellationRequests: OrderCancellationRequest[], + orderTransactionOpts: OrderTransactionOpts = {}, + ): Promise<string> { + assert.doesConformToSchema( + 'orderCancellationRequests', + orderCancellationRequests, + schemas.orderCancellationRequestsSchema, + ); + const exchangeContractAddresses = _.map( + orderCancellationRequests, + orderCancellationRequest => orderCancellationRequest.order.exchangeContractAddress, + ); + assert.hasAtMostOneUniqueValue( + exchangeContractAddresses, + ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress, + ); + const makers = _.map(orderCancellationRequests, cancellationRequest => cancellationRequest.order.maker); + assert.hasAtMostOneUniqueValue(makers, ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed); + const maker = makers[0]; + await assert.isSenderAddressAsync('maker', maker, this._web3Wrapper); + const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) + ? SHOULD_VALIDATE_BY_DEFAULT + : orderTransactionOpts.shouldValidate; + if (shouldValidate) { + for (const orderCancellationRequest of orderCancellationRequests) { + const orderHash = utils.getOrderHashHex(orderCancellationRequest.order); + const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); + OrderValidationUtils.validateCancelOrderThrowIfInvalid( + orderCancellationRequest.order, + orderCancellationRequest.takerTokenCancelAmount, + unavailableTakerTokenAmount, + ); + } + } + if (_.isEmpty(orderCancellationRequests)) { + throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); + } + const exchangeInstance = await this._getExchangeContractAsync(); + const orderAddressesValuesAndTakerTokenCancelAmounts = _.map(orderCancellationRequests, cancellationRequest => { + return [ + ...ExchangeWrapper._getOrderAddressesAndValues(cancellationRequest.order), + cancellationRequest.takerTokenCancelAmount, + ]; + }); + // We use _.unzip<any> because _.unzip doesn't type check if values have different types :'( + const [orderAddresses, orderValues, cancelTakerTokenAmounts] = _.unzip<any>( + orderAddressesValuesAndTakerTokenCancelAmounts, + ); + const txHash = await exchangeInstance.batchCancelOrders.sendTransactionAsync( + orderAddresses, + orderValues, + cancelTakerTokenAmounts, + { + from: maker, + gas: orderTransactionOpts.gasLimit, + gasPrice: orderTransactionOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Subscribe to an event type emitted by the Exchange contract. + * @param eventName The exchange contract event you would like to subscribe to. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` + * @param callback Callback that gets called when a log is added/removed + * @return Subscription token used later to unsubscribe + */ + public subscribe<ArgsType extends ExchangeContractEventArgs>( + eventName: ExchangeEvents, + indexFilterValues: IndexedFilterValues, + callback: EventCallback<ArgsType>, + ): string { + assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + assert.isFunction('callback', callback); + const exchangeContractAddress = this.getContractAddress(); + const subscriptionToken = this._subscribe<ArgsType>( + exchangeContractAddress, + eventName, + indexFilterValues, + artifacts.ExchangeArtifact.abi, + callback, + ); + return subscriptionToken; + } + /** + * Cancel a subscription + * @param subscriptionToken Subscription token returned by `subscribe()` + */ + public unsubscribe(subscriptionToken: string): void { + this._unsubscribe(subscriptionToken); + } + /** + * Cancels all existing subscriptions + */ + public unsubscribeAll(): void { + super.unsubscribeAll(); + } + /** + * Gets historical logs without creating a subscription + * @param eventName The exchange contract event you would like to subscribe to. + * @param blockRange Block range to get logs from. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{_from: aUserAddressHex}` + * @return Array of logs that match the parameters + */ + public async getLogsAsync<ArgsType extends ExchangeContractEventArgs>( + eventName: ExchangeEvents, + blockRange: BlockRange, + indexFilterValues: IndexedFilterValues, + ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { + assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents); + assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + const exchangeContractAddress = this.getContractAddress(); + const logs = await this._getLogsAsync<ArgsType>( + exchangeContractAddress, + eventName, + blockRange, + indexFilterValues, + artifacts.ExchangeArtifact.abi, + ); + return logs; + } + /** + * Retrieves the Ethereum address of the Exchange contract deployed on the network + * that the user-passed web3 provider is connected to. + * @returns The Ethereum address of the Exchange contract being used. + */ + public getContractAddress(): string { + const contractAddress = this._getContractAddress(artifacts.ExchangeArtifact, this._contractAddressIfExists); + return contractAddress; + } + /** + * Checks if order is still fillable and throws an error otherwise. Useful for orderbook + * pruning where you want to remove stale orders without knowing who the taker will be. + * @param signedOrder An object that conforms to the SignedOrder interface. The + * signedOrder you wish to validate. + * @param opts An object that conforms to the ValidateOrderFillableOpts + * interface. Allows specifying a specific fillTakerTokenAmount + * to validate for. + */ + public async validateOrderFillableOrThrowAsync( + signedOrder: SignedOrder, + opts?: ValidateOrderFillableOpts, + ): Promise<void> { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + const zrxTokenAddress = this.getZRXTokenAddress(); + const expectedFillTakerTokenAmount = !_.isUndefined(opts) ? opts.expectedFillTakerTokenAmount : undefined; + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + await this._orderValidationUtils.validateOrderFillableOrThrowAsync( + exchangeTradeEmulator, + signedOrder, + zrxTokenAddress, + expectedFillTakerTokenAmount, + ); + } + /** + * Checks if order fill will succeed and throws an error otherwise. + * @param signedOrder An object that conforms to the SignedOrder interface. The + * signedOrder you wish to fill. + * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. + * @param takerAddress The user Ethereum address who would like to fill this order. + * Must be available via the supplied Web3.Provider passed to 0x.js. + */ + public async validateFillOrderThrowIfInvalidAsync( + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + takerAddress: string, + ): Promise<void> { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const zrxTokenAddress = this.getZRXTokenAddress(); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + signedOrder, + fillTakerTokenAmount, + takerAddress, + zrxTokenAddress, + ); + } + /** + * Checks if cancelling a given order will succeed and throws an informative error if it won't. + * @param order An object that conforms to the Order or SignedOrder interface. + * The order you would like to cancel. + * @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel. + */ + public async validateCancelOrderThrowIfInvalidAsync( + order: Order, + cancelTakerTokenAmount: BigNumber, + ): Promise<void> { + assert.doesConformToSchema('order', order, schemas.orderSchema); + assert.isValidBaseUnitAmount('cancelTakerTokenAmount', cancelTakerTokenAmount); + const orderHash = utils.getOrderHashHex(order); + const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash); + OrderValidationUtils.validateCancelOrderThrowIfInvalid( + order, + cancelTakerTokenAmount, + unavailableTakerTokenAmount, + ); + } + /** + * Checks if calling fillOrKill on a given order will succeed and throws an informative error if it won't. + * @param signedOrder An object that conforms to the SignedOrder interface. The + * signedOrder you wish to fill. + * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill. + * @param takerAddress The user Ethereum address who would like to fill this order. + * Must be available via the supplied Web3.Provider passed to 0x.js. + */ + public async validateFillOrKillOrderThrowIfInvalidAsync( + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + takerAddress: string, + ): Promise<void> { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); + await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper); + const zrxTokenAddress = this.getZRXTokenAddress(); + const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); + await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + signedOrder, + fillTakerTokenAmount, + takerAddress, + zrxTokenAddress, + ); + } + /** + * Checks if rounding error will be > 0.1% when computing makerTokenAmount by doing: + * `(fillTakerTokenAmount * makerTokenAmount) / takerTokenAmount`. + * 0x Protocol does not accept any trades that result in large rounding errors. This means that tokens with few or + * no decimals can only be filled in quantities and ratios that avoid large rounding errors. + * @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that you wish to fill. + * @param takerTokenAmount The order size on the taker side + * @param makerTokenAmount The order size on the maker side + */ + public async isRoundingErrorAsync( + fillTakerTokenAmount: BigNumber, + takerTokenAmount: BigNumber, + makerTokenAmount: BigNumber, + ): Promise<boolean> { + assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount); + assert.isValidBaseUnitAmount('takerTokenAmount', takerTokenAmount); + assert.isValidBaseUnitAmount('makerTokenAmount', makerTokenAmount); + const exchangeInstance = await this._getExchangeContractAsync(); + const isRoundingError = await exchangeInstance.isRoundingError.callAsync( + fillTakerTokenAmount, + takerTokenAmount, + makerTokenAmount, + ); + return isRoundingError; + } + /** + * Checks if logs contain LogError, which is emmited by Exchange contract on transaction failure. + * @param logs Transaction logs as returned by `zeroEx.awaitTransactionMinedAsync` + */ + public throwLogErrorsAsErrors(logs: Array<LogWithDecodedArgs<DecodedLogArgs> | Web3.LogEntry>): void { + const errLog = _.find(logs, { + event: ExchangeEvents.LogError, + }) as LogWithDecodedArgs<LogErrorContractEventArgs> | undefined; + if (!_.isUndefined(errLog)) { + const logArgs = errLog.args; + const errCode = logArgs.errorId.toNumber(); + const errMessage = this._exchangeContractErrCodesToMsg[errCode]; + throw new Error(errMessage); + } + } + /** + * Returns the ZRX token address used by the exchange contract. + * @return Address of ZRX token + */ + public getZRXTokenAddress(): string { + const contractAddress = this._getContractAddress(artifacts.ZRXArtifact, this._zrxContractAddressIfExists); + return contractAddress; + } + private _invalidateContractInstances(): void { + this.unsubscribeAll(); + delete this._exchangeContractIfExists; + } + private async _isValidSignatureUsingContractCallAsync( + dataHex: string, + ecSignature: ECSignature, + signerAddressHex: string, + ): Promise<boolean> { + assert.isHexString('dataHex', dataHex); + assert.doesConformToSchema('ecSignature', ecSignature, schemas.ecSignatureSchema); + assert.isETHAddressHex('signerAddressHex', signerAddressHex); - const exchangeInstance = await this._getExchangeContractAsync(); + const exchangeInstance = await this._getExchangeContractAsync(); - const isValidSignature = await exchangeInstance.isValidSignature.callAsync( - signerAddressHex, - dataHex, - ecSignature.v, - ecSignature.r, - ecSignature.s, - ); - return isValidSignature; - } - private async _getOrderHashHexUsingContractCallAsync(order: Order | SignedOrder): Promise<string> { - const exchangeInstance = await this._getExchangeContractAsync(); - const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order); - const orderHashHex = await exchangeInstance.getOrderHash.callAsync(orderAddresses, orderValues); - return orderHashHex; - } - private async _getExchangeContractAsync(): Promise<ExchangeContract> { - if (!_.isUndefined(this._exchangeContractIfExists)) { - return this._exchangeContractIfExists; - } - const web3ContractInstance = await this._instantiateContractIfExistsAsync( - artifacts.ExchangeArtifact, - this._contractAddressIfExists, - ); - const contractInstance = new ExchangeContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); - this._exchangeContractIfExists = contractInstance; - return this._exchangeContractIfExists; - } - private async _getTokenTransferProxyAddressAsync(): Promise<string> { - const exchangeInstance = await this._getExchangeContractAsync(); - const tokenTransferProxyAddress = await exchangeInstance.TOKEN_TRANSFER_PROXY_CONTRACT.callAsync(); - const tokenTransferProxyAddressLowerCase = tokenTransferProxyAddress.toLowerCase(); - return tokenTransferProxyAddressLowerCase; - } + const isValidSignature = await exchangeInstance.isValidSignature.callAsync( + signerAddressHex, + dataHex, + ecSignature.v, + ecSignature.r, + ecSignature.s, + ); + return isValidSignature; + } + private async _getOrderHashHexUsingContractCallAsync(order: Order | SignedOrder): Promise<string> { + const exchangeInstance = await this._getExchangeContractAsync(); + const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order); + const orderHashHex = await exchangeInstance.getOrderHash.callAsync(orderAddresses, orderValues); + return orderHashHex; + } + private async _getExchangeContractAsync(): Promise<ExchangeContract> { + if (!_.isUndefined(this._exchangeContractIfExists)) { + return this._exchangeContractIfExists; + } + const web3ContractInstance = await this._instantiateContractIfExistsAsync( + artifacts.ExchangeArtifact, + this._contractAddressIfExists, + ); + const contractInstance = new ExchangeContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); + this._exchangeContractIfExists = contractInstance; + return this._exchangeContractIfExists; + } + private async _getTokenTransferProxyAddressAsync(): Promise<string> { + const exchangeInstance = await this._getExchangeContractAsync(); + const tokenTransferProxyAddress = await exchangeInstance.TOKEN_TRANSFER_PROXY_CONTRACT.callAsync(); + const tokenTransferProxyAddressLowerCase = tokenTransferProxyAddress.toLowerCase(); + return tokenTransferProxyAddressLowerCase; + } } // tslint:disable:max-file-line-count diff --git a/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts index f54aaf0f8..bce48a590 100644 --- a/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts @@ -13,117 +13,117 @@ import { TokenRegistryContract } from './generated/token_registry'; * This class includes all the functionality related to interacting with the 0x Token Registry smart contract. */ export class TokenRegistryWrapper extends ContractWrapper { - private _tokenRegistryContractIfExists?: TokenRegistryContract; - private _contractAddressIfExists?: string; - private static _createTokenFromMetadata(metadata: TokenMetadata): Token | undefined { - if (metadata[0] === constants.NULL_ADDRESS) { - return undefined; - } - const token = { - address: metadata[0], - name: metadata[1], - symbol: metadata[2], - decimals: metadata[3].toNumber(), - }; - return token; - } - constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { - super(web3Wrapper, networkId); - this._contractAddressIfExists = contractAddressIfExists; - } - /** - * Retrieves all the tokens currently listed in the Token Registry smart contract - * @return An array of objects that conform to the Token interface. - */ - public async getTokensAsync(): Promise<Token[]> { - const addresses = await this.getTokenAddressesAsync(); - const tokenPromises: Array<Promise<Token | undefined>> = _.map(addresses, async (address: string) => - this.getTokenIfExistsAsync(address), - ); - const tokens = await Promise.all(tokenPromises); - return tokens as Token[]; - } - /** - * Retrieves all the addresses of the tokens currently listed in the Token Registry smart contract - * @return An array of token addresses. - */ - public async getTokenAddressesAsync(): Promise<string[]> { - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const addresses = await tokenRegistryContract.getTokenAddresses.callAsync(); - return addresses; - } - /** - * Retrieves a token by address currently listed in the Token Registry smart contract - * @return An object that conforms to the Token interface or undefined if token not found. - */ - public async getTokenIfExistsAsync(address: string): Promise<Token | undefined> { - assert.isETHAddressHex('address', address); + private _tokenRegistryContractIfExists?: TokenRegistryContract; + private _contractAddressIfExists?: string; + private static _createTokenFromMetadata(metadata: TokenMetadata): Token | undefined { + if (metadata[0] === constants.NULL_ADDRESS) { + return undefined; + } + const token = { + address: metadata[0], + name: metadata[1], + symbol: metadata[2], + decimals: metadata[3].toNumber(), + }; + return token; + } + constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { + super(web3Wrapper, networkId); + this._contractAddressIfExists = contractAddressIfExists; + } + /** + * Retrieves all the tokens currently listed in the Token Registry smart contract + * @return An array of objects that conform to the Token interface. + */ + public async getTokensAsync(): Promise<Token[]> { + const addresses = await this.getTokenAddressesAsync(); + const tokenPromises: Array<Promise<Token | undefined>> = _.map(addresses, async (address: string) => + this.getTokenIfExistsAsync(address), + ); + const tokens = await Promise.all(tokenPromises); + return tokens as Token[]; + } + /** + * Retrieves all the addresses of the tokens currently listed in the Token Registry smart contract + * @return An array of token addresses. + */ + public async getTokenAddressesAsync(): Promise<string[]> { + const tokenRegistryContract = await this._getTokenRegistryContractAsync(); + const addresses = await tokenRegistryContract.getTokenAddresses.callAsync(); + return addresses; + } + /** + * Retrieves a token by address currently listed in the Token Registry smart contract + * @return An object that conforms to the Token interface or undefined if token not found. + */ + public async getTokenIfExistsAsync(address: string): Promise<Token | undefined> { + assert.isETHAddressHex('address', address); - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const metadata = await tokenRegistryContract.getTokenMetaData.callAsync(address); - const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); - return token; - } - public async getTokenAddressBySymbolIfExistsAsync(symbol: string): Promise<string | undefined> { - assert.isString('symbol', symbol); - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const addressIfExists = await tokenRegistryContract.getTokenAddressBySymbol.callAsync(symbol); - if (addressIfExists === constants.NULL_ADDRESS) { - return undefined; - } - return addressIfExists; - } - public async getTokenAddressByNameIfExistsAsync(name: string): Promise<string | undefined> { - assert.isString('name', name); - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const addressIfExists = await tokenRegistryContract.getTokenAddressByName.callAsync(name); - if (addressIfExists === constants.NULL_ADDRESS) { - return undefined; - } - return addressIfExists; - } - public async getTokenBySymbolIfExistsAsync(symbol: string): Promise<Token | undefined> { - assert.isString('symbol', symbol); - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const metadata = await tokenRegistryContract.getTokenBySymbol.callAsync(symbol); - const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); - return token; - } - public async getTokenByNameIfExistsAsync(name: string): Promise<Token | undefined> { - assert.isString('name', name); - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const metadata = await tokenRegistryContract.getTokenByName.callAsync(name); - const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); - return token; - } - /** - * Retrieves the Ethereum address of the TokenRegistry contract deployed on the network - * that the user-passed web3 provider is connected to. - * @returns The Ethereum address of the TokenRegistry contract being used. - */ - public getContractAddress(): string { - const contractAddress = this._getContractAddress( - artifacts.TokenRegistryArtifact, - this._contractAddressIfExists, - ); - return contractAddress; - } - private _invalidateContractInstance(): void { - delete this._tokenRegistryContractIfExists; - } - private async _getTokenRegistryContractAsync(): Promise<TokenRegistryContract> { - if (!_.isUndefined(this._tokenRegistryContractIfExists)) { - return this._tokenRegistryContractIfExists; - } - const web3ContractInstance = await this._instantiateContractIfExistsAsync( - artifacts.TokenRegistryArtifact, - this._contractAddressIfExists, - ); - const contractInstance = new TokenRegistryContract( - web3ContractInstance, - this._web3Wrapper.getContractDefaults(), - ); - this._tokenRegistryContractIfExists = contractInstance; - return this._tokenRegistryContractIfExists; - } + const tokenRegistryContract = await this._getTokenRegistryContractAsync(); + const metadata = await tokenRegistryContract.getTokenMetaData.callAsync(address); + const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); + return token; + } + public async getTokenAddressBySymbolIfExistsAsync(symbol: string): Promise<string | undefined> { + assert.isString('symbol', symbol); + const tokenRegistryContract = await this._getTokenRegistryContractAsync(); + const addressIfExists = await tokenRegistryContract.getTokenAddressBySymbol.callAsync(symbol); + if (addressIfExists === constants.NULL_ADDRESS) { + return undefined; + } + return addressIfExists; + } + public async getTokenAddressByNameIfExistsAsync(name: string): Promise<string | undefined> { + assert.isString('name', name); + const tokenRegistryContract = await this._getTokenRegistryContractAsync(); + const addressIfExists = await tokenRegistryContract.getTokenAddressByName.callAsync(name); + if (addressIfExists === constants.NULL_ADDRESS) { + return undefined; + } + return addressIfExists; + } + public async getTokenBySymbolIfExistsAsync(symbol: string): Promise<Token | undefined> { + assert.isString('symbol', symbol); + const tokenRegistryContract = await this._getTokenRegistryContractAsync(); + const metadata = await tokenRegistryContract.getTokenBySymbol.callAsync(symbol); + const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); + return token; + } + public async getTokenByNameIfExistsAsync(name: string): Promise<Token | undefined> { + assert.isString('name', name); + const tokenRegistryContract = await this._getTokenRegistryContractAsync(); + const metadata = await tokenRegistryContract.getTokenByName.callAsync(name); + const token = TokenRegistryWrapper._createTokenFromMetadata(metadata); + return token; + } + /** + * Retrieves the Ethereum address of the TokenRegistry contract deployed on the network + * that the user-passed web3 provider is connected to. + * @returns The Ethereum address of the TokenRegistry contract being used. + */ + public getContractAddress(): string { + const contractAddress = this._getContractAddress( + artifacts.TokenRegistryArtifact, + this._contractAddressIfExists, + ); + return contractAddress; + } + private _invalidateContractInstance(): void { + delete this._tokenRegistryContractIfExists; + } + private async _getTokenRegistryContractAsync(): Promise<TokenRegistryContract> { + if (!_.isUndefined(this._tokenRegistryContractIfExists)) { + return this._tokenRegistryContractIfExists; + } + const web3ContractInstance = await this._instantiateContractIfExistsAsync( + artifacts.TokenRegistryArtifact, + this._contractAddressIfExists, + ); + const contractInstance = new TokenRegistryContract( + web3ContractInstance, + this._web3Wrapper.getContractDefaults(), + ); + this._tokenRegistryContractIfExists = contractInstance; + return this._tokenRegistryContractIfExists; + } } diff --git a/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts index f5d9d108a..635588fed 100644 --- a/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts @@ -10,59 +10,59 @@ import { TokenTransferProxyContract } from './generated/token_transfer_proxy'; * This class includes the functionality related to interacting with the TokenTransferProxy contract. */ export class TokenTransferProxyWrapper extends ContractWrapper { - private _tokenTransferProxyContractIfExists?: TokenTransferProxyContract; - private _contractAddressIfExists?: string; - constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { - super(web3Wrapper, networkId); - this._contractAddressIfExists = contractAddressIfExists; - } - /** - * Check if the Exchange contract address is authorized by the TokenTransferProxy contract. - * @param exchangeContractAddress The hex encoded address of the Exchange contract to call. - * @return Whether the exchangeContractAddress is authorized. - */ - public async isAuthorizedAsync(exchangeContractAddress: string): Promise<boolean> { - const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync(); - const isAuthorized = await tokenTransferProxyContractInstance.authorized.callAsync(exchangeContractAddress); - return isAuthorized; - } - /** - * Get the list of all Exchange contract addresses authorized by the TokenTransferProxy contract. - * @return The list of authorized addresses. - */ - public async getAuthorizedAddressesAsync(): Promise<string[]> { - const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync(); - const authorizedAddresses = await tokenTransferProxyContractInstance.getAuthorizedAddresses.callAsync(); - return authorizedAddresses; - } - /** - * Retrieves the Ethereum address of the TokenTransferProxy contract deployed on the network - * that the user-passed web3 provider is connected to. - * @returns The Ethereum address of the TokenTransferProxy contract being used. - */ - public getContractAddress(): string { - const contractAddress = this._getContractAddress( - artifacts.TokenTransferProxyArtifact, - this._contractAddressIfExists, - ); - return contractAddress; - } - private _invalidateContractInstance(): void { - delete this._tokenTransferProxyContractIfExists; - } - private async _getTokenTransferProxyContractAsync(): Promise<TokenTransferProxyContract> { - if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) { - return this._tokenTransferProxyContractIfExists; - } - const web3ContractInstance = await this._instantiateContractIfExistsAsync( - artifacts.TokenTransferProxyArtifact, - this._contractAddressIfExists, - ); - const contractInstance = new TokenTransferProxyContract( - web3ContractInstance, - this._web3Wrapper.getContractDefaults(), - ); - this._tokenTransferProxyContractIfExists = contractInstance; - return this._tokenTransferProxyContractIfExists; - } + private _tokenTransferProxyContractIfExists?: TokenTransferProxyContract; + private _contractAddressIfExists?: string; + constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) { + super(web3Wrapper, networkId); + this._contractAddressIfExists = contractAddressIfExists; + } + /** + * Check if the Exchange contract address is authorized by the TokenTransferProxy contract. + * @param exchangeContractAddress The hex encoded address of the Exchange contract to call. + * @return Whether the exchangeContractAddress is authorized. + */ + public async isAuthorizedAsync(exchangeContractAddress: string): Promise<boolean> { + const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync(); + const isAuthorized = await tokenTransferProxyContractInstance.authorized.callAsync(exchangeContractAddress); + return isAuthorized; + } + /** + * Get the list of all Exchange contract addresses authorized by the TokenTransferProxy contract. + * @return The list of authorized addresses. + */ + public async getAuthorizedAddressesAsync(): Promise<string[]> { + const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync(); + const authorizedAddresses = await tokenTransferProxyContractInstance.getAuthorizedAddresses.callAsync(); + return authorizedAddresses; + } + /** + * Retrieves the Ethereum address of the TokenTransferProxy contract deployed on the network + * that the user-passed web3 provider is connected to. + * @returns The Ethereum address of the TokenTransferProxy contract being used. + */ + public getContractAddress(): string { + const contractAddress = this._getContractAddress( + artifacts.TokenTransferProxyArtifact, + this._contractAddressIfExists, + ); + return contractAddress; + } + private _invalidateContractInstance(): void { + delete this._tokenTransferProxyContractIfExists; + } + private async _getTokenTransferProxyContractAsync(): Promise<TokenTransferProxyContract> { + if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) { + return this._tokenTransferProxyContractIfExists; + } + const web3ContractInstance = await this._instantiateContractIfExistsAsync( + artifacts.TokenTransferProxyArtifact, + this._contractAddressIfExists, + ); + const contractInstance = new TokenTransferProxyContract( + web3ContractInstance, + this._web3Wrapper.getContractDefaults(), + ); + this._tokenTransferProxyContractIfExists = contractInstance; + return this._tokenTransferProxyContractIfExists; + } } diff --git a/packages/0x.js/src/contract_wrappers/token_wrapper.ts b/packages/0x.js/src/contract_wrappers/token_wrapper.ts index 7943f4a60..366cabd9e 100644 --- a/packages/0x.js/src/contract_wrappers/token_wrapper.ts +++ b/packages/0x.js/src/contract_wrappers/token_wrapper.ts @@ -5,15 +5,15 @@ import * as _ from 'lodash'; import { artifacts } from '../artifacts'; import { - BlockRange, - EventCallback, - IndexedFilterValues, - LogWithDecodedArgs, - MethodOpts, - TokenContractEventArgs, - TokenEvents, - TransactionOpts, - ZeroExError, + BlockRange, + EventCallback, + IndexedFilterValues, + LogWithDecodedArgs, + MethodOpts, + TokenContractEventArgs, + TokenEvents, + TransactionOpts, + ZeroExError, } from '../types'; import { AbiDecoder } from '../utils/abi_decoder'; import { assert } from '../utils/assert'; @@ -29,367 +29,367 @@ import { TokenTransferProxyWrapper } from './token_transfer_proxy_wrapper'; * to the 0x Proxy smart contract. */ export class TokenWrapper extends ContractWrapper { - public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; - private _tokenContractsByAddress: { [address: string]: TokenContract }; - private _tokenTransferProxyWrapper: TokenTransferProxyWrapper; - constructor( - web3Wrapper: Web3Wrapper, - networkId: number, - abiDecoder: AbiDecoder, - tokenTransferProxyWrapper: TokenTransferProxyWrapper, - ) { - super(web3Wrapper, networkId, abiDecoder); - this._tokenContractsByAddress = {}; - this._tokenTransferProxyWrapper = tokenTransferProxyWrapper; - } - /** - * Retrieves an owner's ERC20 token balance. - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param ownerAddress The hex encoded user Ethereum address whose balance you would like to check. - * @param methodOpts Optional arguments this method accepts. - * @return The owner's ERC20 token balance in base units. - */ - public async getBalanceAsync( - tokenAddress: string, - ownerAddress: string, - methodOpts?: MethodOpts, - ): Promise<BigNumber> { - assert.isETHAddressHex('ownerAddress', ownerAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); + public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; + private _tokenContractsByAddress: { [address: string]: TokenContract }; + private _tokenTransferProxyWrapper: TokenTransferProxyWrapper; + constructor( + web3Wrapper: Web3Wrapper, + networkId: number, + abiDecoder: AbiDecoder, + tokenTransferProxyWrapper: TokenTransferProxyWrapper, + ) { + super(web3Wrapper, networkId, abiDecoder); + this._tokenContractsByAddress = {}; + this._tokenTransferProxyWrapper = tokenTransferProxyWrapper; + } + /** + * Retrieves an owner's ERC20 token balance. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address whose balance you would like to check. + * @param methodOpts Optional arguments this method accepts. + * @return The owner's ERC20 token balance in base units. + */ + public async getBalanceAsync( + tokenAddress: string, + ownerAddress: string, + methodOpts?: MethodOpts, + ): Promise<BigNumber> { + assert.isETHAddressHex('ownerAddress', ownerAddress); + assert.isETHAddressHex('tokenAddress', tokenAddress); - const tokenContract = await this._getTokenContractAsync(tokenAddress); - const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - let balance = await tokenContract.balanceOf.callAsync(ownerAddress, defaultBlock); - // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber - balance = new BigNumber(balance); - return balance; - } - /** - * Sets the spender's allowance to a specified number of baseUnits on behalf of the owner address. - * Equivalent to the ERC20 spec method `approve`. - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance - * for spenderAddress. - * @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance. - * @param amountInBaseUnits The allowance amount you would like to set. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async setAllowanceAsync( - tokenAddress: string, - ownerAddress: string, - spenderAddress: string, - amountInBaseUnits: BigNumber, - txOpts: TransactionOpts = {}, - ): Promise<string> { - await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper); - assert.isETHAddressHex('spenderAddress', spenderAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); - assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); + const tokenContract = await this._getTokenContractAsync(tokenAddress); + const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; + let balance = await tokenContract.balanceOf.callAsync(ownerAddress, defaultBlock); + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber + balance = new BigNumber(balance); + return balance; + } + /** + * Sets the spender's allowance to a specified number of baseUnits on behalf of the owner address. + * Equivalent to the ERC20 spec method `approve`. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance + * for spenderAddress. + * @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance. + * @param amountInBaseUnits The allowance amount you would like to set. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async setAllowanceAsync( + tokenAddress: string, + ownerAddress: string, + spenderAddress: string, + amountInBaseUnits: BigNumber, + txOpts: TransactionOpts = {}, + ): Promise<string> { + await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper); + assert.isETHAddressHex('spenderAddress', spenderAddress); + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - const tokenContract = await this._getTokenContractAsync(tokenAddress); - const txHash = await tokenContract.approve.sendTransactionAsync(spenderAddress, amountInBaseUnits, { - from: ownerAddress, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, - }); - return txHash; - } - /** - * Sets the spender's allowance to an unlimited number of baseUnits on behalf of the owner address. - * Equivalent to the ERC20 spec method `approve`. - * Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating - * allowances set to the max amount (e.g ZRX, WETH) - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance - * for spenderAddress. - * @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async setUnlimitedAllowanceAsync( - tokenAddress: string, - ownerAddress: string, - spenderAddress: string, - txOpts: TransactionOpts = {}, - ): Promise<string> { - const txHash = await this.setAllowanceAsync( - tokenAddress, - ownerAddress, - spenderAddress, - this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, - txOpts, - ); - return txHash; - } - /** - * Retrieves the owners allowance in baseUnits set to the spender's address. - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param ownerAddress The hex encoded user Ethereum address whose allowance to spenderAddress - * you would like to retrieve. - * @param spenderAddress The hex encoded user Ethereum address who can spend the allowance you are fetching. - * @param methodOpts Optional arguments this method accepts. - */ - public async getAllowanceAsync( - tokenAddress: string, - ownerAddress: string, - spenderAddress: string, - methodOpts?: MethodOpts, - ): Promise<BigNumber> { - assert.isETHAddressHex('ownerAddress', ownerAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); + const tokenContract = await this._getTokenContractAsync(tokenAddress); + const txHash = await tokenContract.approve.sendTransactionAsync(spenderAddress, amountInBaseUnits, { + from: ownerAddress, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }); + return txHash; + } + /** + * Sets the spender's allowance to an unlimited number of baseUnits on behalf of the owner address. + * Equivalent to the ERC20 spec method `approve`. + * Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating + * allowances set to the max amount (e.g ZRX, WETH) + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance + * for spenderAddress. + * @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async setUnlimitedAllowanceAsync( + tokenAddress: string, + ownerAddress: string, + spenderAddress: string, + txOpts: TransactionOpts = {}, + ): Promise<string> { + const txHash = await this.setAllowanceAsync( + tokenAddress, + ownerAddress, + spenderAddress, + this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, + txOpts, + ); + return txHash; + } + /** + * Retrieves the owners allowance in baseUnits set to the spender's address. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address whose allowance to spenderAddress + * you would like to retrieve. + * @param spenderAddress The hex encoded user Ethereum address who can spend the allowance you are fetching. + * @param methodOpts Optional arguments this method accepts. + */ + public async getAllowanceAsync( + tokenAddress: string, + ownerAddress: string, + spenderAddress: string, + methodOpts?: MethodOpts, + ): Promise<BigNumber> { + assert.isETHAddressHex('ownerAddress', ownerAddress); + assert.isETHAddressHex('tokenAddress', tokenAddress); - const tokenContract = await this._getTokenContractAsync(tokenAddress); - const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - let allowanceInBaseUnits = await tokenContract.allowance.callAsync(ownerAddress, spenderAddress, defaultBlock); - // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber - allowanceInBaseUnits = new BigNumber(allowanceInBaseUnits); - return allowanceInBaseUnits; - } - /** - * Retrieves the owner's allowance in baseUnits set to the 0x proxy contract. - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param ownerAddress The hex encoded user Ethereum address whose proxy contract allowance we are retrieving. - * @param methodOpts Optional arguments this method accepts. - */ - public async getProxyAllowanceAsync( - tokenAddress: string, - ownerAddress: string, - methodOpts?: MethodOpts, - ): Promise<BigNumber> { - assert.isETHAddressHex('ownerAddress', ownerAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); + const tokenContract = await this._getTokenContractAsync(tokenAddress); + const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; + let allowanceInBaseUnits = await tokenContract.allowance.callAsync(ownerAddress, spenderAddress, defaultBlock); + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber + allowanceInBaseUnits = new BigNumber(allowanceInBaseUnits); + return allowanceInBaseUnits; + } + /** + * Retrieves the owner's allowance in baseUnits set to the 0x proxy contract. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address whose proxy contract allowance we are retrieving. + * @param methodOpts Optional arguments this method accepts. + */ + public async getProxyAllowanceAsync( + tokenAddress: string, + ownerAddress: string, + methodOpts?: MethodOpts, + ): Promise<BigNumber> { + assert.isETHAddressHex('ownerAddress', ownerAddress); + assert.isETHAddressHex('tokenAddress', tokenAddress); - const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress(); - const allowanceInBaseUnits = await this.getAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, methodOpts); - return allowanceInBaseUnits; - } - /** - * Sets the 0x proxy contract's allowance to a specified number of a tokens' baseUnits on behalf - * of an owner address. - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param ownerAddress The hex encoded user Ethereum address who is setting an allowance - * for the Proxy contract. - * @param amountInBaseUnits The allowance amount specified in baseUnits. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async setProxyAllowanceAsync( - tokenAddress: string, - ownerAddress: string, - amountInBaseUnits: BigNumber, - txOpts: TransactionOpts = {}, - ): Promise<string> { - assert.isETHAddressHex('ownerAddress', ownerAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); - assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); + const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress(); + const allowanceInBaseUnits = await this.getAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, methodOpts); + return allowanceInBaseUnits; + } + /** + * Sets the 0x proxy contract's allowance to a specified number of a tokens' baseUnits on behalf + * of an owner address. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address who is setting an allowance + * for the Proxy contract. + * @param amountInBaseUnits The allowance amount specified in baseUnits. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async setProxyAllowanceAsync( + tokenAddress: string, + ownerAddress: string, + amountInBaseUnits: BigNumber, + txOpts: TransactionOpts = {}, + ): Promise<string> { + assert.isETHAddressHex('ownerAddress', ownerAddress); + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress(); - const txHash = await this.setAllowanceAsync( - tokenAddress, - ownerAddress, - proxyAddress, - amountInBaseUnits, - txOpts, - ); - return txHash; - } - /** - * Sets the 0x proxy contract's allowance to a unlimited number of a tokens' baseUnits on behalf - * of an owner address. - * Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating - * allowances set to the max amount (e.g ZRX, WETH) - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param ownerAddress The hex encoded user Ethereum address who is setting an allowance - * for the Proxy contract. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async setUnlimitedProxyAllowanceAsync( - tokenAddress: string, - ownerAddress: string, - txOpts: TransactionOpts = {}, - ): Promise<string> { - const txHash = await this.setProxyAllowanceAsync( - tokenAddress, - ownerAddress, - this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, - txOpts, - ); - return txHash; - } - /** - * Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`. - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param fromAddress The hex encoded user Ethereum address that will send the funds. - * @param toAddress The hex encoded user Ethereum address that will receive the funds. - * @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async transferAsync( - tokenAddress: string, - fromAddress: string, - toAddress: string, - amountInBaseUnits: BigNumber, - txOpts: TransactionOpts = {}, - ): Promise<string> { - assert.isETHAddressHex('tokenAddress', tokenAddress); - await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper); - assert.isETHAddressHex('toAddress', toAddress); - assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); + const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress(); + const txHash = await this.setAllowanceAsync( + tokenAddress, + ownerAddress, + proxyAddress, + amountInBaseUnits, + txOpts, + ); + return txHash; + } + /** + * Sets the 0x proxy contract's allowance to a unlimited number of a tokens' baseUnits on behalf + * of an owner address. + * Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating + * allowances set to the max amount (e.g ZRX, WETH) + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param ownerAddress The hex encoded user Ethereum address who is setting an allowance + * for the Proxy contract. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async setUnlimitedProxyAllowanceAsync( + tokenAddress: string, + ownerAddress: string, + txOpts: TransactionOpts = {}, + ): Promise<string> { + const txHash = await this.setProxyAllowanceAsync( + tokenAddress, + ownerAddress, + this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, + txOpts, + ); + return txHash; + } + /** + * Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param fromAddress The hex encoded user Ethereum address that will send the funds. + * @param toAddress The hex encoded user Ethereum address that will receive the funds. + * @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async transferAsync( + tokenAddress: string, + fromAddress: string, + toAddress: string, + amountInBaseUnits: BigNumber, + txOpts: TransactionOpts = {}, + ): Promise<string> { + assert.isETHAddressHex('tokenAddress', tokenAddress); + await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper); + assert.isETHAddressHex('toAddress', toAddress); + assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - const tokenContract = await this._getTokenContractAsync(tokenAddress); + const tokenContract = await this._getTokenContractAsync(tokenAddress); - const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress); - if (fromAddressBalance.lessThan(amountInBaseUnits)) { - throw new Error(ZeroExError.InsufficientBalanceForTransfer); - } + const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress); + if (fromAddressBalance.lessThan(amountInBaseUnits)) { + throw new Error(ZeroExError.InsufficientBalanceForTransfer); + } - const txHash = await tokenContract.transfer.sendTransactionAsync(toAddress, amountInBaseUnits, { - from: fromAddress, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, - }); - return txHash; - } - /** - * Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`. - * Requires the fromAddress to have sufficient funds and to have approved an allowance of - * `amountInBaseUnits` to `senderAddress`. - * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. - * @param fromAddress The hex encoded user Ethereum address whose funds are being sent. - * @param toAddress The hex encoded user Ethereum address that will receive the funds. - * @param senderAddress The hex encoded user Ethereum address whose initiates the fund transfer. The - * `fromAddress` must have set an allowance to the `senderAddress` - * before this call. - * @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async transferFromAsync( - tokenAddress: string, - fromAddress: string, - toAddress: string, - senderAddress: string, - amountInBaseUnits: BigNumber, - txOpts: TransactionOpts = {}, - ): Promise<string> { - assert.isETHAddressHex('tokenAddress', tokenAddress); - assert.isETHAddressHex('fromAddress', fromAddress); - assert.isETHAddressHex('toAddress', toAddress); - await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper); - assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); + const txHash = await tokenContract.transfer.sendTransactionAsync(toAddress, amountInBaseUnits, { + from: fromAddress, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }); + return txHash; + } + /** + * Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`. + * Requires the fromAddress to have sufficient funds and to have approved an allowance of + * `amountInBaseUnits` to `senderAddress`. + * @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed. + * @param fromAddress The hex encoded user Ethereum address whose funds are being sent. + * @param toAddress The hex encoded user Ethereum address that will receive the funds. + * @param senderAddress The hex encoded user Ethereum address whose initiates the fund transfer. The + * `fromAddress` must have set an allowance to the `senderAddress` + * before this call. + * @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer. + * @param txOpts Transaction parameters. + * @return Transaction hash. + */ + public async transferFromAsync( + tokenAddress: string, + fromAddress: string, + toAddress: string, + senderAddress: string, + amountInBaseUnits: BigNumber, + txOpts: TransactionOpts = {}, + ): Promise<string> { + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.isETHAddressHex('fromAddress', fromAddress); + assert.isETHAddressHex('toAddress', toAddress); + await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper); + assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - const tokenContract = await this._getTokenContractAsync(tokenAddress); + const tokenContract = await this._getTokenContractAsync(tokenAddress); - const fromAddressAllowance = await this.getAllowanceAsync(tokenAddress, fromAddress, senderAddress); - if (fromAddressAllowance.lessThan(amountInBaseUnits)) { - throw new Error(ZeroExError.InsufficientAllowanceForTransfer); - } + const fromAddressAllowance = await this.getAllowanceAsync(tokenAddress, fromAddress, senderAddress); + if (fromAddressAllowance.lessThan(amountInBaseUnits)) { + throw new Error(ZeroExError.InsufficientAllowanceForTransfer); + } - const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress); - if (fromAddressBalance.lessThan(amountInBaseUnits)) { - throw new Error(ZeroExError.InsufficientBalanceForTransfer); - } + const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress); + if (fromAddressBalance.lessThan(amountInBaseUnits)) { + throw new Error(ZeroExError.InsufficientBalanceForTransfer); + } - const txHash = await tokenContract.transferFrom.sendTransactionAsync( - fromAddress, - toAddress, - amountInBaseUnits, - { - from: senderAddress, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, - }, - ); - return txHash; - } - /** - * Subscribe to an event type emitted by the Token contract. - * @param tokenAddress The hex encoded address where the ERC20 token is deployed. - * @param eventName The token contract event you would like to subscribe to. - * @param indexFilterValues An object where the keys are indexed args returned by the event and - * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` - * @param callback Callback that gets called when a log is added/removed - * @return Subscription token used later to unsubscribe - */ - public subscribe<ArgsType extends TokenContractEventArgs>( - tokenAddress: string, - eventName: TokenEvents, - indexFilterValues: IndexedFilterValues, - callback: EventCallback<ArgsType>, - ): string { - assert.isETHAddressHex('tokenAddress', tokenAddress); - assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - assert.isFunction('callback', callback); - const subscriptionToken = this._subscribe<ArgsType>( - tokenAddress, - eventName, - indexFilterValues, - artifacts.TokenArtifact.abi, - callback, - ); - return subscriptionToken; - } - /** - * Cancel a subscription - * @param subscriptionToken Subscription token returned by `subscribe()` - */ - public unsubscribe(subscriptionToken: string): void { - this._unsubscribe(subscriptionToken); - } - /** - * Cancels all existing subscriptions - */ - public unsubscribeAll(): void { - super.unsubscribeAll(); - } - /** - * Gets historical logs without creating a subscription - * @param tokenAddress An address of the token that emmited the logs. - * @param eventName The token contract event you would like to subscribe to. - * @param blockRange Block range to get logs from. - * @param indexFilterValues An object where the keys are indexed args returned by the event and - * the value is the value you are interested in. E.g `{_from: aUserAddressHex}` - * @return Array of logs that match the parameters - */ - public async getLogsAsync<ArgsType extends TokenContractEventArgs>( - tokenAddress: string, - eventName: TokenEvents, - blockRange: BlockRange, - indexFilterValues: IndexedFilterValues, - ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { - assert.isETHAddressHex('tokenAddress', tokenAddress); - assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); - assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - const logs = await this._getLogsAsync<ArgsType>( - tokenAddress, - eventName, - blockRange, - indexFilterValues, - artifacts.TokenArtifact.abi, - ); - return logs; - } - private _invalidateContractInstances(): void { - this.unsubscribeAll(); - this._tokenContractsByAddress = {}; - } - private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> { - let tokenContract = this._tokenContractsByAddress[tokenAddress]; - if (!_.isUndefined(tokenContract)) { - return tokenContract; - } - const web3ContractInstance = await this._instantiateContractIfExistsAsync( - artifacts.TokenArtifact, - tokenAddress, - ); - const contractInstance = new TokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); - tokenContract = contractInstance; - this._tokenContractsByAddress[tokenAddress] = tokenContract; - return tokenContract; - } + const txHash = await tokenContract.transferFrom.sendTransactionAsync( + fromAddress, + toAddress, + amountInBaseUnits, + { + from: senderAddress, + gas: txOpts.gasLimit, + gasPrice: txOpts.gasPrice, + }, + ); + return txHash; + } + /** + * Subscribe to an event type emitted by the Token contract. + * @param tokenAddress The hex encoded address where the ERC20 token is deployed. + * @param eventName The token contract event you would like to subscribe to. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` + * @param callback Callback that gets called when a log is added/removed + * @return Subscription token used later to unsubscribe + */ + public subscribe<ArgsType extends TokenContractEventArgs>( + tokenAddress: string, + eventName: TokenEvents, + indexFilterValues: IndexedFilterValues, + callback: EventCallback<ArgsType>, + ): string { + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + assert.isFunction('callback', callback); + const subscriptionToken = this._subscribe<ArgsType>( + tokenAddress, + eventName, + indexFilterValues, + artifacts.TokenArtifact.abi, + callback, + ); + return subscriptionToken; + } + /** + * Cancel a subscription + * @param subscriptionToken Subscription token returned by `subscribe()` + */ + public unsubscribe(subscriptionToken: string): void { + this._unsubscribe(subscriptionToken); + } + /** + * Cancels all existing subscriptions + */ + public unsubscribeAll(): void { + super.unsubscribeAll(); + } + /** + * Gets historical logs without creating a subscription + * @param tokenAddress An address of the token that emmited the logs. + * @param eventName The token contract event you would like to subscribe to. + * @param blockRange Block range to get logs from. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{_from: aUserAddressHex}` + * @return Array of logs that match the parameters + */ + public async getLogsAsync<ArgsType extends TokenContractEventArgs>( + tokenAddress: string, + eventName: TokenEvents, + blockRange: BlockRange, + indexFilterValues: IndexedFilterValues, + ): Promise<Array<LogWithDecodedArgs<ArgsType>>> { + assert.isETHAddressHex('tokenAddress', tokenAddress); + assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); + assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); + assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + const logs = await this._getLogsAsync<ArgsType>( + tokenAddress, + eventName, + blockRange, + indexFilterValues, + artifacts.TokenArtifact.abi, + ); + return logs; + } + private _invalidateContractInstances(): void { + this.unsubscribeAll(); + this._tokenContractsByAddress = {}; + } + private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> { + let tokenContract = this._tokenContractsByAddress[tokenAddress]; + if (!_.isUndefined(tokenContract)) { + return tokenContract; + } + const web3ContractInstance = await this._instantiateContractIfExistsAsync( + artifacts.TokenArtifact, + tokenAddress, + ); + const contractInstance = new TokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults()); + tokenContract = contractInstance; + this._tokenContractsByAddress[tokenAddress] = tokenContract; + return tokenContract; + } } diff --git a/packages/0x.js/src/globals.d.ts b/packages/0x.js/src/globals.d.ts index 4f4932b6e..9a53949b4 100644 --- a/packages/0x.js/src/globals.d.ts +++ b/packages/0x.js/src/globals.d.ts @@ -10,50 +10,50 @@ declare module 'web3-provider-engine/subproviders/rpc'; // disallow `namespace`, we disable tslint for the following. /* tslint:disable */ declare namespace Chai { - interface Assertion { - bignumber: Assertion; - // HACK: In order to comply with chai-as-promised we make eventually a `PromisedAssertion` not an `Assertion` - eventually: PromisedAssertion; - } + interface Assertion { + bignumber: Assertion; + // HACK: In order to comply with chai-as-promised we make eventually a `PromisedAssertion` not an `Assertion` + eventually: PromisedAssertion; + } } /* tslint:enable */ declare module '*.json' { - const json: any; - /* tslint:disable */ - export default json; - /* tslint:enable */ + const json: any; + /* tslint:disable */ + export default json; + /* tslint:enable */ } declare module 'ethereumjs-abi' { - const soliditySHA3: (argTypes: string[], args: any[]) => Buffer; + const soliditySHA3: (argTypes: string[], args: any[]) => Buffer; } // truffle-hdwallet-provider declarations declare module 'truffle-hdwallet-provider' { - import * as Web3 from 'web3'; - class HDWalletProvider implements Web3.Provider { - constructor(mnemonic: string, rpcUrl: string); - public sendAsync( - payload: Web3.JSONRPCRequestPayload, - callback: (err: Error, result: Web3.JSONRPCResponsePayload) => void, - ): void; - } - export = HDWalletProvider; + import * as Web3 from 'web3'; + class HDWalletProvider implements Web3.Provider { + constructor(mnemonic: string, rpcUrl: string); + public sendAsync( + payload: Web3.JSONRPCRequestPayload, + callback: (err: Error, result: Web3.JSONRPCResponsePayload) => void, + ): void; + } + export = HDWalletProvider; } // abi-decoder declarations interface DecodedLogArg {} interface DecodedLog { - name: string; - events: DecodedLogArg[]; + name: string; + events: DecodedLogArg[]; } declare module 'abi-decoder' { - import * as Web3 from 'web3'; - const addABI: (abi: Web3.AbiDefinition) => void; - const decodeLogs: (logs: Web3.LogEntry[]) => DecodedLog[]; + import * as Web3 from 'web3'; + const addABI: (abi: Web3.AbiDefinition) => void; + const decodeLogs: (logs: Web3.LogEntry[]) => DecodedLog[]; } declare module 'web3/lib/solidity/coder' { - const decodeParams: (types: string[], data: string) => any[]; + const decodeParams: (types: string[], data: string) => any[]; } diff --git a/packages/0x.js/src/globalsAugment.d.ts b/packages/0x.js/src/globalsAugment.d.ts index 3e2f2216b..c2f200ab8 100644 --- a/packages/0x.js/src/globalsAugment.d.ts +++ b/packages/0x.js/src/globalsAugment.d.ts @@ -3,21 +3,21 @@ import { BigNumber } from '@0xproject/utils'; // HACK: This module overrides the Chai namespace so that we can use BigNumber types inside. // Source: https://github.com/Microsoft/TypeScript/issues/7352#issuecomment-191547232 declare global { - // HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion - // interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise - // disallow `namespace`, we disable tslint for the following. - /* tslint:disable */ - namespace Chai { - interface NumberComparer { - (value: number | BigNumber, message?: string): Assertion; - } - interface NumericComparison { - greaterThan: NumberComparer; - } - } - /* tslint:enable */ - interface DecodedLogArg { - name: string; - value: string | BigNumber; - } + // HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion + // interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise + // disallow `namespace`, we disable tslint for the following. + /* tslint:disable */ + namespace Chai { + interface NumberComparer { + (value: number | BigNumber, message?: string): Assertion; + } + interface NumericComparison { + greaterThan: NumberComparer; + } + } + /* tslint:enable */ + interface DecodedLogArg { + name: string; + value: string | BigNumber; + } } diff --git a/packages/0x.js/src/index.ts b/packages/0x.js/src/index.ts index 599c3a2b0..ad5ef8534 100644 --- a/packages/0x.js/src/index.ts +++ b/packages/0x.js/src/index.ts @@ -1,50 +1,50 @@ export { ZeroEx } from './0x'; export { - Order, - BlockParamLiteral, - SignedOrder, - ECSignature, - ZeroExError, - EventCallback, - ExchangeContractErrs, - ContractEvent, - Token, - ExchangeEvents, - TokenEvents, - IndexedFilterValues, - BlockRange, - BlockParam, - OrderCancellationRequest, - OrderFillRequest, - LogErrorContractEventArgs, - LogCancelContractEventArgs, - LogFillContractEventArgs, - ExchangeContractEventArgs, - TransferContractEventArgs, - ApprovalContractEventArgs, - TokenContractEventArgs, - EtherTokenContractEventArgs, - WithdrawalContractEventArgs, - DepositContractEventArgs, - ContractEventArgs, - ContractEventArg, - Web3Provider, - ZeroExConfig, - EtherTokenEvents, - TransactionReceiptWithDecodedLogs, - LogWithDecodedArgs, - MethodOpts, - OrderTransactionOpts, - TransactionOpts, - FilterObject, - LogEvent, - DecodedLogEvent, - EventWatcherCallback, - OnOrderStateChangeCallback, - OrderStateValid, - OrderStateInvalid, - OrderState, + Order, + BlockParamLiteral, + SignedOrder, + ECSignature, + ZeroExError, + EventCallback, + ExchangeContractErrs, + ContractEvent, + Token, + ExchangeEvents, + TokenEvents, + IndexedFilterValues, + BlockRange, + BlockParam, + OrderCancellationRequest, + OrderFillRequest, + LogErrorContractEventArgs, + LogCancelContractEventArgs, + LogFillContractEventArgs, + ExchangeContractEventArgs, + TransferContractEventArgs, + ApprovalContractEventArgs, + TokenContractEventArgs, + EtherTokenContractEventArgs, + WithdrawalContractEventArgs, + DepositContractEventArgs, + ContractEventArgs, + ContractEventArg, + Web3Provider, + ZeroExConfig, + EtherTokenEvents, + TransactionReceiptWithDecodedLogs, + LogWithDecodedArgs, + MethodOpts, + OrderTransactionOpts, + TransactionOpts, + FilterObject, + LogEvent, + DecodedLogEvent, + EventWatcherCallback, + OnOrderStateChangeCallback, + OrderStateValid, + OrderStateInvalid, + OrderState, } from './types'; export { TransactionReceipt } from '@0xproject/types'; diff --git a/packages/0x.js/src/order_watcher/event_watcher.ts b/packages/0x.js/src/order_watcher/event_watcher.ts index 5d05bfb60..3e3cd978d 100644 --- a/packages/0x.js/src/order_watcher/event_watcher.ts +++ b/packages/0x.js/src/order_watcher/event_watcher.ts @@ -9,8 +9,8 @@ import { assert } from '../utils/assert'; const DEFAULT_EVENT_POLLING_INTERVAL_MS = 200; enum LogEventState { - Removed, - Added, + Removed, + Added, } /* @@ -18,76 +18,76 @@ enum LogEventState { * depth. */ export class EventWatcher { - private _web3Wrapper: Web3Wrapper; - private _pollingIntervalMs: number; - private _intervalIdIfExists?: NodeJS.Timer; - private _lastEvents: Web3.LogEntry[] = []; - constructor(web3Wrapper: Web3Wrapper, pollingIntervalIfExistsMs: undefined | number) { - this._web3Wrapper = web3Wrapper; - this._pollingIntervalMs = _.isUndefined(pollingIntervalIfExistsMs) - ? DEFAULT_EVENT_POLLING_INTERVAL_MS - : pollingIntervalIfExistsMs; - } - public subscribe(callback: EventWatcherCallback): void { - assert.isFunction('callback', callback); - if (!_.isUndefined(this._intervalIdIfExists)) { - throw new Error(ZeroExError.SubscriptionAlreadyPresent); - } - this._intervalIdIfExists = intervalUtils.setAsyncExcludingInterval( - this._pollForBlockchainEventsAsync.bind(this, callback), - this._pollingIntervalMs, - (err: Error) => { - this.unsubscribe(); - callback(err); - }, - ); - } - public unsubscribe(): void { - this._lastEvents = []; - if (!_.isUndefined(this._intervalIdIfExists)) { - intervalUtils.clearAsyncExcludingInterval(this._intervalIdIfExists); - delete this._intervalIdIfExists; - } - } - private async _pollForBlockchainEventsAsync(callback: EventWatcherCallback): Promise<void> { - const pendingEvents = await this._getEventsAsync(); - if (_.isUndefined(pendingEvents)) { - // HACK: This should never happen, but happens frequently on CI due to a ganache bug - return; - } - if (pendingEvents.length === 0) { - // HACK: Sometimes when node rebuilds the pending block we get back the empty result. - // We don't want to emit a lot of removal events and bring them back after a couple of miliseconds, - // that's why we just ignore those cases. - return; - } - const removedEvents = _.differenceBy(this._lastEvents, pendingEvents, JSON.stringify); - const newEvents = _.differenceBy(pendingEvents, this._lastEvents, JSON.stringify); - await this._emitDifferencesAsync(removedEvents, LogEventState.Removed, callback); - await this._emitDifferencesAsync(newEvents, LogEventState.Added, callback); - this._lastEvents = pendingEvents; - } - private async _getEventsAsync(): Promise<Web3.LogEntry[]> { - const eventFilter = { - fromBlock: BlockParamLiteral.Pending, - toBlock: BlockParamLiteral.Pending, - }; - const events = await this._web3Wrapper.getLogsAsync(eventFilter); - return events; - } - private async _emitDifferencesAsync( - logs: Web3.LogEntry[], - logEventState: LogEventState, - callback: EventWatcherCallback, - ): Promise<void> { - for (const log of logs) { - const logEvent = { - removed: logEventState === LogEventState.Removed, - ...log, - }; - if (!_.isUndefined(this._intervalIdIfExists)) { - callback(null, logEvent); - } - } - } + private _web3Wrapper: Web3Wrapper; + private _pollingIntervalMs: number; + private _intervalIdIfExists?: NodeJS.Timer; + private _lastEvents: Web3.LogEntry[] = []; + constructor(web3Wrapper: Web3Wrapper, pollingIntervalIfExistsMs: undefined | number) { + this._web3Wrapper = web3Wrapper; + this._pollingIntervalMs = _.isUndefined(pollingIntervalIfExistsMs) + ? DEFAULT_EVENT_POLLING_INTERVAL_MS + : pollingIntervalIfExistsMs; + } + public subscribe(callback: EventWatcherCallback): void { + assert.isFunction('callback', callback); + if (!_.isUndefined(this._intervalIdIfExists)) { + throw new Error(ZeroExError.SubscriptionAlreadyPresent); + } + this._intervalIdIfExists = intervalUtils.setAsyncExcludingInterval( + this._pollForBlockchainEventsAsync.bind(this, callback), + this._pollingIntervalMs, + (err: Error) => { + this.unsubscribe(); + callback(err); + }, + ); + } + public unsubscribe(): void { + this._lastEvents = []; + if (!_.isUndefined(this._intervalIdIfExists)) { + intervalUtils.clearAsyncExcludingInterval(this._intervalIdIfExists); + delete this._intervalIdIfExists; + } + } + private async _pollForBlockchainEventsAsync(callback: EventWatcherCallback): Promise<void> { + const pendingEvents = await this._getEventsAsync(); + if (_.isUndefined(pendingEvents)) { + // HACK: This should never happen, but happens frequently on CI due to a ganache bug + return; + } + if (pendingEvents.length === 0) { + // HACK: Sometimes when node rebuilds the pending block we get back the empty result. + // We don't want to emit a lot of removal events and bring them back after a couple of miliseconds, + // that's why we just ignore those cases. + return; + } + const removedEvents = _.differenceBy(this._lastEvents, pendingEvents, JSON.stringify); + const newEvents = _.differenceBy(pendingEvents, this._lastEvents, JSON.stringify); + await this._emitDifferencesAsync(removedEvents, LogEventState.Removed, callback); + await this._emitDifferencesAsync(newEvents, LogEventState.Added, callback); + this._lastEvents = pendingEvents; + } + private async _getEventsAsync(): Promise<Web3.LogEntry[]> { + const eventFilter = { + fromBlock: BlockParamLiteral.Pending, + toBlock: BlockParamLiteral.Pending, + }; + const events = await this._web3Wrapper.getLogsAsync(eventFilter); + return events; + } + private async _emitDifferencesAsync( + logs: Web3.LogEntry[], + logEventState: LogEventState, + callback: EventWatcherCallback, + ): Promise<void> { + for (const log of logs) { + const logEvent = { + removed: logEventState === LogEventState.Removed, + ...log, + }; + if (!_.isUndefined(this._intervalIdIfExists)) { + callback(null, logEvent); + } + } + } } diff --git a/packages/0x.js/src/order_watcher/expiration_watcher.ts b/packages/0x.js/src/order_watcher/expiration_watcher.ts index 00b62162d..a08de94c0 100644 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ b/packages/0x.js/src/order_watcher/expiration_watcher.ts @@ -13,63 +13,63 @@ const DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS = 50; * It stores them in a min heap by expiration time and checks for expired ones every `orderExpirationCheckingIntervalMs` */ export class ExpirationWatcher { - private _orderHashByExpirationRBTree: RBTree<string>; - private _expiration: { [orderHash: string]: BigNumber } = {}; - private _orderExpirationCheckingIntervalMs: number; - private _expirationMarginMs: number; - private _orderExpirationCheckingIntervalIdIfExists?: NodeJS.Timer; - constructor(expirationMarginIfExistsMs?: number, orderExpirationCheckingIntervalIfExistsMs?: number) { - this._expirationMarginMs = expirationMarginIfExistsMs || DEFAULT_EXPIRATION_MARGIN_MS; - this._orderExpirationCheckingIntervalMs = - expirationMarginIfExistsMs || DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS; - const scoreFunction = (orderHash: string) => this._expiration[orderHash].toNumber(); - const comparator = (lhs: string, rhs: string) => scoreFunction(lhs) - scoreFunction(rhs); - this._orderHashByExpirationRBTree = new RBTree(comparator); - } - public subscribe(callback: (orderHash: string) => void): void { - if (!_.isUndefined(this._orderExpirationCheckingIntervalIdIfExists)) { - throw new Error(ZeroExError.SubscriptionAlreadyPresent); - } - this._orderExpirationCheckingIntervalIdIfExists = intervalUtils.setInterval( - this._pruneExpiredOrders.bind(this, callback), - this._orderExpirationCheckingIntervalMs, - _.noop, // _pruneExpiredOrders never throws - ); - } - public unsubscribe(): void { - if (_.isUndefined(this._orderExpirationCheckingIntervalIdIfExists)) { - throw new Error(ZeroExError.SubscriptionNotFound); - } - intervalUtils.clearInterval(this._orderExpirationCheckingIntervalIdIfExists); - delete this._orderExpirationCheckingIntervalIdIfExists; - } - public addOrder(orderHash: string, expirationUnixTimestampMs: BigNumber): void { - this._expiration[orderHash] = expirationUnixTimestampMs; - this._orderHashByExpirationRBTree.insert(orderHash); - } - public removeOrder(orderHash: string): void { - this._orderHashByExpirationRBTree.remove(orderHash); - delete this._expiration[orderHash]; - } - private _pruneExpiredOrders(callback: (orderHash: string) => void): void { - const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs(); - while (true) { - const hasTrakedOrders = this._orderHashByExpirationRBTree.size === 0; - if (hasTrakedOrders) { - break; - } - const nextOrderHashToExpire = this._orderHashByExpirationRBTree.min(); - const hasNoExpiredOrders = this._expiration[nextOrderHashToExpire].greaterThan( - currentUnixTimestampMs.plus(this._expirationMarginMs), - ); - const isSubscriptionActive = _.isUndefined(this._orderExpirationCheckingIntervalIdIfExists); - if (hasNoExpiredOrders || isSubscriptionActive) { - break; - } - const orderHash = this._orderHashByExpirationRBTree.min(); - this._orderHashByExpirationRBTree.remove(orderHash); - delete this._expiration[orderHash]; - callback(orderHash); - } - } + private _orderHashByExpirationRBTree: RBTree<string>; + private _expiration: { [orderHash: string]: BigNumber } = {}; + private _orderExpirationCheckingIntervalMs: number; + private _expirationMarginMs: number; + private _orderExpirationCheckingIntervalIdIfExists?: NodeJS.Timer; + constructor(expirationMarginIfExistsMs?: number, orderExpirationCheckingIntervalIfExistsMs?: number) { + this._expirationMarginMs = expirationMarginIfExistsMs || DEFAULT_EXPIRATION_MARGIN_MS; + this._orderExpirationCheckingIntervalMs = + expirationMarginIfExistsMs || DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS; + const scoreFunction = (orderHash: string) => this._expiration[orderHash].toNumber(); + const comparator = (lhs: string, rhs: string) => scoreFunction(lhs) - scoreFunction(rhs); + this._orderHashByExpirationRBTree = new RBTree(comparator); + } + public subscribe(callback: (orderHash: string) => void): void { + if (!_.isUndefined(this._orderExpirationCheckingIntervalIdIfExists)) { + throw new Error(ZeroExError.SubscriptionAlreadyPresent); + } + this._orderExpirationCheckingIntervalIdIfExists = intervalUtils.setInterval( + this._pruneExpiredOrders.bind(this, callback), + this._orderExpirationCheckingIntervalMs, + _.noop, // _pruneExpiredOrders never throws + ); + } + public unsubscribe(): void { + if (_.isUndefined(this._orderExpirationCheckingIntervalIdIfExists)) { + throw new Error(ZeroExError.SubscriptionNotFound); + } + intervalUtils.clearInterval(this._orderExpirationCheckingIntervalIdIfExists); + delete this._orderExpirationCheckingIntervalIdIfExists; + } + public addOrder(orderHash: string, expirationUnixTimestampMs: BigNumber): void { + this._expiration[orderHash] = expirationUnixTimestampMs; + this._orderHashByExpirationRBTree.insert(orderHash); + } + public removeOrder(orderHash: string): void { + this._orderHashByExpirationRBTree.remove(orderHash); + delete this._expiration[orderHash]; + } + private _pruneExpiredOrders(callback: (orderHash: string) => void): void { + const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs(); + while (true) { + const hasTrakedOrders = this._orderHashByExpirationRBTree.size === 0; + if (hasTrakedOrders) { + break; + } + const nextOrderHashToExpire = this._orderHashByExpirationRBTree.min(); + const hasNoExpiredOrders = this._expiration[nextOrderHashToExpire].greaterThan( + currentUnixTimestampMs.plus(this._expirationMarginMs), + ); + const isSubscriptionActive = _.isUndefined(this._orderExpirationCheckingIntervalIdIfExists); + if (hasNoExpiredOrders || isSubscriptionActive) { + break; + } + const orderHash = this._orderHashByExpirationRBTree.min(); + this._orderHashByExpirationRBTree.remove(orderHash); + delete this._expiration[orderHash]; + callback(orderHash); + } + } } diff --git a/packages/0x.js/src/order_watcher/order_state_watcher.ts b/packages/0x.js/src/order_watcher/order_state_watcher.ts index 12ac60960..2c5da6b57 100644 --- a/packages/0x.js/src/order_watcher/order_state_watcher.ts +++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts @@ -9,25 +9,25 @@ import { TokenWrapper } from '../contract_wrappers/token_wrapper'; import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allowance_lazy_store'; import { OrderFilledCancelledLazyStore } from '../stores/order_filled_cancelled_lazy_store'; import { - ApprovalContractEventArgs, - BlockParamLiteral, - ContractEventArgs, - DepositContractEventArgs, - EtherTokenEvents, - ExchangeContractErrs, - ExchangeEvents, - LogCancelContractEventArgs, - LogEvent, - LogFillContractEventArgs, - LogWithDecodedArgs, - OnOrderStateChangeCallback, - OrderState, - OrderStateWatcherConfig, - SignedOrder, - TokenEvents, - TransferContractEventArgs, - WithdrawalContractEventArgs, - ZeroExError, + ApprovalContractEventArgs, + BlockParamLiteral, + ContractEventArgs, + DepositContractEventArgs, + EtherTokenEvents, + ExchangeContractErrs, + ExchangeEvents, + LogCancelContractEventArgs, + LogEvent, + LogFillContractEventArgs, + LogWithDecodedArgs, + OnOrderStateChangeCallback, + OrderState, + OrderStateWatcherConfig, + SignedOrder, + TokenEvents, + TransferContractEventArgs, + WithdrawalContractEventArgs, + ZeroExError, } from '../types'; import { AbiDecoder } from '../utils/abi_decoder'; import { assert } from '../utils/assert'; @@ -38,17 +38,17 @@ import { EventWatcher } from './event_watcher'; import { ExpirationWatcher } from './expiration_watcher'; interface DependentOrderHashes { - [makerAddress: string]: { - [makerToken: string]: Set<string>; - }; + [makerAddress: string]: { + [makerToken: string]: Set<string>; + }; } interface OrderByOrderHash { - [orderHash: string]: SignedOrder; + [orderHash: string]: SignedOrder; } interface OrderStateByOrderHash { - [orderHash: string]: OrderState; + [orderHash: string]: OrderState; } const DEFAULT_CLEANUP_JOB_INTERVAL_MS = 1000 * 60 * 60; // 1h @@ -60,319 +60,319 @@ const DEFAULT_CLEANUP_JOB_INTERVAL_MS = 1000 * 60 * 60; // 1h * the order should be deemed invalid. */ export class OrderStateWatcher { - private _orderStateByOrderHashCache: OrderStateByOrderHash = {}; - private _orderByOrderHash: OrderByOrderHash = {}; - private _dependentOrderHashes: DependentOrderHashes = {}; - private _callbackIfExists?: OnOrderStateChangeCallback; - private _eventWatcher: EventWatcher; - private _web3Wrapper: Web3Wrapper; - private _abiDecoder: AbiDecoder; - private _expirationWatcher: ExpirationWatcher; - private _orderStateUtils: OrderStateUtils; - private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore; - private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore; - private _cleanupJobInterval: number; - private _cleanupJobIntervalIdIfExists?: NodeJS.Timer; - constructor( - web3Wrapper: Web3Wrapper, - abiDecoder: AbiDecoder, - token: TokenWrapper, - exchange: ExchangeWrapper, - config?: OrderStateWatcherConfig, - ) { - this._abiDecoder = abiDecoder; - this._web3Wrapper = web3Wrapper; - const pollingIntervalIfExistsMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs; - this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs); - this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( - token, - BlockParamLiteral.Pending, - ); - this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange); - this._orderStateUtils = new OrderStateUtils( - this._balanceAndProxyAllowanceLazyStore, - this._orderFilledCancelledLazyStore, - ); - const orderExpirationCheckingIntervalMsIfExists = _.isUndefined(config) - ? undefined - : config.orderExpirationCheckingIntervalMs; - const expirationMarginIfExistsMs = _.isUndefined(config) ? undefined : config.expirationMarginMs; - this._expirationWatcher = new ExpirationWatcher( - expirationMarginIfExistsMs, - orderExpirationCheckingIntervalMsIfExists, - ); - this._cleanupJobInterval = - _.isUndefined(config) || _.isUndefined(config.cleanupJobIntervalMs) - ? DEFAULT_CLEANUP_JOB_INTERVAL_MS - : config.cleanupJobIntervalMs; - } - /** - * Add an order to the orderStateWatcher. Before the order is added, it's - * signature is verified. - * @param signedOrder The order you wish to start watching. - */ - public addOrder(signedOrder: SignedOrder): void { - assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker); - this._orderByOrderHash[orderHash] = signedOrder; - this._addToDependentOrderHashes(signedOrder, orderHash); - const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(1000); - this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs); - } - /** - * Removes an order from the orderStateWatcher - * @param orderHash The orderHash of the order you wish to stop watching. - */ - public removeOrder(orderHash: string): void { - assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); - const signedOrder = this._orderByOrderHash[orderHash]; - if (_.isUndefined(signedOrder)) { - return; // noop - } - delete this._orderByOrderHash[orderHash]; - delete this._orderStateByOrderHashCache[orderHash]; - const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper; - const zrxTokenAddress = exchange.getZRXTokenAddress(); - this._removeFromDependentOrderHashes(signedOrder.maker, zrxTokenAddress, orderHash); - this._removeFromDependentOrderHashes(signedOrder.maker, signedOrder.makerTokenAddress, orderHash); - this._expirationWatcher.removeOrder(orderHash); - } - /** - * Starts an orderStateWatcher subscription. The callback will be called every time a watched order's - * backing blockchain state has changed. This is a call-to-action for the caller to re-validate the order. - * @param callback Receives the orderHash of the order that should be re-validated, together - * with all the order-relevant blockchain state needed to re-validate the order. - */ - public subscribe(callback: OnOrderStateChangeCallback): void { - assert.isFunction('callback', callback); - if (!_.isUndefined(this._callbackIfExists)) { - throw new Error(ZeroExError.SubscriptionAlreadyPresent); - } - this._callbackIfExists = callback; - this._eventWatcher.subscribe(this._onEventWatcherCallbackAsync.bind(this)); - this._expirationWatcher.subscribe(this._onOrderExpired.bind(this)); - this._cleanupJobIntervalIdIfExists = intervalUtils.setAsyncExcludingInterval( - this._cleanupAsync.bind(this), - this._cleanupJobInterval, - (err: Error) => { - this.unsubscribe(); - callback(err); - }, - ); - } - /** - * Ends an orderStateWatcher subscription. - */ - public unsubscribe(): void { - if (_.isUndefined(this._callbackIfExists) || _.isUndefined(this._cleanupJobIntervalIdIfExists)) { - throw new Error(ZeroExError.SubscriptionNotFound); - } - this._balanceAndProxyAllowanceLazyStore.deleteAll(); - this._orderFilledCancelledLazyStore.deleteAll(); - delete this._callbackIfExists; - this._eventWatcher.unsubscribe(); - this._expirationWatcher.unsubscribe(); - intervalUtils.clearAsyncExcludingInterval(this._cleanupJobIntervalIdIfExists); - } - private async _cleanupAsync(): Promise<void> { - for (const orderHash of _.keys(this._orderByOrderHash)) { - this._cleanupOrderRelatedState(orderHash); - await this._emitRevalidateOrdersAsync([orderHash]); - } - } - private _cleanupOrderRelatedState(orderHash: string): void { - const signedOrder = this._orderByOrderHash[orderHash]; + private _orderStateByOrderHashCache: OrderStateByOrderHash = {}; + private _orderByOrderHash: OrderByOrderHash = {}; + private _dependentOrderHashes: DependentOrderHashes = {}; + private _callbackIfExists?: OnOrderStateChangeCallback; + private _eventWatcher: EventWatcher; + private _web3Wrapper: Web3Wrapper; + private _abiDecoder: AbiDecoder; + private _expirationWatcher: ExpirationWatcher; + private _orderStateUtils: OrderStateUtils; + private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore; + private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore; + private _cleanupJobInterval: number; + private _cleanupJobIntervalIdIfExists?: NodeJS.Timer; + constructor( + web3Wrapper: Web3Wrapper, + abiDecoder: AbiDecoder, + token: TokenWrapper, + exchange: ExchangeWrapper, + config?: OrderStateWatcherConfig, + ) { + this._abiDecoder = abiDecoder; + this._web3Wrapper = web3Wrapper; + const pollingIntervalIfExistsMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs; + this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs); + this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( + token, + BlockParamLiteral.Pending, + ); + this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange); + this._orderStateUtils = new OrderStateUtils( + this._balanceAndProxyAllowanceLazyStore, + this._orderFilledCancelledLazyStore, + ); + const orderExpirationCheckingIntervalMsIfExists = _.isUndefined(config) + ? undefined + : config.orderExpirationCheckingIntervalMs; + const expirationMarginIfExistsMs = _.isUndefined(config) ? undefined : config.expirationMarginMs; + this._expirationWatcher = new ExpirationWatcher( + expirationMarginIfExistsMs, + orderExpirationCheckingIntervalMsIfExists, + ); + this._cleanupJobInterval = + _.isUndefined(config) || _.isUndefined(config.cleanupJobIntervalMs) + ? DEFAULT_CLEANUP_JOB_INTERVAL_MS + : config.cleanupJobIntervalMs; + } + /** + * Add an order to the orderStateWatcher. Before the order is added, it's + * signature is verified. + * @param signedOrder The order you wish to start watching. + */ + public addOrder(signedOrder: SignedOrder): void { + assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker); + this._orderByOrderHash[orderHash] = signedOrder; + this._addToDependentOrderHashes(signedOrder, orderHash); + const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(1000); + this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs); + } + /** + * Removes an order from the orderStateWatcher + * @param orderHash The orderHash of the order you wish to stop watching. + */ + public removeOrder(orderHash: string): void { + assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema); + const signedOrder = this._orderByOrderHash[orderHash]; + if (_.isUndefined(signedOrder)) { + return; // noop + } + delete this._orderByOrderHash[orderHash]; + delete this._orderStateByOrderHashCache[orderHash]; + const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper; + const zrxTokenAddress = exchange.getZRXTokenAddress(); + this._removeFromDependentOrderHashes(signedOrder.maker, zrxTokenAddress, orderHash); + this._removeFromDependentOrderHashes(signedOrder.maker, signedOrder.makerTokenAddress, orderHash); + this._expirationWatcher.removeOrder(orderHash); + } + /** + * Starts an orderStateWatcher subscription. The callback will be called every time a watched order's + * backing blockchain state has changed. This is a call-to-action for the caller to re-validate the order. + * @param callback Receives the orderHash of the order that should be re-validated, together + * with all the order-relevant blockchain state needed to re-validate the order. + */ + public subscribe(callback: OnOrderStateChangeCallback): void { + assert.isFunction('callback', callback); + if (!_.isUndefined(this._callbackIfExists)) { + throw new Error(ZeroExError.SubscriptionAlreadyPresent); + } + this._callbackIfExists = callback; + this._eventWatcher.subscribe(this._onEventWatcherCallbackAsync.bind(this)); + this._expirationWatcher.subscribe(this._onOrderExpired.bind(this)); + this._cleanupJobIntervalIdIfExists = intervalUtils.setAsyncExcludingInterval( + this._cleanupAsync.bind(this), + this._cleanupJobInterval, + (err: Error) => { + this.unsubscribe(); + callback(err); + }, + ); + } + /** + * Ends an orderStateWatcher subscription. + */ + public unsubscribe(): void { + if (_.isUndefined(this._callbackIfExists) || _.isUndefined(this._cleanupJobIntervalIdIfExists)) { + throw new Error(ZeroExError.SubscriptionNotFound); + } + this._balanceAndProxyAllowanceLazyStore.deleteAll(); + this._orderFilledCancelledLazyStore.deleteAll(); + delete this._callbackIfExists; + this._eventWatcher.unsubscribe(); + this._expirationWatcher.unsubscribe(); + intervalUtils.clearAsyncExcludingInterval(this._cleanupJobIntervalIdIfExists); + } + private async _cleanupAsync(): Promise<void> { + for (const orderHash of _.keys(this._orderByOrderHash)) { + this._cleanupOrderRelatedState(orderHash); + await this._emitRevalidateOrdersAsync([orderHash]); + } + } + private _cleanupOrderRelatedState(orderHash: string): void { + const signedOrder = this._orderByOrderHash[orderHash]; - this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(orderHash); - this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(orderHash); + this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(orderHash); + this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(orderHash); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.makerTokenAddress, signedOrder.maker); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.makerTokenAddress, signedOrder.maker); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.takerTokenAddress, signedOrder.taker); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.takerTokenAddress, signedOrder.taker); + this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.makerTokenAddress, signedOrder.maker); + this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.makerTokenAddress, signedOrder.maker); + this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.takerTokenAddress, signedOrder.taker); + this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.takerTokenAddress, signedOrder.taker); - const zrxTokenAddress = this._getZRXTokenAddress(); - if (!signedOrder.makerFee.isZero()) { - this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.maker); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.maker); - } - if (!signedOrder.takerFee.isZero()) { - this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.taker); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.taker); - } - } - private _onOrderExpired(orderHash: string): void { - const orderState: OrderState = { - isValid: false, - orderHash, - error: ExchangeContractErrs.OrderFillExpired, - }; - if (!_.isUndefined(this._orderByOrderHash[orderHash])) { - this.removeOrder(orderHash); - if (!_.isUndefined(this._callbackIfExists)) { - this._callbackIfExists(null, orderState); - } - } - } - private async _onEventWatcherCallbackAsync(err: Error | null, logIfExists?: LogEvent): Promise<void> { - if (!_.isNull(err)) { - if (!_.isUndefined(this._callbackIfExists)) { - this._callbackIfExists(err); - this.unsubscribe(); - } - return; - } - const log = logIfExists as LogEvent; // At this moment we are sure that no error occured and log is defined. - const maybeDecodedLog = this._abiDecoder.tryToDecodeLogOrNoop(log); - const isLogDecoded = !_.isUndefined((maybeDecodedLog as LogWithDecodedArgs<any>).event); - if (!isLogDecoded) { - return; // noop - } - const decodedLog = maybeDecodedLog as LogWithDecodedArgs<ContractEventArgs>; - let makerToken: string; - let makerAddress: string; - switch (decodedLog.event) { - case TokenEvents.Approval: { - // Invalidate cache - const args = decodedLog.args as ApprovalContractEventArgs; - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(decodedLog.address, args._owner); - // Revalidate orders - makerToken = decodedLog.address; - makerAddress = args._owner; - if ( - !_.isUndefined(this._dependentOrderHashes[makerAddress]) && - !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) - ) { - const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); - await this._emitRevalidateOrdersAsync(orderHashes); - } - break; - } - case TokenEvents.Transfer: { - // Invalidate cache - const args = decodedLog.args as TransferContractEventArgs; - this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._from); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._to); - // Revalidate orders - makerToken = decodedLog.address; - makerAddress = args._from; - if ( - !_.isUndefined(this._dependentOrderHashes[makerAddress]) && - !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) - ) { - const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); - await this._emitRevalidateOrdersAsync(orderHashes); - } - break; - } - case EtherTokenEvents.Deposit: { - // Invalidate cache - const args = decodedLog.args as DepositContractEventArgs; - this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner); - // Revalidate orders - makerToken = decodedLog.address; - makerAddress = args._owner; - if ( - !_.isUndefined(this._dependentOrderHashes[makerAddress]) && - !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) - ) { - const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); - await this._emitRevalidateOrdersAsync(orderHashes); - } - break; - } - case EtherTokenEvents.Withdrawal: { - // Invalidate cache - const args = decodedLog.args as WithdrawalContractEventArgs; - this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner); - // Revalidate orders - makerToken = decodedLog.address; - makerAddress = args._owner; - if ( - !_.isUndefined(this._dependentOrderHashes[makerAddress]) && - !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) - ) { - const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); - await this._emitRevalidateOrdersAsync(orderHashes); - } - break; - } - case ExchangeEvents.LogFill: { - // Invalidate cache - const args = decodedLog.args as LogFillContractEventArgs; - this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(args.orderHash); - // Revalidate orders - const orderHash = args.orderHash; - const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]); - if (isOrderWatched) { - await this._emitRevalidateOrdersAsync([orderHash]); - } - break; - } - case ExchangeEvents.LogCancel: { - // Invalidate cache - const args = decodedLog.args as LogCancelContractEventArgs; - this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(args.orderHash); - // Revalidate orders - const orderHash = args.orderHash; - const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]); - if (isOrderWatched) { - await this._emitRevalidateOrdersAsync([orderHash]); - } - break; - } - case ExchangeEvents.LogError: - return; // noop + const zrxTokenAddress = this._getZRXTokenAddress(); + if (!signedOrder.makerFee.isZero()) { + this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.maker); + this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.maker); + } + if (!signedOrder.takerFee.isZero()) { + this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.taker); + this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.taker); + } + } + private _onOrderExpired(orderHash: string): void { + const orderState: OrderState = { + isValid: false, + orderHash, + error: ExchangeContractErrs.OrderFillExpired, + }; + if (!_.isUndefined(this._orderByOrderHash[orderHash])) { + this.removeOrder(orderHash); + if (!_.isUndefined(this._callbackIfExists)) { + this._callbackIfExists(null, orderState); + } + } + } + private async _onEventWatcherCallbackAsync(err: Error | null, logIfExists?: LogEvent): Promise<void> { + if (!_.isNull(err)) { + if (!_.isUndefined(this._callbackIfExists)) { + this._callbackIfExists(err); + this.unsubscribe(); + } + return; + } + const log = logIfExists as LogEvent; // At this moment we are sure that no error occured and log is defined. + const maybeDecodedLog = this._abiDecoder.tryToDecodeLogOrNoop(log); + const isLogDecoded = !_.isUndefined((maybeDecodedLog as LogWithDecodedArgs<any>).event); + if (!isLogDecoded) { + return; // noop + } + const decodedLog = maybeDecodedLog as LogWithDecodedArgs<ContractEventArgs>; + let makerToken: string; + let makerAddress: string; + switch (decodedLog.event) { + case TokenEvents.Approval: { + // Invalidate cache + const args = decodedLog.args as ApprovalContractEventArgs; + this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(decodedLog.address, args._owner); + // Revalidate orders + makerToken = decodedLog.address; + makerAddress = args._owner; + if ( + !_.isUndefined(this._dependentOrderHashes[makerAddress]) && + !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) + ) { + const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); + await this._emitRevalidateOrdersAsync(orderHashes); + } + break; + } + case TokenEvents.Transfer: { + // Invalidate cache + const args = decodedLog.args as TransferContractEventArgs; + this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._from); + this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._to); + // Revalidate orders + makerToken = decodedLog.address; + makerAddress = args._from; + if ( + !_.isUndefined(this._dependentOrderHashes[makerAddress]) && + !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) + ) { + const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); + await this._emitRevalidateOrdersAsync(orderHashes); + } + break; + } + case EtherTokenEvents.Deposit: { + // Invalidate cache + const args = decodedLog.args as DepositContractEventArgs; + this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner); + // Revalidate orders + makerToken = decodedLog.address; + makerAddress = args._owner; + if ( + !_.isUndefined(this._dependentOrderHashes[makerAddress]) && + !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) + ) { + const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); + await this._emitRevalidateOrdersAsync(orderHashes); + } + break; + } + case EtherTokenEvents.Withdrawal: { + // Invalidate cache + const args = decodedLog.args as WithdrawalContractEventArgs; + this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner); + // Revalidate orders + makerToken = decodedLog.address; + makerAddress = args._owner; + if ( + !_.isUndefined(this._dependentOrderHashes[makerAddress]) && + !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) + ) { + const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); + await this._emitRevalidateOrdersAsync(orderHashes); + } + break; + } + case ExchangeEvents.LogFill: { + // Invalidate cache + const args = decodedLog.args as LogFillContractEventArgs; + this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(args.orderHash); + // Revalidate orders + const orderHash = args.orderHash; + const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]); + if (isOrderWatched) { + await this._emitRevalidateOrdersAsync([orderHash]); + } + break; + } + case ExchangeEvents.LogCancel: { + // Invalidate cache + const args = decodedLog.args as LogCancelContractEventArgs; + this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(args.orderHash); + // Revalidate orders + const orderHash = args.orderHash; + const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]); + if (isOrderWatched) { + await this._emitRevalidateOrdersAsync([orderHash]); + } + break; + } + case ExchangeEvents.LogError: + return; // noop - default: - throw utils.spawnSwitchErr('decodedLog.event', decodedLog.event); - } - } - private async _emitRevalidateOrdersAsync(orderHashes: string[]): Promise<void> { - for (const orderHash of orderHashes) { - const signedOrder = this._orderByOrderHash[orderHash]; - // Most of these calls will never reach the network because the data is fetched from stores - // and only updated when cache is invalidated - const orderState = await this._orderStateUtils.getOrderStateAsync(signedOrder); - if (_.isUndefined(this._callbackIfExists)) { - break; // Unsubscribe was called - } - if (_.isEqual(orderState, this._orderStateByOrderHashCache[orderHash])) { - // Actual order state didn't change - continue; - } else { - this._orderStateByOrderHashCache[orderHash] = orderState; - } - this._callbackIfExists(null, orderState); - } - } - private _addToDependentOrderHashes(signedOrder: SignedOrder, orderHash: string): void { - if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker])) { - this._dependentOrderHashes[signedOrder.maker] = {}; - } - if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress])) { - this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress] = new Set(); - } - this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress].add(orderHash); - const zrxTokenAddress = this._getZRXTokenAddress(); - if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress])) { - this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress] = new Set(); - } - this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress].add(orderHash); - } - private _removeFromDependentOrderHashes(makerAddress: string, tokenAddress: string, orderHash: string) { - this._dependentOrderHashes[makerAddress][tokenAddress].delete(orderHash); - if (this._dependentOrderHashes[makerAddress][tokenAddress].size === 0) { - delete this._dependentOrderHashes[makerAddress][tokenAddress]; - } - if (_.isEmpty(this._dependentOrderHashes[makerAddress])) { - delete this._dependentOrderHashes[makerAddress]; - } - } - private _getZRXTokenAddress(): string { - const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper; - const zrxTokenAddress = exchange.getZRXTokenAddress(); - return zrxTokenAddress; - } + default: + throw utils.spawnSwitchErr('decodedLog.event', decodedLog.event); + } + } + private async _emitRevalidateOrdersAsync(orderHashes: string[]): Promise<void> { + for (const orderHash of orderHashes) { + const signedOrder = this._orderByOrderHash[orderHash]; + // Most of these calls will never reach the network because the data is fetched from stores + // and only updated when cache is invalidated + const orderState = await this._orderStateUtils.getOrderStateAsync(signedOrder); + if (_.isUndefined(this._callbackIfExists)) { + break; // Unsubscribe was called + } + if (_.isEqual(orderState, this._orderStateByOrderHashCache[orderHash])) { + // Actual order state didn't change + continue; + } else { + this._orderStateByOrderHashCache[orderHash] = orderState; + } + this._callbackIfExists(null, orderState); + } + } + private _addToDependentOrderHashes(signedOrder: SignedOrder, orderHash: string): void { + if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker])) { + this._dependentOrderHashes[signedOrder.maker] = {}; + } + if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress])) { + this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress] = new Set(); + } + this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress].add(orderHash); + const zrxTokenAddress = this._getZRXTokenAddress(); + if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress])) { + this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress] = new Set(); + } + this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress].add(orderHash); + } + private _removeFromDependentOrderHashes(makerAddress: string, tokenAddress: string, orderHash: string) { + this._dependentOrderHashes[makerAddress][tokenAddress].delete(orderHash); + if (this._dependentOrderHashes[makerAddress][tokenAddress].size === 0) { + delete this._dependentOrderHashes[makerAddress][tokenAddress]; + } + if (_.isEmpty(this._dependentOrderHashes[makerAddress])) { + delete this._dependentOrderHashes[makerAddress]; + } + } + private _getZRXTokenAddress(): string { + const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper; + const zrxTokenAddress = exchange.getZRXTokenAddress(); + return zrxTokenAddress; + } } diff --git a/packages/0x.js/src/order_watcher/remaining_fillable_calculator.ts b/packages/0x.js/src/order_watcher/remaining_fillable_calculator.ts index 20b09d606..e010092af 100644 --- a/packages/0x.js/src/order_watcher/remaining_fillable_calculator.ts +++ b/packages/0x.js/src/order_watcher/remaining_fillable_calculator.ts @@ -3,94 +3,94 @@ import { BigNumber } from '@0xproject/utils'; import { SignedOrder } from '../types'; export class RemainingFillableCalculator { - private _signedOrder: SignedOrder; - private _isMakerTokenZRX: boolean; - // Transferrable Amount is the minimum of Approval and Balance - private _transferrableMakerTokenAmount: BigNumber; - private _transferrableMakerFeeTokenAmount: BigNumber; - private _remainingMakerTokenAmount: BigNumber; - private _remainingMakerFeeAmount: BigNumber; - constructor( - signedOrder: SignedOrder, - isMakerTokenZRX: boolean, - transferrableMakerTokenAmount: BigNumber, - transferrableMakerFeeTokenAmount: BigNumber, - remainingMakerTokenAmount: BigNumber, - ) { - this._signedOrder = signedOrder; - this._isMakerTokenZRX = isMakerTokenZRX; - this._transferrableMakerTokenAmount = transferrableMakerTokenAmount; - this._transferrableMakerFeeTokenAmount = transferrableMakerFeeTokenAmount; - this._remainingMakerTokenAmount = remainingMakerTokenAmount; - this._remainingMakerFeeAmount = remainingMakerTokenAmount - .times(signedOrder.makerFee) - .dividedToIntegerBy(signedOrder.makerTokenAmount); - } - public computeRemainingMakerFillable(): BigNumber { - if (this._hasSufficientFundsForFeeAndTransferAmount()) { - return this._remainingMakerTokenAmount; - } - if (this._signedOrder.makerFee.isZero()) { - return BigNumber.min(this._remainingMakerTokenAmount, this._transferrableMakerTokenAmount); - } - return this._calculatePartiallyFillableMakerTokenAmount(); - } - public computeRemainingTakerFillable(): BigNumber { - return this.computeRemainingMakerFillable() - .times(this._signedOrder.takerTokenAmount) - .dividedToIntegerBy(this._signedOrder.makerTokenAmount); - } - private _hasSufficientFundsForFeeAndTransferAmount(): boolean { - if (this._isMakerTokenZRX) { - const totalZRXTransferAmountRequired = this._remainingMakerTokenAmount.plus(this._remainingMakerFeeAmount); - const hasSufficientFunds = this._transferrableMakerTokenAmount.greaterThanOrEqualTo( - totalZRXTransferAmountRequired, - ); - return hasSufficientFunds; - } else { - const hasSufficientFundsForTransferAmount = this._transferrableMakerTokenAmount.greaterThanOrEqualTo( - this._remainingMakerTokenAmount, - ); - const hasSufficientFundsForFeeAmount = this._transferrableMakerFeeTokenAmount.greaterThanOrEqualTo( - this._remainingMakerFeeAmount, - ); - const hasSufficientFunds = hasSufficientFundsForTransferAmount && hasSufficientFundsForFeeAmount; - return hasSufficientFunds; - } - } - private _calculatePartiallyFillableMakerTokenAmount(): BigNumber { - // Given an order for 200 wei for 2 ZRXwei fee, find 100 wei for 1 ZRXwei. Order ratio is then 100:1 - const orderToFeeRatio = this._signedOrder.makerTokenAmount.dividedBy(this._signedOrder.makerFee); - // The number of times the maker can fill the order, if each fill only required the transfer of a single - // baseUnit of fee tokens. - // Given 2 ZRXwei, the maximum amount of times Maker can fill this order, in terms of fees, is 2 - const fillableTimesInFeeTokenBaseUnits = BigNumber.min( - this._transferrableMakerFeeTokenAmount, - this._remainingMakerFeeAmount, - ); - // The number of times the Maker can fill the order, given the Maker Token Balance - // Assuming a balance of 150 wei, and an orderToFeeRatio of 100:1, maker can fill this order 1 time. - let fillableTimesInMakerTokenUnits = this._transferrableMakerTokenAmount.dividedBy(orderToFeeRatio); - if (this._isMakerTokenZRX) { - // If ZRX is the maker token, the Fee and the Maker amount need to be removed from the same pool; - // 200 ZRXwei for 2ZRXwei fee can only be filled once (need 202 ZRXwei) - const totalZRXTokenPooled = this._transferrableMakerTokenAmount; - // The purchasing power here is less as the tokens are taken from the same Pool - // For every one number of fills, we have to take an extra ZRX out of the pool - fillableTimesInMakerTokenUnits = totalZRXTokenPooled.dividedBy(orderToFeeRatio.plus(new BigNumber(1))); - } - // When Ratio is not fully divisible there can be remainders which cannot be represented, so they are floored. - // This can result in a RoundingError being thrown by the Exchange Contract. - const partiallyFillableMakerTokenAmount = fillableTimesInMakerTokenUnits - .times(this._signedOrder.makerTokenAmount) - .dividedToIntegerBy(this._signedOrder.makerFee); - const partiallyFillableFeeTokenAmount = fillableTimesInFeeTokenBaseUnits - .times(this._signedOrder.makerTokenAmount) - .dividedToIntegerBy(this._signedOrder.makerFee); - const partiallyFillableAmount = BigNumber.min( - partiallyFillableMakerTokenAmount, - partiallyFillableFeeTokenAmount, - ); - return partiallyFillableAmount; - } + private _signedOrder: SignedOrder; + private _isMakerTokenZRX: boolean; + // Transferrable Amount is the minimum of Approval and Balance + private _transferrableMakerTokenAmount: BigNumber; + private _transferrableMakerFeeTokenAmount: BigNumber; + private _remainingMakerTokenAmount: BigNumber; + private _remainingMakerFeeAmount: BigNumber; + constructor( + signedOrder: SignedOrder, + isMakerTokenZRX: boolean, + transferrableMakerTokenAmount: BigNumber, + transferrableMakerFeeTokenAmount: BigNumber, + remainingMakerTokenAmount: BigNumber, + ) { + this._signedOrder = signedOrder; + this._isMakerTokenZRX = isMakerTokenZRX; + this._transferrableMakerTokenAmount = transferrableMakerTokenAmount; + this._transferrableMakerFeeTokenAmount = transferrableMakerFeeTokenAmount; + this._remainingMakerTokenAmount = remainingMakerTokenAmount; + this._remainingMakerFeeAmount = remainingMakerTokenAmount + .times(signedOrder.makerFee) + .dividedToIntegerBy(signedOrder.makerTokenAmount); + } + public computeRemainingMakerFillable(): BigNumber { + if (this._hasSufficientFundsForFeeAndTransferAmount()) { + return this._remainingMakerTokenAmount; + } + if (this._signedOrder.makerFee.isZero()) { + return BigNumber.min(this._remainingMakerTokenAmount, this._transferrableMakerTokenAmount); + } + return this._calculatePartiallyFillableMakerTokenAmount(); + } + public computeRemainingTakerFillable(): BigNumber { + return this.computeRemainingMakerFillable() + .times(this._signedOrder.takerTokenAmount) + .dividedToIntegerBy(this._signedOrder.makerTokenAmount); + } + private _hasSufficientFundsForFeeAndTransferAmount(): boolean { + if (this._isMakerTokenZRX) { + const totalZRXTransferAmountRequired = this._remainingMakerTokenAmount.plus(this._remainingMakerFeeAmount); + const hasSufficientFunds = this._transferrableMakerTokenAmount.greaterThanOrEqualTo( + totalZRXTransferAmountRequired, + ); + return hasSufficientFunds; + } else { + const hasSufficientFundsForTransferAmount = this._transferrableMakerTokenAmount.greaterThanOrEqualTo( + this._remainingMakerTokenAmount, + ); + const hasSufficientFundsForFeeAmount = this._transferrableMakerFeeTokenAmount.greaterThanOrEqualTo( + this._remainingMakerFeeAmount, + ); + const hasSufficientFunds = hasSufficientFundsForTransferAmount && hasSufficientFundsForFeeAmount; + return hasSufficientFunds; + } + } + private _calculatePartiallyFillableMakerTokenAmount(): BigNumber { + // Given an order for 200 wei for 2 ZRXwei fee, find 100 wei for 1 ZRXwei. Order ratio is then 100:1 + const orderToFeeRatio = this._signedOrder.makerTokenAmount.dividedBy(this._signedOrder.makerFee); + // The number of times the maker can fill the order, if each fill only required the transfer of a single + // baseUnit of fee tokens. + // Given 2 ZRXwei, the maximum amount of times Maker can fill this order, in terms of fees, is 2 + const fillableTimesInFeeTokenBaseUnits = BigNumber.min( + this._transferrableMakerFeeTokenAmount, + this._remainingMakerFeeAmount, + ); + // The number of times the Maker can fill the order, given the Maker Token Balance + // Assuming a balance of 150 wei, and an orderToFeeRatio of 100:1, maker can fill this order 1 time. + let fillableTimesInMakerTokenUnits = this._transferrableMakerTokenAmount.dividedBy(orderToFeeRatio); + if (this._isMakerTokenZRX) { + // If ZRX is the maker token, the Fee and the Maker amount need to be removed from the same pool; + // 200 ZRXwei for 2ZRXwei fee can only be filled once (need 202 ZRXwei) + const totalZRXTokenPooled = this._transferrableMakerTokenAmount; + // The purchasing power here is less as the tokens are taken from the same Pool + // For every one number of fills, we have to take an extra ZRX out of the pool + fillableTimesInMakerTokenUnits = totalZRXTokenPooled.dividedBy(orderToFeeRatio.plus(new BigNumber(1))); + } + // When Ratio is not fully divisible there can be remainders which cannot be represented, so they are floored. + // This can result in a RoundingError being thrown by the Exchange Contract. + const partiallyFillableMakerTokenAmount = fillableTimesInMakerTokenUnits + .times(this._signedOrder.makerTokenAmount) + .dividedToIntegerBy(this._signedOrder.makerFee); + const partiallyFillableFeeTokenAmount = fillableTimesInFeeTokenBaseUnits + .times(this._signedOrder.makerTokenAmount) + .dividedToIntegerBy(this._signedOrder.makerFee); + const partiallyFillableAmount = BigNumber.min( + partiallyFillableMakerTokenAmount, + partiallyFillableFeeTokenAmount, + ); + return partiallyFillableAmount; + } } diff --git a/packages/0x.js/src/schemas/zero_ex_config_schema.ts b/packages/0x.js/src/schemas/zero_ex_config_schema.ts index 546b1c2d0..657b24c58 100644 --- a/packages/0x.js/src/schemas/zero_ex_config_schema.ts +++ b/packages/0x.js/src/schemas/zero_ex_config_schema.ts @@ -1,27 +1,27 @@ export const zeroExConfigSchema = { - id: '/ZeroExConfig', - properties: { - networkId: { - type: 'number', - minimum: 0, - }, - gasPrice: { $ref: '/Number' }, - exchangeContractAddress: { $ref: '/Address' }, - tokenRegistryContractAddress: { $ref: '/Address' }, - orderWatcherConfig: { - type: 'object', - properties: { - pollingIntervalMs: { - type: 'number', - minimum: 0, - }, - numConfirmations: { - type: 'number', - minimum: 0, - }, - }, - }, - }, - type: 'object', - required: ['networkId'], + id: '/ZeroExConfig', + properties: { + networkId: { + type: 'number', + minimum: 0, + }, + gasPrice: { $ref: '/Number' }, + exchangeContractAddress: { $ref: '/Address' }, + tokenRegistryContractAddress: { $ref: '/Address' }, + orderWatcherConfig: { + type: 'object', + properties: { + pollingIntervalMs: { + type: 'number', + minimum: 0, + }, + numConfirmations: { + type: 'number', + minimum: 0, + }, + }, + }, + }, + type: 'object', + required: ['networkId'], }; diff --git a/packages/0x.js/src/stores/balance_proxy_allowance_lazy_store.ts b/packages/0x.js/src/stores/balance_proxy_allowance_lazy_store.ts index 33feea105..379afa4bc 100644 --- a/packages/0x.js/src/stores/balance_proxy_allowance_lazy_store.ts +++ b/packages/0x.js/src/stores/balance_proxy_allowance_lazy_store.ts @@ -8,79 +8,79 @@ import { BlockParamLiteral } from '../types'; * Copy on read store for balances/proxyAllowances of tokens/accounts */ export class BalanceAndProxyAllowanceLazyStore { - private _token: TokenWrapper; - private _defaultBlock: BlockParamLiteral; - private _balance: { - [tokenAddress: string]: { - [userAddress: string]: BigNumber; - }; - }; - private _proxyAllowance: { - [tokenAddress: string]: { - [userAddress: string]: BigNumber; - }; - }; - constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) { - this._token = token; - this._defaultBlock = defaultBlock; - this._balance = {}; - this._proxyAllowance = {}; - } - public async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> { - if (_.isUndefined(this._balance[tokenAddress]) || _.isUndefined(this._balance[tokenAddress][userAddress])) { - const methodOpts = { - defaultBlock: this._defaultBlock, - }; - const balance = await this._token.getBalanceAsync(tokenAddress, userAddress, methodOpts); - this.setBalance(tokenAddress, userAddress, balance); - } - const cachedBalance = this._balance[tokenAddress][userAddress]; - return cachedBalance; - } - public setBalance(tokenAddress: string, userAddress: string, balance: BigNumber): void { - if (_.isUndefined(this._balance[tokenAddress])) { - this._balance[tokenAddress] = {}; - } - this._balance[tokenAddress][userAddress] = balance; - } - public deleteBalance(tokenAddress: string, userAddress: string): void { - if (!_.isUndefined(this._balance[tokenAddress])) { - delete this._balance[tokenAddress][userAddress]; - if (_.isEmpty(this._balance[tokenAddress])) { - delete this._balance[tokenAddress]; - } - } - } - public async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> { - if ( - _.isUndefined(this._proxyAllowance[tokenAddress]) || - _.isUndefined(this._proxyAllowance[tokenAddress][userAddress]) - ) { - const methodOpts = { - defaultBlock: this._defaultBlock, - }; - const proxyAllowance = await this._token.getProxyAllowanceAsync(tokenAddress, userAddress, methodOpts); - this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance); - } - const cachedProxyAllowance = this._proxyAllowance[tokenAddress][userAddress]; - return cachedProxyAllowance; - } - public setProxyAllowance(tokenAddress: string, userAddress: string, proxyAllowance: BigNumber): void { - if (_.isUndefined(this._proxyAllowance[tokenAddress])) { - this._proxyAllowance[tokenAddress] = {}; - } - this._proxyAllowance[tokenAddress][userAddress] = proxyAllowance; - } - public deleteProxyAllowance(tokenAddress: string, userAddress: string): void { - if (!_.isUndefined(this._proxyAllowance[tokenAddress])) { - delete this._proxyAllowance[tokenAddress][userAddress]; - if (_.isEmpty(this._proxyAllowance[tokenAddress])) { - delete this._proxyAllowance[tokenAddress]; - } - } - } - public deleteAll(): void { - this._balance = {}; - this._proxyAllowance = {}; - } + private _token: TokenWrapper; + private _defaultBlock: BlockParamLiteral; + private _balance: { + [tokenAddress: string]: { + [userAddress: string]: BigNumber; + }; + }; + private _proxyAllowance: { + [tokenAddress: string]: { + [userAddress: string]: BigNumber; + }; + }; + constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) { + this._token = token; + this._defaultBlock = defaultBlock; + this._balance = {}; + this._proxyAllowance = {}; + } + public async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> { + if (_.isUndefined(this._balance[tokenAddress]) || _.isUndefined(this._balance[tokenAddress][userAddress])) { + const methodOpts = { + defaultBlock: this._defaultBlock, + }; + const balance = await this._token.getBalanceAsync(tokenAddress, userAddress, methodOpts); + this.setBalance(tokenAddress, userAddress, balance); + } + const cachedBalance = this._balance[tokenAddress][userAddress]; + return cachedBalance; + } + public setBalance(tokenAddress: string, userAddress: string, balance: BigNumber): void { + if (_.isUndefined(this._balance[tokenAddress])) { + this._balance[tokenAddress] = {}; + } + this._balance[tokenAddress][userAddress] = balance; + } + public deleteBalance(tokenAddress: string, userAddress: string): void { + if (!_.isUndefined(this._balance[tokenAddress])) { + delete this._balance[tokenAddress][userAddress]; + if (_.isEmpty(this._balance[tokenAddress])) { + delete this._balance[tokenAddress]; + } + } + } + public async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> { + if ( + _.isUndefined(this._proxyAllowance[tokenAddress]) || + _.isUndefined(this._proxyAllowance[tokenAddress][userAddress]) + ) { + const methodOpts = { + defaultBlock: this._defaultBlock, + }; + const proxyAllowance = await this._token.getProxyAllowanceAsync(tokenAddress, userAddress, methodOpts); + this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance); + } + const cachedProxyAllowance = this._proxyAllowance[tokenAddress][userAddress]; + return cachedProxyAllowance; + } + public setProxyAllowance(tokenAddress: string, userAddress: string, proxyAllowance: BigNumber): void { + if (_.isUndefined(this._proxyAllowance[tokenAddress])) { + this._proxyAllowance[tokenAddress] = {}; + } + this._proxyAllowance[tokenAddress][userAddress] = proxyAllowance; + } + public deleteProxyAllowance(tokenAddress: string, userAddress: string): void { + if (!_.isUndefined(this._proxyAllowance[tokenAddress])) { + delete this._proxyAllowance[tokenAddress][userAddress]; + if (_.isEmpty(this._proxyAllowance[tokenAddress])) { + delete this._proxyAllowance[tokenAddress]; + } + } + } + public deleteAll(): void { + this._balance = {}; + this._proxyAllowance = {}; + } } diff --git a/packages/0x.js/src/stores/order_filled_cancelled_lazy_store.ts b/packages/0x.js/src/stores/order_filled_cancelled_lazy_store.ts index e22364c09..a99db0702 100644 --- a/packages/0x.js/src/stores/order_filled_cancelled_lazy_store.ts +++ b/packages/0x.js/src/stores/order_filled_cancelled_lazy_store.ts @@ -8,54 +8,54 @@ import { BlockParamLiteral } from '../types'; * Copy on read store for filled/cancelled taker amounts */ export class OrderFilledCancelledLazyStore { - private _exchange: ExchangeWrapper; - private _filledTakerAmount: { - [orderHash: string]: BigNumber; - }; - private _cancelledTakerAmount: { - [orderHash: string]: BigNumber; - }; - constructor(exchange: ExchangeWrapper) { - this._exchange = exchange; - this._filledTakerAmount = {}; - this._cancelledTakerAmount = {}; - } - public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> { - if (_.isUndefined(this._filledTakerAmount[orderHash])) { - const methodOpts = { - defaultBlock: BlockParamLiteral.Pending, - }; - const filledTakerAmount = await this._exchange.getFilledTakerAmountAsync(orderHash, methodOpts); - this.setFilledTakerAmount(orderHash, filledTakerAmount); - } - const cachedFilled = this._filledTakerAmount[orderHash]; - return cachedFilled; - } - public setFilledTakerAmount(orderHash: string, filledTakerAmount: BigNumber): void { - this._filledTakerAmount[orderHash] = filledTakerAmount; - } - public deleteFilledTakerAmount(orderHash: string): void { - delete this._filledTakerAmount[orderHash]; - } - public async getCancelledTakerAmountAsync(orderHash: string): Promise<BigNumber> { - if (_.isUndefined(this._cancelledTakerAmount[orderHash])) { - const methodOpts = { - defaultBlock: BlockParamLiteral.Pending, - }; - const cancelledTakerAmount = await this._exchange.getCancelledTakerAmountAsync(orderHash, methodOpts); - this.setCancelledTakerAmount(orderHash, cancelledTakerAmount); - } - const cachedCancelled = this._cancelledTakerAmount[orderHash]; - return cachedCancelled; - } - public setCancelledTakerAmount(orderHash: string, cancelledTakerAmount: BigNumber): void { - this._cancelledTakerAmount[orderHash] = cancelledTakerAmount; - } - public deleteCancelledTakerAmount(orderHash: string): void { - delete this._cancelledTakerAmount[orderHash]; - } - public deleteAll(): void { - this._filledTakerAmount = {}; - this._cancelledTakerAmount = {}; - } + private _exchange: ExchangeWrapper; + private _filledTakerAmount: { + [orderHash: string]: BigNumber; + }; + private _cancelledTakerAmount: { + [orderHash: string]: BigNumber; + }; + constructor(exchange: ExchangeWrapper) { + this._exchange = exchange; + this._filledTakerAmount = {}; + this._cancelledTakerAmount = {}; + } + public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> { + if (_.isUndefined(this._filledTakerAmount[orderHash])) { + const methodOpts = { + defaultBlock: BlockParamLiteral.Pending, + }; + const filledTakerAmount = await this._exchange.getFilledTakerAmountAsync(orderHash, methodOpts); + this.setFilledTakerAmount(orderHash, filledTakerAmount); + } + const cachedFilled = this._filledTakerAmount[orderHash]; + return cachedFilled; + } + public setFilledTakerAmount(orderHash: string, filledTakerAmount: BigNumber): void { + this._filledTakerAmount[orderHash] = filledTakerAmount; + } + public deleteFilledTakerAmount(orderHash: string): void { + delete this._filledTakerAmount[orderHash]; + } + public async getCancelledTakerAmountAsync(orderHash: string): Promise<BigNumber> { + if (_.isUndefined(this._cancelledTakerAmount[orderHash])) { + const methodOpts = { + defaultBlock: BlockParamLiteral.Pending, + }; + const cancelledTakerAmount = await this._exchange.getCancelledTakerAmountAsync(orderHash, methodOpts); + this.setCancelledTakerAmount(orderHash, cancelledTakerAmount); + } + const cachedCancelled = this._cancelledTakerAmount[orderHash]; + return cachedCancelled; + } + public setCancelledTakerAmount(orderHash: string, cancelledTakerAmount: BigNumber): void { + this._cancelledTakerAmount[orderHash] = cancelledTakerAmount; + } + public deleteCancelledTakerAmount(orderHash: string): void { + delete this._cancelledTakerAmount[orderHash]; + } + public deleteAll(): void { + this._filledTakerAmount = {}; + this._cancelledTakerAmount = {}; + } } diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts index 3c93910e9..7eda4bea6 100644 --- a/packages/0x.js/src/types.ts +++ b/packages/0x.js/src/types.ts @@ -3,41 +3,41 @@ import { BigNumber } from '@0xproject/utils'; import * as Web3 from 'web3'; export enum ZeroExError { - ExchangeContractDoesNotExist = 'EXCHANGE_CONTRACT_DOES_NOT_EXIST', - ZRXContractDoesNotExist = 'ZRX_CONTRACT_DOES_NOT_EXIST', - EtherTokenContractDoesNotExist = 'ETHER_TOKEN_CONTRACT_DOES_NOT_EXIST', - TokenTransferProxyContractDoesNotExist = 'TOKEN_TRANSFER_PROXY_CONTRACT_DOES_NOT_EXIST', - TokenRegistryContractDoesNotExist = 'TOKEN_REGISTRY_CONTRACT_DOES_NOT_EXIST', - TokenContractDoesNotExist = 'TOKEN_CONTRACT_DOES_NOT_EXIST', - UnhandledError = 'UNHANDLED_ERROR', - UserHasNoAssociatedAddress = 'USER_HAS_NO_ASSOCIATED_ADDRESSES', - InvalidSignature = 'INVALID_SIGNATURE', - ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK', - InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER', - InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER', - InsufficientEthBalanceForDeposit = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT', - InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL', - InvalidJump = 'INVALID_JUMP', - OutOfGas = 'OUT_OF_GAS', - NoNetworkId = 'NO_NETWORK_ID', - SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND', - SubscriptionAlreadyPresent = 'SUBSCRIPTION_ALREADY_PRESENT', - TransactionMiningTimeout = 'TRANSACTION_MINING_TIMEOUT', + ExchangeContractDoesNotExist = 'EXCHANGE_CONTRACT_DOES_NOT_EXIST', + ZRXContractDoesNotExist = 'ZRX_CONTRACT_DOES_NOT_EXIST', + EtherTokenContractDoesNotExist = 'ETHER_TOKEN_CONTRACT_DOES_NOT_EXIST', + TokenTransferProxyContractDoesNotExist = 'TOKEN_TRANSFER_PROXY_CONTRACT_DOES_NOT_EXIST', + TokenRegistryContractDoesNotExist = 'TOKEN_REGISTRY_CONTRACT_DOES_NOT_EXIST', + TokenContractDoesNotExist = 'TOKEN_CONTRACT_DOES_NOT_EXIST', + UnhandledError = 'UNHANDLED_ERROR', + UserHasNoAssociatedAddress = 'USER_HAS_NO_ASSOCIATED_ADDRESSES', + InvalidSignature = 'INVALID_SIGNATURE', + ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK', + InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER', + InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER', + InsufficientEthBalanceForDeposit = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT', + InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL', + InvalidJump = 'INVALID_JUMP', + OutOfGas = 'OUT_OF_GAS', + NoNetworkId = 'NO_NETWORK_ID', + SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND', + SubscriptionAlreadyPresent = 'SUBSCRIPTION_ALREADY_PRESENT', + TransactionMiningTimeout = 'TRANSACTION_MINING_TIMEOUT', } export enum InternalZeroExError { - NoAbiDecoder = 'NO_ABI_DECODER', - ZrxNotInTokenRegistry = 'ZRX_NOT_IN_TOKEN_REGISTRY', - WethNotInTokenRegistry = 'WETH_NOT_IN_TOKEN_REGISTRY', + NoAbiDecoder = 'NO_ABI_DECODER', + ZrxNotInTokenRegistry = 'ZRX_NOT_IN_TOKEN_REGISTRY', + WethNotInTokenRegistry = 'WETH_NOT_IN_TOKEN_REGISTRY', } /** * Elliptic Curve signature */ export interface ECSignature { - v: number; - r: string; - s: string; + v: number; + r: string; + s: string; } export type OrderAddresses = [string, string, string, string, string]; @@ -46,214 +46,214 @@ export type OrderValues = [BigNumber, BigNumber, BigNumber, BigNumber, BigNumber export type LogEvent = Web3.LogEntryEvent; export interface DecodedLogEvent<ArgsType> { - isRemoved: boolean; - log: LogWithDecodedArgs<ArgsType>; + isRemoved: boolean; + log: LogWithDecodedArgs<ArgsType>; } export type EventCallback<ArgsType> = (err: null | Error, log?: DecodedLogEvent<ArgsType>) => void; export type EventWatcherCallback = (err: null | Error, log?: LogEvent) => void; export enum SolidityTypes { - Address = 'address', - Uint256 = 'uint256', - Uint8 = 'uint8', - Uint = 'uint', + Address = 'address', + Uint256 = 'uint256', + Uint8 = 'uint8', + Uint = 'uint', } export enum ExchangeContractErrCodes { - ERROR_FILL_EXPIRED, // Order has already expired - ERROR_FILL_NO_VALUE, // Order has already been fully filled or cancelled - ERROR_FILL_TRUNCATION, // Rounding error too large - ERROR_FILL_BALANCE_ALLOWANCE, // Insufficient balance or allowance for token transfer - ERROR_CANCEL_EXPIRED, // Order has already expired - ERROR_CANCEL_NO_VALUE, // Order has already been fully filled or cancelled + ERROR_FILL_EXPIRED, // Order has already expired + ERROR_FILL_NO_VALUE, // Order has already been fully filled or cancelled + ERROR_FILL_TRUNCATION, // Rounding error too large + ERROR_FILL_BALANCE_ALLOWANCE, // Insufficient balance or allowance for token transfer + ERROR_CANCEL_EXPIRED, // Order has already expired + ERROR_CANCEL_NO_VALUE, // Order has already been fully filled or cancelled } export enum ExchangeContractErrs { - OrderFillExpired = 'ORDER_FILL_EXPIRED', - OrderCancelExpired = 'ORDER_CANCEL_EXPIRED', - OrderCancelAmountZero = 'ORDER_CANCEL_AMOUNT_ZERO', - OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED', - OrderFillAmountZero = 'ORDER_FILL_AMOUNT_ZERO', - OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO', - OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR', - FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR', - InsufficientTakerBalance = 'INSUFFICIENT_TAKER_BALANCE', - InsufficientTakerAllowance = 'INSUFFICIENT_TAKER_ALLOWANCE', - InsufficientMakerBalance = 'INSUFFICIENT_MAKER_BALANCE', - InsufficientMakerAllowance = 'INSUFFICIENT_MAKER_ALLOWANCE', - InsufficientTakerFeeBalance = 'INSUFFICIENT_TAKER_FEE_BALANCE', - InsufficientTakerFeeAllowance = 'INSUFFICIENT_TAKER_FEE_ALLOWANCE', - InsufficientMakerFeeBalance = 'INSUFFICIENT_MAKER_FEE_BALANCE', - InsufficientMakerFeeAllowance = 'INSUFFICIENT_MAKER_FEE_ALLOWANCE', - TransactionSenderIsNotFillOrderTaker = 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER', - MultipleMakersInSingleCancelBatchDisallowed = 'MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH_DISALLOWED', - InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT', - MultipleTakerTokensInFillUpToDisallowed = 'MULTIPLE_TAKER_TOKENS_IN_FILL_UP_TO_DISALLOWED', - BatchOrdersMustHaveSameExchangeAddress = 'BATCH_ORDERS_MUST_HAVE_SAME_EXCHANGE_ADDRESS', - BatchOrdersMustHaveAtLeastOneItem = 'BATCH_ORDERS_MUST_HAVE_AT_LEAST_ONE_ITEM', + OrderFillExpired = 'ORDER_FILL_EXPIRED', + OrderCancelExpired = 'ORDER_CANCEL_EXPIRED', + OrderCancelAmountZero = 'ORDER_CANCEL_AMOUNT_ZERO', + OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED', + OrderFillAmountZero = 'ORDER_FILL_AMOUNT_ZERO', + OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO', + OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR', + FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR', + InsufficientTakerBalance = 'INSUFFICIENT_TAKER_BALANCE', + InsufficientTakerAllowance = 'INSUFFICIENT_TAKER_ALLOWANCE', + InsufficientMakerBalance = 'INSUFFICIENT_MAKER_BALANCE', + InsufficientMakerAllowance = 'INSUFFICIENT_MAKER_ALLOWANCE', + InsufficientTakerFeeBalance = 'INSUFFICIENT_TAKER_FEE_BALANCE', + InsufficientTakerFeeAllowance = 'INSUFFICIENT_TAKER_FEE_ALLOWANCE', + InsufficientMakerFeeBalance = 'INSUFFICIENT_MAKER_FEE_BALANCE', + InsufficientMakerFeeAllowance = 'INSUFFICIENT_MAKER_FEE_ALLOWANCE', + TransactionSenderIsNotFillOrderTaker = 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER', + MultipleMakersInSingleCancelBatchDisallowed = 'MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH_DISALLOWED', + InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT', + MultipleTakerTokensInFillUpToDisallowed = 'MULTIPLE_TAKER_TOKENS_IN_FILL_UP_TO_DISALLOWED', + BatchOrdersMustHaveSameExchangeAddress = 'BATCH_ORDERS_MUST_HAVE_SAME_EXCHANGE_ADDRESS', + BatchOrdersMustHaveAtLeastOneItem = 'BATCH_ORDERS_MUST_HAVE_AT_LEAST_ONE_ITEM', } export type RawLog = Web3.LogEntry; export interface ContractEvent { - logIndex: number; - transactionIndex: number; - transactionHash: string; - blockHash: string; - blockNumber: number; - address: string; - type: string; - event: string; - args: ContractEventArgs; + logIndex: number; + transactionIndex: number; + transactionHash: string; + blockHash: string; + blockNumber: number; + address: string; + type: string; + event: string; + args: ContractEventArgs; } export interface LogFillContractEventArgs { - maker: string; - taker: string; - feeRecipient: string; - makerToken: string; - takerToken: string; - filledMakerTokenAmount: BigNumber; - filledTakerTokenAmount: BigNumber; - paidMakerFee: BigNumber; - paidTakerFee: BigNumber; - tokens: string; - orderHash: string; + maker: string; + taker: string; + feeRecipient: string; + makerToken: string; + takerToken: string; + filledMakerTokenAmount: BigNumber; + filledTakerTokenAmount: BigNumber; + paidMakerFee: BigNumber; + paidTakerFee: BigNumber; + tokens: string; + orderHash: string; } export interface LogCancelContractEventArgs { - maker: string; - feeRecipient: string; - makerToken: string; - takerToken: string; - cancelledMakerTokenAmount: BigNumber; - cancelledTakerTokenAmount: BigNumber; - tokens: string; - orderHash: string; + maker: string; + feeRecipient: string; + makerToken: string; + takerToken: string; + cancelledMakerTokenAmount: BigNumber; + cancelledTakerTokenAmount: BigNumber; + tokens: string; + orderHash: string; } export interface LogErrorContractEventArgs { - errorId: BigNumber; - orderHash: string; + errorId: BigNumber; + orderHash: string; } export type ExchangeContractEventArgs = - | LogFillContractEventArgs - | LogCancelContractEventArgs - | LogErrorContractEventArgs; + | LogFillContractEventArgs + | LogCancelContractEventArgs + | LogErrorContractEventArgs; export interface TransferContractEventArgs { - _from: string; - _to: string; - _value: BigNumber; + _from: string; + _to: string; + _value: BigNumber; } export interface ApprovalContractEventArgs { - _owner: string; - _spender: string; - _value: BigNumber; + _owner: string; + _spender: string; + _value: BigNumber; } export interface DepositContractEventArgs { - _owner: string; - _value: BigNumber; + _owner: string; + _value: BigNumber; } export interface WithdrawalContractEventArgs { - _owner: string; - _value: BigNumber; + _owner: string; + _value: BigNumber; } export type TokenContractEventArgs = TransferContractEventArgs | ApprovalContractEventArgs; export type EtherTokenContractEventArgs = - | TokenContractEventArgs - | DepositContractEventArgs - | WithdrawalContractEventArgs; + | TokenContractEventArgs + | DepositContractEventArgs + | WithdrawalContractEventArgs; export type ContractEventArgs = ExchangeContractEventArgs | TokenContractEventArgs | EtherTokenContractEventArgs; export type ContractEventArg = string | BigNumber; export interface Order { - maker: string; - taker: string; - makerFee: BigNumber; - takerFee: BigNumber; - makerTokenAmount: BigNumber; - takerTokenAmount: BigNumber; - makerTokenAddress: string; - takerTokenAddress: string; - salt: BigNumber; - exchangeContractAddress: string; - feeRecipient: string; - expirationUnixTimestampSec: BigNumber; + maker: string; + taker: string; + makerFee: BigNumber; + takerFee: BigNumber; + makerTokenAmount: BigNumber; + takerTokenAmount: BigNumber; + makerTokenAddress: string; + takerTokenAddress: string; + salt: BigNumber; + exchangeContractAddress: string; + feeRecipient: string; + expirationUnixTimestampSec: BigNumber; } export interface SignedOrder extends Order { - ecSignature: ECSignature; + ecSignature: ECSignature; } // [address, name, symbol, decimals, ipfsHash, swarmHash] export type TokenMetadata = [string, string, string, BigNumber, string, string]; export interface Token { - name: string; - address: string; - symbol: string; - decimals: number; + name: string; + address: string; + symbol: string; + decimals: number; } export interface TxOpts { - from: string; - gas?: number; - value?: BigNumber; - gasPrice?: BigNumber; + from: string; + gas?: number; + value?: BigNumber; + gasPrice?: BigNumber; } export interface TokenAddressBySymbol { - [symbol: string]: string; + [symbol: string]: string; } export enum ExchangeEvents { - LogFill = 'LogFill', - LogCancel = 'LogCancel', - LogError = 'LogError', + LogFill = 'LogFill', + LogCancel = 'LogCancel', + LogError = 'LogError', } export enum TokenEvents { - Transfer = 'Transfer', - Approval = 'Approval', + Transfer = 'Transfer', + Approval = 'Approval', } export enum EtherTokenEvents { - Transfer = 'Transfer', - Approval = 'Approval', - Deposit = 'Deposit', - Withdrawal = 'Withdrawal', + Transfer = 'Transfer', + Approval = 'Approval', + Deposit = 'Deposit', + Withdrawal = 'Withdrawal', } export type ContractEvents = TokenEvents | ExchangeEvents | EtherTokenEvents; export interface IndexedFilterValues { - [index: string]: ContractEventArg; + [index: string]: ContractEventArg; } // Earliest is omitted by design. It is simply an alias for the `0` constant and // is thus not very helpful. Moreover, this type is used in places that only accept // `latest` or `pending`. export enum BlockParamLiteral { - Latest = 'latest', - Pending = 'pending', + Latest = 'latest', + Pending = 'pending', } export type BlockParam = BlockParamLiteral | number; export interface BlockRange { - fromBlock: BlockParam; - toBlock: BlockParam; + fromBlock: BlockParam; + toBlock: BlockParam; } export type DoneCallback = (err?: Error) => void; export interface OrderCancellationRequest { - order: Order | SignedOrder; - takerTokenCancelAmount: BigNumber; + order: Order | SignedOrder; + takerTokenCancelAmount: BigNumber; } export interface OrderFillRequest { - signedOrder: SignedOrder; - takerTokenFillAmount: BigNumber; + signedOrder: SignedOrder; + takerTokenFillAmount: BigNumber; } export type AsyncMethod = (...args: any[]) => Promise<any>; @@ -268,8 +268,8 @@ export type SyncMethod = (...args: any[]) => any; export type Web3Provider = Web3.Provider; export interface JSONRPCPayload { - params: any[]; - method: string; + params: any[]; + method: string; } /* @@ -280,10 +280,10 @@ export interface JSONRPCPayload { * cleanupJobIntervalMs: How often to run a cleanup job which revalidates all the orders. Defaults: 1h */ export interface OrderStateWatcherConfig { - orderExpirationCheckingIntervalMs?: number; - eventPollingIntervalMs?: number; - expirationMarginMs?: number; - cleanupJobIntervalMs?: number; + orderExpirationCheckingIntervalMs?: number; + eventPollingIntervalMs?: number; + expirationMarginMs?: number; + cleanupJobIntervalMs?: number; } /* @@ -296,42 +296,42 @@ export interface OrderStateWatcherConfig { * orderWatcherConfig: All the configs related to the orderWatcher */ export interface ZeroExConfig { - networkId: number; - gasPrice?: BigNumber; - exchangeContractAddress?: string; - zrxContractAddress?: string; - tokenRegistryContractAddress?: string; - tokenTransferProxyContractAddress?: string; - orderWatcherConfig?: OrderStateWatcherConfig; + networkId: number; + gasPrice?: BigNumber; + exchangeContractAddress?: string; + zrxContractAddress?: string; + tokenRegistryContractAddress?: string; + tokenTransferProxyContractAddress?: string; + orderWatcherConfig?: OrderStateWatcherConfig; } export enum AbiType { - Function = 'function', - Constructor = 'constructor', - Event = 'event', - Fallback = 'fallback', + Function = 'function', + Constructor = 'constructor', + Event = 'event', + Fallback = 'fallback', } export interface DecodedLogArgs { - [argName: string]: ContractEventArg; + [argName: string]: ContractEventArg; } export interface LogWithDecodedArgs<ArgsType> extends Web3.DecodedLogEntry<ArgsType> {} export interface TransactionReceiptWithDecodedLogs extends TransactionReceipt { - logs: Array<LogWithDecodedArgs<DecodedLogArgs> | Web3.LogEntry>; + logs: Array<LogWithDecodedArgs<DecodedLogArgs> | Web3.LogEntry>; } export type ArtifactContractName = 'ZRX' | 'TokenTransferProxy' | 'TokenRegistry' | 'Token' | 'Exchange' | 'EtherToken'; export interface Artifact { - contract_name: ArtifactContractName; - abi: Web3.ContractAbi; - networks: { - [networkId: number]: { - address: string; - }; - }; + contract_name: ArtifactContractName; + abi: Web3.ContractAbi; + networks: { + [networkId: number]: { + address: string; + }; + }; } /* @@ -341,7 +341,7 @@ export interface Artifact { * allowance/balance to fill the entire remaining order amount. */ export interface ValidateOrderFillableOpts { - expectedFillTakerTokenAmount?: BigNumber; + expectedFillTakerTokenAmount?: BigNumber; } /* @@ -351,7 +351,7 @@ export interface ValidateOrderFillableOpts { * flag when running Parity). */ export interface MethodOpts { - defaultBlock?: Web3.BlockParam; + defaultBlock?: Web3.BlockParam; } /* @@ -359,8 +359,8 @@ export interface MethodOpts { * gasLimit: The amount of gas to send with a transaction */ export interface TransactionOpts { - gasPrice?: BigNumber; - gasLimit?: number; + gasPrice?: BigNumber; + gasLimit?: number; } /* @@ -368,42 +368,42 @@ export interface TransactionOpts { * broadcasting it. For example, order has a valid signature, maker has sufficient funds, etc. Default: true */ export interface OrderTransactionOpts extends TransactionOpts { - shouldValidate?: boolean; + shouldValidate?: boolean; } export type FilterObject = Web3.FilterObject; export enum TradeSide { - Maker = 'maker', - Taker = 'taker', + Maker = 'maker', + Taker = 'taker', } export enum TransferType { - Trade = 'trade', - Fee = 'fee', + Trade = 'trade', + Fee = 'fee', } export interface OrderRelevantState { - makerBalance: BigNumber; - makerProxyAllowance: BigNumber; - makerFeeBalance: BigNumber; - makerFeeProxyAllowance: BigNumber; - filledTakerTokenAmount: BigNumber; - cancelledTakerTokenAmount: BigNumber; - remainingFillableMakerTokenAmount: BigNumber; - remainingFillableTakerTokenAmount: BigNumber; + makerBalance: BigNumber; + makerProxyAllowance: BigNumber; + makerFeeBalance: BigNumber; + makerFeeProxyAllowance: BigNumber; + filledTakerTokenAmount: BigNumber; + cancelledTakerTokenAmount: BigNumber; + remainingFillableMakerTokenAmount: BigNumber; + remainingFillableTakerTokenAmount: BigNumber; } export interface OrderStateValid { - isValid: true; - orderHash: string; - orderRelevantState: OrderRelevantState; + isValid: true; + orderHash: string; + orderRelevantState: OrderRelevantState; } export interface OrderStateInvalid { - isValid: false; - orderHash: string; - error: ExchangeContractErrs; + isValid: false; + orderHash: string; + error: ExchangeContractErrs; } export type OrderState = OrderStateValid | OrderStateInvalid; diff --git a/packages/0x.js/src/utils/abi_decoder.ts b/packages/0x.js/src/utils/abi_decoder.ts index bbd2a0b1d..ace6dd165 100644 --- a/packages/0x.js/src/utils/abi_decoder.ts +++ b/packages/0x.js/src/utils/abi_decoder.ts @@ -6,67 +6,67 @@ import * as SolidityCoder from 'web3/lib/solidity/coder'; import { AbiType, ContractEventArgs, DecodedLogArgs, LogWithDecodedArgs, RawLog, SolidityTypes } from '../types'; export class AbiDecoder { - private _savedABIs: Web3.AbiDefinition[] = []; - private _methodIds: { [signatureHash: string]: Web3.EventAbi } = {}; - private static _padZeros(address: string) { - let formatted = address; - if (_.startsWith(formatted, '0x')) { - formatted = formatted.slice(2); - } + private _savedABIs: Web3.AbiDefinition[] = []; + private _methodIds: { [signatureHash: string]: Web3.EventAbi } = {}; + private static _padZeros(address: string) { + let formatted = address; + if (_.startsWith(formatted, '0x')) { + formatted = formatted.slice(2); + } - formatted = _.padStart(formatted, 40, '0'); - return `0x${formatted}`; - } - constructor(abiArrays: Web3.AbiDefinition[][]) { - _.map(abiArrays, this._addABI.bind(this)); - } - // This method can only decode logs from the 0x & ERC20 smart contracts - public tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>( - log: Web3.LogEntry, - ): LogWithDecodedArgs<ArgsType> | RawLog { - const methodId = log.topics[0]; - const event = this._methodIds[methodId]; - if (_.isUndefined(event)) { - return log; - } - const logData = log.data; - const decodedParams: DecodedLogArgs = {}; - let dataIndex = 0; - let topicsIndex = 1; + formatted = _.padStart(formatted, 40, '0'); + return `0x${formatted}`; + } + constructor(abiArrays: Web3.AbiDefinition[][]) { + _.map(abiArrays, this._addABI.bind(this)); + } + // This method can only decode logs from the 0x & ERC20 smart contracts + public tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>( + log: Web3.LogEntry, + ): LogWithDecodedArgs<ArgsType> | RawLog { + const methodId = log.topics[0]; + const event = this._methodIds[methodId]; + if (_.isUndefined(event)) { + return log; + } + const logData = log.data; + const decodedParams: DecodedLogArgs = {}; + let dataIndex = 0; + let topicsIndex = 1; - const nonIndexedInputs = _.filter(event.inputs, input => !input.indexed); - const dataTypes = _.map(nonIndexedInputs, input => input.type); - const decodedData = SolidityCoder.decodeParams(dataTypes, logData.slice('0x'.length)); + const nonIndexedInputs = _.filter(event.inputs, input => !input.indexed); + const dataTypes = _.map(nonIndexedInputs, input => input.type); + const decodedData = SolidityCoder.decodeParams(dataTypes, logData.slice('0x'.length)); - _.map(event.inputs, (param: Web3.EventParameter) => { - // Indexed parameters are stored in topics. Non-indexed ones in decodedData - let value = param.indexed ? log.topics[topicsIndex++] : decodedData[dataIndex++]; - if (param.type === SolidityTypes.Address) { - value = AbiDecoder._padZeros(new BigNumber(value).toString(16)); - } else if ( - param.type === SolidityTypes.Uint256 || - param.type === SolidityTypes.Uint8 || - param.type === SolidityTypes.Uint - ) { - value = new BigNumber(value); - } - decodedParams[param.name] = value; - }); + _.map(event.inputs, (param: Web3.EventParameter) => { + // Indexed parameters are stored in topics. Non-indexed ones in decodedData + let value = param.indexed ? log.topics[topicsIndex++] : decodedData[dataIndex++]; + if (param.type === SolidityTypes.Address) { + value = AbiDecoder._padZeros(new BigNumber(value).toString(16)); + } else if ( + param.type === SolidityTypes.Uint256 || + param.type === SolidityTypes.Uint8 || + param.type === SolidityTypes.Uint + ) { + value = new BigNumber(value); + } + decodedParams[param.name] = value; + }); - return { - ...log, - event: event.name, - args: decodedParams, - }; - } - private _addABI(abiArray: Web3.AbiDefinition[]): void { - _.map(abiArray, (abi: Web3.AbiDefinition) => { - if (abi.type === AbiType.Event) { - const signature = `${abi.name}(${_.map(abi.inputs, input => input.type).join(',')})`; - const signatureHash = new Web3().sha3(signature); - this._methodIds[signatureHash] = abi; - } - }); - this._savedABIs = this._savedABIs.concat(abiArray); - } + return { + ...log, + event: event.name, + args: decodedParams, + }; + } + private _addABI(abiArray: Web3.AbiDefinition[]): void { + _.map(abiArray, (abi: Web3.AbiDefinition) => { + if (abi.type === AbiType.Event) { + const signature = `${abi.name}(${_.map(abi.inputs, input => input.type).join(',')})`; + const signatureHash = new Web3().sha3(signature); + this._methodIds[signatureHash] = abi; + } + }); + this._savedABIs = this._savedABIs.concat(abiArray); + } } diff --git a/packages/0x.js/src/utils/assert.ts b/packages/0x.js/src/utils/assert.ts index c21f2dbca..776ee7b79 100644 --- a/packages/0x.js/src/utils/assert.ts +++ b/packages/0x.js/src/utils/assert.ts @@ -11,25 +11,25 @@ import { ECSignature } from '../types'; import { signatureUtils } from '../utils/signature_utils'; export const assert = { - ...sharedAssert, - isValidSignature(orderHash: string, ecSignature: ECSignature, signerAddress: string) { - const isValidSignature = signatureUtils.isValidSignature(orderHash, ecSignature, signerAddress); - this.assert(isValidSignature, `Expected order with hash '${orderHash}' to have a valid signature`); - }, - async isSenderAddressAsync( - variableName: string, - senderAddressHex: string, - web3Wrapper: Web3Wrapper, - ): Promise<void> { - sharedAssert.isETHAddressHex(variableName, senderAddressHex); - const isSenderAddressAvailable = await web3Wrapper.isSenderAddressAvailableAsync(senderAddressHex); - sharedAssert.assert( - isSenderAddressAvailable, - `Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`, - ); - }, - async isUserAddressAvailableAsync(web3Wrapper: Web3Wrapper): Promise<void> { - const availableAddresses = await web3Wrapper.getAvailableAddressesAsync(); - this.assert(!_.isEmpty(availableAddresses), 'No addresses were available on the provided web3 provider'); - }, + ...sharedAssert, + isValidSignature(orderHash: string, ecSignature: ECSignature, signerAddress: string) { + const isValidSignature = signatureUtils.isValidSignature(orderHash, ecSignature, signerAddress); + this.assert(isValidSignature, `Expected order with hash '${orderHash}' to have a valid signature`); + }, + async isSenderAddressAsync( + variableName: string, + senderAddressHex: string, + web3Wrapper: Web3Wrapper, + ): Promise<void> { + sharedAssert.isETHAddressHex(variableName, senderAddressHex); + const isSenderAddressAvailable = await web3Wrapper.isSenderAddressAvailableAsync(senderAddressHex); + sharedAssert.assert( + isSenderAddressAvailable, + `Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`, + ); + }, + async isUserAddressAvailableAsync(web3Wrapper: Web3Wrapper): Promise<void> { + const availableAddresses = await web3Wrapper.getAvailableAddressesAsync(); + this.assert(!_.isEmpty(availableAddresses), 'No addresses were available on the provided web3 provider'); + }, }; diff --git a/packages/0x.js/src/utils/constants.ts b/packages/0x.js/src/utils/constants.ts index 06beec8e2..f32847ec2 100644 --- a/packages/0x.js/src/utils/constants.ts +++ b/packages/0x.js/src/utils/constants.ts @@ -1,12 +1,12 @@ import { BigNumber } from '@0xproject/utils'; export const constants = { - NULL_ADDRESS: '0x0000000000000000000000000000000000000000', - TESTRPC_NETWORK_ID: 50, - MAX_DIGITS_IN_UNSIGNED_256_INT: 78, - INVALID_JUMP_PATTERN: 'invalid JUMP at', - OUT_OF_GAS_PATTERN: 'out of gas', - INVALID_TAKER_FORMAT: 'instance.taker is not of a type(s) string', - UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1), - DEFAULT_BLOCK_POLLING_INTERVAL: 1000, + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', + TESTRPC_NETWORK_ID: 50, + MAX_DIGITS_IN_UNSIGNED_256_INT: 78, + INVALID_JUMP_PATTERN: 'invalid JUMP at', + OUT_OF_GAS_PATTERN: 'out of gas', + INVALID_TAKER_FORMAT: 'instance.taker is not of a type(s) string', + UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1), + DEFAULT_BLOCK_POLLING_INTERVAL: 1000, }; diff --git a/packages/0x.js/src/utils/decorators.ts b/packages/0x.js/src/utils/decorators.ts index f774d734e..c1a022a81 100644 --- a/packages/0x.js/src/utils/decorators.ts +++ b/packages/0x.js/src/utils/decorators.ts @@ -7,85 +7,85 @@ import { constants } from './constants'; type ErrorTransformer = (err: Error) => Error; const contractCallErrorTransformer = (error: Error) => { - if (_.includes(error.message, constants.INVALID_JUMP_PATTERN)) { - return new Error(ZeroExError.InvalidJump); - } - if (_.includes(error.message, constants.OUT_OF_GAS_PATTERN)) { - return new Error(ZeroExError.OutOfGas); - } - return error; + if (_.includes(error.message, constants.INVALID_JUMP_PATTERN)) { + return new Error(ZeroExError.InvalidJump); + } + if (_.includes(error.message, constants.OUT_OF_GAS_PATTERN)) { + return new Error(ZeroExError.OutOfGas); + } + return error; }; const schemaErrorTransformer = (error: Error) => { - if (_.includes(error.message, constants.INVALID_TAKER_FORMAT)) { - const errMsg = - 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS'; - return new Error(errMsg); - } - return error; + if (_.includes(error.message, constants.INVALID_TAKER_FORMAT)) { + const errMsg = + 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS'; + return new Error(errMsg); + } + return error; }; /** * Source: https://stackoverflow.com/a/29837695/3546986 */ const asyncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => { - const asyncErrorHandlingDecorator = ( - target: object, - key: string | symbol, - descriptor: TypedPropertyDescriptor<AsyncMethod>, - ) => { - const originalMethod = descriptor.value as AsyncMethod; + const asyncErrorHandlingDecorator = ( + target: object, + key: string | symbol, + descriptor: TypedPropertyDescriptor<AsyncMethod>, + ) => { + const originalMethod = descriptor.value as AsyncMethod; - // Do not use arrow syntax here. Use a function expression in - // order to use the correct value of `this` in this method - // tslint:disable-next-line:only-arrow-functions - descriptor.value = async function(...args: any[]) { - try { - const result = await originalMethod.apply(this, args); - return result; - } catch (error) { - const transformedError = errorTransformer(error); - throw transformedError; - } - }; + // Do not use arrow syntax here. Use a function expression in + // order to use the correct value of `this` in this method + // tslint:disable-next-line:only-arrow-functions + descriptor.value = async function(...args: any[]) { + try { + const result = await originalMethod.apply(this, args); + return result; + } catch (error) { + const transformedError = errorTransformer(error); + throw transformedError; + } + }; - return descriptor; - }; + return descriptor; + }; - return asyncErrorHandlingDecorator; + return asyncErrorHandlingDecorator; }; const syncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => { - const syncErrorHandlingDecorator = ( - target: object, - key: string | symbol, - descriptor: TypedPropertyDescriptor<SyncMethod>, - ) => { - const originalMethod = descriptor.value as SyncMethod; + const syncErrorHandlingDecorator = ( + target: object, + key: string | symbol, + descriptor: TypedPropertyDescriptor<SyncMethod>, + ) => { + const originalMethod = descriptor.value as SyncMethod; - // Do not use arrow syntax here. Use a function expression in - // order to use the correct value of `this` in this method - // tslint:disable-next-line:only-arrow-functions - descriptor.value = function(...args: any[]) { - try { - const result = originalMethod.apply(this, args); - return result; - } catch (error) { - const transformedError = errorTransformer(error); - throw transformedError; - } - }; + // Do not use arrow syntax here. Use a function expression in + // order to use the correct value of `this` in this method + // tslint:disable-next-line:only-arrow-functions + descriptor.value = function(...args: any[]) { + try { + const result = originalMethod.apply(this, args); + return result; + } catch (error) { + const transformedError = errorTransformer(error); + throw transformedError; + } + }; - return descriptor; - }; + return descriptor; + }; - return syncErrorHandlingDecorator; + return syncErrorHandlingDecorator; }; // _.flow(f, g) = f ∘ g const zeroExErrorTransformer = _.flow(schemaErrorTransformer, contractCallErrorTransformer); export const decorators = { - asyncZeroExErrorHandler: asyncErrorHandlerFactory(zeroExErrorTransformer), - syncZeroExErrorHandler: syncErrorHandlerFactory(zeroExErrorTransformer), + asyncZeroExErrorHandler: asyncErrorHandlerFactory(zeroExErrorTransformer), + syncZeroExErrorHandler: syncErrorHandlerFactory(zeroExErrorTransformer), }; diff --git a/packages/0x.js/src/utils/exchange_transfer_simulator.ts b/packages/0x.js/src/utils/exchange_transfer_simulator.ts index 662cd210c..c9a2f87ce 100644 --- a/packages/0x.js/src/utils/exchange_transfer_simulator.ts +++ b/packages/0x.js/src/utils/exchange_transfer_simulator.ts @@ -6,101 +6,101 @@ import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allow import { BlockParamLiteral, ExchangeContractErrs, TradeSide, TransferType } from '../types'; enum FailureReason { - Balance = 'balance', - ProxyAllowance = 'proxyAllowance', + Balance = 'balance', + ProxyAllowance = 'proxyAllowance', } const ERR_MSG_MAPPING = { - [FailureReason.Balance]: { - [TradeSide.Maker]: { - [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerBalance, - [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeBalance, - }, - [TradeSide.Taker]: { - [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerBalance, - [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeBalance, - }, - }, - [FailureReason.ProxyAllowance]: { - [TradeSide.Maker]: { - [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerAllowance, - [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeAllowance, - }, - [TradeSide.Taker]: { - [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerAllowance, - [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeAllowance, - }, - }, + [FailureReason.Balance]: { + [TradeSide.Maker]: { + [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerBalance, + [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeBalance, + }, + [TradeSide.Taker]: { + [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerBalance, + [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeBalance, + }, + }, + [FailureReason.ProxyAllowance]: { + [TradeSide.Maker]: { + [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerAllowance, + [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeAllowance, + }, + [TradeSide.Taker]: { + [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerAllowance, + [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeAllowance, + }, + }, }; export class ExchangeTransferSimulator { - private _store: BalanceAndProxyAllowanceLazyStore; - private _UNLIMITED_ALLOWANCE_IN_BASE_UNITS: BigNumber; - private static _throwValidationError( - failureReason: FailureReason, - tradeSide: TradeSide, - transferType: TransferType, - ): never { - const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType]; - throw new Error(errMsg); - } - constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) { - this._store = new BalanceAndProxyAllowanceLazyStore(token, defaultBlock); - this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS = token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; - } - /** - * Simulates transferFrom call performed by a proxy - * @param tokenAddress Address of the token to be transferred - * @param from Owner of the transferred tokens - * @param to Recipient of the transferred tokens - * @param amountInBaseUnits The amount of tokens being transferred - * @param tradeSide Is Maker/Taker transferring - * @param transferType Is it a fee payment or a value transfer - */ - public async transferFromAsync( - tokenAddress: string, - from: string, - to: string, - amountInBaseUnits: BigNumber, - tradeSide: TradeSide, - transferType: TransferType, - ): Promise<void> { - const balance = await this._store.getBalanceAsync(tokenAddress, from); - const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, from); - if (proxyAllowance.lessThan(amountInBaseUnits)) { - ExchangeTransferSimulator._throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType); - } - if (balance.lessThan(amountInBaseUnits)) { - ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType); - } - await this._decreaseProxyAllowanceAsync(tokenAddress, from, amountInBaseUnits); - await this._decreaseBalanceAsync(tokenAddress, from, amountInBaseUnits); - await this._increaseBalanceAsync(tokenAddress, to, amountInBaseUnits); - } - private async _decreaseProxyAllowanceAsync( - tokenAddress: string, - userAddress: string, - amountInBaseUnits: BigNumber, - ): Promise<void> { - const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, userAddress); - if (!proxyAllowance.eq(this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) { - this._store.setProxyAllowance(tokenAddress, userAddress, proxyAllowance.minus(amountInBaseUnits)); - } - } - private async _increaseBalanceAsync( - tokenAddress: string, - userAddress: string, - amountInBaseUnits: BigNumber, - ): Promise<void> { - const balance = await this._store.getBalanceAsync(tokenAddress, userAddress); - this._store.setBalance(tokenAddress, userAddress, balance.plus(amountInBaseUnits)); - } - private async _decreaseBalanceAsync( - tokenAddress: string, - userAddress: string, - amountInBaseUnits: BigNumber, - ): Promise<void> { - const balance = await this._store.getBalanceAsync(tokenAddress, userAddress); - this._store.setBalance(tokenAddress, userAddress, balance.minus(amountInBaseUnits)); - } + private _store: BalanceAndProxyAllowanceLazyStore; + private _UNLIMITED_ALLOWANCE_IN_BASE_UNITS: BigNumber; + private static _throwValidationError( + failureReason: FailureReason, + tradeSide: TradeSide, + transferType: TransferType, + ): never { + const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType]; + throw new Error(errMsg); + } + constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) { + this._store = new BalanceAndProxyAllowanceLazyStore(token, defaultBlock); + this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS = token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; + } + /** + * Simulates transferFrom call performed by a proxy + * @param tokenAddress Address of the token to be transferred + * @param from Owner of the transferred tokens + * @param to Recipient of the transferred tokens + * @param amountInBaseUnits The amount of tokens being transferred + * @param tradeSide Is Maker/Taker transferring + * @param transferType Is it a fee payment or a value transfer + */ + public async transferFromAsync( + tokenAddress: string, + from: string, + to: string, + amountInBaseUnits: BigNumber, + tradeSide: TradeSide, + transferType: TransferType, + ): Promise<void> { + const balance = await this._store.getBalanceAsync(tokenAddress, from); + const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, from); + if (proxyAllowance.lessThan(amountInBaseUnits)) { + ExchangeTransferSimulator._throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType); + } + if (balance.lessThan(amountInBaseUnits)) { + ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType); + } + await this._decreaseProxyAllowanceAsync(tokenAddress, from, amountInBaseUnits); + await this._decreaseBalanceAsync(tokenAddress, from, amountInBaseUnits); + await this._increaseBalanceAsync(tokenAddress, to, amountInBaseUnits); + } + private async _decreaseProxyAllowanceAsync( + tokenAddress: string, + userAddress: string, + amountInBaseUnits: BigNumber, + ): Promise<void> { + const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, userAddress); + if (!proxyAllowance.eq(this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) { + this._store.setProxyAllowance(tokenAddress, userAddress, proxyAllowance.minus(amountInBaseUnits)); + } + } + private async _increaseBalanceAsync( + tokenAddress: string, + userAddress: string, + amountInBaseUnits: BigNumber, + ): Promise<void> { + const balance = await this._store.getBalanceAsync(tokenAddress, userAddress); + this._store.setBalance(tokenAddress, userAddress, balance.plus(amountInBaseUnits)); + } + private async _decreaseBalanceAsync( + tokenAddress: string, + userAddress: string, + amountInBaseUnits: BigNumber, + ): Promise<void> { + const balance = await this._store.getBalanceAsync(tokenAddress, userAddress); + this._store.setBalance(tokenAddress, userAddress, balance.minus(amountInBaseUnits)); + } } diff --git a/packages/0x.js/src/utils/filter_utils.ts b/packages/0x.js/src/utils/filter_utils.ts index 97205ace3..c34ae8893 100644 --- a/packages/0x.js/src/utils/filter_utils.ts +++ b/packages/0x.js/src/utils/filter_utils.ts @@ -9,79 +9,79 @@ import { BlockRange, ContractEvents, IndexedFilterValues } from '../types'; const TOPIC_LENGTH = 32; export const filterUtils = { - generateUUID(): string { - return uuid(); - }, - getFilter( - address: string, - eventName: ContractEvents, - indexFilterValues: IndexedFilterValues, - abi: Web3.ContractAbi, - blockRange?: BlockRange, - ): Web3.FilterObject { - const eventAbi = _.find(abi, { name: eventName }) as Web3.EventAbi; - const eventSignature = filterUtils.getEventSignatureFromAbiByName(eventAbi, eventName); - const topicForEventSignature = ethUtil.addHexPrefix(jsSHA3.keccak256(eventSignature)); - const topicsForIndexedArgs = filterUtils.getTopicsForIndexedArgs(eventAbi, indexFilterValues); - const topics = [topicForEventSignature, ...topicsForIndexedArgs]; - let filter: Web3.FilterObject = { - address, - topics, - }; - if (!_.isUndefined(blockRange)) { - filter = { - ...blockRange, - ...filter, - }; - } - return filter; - }, - getEventSignatureFromAbiByName(eventAbi: Web3.EventAbi, eventName: ContractEvents): string { - const types = _.map(eventAbi.inputs, 'type'); - const signature = `${eventAbi.name}(${types.join(',')})`; - return signature; - }, - getTopicsForIndexedArgs(abi: Web3.EventAbi, indexFilterValues: IndexedFilterValues): Array<string | null> { - const topics: Array<string | null> = []; - for (const eventInput of abi.inputs) { - if (!eventInput.indexed) { - continue; - } - if (_.isUndefined(indexFilterValues[eventInput.name])) { - // Null is a wildcard topic in a JSON-RPC call - topics.push(null); - } else { - const value = indexFilterValues[eventInput.name] as string; - const buffer = ethUtil.toBuffer(value); - const paddedBuffer = ethUtil.setLengthLeft(buffer, TOPIC_LENGTH); - const topic = ethUtil.bufferToHex(paddedBuffer); - topics.push(topic); - } - } - return topics; - }, - matchesFilter(log: Web3.LogEntry, filter: Web3.FilterObject): boolean { - if (!_.isUndefined(filter.address) && log.address !== filter.address) { - return false; - } - if (!_.isUndefined(filter.topics)) { - return filterUtils.matchesTopics(log.topics, filter.topics); - } - return true; - }, - matchesTopics(logTopics: string[], filterTopics: Array<string[] | string | null>): boolean { - const matchesTopic = _.zipWith(logTopics, filterTopics, filterUtils.matchesTopic.bind(filterUtils)); - const matchesTopics = _.every(matchesTopic); - return matchesTopics; - }, - matchesTopic(logTopic: string, filterTopic: string[] | string | null): boolean { - if (_.isArray(filterTopic)) { - return _.includes(filterTopic, logTopic); - } - if (_.isString(filterTopic)) { - return filterTopic === logTopic; - } - // null topic is a wildcard - return true; - }, + generateUUID(): string { + return uuid(); + }, + getFilter( + address: string, + eventName: ContractEvents, + indexFilterValues: IndexedFilterValues, + abi: Web3.ContractAbi, + blockRange?: BlockRange, + ): Web3.FilterObject { + const eventAbi = _.find(abi, { name: eventName }) as Web3.EventAbi; + const eventSignature = filterUtils.getEventSignatureFromAbiByName(eventAbi, eventName); + const topicForEventSignature = ethUtil.addHexPrefix(jsSHA3.keccak256(eventSignature)); + const topicsForIndexedArgs = filterUtils.getTopicsForIndexedArgs(eventAbi, indexFilterValues); + const topics = [topicForEventSignature, ...topicsForIndexedArgs]; + let filter: Web3.FilterObject = { + address, + topics, + }; + if (!_.isUndefined(blockRange)) { + filter = { + ...blockRange, + ...filter, + }; + } + return filter; + }, + getEventSignatureFromAbiByName(eventAbi: Web3.EventAbi, eventName: ContractEvents): string { + const types = _.map(eventAbi.inputs, 'type'); + const signature = `${eventAbi.name}(${types.join(',')})`; + return signature; + }, + getTopicsForIndexedArgs(abi: Web3.EventAbi, indexFilterValues: IndexedFilterValues): Array<string | null> { + const topics: Array<string | null> = []; + for (const eventInput of abi.inputs) { + if (!eventInput.indexed) { + continue; + } + if (_.isUndefined(indexFilterValues[eventInput.name])) { + // Null is a wildcard topic in a JSON-RPC call + topics.push(null); + } else { + const value = indexFilterValues[eventInput.name] as string; + const buffer = ethUtil.toBuffer(value); + const paddedBuffer = ethUtil.setLengthLeft(buffer, TOPIC_LENGTH); + const topic = ethUtil.bufferToHex(paddedBuffer); + topics.push(topic); + } + } + return topics; + }, + matchesFilter(log: Web3.LogEntry, filter: Web3.FilterObject): boolean { + if (!_.isUndefined(filter.address) && log.address !== filter.address) { + return false; + } + if (!_.isUndefined(filter.topics)) { + return filterUtils.matchesTopics(log.topics, filter.topics); + } + return true; + }, + matchesTopics(logTopics: string[], filterTopics: Array<string[] | string | null>): boolean { + const matchesTopic = _.zipWith(logTopics, filterTopics, filterUtils.matchesTopic.bind(filterUtils)); + const matchesTopics = _.every(matchesTopic); + return matchesTopics; + }, + matchesTopic(logTopic: string, filterTopic: string[] | string | null): boolean { + if (_.isArray(filterTopic)) { + return _.includes(filterTopic, logTopic); + } + if (_.isString(filterTopic)) { + return filterTopic === logTopic; + } + // null topic is a wildcard + return true; + }, }; diff --git a/packages/0x.js/src/utils/order_state_utils.ts b/packages/0x.js/src/utils/order_state_utils.ts index b7a55ff42..04d3b641e 100644 --- a/packages/0x.js/src/utils/order_state_utils.ts +++ b/packages/0x.js/src/utils/order_state_utils.ts @@ -7,138 +7,138 @@ import { RemainingFillableCalculator } from '../order_watcher/remaining_fillable import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allowance_lazy_store'; import { OrderFilledCancelledLazyStore } from '../stores/order_filled_cancelled_lazy_store'; import { - ExchangeContractErrs, - OrderRelevantState, - OrderState, - OrderStateInvalid, - OrderStateValid, - SignedOrder, + ExchangeContractErrs, + OrderRelevantState, + OrderState, + OrderStateInvalid, + OrderStateValid, + SignedOrder, } from '../types'; const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001; export class OrderStateUtils { - private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore; - private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore; - private static _validateIfOrderIsValid(signedOrder: SignedOrder, orderRelevantState: OrderRelevantState): void { - const unavailableTakerTokenAmount = orderRelevantState.cancelledTakerTokenAmount.add( - orderRelevantState.filledTakerTokenAmount, - ); - const availableTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount); - if (availableTakerTokenAmount.eq(0)) { - throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero); - } + private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore; + private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore; + private static _validateIfOrderIsValid(signedOrder: SignedOrder, orderRelevantState: OrderRelevantState): void { + const unavailableTakerTokenAmount = orderRelevantState.cancelledTakerTokenAmount.add( + orderRelevantState.filledTakerTokenAmount, + ); + const availableTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount); + if (availableTakerTokenAmount.eq(0)) { + throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero); + } - if (orderRelevantState.makerBalance.eq(0)) { - throw new Error(ExchangeContractErrs.InsufficientMakerBalance); - } - if (orderRelevantState.makerProxyAllowance.eq(0)) { - throw new Error(ExchangeContractErrs.InsufficientMakerAllowance); - } - if (!signedOrder.makerFee.eq(0)) { - if (orderRelevantState.makerFeeBalance.eq(0)) { - throw new Error(ExchangeContractErrs.InsufficientMakerFeeBalance); - } - if (orderRelevantState.makerFeeProxyAllowance.eq(0)) { - throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance); - } - } - const minFillableTakerTokenAmountWithinNoRoundingErrorRange = signedOrder.takerTokenAmount - .dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR) - .dividedBy(signedOrder.makerTokenAmount); - if ( - orderRelevantState.remainingFillableTakerTokenAmount.lessThan( - minFillableTakerTokenAmountWithinNoRoundingErrorRange, - ) - ) { - throw new Error(ExchangeContractErrs.OrderFillRoundingError); - } - } - constructor( - balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore, - orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore, - ) { - this._balanceAndProxyAllowanceLazyStore = balanceAndProxyAllowanceLazyStore; - this._orderFilledCancelledLazyStore = orderFilledCancelledLazyStore; - } - public async getOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> { - const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - try { - OrderStateUtils._validateIfOrderIsValid(signedOrder, orderRelevantState); - const orderState: OrderStateValid = { - isValid: true, - orderHash, - orderRelevantState, - }; - return orderState; - } catch (err) { - const orderState: OrderStateInvalid = { - isValid: false, - orderHash, - error: err.message, - }; - return orderState; - } - } - public async getOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> { - // HACK: We access the private property here but otherwise the interface will be less nice. - // If we pass it from the instantiator - there is no opportunity to get it there - // because JS doesn't support async constructors. - // Moreover - it's cached under the hood so it's equivalent to an async constructor. - const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper; - const zrxTokenAddress = exchange.getZRXTokenAddress(); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - const makerBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync( - signedOrder.makerTokenAddress, - signedOrder.maker, - ); - const makerProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync( - signedOrder.makerTokenAddress, - signedOrder.maker, - ); - const makerFeeBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync( - zrxTokenAddress, - signedOrder.maker, - ); - const makerFeeProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync( - zrxTokenAddress, - signedOrder.maker, - ); - const filledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getFilledTakerAmountAsync(orderHash); - const cancelledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getCancelledTakerAmountAsync( - orderHash, - ); - const unavailableTakerTokenAmount = await exchange.getUnavailableTakerAmountAsync(orderHash); - const totalMakerTokenAmount = signedOrder.makerTokenAmount; - const totalTakerTokenAmount = signedOrder.takerTokenAmount; - const remainingTakerTokenAmount = totalTakerTokenAmount.minus(unavailableTakerTokenAmount); - const remainingMakerTokenAmount = remainingTakerTokenAmount - .times(totalMakerTokenAmount) - .dividedToIntegerBy(totalTakerTokenAmount); - const transferrableMakerTokenAmount = BigNumber.min([makerProxyAllowance, makerBalance]); - const transferrableFeeTokenAmount = BigNumber.min([makerFeeProxyAllowance, makerFeeBalance]); + if (orderRelevantState.makerBalance.eq(0)) { + throw new Error(ExchangeContractErrs.InsufficientMakerBalance); + } + if (orderRelevantState.makerProxyAllowance.eq(0)) { + throw new Error(ExchangeContractErrs.InsufficientMakerAllowance); + } + if (!signedOrder.makerFee.eq(0)) { + if (orderRelevantState.makerFeeBalance.eq(0)) { + throw new Error(ExchangeContractErrs.InsufficientMakerFeeBalance); + } + if (orderRelevantState.makerFeeProxyAllowance.eq(0)) { + throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance); + } + } + const minFillableTakerTokenAmountWithinNoRoundingErrorRange = signedOrder.takerTokenAmount + .dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR) + .dividedBy(signedOrder.makerTokenAmount); + if ( + orderRelevantState.remainingFillableTakerTokenAmount.lessThan( + minFillableTakerTokenAmountWithinNoRoundingErrorRange, + ) + ) { + throw new Error(ExchangeContractErrs.OrderFillRoundingError); + } + } + constructor( + balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore, + orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore, + ) { + this._balanceAndProxyAllowanceLazyStore = balanceAndProxyAllowanceLazyStore; + this._orderFilledCancelledLazyStore = orderFilledCancelledLazyStore; + } + public async getOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> { + const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + try { + OrderStateUtils._validateIfOrderIsValid(signedOrder, orderRelevantState); + const orderState: OrderStateValid = { + isValid: true, + orderHash, + orderRelevantState, + }; + return orderState; + } catch (err) { + const orderState: OrderStateInvalid = { + isValid: false, + orderHash, + error: err.message, + }; + return orderState; + } + } + public async getOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> { + // HACK: We access the private property here but otherwise the interface will be less nice. + // If we pass it from the instantiator - there is no opportunity to get it there + // because JS doesn't support async constructors. + // Moreover - it's cached under the hood so it's equivalent to an async constructor. + const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper; + const zrxTokenAddress = exchange.getZRXTokenAddress(); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + const makerBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync( + signedOrder.makerTokenAddress, + signedOrder.maker, + ); + const makerProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync( + signedOrder.makerTokenAddress, + signedOrder.maker, + ); + const makerFeeBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync( + zrxTokenAddress, + signedOrder.maker, + ); + const makerFeeProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync( + zrxTokenAddress, + signedOrder.maker, + ); + const filledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getFilledTakerAmountAsync(orderHash); + const cancelledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getCancelledTakerAmountAsync( + orderHash, + ); + const unavailableTakerTokenAmount = await exchange.getUnavailableTakerAmountAsync(orderHash); + const totalMakerTokenAmount = signedOrder.makerTokenAmount; + const totalTakerTokenAmount = signedOrder.takerTokenAmount; + const remainingTakerTokenAmount = totalTakerTokenAmount.minus(unavailableTakerTokenAmount); + const remainingMakerTokenAmount = remainingTakerTokenAmount + .times(totalMakerTokenAmount) + .dividedToIntegerBy(totalTakerTokenAmount); + const transferrableMakerTokenAmount = BigNumber.min([makerProxyAllowance, makerBalance]); + const transferrableFeeTokenAmount = BigNumber.min([makerFeeProxyAllowance, makerFeeBalance]); - const isMakerTokenZRX = signedOrder.makerTokenAddress === zrxTokenAddress; - const remainingFillableCalculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableFeeTokenAmount, - remainingMakerTokenAmount, - ); - const remainingFillableMakerTokenAmount = remainingFillableCalculator.computeRemainingMakerFillable(); - const remainingFillableTakerTokenAmount = remainingFillableCalculator.computeRemainingTakerFillable(); - const orderRelevantState = { - makerBalance, - makerProxyAllowance, - makerFeeBalance, - makerFeeProxyAllowance, - filledTakerTokenAmount, - cancelledTakerTokenAmount, - remainingFillableMakerTokenAmount, - remainingFillableTakerTokenAmount, - }; - return orderRelevantState; - } + const isMakerTokenZRX = signedOrder.makerTokenAddress === zrxTokenAddress; + const remainingFillableCalculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableFeeTokenAmount, + remainingMakerTokenAmount, + ); + const remainingFillableMakerTokenAmount = remainingFillableCalculator.computeRemainingMakerFillable(); + const remainingFillableTakerTokenAmount = remainingFillableCalculator.computeRemainingTakerFillable(); + const orderRelevantState = { + makerBalance, + makerProxyAllowance, + makerFeeBalance, + makerFeeProxyAllowance, + filledTakerTokenAmount, + cancelledTakerTokenAmount, + remainingFillableMakerTokenAmount, + remainingFillableTakerTokenAmount, + }; + return orderRelevantState; + } } diff --git a/packages/0x.js/src/utils/order_validation_utils.ts b/packages/0x.js/src/utils/order_validation_utils.ts index 917d414c8..8ff22266f 100644 --- a/packages/0x.js/src/utils/order_validation_utils.ts +++ b/packages/0x.js/src/utils/order_validation_utils.ts @@ -10,207 +10,207 @@ import { utils } from '../utils/utils'; import { ExchangeTransferSimulator } from './exchange_transfer_simulator'; export class OrderValidationUtils { - private _exchangeWrapper: ExchangeWrapper; - public static validateCancelOrderThrowIfInvalid( - order: Order, - cancelTakerTokenAmount: BigNumber, - unavailableTakerTokenAmount: BigNumber, - ): void { - if (cancelTakerTokenAmount.eq(0)) { - throw new Error(ExchangeContractErrs.OrderCancelAmountZero); - } - if (order.takerTokenAmount.eq(unavailableTakerTokenAmount)) { - throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled); - } - const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec(); - if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) { - throw new Error(ExchangeContractErrs.OrderCancelExpired); - } - } - public static async validateFillOrderBalancesAllowancesThrowIfInvalidAsync( - exchangeTradeEmulator: ExchangeTransferSimulator, - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - senderAddress: string, - zrxTokenAddress: string, - ): Promise<void> { - const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount( - fillTakerTokenAmount, - signedOrder.takerTokenAmount, - signedOrder.makerTokenAmount, - ); - await exchangeTradeEmulator.transferFromAsync( - signedOrder.makerTokenAddress, - signedOrder.maker, - senderAddress, - fillMakerTokenAmount, - TradeSide.Maker, - TransferType.Trade, - ); - await exchangeTradeEmulator.transferFromAsync( - signedOrder.takerTokenAddress, - senderAddress, - signedOrder.maker, - fillTakerTokenAmount, - TradeSide.Taker, - TransferType.Trade, - ); - const makerFeeAmount = OrderValidationUtils._getPartialAmount( - fillTakerTokenAmount, - signedOrder.takerTokenAmount, - signedOrder.makerFee, - ); - await exchangeTradeEmulator.transferFromAsync( - zrxTokenAddress, - signedOrder.maker, - signedOrder.feeRecipient, - makerFeeAmount, - TradeSide.Maker, - TransferType.Fee, - ); - const takerFeeAmount = OrderValidationUtils._getPartialAmount( - fillTakerTokenAmount, - signedOrder.takerTokenAmount, - signedOrder.takerFee, - ); - await exchangeTradeEmulator.transferFromAsync( - zrxTokenAddress, - senderAddress, - signedOrder.feeRecipient, - takerFeeAmount, - TradeSide.Taker, - TransferType.Fee, - ); - } - private static _validateRemainingFillAmountNotZeroOrThrow( - takerTokenAmount: BigNumber, - unavailableTakerTokenAmount: BigNumber, - ) { - if (takerTokenAmount.eq(unavailableTakerTokenAmount)) { - throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero); - } - } - private static _validateOrderNotExpiredOrThrow(expirationUnixTimestampSec: BigNumber) { - const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec(); - if (expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) { - throw new Error(ExchangeContractErrs.OrderFillExpired); - } - } - private static _getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { - const fillMakerTokenAmount = numerator - .mul(target) - .div(denominator) - .round(0); - return fillMakerTokenAmount; - } - constructor(exchangeWrapper: ExchangeWrapper) { - this._exchangeWrapper = exchangeWrapper; - } - public async validateOrderFillableOrThrowAsync( - exchangeTradeEmulator: ExchangeTransferSimulator, - signedOrder: SignedOrder, - zrxTokenAddress: string, - expectedFillTakerTokenAmount?: BigNumber, - ): Promise<void> { - const orderHash = utils.getOrderHashHex(signedOrder); - const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash); - OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow( - signedOrder.takerTokenAmount, - unavailableTakerTokenAmount, - ); - OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec); - let fillTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount); - if (!_.isUndefined(expectedFillTakerTokenAmount)) { - fillTakerTokenAmount = expectedFillTakerTokenAmount; - } - const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount( - fillTakerTokenAmount, - signedOrder.takerTokenAmount, - signedOrder.makerTokenAmount, - ); - await exchangeTradeEmulator.transferFromAsync( - signedOrder.makerTokenAddress, - signedOrder.maker, - signedOrder.taker, - fillMakerTokenAmount, - TradeSide.Maker, - TransferType.Trade, - ); - const makerFeeAmount = OrderValidationUtils._getPartialAmount( - fillTakerTokenAmount, - signedOrder.takerTokenAmount, - signedOrder.makerFee, - ); - await exchangeTradeEmulator.transferFromAsync( - zrxTokenAddress, - signedOrder.maker, - signedOrder.feeRecipient, - makerFeeAmount, - TradeSide.Maker, - TransferType.Fee, - ); - } - public async validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator: ExchangeTransferSimulator, - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - zrxTokenAddress: string, - ): Promise<BigNumber> { - if (fillTakerTokenAmount.eq(0)) { - throw new Error(ExchangeContractErrs.OrderFillAmountZero); - } - const orderHash = utils.getOrderHashHex(signedOrder); - if (!ZeroEx.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker)) { - throw new Error(ZeroExError.InvalidSignature); - } - const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash); - OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow( - signedOrder.takerTokenAmount, - unavailableTakerTokenAmount, - ); - if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== takerAddress) { - throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker); - } - OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec); - const remainingTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount); - const filledTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerTokenAmount) - ? remainingTakerTokenAmount - : fillTakerTokenAmount; - await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - filledTakerTokenAmount, - takerAddress, - zrxTokenAddress, - ); + private _exchangeWrapper: ExchangeWrapper; + public static validateCancelOrderThrowIfInvalid( + order: Order, + cancelTakerTokenAmount: BigNumber, + unavailableTakerTokenAmount: BigNumber, + ): void { + if (cancelTakerTokenAmount.eq(0)) { + throw new Error(ExchangeContractErrs.OrderCancelAmountZero); + } + if (order.takerTokenAmount.eq(unavailableTakerTokenAmount)) { + throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled); + } + const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec(); + if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) { + throw new Error(ExchangeContractErrs.OrderCancelExpired); + } + } + public static async validateFillOrderBalancesAllowancesThrowIfInvalidAsync( + exchangeTradeEmulator: ExchangeTransferSimulator, + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + senderAddress: string, + zrxTokenAddress: string, + ): Promise<void> { + const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount( + fillTakerTokenAmount, + signedOrder.takerTokenAmount, + signedOrder.makerTokenAmount, + ); + await exchangeTradeEmulator.transferFromAsync( + signedOrder.makerTokenAddress, + signedOrder.maker, + senderAddress, + fillMakerTokenAmount, + TradeSide.Maker, + TransferType.Trade, + ); + await exchangeTradeEmulator.transferFromAsync( + signedOrder.takerTokenAddress, + senderAddress, + signedOrder.maker, + fillTakerTokenAmount, + TradeSide.Taker, + TransferType.Trade, + ); + const makerFeeAmount = OrderValidationUtils._getPartialAmount( + fillTakerTokenAmount, + signedOrder.takerTokenAmount, + signedOrder.makerFee, + ); + await exchangeTradeEmulator.transferFromAsync( + zrxTokenAddress, + signedOrder.maker, + signedOrder.feeRecipient, + makerFeeAmount, + TradeSide.Maker, + TransferType.Fee, + ); + const takerFeeAmount = OrderValidationUtils._getPartialAmount( + fillTakerTokenAmount, + signedOrder.takerTokenAmount, + signedOrder.takerFee, + ); + await exchangeTradeEmulator.transferFromAsync( + zrxTokenAddress, + senderAddress, + signedOrder.feeRecipient, + takerFeeAmount, + TradeSide.Taker, + TransferType.Fee, + ); + } + private static _validateRemainingFillAmountNotZeroOrThrow( + takerTokenAmount: BigNumber, + unavailableTakerTokenAmount: BigNumber, + ) { + if (takerTokenAmount.eq(unavailableTakerTokenAmount)) { + throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero); + } + } + private static _validateOrderNotExpiredOrThrow(expirationUnixTimestampSec: BigNumber) { + const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec(); + if (expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) { + throw new Error(ExchangeContractErrs.OrderFillExpired); + } + } + private static _getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { + const fillMakerTokenAmount = numerator + .mul(target) + .div(denominator) + .round(0); + return fillMakerTokenAmount; + } + constructor(exchangeWrapper: ExchangeWrapper) { + this._exchangeWrapper = exchangeWrapper; + } + public async validateOrderFillableOrThrowAsync( + exchangeTradeEmulator: ExchangeTransferSimulator, + signedOrder: SignedOrder, + zrxTokenAddress: string, + expectedFillTakerTokenAmount?: BigNumber, + ): Promise<void> { + const orderHash = utils.getOrderHashHex(signedOrder); + const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash); + OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow( + signedOrder.takerTokenAmount, + unavailableTakerTokenAmount, + ); + OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec); + let fillTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount); + if (!_.isUndefined(expectedFillTakerTokenAmount)) { + fillTakerTokenAmount = expectedFillTakerTokenAmount; + } + const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount( + fillTakerTokenAmount, + signedOrder.takerTokenAmount, + signedOrder.makerTokenAmount, + ); + await exchangeTradeEmulator.transferFromAsync( + signedOrder.makerTokenAddress, + signedOrder.maker, + signedOrder.taker, + fillMakerTokenAmount, + TradeSide.Maker, + TransferType.Trade, + ); + const makerFeeAmount = OrderValidationUtils._getPartialAmount( + fillTakerTokenAmount, + signedOrder.takerTokenAmount, + signedOrder.makerFee, + ); + await exchangeTradeEmulator.transferFromAsync( + zrxTokenAddress, + signedOrder.maker, + signedOrder.feeRecipient, + makerFeeAmount, + TradeSide.Maker, + TransferType.Fee, + ); + } + public async validateFillOrderThrowIfInvalidAsync( + exchangeTradeEmulator: ExchangeTransferSimulator, + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + takerAddress: string, + zrxTokenAddress: string, + ): Promise<BigNumber> { + if (fillTakerTokenAmount.eq(0)) { + throw new Error(ExchangeContractErrs.OrderFillAmountZero); + } + const orderHash = utils.getOrderHashHex(signedOrder); + if (!ZeroEx.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker)) { + throw new Error(ZeroExError.InvalidSignature); + } + const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash); + OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow( + signedOrder.takerTokenAmount, + unavailableTakerTokenAmount, + ); + if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== takerAddress) { + throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker); + } + OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec); + const remainingTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount); + const filledTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerTokenAmount) + ? remainingTakerTokenAmount + : fillTakerTokenAmount; + await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( + exchangeTradeEmulator, + signedOrder, + filledTakerTokenAmount, + takerAddress, + zrxTokenAddress, + ); - const wouldRoundingErrorOccur = await this._exchangeWrapper.isRoundingErrorAsync( - filledTakerTokenAmount, - signedOrder.takerTokenAmount, - signedOrder.makerTokenAmount, - ); - if (wouldRoundingErrorOccur) { - throw new Error(ExchangeContractErrs.OrderFillRoundingError); - } - return filledTakerTokenAmount; - } - public async validateFillOrKillOrderThrowIfInvalidAsync( - exchangeTradeEmulator: ExchangeTransferSimulator, - signedOrder: SignedOrder, - fillTakerTokenAmount: BigNumber, - takerAddress: string, - zrxTokenAddress: string, - ): Promise<void> { - const filledTakerTokenAmount = await this.validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - takerAddress, - zrxTokenAddress, - ); - if (filledTakerTokenAmount !== fillTakerTokenAmount) { - throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount); - } - } + const wouldRoundingErrorOccur = await this._exchangeWrapper.isRoundingErrorAsync( + filledTakerTokenAmount, + signedOrder.takerTokenAmount, + signedOrder.makerTokenAmount, + ); + if (wouldRoundingErrorOccur) { + throw new Error(ExchangeContractErrs.OrderFillRoundingError); + } + return filledTakerTokenAmount; + } + public async validateFillOrKillOrderThrowIfInvalidAsync( + exchangeTradeEmulator: ExchangeTransferSimulator, + signedOrder: SignedOrder, + fillTakerTokenAmount: BigNumber, + takerAddress: string, + zrxTokenAddress: string, + ): Promise<void> { + const filledTakerTokenAmount = await this.validateFillOrderThrowIfInvalidAsync( + exchangeTradeEmulator, + signedOrder, + fillTakerTokenAmount, + takerAddress, + zrxTokenAddress, + ); + if (filledTakerTokenAmount !== fillTakerTokenAmount) { + throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount); + } + } } diff --git a/packages/0x.js/src/utils/signature_utils.ts b/packages/0x.js/src/utils/signature_utils.ts index b0f1d61ef..19b5c76c7 100644 --- a/packages/0x.js/src/utils/signature_utils.ts +++ b/packages/0x.js/src/utils/signature_utils.ts @@ -3,44 +3,44 @@ import * as ethUtil from 'ethereumjs-util'; import { ECSignature } from '../types'; export const signatureUtils = { - isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean { - const dataBuff = ethUtil.toBuffer(data); - const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff); - try { - const pubKey = ethUtil.ecrecover( - msgHashBuff, - signature.v, - ethUtil.toBuffer(signature.r), - ethUtil.toBuffer(signature.s), - ); - const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey)); - return retrievedAddress === signerAddress; - } catch (err) { - return false; - } - }, - parseSignatureHexAsVRS(signatureHex: string): ECSignature { - const signatureBuffer = ethUtil.toBuffer(signatureHex); - let v = signatureBuffer[0]; - if (v < 27) { - v += 27; - } - const r = signatureBuffer.slice(1, 33); - const s = signatureBuffer.slice(33, 65); - const ecSignature: ECSignature = { - v, - r: ethUtil.bufferToHex(r), - s: ethUtil.bufferToHex(s), - }; - return ecSignature; - }, - parseSignatureHexAsRSV(signatureHex: string): ECSignature { - const { v, r, s } = ethUtil.fromRpcSig(signatureHex); - const ecSignature: ECSignature = { - v, - r: ethUtil.bufferToHex(r), - s: ethUtil.bufferToHex(s), - }; - return ecSignature; - }, + isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean { + const dataBuff = ethUtil.toBuffer(data); + const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff); + try { + const pubKey = ethUtil.ecrecover( + msgHashBuff, + signature.v, + ethUtil.toBuffer(signature.r), + ethUtil.toBuffer(signature.s), + ); + const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey)); + return retrievedAddress === signerAddress; + } catch (err) { + return false; + } + }, + parseSignatureHexAsVRS(signatureHex: string): ECSignature { + const signatureBuffer = ethUtil.toBuffer(signatureHex); + let v = signatureBuffer[0]; + if (v < 27) { + v += 27; + } + const r = signatureBuffer.slice(1, 33); + const s = signatureBuffer.slice(33, 65); + const ecSignature: ECSignature = { + v, + r: ethUtil.bufferToHex(r), + s: ethUtil.bufferToHex(s), + }; + return ecSignature; + }, + parseSignatureHexAsRSV(signatureHex: string): ECSignature { + const { v, r, s } = ethUtil.fromRpcSig(signatureHex); + const ecSignature: ECSignature = { + v, + r: ethUtil.bufferToHex(r), + s: ethUtil.bufferToHex(s), + }; + return ecSignature; + }, }; diff --git a/packages/0x.js/src/utils/utils.ts b/packages/0x.js/src/utils/utils.ts index 42cf5d956..ae07941ef 100644 --- a/packages/0x.js/src/utils/utils.ts +++ b/packages/0x.js/src/utils/utils.ts @@ -7,62 +7,62 @@ import * as _ from 'lodash'; import { Order, SignedOrder, SolidityTypes } from '../types'; export const utils = { - /** - * Converts BigNumber instance to BN - * The only reason we convert to BN is to remain compatible with `ethABI. soliditySHA3` that - * expects values of Solidity type `uint` to be passed as type `BN`. - * We do not use BN anywhere else in the codebase. - */ - bigNumberToBN(value: BigNumber) { - return new BN(value.toString(), 10); - }, - consoleLog(message: string): void { - // tslint:disable-next-line: no-console - console.log(message); - }, - spawnSwitchErr(name: string, value: any): Error { - return new Error(`Unexpected switch value: ${value} encountered for ${name}`); - }, - getOrderHashHex(order: Order | SignedOrder): string { - const orderParts = [ - { value: order.exchangeContractAddress, type: SolidityTypes.Address }, - { value: order.maker, type: SolidityTypes.Address }, - { value: order.taker, type: SolidityTypes.Address }, - { value: order.makerTokenAddress, type: SolidityTypes.Address }, - { value: order.takerTokenAddress, type: SolidityTypes.Address }, - { value: order.feeRecipient, type: SolidityTypes.Address }, - { - value: utils.bigNumberToBN(order.makerTokenAmount), - type: SolidityTypes.Uint256, - }, - { - value: utils.bigNumberToBN(order.takerTokenAmount), - type: SolidityTypes.Uint256, - }, - { - value: utils.bigNumberToBN(order.makerFee), - type: SolidityTypes.Uint256, - }, - { - value: utils.bigNumberToBN(order.takerFee), - type: SolidityTypes.Uint256, - }, - { - value: utils.bigNumberToBN(order.expirationUnixTimestampSec), - type: SolidityTypes.Uint256, - }, - { value: utils.bigNumberToBN(order.salt), type: SolidityTypes.Uint256 }, - ]; - const types = _.map(orderParts, o => o.type); - const values = _.map(orderParts, o => o.value); - const hashBuff = ethABI.soliditySHA3(types, values); - const hashHex = ethUtil.bufferToHex(hashBuff); - return hashHex; - }, - getCurrentUnixTimestampSec(): BigNumber { - return new BigNumber(Date.now() / 1000).round(); - }, - getCurrentUnixTimestampMs(): BigNumber { - return new BigNumber(Date.now()); - }, + /** + * Converts BigNumber instance to BN + * The only reason we convert to BN is to remain compatible with `ethABI. soliditySHA3` that + * expects values of Solidity type `uint` to be passed as type `BN`. + * We do not use BN anywhere else in the codebase. + */ + bigNumberToBN(value: BigNumber) { + return new BN(value.toString(), 10); + }, + consoleLog(message: string): void { + // tslint:disable-next-line: no-console + console.log(message); + }, + spawnSwitchErr(name: string, value: any): Error { + return new Error(`Unexpected switch value: ${value} encountered for ${name}`); + }, + getOrderHashHex(order: Order | SignedOrder): string { + const orderParts = [ + { value: order.exchangeContractAddress, type: SolidityTypes.Address }, + { value: order.maker, type: SolidityTypes.Address }, + { value: order.taker, type: SolidityTypes.Address }, + { value: order.makerTokenAddress, type: SolidityTypes.Address }, + { value: order.takerTokenAddress, type: SolidityTypes.Address }, + { value: order.feeRecipient, type: SolidityTypes.Address }, + { + value: utils.bigNumberToBN(order.makerTokenAmount), + type: SolidityTypes.Uint256, + }, + { + value: utils.bigNumberToBN(order.takerTokenAmount), + type: SolidityTypes.Uint256, + }, + { + value: utils.bigNumberToBN(order.makerFee), + type: SolidityTypes.Uint256, + }, + { + value: utils.bigNumberToBN(order.takerFee), + type: SolidityTypes.Uint256, + }, + { + value: utils.bigNumberToBN(order.expirationUnixTimestampSec), + type: SolidityTypes.Uint256, + }, + { value: utils.bigNumberToBN(order.salt), type: SolidityTypes.Uint256 }, + ]; + const types = _.map(orderParts, o => o.type); + const values = _.map(orderParts, o => o.value); + const hashBuff = ethABI.soliditySHA3(types, values); + const hashHex = ethUtil.bufferToHex(hashBuff); + return hashHex; + }, + getCurrentUnixTimestampSec(): BigNumber { + return new BigNumber(Date.now() / 1000).round(); + }, + getCurrentUnixTimestampMs(): BigNumber { + return new BigNumber(Date.now()); + }, }; diff --git a/packages/0x.js/test/0x.js_test.ts b/packages/0x.js/test/0x.js_test.ts index 927fe20be..5ece5192f 100644 --- a/packages/0x.js/test/0x.js_test.ts +++ b/packages/0x.js/test/0x.js_test.ts @@ -19,278 +19,278 @@ const expect = chai.expect; const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false; describe('ZeroEx library', () => { - const web3 = web3Factory.create(); - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - const zeroEx = new ZeroEx(web3.currentProvider, config); - describe('#setProvider', () => { - it('overrides provider in nested web3s and invalidates contractInstances', async () => { - // Instantiate the contract instances with the current provider - await (zeroEx.exchange as any)._getExchangeContractAsync(); - await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync(); - expect((zeroEx.exchange as any)._exchangeContractIfExists).to.not.be.undefined(); - expect((zeroEx.tokenRegistry as any)._tokenRegistryContractIfExists).to.not.be.undefined(); + const web3 = web3Factory.create(); + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + const zeroEx = new ZeroEx(web3.currentProvider, config); + describe('#setProvider', () => { + it('overrides provider in nested web3s and invalidates contractInstances', async () => { + // Instantiate the contract instances with the current provider + await (zeroEx.exchange as any)._getExchangeContractAsync(); + await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync(); + expect((zeroEx.exchange as any)._exchangeContractIfExists).to.not.be.undefined(); + expect((zeroEx.tokenRegistry as any)._tokenRegistryContractIfExists).to.not.be.undefined(); - const newProvider = web3Factory.getRpcProvider(); - // Add property to newProvider so that we can differentiate it from old provider - (newProvider as any).zeroExTestId = 1; - zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); + const newProvider = web3Factory.getRpcProvider(); + // Add property to newProvider so that we can differentiate it from old provider + (newProvider as any).zeroExTestId = 1; + zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); - // Check that contractInstances with old provider are removed after provider update - expect((zeroEx.exchange as any)._exchangeContractIfExists).to.be.undefined(); - expect((zeroEx.tokenRegistry as any)._tokenRegistryContractIfExists).to.be.undefined(); + // Check that contractInstances with old provider are removed after provider update + expect((zeroEx.exchange as any)._exchangeContractIfExists).to.be.undefined(); + expect((zeroEx.tokenRegistry as any)._tokenRegistryContractIfExists).to.be.undefined(); - // Check that all nested web3 wrapper instances return the updated provider - const nestedWeb3WrapperProvider = (zeroEx as any)._web3Wrapper.getCurrentProvider(); - expect(nestedWeb3WrapperProvider.zeroExTestId).to.be.a('number'); - const exchangeWeb3WrapperProvider = (zeroEx.exchange as any)._web3Wrapper.getCurrentProvider(); - expect(exchangeWeb3WrapperProvider.zeroExTestId).to.be.a('number'); - const tokenRegistryWeb3WrapperProvider = (zeroEx.tokenRegistry as any)._web3Wrapper.getCurrentProvider(); - expect(tokenRegistryWeb3WrapperProvider.zeroExTestId).to.be.a('number'); - }); - }); - describe('#isValidSignature', () => { - // The Exchange smart contract `isValidSignature` method only validates orderHashes and assumes - // the length of the data is exactly 32 bytes. Thus for these tests, we use data of this size. - const dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; - const signature = { - v: 27, - r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', - s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', - }; - const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; - it("should return false if the data doesn't pertain to the signature & address", async () => { - expect(ZeroEx.isValidSignature('0x0', signature, address)).to.be.false(); - return expect( - (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync('0x0', signature, address), - ).to.become(false); - }); - it("should return false if the address doesn't pertain to the signature & data", async () => { - const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42'; - expect(ZeroEx.isValidSignature(dataHex, signature, validUnrelatedAddress)).to.be.false(); - return expect( - (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync( - dataHex, - signature, - validUnrelatedAddress, - ), - ).to.become(false); - }); - it("should return false if the signature doesn't pertain to the dataHex & address", async () => { - const wrongSignature = _.assign({}, signature, { v: 28 }); - expect(ZeroEx.isValidSignature(dataHex, wrongSignature, address)).to.be.false(); - return expect( - (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, wrongSignature, address), - ).to.become(false); - }); - it('should return true if the signature does pertain to the dataHex & address', async () => { - const isValidSignatureLocal = ZeroEx.isValidSignature(dataHex, signature, address); - expect(isValidSignatureLocal).to.be.true(); - return expect( - (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, signature, address), - ).to.become(true); - }); - }); - describe('#generateSalt', () => { - it('generates different salts', () => { - const equal = ZeroEx.generatePseudoRandomSalt().eq(ZeroEx.generatePseudoRandomSalt()); - expect(equal).to.be.false(); - }); - it('generates salt in range [0..2^256)', () => { - const salt = ZeroEx.generatePseudoRandomSalt(); - expect(salt.greaterThanOrEqualTo(0)).to.be.true(); - const twoPow256 = new BigNumber(2).pow(256); - expect(salt.lessThan(twoPow256)).to.be.true(); - }); - }); - describe('#isValidOrderHash', () => { - it('returns false if the value is not a hex string', () => { - const isValid = ZeroEx.isValidOrderHash('not a hex'); - expect(isValid).to.be.false(); - }); - it('returns false if the length is wrong', () => { - const isValid = ZeroEx.isValidOrderHash('0xdeadbeef'); - expect(isValid).to.be.false(); - }); - it('returns true if order hash is correct', () => { - const isValid = ZeroEx.isValidOrderHash('0x' + Array(65).join('0')); - expect(isValid).to.be.true(); - }); - }); - describe('#toUnitAmount', () => { - it('should throw if invalid baseUnit amount supplied as argument', () => { - const invalidBaseUnitAmount = new BigNumber(1000000000.4); - const decimals = 6; - expect(() => ZeroEx.toUnitAmount(invalidBaseUnitAmount, decimals)).to.throw( - 'amount should be in baseUnits (no decimals), found value: 1000000000.4', - ); - }); - it('Should return the expected unit amount for the decimals passed in', () => { - const baseUnitAmount = new BigNumber(1000000000); - const decimals = 6; - const unitAmount = ZeroEx.toUnitAmount(baseUnitAmount, decimals); - const expectedUnitAmount = new BigNumber(1000); - expect(unitAmount).to.be.bignumber.equal(expectedUnitAmount); - }); - }); - describe('#toBaseUnitAmount', () => { - it('Should return the expected base unit amount for the decimals passed in', () => { - const unitAmount = new BigNumber(1000); - const decimals = 6; - const baseUnitAmount = ZeroEx.toBaseUnitAmount(unitAmount, decimals); - const expectedUnitAmount = new BigNumber(1000000000); - expect(baseUnitAmount).to.be.bignumber.equal(expectedUnitAmount); - }); - it('should throw if unitAmount has more decimals then specified as the max decimal precision', () => { - const unitAmount = new BigNumber(0.823091); - const decimals = 5; - expect(() => ZeroEx.toBaseUnitAmount(unitAmount, decimals)).to.throw( - 'Invalid unit amount: 0.823091 - Too many decimal places', - ); - }); - }); - describe('#getOrderHashHex', () => { - const expectedOrderHash = '0x39da987067a3c9e5f1617694f1301326ba8c8b0498ebef5df4863bed394e3c83'; - const fakeExchangeContractAddress = '0xb69e673309512a9d726f87304c6984054f87a93b'; - const order: Order = { - maker: constants.NULL_ADDRESS, - taker: constants.NULL_ADDRESS, - feeRecipient: constants.NULL_ADDRESS, - makerTokenAddress: constants.NULL_ADDRESS, - takerTokenAddress: constants.NULL_ADDRESS, - exchangeContractAddress: fakeExchangeContractAddress, - salt: new BigNumber(0), - makerFee: new BigNumber(0), - takerFee: new BigNumber(0), - makerTokenAmount: new BigNumber(0), - takerTokenAmount: new BigNumber(0), - expirationUnixTimestampSec: new BigNumber(0), - }; - it('calculates the order hash', async () => { - const orderHash = ZeroEx.getOrderHashHex(order); - expect(orderHash).to.be.equal(expectedOrderHash); - }); - it('throws a readable error message if taker format is invalid', async () => { - const orderWithInvalidtakerFormat = { - ...order, - taker: (null as any) as string, - }; - const expectedErrorMessage = - 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS'; - expect(() => ZeroEx.getOrderHashHex(orderWithInvalidtakerFormat)).to.throw(expectedErrorMessage); - }); - }); - describe('#signOrderHashAsync', () => { - let stubs: Sinon.SinonStub[] = []; - let makerAddress: string; - before(async () => { - const availableAddreses = await zeroEx.getAvailableAddressesAsync(); - makerAddress = availableAddreses[0]; - }); - afterEach(() => { - // clean up any stubs after the test has completed - _.each(stubs, s => s.restore()); - stubs = []; - }); - it('Should return the correct ECSignature', async () => { - const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; - const expectedECSignature = { - v: 27, - r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', - s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', - }; - const ecSignature = await zeroEx.signOrderHashAsync( - orderHash, - makerAddress, - SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, - ); - expect(ecSignature).to.deep.equal(expectedECSignature); - }); - it('should return the correct ECSignature for signatureHex concatenated as R + S + V', async () => { - const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004'; - const signature = - '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb021b'; - const expectedECSignature = { - v: 27, - r: '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3', - s: '0x050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb02', - }; - stubs = [ - Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync').returns(Promise.resolve(signature)), - Sinon.stub(ZeroEx, 'isValidSignature').returns(true), - ]; + // Check that all nested web3 wrapper instances return the updated provider + const nestedWeb3WrapperProvider = (zeroEx as any)._web3Wrapper.getCurrentProvider(); + expect(nestedWeb3WrapperProvider.zeroExTestId).to.be.a('number'); + const exchangeWeb3WrapperProvider = (zeroEx.exchange as any)._web3Wrapper.getCurrentProvider(); + expect(exchangeWeb3WrapperProvider.zeroExTestId).to.be.a('number'); + const tokenRegistryWeb3WrapperProvider = (zeroEx.tokenRegistry as any)._web3Wrapper.getCurrentProvider(); + expect(tokenRegistryWeb3WrapperProvider.zeroExTestId).to.be.a('number'); + }); + }); + describe('#isValidSignature', () => { + // The Exchange smart contract `isValidSignature` method only validates orderHashes and assumes + // the length of the data is exactly 32 bytes. Thus for these tests, we use data of this size. + const dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; + const signature = { + v: 27, + r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', + s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', + }; + const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; + it("should return false if the data doesn't pertain to the signature & address", async () => { + expect(ZeroEx.isValidSignature('0x0', signature, address)).to.be.false(); + return expect( + (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync('0x0', signature, address), + ).to.become(false); + }); + it("should return false if the address doesn't pertain to the signature & data", async () => { + const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42'; + expect(ZeroEx.isValidSignature(dataHex, signature, validUnrelatedAddress)).to.be.false(); + return expect( + (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync( + dataHex, + signature, + validUnrelatedAddress, + ), + ).to.become(false); + }); + it("should return false if the signature doesn't pertain to the dataHex & address", async () => { + const wrongSignature = _.assign({}, signature, { v: 28 }); + expect(ZeroEx.isValidSignature(dataHex, wrongSignature, address)).to.be.false(); + return expect( + (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, wrongSignature, address), + ).to.become(false); + }); + it('should return true if the signature does pertain to the dataHex & address', async () => { + const isValidSignatureLocal = ZeroEx.isValidSignature(dataHex, signature, address); + expect(isValidSignatureLocal).to.be.true(); + return expect( + (zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, signature, address), + ).to.become(true); + }); + }); + describe('#generateSalt', () => { + it('generates different salts', () => { + const equal = ZeroEx.generatePseudoRandomSalt().eq(ZeroEx.generatePseudoRandomSalt()); + expect(equal).to.be.false(); + }); + it('generates salt in range [0..2^256)', () => { + const salt = ZeroEx.generatePseudoRandomSalt(); + expect(salt.greaterThanOrEqualTo(0)).to.be.true(); + const twoPow256 = new BigNumber(2).pow(256); + expect(salt.lessThan(twoPow256)).to.be.true(); + }); + }); + describe('#isValidOrderHash', () => { + it('returns false if the value is not a hex string', () => { + const isValid = ZeroEx.isValidOrderHash('not a hex'); + expect(isValid).to.be.false(); + }); + it('returns false if the length is wrong', () => { + const isValid = ZeroEx.isValidOrderHash('0xdeadbeef'); + expect(isValid).to.be.false(); + }); + it('returns true if order hash is correct', () => { + const isValid = ZeroEx.isValidOrderHash('0x' + Array(65).join('0')); + expect(isValid).to.be.true(); + }); + }); + describe('#toUnitAmount', () => { + it('should throw if invalid baseUnit amount supplied as argument', () => { + const invalidBaseUnitAmount = new BigNumber(1000000000.4); + const decimals = 6; + expect(() => ZeroEx.toUnitAmount(invalidBaseUnitAmount, decimals)).to.throw( + 'amount should be in baseUnits (no decimals), found value: 1000000000.4', + ); + }); + it('Should return the expected unit amount for the decimals passed in', () => { + const baseUnitAmount = new BigNumber(1000000000); + const decimals = 6; + const unitAmount = ZeroEx.toUnitAmount(baseUnitAmount, decimals); + const expectedUnitAmount = new BigNumber(1000); + expect(unitAmount).to.be.bignumber.equal(expectedUnitAmount); + }); + }); + describe('#toBaseUnitAmount', () => { + it('Should return the expected base unit amount for the decimals passed in', () => { + const unitAmount = new BigNumber(1000); + const decimals = 6; + const baseUnitAmount = ZeroEx.toBaseUnitAmount(unitAmount, decimals); + const expectedUnitAmount = new BigNumber(1000000000); + expect(baseUnitAmount).to.be.bignumber.equal(expectedUnitAmount); + }); + it('should throw if unitAmount has more decimals then specified as the max decimal precision', () => { + const unitAmount = new BigNumber(0.823091); + const decimals = 5; + expect(() => ZeroEx.toBaseUnitAmount(unitAmount, decimals)).to.throw( + 'Invalid unit amount: 0.823091 - Too many decimal places', + ); + }); + }); + describe('#getOrderHashHex', () => { + const expectedOrderHash = '0x39da987067a3c9e5f1617694f1301326ba8c8b0498ebef5df4863bed394e3c83'; + const fakeExchangeContractAddress = '0xb69e673309512a9d726f87304c6984054f87a93b'; + const order: Order = { + maker: constants.NULL_ADDRESS, + taker: constants.NULL_ADDRESS, + feeRecipient: constants.NULL_ADDRESS, + makerTokenAddress: constants.NULL_ADDRESS, + takerTokenAddress: constants.NULL_ADDRESS, + exchangeContractAddress: fakeExchangeContractAddress, + salt: new BigNumber(0), + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + makerTokenAmount: new BigNumber(0), + takerTokenAmount: new BigNumber(0), + expirationUnixTimestampSec: new BigNumber(0), + }; + it('calculates the order hash', async () => { + const orderHash = ZeroEx.getOrderHashHex(order); + expect(orderHash).to.be.equal(expectedOrderHash); + }); + it('throws a readable error message if taker format is invalid', async () => { + const orderWithInvalidtakerFormat = { + ...order, + taker: (null as any) as string, + }; + const expectedErrorMessage = + 'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS'; + expect(() => ZeroEx.getOrderHashHex(orderWithInvalidtakerFormat)).to.throw(expectedErrorMessage); + }); + }); + describe('#signOrderHashAsync', () => { + let stubs: Sinon.SinonStub[] = []; + let makerAddress: string; + before(async () => { + const availableAddreses = await zeroEx.getAvailableAddressesAsync(); + makerAddress = availableAddreses[0]; + }); + afterEach(() => { + // clean up any stubs after the test has completed + _.each(stubs, s => s.restore()); + stubs = []; + }); + it('Should return the correct ECSignature', async () => { + const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; + const expectedECSignature = { + v: 27, + r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33', + s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', + }; + const ecSignature = await zeroEx.signOrderHashAsync( + orderHash, + makerAddress, + SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, + ); + expect(ecSignature).to.deep.equal(expectedECSignature); + }); + it('should return the correct ECSignature for signatureHex concatenated as R + S + V', async () => { + const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004'; + const signature = + '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb021b'; + const expectedECSignature = { + v: 27, + r: '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3', + s: '0x050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb02', + }; + stubs = [ + Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync').returns(Promise.resolve(signature)), + Sinon.stub(ZeroEx, 'isValidSignature').returns(true), + ]; - const ecSignature = await zeroEx.signOrderHashAsync( - orderHash, - makerAddress, - SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, - ); - expect(ecSignature).to.deep.equal(expectedECSignature); - }); - it('should return the correct ECSignature for signatureHex concatenated as V + R + S', async () => { - const orderHash = '0xc793e33ffded933b76f2f48d9aa3339fc090399d5e7f5dec8d3660f5480793f7'; - const signature = - '0x1bc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee02dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960'; - const expectedECSignature = { - v: 27, - r: '0xc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee0', - s: '0x2dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960', - }; - stubs = [ - Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync').returns(Promise.resolve(signature)), - Sinon.stub(ZeroEx, 'isValidSignature').returns(true), - ]; + const ecSignature = await zeroEx.signOrderHashAsync( + orderHash, + makerAddress, + SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, + ); + expect(ecSignature).to.deep.equal(expectedECSignature); + }); + it('should return the correct ECSignature for signatureHex concatenated as V + R + S', async () => { + const orderHash = '0xc793e33ffded933b76f2f48d9aa3339fc090399d5e7f5dec8d3660f5480793f7'; + const signature = + '0x1bc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee02dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960'; + const expectedECSignature = { + v: 27, + r: '0xc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee0', + s: '0x2dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960', + }; + stubs = [ + Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync').returns(Promise.resolve(signature)), + Sinon.stub(ZeroEx, 'isValidSignature').returns(true), + ]; - const ecSignature = await zeroEx.signOrderHashAsync( - orderHash, - makerAddress, - SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, - ); - expect(ecSignature).to.deep.equal(expectedECSignature); - }); - }); - describe('#awaitTransactionMinedAsync', () => { - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - it('returns transaction receipt with decoded logs', async () => { - const availableAddresses = await zeroEx.getAvailableAddressesAsync(); - const coinbase = availableAddresses[0]; - const tokens = await zeroEx.tokenRegistry.getTokensAsync(); - const tokenUtils = new TokenUtils(tokens); - const zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; - const proxyAddress = zeroEx.proxy.getContractAddress(); - const txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(zrxTokenAddress, coinbase); - const txReceiptWithDecodedLogs = await zeroEx.awaitTransactionMinedAsync(txHash); - const log = txReceiptWithDecodedLogs.logs[0] as LogWithDecodedArgs<ApprovalContractEventArgs>; - expect(log.event).to.be.equal(TokenEvents.Approval); - expect(log.args._owner).to.be.equal(coinbase); - expect(log.args._spender).to.be.equal(proxyAddress); - expect(log.args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - }); - describe('#config', () => { - it('allows to specify exchange contract address', async () => { - const zeroExConfig = { - exchangeContractAddress: ZeroEx.NULL_ADDRESS, - networkId: constants.TESTRPC_NETWORK_ID, - }; - const zeroExWithWrongExchangeAddress = new ZeroEx(web3.currentProvider, zeroExConfig); - expect(zeroExWithWrongExchangeAddress.exchange.getContractAddress()).to.be.equal(ZeroEx.NULL_ADDRESS); - }); - it('allows to specify token registry token contract address', async () => { - const zeroExConfig = { - tokenRegistryContractAddress: ZeroEx.NULL_ADDRESS, - networkId: constants.TESTRPC_NETWORK_ID, - }; - const zeroExWithWrongTokenRegistryAddress = new ZeroEx(web3.currentProvider, zeroExConfig); - expect(zeroExWithWrongTokenRegistryAddress.tokenRegistry.getContractAddress()).to.be.equal( - ZeroEx.NULL_ADDRESS, - ); - }); - }); + const ecSignature = await zeroEx.signOrderHashAsync( + orderHash, + makerAddress, + SHOULD_ADD_PERSONAL_MESSAGE_PREFIX, + ); + expect(ecSignature).to.deep.equal(expectedECSignature); + }); + }); + describe('#awaitTransactionMinedAsync', () => { + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + it('returns transaction receipt with decoded logs', async () => { + const availableAddresses = await zeroEx.getAvailableAddressesAsync(); + const coinbase = availableAddresses[0]; + const tokens = await zeroEx.tokenRegistry.getTokensAsync(); + const tokenUtils = new TokenUtils(tokens); + const zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; + const proxyAddress = zeroEx.proxy.getContractAddress(); + const txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(zrxTokenAddress, coinbase); + const txReceiptWithDecodedLogs = await zeroEx.awaitTransactionMinedAsync(txHash); + const log = txReceiptWithDecodedLogs.logs[0] as LogWithDecodedArgs<ApprovalContractEventArgs>; + expect(log.event).to.be.equal(TokenEvents.Approval); + expect(log.args._owner).to.be.equal(coinbase); + expect(log.args._spender).to.be.equal(proxyAddress); + expect(log.args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + }); + }); + describe('#config', () => { + it('allows to specify exchange contract address', async () => { + const zeroExConfig = { + exchangeContractAddress: ZeroEx.NULL_ADDRESS, + networkId: constants.TESTRPC_NETWORK_ID, + }; + const zeroExWithWrongExchangeAddress = new ZeroEx(web3.currentProvider, zeroExConfig); + expect(zeroExWithWrongExchangeAddress.exchange.getContractAddress()).to.be.equal(ZeroEx.NULL_ADDRESS); + }); + it('allows to specify token registry token contract address', async () => { + const zeroExConfig = { + tokenRegistryContractAddress: ZeroEx.NULL_ADDRESS, + networkId: constants.TESTRPC_NETWORK_ID, + }; + const zeroExWithWrongTokenRegistryAddress = new ZeroEx(web3.currentProvider, zeroExConfig); + expect(zeroExWithWrongTokenRegistryAddress.tokenRegistry.getContractAddress()).to.be.equal( + ZeroEx.NULL_ADDRESS, + ); + }); + }); }); diff --git a/packages/0x.js/test/artifacts_test.ts b/packages/0x.js/test/artifacts_test.ts index e8ab9aa97..3a791f436 100644 --- a/packages/0x.js/test/artifacts_test.ts +++ b/packages/0x.js/test/artifacts_test.ts @@ -12,44 +12,44 @@ chaiSetup.configure(); const TIMEOUT = 10000; describe('Artifacts', () => { - describe('contracts are deployed on kovan', () => { - const kovanRpcUrl = constants.KOVAN_RPC_URL; - const packageJSONContent = fs.readFileSync('package.json', 'utf-8'); - const packageJSON = JSON.parse(packageJSONContent); - const mnemonic = packageJSON.config.mnemonic; - const web3Provider = new HDWalletProvider(mnemonic, kovanRpcUrl); - const config = { - networkId: constants.KOVAN_NETWORK_ID, - }; - const zeroEx = new ZeroEx(web3Provider, config); - it('token registry contract is deployed', async () => { - await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync(); - }).timeout(TIMEOUT); - it('proxy contract is deployed', async () => { - await (zeroEx.proxy as any)._getTokenTransferProxyContractAsync(); - }).timeout(TIMEOUT); - it('exchange contract is deployed', async () => { - await (zeroEx.exchange as any)._getExchangeContractAsync(); - }).timeout(TIMEOUT); - }); - describe('contracts are deployed on ropsten', () => { - const ropstenRpcUrl = constants.ROPSTEN_RPC_URL; - const packageJSONContent = fs.readFileSync('package.json', 'utf-8'); - const packageJSON = JSON.parse(packageJSONContent); - const mnemonic = packageJSON.config.mnemonic; - const web3Provider = new HDWalletProvider(mnemonic, ropstenRpcUrl); - const config = { - networkId: constants.ROPSTEN_NETWORK_ID, - }; - const zeroEx = new ZeroEx(web3Provider, config); - it('token registry contract is deployed', async () => { - await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync(); - }).timeout(TIMEOUT); - it('proxy contract is deployed', async () => { - await (zeroEx.proxy as any)._getTokenTransferProxyContractAsync(); - }).timeout(TIMEOUT); - it('exchange contract is deployed', async () => { - await (zeroEx.exchange as any)._getExchangeContractAsync(); - }).timeout(TIMEOUT); - }); + describe('contracts are deployed on kovan', () => { + const kovanRpcUrl = constants.KOVAN_RPC_URL; + const packageJSONContent = fs.readFileSync('package.json', 'utf-8'); + const packageJSON = JSON.parse(packageJSONContent); + const mnemonic = packageJSON.config.mnemonic; + const web3Provider = new HDWalletProvider(mnemonic, kovanRpcUrl); + const config = { + networkId: constants.KOVAN_NETWORK_ID, + }; + const zeroEx = new ZeroEx(web3Provider, config); + it('token registry contract is deployed', async () => { + await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync(); + }).timeout(TIMEOUT); + it('proxy contract is deployed', async () => { + await (zeroEx.proxy as any)._getTokenTransferProxyContractAsync(); + }).timeout(TIMEOUT); + it('exchange contract is deployed', async () => { + await (zeroEx.exchange as any)._getExchangeContractAsync(); + }).timeout(TIMEOUT); + }); + describe('contracts are deployed on ropsten', () => { + const ropstenRpcUrl = constants.ROPSTEN_RPC_URL; + const packageJSONContent = fs.readFileSync('package.json', 'utf-8'); + const packageJSON = JSON.parse(packageJSONContent); + const mnemonic = packageJSON.config.mnemonic; + const web3Provider = new HDWalletProvider(mnemonic, ropstenRpcUrl); + const config = { + networkId: constants.ROPSTEN_NETWORK_ID, + }; + const zeroEx = new ZeroEx(web3Provider, config); + it('token registry contract is deployed', async () => { + await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync(); + }).timeout(TIMEOUT); + it('proxy contract is deployed', async () => { + await (zeroEx.proxy as any)._getTokenTransferProxyContractAsync(); + }).timeout(TIMEOUT); + it('exchange contract is deployed', async () => { + await (zeroEx.exchange as any)._getExchangeContractAsync(); + }).timeout(TIMEOUT); + }); }); diff --git a/packages/0x.js/test/assert_test.ts b/packages/0x.js/test/assert_test.ts index 1f2820070..2c72a5e88 100644 --- a/packages/0x.js/test/assert_test.ts +++ b/packages/0x.js/test/assert_test.ts @@ -10,34 +10,34 @@ import { web3Factory } from './utils/web3_factory'; const expect = chai.expect; describe('Assertion library', () => { - const web3 = web3Factory.create(); - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - const zeroEx = new ZeroEx(web3.currentProvider, config); - describe('#isSenderAddressHexAsync', () => { - it('throws when address is invalid', async () => { - const address = '0xdeadbeef'; - const varName = 'address'; - return expect( - assert.isSenderAddressAsync(varName, address, (zeroEx as any)._web3Wrapper), - ).to.be.rejectedWith(`Expected ${varName} to be of type ETHAddressHex, encountered: ${address}`); - }); - it('throws when address is unavailable', async () => { - const validUnrelatedAddress = '0x8b0292b11a196601eddce54b665cafeca0347d42'; - const varName = 'address'; - return expect( - assert.isSenderAddressAsync(varName, validUnrelatedAddress, (zeroEx as any)._web3Wrapper), - ).to.be.rejectedWith( - `Specified ${varName} ${validUnrelatedAddress} isn't available through the supplied web3 provider`, - ); - }); - it("doesn't throw if address is available", async () => { - const availableAddress = (await zeroEx.getAvailableAddressesAsync())[0]; - const varName = 'address'; - return expect( - assert.isSenderAddressAsync(varName, availableAddress, (zeroEx as any)._web3Wrapper), - ).to.become(undefined); - }); - }); + const web3 = web3Factory.create(); + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + const zeroEx = new ZeroEx(web3.currentProvider, config); + describe('#isSenderAddressHexAsync', () => { + it('throws when address is invalid', async () => { + const address = '0xdeadbeef'; + const varName = 'address'; + return expect( + assert.isSenderAddressAsync(varName, address, (zeroEx as any)._web3Wrapper), + ).to.be.rejectedWith(`Expected ${varName} to be of type ETHAddressHex, encountered: ${address}`); + }); + it('throws when address is unavailable', async () => { + const validUnrelatedAddress = '0x8b0292b11a196601eddce54b665cafeca0347d42'; + const varName = 'address'; + return expect( + assert.isSenderAddressAsync(varName, validUnrelatedAddress, (zeroEx as any)._web3Wrapper), + ).to.be.rejectedWith( + `Specified ${varName} ${validUnrelatedAddress} isn't available through the supplied web3 provider`, + ); + }); + it("doesn't throw if address is available", async () => { + const availableAddress = (await zeroEx.getAvailableAddressesAsync())[0]; + const varName = 'address'; + return expect( + assert.isSenderAddressAsync(varName, availableAddress, (zeroEx as any)._web3Wrapper), + ).to.become(undefined); + }); + }); }); diff --git a/packages/0x.js/test/ether_token_wrapper_test.ts b/packages/0x.js/test/ether_token_wrapper_test.ts index b810fc9f1..67e62dc12 100644 --- a/packages/0x.js/test/ether_token_wrapper_test.ts +++ b/packages/0x.js/test/ether_token_wrapper_test.ts @@ -5,17 +5,17 @@ import 'mocha'; import * as Web3 from 'web3'; import { - ApprovalContractEventArgs, - BlockParamLiteral, - BlockRange, - DecodedLogEvent, - DepositContractEventArgs, - EtherTokenEvents, - Token, - TransferContractEventArgs, - WithdrawalContractEventArgs, - ZeroEx, - ZeroExError, + ApprovalContractEventArgs, + BlockParamLiteral, + BlockRange, + DecodedLogEvent, + DepositContractEventArgs, + EtherTokenEvents, + Token, + TransferContractEventArgs, + WithdrawalContractEventArgs, + ZeroEx, + ZeroExError, } from '../src'; import { artifacts } from '../src/artifacts'; import { DoneCallback } from '../src/types'; @@ -37,339 +37,339 @@ const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); const MAX_REASONABLE_GAS_COST_IN_WEI = 62517; describe('EtherTokenWrapper', () => { - let web3: Web3; - let zeroEx: ZeroEx; - let tokens: Token[]; - let userAddresses: string[]; - let addressWithETH: string; - let wethContractAddress: string; - let depositWeiAmount: BigNumber; - let decimalPlaces: number; - let addressWithoutFunds: string; - const gasPrice = new BigNumber(1); - const zeroExConfig = { - gasPrice, - networkId: constants.TESTRPC_NETWORK_ID, - }; - const transferAmount = new BigNumber(42); - const allowanceAmount = new BigNumber(42); - const depositAmount = new BigNumber(42); - const withdrawalAmount = new BigNumber(42); - before(async () => { - web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, zeroExConfig); - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - userAddresses = await zeroEx.getAvailableAddressesAsync(); - addressWithETH = userAddresses[0]; - wethContractAddress = (zeroEx.etherToken as any)._getContractAddress(artifacts.EtherTokenArtifact); - depositWeiAmount = (zeroEx as any)._web3Wrapper.toWei(new BigNumber(5)); - decimalPlaces = 7; - addressWithoutFunds = userAddresses[1]; - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('#depositAsync', () => { - it('should successfully deposit ETH and issue Wrapped ETH tokens', async () => { - const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); - const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); - expect(preETHBalance).to.be.bignumber.gt(0); - expect(preWETHBalance).to.be.bignumber.equal(0); + let web3: Web3; + let zeroEx: ZeroEx; + let tokens: Token[]; + let userAddresses: string[]; + let addressWithETH: string; + let wethContractAddress: string; + let depositWeiAmount: BigNumber; + let decimalPlaces: number; + let addressWithoutFunds: string; + const gasPrice = new BigNumber(1); + const zeroExConfig = { + gasPrice, + networkId: constants.TESTRPC_NETWORK_ID, + }; + const transferAmount = new BigNumber(42); + const allowanceAmount = new BigNumber(42); + const depositAmount = new BigNumber(42); + const withdrawalAmount = new BigNumber(42); + before(async () => { + web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, zeroExConfig); + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + userAddresses = await zeroEx.getAvailableAddressesAsync(); + addressWithETH = userAddresses[0]; + wethContractAddress = (zeroEx.etherToken as any)._getContractAddress(artifacts.EtherTokenArtifact); + depositWeiAmount = (zeroEx as any)._web3Wrapper.toWei(new BigNumber(5)); + decimalPlaces = 7; + addressWithoutFunds = userAddresses[1]; + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#depositAsync', () => { + it('should successfully deposit ETH and issue Wrapped ETH tokens', async () => { + const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); + const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); + expect(preETHBalance).to.be.bignumber.gt(0); + expect(preWETHBalance).to.be.bignumber.equal(0); - const txHash = await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH); - await zeroEx.awaitTransactionMinedAsync(txHash); + const txHash = await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH); + await zeroEx.awaitTransactionMinedAsync(txHash); - const postETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); - const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); + const postETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); + const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); - expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(depositWeiAmount); - const remainingETHInWei = preETHBalance.minus(depositWeiAmount); - const gasCost = remainingETHInWei.minus(postETHBalanceInWei); - expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI); - }); - it('should throw if user has insufficient ETH balance for deposit', async () => { - const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); + expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(depositWeiAmount); + const remainingETHInWei = preETHBalance.minus(depositWeiAmount); + const gasCost = remainingETHInWei.minus(postETHBalanceInWei); + expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI); + }); + it('should throw if user has insufficient ETH balance for deposit', async () => { + const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); - const extraETHBalance = (zeroEx as any)._web3Wrapper.toWei(5, 'ether'); - const overETHBalanceinWei = preETHBalance.add(extraETHBalance); + const extraETHBalance = (zeroEx as any)._web3Wrapper.toWei(5, 'ether'); + const overETHBalanceinWei = preETHBalance.add(extraETHBalance); - return expect( - zeroEx.etherToken.depositAsync(wethContractAddress, overETHBalanceinWei, addressWithETH), - ).to.be.rejectedWith(ZeroExError.InsufficientEthBalanceForDeposit); - }); - }); - describe('#withdrawAsync', () => { - it('should successfully withdraw ETH in return for Wrapped ETH tokens', async () => { - const ETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); + return expect( + zeroEx.etherToken.depositAsync(wethContractAddress, overETHBalanceinWei, addressWithETH), + ).to.be.rejectedWith(ZeroExError.InsufficientEthBalanceForDeposit); + }); + }); + describe('#withdrawAsync', () => { + it('should successfully withdraw ETH in return for Wrapped ETH tokens', async () => { + const ETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); - await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH); + await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH); - const expectedPreETHBalance = ETHBalanceInWei.minus(depositWeiAmount); - const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); - const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); - let gasCost = expectedPreETHBalance.minus(preETHBalance); - expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI); - expect(preWETHBalance).to.be.bignumber.equal(depositWeiAmount); + const expectedPreETHBalance = ETHBalanceInWei.minus(depositWeiAmount); + const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); + const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); + let gasCost = expectedPreETHBalance.minus(preETHBalance); + expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI); + expect(preWETHBalance).to.be.bignumber.equal(depositWeiAmount); - const txHash = await zeroEx.etherToken.withdrawAsync(wethContractAddress, depositWeiAmount, addressWithETH); - await zeroEx.awaitTransactionMinedAsync(txHash); + const txHash = await zeroEx.etherToken.withdrawAsync(wethContractAddress, depositWeiAmount, addressWithETH); + await zeroEx.awaitTransactionMinedAsync(txHash); - const postETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); - const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); + const postETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH); + const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); - expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(0); - const expectedETHBalance = preETHBalance.add(depositWeiAmount).round(decimalPlaces); - gasCost = expectedETHBalance.minus(postETHBalance); - expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI); - }); - it('should throw if user has insufficient WETH balance for withdrawl', async () => { - const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); - expect(preWETHBalance).to.be.bignumber.equal(0); + expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(0); + const expectedETHBalance = preETHBalance.add(depositWeiAmount).round(decimalPlaces); + gasCost = expectedETHBalance.minus(postETHBalance); + expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI); + }); + it('should throw if user has insufficient WETH balance for withdrawl', async () => { + const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); + expect(preWETHBalance).to.be.bignumber.equal(0); - const overWETHBalance = preWETHBalance.add(999999999); + const overWETHBalance = preWETHBalance.add(999999999); - return expect( - zeroEx.etherToken.withdrawAsync(wethContractAddress, overWETHBalance, addressWithETH), - ).to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal); - }); - }); - describe('#subscribe', () => { - const indexFilterValues = {}; - let etherTokenAddress: string; - before(() => { - const tokenUtils = new TokenUtils(tokens); - const etherToken = tokenUtils.getWethTokenOrThrow(); - etherTokenAddress = etherToken.address; - }); - afterEach(() => { - zeroEx.etherToken.unsubscribeAll(); - }); - // Hack: Mocha does not allow a test to be both async and have a `done` callback - // Since we need to await the receipt of the event in the `subscribe` callback, - // we do need both. A hack is to make the top-level async fn w/ a done callback and then - // wrap the rest of the test in an async block - // Source: https://github.com/mochajs/mocha/issues/2407 - it('Should receive the Transfer event when tokens are transfered', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<TransferContractEventArgs>) => { - expect(logEvent).to.not.be.undefined(); - expect(logEvent.isRemoved).to.be.false(); - expect(logEvent.log.logIndex).to.be.equal(0); - expect(logEvent.log.transactionIndex).to.be.equal(0); - expect(logEvent.log.blockNumber).to.be.a('number'); - const args = logEvent.log.args; - expect(args._from).to.be.equal(addressWithETH); - expect(args._to).to.be.equal(addressWithoutFunds); - expect(args._value).to.be.bignumber.equal(transferAmount); - }, - ); - await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH); - zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Transfer, indexFilterValues, callback); - await zeroEx.token.transferAsync( - etherTokenAddress, - addressWithETH, - addressWithoutFunds, - transferAmount, - ); - })().catch(done); - }); - it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { - expect(logEvent).to.not.be.undefined(); - expect(logEvent.isRemoved).to.be.false(); - const args = logEvent.log.args; - expect(args._owner).to.be.equal(addressWithETH); - expect(args._spender).to.be.equal(addressWithoutFunds); - expect(args._value).to.be.bignumber.equal(allowanceAmount); - }, - ); - zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Approval, indexFilterValues, callback); - await zeroEx.token.setAllowanceAsync( - etherTokenAddress, - addressWithETH, - addressWithoutFunds, - allowanceAmount, - ); - })().catch(done); - }); - it('Should receive the Deposit event when ether is being deposited', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<DepositContractEventArgs>) => { - expect(logEvent).to.not.be.undefined(); - expect(logEvent.isRemoved).to.be.false(); - const args = logEvent.log.args; - expect(args._owner).to.be.equal(addressWithETH); - expect(args._value).to.be.bignumber.equal(depositAmount); - }, - ); - zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Deposit, indexFilterValues, callback); - await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH); - })().catch(done); - }); - it('Should receive the Withdrawal event when ether is being withdrawn', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<WithdrawalContractEventArgs>) => { - expect(logEvent).to.not.be.undefined(); - expect(logEvent.isRemoved).to.be.false(); - const args = logEvent.log.args; - expect(args._owner).to.be.equal(addressWithETH); - expect(args._value).to.be.bignumber.equal(depositAmount); - }, - ); - await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH); - zeroEx.etherToken.subscribe( - etherTokenAddress, - EtherTokenEvents.Withdrawal, - indexFilterValues, - callback, - ); - await zeroEx.etherToken.withdrawAsync(etherTokenAddress, withdrawalAmount, addressWithETH); - })().catch(done); - }); - it('should cancel outstanding subscriptions when ZeroEx.setProvider is called', (done: DoneCallback) => { - (async () => { - const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { - done(new Error('Expected this subscription to have been cancelled')); - }, - ); - zeroEx.etherToken.subscribe( - etherTokenAddress, - EtherTokenEvents.Transfer, - indexFilterValues, - callbackNeverToBeCalled, - ); - const callbackToBeCalled = reportNodeCallbackErrors(done)(); - const newProvider = web3Factory.getRpcProvider(); - zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); - await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH); - zeroEx.etherToken.subscribe( - etherTokenAddress, - EtherTokenEvents.Transfer, - indexFilterValues, - callbackToBeCalled, - ); - await zeroEx.token.transferAsync( - etherTokenAddress, - addressWithETH, - addressWithoutFunds, - transferAmount, - ); - })().catch(done); - }); - it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => { - (async () => { - const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { - done(new Error('Expected this subscription to have been cancelled')); - }, - ); - await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH); - const subscriptionToken = zeroEx.etherToken.subscribe( - etherTokenAddress, - EtherTokenEvents.Transfer, - indexFilterValues, - callbackNeverToBeCalled, - ); - zeroEx.etherToken.unsubscribe(subscriptionToken); - await zeroEx.token.transferAsync( - etherTokenAddress, - addressWithETH, - addressWithoutFunds, - transferAmount, - ); - done(); - })().catch(done); - }); - }); - describe('#getLogsAsync', () => { - let etherTokenAddress: string; - let tokenTransferProxyAddress: string; - const blockRange: BlockRange = { - fromBlock: 0, - toBlock: BlockParamLiteral.Latest, - }; - let txHash: string; - before(() => { - addressWithETH = userAddresses[0]; - const tokenUtils = new TokenUtils(tokens); - const etherToken = tokenUtils.getWethTokenOrThrow(); - etherTokenAddress = etherToken.address; - tokenTransferProxyAddress = zeroEx.proxy.getContractAddress(); - }); - it('should get logs with decoded args emitted by Approval', async () => { - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH); - await zeroEx.awaitTransactionMinedAsync(txHash); - const eventName = EtherTokenEvents.Approval; - const indexFilterValues = {}; - const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>( - etherTokenAddress, - eventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(1); - const args = logs[0].args; - expect(logs[0].event).to.be.equal(eventName); - expect(args._owner).to.be.equal(addressWithETH); - expect(args._spender).to.be.equal(tokenTransferProxyAddress); - expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - it('should get logs with decoded args emitted by Deposit', async () => { - await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH); - const eventName = EtherTokenEvents.Deposit; - const indexFilterValues = {}; - const logs = await zeroEx.etherToken.getLogsAsync<DepositContractEventArgs>( - etherTokenAddress, - eventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(1); - const args = logs[0].args; - expect(logs[0].event).to.be.equal(eventName); - expect(args._owner).to.be.equal(addressWithETH); - expect(args._value).to.be.bignumber.equal(depositAmount); - }); - it('should only get the logs with the correct event name', async () => { - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH); - await zeroEx.awaitTransactionMinedAsync(txHash); - const differentEventName = EtherTokenEvents.Transfer; - const indexFilterValues = {}; - const logs = await zeroEx.etherToken.getLogsAsync( - etherTokenAddress, - differentEventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(0); - }); - it('should only get the logs with the correct indexed fields', async () => { - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH); - await zeroEx.awaitTransactionMinedAsync(txHash); - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithoutFunds); - await zeroEx.awaitTransactionMinedAsync(txHash); - const eventName = EtherTokenEvents.Approval; - const indexFilterValues = { - _owner: addressWithETH, - }; - const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>( - etherTokenAddress, - eventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(1); - const args = logs[0].args; - expect(args._owner).to.be.equal(addressWithETH); - }); - }); + return expect( + zeroEx.etherToken.withdrawAsync(wethContractAddress, overWETHBalance, addressWithETH), + ).to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal); + }); + }); + describe('#subscribe', () => { + const indexFilterValues = {}; + let etherTokenAddress: string; + before(() => { + const tokenUtils = new TokenUtils(tokens); + const etherToken = tokenUtils.getWethTokenOrThrow(); + etherTokenAddress = etherToken.address; + }); + afterEach(() => { + zeroEx.etherToken.unsubscribeAll(); + }); + // Hack: Mocha does not allow a test to be both async and have a `done` callback + // Since we need to await the receipt of the event in the `subscribe` callback, + // we do need both. A hack is to make the top-level async fn w/ a done callback and then + // wrap the rest of the test in an async block + // Source: https://github.com/mochajs/mocha/issues/2407 + it('Should receive the Transfer event when tokens are transfered', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<TransferContractEventArgs>) => { + expect(logEvent).to.not.be.undefined(); + expect(logEvent.isRemoved).to.be.false(); + expect(logEvent.log.logIndex).to.be.equal(0); + expect(logEvent.log.transactionIndex).to.be.equal(0); + expect(logEvent.log.blockNumber).to.be.a('number'); + const args = logEvent.log.args; + expect(args._from).to.be.equal(addressWithETH); + expect(args._to).to.be.equal(addressWithoutFunds); + expect(args._value).to.be.bignumber.equal(transferAmount); + }, + ); + await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH); + zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Transfer, indexFilterValues, callback); + await zeroEx.token.transferAsync( + etherTokenAddress, + addressWithETH, + addressWithoutFunds, + transferAmount, + ); + })().catch(done); + }); + it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { + expect(logEvent).to.not.be.undefined(); + expect(logEvent.isRemoved).to.be.false(); + const args = logEvent.log.args; + expect(args._owner).to.be.equal(addressWithETH); + expect(args._spender).to.be.equal(addressWithoutFunds); + expect(args._value).to.be.bignumber.equal(allowanceAmount); + }, + ); + zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Approval, indexFilterValues, callback); + await zeroEx.token.setAllowanceAsync( + etherTokenAddress, + addressWithETH, + addressWithoutFunds, + allowanceAmount, + ); + })().catch(done); + }); + it('Should receive the Deposit event when ether is being deposited', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<DepositContractEventArgs>) => { + expect(logEvent).to.not.be.undefined(); + expect(logEvent.isRemoved).to.be.false(); + const args = logEvent.log.args; + expect(args._owner).to.be.equal(addressWithETH); + expect(args._value).to.be.bignumber.equal(depositAmount); + }, + ); + zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Deposit, indexFilterValues, callback); + await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH); + })().catch(done); + }); + it('Should receive the Withdrawal event when ether is being withdrawn', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<WithdrawalContractEventArgs>) => { + expect(logEvent).to.not.be.undefined(); + expect(logEvent.isRemoved).to.be.false(); + const args = logEvent.log.args; + expect(args._owner).to.be.equal(addressWithETH); + expect(args._value).to.be.bignumber.equal(depositAmount); + }, + ); + await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH); + zeroEx.etherToken.subscribe( + etherTokenAddress, + EtherTokenEvents.Withdrawal, + indexFilterValues, + callback, + ); + await zeroEx.etherToken.withdrawAsync(etherTokenAddress, withdrawalAmount, addressWithETH); + })().catch(done); + }); + it('should cancel outstanding subscriptions when ZeroEx.setProvider is called', (done: DoneCallback) => { + (async () => { + const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { + done(new Error('Expected this subscription to have been cancelled')); + }, + ); + zeroEx.etherToken.subscribe( + etherTokenAddress, + EtherTokenEvents.Transfer, + indexFilterValues, + callbackNeverToBeCalled, + ); + const callbackToBeCalled = reportNodeCallbackErrors(done)(); + const newProvider = web3Factory.getRpcProvider(); + zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); + await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH); + zeroEx.etherToken.subscribe( + etherTokenAddress, + EtherTokenEvents.Transfer, + indexFilterValues, + callbackToBeCalled, + ); + await zeroEx.token.transferAsync( + etherTokenAddress, + addressWithETH, + addressWithoutFunds, + transferAmount, + ); + })().catch(done); + }); + it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => { + (async () => { + const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { + done(new Error('Expected this subscription to have been cancelled')); + }, + ); + await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH); + const subscriptionToken = zeroEx.etherToken.subscribe( + etherTokenAddress, + EtherTokenEvents.Transfer, + indexFilterValues, + callbackNeverToBeCalled, + ); + zeroEx.etherToken.unsubscribe(subscriptionToken); + await zeroEx.token.transferAsync( + etherTokenAddress, + addressWithETH, + addressWithoutFunds, + transferAmount, + ); + done(); + })().catch(done); + }); + }); + describe('#getLogsAsync', () => { + let etherTokenAddress: string; + let tokenTransferProxyAddress: string; + const blockRange: BlockRange = { + fromBlock: 0, + toBlock: BlockParamLiteral.Latest, + }; + let txHash: string; + before(() => { + addressWithETH = userAddresses[0]; + const tokenUtils = new TokenUtils(tokens); + const etherToken = tokenUtils.getWethTokenOrThrow(); + etherTokenAddress = etherToken.address; + tokenTransferProxyAddress = zeroEx.proxy.getContractAddress(); + }); + it('should get logs with decoded args emitted by Approval', async () => { + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH); + await zeroEx.awaitTransactionMinedAsync(txHash); + const eventName = EtherTokenEvents.Approval; + const indexFilterValues = {}; + const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>( + etherTokenAddress, + eventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(1); + const args = logs[0].args; + expect(logs[0].event).to.be.equal(eventName); + expect(args._owner).to.be.equal(addressWithETH); + expect(args._spender).to.be.equal(tokenTransferProxyAddress); + expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + }); + it('should get logs with decoded args emitted by Deposit', async () => { + await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH); + const eventName = EtherTokenEvents.Deposit; + const indexFilterValues = {}; + const logs = await zeroEx.etherToken.getLogsAsync<DepositContractEventArgs>( + etherTokenAddress, + eventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(1); + const args = logs[0].args; + expect(logs[0].event).to.be.equal(eventName); + expect(args._owner).to.be.equal(addressWithETH); + expect(args._value).to.be.bignumber.equal(depositAmount); + }); + it('should only get the logs with the correct event name', async () => { + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH); + await zeroEx.awaitTransactionMinedAsync(txHash); + const differentEventName = EtherTokenEvents.Transfer; + const indexFilterValues = {}; + const logs = await zeroEx.etherToken.getLogsAsync( + etherTokenAddress, + differentEventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(0); + }); + it('should only get the logs with the correct indexed fields', async () => { + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH); + await zeroEx.awaitTransactionMinedAsync(txHash); + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithoutFunds); + await zeroEx.awaitTransactionMinedAsync(txHash); + const eventName = EtherTokenEvents.Approval; + const indexFilterValues = { + _owner: addressWithETH, + }; + const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>( + etherTokenAddress, + eventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(1); + const args = logs[0].args; + expect(args._owner).to.be.equal(addressWithETH); + }); + }); }); diff --git a/packages/0x.js/test/event_watcher_test.ts b/packages/0x.js/test/event_watcher_test.ts index f92fb2b02..f0c97eced 100644 --- a/packages/0x.js/test/event_watcher_test.ts +++ b/packages/0x.js/test/event_watcher_test.ts @@ -17,110 +17,110 @@ chaiSetup.configure(); const expect = chai.expect; describe('EventWatcher', () => { - let web3: Web3; - let stubs: Sinon.SinonStub[] = []; - let eventWatcher: EventWatcher; - let web3Wrapper: Web3Wrapper; - const logA: Web3.LogEntry = { - address: '0x71d271f8b14adef568f8f28f1587ce7271ac4ca5', - blockHash: null, - blockNumber: null, - data: '', - logIndex: null, - topics: [], - transactionHash: '0x004881d38cd4a8f72f1a0d68c8b9b8124504706041ff37019c1d1ed6bfda8e17', - transactionIndex: 0, - }; - const logB: Web3.LogEntry = { - address: '0x8d12a197cb00d4747a1fe03395095ce2a5cc6819', - blockHash: null, - blockNumber: null, - data: '', - logIndex: null, - topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'], - transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25', - transactionIndex: 0, - }; - const logC: Web3.LogEntry = { - address: '0x1d271f8b174adef58f1587ce68f8f27271ac4ca5', - blockHash: null, - blockNumber: null, - data: '', - logIndex: null, - topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'], - transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25', - transactionIndex: 0, - }; - before(async () => { - web3 = web3Factory.create(); - const pollingIntervalMs = 10; - web3Wrapper = new Web3Wrapper(web3.currentProvider); - eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalMs); - }); - afterEach(() => { - // clean up any stubs after the test has completed - _.each(stubs, s => s.restore()); - stubs = []; - eventWatcher.unsubscribe(); - }); - it('correctly emits initial log events', (done: DoneCallback) => { - const logs: Web3.LogEntry[] = [logA, logB]; - const expectedLogEvents = [ - { - removed: false, - ...logA, - }, - { - removed: false, - ...logB, - }, - ]; - const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync'); - getLogsStub.onCall(0).returns(logs); - stubs.push(getLogsStub); - const expectedToBeCalledOnce = false; - const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => { - const expectedLogEvent = expectedLogEvents.shift(); - expect(event).to.be.deep.equal(expectedLogEvent); - if (_.isEmpty(expectedLogEvents)) { - done(); - } - }); - eventWatcher.subscribe(callback); - }); - it('correctly computes the difference and emits only changes', (done: DoneCallback) => { - const initialLogs: Web3.LogEntry[] = [logA, logB]; - const changedLogs: Web3.LogEntry[] = [logA, logC]; - const expectedLogEvents = [ - { - removed: false, - ...logA, - }, - { - removed: false, - ...logB, - }, - { - removed: true, - ...logB, - }, - { - removed: false, - ...logC, - }, - ]; - const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync'); - getLogsStub.onCall(0).returns(initialLogs); - getLogsStub.onCall(1).returns(changedLogs); - stubs.push(getLogsStub); - const expectedToBeCalledOnce = false; - const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => { - const expectedLogEvent = expectedLogEvents.shift(); - expect(event).to.be.deep.equal(expectedLogEvent); - if (_.isEmpty(expectedLogEvents)) { - done(); - } - }); - eventWatcher.subscribe(callback); - }); + let web3: Web3; + let stubs: Sinon.SinonStub[] = []; + let eventWatcher: EventWatcher; + let web3Wrapper: Web3Wrapper; + const logA: Web3.LogEntry = { + address: '0x71d271f8b14adef568f8f28f1587ce7271ac4ca5', + blockHash: null, + blockNumber: null, + data: '', + logIndex: null, + topics: [], + transactionHash: '0x004881d38cd4a8f72f1a0d68c8b9b8124504706041ff37019c1d1ed6bfda8e17', + transactionIndex: 0, + }; + const logB: Web3.LogEntry = { + address: '0x8d12a197cb00d4747a1fe03395095ce2a5cc6819', + blockHash: null, + blockNumber: null, + data: '', + logIndex: null, + topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'], + transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25', + transactionIndex: 0, + }; + const logC: Web3.LogEntry = { + address: '0x1d271f8b174adef58f1587ce68f8f27271ac4ca5', + blockHash: null, + blockNumber: null, + data: '', + logIndex: null, + topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'], + transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25', + transactionIndex: 0, + }; + before(async () => { + web3 = web3Factory.create(); + const pollingIntervalMs = 10; + web3Wrapper = new Web3Wrapper(web3.currentProvider); + eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalMs); + }); + afterEach(() => { + // clean up any stubs after the test has completed + _.each(stubs, s => s.restore()); + stubs = []; + eventWatcher.unsubscribe(); + }); + it('correctly emits initial log events', (done: DoneCallback) => { + const logs: Web3.LogEntry[] = [logA, logB]; + const expectedLogEvents = [ + { + removed: false, + ...logA, + }, + { + removed: false, + ...logB, + }, + ]; + const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync'); + getLogsStub.onCall(0).returns(logs); + stubs.push(getLogsStub); + const expectedToBeCalledOnce = false; + const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => { + const expectedLogEvent = expectedLogEvents.shift(); + expect(event).to.be.deep.equal(expectedLogEvent); + if (_.isEmpty(expectedLogEvents)) { + done(); + } + }); + eventWatcher.subscribe(callback); + }); + it('correctly computes the difference and emits only changes', (done: DoneCallback) => { + const initialLogs: Web3.LogEntry[] = [logA, logB]; + const changedLogs: Web3.LogEntry[] = [logA, logC]; + const expectedLogEvents = [ + { + removed: false, + ...logA, + }, + { + removed: false, + ...logB, + }, + { + removed: true, + ...logB, + }, + { + removed: false, + ...logC, + }, + ]; + const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync'); + getLogsStub.onCall(0).returns(initialLogs); + getLogsStub.onCall(1).returns(changedLogs); + stubs.push(getLogsStub); + const expectedToBeCalledOnce = false; + const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => { + const expectedLogEvent = expectedLogEvents.shift(); + expect(event).to.be.deep.equal(expectedLogEvent); + if (_.isEmpty(expectedLogEvents)) { + done(); + } + }); + eventWatcher.subscribe(callback); + }); }); diff --git a/packages/0x.js/test/exchange_transfer_simulator_test.ts b/packages/0x.js/test/exchange_transfer_simulator_test.ts index 20b4a05ca..9f4d76933 100644 --- a/packages/0x.js/test/exchange_transfer_simulator_test.ts +++ b/packages/0x.js/test/exchange_transfer_simulator_test.ts @@ -15,103 +15,103 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); describe('ExchangeTransferSimulator', () => { - const web3 = web3Factory.create(); - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - const zeroEx = new ZeroEx(web3.currentProvider, config); - const transferAmount = new BigNumber(5); - let userAddresses: string[]; - let tokens: Token[]; - let coinbase: string; - let sender: string; - let recipient: string; - let exampleTokenAddress: string; - let exchangeTransferSimulator: ExchangeTransferSimulator; - let txHash: string; - before(async () => { - userAddresses = await zeroEx.getAvailableAddressesAsync(); - [coinbase, sender, recipient] = userAddresses; - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - exampleTokenAddress = tokens[0].address; - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('#transferFromAsync', () => { - beforeEach(() => { - exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest); - }); - it("throws if the user doesn't have enough allowance", async () => { - return expect( - exchangeTransferSimulator.transferFromAsync( - exampleTokenAddress, - sender, - recipient, - transferAmount, - TradeSide.Taker, - TransferType.Trade, - ), - ).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerAllowance); - }); - it("throws if the user doesn't have enough balance", async () => { - txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount); - await zeroEx.awaitTransactionMinedAsync(txHash); - return expect( - exchangeTransferSimulator.transferFromAsync( - exampleTokenAddress, - sender, - recipient, - transferAmount, - TradeSide.Maker, - TransferType.Trade, - ), - ).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance); - }); - it('updates balances and proxyAllowance after transfer', async () => { - txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount); - await zeroEx.awaitTransactionMinedAsync(txHash); - txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount); - await zeroEx.awaitTransactionMinedAsync(txHash); - await exchangeTransferSimulator.transferFromAsync( - exampleTokenAddress, - sender, - recipient, - transferAmount, - TradeSide.Taker, - TransferType.Trade, - ); - const store = (exchangeTransferSimulator as any)._store; - const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender); - const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient); - const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender); - expect(senderBalance).to.be.bignumber.equal(0); - expect(recipientBalance).to.be.bignumber.equal(transferAmount); - expect(senderProxyAllowance).to.be.bignumber.equal(0); - }); - it("doesn't update proxyAllowance after transfer if unlimited", async () => { - txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount); - await zeroEx.awaitTransactionMinedAsync(txHash); - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(exampleTokenAddress, sender); - await zeroEx.awaitTransactionMinedAsync(txHash); - await exchangeTransferSimulator.transferFromAsync( - exampleTokenAddress, - sender, - recipient, - transferAmount, - TradeSide.Taker, - TransferType.Trade, - ); - const store = (exchangeTransferSimulator as any)._store; - const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender); - const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient); - const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender); - expect(senderBalance).to.be.bignumber.equal(0); - expect(recipientBalance).to.be.bignumber.equal(transferAmount); - expect(senderProxyAllowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - }); + const web3 = web3Factory.create(); + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + const zeroEx = new ZeroEx(web3.currentProvider, config); + const transferAmount = new BigNumber(5); + let userAddresses: string[]; + let tokens: Token[]; + let coinbase: string; + let sender: string; + let recipient: string; + let exampleTokenAddress: string; + let exchangeTransferSimulator: ExchangeTransferSimulator; + let txHash: string; + before(async () => { + userAddresses = await zeroEx.getAvailableAddressesAsync(); + [coinbase, sender, recipient] = userAddresses; + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + exampleTokenAddress = tokens[0].address; + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#transferFromAsync', () => { + beforeEach(() => { + exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest); + }); + it("throws if the user doesn't have enough allowance", async () => { + return expect( + exchangeTransferSimulator.transferFromAsync( + exampleTokenAddress, + sender, + recipient, + transferAmount, + TradeSide.Taker, + TransferType.Trade, + ), + ).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerAllowance); + }); + it("throws if the user doesn't have enough balance", async () => { + txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount); + await zeroEx.awaitTransactionMinedAsync(txHash); + return expect( + exchangeTransferSimulator.transferFromAsync( + exampleTokenAddress, + sender, + recipient, + transferAmount, + TradeSide.Maker, + TransferType.Trade, + ), + ).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance); + }); + it('updates balances and proxyAllowance after transfer', async () => { + txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount); + await zeroEx.awaitTransactionMinedAsync(txHash); + txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount); + await zeroEx.awaitTransactionMinedAsync(txHash); + await exchangeTransferSimulator.transferFromAsync( + exampleTokenAddress, + sender, + recipient, + transferAmount, + TradeSide.Taker, + TransferType.Trade, + ); + const store = (exchangeTransferSimulator as any)._store; + const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender); + const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient); + const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender); + expect(senderBalance).to.be.bignumber.equal(0); + expect(recipientBalance).to.be.bignumber.equal(transferAmount); + expect(senderProxyAllowance).to.be.bignumber.equal(0); + }); + it("doesn't update proxyAllowance after transfer if unlimited", async () => { + txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount); + await zeroEx.awaitTransactionMinedAsync(txHash); + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(exampleTokenAddress, sender); + await zeroEx.awaitTransactionMinedAsync(txHash); + await exchangeTransferSimulator.transferFromAsync( + exampleTokenAddress, + sender, + recipient, + transferAmount, + TradeSide.Taker, + TransferType.Trade, + ); + const store = (exchangeTransferSimulator as any)._store; + const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender); + const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient); + const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender); + expect(senderBalance).to.be.bignumber.equal(0); + expect(recipientBalance).to.be.bignumber.equal(transferAmount); + expect(senderProxyAllowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + }); + }); }); diff --git a/packages/0x.js/test/exchange_wrapper_test.ts b/packages/0x.js/test/exchange_wrapper_test.ts index 7e0ffd818..f67e61a55 100644 --- a/packages/0x.js/test/exchange_wrapper_test.ts +++ b/packages/0x.js/test/exchange_wrapper_test.ts @@ -6,17 +6,17 @@ import 'mocha'; import * as Web3 from 'web3'; import { - BlockRange, - DecodedLogEvent, - ExchangeContractErrs, - ExchangeEvents, - LogCancelContractEventArgs, - LogFillContractEventArgs, - OrderCancellationRequest, - OrderFillRequest, - SignedOrder, - Token, - ZeroEx, + BlockRange, + DecodedLogEvent, + ExchangeContractErrs, + ExchangeEvents, + LogCancelContractEventArgs, + LogFillContractEventArgs, + OrderCancellationRequest, + OrderFillRequest, + SignedOrder, + Token, + ZeroEx, } from '../src'; import { BlockParamLiteral, DoneCallback } from '../src/types'; @@ -34,1114 +34,1114 @@ const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); const NON_EXISTENT_ORDER_HASH = '0x79370342234e7acd6bbeac335bd3bb1d368383294b64b8160a00f4060e4d3777'; describe('ExchangeWrapper', () => { - let web3: Web3; - let zeroEx: ZeroEx; - let tokenUtils: TokenUtils; - let tokens: Token[]; - let userAddresses: string[]; - let zrxTokenAddress: string; - let fillScenarios: FillScenarios; - let exchangeContractAddress: string; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - before(async () => { - web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, config); - exchangeContractAddress = zeroEx.exchange.getContractAddress(); - userAddresses = await zeroEx.getAvailableAddressesAsync(); - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - tokenUtils = new TokenUtils(tokens); - zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; - fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); - await fillScenarios.initTokenBalancesAsync(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('fillOrKill order(s)', () => { - let makerTokenAddress: string; - let takerTokenAddress: string; - let coinbase: string; - let makerAddress: string; - let takerAddress: string; - let feeRecipient: string; - const takerTokenFillAmount = new BigNumber(5); - before(async () => { - [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - describe('#batchFillOrKillAsync', () => { - it('successfully batch fillOrKill', async () => { - const fillableAmount = new BigNumber(5); - const partialFillTakerAmount = new BigNumber(2); - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - const anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - const orderFillRequests = [ - { - signedOrder, - takerTokenFillAmount: partialFillTakerAmount, - }, - { - signedOrder: anotherSignedOrder, - takerTokenFillAmount: partialFillTakerAmount, - }, - ]; - await zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress); - }); - describe('order transaction options', () => { - let signedOrder: SignedOrder; - let orderFillRequests: OrderFillRequest[]; - const fillableAmount = new BigNumber(5); - beforeEach(async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - orderFillRequests = [ - { - signedOrder, - takerTokenFillAmount: new BigNumber(0), - }, - ]; - }); - it('should validate when orderTransactionOptions are not present', async () => { - return expect( - zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should validate when orderTransactionOptions specify to validate', async () => { - return expect( - zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress, { - shouldValidate: true, - }), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should not validate when orderTransactionOptions specify not to validate', async () => { - return expect( - zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress, { - shouldValidate: false, - }), - ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - }); - }); - describe('#fillOrKillOrderAsync', () => { - let signedOrder: SignedOrder; - const fillableAmount = new BigNumber(5); - beforeEach(async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - }); - describe('successful fills', () => { - it('should fill a valid order', async () => { - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( - fillableAmount, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( - 0, - ); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( - 0, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( - fillableAmount, - ); - await zeroEx.exchange.fillOrKillOrderAsync(signedOrder, takerTokenFillAmount, takerAddress); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( - fillableAmount.minus(takerTokenFillAmount), - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( - takerTokenFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( - takerTokenFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( - fillableAmount.minus(takerTokenFillAmount), - ); - }); - it('should partially fill a valid order', async () => { - const partialFillAmount = new BigNumber(3); - await zeroEx.exchange.fillOrKillOrderAsync(signedOrder, partialFillAmount, takerAddress); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( - fillableAmount.minus(partialFillAmount), - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( - partialFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( - partialFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( - fillableAmount.minus(partialFillAmount), - ); - }); - }); - describe('order transaction options', () => { - const emptyFillableAmount = new BigNumber(0); - it('should validate when orderTransactionOptions are not present', async () => { - return expect( - zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should validate when orderTransactionOptions specify to validate', async () => { - return expect( - zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress, { - shouldValidate: true, - }), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should not validate when orderTransactionOptions specify not to validate', async () => { - return expect( - zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress, { - shouldValidate: false, - }), - ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - }); - }); - }); - describe('fill order(s)', () => { - let makerTokenAddress: string; - let takerTokenAddress: string; - let coinbase: string; - let makerAddress: string; - let takerAddress: string; - let feeRecipient: string; - const fillableAmount = new BigNumber(5); - const takerTokenFillAmount = new BigNumber(5); - const shouldThrowOnInsufficientBalanceOrAllowance = true; - before(async () => { - [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - describe('#fillOrderAsync', () => { - describe('successful fills', () => { - it('should fill a valid order', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( - fillableAmount, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( - 0, - ); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( - 0, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( - fillableAmount, - ); - const txHash = await zeroEx.exchange.fillOrderAsync( - signedOrder, - takerTokenFillAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( - fillableAmount.minus(takerTokenFillAmount), - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( - takerTokenFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( - takerTokenFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( - fillableAmount.minus(takerTokenFillAmount), - ); - }); - it('should partially fill the valid order', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - const partialFillAmount = new BigNumber(3); - const txHash = await zeroEx.exchange.fillOrderAsync( - signedOrder, - partialFillAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( - fillableAmount.minus(partialFillAmount), - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( - partialFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( - partialFillAmount, - ); - expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( - fillableAmount.minus(partialFillAmount), - ); - }); - it('should fill the valid orders with fees', async () => { - const makerFee = new BigNumber(1); - const takerFee = new BigNumber(2); - const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerTokenAddress, - takerTokenAddress, - makerFee, - takerFee, - makerAddress, - takerAddress, - fillableAmount, - feeRecipient, - ); - const txHash = await zeroEx.exchange.fillOrderAsync( - signedOrder, - takerTokenFillAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - expect(await zeroEx.token.getBalanceAsync(zrxTokenAddress, feeRecipient)).to.be.bignumber.equal( - makerFee.plus(takerFee), - ); - }); - }); - describe('order transaction options', () => { - let signedOrder: SignedOrder; - const emptyFillTakerAmount = new BigNumber(0); - beforeEach(async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - }); - it('should validate when orderTransactionOptions are not present', async () => { - return expect( - zeroEx.exchange.fillOrderAsync( - signedOrder, - emptyFillTakerAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should validate when orderTransactionOptions specify to validate', async () => { - return expect( - zeroEx.exchange.fillOrderAsync( - signedOrder, - emptyFillTakerAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - { - shouldValidate: true, - }, - ), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should not validate when orderTransactionOptions specify not to validate', async () => { - return expect( - zeroEx.exchange.fillOrderAsync( - signedOrder, - emptyFillTakerAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - { - shouldValidate: false, - }, - ), - ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - }); - describe('negative fill amount', async () => { - let signedOrder: SignedOrder; - const negativeFillTakerAmount = new BigNumber(-100); - beforeEach(async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - }); - it('should not allow the exchange wrapper to fill if amount is negative', async () => { - return expect( - zeroEx.exchange.fillOrderAsync( - signedOrder, - negativeFillTakerAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejected(); - }); - }); - }); - describe('#batchFillOrdersAsync', () => { - let signedOrder: SignedOrder; - let signedOrderHashHex: string; - let anotherSignedOrder: SignedOrder; - let anotherOrderHashHex: string; - let orderFillBatch: OrderFillRequest[]; - beforeEach(async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - signedOrderHashHex = ZeroEx.getOrderHashHex(signedOrder); - anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder); - }); - describe('successful batch fills', () => { - beforeEach(() => { - orderFillBatch = [ - { - signedOrder, - takerTokenFillAmount, - }, - { - signedOrder: anotherSignedOrder, - takerTokenFillAmount, - }, - ]; - }); - it('should throw if a batch is empty', async () => { - return expect( - zeroEx.exchange.batchFillOrdersAsync( - [], - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - }); - it('should successfully fill multiple orders', async () => { - const txHash = await zeroEx.exchange.batchFillOrdersAsync( - orderFillBatch, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); - const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); - expect(filledAmount).to.be.bignumber.equal(takerTokenFillAmount); - expect(anotherFilledAmount).to.be.bignumber.equal(takerTokenFillAmount); - }); - }); - describe('order transaction options', () => { - beforeEach(async () => { - const emptyFillTakerAmount = new BigNumber(0); - orderFillBatch = [ - { - signedOrder, - takerTokenFillAmount: emptyFillTakerAmount, - }, - { - signedOrder: anotherSignedOrder, - takerTokenFillAmount: emptyFillTakerAmount, - }, - ]; - }); - it('should validate when orderTransactionOptions are not present', async () => { - return expect( - zeroEx.exchange.batchFillOrdersAsync( - orderFillBatch, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should validate when orderTransactionOptions specify to validate', async () => { - return expect( - zeroEx.exchange.batchFillOrdersAsync( - orderFillBatch, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - { - shouldValidate: true, - }, - ), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should not validate when orderTransactionOptions specify not to validate', async () => { - return expect( - zeroEx.exchange.batchFillOrdersAsync( - orderFillBatch, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - { - shouldValidate: false, - }, - ), - ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - }); - describe('negative batch fill amount', async () => { - beforeEach(async () => { - const negativeFillTakerAmount = new BigNumber(-100); - orderFillBatch = [ - { - signedOrder, - takerTokenFillAmount, - }, - { - signedOrder: anotherSignedOrder, - takerTokenFillAmount: negativeFillTakerAmount, - }, - ]; - }); - it('should not allow the exchange wrapper to batch fill if any amount is negative', async () => { - return expect( - zeroEx.exchange.batchFillOrdersAsync( - orderFillBatch, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejected(); - }); - }); - }); - describe('#fillOrdersUpTo', () => { - let signedOrder: SignedOrder; - let signedOrderHashHex: string; - let anotherSignedOrder: SignedOrder; - let anotherOrderHashHex: string; - let signedOrders: SignedOrder[]; - const fillUpToAmount = fillableAmount.plus(fillableAmount).minus(1); - beforeEach(async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - signedOrderHashHex = ZeroEx.getOrderHashHex(signedOrder); - anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder); - signedOrders = [signedOrder, anotherSignedOrder]; - }); - describe('successful batch fills', () => { - it('should throw if a batch is empty', async () => { - return expect( - zeroEx.exchange.fillOrdersUpToAsync( - [], - fillUpToAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); - }); - it('should successfully fill up to specified amount when all orders are fully funded', async () => { - const txHash = await zeroEx.exchange.fillOrdersUpToAsync( - signedOrders, - fillUpToAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); - const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); - expect(filledAmount).to.be.bignumber.equal(fillableAmount); - const remainingFillAmount = fillableAmount.minus(1); - expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount); - }); - it('should successfully fill up to specified amount even if filling all orders would fail', async () => { - const missingBalance = new BigNumber(1); // User will still have enough balance to fill up to 9, - // but won't have 10 to fully fill all orders in a batch. - await zeroEx.token.transferAsync(makerTokenAddress, makerAddress, coinbase, missingBalance); - const txHash = await zeroEx.exchange.fillOrdersUpToAsync( - signedOrders, - fillUpToAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); - const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); - expect(filledAmount).to.be.bignumber.equal(fillableAmount); - const remainingFillAmount = fillableAmount.minus(1); - expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount); - }); - }); - describe('failed batch fills', () => { - it("should fail validation if user doesn't have enough balance without fill up to", async () => { - const missingBalance = new BigNumber(2); // User will only have enough balance to fill up to 8 - await zeroEx.token.transferAsync(makerTokenAddress, makerAddress, coinbase, missingBalance); - return expect( - zeroEx.exchange.fillOrdersUpToAsync( - signedOrders, - fillUpToAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance); - }); - }); - describe('order transaction options', () => { - const emptyFillUpToAmount = new BigNumber(0); - it('should validate when orderTransactionOptions are not present', async () => { - return expect( - zeroEx.exchange.fillOrdersUpToAsync( - signedOrders, - emptyFillUpToAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should validate when orderTransactionOptions specify to validate', async () => { - return expect( - zeroEx.exchange.fillOrdersUpToAsync( - signedOrders, - emptyFillUpToAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - { - shouldValidate: true, - }, - ), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should not validate when orderTransactionOptions specify not to validate', async () => { - return expect( - zeroEx.exchange.fillOrdersUpToAsync( - signedOrders, - emptyFillUpToAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - { - shouldValidate: false, - }, - ), - ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - }); - }); - }); - describe('cancel order(s)', () => { - let makerTokenAddress: string; - let takerTokenAddress: string; - let coinbase: string; - let makerAddress: string; - let takerAddress: string; - const fillableAmount = new BigNumber(5); - let signedOrder: SignedOrder; - let orderHashHex: string; - const cancelAmount = new BigNumber(3); - beforeEach(async () => { - [coinbase, makerAddress, takerAddress] = userAddresses; - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - orderHashHex = ZeroEx.getOrderHashHex(signedOrder); - }); - describe('#cancelOrderAsync', () => { - describe('successful cancels', () => { - it('should cancel an order', async () => { - const txHash = await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount); - await zeroEx.awaitTransactionMinedAsync(txHash); - const cancelledAmount = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHashHex); - expect(cancelledAmount).to.be.bignumber.equal(cancelAmount); - }); - }); - describe('order transaction options', () => { - const emptyCancelTakerTokenAmount = new BigNumber(0); - it('should validate when orderTransactionOptions are not present', async () => { - return expect( - zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount), - ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); - }); - it('should validate when orderTransactionOptions specify to validate', async () => { - return expect( - zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount, { - shouldValidate: true, - }), - ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); - }); - it('should not validate when orderTransactionOptions specify not to validate', async () => { - return expect( - zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount, { - shouldValidate: false, - }), - ).to.not.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); - }); - }); - }); - describe('#batchCancelOrdersAsync', () => { - let anotherSignedOrder: SignedOrder; - let anotherOrderHashHex: string; - let cancelBatch: OrderCancellationRequest[]; - beforeEach(async () => { - anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder); - cancelBatch = [ - { - order: signedOrder, - takerTokenCancelAmount: cancelAmount, - }, - { - order: anotherSignedOrder, - takerTokenCancelAmount: cancelAmount, - }, - ]; - }); - describe('failed batch cancels', () => { - it('should throw when orders have different makers', async () => { - const signedOrderWithDifferentMaker = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - takerAddress, - takerAddress, - fillableAmount, - ); - return expect( - zeroEx.exchange.batchCancelOrdersAsync([ - cancelBatch[0], - { - order: signedOrderWithDifferentMaker, - takerTokenCancelAmount: cancelAmount, - }, - ]), - ).to.be.rejectedWith(ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed); - }); - }); - describe('successful batch cancels', () => { - it('should cancel a batch of orders', async () => { - await zeroEx.exchange.batchCancelOrdersAsync(cancelBatch); - const cancelledAmount = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHashHex); - const anotherCancelledAmount = await zeroEx.exchange.getCancelledTakerAmountAsync( - anotherOrderHashHex, - ); - expect(cancelledAmount).to.be.bignumber.equal(cancelAmount); - expect(anotherCancelledAmount).to.be.bignumber.equal(cancelAmount); - }); - }); - describe('order transaction options', () => { - beforeEach(async () => { - const emptyTakerTokenCancelAmount = new BigNumber(0); - cancelBatch = [ - { - order: signedOrder, - takerTokenCancelAmount: emptyTakerTokenCancelAmount, - }, - { - order: anotherSignedOrder, - takerTokenCancelAmount: emptyTakerTokenCancelAmount, - }, - ]; - }); - it('should validate when orderTransactionOptions are not present', async () => { - return expect(zeroEx.exchange.batchCancelOrdersAsync(cancelBatch)).to.be.rejectedWith( - ExchangeContractErrs.OrderCancelAmountZero, - ); - }); - it('should validate when orderTransactionOptions specify to validate', async () => { - return expect( - zeroEx.exchange.batchCancelOrdersAsync(cancelBatch, { - shouldValidate: true, - }), - ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); - }); - it('should not validate when orderTransactionOptions specify not to validate', async () => { - return expect( - zeroEx.exchange.batchCancelOrdersAsync(cancelBatch, { - shouldValidate: false, - }), - ).to.not.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); - }); - }); - }); - }); - describe('tests that require partially filled order', () => { - let makerTokenAddress: string; - let takerTokenAddress: string; - let takerAddress: string; - let fillableAmount: BigNumber; - let partialFillAmount: BigNumber; - let signedOrder: SignedOrder; - let orderHash: string; - before(() => { - takerAddress = userAddresses[1]; - tokenUtils = new TokenUtils(tokens); - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - beforeEach(async () => { - fillableAmount = new BigNumber(5); - partialFillAmount = new BigNumber(2); - signedOrder = await fillScenarios.createPartiallyFilledSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - takerAddress, - fillableAmount, - partialFillAmount, - ); - orderHash = ZeroEx.getOrderHashHex(signedOrder); - }); - describe('#getUnavailableTakerAmountAsync', () => { - it('should throw if passed an invalid orderHash', async () => { - const invalidOrderHashHex = '0x123'; - return expect(zeroEx.exchange.getUnavailableTakerAmountAsync(invalidOrderHashHex)).to.be.rejected(); - }); - it('should return zero if passed a valid but non-existent orderHash', async () => { - const unavailableValueT = await zeroEx.exchange.getUnavailableTakerAmountAsync(NON_EXISTENT_ORDER_HASH); - expect(unavailableValueT).to.be.bignumber.equal(0); - }); - it('should return the unavailableValueT for a valid and partially filled orderHash', async () => { - const unavailableValueT = await zeroEx.exchange.getUnavailableTakerAmountAsync(orderHash); - expect(unavailableValueT).to.be.bignumber.equal(partialFillAmount); - }); - }); - describe('#getFilledTakerAmountAsync', () => { - it('should throw if passed an invalid orderHash', async () => { - const invalidOrderHashHex = '0x123'; - return expect(zeroEx.exchange.getFilledTakerAmountAsync(invalidOrderHashHex)).to.be.rejected(); - }); - it('should return zero if passed a valid but non-existent orderHash', async () => { - const filledValueT = await zeroEx.exchange.getFilledTakerAmountAsync(NON_EXISTENT_ORDER_HASH); - expect(filledValueT).to.be.bignumber.equal(0); - }); - it('should return the filledValueT for a valid and partially filled orderHash', async () => { - const filledValueT = await zeroEx.exchange.getFilledTakerAmountAsync(orderHash); - expect(filledValueT).to.be.bignumber.equal(partialFillAmount); - }); - }); - describe('#getCancelledTakerAmountAsync', () => { - it('should throw if passed an invalid orderHash', async () => { - const invalidOrderHashHex = '0x123'; - return expect(zeroEx.exchange.getCancelledTakerAmountAsync(invalidOrderHashHex)).to.be.rejected(); - }); - it('should return zero if passed a valid but non-existent orderHash', async () => { - const cancelledValueT = await zeroEx.exchange.getCancelledTakerAmountAsync(NON_EXISTENT_ORDER_HASH); - expect(cancelledValueT).to.be.bignumber.equal(0); - }); - it('should return the cancelledValueT for a valid and partially filled orderHash', async () => { - const cancelledValueT = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHash); - expect(cancelledValueT).to.be.bignumber.equal(0); - }); - it('should return the cancelledValueT for a valid and cancelled orderHash', async () => { - const cancelAmount = fillableAmount.minus(partialFillAmount); - await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount); - const cancelledValueT = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHash); - expect(cancelledValueT).to.be.bignumber.equal(cancelAmount); - }); - }); - }); - describe('#subscribe', () => { - const indexFilterValues = {}; - const shouldThrowOnInsufficientBalanceOrAllowance = true; - let makerTokenAddress: string; - let takerTokenAddress: string; - let coinbase: string; - let takerAddress: string; - let makerAddress: string; - let fillableAmount: BigNumber; - let signedOrder: SignedOrder; - const takerTokenFillAmountInBaseUnits = new BigNumber(1); - const cancelTakerAmountInBaseUnits = new BigNumber(1); - before(() => { - [coinbase, makerAddress, takerAddress] = userAddresses; - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - beforeEach(async () => { - fillableAmount = new BigNumber(5); - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - }); - afterEach(async () => { - zeroEx.exchange.unsubscribeAll(); - }); - // Hack: Mocha does not allow a test to be both async and have a `done` callback - // Since we need to await the receipt of the event in the `subscribe` callback, - // we do need both. A hack is to make the top-level a sync fn w/ a done callback and then - // wrap the rest of the test in an async block - // Source: https://github.com/mochajs/mocha/issues/2407 - it('Should receive the LogFill event when an order is filled', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { - expect(logEvent.log.event).to.be.equal(ExchangeEvents.LogFill); - }, - ); - zeroEx.exchange.subscribe(ExchangeEvents.LogFill, indexFilterValues, callback); - await zeroEx.exchange.fillOrderAsync( - signedOrder, - takerTokenFillAmountInBaseUnits, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - })().catch(done); - }); - it('Should receive the LogCancel event when an order is cancelled', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<LogCancelContractEventArgs>) => { - expect(logEvent.log.event).to.be.equal(ExchangeEvents.LogCancel); - }, - ); - zeroEx.exchange.subscribe(ExchangeEvents.LogCancel, indexFilterValues, callback); - await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerAmountInBaseUnits); - })().catch(done); - }); - it('Outstanding subscriptions are cancelled when zeroEx.setProvider called', (done: DoneCallback) => { - (async () => { - const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { - done(new Error('Expected this subscription to have been cancelled')); - }, - ); - zeroEx.exchange.subscribe(ExchangeEvents.LogFill, indexFilterValues, callbackNeverToBeCalled); + let web3: Web3; + let zeroEx: ZeroEx; + let tokenUtils: TokenUtils; + let tokens: Token[]; + let userAddresses: string[]; + let zrxTokenAddress: string; + let fillScenarios: FillScenarios; + let exchangeContractAddress: string; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + before(async () => { + web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, config); + exchangeContractAddress = zeroEx.exchange.getContractAddress(); + userAddresses = await zeroEx.getAvailableAddressesAsync(); + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + tokenUtils = new TokenUtils(tokens); + zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; + fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); + await fillScenarios.initTokenBalancesAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('fillOrKill order(s)', () => { + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let makerAddress: string; + let takerAddress: string; + let feeRecipient: string; + const takerTokenFillAmount = new BigNumber(5); + before(async () => { + [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + describe('#batchFillOrKillAsync', () => { + it('successfully batch fillOrKill', async () => { + const fillableAmount = new BigNumber(5); + const partialFillTakerAmount = new BigNumber(2); + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + const anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + const orderFillRequests = [ + { + signedOrder, + takerTokenFillAmount: partialFillTakerAmount, + }, + { + signedOrder: anotherSignedOrder, + takerTokenFillAmount: partialFillTakerAmount, + }, + ]; + await zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress); + }); + describe('order transaction options', () => { + let signedOrder: SignedOrder; + let orderFillRequests: OrderFillRequest[]; + const fillableAmount = new BigNumber(5); + beforeEach(async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + orderFillRequests = [ + { + signedOrder, + takerTokenFillAmount: new BigNumber(0), + }, + ]; + }); + it('should validate when orderTransactionOptions are not present', async () => { + return expect( + zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should validate when orderTransactionOptions specify to validate', async () => { + return expect( + zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress, { + shouldValidate: true, + }), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should not validate when orderTransactionOptions specify not to validate', async () => { + return expect( + zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress, { + shouldValidate: false, + }), + ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + }); + }); + describe('#fillOrKillOrderAsync', () => { + let signedOrder: SignedOrder; + const fillableAmount = new BigNumber(5); + beforeEach(async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + }); + describe('successful fills', () => { + it('should fill a valid order', async () => { + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( + fillableAmount, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( + 0, + ); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( + 0, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( + fillableAmount, + ); + await zeroEx.exchange.fillOrKillOrderAsync(signedOrder, takerTokenFillAmount, takerAddress); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( + fillableAmount.minus(takerTokenFillAmount), + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( + takerTokenFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( + takerTokenFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( + fillableAmount.minus(takerTokenFillAmount), + ); + }); + it('should partially fill a valid order', async () => { + const partialFillAmount = new BigNumber(3); + await zeroEx.exchange.fillOrKillOrderAsync(signedOrder, partialFillAmount, takerAddress); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( + fillableAmount.minus(partialFillAmount), + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( + partialFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( + partialFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( + fillableAmount.minus(partialFillAmount), + ); + }); + }); + describe('order transaction options', () => { + const emptyFillableAmount = new BigNumber(0); + it('should validate when orderTransactionOptions are not present', async () => { + return expect( + zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should validate when orderTransactionOptions specify to validate', async () => { + return expect( + zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress, { + shouldValidate: true, + }), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should not validate when orderTransactionOptions specify not to validate', async () => { + return expect( + zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress, { + shouldValidate: false, + }), + ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + }); + }); + }); + describe('fill order(s)', () => { + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let makerAddress: string; + let takerAddress: string; + let feeRecipient: string; + const fillableAmount = new BigNumber(5); + const takerTokenFillAmount = new BigNumber(5); + const shouldThrowOnInsufficientBalanceOrAllowance = true; + before(async () => { + [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + describe('#fillOrderAsync', () => { + describe('successful fills', () => { + it('should fill a valid order', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( + fillableAmount, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( + 0, + ); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( + 0, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( + fillableAmount, + ); + const txHash = await zeroEx.exchange.fillOrderAsync( + signedOrder, + takerTokenFillAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( + fillableAmount.minus(takerTokenFillAmount), + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( + takerTokenFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( + takerTokenFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( + fillableAmount.minus(takerTokenFillAmount), + ); + }); + it('should partially fill the valid order', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + const partialFillAmount = new BigNumber(3); + const txHash = await zeroEx.exchange.fillOrderAsync( + signedOrder, + partialFillAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress)).to.be.bignumber.equal( + fillableAmount.minus(partialFillAmount), + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress)).to.be.bignumber.equal( + partialFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress)).to.be.bignumber.equal( + partialFillAmount, + ); + expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress)).to.be.bignumber.equal( + fillableAmount.minus(partialFillAmount), + ); + }); + it('should fill the valid orders with fees', async () => { + const makerFee = new BigNumber(1); + const takerFee = new BigNumber(2); + const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerTokenAddress, + takerTokenAddress, + makerFee, + takerFee, + makerAddress, + takerAddress, + fillableAmount, + feeRecipient, + ); + const txHash = await zeroEx.exchange.fillOrderAsync( + signedOrder, + takerTokenFillAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + expect(await zeroEx.token.getBalanceAsync(zrxTokenAddress, feeRecipient)).to.be.bignumber.equal( + makerFee.plus(takerFee), + ); + }); + }); + describe('order transaction options', () => { + let signedOrder: SignedOrder; + const emptyFillTakerAmount = new BigNumber(0); + beforeEach(async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + }); + it('should validate when orderTransactionOptions are not present', async () => { + return expect( + zeroEx.exchange.fillOrderAsync( + signedOrder, + emptyFillTakerAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should validate when orderTransactionOptions specify to validate', async () => { + return expect( + zeroEx.exchange.fillOrderAsync( + signedOrder, + emptyFillTakerAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + { + shouldValidate: true, + }, + ), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should not validate when orderTransactionOptions specify not to validate', async () => { + return expect( + zeroEx.exchange.fillOrderAsync( + signedOrder, + emptyFillTakerAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + { + shouldValidate: false, + }, + ), + ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + }); + describe('negative fill amount', async () => { + let signedOrder: SignedOrder; + const negativeFillTakerAmount = new BigNumber(-100); + beforeEach(async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + }); + it('should not allow the exchange wrapper to fill if amount is negative', async () => { + return expect( + zeroEx.exchange.fillOrderAsync( + signedOrder, + negativeFillTakerAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejected(); + }); + }); + }); + describe('#batchFillOrdersAsync', () => { + let signedOrder: SignedOrder; + let signedOrderHashHex: string; + let anotherSignedOrder: SignedOrder; + let anotherOrderHashHex: string; + let orderFillBatch: OrderFillRequest[]; + beforeEach(async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + signedOrderHashHex = ZeroEx.getOrderHashHex(signedOrder); + anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder); + }); + describe('successful batch fills', () => { + beforeEach(() => { + orderFillBatch = [ + { + signedOrder, + takerTokenFillAmount, + }, + { + signedOrder: anotherSignedOrder, + takerTokenFillAmount, + }, + ]; + }); + it('should throw if a batch is empty', async () => { + return expect( + zeroEx.exchange.batchFillOrdersAsync( + [], + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); + }); + it('should successfully fill multiple orders', async () => { + const txHash = await zeroEx.exchange.batchFillOrdersAsync( + orderFillBatch, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); + const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); + expect(filledAmount).to.be.bignumber.equal(takerTokenFillAmount); + expect(anotherFilledAmount).to.be.bignumber.equal(takerTokenFillAmount); + }); + }); + describe('order transaction options', () => { + beforeEach(async () => { + const emptyFillTakerAmount = new BigNumber(0); + orderFillBatch = [ + { + signedOrder, + takerTokenFillAmount: emptyFillTakerAmount, + }, + { + signedOrder: anotherSignedOrder, + takerTokenFillAmount: emptyFillTakerAmount, + }, + ]; + }); + it('should validate when orderTransactionOptions are not present', async () => { + return expect( + zeroEx.exchange.batchFillOrdersAsync( + orderFillBatch, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should validate when orderTransactionOptions specify to validate', async () => { + return expect( + zeroEx.exchange.batchFillOrdersAsync( + orderFillBatch, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + { + shouldValidate: true, + }, + ), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should not validate when orderTransactionOptions specify not to validate', async () => { + return expect( + zeroEx.exchange.batchFillOrdersAsync( + orderFillBatch, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + { + shouldValidate: false, + }, + ), + ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + }); + describe('negative batch fill amount', async () => { + beforeEach(async () => { + const negativeFillTakerAmount = new BigNumber(-100); + orderFillBatch = [ + { + signedOrder, + takerTokenFillAmount, + }, + { + signedOrder: anotherSignedOrder, + takerTokenFillAmount: negativeFillTakerAmount, + }, + ]; + }); + it('should not allow the exchange wrapper to batch fill if any amount is negative', async () => { + return expect( + zeroEx.exchange.batchFillOrdersAsync( + orderFillBatch, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejected(); + }); + }); + }); + describe('#fillOrdersUpTo', () => { + let signedOrder: SignedOrder; + let signedOrderHashHex: string; + let anotherSignedOrder: SignedOrder; + let anotherOrderHashHex: string; + let signedOrders: SignedOrder[]; + const fillUpToAmount = fillableAmount.plus(fillableAmount).minus(1); + beforeEach(async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + signedOrderHashHex = ZeroEx.getOrderHashHex(signedOrder); + anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder); + signedOrders = [signedOrder, anotherSignedOrder]; + }); + describe('successful batch fills', () => { + it('should throw if a batch is empty', async () => { + return expect( + zeroEx.exchange.fillOrdersUpToAsync( + [], + fillUpToAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem); + }); + it('should successfully fill up to specified amount when all orders are fully funded', async () => { + const txHash = await zeroEx.exchange.fillOrdersUpToAsync( + signedOrders, + fillUpToAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); + const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); + expect(filledAmount).to.be.bignumber.equal(fillableAmount); + const remainingFillAmount = fillableAmount.minus(1); + expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount); + }); + it('should successfully fill up to specified amount even if filling all orders would fail', async () => { + const missingBalance = new BigNumber(1); // User will still have enough balance to fill up to 9, + // but won't have 10 to fully fill all orders in a batch. + await zeroEx.token.transferAsync(makerTokenAddress, makerAddress, coinbase, missingBalance); + const txHash = await zeroEx.exchange.fillOrdersUpToAsync( + signedOrders, + fillUpToAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); + const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); + expect(filledAmount).to.be.bignumber.equal(fillableAmount); + const remainingFillAmount = fillableAmount.minus(1); + expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount); + }); + }); + describe('failed batch fills', () => { + it("should fail validation if user doesn't have enough balance without fill up to", async () => { + const missingBalance = new BigNumber(2); // User will only have enough balance to fill up to 8 + await zeroEx.token.transferAsync(makerTokenAddress, makerAddress, coinbase, missingBalance); + return expect( + zeroEx.exchange.fillOrdersUpToAsync( + signedOrders, + fillUpToAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance); + }); + }); + describe('order transaction options', () => { + const emptyFillUpToAmount = new BigNumber(0); + it('should validate when orderTransactionOptions are not present', async () => { + return expect( + zeroEx.exchange.fillOrdersUpToAsync( + signedOrders, + emptyFillUpToAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should validate when orderTransactionOptions specify to validate', async () => { + return expect( + zeroEx.exchange.fillOrdersUpToAsync( + signedOrders, + emptyFillUpToAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + { + shouldValidate: true, + }, + ), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should not validate when orderTransactionOptions specify not to validate', async () => { + return expect( + zeroEx.exchange.fillOrdersUpToAsync( + signedOrders, + emptyFillUpToAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + { + shouldValidate: false, + }, + ), + ).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + }); + }); + }); + describe('cancel order(s)', () => { + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let makerAddress: string; + let takerAddress: string; + const fillableAmount = new BigNumber(5); + let signedOrder: SignedOrder; + let orderHashHex: string; + const cancelAmount = new BigNumber(3); + beforeEach(async () => { + [coinbase, makerAddress, takerAddress] = userAddresses; + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + orderHashHex = ZeroEx.getOrderHashHex(signedOrder); + }); + describe('#cancelOrderAsync', () => { + describe('successful cancels', () => { + it('should cancel an order', async () => { + const txHash = await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount); + await zeroEx.awaitTransactionMinedAsync(txHash); + const cancelledAmount = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHashHex); + expect(cancelledAmount).to.be.bignumber.equal(cancelAmount); + }); + }); + describe('order transaction options', () => { + const emptyCancelTakerTokenAmount = new BigNumber(0); + it('should validate when orderTransactionOptions are not present', async () => { + return expect( + zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount), + ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); + }); + it('should validate when orderTransactionOptions specify to validate', async () => { + return expect( + zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount, { + shouldValidate: true, + }), + ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); + }); + it('should not validate when orderTransactionOptions specify not to validate', async () => { + return expect( + zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount, { + shouldValidate: false, + }), + ).to.not.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); + }); + }); + }); + describe('#batchCancelOrdersAsync', () => { + let anotherSignedOrder: SignedOrder; + let anotherOrderHashHex: string; + let cancelBatch: OrderCancellationRequest[]; + beforeEach(async () => { + anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder); + cancelBatch = [ + { + order: signedOrder, + takerTokenCancelAmount: cancelAmount, + }, + { + order: anotherSignedOrder, + takerTokenCancelAmount: cancelAmount, + }, + ]; + }); + describe('failed batch cancels', () => { + it('should throw when orders have different makers', async () => { + const signedOrderWithDifferentMaker = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + takerAddress, + takerAddress, + fillableAmount, + ); + return expect( + zeroEx.exchange.batchCancelOrdersAsync([ + cancelBatch[0], + { + order: signedOrderWithDifferentMaker, + takerTokenCancelAmount: cancelAmount, + }, + ]), + ).to.be.rejectedWith(ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed); + }); + }); + describe('successful batch cancels', () => { + it('should cancel a batch of orders', async () => { + await zeroEx.exchange.batchCancelOrdersAsync(cancelBatch); + const cancelledAmount = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHashHex); + const anotherCancelledAmount = await zeroEx.exchange.getCancelledTakerAmountAsync( + anotherOrderHashHex, + ); + expect(cancelledAmount).to.be.bignumber.equal(cancelAmount); + expect(anotherCancelledAmount).to.be.bignumber.equal(cancelAmount); + }); + }); + describe('order transaction options', () => { + beforeEach(async () => { + const emptyTakerTokenCancelAmount = new BigNumber(0); + cancelBatch = [ + { + order: signedOrder, + takerTokenCancelAmount: emptyTakerTokenCancelAmount, + }, + { + order: anotherSignedOrder, + takerTokenCancelAmount: emptyTakerTokenCancelAmount, + }, + ]; + }); + it('should validate when orderTransactionOptions are not present', async () => { + return expect(zeroEx.exchange.batchCancelOrdersAsync(cancelBatch)).to.be.rejectedWith( + ExchangeContractErrs.OrderCancelAmountZero, + ); + }); + it('should validate when orderTransactionOptions specify to validate', async () => { + return expect( + zeroEx.exchange.batchCancelOrdersAsync(cancelBatch, { + shouldValidate: true, + }), + ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); + }); + it('should not validate when orderTransactionOptions specify not to validate', async () => { + return expect( + zeroEx.exchange.batchCancelOrdersAsync(cancelBatch, { + shouldValidate: false, + }), + ).to.not.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); + }); + }); + }); + }); + describe('tests that require partially filled order', () => { + let makerTokenAddress: string; + let takerTokenAddress: string; + let takerAddress: string; + let fillableAmount: BigNumber; + let partialFillAmount: BigNumber; + let signedOrder: SignedOrder; + let orderHash: string; + before(() => { + takerAddress = userAddresses[1]; + tokenUtils = new TokenUtils(tokens); + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + beforeEach(async () => { + fillableAmount = new BigNumber(5); + partialFillAmount = new BigNumber(2); + signedOrder = await fillScenarios.createPartiallyFilledSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + takerAddress, + fillableAmount, + partialFillAmount, + ); + orderHash = ZeroEx.getOrderHashHex(signedOrder); + }); + describe('#getUnavailableTakerAmountAsync', () => { + it('should throw if passed an invalid orderHash', async () => { + const invalidOrderHashHex = '0x123'; + return expect(zeroEx.exchange.getUnavailableTakerAmountAsync(invalidOrderHashHex)).to.be.rejected(); + }); + it('should return zero if passed a valid but non-existent orderHash', async () => { + const unavailableValueT = await zeroEx.exchange.getUnavailableTakerAmountAsync(NON_EXISTENT_ORDER_HASH); + expect(unavailableValueT).to.be.bignumber.equal(0); + }); + it('should return the unavailableValueT for a valid and partially filled orderHash', async () => { + const unavailableValueT = await zeroEx.exchange.getUnavailableTakerAmountAsync(orderHash); + expect(unavailableValueT).to.be.bignumber.equal(partialFillAmount); + }); + }); + describe('#getFilledTakerAmountAsync', () => { + it('should throw if passed an invalid orderHash', async () => { + const invalidOrderHashHex = '0x123'; + return expect(zeroEx.exchange.getFilledTakerAmountAsync(invalidOrderHashHex)).to.be.rejected(); + }); + it('should return zero if passed a valid but non-existent orderHash', async () => { + const filledValueT = await zeroEx.exchange.getFilledTakerAmountAsync(NON_EXISTENT_ORDER_HASH); + expect(filledValueT).to.be.bignumber.equal(0); + }); + it('should return the filledValueT for a valid and partially filled orderHash', async () => { + const filledValueT = await zeroEx.exchange.getFilledTakerAmountAsync(orderHash); + expect(filledValueT).to.be.bignumber.equal(partialFillAmount); + }); + }); + describe('#getCancelledTakerAmountAsync', () => { + it('should throw if passed an invalid orderHash', async () => { + const invalidOrderHashHex = '0x123'; + return expect(zeroEx.exchange.getCancelledTakerAmountAsync(invalidOrderHashHex)).to.be.rejected(); + }); + it('should return zero if passed a valid but non-existent orderHash', async () => { + const cancelledValueT = await zeroEx.exchange.getCancelledTakerAmountAsync(NON_EXISTENT_ORDER_HASH); + expect(cancelledValueT).to.be.bignumber.equal(0); + }); + it('should return the cancelledValueT for a valid and partially filled orderHash', async () => { + const cancelledValueT = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHash); + expect(cancelledValueT).to.be.bignumber.equal(0); + }); + it('should return the cancelledValueT for a valid and cancelled orderHash', async () => { + const cancelAmount = fillableAmount.minus(partialFillAmount); + await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount); + const cancelledValueT = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHash); + expect(cancelledValueT).to.be.bignumber.equal(cancelAmount); + }); + }); + }); + describe('#subscribe', () => { + const indexFilterValues = {}; + const shouldThrowOnInsufficientBalanceOrAllowance = true; + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let takerAddress: string; + let makerAddress: string; + let fillableAmount: BigNumber; + let signedOrder: SignedOrder; + const takerTokenFillAmountInBaseUnits = new BigNumber(1); + const cancelTakerAmountInBaseUnits = new BigNumber(1); + before(() => { + [coinbase, makerAddress, takerAddress] = userAddresses; + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + beforeEach(async () => { + fillableAmount = new BigNumber(5); + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + }); + afterEach(async () => { + zeroEx.exchange.unsubscribeAll(); + }); + // Hack: Mocha does not allow a test to be both async and have a `done` callback + // Since we need to await the receipt of the event in the `subscribe` callback, + // we do need both. A hack is to make the top-level a sync fn w/ a done callback and then + // wrap the rest of the test in an async block + // Source: https://github.com/mochajs/mocha/issues/2407 + it('Should receive the LogFill event when an order is filled', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { + expect(logEvent.log.event).to.be.equal(ExchangeEvents.LogFill); + }, + ); + zeroEx.exchange.subscribe(ExchangeEvents.LogFill, indexFilterValues, callback); + await zeroEx.exchange.fillOrderAsync( + signedOrder, + takerTokenFillAmountInBaseUnits, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + })().catch(done); + }); + it('Should receive the LogCancel event when an order is cancelled', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<LogCancelContractEventArgs>) => { + expect(logEvent.log.event).to.be.equal(ExchangeEvents.LogCancel); + }, + ); + zeroEx.exchange.subscribe(ExchangeEvents.LogCancel, indexFilterValues, callback); + await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerAmountInBaseUnits); + })().catch(done); + }); + it('Outstanding subscriptions are cancelled when zeroEx.setProvider called', (done: DoneCallback) => { + (async () => { + const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { + done(new Error('Expected this subscription to have been cancelled')); + }, + ); + zeroEx.exchange.subscribe(ExchangeEvents.LogFill, indexFilterValues, callbackNeverToBeCalled); - const newProvider = web3Factory.getRpcProvider(); - zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); + const newProvider = web3Factory.getRpcProvider(); + zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { - expect(logEvent.log.event).to.be.equal(ExchangeEvents.LogFill); - }, - ); - zeroEx.exchange.subscribe(ExchangeEvents.LogFill, indexFilterValues, callback); - await zeroEx.exchange.fillOrderAsync( - signedOrder, - takerTokenFillAmountInBaseUnits, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - })().catch(done); - }); - it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => { - (async () => { - const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { - done(new Error('Expected this subscription to have been cancelled')); - }, - ); - const subscriptionToken = zeroEx.exchange.subscribe( - ExchangeEvents.LogFill, - indexFilterValues, - callbackNeverToBeCalled, - ); - zeroEx.exchange.unsubscribe(subscriptionToken); - await zeroEx.exchange.fillOrderAsync( - signedOrder, - takerTokenFillAmountInBaseUnits, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - done(); - })().catch(done); - }); - }); - describe('#getOrderHashHexUsingContractCallAsync', () => { - let makerTokenAddress: string; - let takerTokenAddress: string; - let makerAddress: string; - let takerAddress: string; - const fillableAmount = new BigNumber(5); - before(async () => { - [, makerAddress, takerAddress] = userAddresses; - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - it("get's the same hash as the local function", async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - const orderHashFromContract = await (zeroEx.exchange as any)._getOrderHashHexUsingContractCallAsync( - signedOrder, - ); - expect(orderHash).to.equal(orderHashFromContract); - }); - }); - describe('#getZRXTokenAddressAsync', () => { - it('gets the same token as is in token registry', () => { - const zrxAddress = zeroEx.exchange.getZRXTokenAddress(); - const zrxToken = tokenUtils.getProtocolTokenOrThrow(); - expect(zrxAddress).to.equal(zrxToken.address); - }); - }); - describe('#getLogsAsync', () => { - let makerTokenAddress: string; - let takerTokenAddress: string; - let makerAddress: string; - let takerAddress: string; - const fillableAmount = new BigNumber(5); - const shouldThrowOnInsufficientBalanceOrAllowance = true; - const blockRange: BlockRange = { - fromBlock: 0, - toBlock: BlockParamLiteral.Latest, - }; - let txHash: string; - before(async () => { - [, makerAddress, takerAddress] = userAddresses; - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - it('should get logs with decoded args emitted by LogFill', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - txHash = await zeroEx.exchange.fillOrderAsync( - signedOrder, - fillableAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - const eventName = ExchangeEvents.LogFill; - const indexFilterValues = {}; - const logs = await zeroEx.exchange.getLogsAsync(eventName, blockRange, indexFilterValues); - expect(logs).to.have.length(1); - expect(logs[0].event).to.be.equal(eventName); - }); - it('should only get the logs with the correct event name', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - txHash = await zeroEx.exchange.fillOrderAsync( - signedOrder, - fillableAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - const differentEventName = ExchangeEvents.LogCancel; - const indexFilterValues = {}; - const logs = await zeroEx.exchange.getLogsAsync(differentEventName, blockRange, indexFilterValues); - expect(logs).to.have.length(0); - }); - it('should only get the logs with the correct indexed fields', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - txHash = await zeroEx.exchange.fillOrderAsync( - signedOrder, - fillableAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { + expect(logEvent.log.event).to.be.equal(ExchangeEvents.LogFill); + }, + ); + zeroEx.exchange.subscribe(ExchangeEvents.LogFill, indexFilterValues, callback); + await zeroEx.exchange.fillOrderAsync( + signedOrder, + takerTokenFillAmountInBaseUnits, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + })().catch(done); + }); + it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => { + (async () => { + const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<LogFillContractEventArgs>) => { + done(new Error('Expected this subscription to have been cancelled')); + }, + ); + const subscriptionToken = zeroEx.exchange.subscribe( + ExchangeEvents.LogFill, + indexFilterValues, + callbackNeverToBeCalled, + ); + zeroEx.exchange.unsubscribe(subscriptionToken); + await zeroEx.exchange.fillOrderAsync( + signedOrder, + takerTokenFillAmountInBaseUnits, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + done(); + })().catch(done); + }); + }); + describe('#getOrderHashHexUsingContractCallAsync', () => { + let makerTokenAddress: string; + let takerTokenAddress: string; + let makerAddress: string; + let takerAddress: string; + const fillableAmount = new BigNumber(5); + before(async () => { + [, makerAddress, takerAddress] = userAddresses; + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + it("get's the same hash as the local function", async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + const orderHashFromContract = await (zeroEx.exchange as any)._getOrderHashHexUsingContractCallAsync( + signedOrder, + ); + expect(orderHash).to.equal(orderHashFromContract); + }); + }); + describe('#getZRXTokenAddressAsync', () => { + it('gets the same token as is in token registry', () => { + const zrxAddress = zeroEx.exchange.getZRXTokenAddress(); + const zrxToken = tokenUtils.getProtocolTokenOrThrow(); + expect(zrxAddress).to.equal(zrxToken.address); + }); + }); + describe('#getLogsAsync', () => { + let makerTokenAddress: string; + let takerTokenAddress: string; + let makerAddress: string; + let takerAddress: string; + const fillableAmount = new BigNumber(5); + const shouldThrowOnInsufficientBalanceOrAllowance = true; + const blockRange: BlockRange = { + fromBlock: 0, + toBlock: BlockParamLiteral.Latest, + }; + let txHash: string; + before(async () => { + [, makerAddress, takerAddress] = userAddresses; + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + it('should get logs with decoded args emitted by LogFill', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + txHash = await zeroEx.exchange.fillOrderAsync( + signedOrder, + fillableAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + const eventName = ExchangeEvents.LogFill; + const indexFilterValues = {}; + const logs = await zeroEx.exchange.getLogsAsync(eventName, blockRange, indexFilterValues); + expect(logs).to.have.length(1); + expect(logs[0].event).to.be.equal(eventName); + }); + it('should only get the logs with the correct event name', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + txHash = await zeroEx.exchange.fillOrderAsync( + signedOrder, + fillableAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); + const differentEventName = ExchangeEvents.LogCancel; + const indexFilterValues = {}; + const logs = await zeroEx.exchange.getLogsAsync(differentEventName, blockRange, indexFilterValues); + expect(logs).to.have.length(0); + }); + it('should only get the logs with the correct indexed fields', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + txHash = await zeroEx.exchange.fillOrderAsync( + signedOrder, + fillableAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); - const differentMakerAddress = userAddresses[2]; - const anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - differentMakerAddress, - takerAddress, - fillableAmount, - ); - txHash = await zeroEx.exchange.fillOrderAsync( - anotherSignedOrder, - fillableAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); + const differentMakerAddress = userAddresses[2]; + const anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + differentMakerAddress, + takerAddress, + fillableAmount, + ); + txHash = await zeroEx.exchange.fillOrderAsync( + anotherSignedOrder, + fillableAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + await zeroEx.awaitTransactionMinedAsync(txHash); - const eventName = ExchangeEvents.LogFill; - const indexFilterValues = { - maker: differentMakerAddress, - }; - const logs = await zeroEx.exchange.getLogsAsync<LogFillContractEventArgs>( - eventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(1); - const args = logs[0].args; - expect(args.maker).to.be.equal(differentMakerAddress); - }); - }); + const eventName = ExchangeEvents.LogFill; + const indexFilterValues = { + maker: differentMakerAddress, + }; + const logs = await zeroEx.exchange.getLogsAsync<LogFillContractEventArgs>( + eventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(1); + const args = logs[0].args; + expect(args.maker).to.be.equal(differentMakerAddress); + }); + }); }); // tslint:disable:max-file-line-count diff --git a/packages/0x.js/test/expiration_watcher_test.ts b/packages/0x.js/test/expiration_watcher_test.ts index 770615f88..a76d84da1 100644 --- a/packages/0x.js/test/expiration_watcher_test.ts +++ b/packages/0x.js/test/expiration_watcher_test.ts @@ -24,137 +24,137 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(testConstants.RPC_URL); describe('ExpirationWatcher', () => { - let web3: Web3; - let zeroEx: ZeroEx; - let tokenUtils: TokenUtils; - let tokens: Token[]; - let userAddresses: string[]; - let zrxTokenAddress: string; - let fillScenarios: FillScenarios; - let exchangeContractAddress: string; - let makerTokenAddress: string; - let takerTokenAddress: string; - let coinbase: string; - let makerAddress: string; - let takerAddress: string; - let feeRecipient: string; - const fillableAmount = new BigNumber(5); - let currentUnixTimestampSec: BigNumber; - let timer: Sinon.SinonFakeTimers; - let expirationWatcher: ExpirationWatcher; - before(async () => { - web3 = web3Factory.create(); - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - zeroEx = new ZeroEx(web3.currentProvider, config); - exchangeContractAddress = zeroEx.exchange.getContractAddress(); - userAddresses = await zeroEx.getAvailableAddressesAsync(); - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - tokenUtils = new TokenUtils(tokens); - zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; - fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); - [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - const sinonTimerConfig = { shouldAdvanceTime: true } as any; - // This constructor has incorrect types - timer = Sinon.useFakeTimers(sinonTimerConfig); - currentUnixTimestampSec = utils.getCurrentUnixTimestampSec(); - expirationWatcher = new ExpirationWatcher(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - timer.restore(); - expirationWatcher.unsubscribe(); - }); - it('correctly emits events when order expires', (done: DoneCallback) => { - (async () => { - const orderLifetimeSec = 60; - const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec); - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - expirationUnixTimestampSec, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000)); - const callbackAsync = reportNoErrorCallbackErrors(done)((hash: string) => { - expect(hash).to.be.equal(orderHash); - expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.gte(expirationUnixTimestampSec); - }); - expirationWatcher.subscribe(callbackAsync); - timer.tick(orderLifetimeSec * 1000); - })().catch(done); - }); - it("doesn't emit events before order expires", (done: DoneCallback) => { - (async () => { - const orderLifetimeSec = 60; - const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec); - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - expirationUnixTimestampSec, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000)); - const callbackAsync = reportNoErrorCallbackErrors(done)(async (hash: string) => { - done(new Error('Emitted expiration went before the order actually expired')); - }); - expirationWatcher.subscribe(callbackAsync); - const notEnoughTime = orderLifetimeSec - 1; - timer.tick(notEnoughTime * 1000); - done(); - })().catch(done); - }); - it('emits events in correct order', (done: DoneCallback) => { - (async () => { - const order1Lifetime = 60; - const order2Lifetime = 120; - const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime); - const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime); - const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - order1ExpirationUnixTimestampSec, - ); - const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - order2ExpirationUnixTimestampSec, - ); - const orderHash1 = ZeroEx.getOrderHashHex(signedOrder1); - const orderHash2 = ZeroEx.getOrderHashHex(signedOrder2); - expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000)); - expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000)); - const expirationOrder = [orderHash1, orderHash2]; - const expectToBeCalledOnce = false; - const callbackAsync = reportNoErrorCallbackErrors(done, expectToBeCalledOnce)((hash: string) => { - const orderHash = expirationOrder.shift(); - expect(hash).to.be.equal(orderHash); - if (_.isEmpty(expirationOrder)) { - done(); - } - }); - expirationWatcher.subscribe(callbackAsync); - timer.tick(order2Lifetime * 1000); - })().catch(done); - }); + let web3: Web3; + let zeroEx: ZeroEx; + let tokenUtils: TokenUtils; + let tokens: Token[]; + let userAddresses: string[]; + let zrxTokenAddress: string; + let fillScenarios: FillScenarios; + let exchangeContractAddress: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let makerAddress: string; + let takerAddress: string; + let feeRecipient: string; + const fillableAmount = new BigNumber(5); + let currentUnixTimestampSec: BigNumber; + let timer: Sinon.SinonFakeTimers; + let expirationWatcher: ExpirationWatcher; + before(async () => { + web3 = web3Factory.create(); + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + zeroEx = new ZeroEx(web3.currentProvider, config); + exchangeContractAddress = zeroEx.exchange.getContractAddress(); + userAddresses = await zeroEx.getAvailableAddressesAsync(); + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + tokenUtils = new TokenUtils(tokens); + zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; + fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); + [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + const sinonTimerConfig = { shouldAdvanceTime: true } as any; + // This constructor has incorrect types + timer = Sinon.useFakeTimers(sinonTimerConfig); + currentUnixTimestampSec = utils.getCurrentUnixTimestampSec(); + expirationWatcher = new ExpirationWatcher(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + timer.restore(); + expirationWatcher.unsubscribe(); + }); + it('correctly emits events when order expires', (done: DoneCallback) => { + (async () => { + const orderLifetimeSec = 60; + const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec); + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + expirationUnixTimestampSec, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000)); + const callbackAsync = reportNoErrorCallbackErrors(done)((hash: string) => { + expect(hash).to.be.equal(orderHash); + expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.gte(expirationUnixTimestampSec); + }); + expirationWatcher.subscribe(callbackAsync); + timer.tick(orderLifetimeSec * 1000); + })().catch(done); + }); + it("doesn't emit events before order expires", (done: DoneCallback) => { + (async () => { + const orderLifetimeSec = 60; + const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec); + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + expirationUnixTimestampSec, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000)); + const callbackAsync = reportNoErrorCallbackErrors(done)(async (hash: string) => { + done(new Error('Emitted expiration went before the order actually expired')); + }); + expirationWatcher.subscribe(callbackAsync); + const notEnoughTime = orderLifetimeSec - 1; + timer.tick(notEnoughTime * 1000); + done(); + })().catch(done); + }); + it('emits events in correct order', (done: DoneCallback) => { + (async () => { + const order1Lifetime = 60; + const order2Lifetime = 120; + const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime); + const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime); + const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + order1ExpirationUnixTimestampSec, + ); + const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + order2ExpirationUnixTimestampSec, + ); + const orderHash1 = ZeroEx.getOrderHashHex(signedOrder1); + const orderHash2 = ZeroEx.getOrderHashHex(signedOrder2); + expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000)); + expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000)); + const expirationOrder = [orderHash1, orderHash2]; + const expectToBeCalledOnce = false; + const callbackAsync = reportNoErrorCallbackErrors(done, expectToBeCalledOnce)((hash: string) => { + const orderHash = expirationOrder.shift(); + expect(hash).to.be.equal(orderHash); + if (_.isEmpty(expirationOrder)) { + done(); + } + }); + expirationWatcher.subscribe(callbackAsync); + timer.tick(order2Lifetime * 1000); + })().catch(done); + }); }); diff --git a/packages/0x.js/test/order_state_watcher_test.ts b/packages/0x.js/test/order_state_watcher_test.ts index 2e9202fe2..22ed80446 100644 --- a/packages/0x.js/test/order_state_watcher_test.ts +++ b/packages/0x.js/test/order_state_watcher_test.ts @@ -6,14 +6,14 @@ import 'mocha'; import * as Web3 from 'web3'; import { - ExchangeContractErrs, - OrderState, - OrderStateInvalid, - OrderStateValid, - SignedOrder, - Token, - ZeroEx, - ZeroExError, + ExchangeContractErrs, + OrderState, + OrderStateInvalid, + OrderStateValid, + SignedOrder, + Token, + ZeroEx, + ZeroExError, } from '../src'; import { DoneCallback } from '../src/types'; @@ -31,528 +31,528 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); describe('OrderStateWatcher', () => { - let web3: Web3; - let zeroEx: ZeroEx; - let tokens: Token[]; - let tokenUtils: TokenUtils; - let fillScenarios: FillScenarios; - let userAddresses: string[]; - let zrxTokenAddress: string; - let exchangeContractAddress: string; - let makerToken: Token; - let takerToken: Token; - let maker: string; - let taker: string; - let signedOrder: SignedOrder; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - const decimals = constants.ZRX_DECIMALS; - const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals); - before(async () => { - web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, config); - exchangeContractAddress = zeroEx.exchange.getContractAddress(); - userAddresses = await zeroEx.getAvailableAddressesAsync(); - [, maker, taker] = userAddresses; - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - tokenUtils = new TokenUtils(tokens); - zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; - fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); - await fillScenarios.initTokenBalancesAsync(); - [makerToken, takerToken] = tokenUtils.getDummyTokens(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('#removeOrder', async () => { - it('should successfully remove existing order', async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); - expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.include({ - [orderHash]: signedOrder, - }); - let dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes; - expect(dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress]).to.have.keys(orderHash); - zeroEx.orderStateWatcher.removeOrder(orderHash); - expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.not.include({ - [orderHash]: signedOrder, - }); - dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes; - expect(dependentOrderHashes[signedOrder.maker]).to.be.undefined(); - }); - it('should no-op when removing a non-existing order', async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - const nonExistentOrderHash = `0x${orderHash - .substr(2) - .split('') - .reverse() - .join('')}`; - zeroEx.orderStateWatcher.removeOrder(nonExistentOrderHash); - }); - }); - describe('#subscribe', async () => { - afterEach(async () => { - zeroEx.orderStateWatcher.unsubscribe(); - }); - it('should fail when trying to subscribe twice', async () => { - zeroEx.orderStateWatcher.subscribe(_.noop); - expect(() => zeroEx.orderStateWatcher.subscribe(_.noop)).to.throw(ZeroExError.SubscriptionAlreadyPresent); - }); - }); - describe('tests with cleanup', async () => { - afterEach(async () => { - zeroEx.orderStateWatcher.unsubscribe(); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.removeOrder(orderHash); - }); - it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.false(); - const invalidOrderState = orderState as OrderStateInvalid; - expect(invalidOrderState.orderHash).to.be.equal(orderHash); - expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, new BigNumber(0)); - })().catch(done); - }); - it('should not emit an orderState event when irrelevant Transfer event received', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - throw new Error('OrderState callback fired for irrelevant order'); - }); - zeroEx.orderStateWatcher.subscribe(callback); - const notTheMaker = userAddresses[0]; - const anyRecipient = taker; - const transferAmount = new BigNumber(2); - await zeroEx.token.transferAsync(makerToken.address, notTheMaker, anyRecipient, transferAmount); - setTimeout(() => { - done(); - }, TIMEOUT_MS); - })().catch(done); - }); - it('should emit orderStateInvalid when maker moves balance backing watched order', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.false(); - const invalidOrderState = orderState as OrderStateInvalid; - expect(invalidOrderState.orderHash).to.be.equal(orderHash); - expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance); - }); - zeroEx.orderStateWatcher.subscribe(callback); - const anyRecipient = taker; - const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); - await zeroEx.token.transferAsync(makerToken.address, maker, anyRecipient, makerBalance); - })().catch(done); - }); - it('should emit orderStateInvalid when watched order fully filled', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + let web3: Web3; + let zeroEx: ZeroEx; + let tokens: Token[]; + let tokenUtils: TokenUtils; + let fillScenarios: FillScenarios; + let userAddresses: string[]; + let zrxTokenAddress: string; + let exchangeContractAddress: string; + let makerToken: Token; + let takerToken: Token; + let maker: string; + let taker: string; + let signedOrder: SignedOrder; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + const decimals = constants.ZRX_DECIMALS; + const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals); + before(async () => { + web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, config); + exchangeContractAddress = zeroEx.exchange.getContractAddress(); + userAddresses = await zeroEx.getAvailableAddressesAsync(); + [, maker, taker] = userAddresses; + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + tokenUtils = new TokenUtils(tokens); + zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; + fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); + await fillScenarios.initTokenBalancesAsync(); + [makerToken, takerToken] = tokenUtils.getDummyTokens(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#removeOrder', async () => { + it('should successfully remove existing order', async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); + expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.include({ + [orderHash]: signedOrder, + }); + let dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes; + expect(dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress]).to.have.keys(orderHash); + zeroEx.orderStateWatcher.removeOrder(orderHash); + expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.not.include({ + [orderHash]: signedOrder, + }); + dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes; + expect(dependentOrderHashes[signedOrder.maker]).to.be.undefined(); + }); + it('should no-op when removing a non-existing order', async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + const nonExistentOrderHash = `0x${orderHash + .substr(2) + .split('') + .reverse() + .join('')}`; + zeroEx.orderStateWatcher.removeOrder(nonExistentOrderHash); + }); + }); + describe('#subscribe', async () => { + afterEach(async () => { + zeroEx.orderStateWatcher.unsubscribe(); + }); + it('should fail when trying to subscribe twice', async () => { + zeroEx.orderStateWatcher.subscribe(_.noop); + expect(() => zeroEx.orderStateWatcher.subscribe(_.noop)).to.throw(ZeroExError.SubscriptionAlreadyPresent); + }); + }); + describe('tests with cleanup', async () => { + afterEach(async () => { + zeroEx.orderStateWatcher.unsubscribe(); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.removeOrder(orderHash); + }); + it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, new BigNumber(0)); + })().catch(done); + }); + it('should not emit an orderState event when irrelevant Transfer event received', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + zeroEx.orderStateWatcher.addOrder(signedOrder); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + throw new Error('OrderState callback fired for irrelevant order'); + }); + zeroEx.orderStateWatcher.subscribe(callback); + const notTheMaker = userAddresses[0]; + const anyRecipient = taker; + const transferAmount = new BigNumber(2); + await zeroEx.token.transferAsync(makerToken.address, notTheMaker, anyRecipient, transferAmount); + setTimeout(() => { + done(); + }, TIMEOUT_MS); + })().catch(done); + }); + it('should emit orderStateInvalid when maker moves balance backing watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance); + }); + zeroEx.orderStateWatcher.subscribe(callback); + const anyRecipient = taker; + const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); + await zeroEx.token.transferAsync(makerToken.address, maker, anyRecipient, makerBalance); + })().catch(done); + }); + it('should emit orderStateInvalid when watched order fully filled', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.false(); - const invalidOrderState = orderState as OrderStateInvalid; - expect(invalidOrderState.orderHash).to.be.equal(orderHash); - expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero); - }); - zeroEx.orderStateWatcher.subscribe(callback); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero); + }); + zeroEx.orderStateWatcher.subscribe(callback); - const shouldThrowOnInsufficientBalanceOrAllowance = true; - await zeroEx.exchange.fillOrderAsync( - signedOrder, - fillableAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - taker, - ); - })().catch(done); - }); - it('should emit orderStateValid when watched order partially filled', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); + const shouldThrowOnInsufficientBalanceOrAllowance = true; + await zeroEx.exchange.fillOrderAsync( + signedOrder, + fillableAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + taker, + ); + })().catch(done); + }); + it('should emit orderStateValid when watched order partially filled', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); - const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); - const fillAmountInBaseUnits = new BigNumber(2); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); + const fillAmountInBaseUnits = new BigNumber(2); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.true(); - const validOrderState = orderState as OrderStateValid; - expect(validOrderState.orderHash).to.be.equal(orderHash); - const orderRelevantState = validOrderState.orderRelevantState; - const remainingMakerBalance = makerBalance.sub(fillAmountInBaseUnits); - const remainingFillable = fillableAmount.minus(fillAmountInBaseUnits); - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( - remainingFillable, - ); - expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( - remainingFillable, - ); - expect(orderRelevantState.makerBalance).to.be.bignumber.equal(remainingMakerBalance); - }); - zeroEx.orderStateWatcher.subscribe(callback); - const shouldThrowOnInsufficientBalanceOrAllowance = true; - await zeroEx.exchange.fillOrderAsync( - signedOrder, - fillAmountInBaseUnits, - shouldThrowOnInsufficientBalanceOrAllowance, - taker, - ); - })().catch(done); - }); - it('should trigger the callback when orders backing ZRX allowance changes', (done: DoneCallback) => { - (async () => { - const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18); - const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), 18); - signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerToken.address, - takerToken.address, - makerFee, - takerFee, - maker, - taker, - fillableAmount, - taker, - ); - const callback = reportNodeCallbackErrors(done)(); - zeroEx.orderStateWatcher.addOrder(signedOrder); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, new BigNumber(0)); - })().catch(done); - }); - describe('remainingFillable(M|T)akerTokenAmount', () => { - it('should calculate correct remaining fillable', (done: DoneCallback) => { - (async () => { - const takerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(10), decimals); - const makerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(20), decimals); - signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - makerFillableAmount, - takerFillableAmount, - ); - const fillAmountInBaseUnits = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.true(); - const validOrderState = orderState as OrderStateValid; - expect(validOrderState.orderHash).to.be.equal(orderHash); - const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( - ZeroEx.toBaseUnitAmount(new BigNumber(16), decimals), - ); - expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( - ZeroEx.toBaseUnitAmount(new BigNumber(8), decimals), - ); - }); - zeroEx.orderStateWatcher.subscribe(callback); - const shouldThrowOnInsufficientBalanceOrAllowance = true; - await zeroEx.exchange.fillOrderAsync( - signedOrder, - fillAmountInBaseUnits, - shouldThrowOnInsufficientBalanceOrAllowance, - taker, - ); - })().catch(done); - }); - it('should equal approved amount when approved amount is lowest', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.true(); + const validOrderState = orderState as OrderStateValid; + expect(validOrderState.orderHash).to.be.equal(orderHash); + const orderRelevantState = validOrderState.orderRelevantState; + const remainingMakerBalance = makerBalance.sub(fillAmountInBaseUnits); + const remainingFillable = fillableAmount.minus(fillAmountInBaseUnits); + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + remainingFillable, + ); + expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + remainingFillable, + ); + expect(orderRelevantState.makerBalance).to.be.bignumber.equal(remainingMakerBalance); + }); + zeroEx.orderStateWatcher.subscribe(callback); + const shouldThrowOnInsufficientBalanceOrAllowance = true; + await zeroEx.exchange.fillOrderAsync( + signedOrder, + fillAmountInBaseUnits, + shouldThrowOnInsufficientBalanceOrAllowance, + taker, + ); + })().catch(done); + }); + it('should trigger the callback when orders backing ZRX allowance changes', (done: DoneCallback) => { + (async () => { + const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18); + const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), 18); + signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerToken.address, + takerToken.address, + makerFee, + takerFee, + maker, + taker, + fillableAmount, + taker, + ); + const callback = reportNodeCallbackErrors(done)(); + zeroEx.orderStateWatcher.addOrder(signedOrder); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, new BigNumber(0)); + })().catch(done); + }); + describe('remainingFillable(M|T)akerTokenAmount', () => { + it('should calculate correct remaining fillable', (done: DoneCallback) => { + (async () => { + const takerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(10), decimals); + const makerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(20), decimals); + signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + makerFillableAmount, + takerFillableAmount, + ); + const fillAmountInBaseUnits = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.true(); + const validOrderState = orderState as OrderStateValid; + expect(validOrderState.orderHash).to.be.equal(orderHash); + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + ZeroEx.toBaseUnitAmount(new BigNumber(16), decimals), + ); + expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + ZeroEx.toBaseUnitAmount(new BigNumber(8), decimals), + ); + }); + zeroEx.orderStateWatcher.subscribe(callback); + const shouldThrowOnInsufficientBalanceOrAllowance = true; + await zeroEx.exchange.fillOrderAsync( + signedOrder, + fillAmountInBaseUnits, + shouldThrowOnInsufficientBalanceOrAllowance, + taker, + ); + })().catch(done); + }); + it('should equal approved amount when approved amount is lowest', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); - const changedMakerApprovalAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals); - zeroEx.orderStateWatcher.addOrder(signedOrder); + const changedMakerApprovalAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - const validOrderState = orderState as OrderStateValid; - const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( - changedMakerApprovalAmount, - ); - expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( - changedMakerApprovalAmount, - ); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, changedMakerApprovalAmount); - })().catch(done); - }); - it('should equal balance amount when balance amount is lowest', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + const validOrderState = orderState as OrderStateValid; + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + changedMakerApprovalAmount, + ); + expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + changedMakerApprovalAmount, + ); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, changedMakerApprovalAmount); + })().catch(done); + }); + it('should equal balance amount when balance amount is lowest', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); - const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); + const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); - const remainingAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals); - const transferAmount = makerBalance.sub(remainingAmount); - zeroEx.orderStateWatcher.addOrder(signedOrder); + const remainingAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals); + const transferAmount = makerBalance.sub(remainingAmount); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.true(); - const validOrderState = orderState as OrderStateValid; - const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( - remainingAmount, - ); - expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( - remainingAmount, - ); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.token.transferAsync(makerToken.address, maker, ZeroEx.NULL_ADDRESS, transferAmount); - })().catch(done); - }); - it('should equal remaining amount when partially cancelled and order has fees', (done: DoneCallback) => { - (async () => { - const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals); - const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals); - const feeRecipient = taker; - signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerToken.address, - takerToken.address, - makerFee, - takerFee, - maker, - taker, - fillableAmount, - feeRecipient, - ); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.true(); + const validOrderState = orderState as OrderStateValid; + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + remainingAmount, + ); + expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + remainingAmount, + ); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.token.transferAsync(makerToken.address, maker, ZeroEx.NULL_ADDRESS, transferAmount); + })().catch(done); + }); + it('should equal remaining amount when partially cancelled and order has fees', (done: DoneCallback) => { + (async () => { + const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals); + const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals); + const feeRecipient = taker; + signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerToken.address, + takerToken.address, + makerFee, + takerFee, + maker, + taker, + fillableAmount, + feeRecipient, + ); - const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals); - const transferTokenAmount = makerFee.sub(remainingTokenAmount); - zeroEx.orderStateWatcher.addOrder(signedOrder); + const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals); + const transferTokenAmount = makerFee.sub(remainingTokenAmount); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.true(); - const validOrderState = orderState as OrderStateValid; - const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( - remainingTokenAmount, - ); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.exchange.cancelOrderAsync(signedOrder, transferTokenAmount); - })().catch(done); - }); - it('should equal ratio amount when fee balance is lowered', (done: DoneCallback) => { - (async () => { - const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals); - const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals); - const feeRecipient = taker; - signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerToken.address, - takerToken.address, - makerFee, - takerFee, - maker, - taker, - fillableAmount, - feeRecipient, - ); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.true(); + const validOrderState = orderState as OrderStateValid; + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + remainingTokenAmount, + ); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.exchange.cancelOrderAsync(signedOrder, transferTokenAmount); + })().catch(done); + }); + it('should equal ratio amount when fee balance is lowered', (done: DoneCallback) => { + (async () => { + const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals); + const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals); + const feeRecipient = taker; + signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerToken.address, + takerToken.address, + makerFee, + takerFee, + maker, + taker, + fillableAmount, + feeRecipient, + ); - const remainingFeeAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals); + const remainingFeeAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals); - const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals); - const transferTokenAmount = makerFee.sub(remainingTokenAmount); - zeroEx.orderStateWatcher.addOrder(signedOrder); + const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals); + const transferTokenAmount = makerFee.sub(remainingTokenAmount); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - const validOrderState = orderState as OrderStateValid; - const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( - remainingFeeAmount, - ); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, remainingFeeAmount); - await zeroEx.token.transferAsync( - makerToken.address, - maker, - ZeroEx.NULL_ADDRESS, - transferTokenAmount, - ); - })().catch(done); - }); - it('should calculate full amount when all available and non-divisible', (done: DoneCallback) => { - (async () => { - const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals); - const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); - const feeRecipient = taker; - signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerToken.address, - takerToken.address, - makerFee, - takerFee, - maker, - taker, - fillableAmount, - feeRecipient, - ); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + const validOrderState = orderState as OrderStateValid; + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + remainingFeeAmount, + ); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, remainingFeeAmount); + await zeroEx.token.transferAsync( + makerToken.address, + maker, + ZeroEx.NULL_ADDRESS, + transferTokenAmount, + ); + })().catch(done); + }); + it('should calculate full amount when all available and non-divisible', (done: DoneCallback) => { + (async () => { + const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals); + const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); + const feeRecipient = taker; + signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerToken.address, + takerToken.address, + makerFee, + takerFee, + maker, + taker, + fillableAmount, + feeRecipient, + ); - zeroEx.orderStateWatcher.addOrder(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - const validOrderState = orderState as OrderStateValid; - const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( - fillableAmount, - ); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.token.setProxyAllowanceAsync( - makerToken.address, - maker, - ZeroEx.toBaseUnitAmount(new BigNumber(100), decimals), - ); - })().catch(done); - }); - }); - it('should emit orderStateInvalid when watched order cancelled', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + const validOrderState = orderState as OrderStateValid; + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + fillableAmount, + ); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.token.setProxyAllowanceAsync( + makerToken.address, + maker, + ZeroEx.toBaseUnitAmount(new BigNumber(100), decimals), + ); + })().catch(done); + }); + }); + it('should emit orderStateInvalid when watched order cancelled', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.false(); - const invalidOrderState = orderState as OrderStateInvalid; - expect(invalidOrderState.orderHash).to.be.equal(orderHash); - expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero); - }); - zeroEx.orderStateWatcher.subscribe(callback); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero); + }); + zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); - })().catch(done); - }); - it('should emit orderStateInvalid when within rounding error range', (done: DoneCallback) => { - (async () => { - const remainingFillableAmountInBaseUnits = new BigNumber(100); - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); + })().catch(done); + }); + it('should emit orderStateInvalid when within rounding error range', (done: DoneCallback) => { + (async () => { + const remainingFillableAmountInBaseUnits = new BigNumber(100); + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.false(); - const invalidOrderState = orderState as OrderStateInvalid; - expect(invalidOrderState.orderHash).to.be.equal(orderHash); - expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.exchange.cancelOrderAsync( - signedOrder, - fillableAmount.minus(remainingFillableAmountInBaseUnits), - ); - })().catch(done); - }); - it('should emit orderStateValid when watched order partially cancelled', (done: DoneCallback) => { - (async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.exchange.cancelOrderAsync( + signedOrder, + fillableAmount.minus(remainingFillableAmountInBaseUnits), + ); + })().catch(done); + }); + it('should emit orderStateValid when watched order partially cancelled', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); - const cancelAmountInBaseUnits = new BigNumber(2); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + const cancelAmountInBaseUnits = new BigNumber(2); + const orderHash = ZeroEx.getOrderHashHex(signedOrder); + zeroEx.orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - expect(orderState.isValid).to.be.true(); - const validOrderState = orderState as OrderStateValid; - expect(validOrderState.orderHash).to.be.equal(orderHash); - const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.cancelledTakerTokenAmount).to.be.bignumber.equal(cancelAmountInBaseUnits); - }); - zeroEx.orderStateWatcher.subscribe(callback); - await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmountInBaseUnits); - })().catch(done); - }); - }); + const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.true(); + const validOrderState = orderState as OrderStateValid; + expect(validOrderState.orderHash).to.be.equal(orderHash); + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.cancelledTakerTokenAmount).to.be.bignumber.equal(cancelAmountInBaseUnits); + }); + zeroEx.orderStateWatcher.subscribe(callback); + await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmountInBaseUnits); + })().catch(done); + }); + }); }); // tslint:disable:max-file-line-count diff --git a/packages/0x.js/test/order_validation_test.ts b/packages/0x.js/test/order_validation_test.ts index be3e0590c..4e023a9cc 100644 --- a/packages/0x.js/test/order_validation_test.ts +++ b/packages/0x.js/test/order_validation_test.ts @@ -20,455 +20,455 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); describe('OrderValidation', () => { - let web3: Web3; - let zeroEx: ZeroEx; - let userAddresses: string[]; - let tokens: Token[]; - let tokenUtils: TokenUtils; - let exchangeContractAddress: string; - let zrxTokenAddress: string; - let fillScenarios: FillScenarios; - let makerTokenAddress: string; - let takerTokenAddress: string; - let coinbase: string; - let makerAddress: string; - let takerAddress: string; - let feeRecipient: string; - const fillableAmount = new BigNumber(5); - const fillTakerAmount = new BigNumber(5); - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - before(async () => { - web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, config); - exchangeContractAddress = zeroEx.exchange.getContractAddress(); - userAddresses = await zeroEx.getAvailableAddressesAsync(); - [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - tokenUtils = new TokenUtils(tokens); - zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; - fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('validateOrderFillableOrThrowAsync', () => { - it('should succeed if the order is fillable', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder); - }); - it('should succeed if the order is asymmetric and fillable', async () => { - const makerFillableAmount = fillableAmount; - const takerFillableAmount = fillableAmount.minus(4); - const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - makerFillableAmount, - takerFillableAmount, - ); - await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder); - }); - it('should throw when the order is fully filled or cancelled', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); - return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith( - ExchangeContractErrs.OrderRemainingFillAmountZero, - ); - }); - it('should throw when order is expired', async () => { - const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017 - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - expirationInPast, - ); - return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith( - ExchangeContractErrs.OrderFillExpired, - ); - }); - }); - describe('validateFillOrderAndThrowIfInvalidAsync', () => { - it('should throw when the fill amount is zero', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - const zeroFillAmount = new BigNumber(0); - return expect( - zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, zeroFillAmount, takerAddress), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); - }); - it('should throw when the signature is invalid', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - // 27 <--> 28 - signedOrder.ecSignature.v = 28 - signedOrder.ecSignature.v + 27; - return expect( - zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillableAmount, takerAddress), - ).to.be.rejectedWith(ZeroExError.InvalidSignature); - }); - it('should throw when the order is fully filled or cancelled', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); - return expect( - zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillableAmount, takerAddress), - ).to.be.rejectedWith(ExchangeContractErrs.OrderRemainingFillAmountZero); - }); - it('should throw when sender is not a taker', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - const nonTakerAddress = userAddresses[6]; - return expect( - zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerAmount, nonTakerAddress), - ).to.be.rejectedWith(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker); - }); - it('should throw when order is expired', async () => { - const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017 - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - expirationInPast, - ); - return expect( - zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerAmount, takerAddress), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillExpired); - }); - it('should throw when there a rounding error would have occurred', async () => { - const makerAmount = new BigNumber(3); - const takerAmount = new BigNumber(5); - const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - makerAmount, - takerAmount, - ); - const fillTakerAmountThatCausesRoundingError = new BigNumber(3); - return expect( - zeroEx.exchange.validateFillOrderThrowIfInvalidAsync( - signedOrder, - fillTakerAmountThatCausesRoundingError, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.OrderFillRoundingError); - }); - }); - describe('#validateFillOrKillOrderAndThrowIfInvalidAsync', () => { - it('should throw if remaining fillAmount is less then the desired fillAmount', async () => { - const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - const tooLargeFillAmount = new BigNumber(7); - const fillAmountDifference = tooLargeFillAmount.minus(fillableAmount); - await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, fillAmountDifference); - await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, tooLargeFillAmount); - await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, fillAmountDifference); - await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, tooLargeFillAmount); + let web3: Web3; + let zeroEx: ZeroEx; + let userAddresses: string[]; + let tokens: Token[]; + let tokenUtils: TokenUtils; + let exchangeContractAddress: string; + let zrxTokenAddress: string; + let fillScenarios: FillScenarios; + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let makerAddress: string; + let takerAddress: string; + let feeRecipient: string; + const fillableAmount = new BigNumber(5); + const fillTakerAmount = new BigNumber(5); + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + before(async () => { + web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, config); + exchangeContractAddress = zeroEx.exchange.getContractAddress(); + userAddresses = await zeroEx.getAvailableAddressesAsync(); + [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + tokenUtils = new TokenUtils(tokens); + zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; + fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('validateOrderFillableOrThrowAsync', () => { + it('should succeed if the order is fillable', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder); + }); + it('should succeed if the order is asymmetric and fillable', async () => { + const makerFillableAmount = fillableAmount; + const takerFillableAmount = fillableAmount.minus(4); + const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + makerFillableAmount, + takerFillableAmount, + ); + await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder); + }); + it('should throw when the order is fully filled or cancelled', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); + return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith( + ExchangeContractErrs.OrderRemainingFillAmountZero, + ); + }); + it('should throw when order is expired', async () => { + const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017 + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + expirationInPast, + ); + return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith( + ExchangeContractErrs.OrderFillExpired, + ); + }); + }); + describe('validateFillOrderAndThrowIfInvalidAsync', () => { + it('should throw when the fill amount is zero', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + const zeroFillAmount = new BigNumber(0); + return expect( + zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, zeroFillAmount, takerAddress), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero); + }); + it('should throw when the signature is invalid', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + // 27 <--> 28 + signedOrder.ecSignature.v = 28 - signedOrder.ecSignature.v + 27; + return expect( + zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillableAmount, takerAddress), + ).to.be.rejectedWith(ZeroExError.InvalidSignature); + }); + it('should throw when the order is fully filled or cancelled', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); + return expect( + zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillableAmount, takerAddress), + ).to.be.rejectedWith(ExchangeContractErrs.OrderRemainingFillAmountZero); + }); + it('should throw when sender is not a taker', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + const nonTakerAddress = userAddresses[6]; + return expect( + zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerAmount, nonTakerAddress), + ).to.be.rejectedWith(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker); + }); + it('should throw when order is expired', async () => { + const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017 + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + expirationInPast, + ); + return expect( + zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerAmount, takerAddress), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillExpired); + }); + it('should throw when there a rounding error would have occurred', async () => { + const makerAmount = new BigNumber(3); + const takerAmount = new BigNumber(5); + const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + makerAmount, + takerAmount, + ); + const fillTakerAmountThatCausesRoundingError = new BigNumber(3); + return expect( + zeroEx.exchange.validateFillOrderThrowIfInvalidAsync( + signedOrder, + fillTakerAmountThatCausesRoundingError, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.OrderFillRoundingError); + }); + }); + describe('#validateFillOrKillOrderAndThrowIfInvalidAsync', () => { + it('should throw if remaining fillAmount is less then the desired fillAmount', async () => { + const signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + const tooLargeFillAmount = new BigNumber(7); + const fillAmountDifference = tooLargeFillAmount.minus(fillableAmount); + await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, fillAmountDifference); + await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, tooLargeFillAmount); + await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, fillAmountDifference); + await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, tooLargeFillAmount); - return expect( - zeroEx.exchange.validateFillOrKillOrderThrowIfInvalidAsync( - signedOrder, - tooLargeFillAmount, - takerAddress, - ), - ).to.be.rejectedWith(ExchangeContractErrs.InsufficientRemainingFillAmount); - }); - }); - describe('validateCancelOrderAndThrowIfInvalidAsync', () => { - let signedOrder: SignedOrder; - const cancelAmount = new BigNumber(3); - beforeEach(async () => { - [coinbase, makerAddress, takerAddress] = userAddresses; - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - ); - }); - it('should throw when cancel amount is zero', async () => { - const zeroCancelAmount = new BigNumber(0); - return expect( - zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, zeroCancelAmount), - ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); - }); - it('should throw when order is expired', async () => { - const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017 - const expiredSignedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - expirationInPast, - ); - return expect( - zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(expiredSignedOrder, cancelAmount), - ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelExpired); - }); - it('should throw when order is already cancelled or filled', async () => { - await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); - return expect( - zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, fillableAmount), - ).to.be.rejectedWith(ExchangeContractErrs.OrderAlreadyCancelledOrFilled); - }); - }); - describe('#validateFillOrderBalancesAllowancesThrowIfInvalidAsync', () => { - let exchangeTransferSimulator: ExchangeTransferSimulator; - let transferFromAsync: Sinon.SinonSpy; - const bigNumberMatch = (expected: BigNumber) => { - return Sinon.match((value: BigNumber) => value.eq(expected)); - }; - beforeEach('create exchangeTransferSimulator', async () => { - exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest); - transferFromAsync = Sinon.spy(); - exchangeTransferSimulator.transferFromAsync = transferFromAsync as any; - }); - it('should call exchangeTransferSimulator.transferFrom in a correct order', async () => { - const makerFee = new BigNumber(2); - const takerFee = new BigNumber(2); - const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerTokenAddress, - takerTokenAddress, - makerFee, - takerFee, - makerAddress, - takerAddress, - fillableAmount, - feeRecipient, - ); - await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( - exchangeTransferSimulator, - signedOrder, - fillableAmount, - takerAddress, - zrxTokenAddress, - ); - expect(transferFromAsync.callCount).to.be.equal(4); - expect( - transferFromAsync - .getCall(0) - .calledWith( - makerTokenAddress, - makerAddress, - takerAddress, - bigNumberMatch(fillableAmount), - TradeSide.Maker, - TransferType.Trade, - ), - ).to.be.true(); - expect( - transferFromAsync - .getCall(1) - .calledWith( - takerTokenAddress, - takerAddress, - makerAddress, - bigNumberMatch(fillableAmount), - TradeSide.Taker, - TransferType.Trade, - ), - ).to.be.true(); - expect( - transferFromAsync - .getCall(2) - .calledWith( - zrxTokenAddress, - makerAddress, - feeRecipient, - bigNumberMatch(makerFee), - TradeSide.Maker, - TransferType.Fee, - ), - ).to.be.true(); - expect( - transferFromAsync - .getCall(3) - .calledWith( - zrxTokenAddress, - takerAddress, - feeRecipient, - bigNumberMatch(takerFee), - TradeSide.Taker, - TransferType.Fee, - ), - ).to.be.true(); - }); - it('should call exchangeTransferSimulator.transferFrom with correct values for an open order', async () => { - const makerFee = new BigNumber(2); - const takerFee = new BigNumber(2); - const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerTokenAddress, - takerTokenAddress, - makerFee, - takerFee, - makerAddress, - ZeroEx.NULL_ADDRESS, - fillableAmount, - feeRecipient, - ); - await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( - exchangeTransferSimulator, - signedOrder, - fillableAmount, - takerAddress, - zrxTokenAddress, - ); - expect(transferFromAsync.callCount).to.be.equal(4); - expect( - transferFromAsync - .getCall(0) - .calledWith( - makerTokenAddress, - makerAddress, - takerAddress, - bigNumberMatch(fillableAmount), - TradeSide.Maker, - TransferType.Trade, - ), - ).to.be.true(); - expect( - transferFromAsync - .getCall(1) - .calledWith( - takerTokenAddress, - takerAddress, - makerAddress, - bigNumberMatch(fillableAmount), - TradeSide.Taker, - TransferType.Trade, - ), - ).to.be.true(); - expect( - transferFromAsync - .getCall(2) - .calledWith( - zrxTokenAddress, - makerAddress, - feeRecipient, - bigNumberMatch(makerFee), - TradeSide.Maker, - TransferType.Fee, - ), - ).to.be.true(); - expect( - transferFromAsync - .getCall(3) - .calledWith( - zrxTokenAddress, - takerAddress, - feeRecipient, - bigNumberMatch(takerFee), - TradeSide.Taker, - TransferType.Fee, - ), - ).to.be.true(); - }); - it('should correctly round the fillMakerTokenAmount', async () => { - const makerTokenAmount = new BigNumber(3); - const takerTokenAmount = new BigNumber(1); - const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - makerTokenAmount, - takerTokenAmount, - ); - await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( - exchangeTransferSimulator, - signedOrder, - takerTokenAmount, - takerAddress, - zrxTokenAddress, - ); - expect(transferFromAsync.callCount).to.be.equal(4); - const makerFillAmount = transferFromAsync.getCall(0).args[3]; - expect(makerFillAmount).to.be.bignumber.equal(makerTokenAmount); - }); - it('should correctly round the makerFeeAmount', async () => { - const makerFee = new BigNumber(2); - const takerFee = new BigNumber(4); - const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerTokenAddress, - takerTokenAddress, - makerFee, - takerFee, - makerAddress, - takerAddress, - fillableAmount, - ZeroEx.NULL_ADDRESS, - ); - const fillTakerTokenAmount = fillableAmount.div(2).round(0); - await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( - exchangeTransferSimulator, - signedOrder, - fillTakerTokenAmount, - takerAddress, - zrxTokenAddress, - ); - const makerPartialFee = makerFee.div(2); - const takerPartialFee = takerFee.div(2); - expect(transferFromAsync.callCount).to.be.equal(4); - const partialMakerFee = transferFromAsync.getCall(2).args[3]; - expect(partialMakerFee).to.be.bignumber.equal(makerPartialFee); - const partialTakerFee = transferFromAsync.getCall(3).args[3]; - expect(partialTakerFee).to.be.bignumber.equal(takerPartialFee); - }); - }); + return expect( + zeroEx.exchange.validateFillOrKillOrderThrowIfInvalidAsync( + signedOrder, + tooLargeFillAmount, + takerAddress, + ), + ).to.be.rejectedWith(ExchangeContractErrs.InsufficientRemainingFillAmount); + }); + }); + describe('validateCancelOrderAndThrowIfInvalidAsync', () => { + let signedOrder: SignedOrder; + const cancelAmount = new BigNumber(3); + beforeEach(async () => { + [coinbase, makerAddress, takerAddress] = userAddresses; + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + ); + }); + it('should throw when cancel amount is zero', async () => { + const zeroCancelAmount = new BigNumber(0); + return expect( + zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, zeroCancelAmount), + ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero); + }); + it('should throw when order is expired', async () => { + const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017 + const expiredSignedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + expirationInPast, + ); + return expect( + zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(expiredSignedOrder, cancelAmount), + ).to.be.rejectedWith(ExchangeContractErrs.OrderCancelExpired); + }); + it('should throw when order is already cancelled or filled', async () => { + await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); + return expect( + zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, fillableAmount), + ).to.be.rejectedWith(ExchangeContractErrs.OrderAlreadyCancelledOrFilled); + }); + }); + describe('#validateFillOrderBalancesAllowancesThrowIfInvalidAsync', () => { + let exchangeTransferSimulator: ExchangeTransferSimulator; + let transferFromAsync: Sinon.SinonSpy; + const bigNumberMatch = (expected: BigNumber) => { + return Sinon.match((value: BigNumber) => value.eq(expected)); + }; + beforeEach('create exchangeTransferSimulator', async () => { + exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest); + transferFromAsync = Sinon.spy(); + exchangeTransferSimulator.transferFromAsync = transferFromAsync as any; + }); + it('should call exchangeTransferSimulator.transferFrom in a correct order', async () => { + const makerFee = new BigNumber(2); + const takerFee = new BigNumber(2); + const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerTokenAddress, + takerTokenAddress, + makerFee, + takerFee, + makerAddress, + takerAddress, + fillableAmount, + feeRecipient, + ); + await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( + exchangeTransferSimulator, + signedOrder, + fillableAmount, + takerAddress, + zrxTokenAddress, + ); + expect(transferFromAsync.callCount).to.be.equal(4); + expect( + transferFromAsync + .getCall(0) + .calledWith( + makerTokenAddress, + makerAddress, + takerAddress, + bigNumberMatch(fillableAmount), + TradeSide.Maker, + TransferType.Trade, + ), + ).to.be.true(); + expect( + transferFromAsync + .getCall(1) + .calledWith( + takerTokenAddress, + takerAddress, + makerAddress, + bigNumberMatch(fillableAmount), + TradeSide.Taker, + TransferType.Trade, + ), + ).to.be.true(); + expect( + transferFromAsync + .getCall(2) + .calledWith( + zrxTokenAddress, + makerAddress, + feeRecipient, + bigNumberMatch(makerFee), + TradeSide.Maker, + TransferType.Fee, + ), + ).to.be.true(); + expect( + transferFromAsync + .getCall(3) + .calledWith( + zrxTokenAddress, + takerAddress, + feeRecipient, + bigNumberMatch(takerFee), + TradeSide.Taker, + TransferType.Fee, + ), + ).to.be.true(); + }); + it('should call exchangeTransferSimulator.transferFrom with correct values for an open order', async () => { + const makerFee = new BigNumber(2); + const takerFee = new BigNumber(2); + const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerTokenAddress, + takerTokenAddress, + makerFee, + takerFee, + makerAddress, + ZeroEx.NULL_ADDRESS, + fillableAmount, + feeRecipient, + ); + await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( + exchangeTransferSimulator, + signedOrder, + fillableAmount, + takerAddress, + zrxTokenAddress, + ); + expect(transferFromAsync.callCount).to.be.equal(4); + expect( + transferFromAsync + .getCall(0) + .calledWith( + makerTokenAddress, + makerAddress, + takerAddress, + bigNumberMatch(fillableAmount), + TradeSide.Maker, + TransferType.Trade, + ), + ).to.be.true(); + expect( + transferFromAsync + .getCall(1) + .calledWith( + takerTokenAddress, + takerAddress, + makerAddress, + bigNumberMatch(fillableAmount), + TradeSide.Taker, + TransferType.Trade, + ), + ).to.be.true(); + expect( + transferFromAsync + .getCall(2) + .calledWith( + zrxTokenAddress, + makerAddress, + feeRecipient, + bigNumberMatch(makerFee), + TradeSide.Maker, + TransferType.Fee, + ), + ).to.be.true(); + expect( + transferFromAsync + .getCall(3) + .calledWith( + zrxTokenAddress, + takerAddress, + feeRecipient, + bigNumberMatch(takerFee), + TradeSide.Taker, + TransferType.Fee, + ), + ).to.be.true(); + }); + it('should correctly round the fillMakerTokenAmount', async () => { + const makerTokenAmount = new BigNumber(3); + const takerTokenAmount = new BigNumber(1); + const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + makerTokenAmount, + takerTokenAmount, + ); + await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( + exchangeTransferSimulator, + signedOrder, + takerTokenAmount, + takerAddress, + zrxTokenAddress, + ); + expect(transferFromAsync.callCount).to.be.equal(4); + const makerFillAmount = transferFromAsync.getCall(0).args[3]; + expect(makerFillAmount).to.be.bignumber.equal(makerTokenAmount); + }); + it('should correctly round the makerFeeAmount', async () => { + const makerFee = new BigNumber(2); + const takerFee = new BigNumber(4); + const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerTokenAddress, + takerTokenAddress, + makerFee, + takerFee, + makerAddress, + takerAddress, + fillableAmount, + ZeroEx.NULL_ADDRESS, + ); + const fillTakerTokenAmount = fillableAmount.div(2).round(0); + await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( + exchangeTransferSimulator, + signedOrder, + fillTakerTokenAmount, + takerAddress, + zrxTokenAddress, + ); + const makerPartialFee = makerFee.div(2); + const takerPartialFee = takerFee.div(2); + expect(transferFromAsync.callCount).to.be.equal(4); + const partialMakerFee = transferFromAsync.getCall(2).args[3]; + expect(partialMakerFee).to.be.bignumber.equal(makerPartialFee); + const partialTakerFee = transferFromAsync.getCall(3).args[3]; + expect(partialTakerFee).to.be.bignumber.equal(takerPartialFee); + }); + }); }); diff --git a/packages/0x.js/test/remaining_fillable_calculator_test.ts b/packages/0x.js/test/remaining_fillable_calculator_test.ts index 4c6b8f3ac..58e9dd93b 100644 --- a/packages/0x.js/test/remaining_fillable_calculator_test.ts +++ b/packages/0x.js/test/remaining_fillable_calculator_test.ts @@ -12,223 +12,223 @@ chaiSetup.configure(); const expect = chai.expect; describe('RemainingFillableCalculator', () => { - let calculator: RemainingFillableCalculator; - let signedOrder: SignedOrder; - let transferrableMakerTokenAmount: BigNumber; - let transferrableMakerFeeTokenAmount: BigNumber; - let remainingMakerTokenAmount: BigNumber; - let makerAmount: BigNumber; - let takerAmount: BigNumber; - let makerFeeAmount: BigNumber; - let isMakerTokenZRX: boolean; - const makerToken: string = '0x1'; - const takerToken: string = '0x2'; - const decimals: number = 4; - const zero: BigNumber = new BigNumber(0); - const zeroAddress = '0x0'; - const signature: ECSignature = { v: 27, r: '', s: '' }; - beforeEach(async () => { - [makerAmount, takerAmount, makerFeeAmount] = [ - ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals), - ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals), - ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals), - ]; - [transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount] = [ - ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals), - ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals), - ]; - }); - function buildSignedOrder(): SignedOrder { - return { - ecSignature: signature, - exchangeContractAddress: zeroAddress, - feeRecipient: zeroAddress, - maker: zeroAddress, - taker: zeroAddress, - makerFee: makerFeeAmount, - takerFee: zero, - makerTokenAmount: makerAmount, - takerTokenAmount: takerAmount, - makerTokenAddress: makerToken, - takerTokenAddress: takerToken, - salt: zero, - expirationUnixTimestampSec: zero, - }; - } - describe('Maker token is NOT ZRX', () => { - before(async () => { - isMakerTokenZRX = false; - }); - it('calculates the correct amount when unfilled and funds available', () => { - signedOrder = buildSignedOrder(); - remainingMakerTokenAmount = signedOrder.makerTokenAmount; - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); - }); - it('calculates the correct amount when partially filled and funds available', () => { - signedOrder = buildSignedOrder(); - remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals); - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); - }); - it('calculates the amount to be 0 when all fee funds are transferred', () => { - signedOrder = buildSignedOrder(); - transferrableMakerFeeTokenAmount = zero; - remainingMakerTokenAmount = signedOrder.makerTokenAmount; - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero); - }); - it('calculates the correct amount when balance is less than remaining fillable', () => { - signedOrder = buildSignedOrder(); - const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); - remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount); - transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount); - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount); - }); - describe('Order to Fee Ratio is < 1', () => { - beforeEach(async () => { - [makerAmount, takerAmount, makerFeeAmount] = [ - ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals), - ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals), - ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals), - ]; - }); - it('calculates the correct amount when funds unavailable', () => { - signedOrder = buildSignedOrder(); - remainingMakerTokenAmount = signedOrder.makerTokenAmount; - const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); - transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount); - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount); - }); - }); - describe('Ratio is not evenly divisble', () => { - beforeEach(async () => { - [makerAmount, takerAmount, makerFeeAmount] = [ - ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals), - ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals), - ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals), - ]; - }); - it('calculates the correct amount when funds unavailable', () => { - signedOrder = buildSignedOrder(); - remainingMakerTokenAmount = signedOrder.makerTokenAmount; - const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); - transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount); - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - const calculatedFillableAmount = calculator.computeRemainingMakerFillable(); - expect(calculatedFillableAmount.lessThanOrEqualTo(transferrableMakerTokenAmount)).to.be.true(); - expect(calculatedFillableAmount).to.be.bignumber.greaterThan(new BigNumber(0)); - const orderToFeeRatio = signedOrder.makerTokenAmount.dividedBy(signedOrder.makerFee); - const calculatedFeeAmount = calculatedFillableAmount.dividedBy(orderToFeeRatio); - expect(calculatedFeeAmount).to.be.bignumber.lessThan(transferrableMakerFeeTokenAmount); - }); - }); - }); - describe('Maker Token is ZRX', () => { - before(async () => { - isMakerTokenZRX = true; - }); - it('calculates the correct amount when unfilled and funds available', () => { - signedOrder = buildSignedOrder(); - transferrableMakerTokenAmount = makerAmount.plus(makerFeeAmount); - transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount; - remainingMakerTokenAmount = signedOrder.makerTokenAmount; - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); - }); - it('calculates the correct amount when partially filled and funds available', () => { - signedOrder = buildSignedOrder(); - remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals); - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); - }); - it('calculates the amount to be 0 when all fee funds are transferred', () => { - signedOrder = buildSignedOrder(); - transferrableMakerTokenAmount = zero; - transferrableMakerFeeTokenAmount = zero; - remainingMakerTokenAmount = signedOrder.makerTokenAmount; - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero); - }); - it('calculates the correct amount when balance is less than remaining fillable', () => { - signedOrder = buildSignedOrder(); - const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); - remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount); - transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount); - transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount; + let calculator: RemainingFillableCalculator; + let signedOrder: SignedOrder; + let transferrableMakerTokenAmount: BigNumber; + let transferrableMakerFeeTokenAmount: BigNumber; + let remainingMakerTokenAmount: BigNumber; + let makerAmount: BigNumber; + let takerAmount: BigNumber; + let makerFeeAmount: BigNumber; + let isMakerTokenZRX: boolean; + const makerToken: string = '0x1'; + const takerToken: string = '0x2'; + const decimals: number = 4; + const zero: BigNumber = new BigNumber(0); + const zeroAddress = '0x0'; + const signature: ECSignature = { v: 27, r: '', s: '' }; + beforeEach(async () => { + [makerAmount, takerAmount, makerFeeAmount] = [ + ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals), + ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals), + ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals), + ]; + [transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount] = [ + ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals), + ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals), + ]; + }); + function buildSignedOrder(): SignedOrder { + return { + ecSignature: signature, + exchangeContractAddress: zeroAddress, + feeRecipient: zeroAddress, + maker: zeroAddress, + taker: zeroAddress, + makerFee: makerFeeAmount, + takerFee: zero, + makerTokenAmount: makerAmount, + takerTokenAmount: takerAmount, + makerTokenAddress: makerToken, + takerTokenAddress: takerToken, + salt: zero, + expirationUnixTimestampSec: zero, + }; + } + describe('Maker token is NOT ZRX', () => { + before(async () => { + isMakerTokenZRX = false; + }); + it('calculates the correct amount when unfilled and funds available', () => { + signedOrder = buildSignedOrder(); + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); + }); + it('calculates the correct amount when partially filled and funds available', () => { + signedOrder = buildSignedOrder(); + remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); + }); + it('calculates the amount to be 0 when all fee funds are transferred', () => { + signedOrder = buildSignedOrder(); + transferrableMakerFeeTokenAmount = zero; + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero); + }); + it('calculates the correct amount when balance is less than remaining fillable', () => { + signedOrder = buildSignedOrder(); + const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); + remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount); + transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount); + }); + describe('Order to Fee Ratio is < 1', () => { + beforeEach(async () => { + [makerAmount, takerAmount, makerFeeAmount] = [ + ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals), + ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals), + ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals), + ]; + }); + it('calculates the correct amount when funds unavailable', () => { + signedOrder = buildSignedOrder(); + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); + transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount); + }); + }); + describe('Ratio is not evenly divisble', () => { + beforeEach(async () => { + [makerAmount, takerAmount, makerFeeAmount] = [ + ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals), + ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals), + ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals), + ]; + }); + it('calculates the correct amount when funds unavailable', () => { + signedOrder = buildSignedOrder(); + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); + transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + const calculatedFillableAmount = calculator.computeRemainingMakerFillable(); + expect(calculatedFillableAmount.lessThanOrEqualTo(transferrableMakerTokenAmount)).to.be.true(); + expect(calculatedFillableAmount).to.be.bignumber.greaterThan(new BigNumber(0)); + const orderToFeeRatio = signedOrder.makerTokenAmount.dividedBy(signedOrder.makerFee); + const calculatedFeeAmount = calculatedFillableAmount.dividedBy(orderToFeeRatio); + expect(calculatedFeeAmount).to.be.bignumber.lessThan(transferrableMakerFeeTokenAmount); + }); + }); + }); + describe('Maker Token is ZRX', () => { + before(async () => { + isMakerTokenZRX = true; + }); + it('calculates the correct amount when unfilled and funds available', () => { + signedOrder = buildSignedOrder(); + transferrableMakerTokenAmount = makerAmount.plus(makerFeeAmount); + transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount; + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); + }); + it('calculates the correct amount when partially filled and funds available', () => { + signedOrder = buildSignedOrder(); + remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); + }); + it('calculates the amount to be 0 when all fee funds are transferred', () => { + signedOrder = buildSignedOrder(); + transferrableMakerTokenAmount = zero; + transferrableMakerFeeTokenAmount = zero; + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero); + }); + it('calculates the correct amount when balance is less than remaining fillable', () => { + signedOrder = buildSignedOrder(); + const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); + remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount); + transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount); + transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount; - const orderToFeeRatio = signedOrder.makerTokenAmount.dividedToIntegerBy(signedOrder.makerFee); - const expectedFillableAmount = new BigNumber(450980); - calculator = new RemainingFillableCalculator( - signedOrder, - isMakerTokenZRX, - transferrableMakerTokenAmount, - transferrableMakerFeeTokenAmount, - remainingMakerTokenAmount, - ); - const calculatedFillableAmount = calculator.computeRemainingMakerFillable(); - const numberOfFillsInRatio = calculatedFillableAmount.dividedToIntegerBy(orderToFeeRatio); - const calculatedFillableAmountPlusFees = calculatedFillableAmount.plus(numberOfFillsInRatio); - expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(transferrableMakerTokenAmount); - expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(remainingMakerTokenAmount); - expect(calculatedFillableAmount).to.be.bignumber.equal(expectedFillableAmount); - expect(numberOfFillsInRatio.decimalPlaces()).to.be.equal(0); - }); - }); + const orderToFeeRatio = signedOrder.makerTokenAmount.dividedToIntegerBy(signedOrder.makerFee); + const expectedFillableAmount = new BigNumber(450980); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + const calculatedFillableAmount = calculator.computeRemainingMakerFillable(); + const numberOfFillsInRatio = calculatedFillableAmount.dividedToIntegerBy(orderToFeeRatio); + const calculatedFillableAmountPlusFees = calculatedFillableAmount.plus(numberOfFillsInRatio); + expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(transferrableMakerTokenAmount); + expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(remainingMakerTokenAmount); + expect(calculatedFillableAmount).to.be.bignumber.equal(expectedFillableAmount); + expect(numberOfFillsInRatio.decimalPlaces()).to.be.equal(0); + }); + }); }); diff --git a/packages/0x.js/test/subscription_test.ts b/packages/0x.js/test/subscription_test.ts index f4c6f748f..f70ecd6cd 100644 --- a/packages/0x.js/test/subscription_test.ts +++ b/packages/0x.js/test/subscription_test.ts @@ -17,69 +17,69 @@ chaiSetup.configure(); const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); describe('SubscriptionTest', () => { - let web3: Web3; - let zeroEx: ZeroEx; - let userAddresses: string[]; - let tokens: Token[]; - let coinbase: string; - let addressWithoutFunds: string; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - before(async () => { - web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, config); - userAddresses = await zeroEx.getAvailableAddressesAsync(); - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - coinbase = userAddresses[0]; - addressWithoutFunds = userAddresses[1]; - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('#subscribe', () => { - const indexFilterValues = {}; - let tokenAddress: string; - const allowanceAmount = new BigNumber(42); - let stubs: Sinon.SinonStub[] = []; - before(() => { - const token = tokens[0]; - tokenAddress = token.address; - }); - afterEach(() => { - zeroEx.token.unsubscribeAll(); - _.each(stubs, s => s.restore()); - stubs = []; - }); - it('Should receive the Error when an error occurs while fetching the block', (done: DoneCallback) => { - (async () => { - const errMsg = 'Error fetching block'; - const callback = assertNodeCallbackError(done, errMsg); - stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error(errMsg))]; - zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); - await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount); - })().catch(done); - }); - it('Should receive the Error when an error occurs while reconciling the new block', (done: DoneCallback) => { - (async () => { - const errMsg = 'Error fetching logs'; - const callback = assertNodeCallbackError(done, errMsg); - stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getLogsAsync').throws(new Error(errMsg))]; - zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); - await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount); - })().catch(done); - }); - it('Should allow unsubscribeAll to be called successfully after an error', (done: DoneCallback) => { - (async () => { - const callback = (err: Error | null, logEvent?: DecodedLogEvent<ApprovalContractEventArgs>) => _.noop; - zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); - stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error('JSON RPC error'))]; - zeroEx.token.unsubscribeAll(); - done(); - })().catch(done); - }); - }); + let web3: Web3; + let zeroEx: ZeroEx; + let userAddresses: string[]; + let tokens: Token[]; + let coinbase: string; + let addressWithoutFunds: string; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + before(async () => { + web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, config); + userAddresses = await zeroEx.getAvailableAddressesAsync(); + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + coinbase = userAddresses[0]; + addressWithoutFunds = userAddresses[1]; + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#subscribe', () => { + const indexFilterValues = {}; + let tokenAddress: string; + const allowanceAmount = new BigNumber(42); + let stubs: Sinon.SinonStub[] = []; + before(() => { + const token = tokens[0]; + tokenAddress = token.address; + }); + afterEach(() => { + zeroEx.token.unsubscribeAll(); + _.each(stubs, s => s.restore()); + stubs = []; + }); + it('Should receive the Error when an error occurs while fetching the block', (done: DoneCallback) => { + (async () => { + const errMsg = 'Error fetching block'; + const callback = assertNodeCallbackError(done, errMsg); + stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error(errMsg))]; + zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); + await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount); + })().catch(done); + }); + it('Should receive the Error when an error occurs while reconciling the new block', (done: DoneCallback) => { + (async () => { + const errMsg = 'Error fetching logs'; + const callback = assertNodeCallbackError(done, errMsg); + stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getLogsAsync').throws(new Error(errMsg))]; + zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); + await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount); + })().catch(done); + }); + it('Should allow unsubscribeAll to be called successfully after an error', (done: DoneCallback) => { + (async () => { + const callback = (err: Error | null, logEvent?: DecodedLogEvent<ApprovalContractEventArgs>) => _.noop; + zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); + stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error('JSON RPC error'))]; + zeroEx.token.unsubscribeAll(); + done(); + })().catch(done); + }); + }); }); diff --git a/packages/0x.js/test/token_registry_wrapper_test.ts b/packages/0x.js/test/token_registry_wrapper_test.ts index 0a170db8f..c798c00d7 100644 --- a/packages/0x.js/test/token_registry_wrapper_test.ts +++ b/packages/0x.js/test/token_registry_wrapper_test.ts @@ -17,113 +17,113 @@ const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); const TOKEN_REGISTRY_SIZE_AFTER_MIGRATION = 7; describe('TokenRegistryWrapper', () => { - let zeroEx: ZeroEx; - let tokens: Token[]; - const tokenAddressBySymbol: { [symbol: string]: string } = {}; - const tokenAddressByName: { [symbol: string]: string } = {}; - const tokenBySymbol: { [symbol: string]: Token } = {}; - const tokenByName: { [symbol: string]: Token } = {}; - const registeredSymbol = 'ZRX'; - const registeredName = '0x Protocol Token'; - const unregisteredSymbol = 'MAL'; - const unregisteredName = 'Malicious Token'; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - before(async () => { - const web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, config); - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - _.map(tokens, token => { - tokenAddressBySymbol[token.symbol] = token.address; - tokenAddressByName[token.name] = token.address; - tokenBySymbol[token.symbol] = token; - tokenByName[token.name] = token; - }); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('#getTokensAsync', () => { - it('should return all the tokens added to the tokenRegistry during the migration', async () => { - expect(tokens).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION); + let zeroEx: ZeroEx; + let tokens: Token[]; + const tokenAddressBySymbol: { [symbol: string]: string } = {}; + const tokenAddressByName: { [symbol: string]: string } = {}; + const tokenBySymbol: { [symbol: string]: Token } = {}; + const tokenByName: { [symbol: string]: Token } = {}; + const registeredSymbol = 'ZRX'; + const registeredName = '0x Protocol Token'; + const unregisteredSymbol = 'MAL'; + const unregisteredName = 'Malicious Token'; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + before(async () => { + const web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, config); + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + _.map(tokens, token => { + tokenAddressBySymbol[token.symbol] = token.address; + tokenAddressByName[token.name] = token.address; + tokenBySymbol[token.symbol] = token; + tokenByName[token.name] = token; + }); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#getTokensAsync', () => { + it('should return all the tokens added to the tokenRegistry during the migration', async () => { + expect(tokens).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION); - const schemaValidator = new SchemaValidator(); - _.each(tokens, token => { - const validationResult = schemaValidator.validate(token, schemas.tokenSchema); - expect(validationResult.errors).to.have.lengthOf(0); - }); - }); - }); - describe('#getTokenAddressesAsync', () => { - it('should return all the token addresses added to the tokenRegistry during the migration', async () => { - const tokenAddresses = await zeroEx.tokenRegistry.getTokenAddressesAsync(); - expect(tokenAddresses).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION); + const schemaValidator = new SchemaValidator(); + _.each(tokens, token => { + const validationResult = schemaValidator.validate(token, schemas.tokenSchema); + expect(validationResult.errors).to.have.lengthOf(0); + }); + }); + }); + describe('#getTokenAddressesAsync', () => { + it('should return all the token addresses added to the tokenRegistry during the migration', async () => { + const tokenAddresses = await zeroEx.tokenRegistry.getTokenAddressesAsync(); + expect(tokenAddresses).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION); - const schemaValidator = new SchemaValidator(); - _.each(tokenAddresses, tokenAddress => { - const validationResult = schemaValidator.validate(tokenAddress, schemas.addressSchema); - expect(validationResult.errors).to.have.lengthOf(0); - expect(tokenAddress).to.not.be.equal(ZeroEx.NULL_ADDRESS); - }); - }); - }); - describe('#getTokenAddressBySymbol', () => { - it('should return correct address for a token in the registry', async () => { - const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(registeredSymbol); - expect(tokenAddress).to.be.equal(tokenAddressBySymbol[registeredSymbol]); - }); - it('should return undefined for a token out of registry', async () => { - const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(unregisteredSymbol); - expect(tokenAddress).to.be.undefined(); - }); - }); - describe('#getTokenAddressByName', () => { - it('should return correct address for a token in the registry', async () => { - const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(registeredName); - expect(tokenAddress).to.be.equal(tokenAddressByName[registeredName]); - }); - it('should return undefined for a token out of registry', async () => { - const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(unregisteredName); - expect(tokenAddress).to.be.undefined(); - }); - }); - describe('#getTokenBySymbol', () => { - it('should return correct token for a token in the registry', async () => { - const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(registeredSymbol); - expect(token).to.be.deep.equal(tokenBySymbol[registeredSymbol]); - }); - it('should return undefined for a token out of registry', async () => { - const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(unregisteredSymbol); - expect(token).to.be.undefined(); - }); - }); - describe('#getTokenByName', () => { - it('should return correct token for a token in the registry', async () => { - const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(registeredName); - expect(token).to.be.deep.equal(tokenByName[registeredName]); - }); - it('should return undefined for a token out of registry', async () => { - const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(unregisteredName); - expect(token).to.be.undefined(); - }); - }); - describe('#getTokenIfExistsAsync', () => { - it('should return the token added to the tokenRegistry during the migration', async () => { - const aToken = tokens[0]; + const schemaValidator = new SchemaValidator(); + _.each(tokenAddresses, tokenAddress => { + const validationResult = schemaValidator.validate(tokenAddress, schemas.addressSchema); + expect(validationResult.errors).to.have.lengthOf(0); + expect(tokenAddress).to.not.be.equal(ZeroEx.NULL_ADDRESS); + }); + }); + }); + describe('#getTokenAddressBySymbol', () => { + it('should return correct address for a token in the registry', async () => { + const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(registeredSymbol); + expect(tokenAddress).to.be.equal(tokenAddressBySymbol[registeredSymbol]); + }); + it('should return undefined for a token out of registry', async () => { + const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(unregisteredSymbol); + expect(tokenAddress).to.be.undefined(); + }); + }); + describe('#getTokenAddressByName', () => { + it('should return correct address for a token in the registry', async () => { + const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(registeredName); + expect(tokenAddress).to.be.equal(tokenAddressByName[registeredName]); + }); + it('should return undefined for a token out of registry', async () => { + const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(unregisteredName); + expect(tokenAddress).to.be.undefined(); + }); + }); + describe('#getTokenBySymbol', () => { + it('should return correct token for a token in the registry', async () => { + const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(registeredSymbol); + expect(token).to.be.deep.equal(tokenBySymbol[registeredSymbol]); + }); + it('should return undefined for a token out of registry', async () => { + const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(unregisteredSymbol); + expect(token).to.be.undefined(); + }); + }); + describe('#getTokenByName', () => { + it('should return correct token for a token in the registry', async () => { + const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(registeredName); + expect(token).to.be.deep.equal(tokenByName[registeredName]); + }); + it('should return undefined for a token out of registry', async () => { + const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(unregisteredName); + expect(token).to.be.undefined(); + }); + }); + describe('#getTokenIfExistsAsync', () => { + it('should return the token added to the tokenRegistry during the migration', async () => { + const aToken = tokens[0]; - const token = await zeroEx.tokenRegistry.getTokenIfExistsAsync(aToken.address); - const schemaValidator = new SchemaValidator(); - const validationResult = schemaValidator.validate(token, schemas.tokenSchema); - expect(validationResult.errors).to.have.lengthOf(0); - }); - it('should return return undefined when passed a token address not in the tokenRegistry', async () => { - const unregisteredTokenAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; - const tokenIfExists = await zeroEx.tokenRegistry.getTokenIfExistsAsync(unregisteredTokenAddress); - expect(tokenIfExists).to.be.undefined(); - }); - }); + const token = await zeroEx.tokenRegistry.getTokenIfExistsAsync(aToken.address); + const schemaValidator = new SchemaValidator(); + const validationResult = schemaValidator.validate(token, schemas.tokenSchema); + expect(validationResult.errors).to.have.lengthOf(0); + }); + it('should return return undefined when passed a token address not in the tokenRegistry', async () => { + const unregisteredTokenAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; + const tokenIfExists = await zeroEx.tokenRegistry.getTokenIfExistsAsync(unregisteredTokenAddress); + expect(tokenIfExists).to.be.undefined(); + }); + }); }); diff --git a/packages/0x.js/test/token_transfer_proxy_wrapper_test.ts b/packages/0x.js/test/token_transfer_proxy_wrapper_test.ts index 15bd7a8ba..622919be2 100644 --- a/packages/0x.js/test/token_transfer_proxy_wrapper_test.ts +++ b/packages/0x.js/test/token_transfer_proxy_wrapper_test.ts @@ -10,27 +10,27 @@ chaiSetup.configure(); const expect = chai.expect; describe('TokenTransferProxyWrapper', () => { - let zeroEx: ZeroEx; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - before(async () => { - const web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, config); - }); - describe('#isAuthorizedAsync', () => { - it('should return false if the address is not authorized', async () => { - const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(ZeroEx.NULL_ADDRESS); - expect(isAuthorized).to.be.false(); - }); - }); - describe('#getAuthorizedAddressesAsync', () => { - it('should return the list of authorized addresses', async () => { - const authorizedAddresses = await zeroEx.proxy.getAuthorizedAddressesAsync(); - for (const authorizedAddress of authorizedAddresses) { - const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(authorizedAddress); - expect(isAuthorized).to.be.true(); - } - }); - }); + let zeroEx: ZeroEx; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + before(async () => { + const web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, config); + }); + describe('#isAuthorizedAsync', () => { + it('should return false if the address is not authorized', async () => { + const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(ZeroEx.NULL_ADDRESS); + expect(isAuthorized).to.be.false(); + }); + }); + describe('#getAuthorizedAddressesAsync', () => { + it('should return the list of authorized addresses', async () => { + const authorizedAddresses = await zeroEx.proxy.getAuthorizedAddressesAsync(); + for (const authorizedAddress of authorizedAddresses) { + const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(authorizedAddress); + expect(isAuthorized).to.be.true(); + } + }); + }); }); diff --git a/packages/0x.js/test/token_wrapper_test.ts b/packages/0x.js/test/token_wrapper_test.ts index 4ba1f07c5..6919183cb 100644 --- a/packages/0x.js/test/token_wrapper_test.ts +++ b/packages/0x.js/test/token_wrapper_test.ts @@ -6,15 +6,15 @@ import 'mocha'; import * as Web3 from 'web3'; import { - ApprovalContractEventArgs, - BlockParamLiteral, - BlockRange, - DecodedLogEvent, - Token, - TokenEvents, - TransferContractEventArgs, - ZeroEx, - ZeroExError, + ApprovalContractEventArgs, + BlockParamLiteral, + BlockRange, + DecodedLogEvent, + Token, + TokenEvents, + TransferContractEventArgs, + ZeroEx, + ZeroExError, } from '../src'; import { DoneCallback } from '../src/types'; @@ -29,493 +29,493 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL); describe('TokenWrapper', () => { - let web3: Web3; - let zeroEx: ZeroEx; - let userAddresses: string[]; - let tokens: Token[]; - let tokenUtils: TokenUtils; - let coinbase: string; - let addressWithoutFunds: string; - let web3Wrapper: Web3Wrapper; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - before(async () => { - web3 = web3Factory.create(); - zeroEx = new ZeroEx(web3.currentProvider, config); - web3Wrapper = new Web3Wrapper(web3.currentProvider); - userAddresses = await zeroEx.getAvailableAddressesAsync(); - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - tokenUtils = new TokenUtils(tokens); - coinbase = userAddresses[0]; - addressWithoutFunds = userAddresses[1]; - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('#transferAsync', () => { - let token: Token; - let transferAmount: BigNumber; - before(() => { - token = tokens[0]; - transferAmount = new BigNumber(42); - }); - it('should successfully transfer tokens', async () => { - const fromAddress = coinbase; - const toAddress = addressWithoutFunds; - const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); - expect(preBalance).to.be.bignumber.equal(0); - await zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount); - const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); - return expect(postBalance).to.be.bignumber.equal(transferAmount); - }); - it('should fail to transfer tokens if fromAddress has an insufficient balance', async () => { - const fromAddress = addressWithoutFunds; - const toAddress = coinbase; - return expect( - zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount), - ).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer); - }); - it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => { - const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065'; - const fromAddress = coinbase; - const toAddress = coinbase; - return expect( - zeroEx.token.transferAsync(nonExistentTokenAddress, fromAddress, toAddress, transferAmount), - ).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist); - }); - }); - describe('#transferFromAsync', () => { - let token: Token; - let toAddress: string; - let senderAddress: string; - before(async () => { - token = tokens[0]; - toAddress = addressWithoutFunds; - senderAddress = userAddresses[2]; - }); - it('should fail to transfer tokens if fromAddress has insufficient allowance set', async () => { - const fromAddress = coinbase; - const transferAmount = new BigNumber(42); - - const fromAddressBalance = await zeroEx.token.getBalanceAsync(token.address, fromAddress); - expect(fromAddressBalance).to.be.bignumber.greaterThan(transferAmount); - - const fromAddressAllowance = await zeroEx.token.getAllowanceAsync(token.address, fromAddress, toAddress); - expect(fromAddressAllowance).to.be.bignumber.equal(0); - - return expect( - zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount), - ).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer); - }); - it('[regression] should fail to transfer tokens if set allowance for toAddress instead of senderAddress', async () => { - const fromAddress = coinbase; - const transferAmount = new BigNumber(42); - - await zeroEx.token.setAllowanceAsync(token.address, fromAddress, toAddress, transferAmount); - - return expect( - zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount), - ).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer); - }); - it('should fail to transfer tokens if fromAddress has insufficient balance', async () => { - const fromAddress = addressWithoutFunds; - const transferAmount = new BigNumber(42); - - const fromAddressBalance = await zeroEx.token.getBalanceAsync(token.address, fromAddress); - expect(fromAddressBalance).to.be.bignumber.equal(0); - - await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount); - const fromAddressAllowance = await zeroEx.token.getAllowanceAsync( - token.address, - fromAddress, - senderAddress, - ); - expect(fromAddressAllowance).to.be.bignumber.equal(transferAmount); - - return expect( - zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount), - ).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer); - }); - it('should successfully transfer tokens', async () => { - const fromAddress = coinbase; - - const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); - expect(preBalance).to.be.bignumber.equal(0); - - const transferAmount = new BigNumber(42); - await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount); - - await zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount); - const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); - return expect(postBalance).to.be.bignumber.equal(transferAmount); - }); - it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => { - const fromAddress = coinbase; - const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065'; - return expect( - zeroEx.token.transferFromAsync( - nonExistentTokenAddress, - fromAddress, - toAddress, - senderAddress, - new BigNumber(42), - ), - ).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist); - }); - }); - describe('#getBalanceAsync', () => { - describe('With web3 provider with accounts', () => { - it('should return the balance for an existing ERC20 token', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - const balance = await zeroEx.token.getBalanceAsync(token.address, ownerAddress); - const expectedBalance = new BigNumber('1000000000000000000000000000'); - return expect(balance).to.be.bignumber.equal(expectedBalance); - }); - it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => { - const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065'; - const ownerAddress = coinbase; - return expect(zeroEx.token.getBalanceAsync(nonExistentTokenAddress, ownerAddress)).to.be.rejectedWith( - ZeroExError.TokenContractDoesNotExist, - ); - }); - it('should return a balance of 0 for a non-existent owner address', async () => { - const token = tokens[0]; - const nonExistentOwner = '0x198c6ad858f213fb31b6fe809e25040e6b964593'; - const balance = await zeroEx.token.getBalanceAsync(token.address, nonExistentOwner); - const expectedBalance = new BigNumber(0); - return expect(balance).to.be.bignumber.equal(expectedBalance); - }); - }); - describe('With web3 provider without accounts', () => { - let zeroExWithoutAccounts: ZeroEx; - before(async () => { - const hasAddresses = false; - const web3WithoutAccounts = web3Factory.create(hasAddresses); - zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config); - }); - it('should return balance even when called with Web3 provider instance without addresses', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - const balance = await zeroExWithoutAccounts.token.getBalanceAsync(token.address, ownerAddress); - const expectedBalance = new BigNumber('1000000000000000000000000000'); - return expect(balance).to.be.bignumber.equal(expectedBalance); - }); - }); - }); - describe('#setAllowanceAsync', () => { - it("should set the spender's allowance", async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - const spenderAddress = addressWithoutFunds; - - const allowanceBeforeSet = await zeroEx.token.getAllowanceAsync( - token.address, - ownerAddress, - spenderAddress, - ); - const expectedAllowanceBeforeAllowanceSet = new BigNumber(0); - expect(allowanceBeforeSet).to.be.bignumber.equal(expectedAllowanceBeforeAllowanceSet); - - const amountInBaseUnits = new BigNumber(50); - await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits); - - const allowanceAfterSet = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); - const expectedAllowanceAfterAllowanceSet = amountInBaseUnits; - return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet); - }); - }); - describe('#setUnlimitedAllowanceAsync', () => { - it("should set the unlimited spender's allowance", async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - const spenderAddress = addressWithoutFunds; - - await zeroEx.token.setUnlimitedAllowanceAsync(token.address, ownerAddress, spenderAddress); - const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); - return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - it('should reduce the gas cost for transfers including tokens with unlimited allowance support', async () => { - const transferAmount = new BigNumber(5); - const zrx = tokenUtils.getProtocolTokenOrThrow(); - const [, userWithNormalAllowance, userWithUnlimitedAllowance] = userAddresses; - await zeroEx.token.setAllowanceAsync(zrx.address, coinbase, userWithNormalAllowance, transferAmount); - await zeroEx.token.setUnlimitedAllowanceAsync(zrx.address, coinbase, userWithUnlimitedAllowance); - - const initBalanceWithNormalAllowance = await web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance); - const initBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync( - userWithUnlimitedAllowance, - ); - - await zeroEx.token.transferFromAsync( - zrx.address, - coinbase, - userWithNormalAllowance, - userWithNormalAllowance, - transferAmount, - ); - await zeroEx.token.transferFromAsync( - zrx.address, - coinbase, - userWithUnlimitedAllowance, - userWithUnlimitedAllowance, - transferAmount, - ); - - const finalBalanceWithNormalAllowance = await web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance); - const finalBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync( - userWithUnlimitedAllowance, - ); - - const normalGasCost = initBalanceWithNormalAllowance.minus(finalBalanceWithNormalAllowance); - const unlimitedGasCost = initBalanceWithUnlimitedAllowance.minus(finalBalanceWithUnlimitedAllowance); - - // In theory the gas cost with unlimited allowance should be smaller, but with testrpc it's actually bigger. - // This needs to be investigated in ethereumjs-vm. This test is essentially a repro. - // TODO: Make this test pass with inverted assertion. - expect(unlimitedGasCost.toNumber()).to.be.gt(normalGasCost.toNumber()); - }); - }); - describe('#getAllowanceAsync', () => { - describe('With web3 provider with accounts', () => { - it('should get the proxy allowance', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - const spenderAddress = addressWithoutFunds; - - const amountInBaseUnits = new BigNumber(50); - await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits); - - const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); - const expectedAllowance = amountInBaseUnits; - return expect(allowance).to.be.bignumber.equal(expectedAllowance); - }); - it('should return 0 if no allowance set yet', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - const spenderAddress = addressWithoutFunds; - const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); - const expectedAllowance = new BigNumber(0); - return expect(allowance).to.be.bignumber.equal(expectedAllowance); - }); - }); - describe('With web3 provider without accounts', () => { - let zeroExWithoutAccounts: ZeroEx; - before(async () => { - const hasAddresses = false; - const web3WithoutAccounts = web3Factory.create(hasAddresses); - zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config); - }); - it('should get the proxy allowance', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - const spenderAddress = addressWithoutFunds; - - const amountInBaseUnits = new BigNumber(50); - await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits); - - const allowance = await zeroExWithoutAccounts.token.getAllowanceAsync( - token.address, - ownerAddress, - spenderAddress, - ); - const expectedAllowance = amountInBaseUnits; - return expect(allowance).to.be.bignumber.equal(expectedAllowance); - }); - }); - }); - describe('#getProxyAllowanceAsync', () => { - it('should get the proxy allowance', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - - const amountInBaseUnits = new BigNumber(50); - await zeroEx.token.setProxyAllowanceAsync(token.address, ownerAddress, amountInBaseUnits); - - const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); - const expectedAllowance = amountInBaseUnits; - return expect(allowance).to.be.bignumber.equal(expectedAllowance); - }); - }); - describe('#setProxyAllowanceAsync', () => { - it('should set the proxy allowance', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - - const allowanceBeforeSet = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); - const expectedAllowanceBeforeAllowanceSet = new BigNumber(0); - expect(allowanceBeforeSet).to.be.bignumber.equal(expectedAllowanceBeforeAllowanceSet); - - const amountInBaseUnits = new BigNumber(50); - await zeroEx.token.setProxyAllowanceAsync(token.address, ownerAddress, amountInBaseUnits); - - const allowanceAfterSet = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); - const expectedAllowanceAfterAllowanceSet = amountInBaseUnits; - return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet); - }); - }); - describe('#setUnlimitedProxyAllowanceAsync', () => { - it('should set the unlimited proxy allowance', async () => { - const token = tokens[0]; - const ownerAddress = coinbase; - - await zeroEx.token.setUnlimitedProxyAllowanceAsync(token.address, ownerAddress); - const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); - return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - }); - describe('#subscribe', () => { - const indexFilterValues = {}; - let tokenAddress: string; - const transferAmount = new BigNumber(42); - const allowanceAmount = new BigNumber(42); - before(() => { - const token = tokens[0]; - tokenAddress = token.address; - }); - afterEach(() => { - zeroEx.token.unsubscribeAll(); - }); - // Hack: Mocha does not allow a test to be both async and have a `done` callback - // Since we need to await the receipt of the event in the `subscribe` callback, - // we do need both. A hack is to make the top-level a sync fn w/ a done callback and then - // wrap the rest of the test in an async block - // Source: https://github.com/mochajs/mocha/issues/2407 - it('Should receive the Transfer event when tokens are transfered', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<TransferContractEventArgs>) => { - expect(logEvent.isRemoved).to.be.false(); - expect(logEvent.log.logIndex).to.be.equal(0); - expect(logEvent.log.transactionIndex).to.be.equal(0); - expect(logEvent.log.blockNumber).to.be.a('number'); - const args = logEvent.log.args; - expect(args._from).to.be.equal(coinbase); - expect(args._to).to.be.equal(addressWithoutFunds); - expect(args._value).to.be.bignumber.equal(transferAmount); - }, - ); - zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callback); - await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); - })().catch(done); - }); - it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => { - (async () => { - const callback = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { - expect(logEvent).to.not.be.undefined(); - expect(logEvent.isRemoved).to.be.false(); - const args = logEvent.log.args; - expect(args._owner).to.be.equal(coinbase); - expect(args._spender).to.be.equal(addressWithoutFunds); - expect(args._value).to.be.bignumber.equal(allowanceAmount); - }, - ); - zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); - await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount); - })().catch(done); - }); - it('Outstanding subscriptions are cancelled when zeroEx.setProvider called', (done: DoneCallback) => { - (async () => { - const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { - done(new Error('Expected this subscription to have been cancelled')); - }, - ); - zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled); - const callbackToBeCalled = reportNodeCallbackErrors(done)(); - const newProvider = web3Factory.getRpcProvider(); - zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); - zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackToBeCalled); - await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); - })().catch(done); - }); - it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => { - (async () => { - const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( - (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { - done(new Error('Expected this subscription to have been cancelled')); - }, - ); - const subscriptionToken = zeroEx.token.subscribe( - tokenAddress, - TokenEvents.Transfer, - indexFilterValues, - callbackNeverToBeCalled, - ); - zeroEx.token.unsubscribe(subscriptionToken); - await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); - done(); - })().catch(done); - }); - }); - describe('#getLogsAsync', () => { - let tokenAddress: string; - let tokenTransferProxyAddress: string; - const blockRange: BlockRange = { - fromBlock: 0, - toBlock: BlockParamLiteral.Latest, - }; - let txHash: string; - before(() => { - const token = tokens[0]; - tokenAddress = token.address; - tokenTransferProxyAddress = zeroEx.proxy.getContractAddress(); - }); - it('should get logs with decoded args emitted by Approval', async () => { - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase); - await zeroEx.awaitTransactionMinedAsync(txHash); - const eventName = TokenEvents.Approval; - const indexFilterValues = {}; - const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>( - tokenAddress, - eventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(1); - const args = logs[0].args; - expect(logs[0].event).to.be.equal(eventName); - expect(args._owner).to.be.equal(coinbase); - expect(args._spender).to.be.equal(tokenTransferProxyAddress); - expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - it('should only get the logs with the correct event name', async () => { - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase); - await zeroEx.awaitTransactionMinedAsync(txHash); - const differentEventName = TokenEvents.Transfer; - const indexFilterValues = {}; - const logs = await zeroEx.token.getLogsAsync( - tokenAddress, - differentEventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(0); - }); - it('should only get the logs with the correct indexed fields', async () => { - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase); - await zeroEx.awaitTransactionMinedAsync(txHash); - txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, addressWithoutFunds); - await zeroEx.awaitTransactionMinedAsync(txHash); - const eventName = TokenEvents.Approval; - const indexFilterValues = { - _owner: coinbase, - }; - const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>( - tokenAddress, - eventName, - blockRange, - indexFilterValues, - ); - expect(logs).to.have.length(1); - const args = logs[0].args; - expect(args._owner).to.be.equal(coinbase); - }); - }); + let web3: Web3; + let zeroEx: ZeroEx; + let userAddresses: string[]; + let tokens: Token[]; + let tokenUtils: TokenUtils; + let coinbase: string; + let addressWithoutFunds: string; + let web3Wrapper: Web3Wrapper; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + before(async () => { + web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3.currentProvider, config); + web3Wrapper = new Web3Wrapper(web3.currentProvider); + userAddresses = await zeroEx.getAvailableAddressesAsync(); + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + tokenUtils = new TokenUtils(tokens); + coinbase = userAddresses[0]; + addressWithoutFunds = userAddresses[1]; + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#transferAsync', () => { + let token: Token; + let transferAmount: BigNumber; + before(() => { + token = tokens[0]; + transferAmount = new BigNumber(42); + }); + it('should successfully transfer tokens', async () => { + const fromAddress = coinbase; + const toAddress = addressWithoutFunds; + const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); + expect(preBalance).to.be.bignumber.equal(0); + await zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount); + const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); + return expect(postBalance).to.be.bignumber.equal(transferAmount); + }); + it('should fail to transfer tokens if fromAddress has an insufficient balance', async () => { + const fromAddress = addressWithoutFunds; + const toAddress = coinbase; + return expect( + zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount), + ).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer); + }); + it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => { + const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065'; + const fromAddress = coinbase; + const toAddress = coinbase; + return expect( + zeroEx.token.transferAsync(nonExistentTokenAddress, fromAddress, toAddress, transferAmount), + ).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist); + }); + }); + describe('#transferFromAsync', () => { + let token: Token; + let toAddress: string; + let senderAddress: string; + before(async () => { + token = tokens[0]; + toAddress = addressWithoutFunds; + senderAddress = userAddresses[2]; + }); + it('should fail to transfer tokens if fromAddress has insufficient allowance set', async () => { + const fromAddress = coinbase; + const transferAmount = new BigNumber(42); + + const fromAddressBalance = await zeroEx.token.getBalanceAsync(token.address, fromAddress); + expect(fromAddressBalance).to.be.bignumber.greaterThan(transferAmount); + + const fromAddressAllowance = await zeroEx.token.getAllowanceAsync(token.address, fromAddress, toAddress); + expect(fromAddressAllowance).to.be.bignumber.equal(0); + + return expect( + zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount), + ).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer); + }); + it('[regression] should fail to transfer tokens if set allowance for toAddress instead of senderAddress', async () => { + const fromAddress = coinbase; + const transferAmount = new BigNumber(42); + + await zeroEx.token.setAllowanceAsync(token.address, fromAddress, toAddress, transferAmount); + + return expect( + zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount), + ).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer); + }); + it('should fail to transfer tokens if fromAddress has insufficient balance', async () => { + const fromAddress = addressWithoutFunds; + const transferAmount = new BigNumber(42); + + const fromAddressBalance = await zeroEx.token.getBalanceAsync(token.address, fromAddress); + expect(fromAddressBalance).to.be.bignumber.equal(0); + + await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount); + const fromAddressAllowance = await zeroEx.token.getAllowanceAsync( + token.address, + fromAddress, + senderAddress, + ); + expect(fromAddressAllowance).to.be.bignumber.equal(transferAmount); + + return expect( + zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount), + ).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer); + }); + it('should successfully transfer tokens', async () => { + const fromAddress = coinbase; + + const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); + expect(preBalance).to.be.bignumber.equal(0); + + const transferAmount = new BigNumber(42); + await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount); + + await zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount); + const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress); + return expect(postBalance).to.be.bignumber.equal(transferAmount); + }); + it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => { + const fromAddress = coinbase; + const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065'; + return expect( + zeroEx.token.transferFromAsync( + nonExistentTokenAddress, + fromAddress, + toAddress, + senderAddress, + new BigNumber(42), + ), + ).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist); + }); + }); + describe('#getBalanceAsync', () => { + describe('With web3 provider with accounts', () => { + it('should return the balance for an existing ERC20 token', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + const balance = await zeroEx.token.getBalanceAsync(token.address, ownerAddress); + const expectedBalance = new BigNumber('1000000000000000000000000000'); + return expect(balance).to.be.bignumber.equal(expectedBalance); + }); + it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => { + const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065'; + const ownerAddress = coinbase; + return expect(zeroEx.token.getBalanceAsync(nonExistentTokenAddress, ownerAddress)).to.be.rejectedWith( + ZeroExError.TokenContractDoesNotExist, + ); + }); + it('should return a balance of 0 for a non-existent owner address', async () => { + const token = tokens[0]; + const nonExistentOwner = '0x198c6ad858f213fb31b6fe809e25040e6b964593'; + const balance = await zeroEx.token.getBalanceAsync(token.address, nonExistentOwner); + const expectedBalance = new BigNumber(0); + return expect(balance).to.be.bignumber.equal(expectedBalance); + }); + }); + describe('With web3 provider without accounts', () => { + let zeroExWithoutAccounts: ZeroEx; + before(async () => { + const hasAddresses = false; + const web3WithoutAccounts = web3Factory.create(hasAddresses); + zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config); + }); + it('should return balance even when called with Web3 provider instance without addresses', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + const balance = await zeroExWithoutAccounts.token.getBalanceAsync(token.address, ownerAddress); + const expectedBalance = new BigNumber('1000000000000000000000000000'); + return expect(balance).to.be.bignumber.equal(expectedBalance); + }); + }); + }); + describe('#setAllowanceAsync', () => { + it("should set the spender's allowance", async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + const spenderAddress = addressWithoutFunds; + + const allowanceBeforeSet = await zeroEx.token.getAllowanceAsync( + token.address, + ownerAddress, + spenderAddress, + ); + const expectedAllowanceBeforeAllowanceSet = new BigNumber(0); + expect(allowanceBeforeSet).to.be.bignumber.equal(expectedAllowanceBeforeAllowanceSet); + + const amountInBaseUnits = new BigNumber(50); + await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits); + + const allowanceAfterSet = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); + const expectedAllowanceAfterAllowanceSet = amountInBaseUnits; + return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet); + }); + }); + describe('#setUnlimitedAllowanceAsync', () => { + it("should set the unlimited spender's allowance", async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + const spenderAddress = addressWithoutFunds; + + await zeroEx.token.setUnlimitedAllowanceAsync(token.address, ownerAddress, spenderAddress); + const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); + return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + }); + it('should reduce the gas cost for transfers including tokens with unlimited allowance support', async () => { + const transferAmount = new BigNumber(5); + const zrx = tokenUtils.getProtocolTokenOrThrow(); + const [, userWithNormalAllowance, userWithUnlimitedAllowance] = userAddresses; + await zeroEx.token.setAllowanceAsync(zrx.address, coinbase, userWithNormalAllowance, transferAmount); + await zeroEx.token.setUnlimitedAllowanceAsync(zrx.address, coinbase, userWithUnlimitedAllowance); + + const initBalanceWithNormalAllowance = await web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance); + const initBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync( + userWithUnlimitedAllowance, + ); + + await zeroEx.token.transferFromAsync( + zrx.address, + coinbase, + userWithNormalAllowance, + userWithNormalAllowance, + transferAmount, + ); + await zeroEx.token.transferFromAsync( + zrx.address, + coinbase, + userWithUnlimitedAllowance, + userWithUnlimitedAllowance, + transferAmount, + ); + + const finalBalanceWithNormalAllowance = await web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance); + const finalBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync( + userWithUnlimitedAllowance, + ); + + const normalGasCost = initBalanceWithNormalAllowance.minus(finalBalanceWithNormalAllowance); + const unlimitedGasCost = initBalanceWithUnlimitedAllowance.minus(finalBalanceWithUnlimitedAllowance); + + // In theory the gas cost with unlimited allowance should be smaller, but with testrpc it's actually bigger. + // This needs to be investigated in ethereumjs-vm. This test is essentially a repro. + // TODO: Make this test pass with inverted assertion. + expect(unlimitedGasCost.toNumber()).to.be.gt(normalGasCost.toNumber()); + }); + }); + describe('#getAllowanceAsync', () => { + describe('With web3 provider with accounts', () => { + it('should get the proxy allowance', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + const spenderAddress = addressWithoutFunds; + + const amountInBaseUnits = new BigNumber(50); + await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits); + + const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); + const expectedAllowance = amountInBaseUnits; + return expect(allowance).to.be.bignumber.equal(expectedAllowance); + }); + it('should return 0 if no allowance set yet', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + const spenderAddress = addressWithoutFunds; + const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress); + const expectedAllowance = new BigNumber(0); + return expect(allowance).to.be.bignumber.equal(expectedAllowance); + }); + }); + describe('With web3 provider without accounts', () => { + let zeroExWithoutAccounts: ZeroEx; + before(async () => { + const hasAddresses = false; + const web3WithoutAccounts = web3Factory.create(hasAddresses); + zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config); + }); + it('should get the proxy allowance', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + const spenderAddress = addressWithoutFunds; + + const amountInBaseUnits = new BigNumber(50); + await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits); + + const allowance = await zeroExWithoutAccounts.token.getAllowanceAsync( + token.address, + ownerAddress, + spenderAddress, + ); + const expectedAllowance = amountInBaseUnits; + return expect(allowance).to.be.bignumber.equal(expectedAllowance); + }); + }); + }); + describe('#getProxyAllowanceAsync', () => { + it('should get the proxy allowance', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + + const amountInBaseUnits = new BigNumber(50); + await zeroEx.token.setProxyAllowanceAsync(token.address, ownerAddress, amountInBaseUnits); + + const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); + const expectedAllowance = amountInBaseUnits; + return expect(allowance).to.be.bignumber.equal(expectedAllowance); + }); + }); + describe('#setProxyAllowanceAsync', () => { + it('should set the proxy allowance', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + + const allowanceBeforeSet = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); + const expectedAllowanceBeforeAllowanceSet = new BigNumber(0); + expect(allowanceBeforeSet).to.be.bignumber.equal(expectedAllowanceBeforeAllowanceSet); + + const amountInBaseUnits = new BigNumber(50); + await zeroEx.token.setProxyAllowanceAsync(token.address, ownerAddress, amountInBaseUnits); + + const allowanceAfterSet = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); + const expectedAllowanceAfterAllowanceSet = amountInBaseUnits; + return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet); + }); + }); + describe('#setUnlimitedProxyAllowanceAsync', () => { + it('should set the unlimited proxy allowance', async () => { + const token = tokens[0]; + const ownerAddress = coinbase; + + await zeroEx.token.setUnlimitedProxyAllowanceAsync(token.address, ownerAddress); + const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress); + return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + }); + }); + describe('#subscribe', () => { + const indexFilterValues = {}; + let tokenAddress: string; + const transferAmount = new BigNumber(42); + const allowanceAmount = new BigNumber(42); + before(() => { + const token = tokens[0]; + tokenAddress = token.address; + }); + afterEach(() => { + zeroEx.token.unsubscribeAll(); + }); + // Hack: Mocha does not allow a test to be both async and have a `done` callback + // Since we need to await the receipt of the event in the `subscribe` callback, + // we do need both. A hack is to make the top-level a sync fn w/ a done callback and then + // wrap the rest of the test in an async block + // Source: https://github.com/mochajs/mocha/issues/2407 + it('Should receive the Transfer event when tokens are transfered', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<TransferContractEventArgs>) => { + expect(logEvent.isRemoved).to.be.false(); + expect(logEvent.log.logIndex).to.be.equal(0); + expect(logEvent.log.transactionIndex).to.be.equal(0); + expect(logEvent.log.blockNumber).to.be.a('number'); + const args = logEvent.log.args; + expect(args._from).to.be.equal(coinbase); + expect(args._to).to.be.equal(addressWithoutFunds); + expect(args._value).to.be.bignumber.equal(transferAmount); + }, + ); + zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callback); + await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); + })().catch(done); + }); + it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => { + (async () => { + const callback = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { + expect(logEvent).to.not.be.undefined(); + expect(logEvent.isRemoved).to.be.false(); + const args = logEvent.log.args; + expect(args._owner).to.be.equal(coinbase); + expect(args._spender).to.be.equal(addressWithoutFunds); + expect(args._value).to.be.bignumber.equal(allowanceAmount); + }, + ); + zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback); + await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount); + })().catch(done); + }); + it('Outstanding subscriptions are cancelled when zeroEx.setProvider called', (done: DoneCallback) => { + (async () => { + const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { + done(new Error('Expected this subscription to have been cancelled')); + }, + ); + zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled); + const callbackToBeCalled = reportNodeCallbackErrors(done)(); + const newProvider = web3Factory.getRpcProvider(); + zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID); + zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackToBeCalled); + await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); + })().catch(done); + }); + it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => { + (async () => { + const callbackNeverToBeCalled = reportNodeCallbackErrors(done)( + (logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => { + done(new Error('Expected this subscription to have been cancelled')); + }, + ); + const subscriptionToken = zeroEx.token.subscribe( + tokenAddress, + TokenEvents.Transfer, + indexFilterValues, + callbackNeverToBeCalled, + ); + zeroEx.token.unsubscribe(subscriptionToken); + await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); + done(); + })().catch(done); + }); + }); + describe('#getLogsAsync', () => { + let tokenAddress: string; + let tokenTransferProxyAddress: string; + const blockRange: BlockRange = { + fromBlock: 0, + toBlock: BlockParamLiteral.Latest, + }; + let txHash: string; + before(() => { + const token = tokens[0]; + tokenAddress = token.address; + tokenTransferProxyAddress = zeroEx.proxy.getContractAddress(); + }); + it('should get logs with decoded args emitted by Approval', async () => { + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase); + await zeroEx.awaitTransactionMinedAsync(txHash); + const eventName = TokenEvents.Approval; + const indexFilterValues = {}; + const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>( + tokenAddress, + eventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(1); + const args = logs[0].args; + expect(logs[0].event).to.be.equal(eventName); + expect(args._owner).to.be.equal(coinbase); + expect(args._spender).to.be.equal(tokenTransferProxyAddress); + expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + }); + it('should only get the logs with the correct event name', async () => { + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase); + await zeroEx.awaitTransactionMinedAsync(txHash); + const differentEventName = TokenEvents.Transfer; + const indexFilterValues = {}; + const logs = await zeroEx.token.getLogsAsync( + tokenAddress, + differentEventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(0); + }); + it('should only get the logs with the correct indexed fields', async () => { + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase); + await zeroEx.awaitTransactionMinedAsync(txHash); + txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, addressWithoutFunds); + await zeroEx.awaitTransactionMinedAsync(txHash); + const eventName = TokenEvents.Approval; + const indexFilterValues = { + _owner: coinbase, + }; + const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>( + tokenAddress, + eventName, + blockRange, + indexFilterValues, + ); + expect(logs).to.have.length(1); + const args = logs[0].args; + expect(args._owner).to.be.equal(coinbase); + }); + }); }); // tslint:disable:max-file-line-count diff --git a/packages/0x.js/test/utils/chai_setup.ts b/packages/0x.js/test/utils/chai_setup.ts index 078edd309..e156b5f7c 100644 --- a/packages/0x.js/test/utils/chai_setup.ts +++ b/packages/0x.js/test/utils/chai_setup.ts @@ -4,10 +4,10 @@ import ChaiBigNumber = require('chai-bignumber'); import * as dirtyChai from 'dirty-chai'; export const chaiSetup = { - configure() { - chai.config.includeStack = true; - chai.use(ChaiBigNumber()); - chai.use(dirtyChai); - chai.use(chaiAsPromised); - }, + configure() { + chai.config.includeStack = true; + chai.use(ChaiBigNumber()); + chai.use(dirtyChai); + chai.use(chaiAsPromised); + }, }; diff --git a/packages/0x.js/test/utils/constants.ts b/packages/0x.js/test/utils/constants.ts index a9e665c25..b7161e9ab 100644 --- a/packages/0x.js/test/utils/constants.ts +++ b/packages/0x.js/test/utils/constants.ts @@ -1,11 +1,11 @@ export const constants = { - NULL_ADDRESS: '0x0000000000000000000000000000000000000000', - RPC_URL: 'http://localhost:8545', - ROPSTEN_NETWORK_ID: 3, - KOVAN_NETWORK_ID: 42, - TESTRPC_NETWORK_ID: 50, - KOVAN_RPC_URL: 'https://kovan.infura.io/', - ROPSTEN_RPC_URL: 'https://ropsten.infura.io/', - ZRX_DECIMALS: 18, - GAS_ESTIMATE: 500000, + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', + RPC_URL: 'http://localhost:8545', + ROPSTEN_NETWORK_ID: 3, + KOVAN_NETWORK_ID: 42, + TESTRPC_NETWORK_ID: 50, + KOVAN_RPC_URL: 'https://kovan.infura.io/', + ROPSTEN_RPC_URL: 'https://ropsten.infura.io/', + ZRX_DECIMALS: 18, + GAS_ESTIMATE: 500000, }; diff --git a/packages/0x.js/test/utils/fill_scenarios.ts b/packages/0x.js/test/utils/fill_scenarios.ts index 1a61487f4..1d4b8b6f4 100644 --- a/packages/0x.js/test/utils/fill_scenarios.ts +++ b/packages/0x.js/test/utils/fill_scenarios.ts @@ -11,192 +11,192 @@ import { constants } from './constants'; const INITIAL_COINBASE_TOKEN_SUPPLY_IN_UNITS = new BigNumber(100); export class FillScenarios { - private _zeroEx: ZeroEx; - private _userAddresses: string[]; - private _tokens: Token[]; - private _coinbase: string; - private _zrxTokenAddress: string; - private _exchangeContractAddress: string; - constructor( - zeroEx: ZeroEx, - userAddresses: string[], - tokens: Token[], - zrxTokenAddress: string, - exchangeContractAddress: string, - ) { - this._zeroEx = zeroEx; - this._userAddresses = userAddresses; - this._tokens = tokens; - this._coinbase = userAddresses[0]; - this._zrxTokenAddress = zrxTokenAddress; - this._exchangeContractAddress = exchangeContractAddress; - } - public async initTokenBalancesAsync() { - const web3Wrapper = (this._zeroEx as any)._web3Wrapper as Web3Wrapper; - for (const token of this._tokens) { - if (token.symbol !== 'ZRX' && token.symbol !== 'WETH') { - const contractInstance = web3Wrapper.getContractInstance( - artifacts.DummyTokenArtifact.abi, - token.address, - ); - const defaults = {}; - const dummyToken = new DummyTokenContract(contractInstance, defaults); - const tokenSupply = ZeroEx.toBaseUnitAmount(INITIAL_COINBASE_TOKEN_SUPPLY_IN_UNITS, token.decimals); - const txHash = await dummyToken.setBalance.sendTransactionAsync(this._coinbase, tokenSupply, { - from: this._coinbase, - }); - await this._zeroEx.awaitTransactionMinedAsync(txHash); - } - } - } - public async createFillableSignedOrderAsync( - makerTokenAddress: string, - takerTokenAddress: string, - makerAddress: string, - takerAddress: string, - fillableAmount: BigNumber, - expirationUnixTimestampSec?: BigNumber, - ): Promise<SignedOrder> { - return this.createAsymmetricFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - fillableAmount, - expirationUnixTimestampSec, - ); - } - public async createFillableSignedOrderWithFeesAsync( - makerTokenAddress: string, - takerTokenAddress: string, - makerFee: BigNumber, - takerFee: BigNumber, - makerAddress: string, - takerAddress: string, - fillableAmount: BigNumber, - feeRecepient: string, - expirationUnixTimestampSec?: BigNumber, - ): Promise<SignedOrder> { - return this._createAsymmetricFillableSignedOrderWithFeesAsync( - makerTokenAddress, - takerTokenAddress, - makerFee, - takerFee, - makerAddress, - takerAddress, - fillableAmount, - fillableAmount, - feeRecepient, - expirationUnixTimestampSec, - ); - } - public async createAsymmetricFillableSignedOrderAsync( - makerTokenAddress: string, - takerTokenAddress: string, - makerAddress: string, - takerAddress: string, - makerFillableAmount: BigNumber, - takerFillableAmount: BigNumber, - expirationUnixTimestampSec?: BigNumber, - ): Promise<SignedOrder> { - const makerFee = new BigNumber(0); - const takerFee = new BigNumber(0); - const feeRecepient = constants.NULL_ADDRESS; - return this._createAsymmetricFillableSignedOrderWithFeesAsync( - makerTokenAddress, - takerTokenAddress, - makerFee, - takerFee, - makerAddress, - takerAddress, - makerFillableAmount, - takerFillableAmount, - feeRecepient, - expirationUnixTimestampSec, - ); - } - public async createPartiallyFilledSignedOrderAsync( - makerTokenAddress: string, - takerTokenAddress: string, - takerAddress: string, - fillableAmount: BigNumber, - partialFillAmount: BigNumber, - ) { - const [makerAddress] = this._userAddresses; - const signedOrder = await this.createAsymmetricFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, - makerAddress, - takerAddress, - fillableAmount, - fillableAmount, - ); - const shouldThrowOnInsufficientBalanceOrAllowance = false; - await this._zeroEx.exchange.fillOrderAsync( - signedOrder, - partialFillAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - return signedOrder; - } - private async _createAsymmetricFillableSignedOrderWithFeesAsync( - makerTokenAddress: string, - takerTokenAddress: string, - makerFee: BigNumber, - takerFee: BigNumber, - makerAddress: string, - takerAddress: string, - makerFillableAmount: BigNumber, - takerFillableAmount: BigNumber, - feeRecepient: string, - expirationUnixTimestampSec?: BigNumber, - ): Promise<SignedOrder> { - await Promise.all([ - this._increaseBalanceAndAllowanceAsync(makerTokenAddress, makerAddress, makerFillableAmount), - this._increaseBalanceAndAllowanceAsync(takerTokenAddress, takerAddress, takerFillableAmount), - ]); - await Promise.all([ - this._increaseBalanceAndAllowanceAsync(this._zrxTokenAddress, makerAddress, makerFee), - this._increaseBalanceAndAllowanceAsync(this._zrxTokenAddress, takerAddress, takerFee), - ]); + private _zeroEx: ZeroEx; + private _userAddresses: string[]; + private _tokens: Token[]; + private _coinbase: string; + private _zrxTokenAddress: string; + private _exchangeContractAddress: string; + constructor( + zeroEx: ZeroEx, + userAddresses: string[], + tokens: Token[], + zrxTokenAddress: string, + exchangeContractAddress: string, + ) { + this._zeroEx = zeroEx; + this._userAddresses = userAddresses; + this._tokens = tokens; + this._coinbase = userAddresses[0]; + this._zrxTokenAddress = zrxTokenAddress; + this._exchangeContractAddress = exchangeContractAddress; + } + public async initTokenBalancesAsync() { + const web3Wrapper = (this._zeroEx as any)._web3Wrapper as Web3Wrapper; + for (const token of this._tokens) { + if (token.symbol !== 'ZRX' && token.symbol !== 'WETH') { + const contractInstance = web3Wrapper.getContractInstance( + artifacts.DummyTokenArtifact.abi, + token.address, + ); + const defaults = {}; + const dummyToken = new DummyTokenContract(contractInstance, defaults); + const tokenSupply = ZeroEx.toBaseUnitAmount(INITIAL_COINBASE_TOKEN_SUPPLY_IN_UNITS, token.decimals); + const txHash = await dummyToken.setBalance.sendTransactionAsync(this._coinbase, tokenSupply, { + from: this._coinbase, + }); + await this._zeroEx.awaitTransactionMinedAsync(txHash); + } + } + } + public async createFillableSignedOrderAsync( + makerTokenAddress: string, + takerTokenAddress: string, + makerAddress: string, + takerAddress: string, + fillableAmount: BigNumber, + expirationUnixTimestampSec?: BigNumber, + ): Promise<SignedOrder> { + return this.createAsymmetricFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + fillableAmount, + expirationUnixTimestampSec, + ); + } + public async createFillableSignedOrderWithFeesAsync( + makerTokenAddress: string, + takerTokenAddress: string, + makerFee: BigNumber, + takerFee: BigNumber, + makerAddress: string, + takerAddress: string, + fillableAmount: BigNumber, + feeRecepient: string, + expirationUnixTimestampSec?: BigNumber, + ): Promise<SignedOrder> { + return this._createAsymmetricFillableSignedOrderWithFeesAsync( + makerTokenAddress, + takerTokenAddress, + makerFee, + takerFee, + makerAddress, + takerAddress, + fillableAmount, + fillableAmount, + feeRecepient, + expirationUnixTimestampSec, + ); + } + public async createAsymmetricFillableSignedOrderAsync( + makerTokenAddress: string, + takerTokenAddress: string, + makerAddress: string, + takerAddress: string, + makerFillableAmount: BigNumber, + takerFillableAmount: BigNumber, + expirationUnixTimestampSec?: BigNumber, + ): Promise<SignedOrder> { + const makerFee = new BigNumber(0); + const takerFee = new BigNumber(0); + const feeRecepient = constants.NULL_ADDRESS; + return this._createAsymmetricFillableSignedOrderWithFeesAsync( + makerTokenAddress, + takerTokenAddress, + makerFee, + takerFee, + makerAddress, + takerAddress, + makerFillableAmount, + takerFillableAmount, + feeRecepient, + expirationUnixTimestampSec, + ); + } + public async createPartiallyFilledSignedOrderAsync( + makerTokenAddress: string, + takerTokenAddress: string, + takerAddress: string, + fillableAmount: BigNumber, + partialFillAmount: BigNumber, + ) { + const [makerAddress] = this._userAddresses; + const signedOrder = await this.createAsymmetricFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + fillableAmount, + ); + const shouldThrowOnInsufficientBalanceOrAllowance = false; + await this._zeroEx.exchange.fillOrderAsync( + signedOrder, + partialFillAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + takerAddress, + ); + return signedOrder; + } + private async _createAsymmetricFillableSignedOrderWithFeesAsync( + makerTokenAddress: string, + takerTokenAddress: string, + makerFee: BigNumber, + takerFee: BigNumber, + makerAddress: string, + takerAddress: string, + makerFillableAmount: BigNumber, + takerFillableAmount: BigNumber, + feeRecepient: string, + expirationUnixTimestampSec?: BigNumber, + ): Promise<SignedOrder> { + await Promise.all([ + this._increaseBalanceAndAllowanceAsync(makerTokenAddress, makerAddress, makerFillableAmount), + this._increaseBalanceAndAllowanceAsync(takerTokenAddress, takerAddress, takerFillableAmount), + ]); + await Promise.all([ + this._increaseBalanceAndAllowanceAsync(this._zrxTokenAddress, makerAddress, makerFee), + this._increaseBalanceAndAllowanceAsync(this._zrxTokenAddress, takerAddress, takerFee), + ]); - const signedOrder = await orderFactory.createSignedOrderAsync( - this._zeroEx, - makerAddress, - takerAddress, - makerFee, - takerFee, - makerFillableAmount, - makerTokenAddress, - takerFillableAmount, - takerTokenAddress, - this._exchangeContractAddress, - feeRecepient, - expirationUnixTimestampSec, - ); - return signedOrder; - } - private async _increaseBalanceAndAllowanceAsync( - tokenAddress: string, - address: string, - amount: BigNumber, - ): Promise<void> { - if (amount.isZero() || address === ZeroEx.NULL_ADDRESS) { - return; // noop - } - await Promise.all([ - this._increaseBalanceAsync(tokenAddress, address, amount), - this._increaseAllowanceAsync(tokenAddress, address, amount), - ]); - } - private async _increaseBalanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> { - await this._zeroEx.token.transferAsync(tokenAddress, this._coinbase, address, amount); - } - private async _increaseAllowanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> { - const oldMakerAllowance = await this._zeroEx.token.getProxyAllowanceAsync(tokenAddress, address); - const newMakerAllowance = oldMakerAllowance.plus(amount); - await this._zeroEx.token.setProxyAllowanceAsync(tokenAddress, address, newMakerAllowance); - } + const signedOrder = await orderFactory.createSignedOrderAsync( + this._zeroEx, + makerAddress, + takerAddress, + makerFee, + takerFee, + makerFillableAmount, + makerTokenAddress, + takerFillableAmount, + takerTokenAddress, + this._exchangeContractAddress, + feeRecepient, + expirationUnixTimestampSec, + ); + return signedOrder; + } + private async _increaseBalanceAndAllowanceAsync( + tokenAddress: string, + address: string, + amount: BigNumber, + ): Promise<void> { + if (amount.isZero() || address === ZeroEx.NULL_ADDRESS) { + return; // noop + } + await Promise.all([ + this._increaseBalanceAsync(tokenAddress, address, amount), + this._increaseAllowanceAsync(tokenAddress, address, amount), + ]); + } + private async _increaseBalanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> { + await this._zeroEx.token.transferAsync(tokenAddress, this._coinbase, address, amount); + } + private async _increaseAllowanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> { + const oldMakerAllowance = await this._zeroEx.token.getProxyAllowanceAsync(tokenAddress, address); + const newMakerAllowance = oldMakerAllowance.plus(amount); + await this._zeroEx.token.setProxyAllowanceAsync(tokenAddress, address, newMakerAllowance); + } } diff --git a/packages/0x.js/test/utils/order_factory.ts b/packages/0x.js/test/utils/order_factory.ts index 08f2081a4..b65920e64 100644 --- a/packages/0x.js/test/utils/order_factory.ts +++ b/packages/0x.js/test/utils/order_factory.ts @@ -6,41 +6,41 @@ import { SignedOrder, ZeroEx } from '../../src'; const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false; export const orderFactory = { - async createSignedOrderAsync( - zeroEx: ZeroEx, - maker: string, - taker: string, - makerFee: BigNumber, - takerFee: BigNumber, - makerTokenAmount: BigNumber, - makerTokenAddress: string, - takerTokenAmount: BigNumber, - takerTokenAddress: string, - exchangeContractAddress: string, - feeRecipient: string, - expirationUnixTimestampSecIfExists?: BigNumber, - ): Promise<SignedOrder> { - const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite - const expirationUnixTimestampSec = _.isUndefined(expirationUnixTimestampSecIfExists) - ? defaultExpirationUnixTimestampSec - : expirationUnixTimestampSecIfExists; - const order = { - maker, - taker, - makerFee, - takerFee, - makerTokenAmount, - takerTokenAmount, - makerTokenAddress, - takerTokenAddress, - salt: ZeroEx.generatePseudoRandomSalt(), - exchangeContractAddress, - feeRecipient, - expirationUnixTimestampSec, - }; - const orderHash = ZeroEx.getOrderHashHex(order); - const ecSignature = await zeroEx.signOrderHashAsync(orderHash, maker, SHOULD_ADD_PERSONAL_MESSAGE_PREFIX); - const signedOrder: SignedOrder = _.assign(order, { ecSignature }); - return signedOrder; - }, + async createSignedOrderAsync( + zeroEx: ZeroEx, + maker: string, + taker: string, + makerFee: BigNumber, + takerFee: BigNumber, + makerTokenAmount: BigNumber, + makerTokenAddress: string, + takerTokenAmount: BigNumber, + takerTokenAddress: string, + exchangeContractAddress: string, + feeRecipient: string, + expirationUnixTimestampSecIfExists?: BigNumber, + ): Promise<SignedOrder> { + const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite + const expirationUnixTimestampSec = _.isUndefined(expirationUnixTimestampSecIfExists) + ? defaultExpirationUnixTimestampSec + : expirationUnixTimestampSecIfExists; + const order = { + maker, + taker, + makerFee, + takerFee, + makerTokenAmount, + takerTokenAmount, + makerTokenAddress, + takerTokenAddress, + salt: ZeroEx.generatePseudoRandomSalt(), + exchangeContractAddress, + feeRecipient, + expirationUnixTimestampSec, + }; + const orderHash = ZeroEx.getOrderHashHex(order); + const ecSignature = await zeroEx.signOrderHashAsync(orderHash, maker, SHOULD_ADD_PERSONAL_MESSAGE_PREFIX); + const signedOrder: SignedOrder = _.assign(order, { ecSignature }); + return signedOrder; + }, }; diff --git a/packages/0x.js/test/utils/report_callback_errors.ts b/packages/0x.js/test/utils/report_callback_errors.ts index 27c9745c9..0b619f73e 100644 --- a/packages/0x.js/test/utils/report_callback_errors.ts +++ b/packages/0x.js/test/utils/report_callback_errors.ts @@ -6,61 +6,61 @@ import { DoneCallback } from '../../src/types'; const expect = chai.expect; export const reportNoErrorCallbackErrors = (done: DoneCallback, expectToBeCalledOnce = true) => { - return <T>(f?: (value: T) => void) => { - const wrapped = (value: T) => { - if (_.isUndefined(f)) { - done(); - return; - } - try { - f(value); - if (expectToBeCalledOnce) { - done(); - } - } catch (err) { - done(err); - } - }; - return wrapped; - }; + return <T>(f?: (value: T) => void) => { + const wrapped = (value: T) => { + if (_.isUndefined(f)) { + done(); + return; + } + try { + f(value); + if (expectToBeCalledOnce) { + done(); + } + } catch (err) { + done(err); + } + }; + return wrapped; + }; }; export const reportNodeCallbackErrors = (done: DoneCallback, expectToBeCalledOnce = true) => { - return <T>(f?: (value: T) => void) => { - const wrapped = (error: Error | null, value: T | undefined) => { - if (!_.isNull(error)) { - done(error); - } else { - if (_.isUndefined(f)) { - done(); - return; - } - try { - f(value as T); - if (expectToBeCalledOnce) { - done(); - } - } catch (err) { - done(err); - } - } - }; - return wrapped; - }; + return <T>(f?: (value: T) => void) => { + const wrapped = (error: Error | null, value: T | undefined) => { + if (!_.isNull(error)) { + done(error); + } else { + if (_.isUndefined(f)) { + done(); + return; + } + try { + f(value as T); + if (expectToBeCalledOnce) { + done(); + } + } catch (err) { + done(err); + } + } + }; + return wrapped; + }; }; export const assertNodeCallbackError = (done: DoneCallback, errMsg: string) => { - const wrapped = <T>(error: Error | null, value: T | undefined) => { - if (_.isNull(error)) { - done(new Error('Expected callback to receive an error')); - } else { - try { - expect(error.message).to.be.equal(errMsg); - done(); - } catch (err) { - done(err); - } - } - }; - return wrapped; + const wrapped = <T>(error: Error | null, value: T | undefined) => { + if (_.isNull(error)) { + done(new Error('Expected callback to receive an error')); + } else { + try { + expect(error.message).to.be.equal(errMsg); + done(); + } catch (err) { + done(err); + } + } + }; + return wrapped; }; diff --git a/packages/0x.js/test/utils/subproviders/empty_wallet_subprovider.ts b/packages/0x.js/test/utils/subproviders/empty_wallet_subprovider.ts index 53f2be83d..466b0f350 100644 --- a/packages/0x.js/test/utils/subproviders/empty_wallet_subprovider.ts +++ b/packages/0x.js/test/utils/subproviders/empty_wallet_subprovider.ts @@ -6,22 +6,22 @@ import { JSONRPCPayload } from '../../../src/types'; * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js */ export class EmptyWalletSubprovider { - // This method needs to be here to satisfy the interface but linter wants it to be static. - // tslint:disable-next-line:prefer-function-over-method - public handleRequest(payload: JSONRPCPayload, next: () => void, end: (err: Error | null, result: any) => void) { - switch (payload.method) { - case 'eth_accounts': - end(null, []); - return; + // This method needs to be here to satisfy the interface but linter wants it to be static. + // tslint:disable-next-line:prefer-function-over-method + public handleRequest(payload: JSONRPCPayload, next: () => void, end: (err: Error | null, result: any) => void) { + switch (payload.method) { + case 'eth_accounts': + end(null, []); + return; - default: - next(); - return; - } - } - // Required to implement this method despite not needing it for this subprovider - // tslint:disable-next-line:prefer-function-over-method - public setEngine(engine: any) { - // noop - } + default: + next(); + return; + } + } + // Required to implement this method despite not needing it for this subprovider + // tslint:disable-next-line:prefer-function-over-method + public setEngine(engine: any) { + // noop + } } diff --git a/packages/0x.js/test/utils/subproviders/fake_gas_estimate_subprovider.ts b/packages/0x.js/test/utils/subproviders/fake_gas_estimate_subprovider.ts index e1113a851..307bfb188 100644 --- a/packages/0x.js/test/utils/subproviders/fake_gas_estimate_subprovider.ts +++ b/packages/0x.js/test/utils/subproviders/fake_gas_estimate_subprovider.ts @@ -9,26 +9,26 @@ import { JSONRPCPayload } from '../../../src/types'; * Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js */ export class FakeGasEstimateSubprovider { - private _constantGasAmount: number; - constructor(constantGasAmount: number) { - this._constantGasAmount = constantGasAmount; - } - // This method needs to be here to satisfy the interface but linter wants it to be static. - // tslint:disable-next-line:prefer-function-over-method - public handleRequest(payload: JSONRPCPayload, next: () => void, end: (err: Error | null, result: any) => void) { - switch (payload.method) { - case 'eth_estimateGas': - end(null, this._constantGasAmount); - return; + private _constantGasAmount: number; + constructor(constantGasAmount: number) { + this._constantGasAmount = constantGasAmount; + } + // This method needs to be here to satisfy the interface but linter wants it to be static. + // tslint:disable-next-line:prefer-function-over-method + public handleRequest(payload: JSONRPCPayload, next: () => void, end: (err: Error | null, result: any) => void) { + switch (payload.method) { + case 'eth_estimateGas': + end(null, this._constantGasAmount); + return; - default: - next(); - return; - } - } - // Required to implement this method despite not needing it for this subprovider - // tslint:disable-next-line:prefer-function-over-method - public setEngine(engine: any) { - // noop - } + default: + next(); + return; + } + } + // Required to implement this method despite not needing it for this subprovider + // tslint:disable-next-line:prefer-function-over-method + public setEngine(engine: any) { + // noop + } } diff --git a/packages/0x.js/test/utils/token_utils.ts b/packages/0x.js/test/utils/token_utils.ts index d3fc22ff4..9cbefcf8e 100644 --- a/packages/0x.js/test/utils/token_utils.ts +++ b/packages/0x.js/test/utils/token_utils.ts @@ -6,28 +6,28 @@ const PROTOCOL_TOKEN_SYMBOL = 'ZRX'; const WETH_TOKEN_SYMBOL = 'WETH'; export class TokenUtils { - private _tokens: Token[]; - constructor(tokens: Token[]) { - this._tokens = tokens; - } - public getProtocolTokenOrThrow(): Token { - const zrxToken = _.find(this._tokens, { symbol: PROTOCOL_TOKEN_SYMBOL }); - if (_.isUndefined(zrxToken)) { - throw new Error(InternalZeroExError.ZrxNotInTokenRegistry); - } - return zrxToken; - } - public getWethTokenOrThrow(): Token { - const wethToken = _.find(this._tokens, { symbol: WETH_TOKEN_SYMBOL }); - if (_.isUndefined(wethToken)) { - throw new Error(InternalZeroExError.WethNotInTokenRegistry); - } - return wethToken; - } - public getDummyTokens(): Token[] { - const dummyTokens = _.filter(this._tokens, token => { - return !_.includes([PROTOCOL_TOKEN_SYMBOL, WETH_TOKEN_SYMBOL], token.symbol); - }); - return dummyTokens; - } + private _tokens: Token[]; + constructor(tokens: Token[]) { + this._tokens = tokens; + } + public getProtocolTokenOrThrow(): Token { + const zrxToken = _.find(this._tokens, { symbol: PROTOCOL_TOKEN_SYMBOL }); + if (_.isUndefined(zrxToken)) { + throw new Error(InternalZeroExError.ZrxNotInTokenRegistry); + } + return zrxToken; + } + public getWethTokenOrThrow(): Token { + const wethToken = _.find(this._tokens, { symbol: WETH_TOKEN_SYMBOL }); + if (_.isUndefined(wethToken)) { + throw new Error(InternalZeroExError.WethNotInTokenRegistry); + } + return wethToken; + } + public getDummyTokens(): Token[] { + const dummyTokens = _.filter(this._tokens, token => { + return !_.includes([PROTOCOL_TOKEN_SYMBOL, WETH_TOKEN_SYMBOL], token.symbol); + }); + return dummyTokens; + } } diff --git a/packages/0x.js/test/utils/web3_factory.ts b/packages/0x.js/test/utils/web3_factory.ts index 26c26e03d..640b96256 100644 --- a/packages/0x.js/test/utils/web3_factory.ts +++ b/packages/0x.js/test/utils/web3_factory.ts @@ -19,24 +19,24 @@ import { constants } from './constants'; import * as Web3 from 'web3'; export const web3Factory = { - create(hasAddresses: boolean = true): Web3 { - const provider = this.getRpcProvider(hasAddresses); - const web3 = new Web3(); - web3.setProvider(provider); - return web3; - }, - getRpcProvider(hasAddresses: boolean = true): Web3.Provider { - const provider = new ProviderEngine(); - if (!hasAddresses) { - provider.addProvider(new EmptyWalletSubprovider()); - } - provider.addProvider(new FakeGasEstimateSubprovider(constants.GAS_ESTIMATE)); - provider.addProvider( - new RpcSubprovider({ - rpcUrl: constants.RPC_URL, - }), - ); - provider.start(); - return provider; - }, + create(hasAddresses: boolean = true): Web3 { + const provider = this.getRpcProvider(hasAddresses); + const web3 = new Web3(); + web3.setProvider(provider); + return web3; + }, + getRpcProvider(hasAddresses: boolean = true): Web3.Provider { + const provider = new ProviderEngine(); + if (!hasAddresses) { + provider.addProvider(new EmptyWalletSubprovider()); + } + provider.addProvider(new FakeGasEstimateSubprovider(constants.GAS_ESTIMATE)); + provider.addProvider( + new RpcSubprovider({ + rpcUrl: constants.RPC_URL, + }), + ); + provider.start(); + return provider; + }, }; diff --git a/packages/0x.js/tsconfig.json b/packages/0x.js/tsconfig.json index 117f51e83..0666fbd82 100644 --- a/packages/0x.js/tsconfig.json +++ b/packages/0x.js/tsconfig.json @@ -1,16 +1,16 @@ { - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib", - "noImplicitThis": false - }, - "include": [ - "./src/**/*", - "./test/**/*", - "../../node_modules/types-bn/index.d.ts", - "../../node_modules/types-ethereumjs-util/index.d.ts", - "../../node_modules/web3-typescript-typings/index.d.ts", - "../../node_modules/chai-typescript-typings/index.d.ts", - "../../node_modules/chai-as-promised-typescript-typings/index.d.ts" - ] + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "noImplicitThis": false + }, + "include": [ + "./src/**/*", + "./test/**/*", + "../../node_modules/types-bn/index.d.ts", + "../../node_modules/types-ethereumjs-util/index.d.ts", + "../../node_modules/web3-typescript-typings/index.d.ts", + "../../node_modules/chai-typescript-typings/index.d.ts", + "../../node_modules/chai-as-promised-typescript-typings/index.d.ts" + ] } diff --git a/packages/0x.js/tslint.json b/packages/0x.js/tslint.json index ffaefe83a..e63054bfc 100644 --- a/packages/0x.js/tslint.json +++ b/packages/0x.js/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0xproject/tslint-config"] } |