diff options
author | Fabio Berger <me@fabioberger.com> | 2018-05-10 02:36:28 +0800 |
---|---|---|
committer | Fabio Berger <me@fabioberger.com> | 2018-05-10 02:36:28 +0800 |
commit | 209266dbed9d7d038c90c2da8d9b99acab77c80c (patch) | |
tree | cfefd5ab15bc237716b15687629e0f41362a8e86 /packages/0x.js | |
parent | 69a6166b6a1d39afc24b8dd950ec5d8539a03420 (diff) | |
download | dexon-sol-tools-209266dbed9d7d038c90c2da8d9b99acab77c80c.tar dexon-sol-tools-209266dbed9d7d038c90c2da8d9b99acab77c80c.tar.gz dexon-sol-tools-209266dbed9d7d038c90c2da8d9b99acab77c80c.tar.bz2 dexon-sol-tools-209266dbed9d7d038c90c2da8d9b99acab77c80c.tar.lz dexon-sol-tools-209266dbed9d7d038c90c2da8d9b99acab77c80c.tar.xz dexon-sol-tools-209266dbed9d7d038c90c2da8d9b99acab77c80c.tar.zst dexon-sol-tools-209266dbed9d7d038c90c2da8d9b99acab77c80c.zip |
Split 0x.js into contract-wrappers, order-watcher but keep 0x.js as a unifying library with the same interface
Diffstat (limited to 'packages/0x.js')
47 files changed, 79 insertions, 8332 deletions
diff --git a/packages/0x.js/CHANGELOG.json b/packages/0x.js/CHANGELOG.json index bdc575903..41879ade0 100644 --- a/packages/0x.js/CHANGELOG.json +++ b/packages/0x.js/CHANGELOG.json @@ -1,5 +1,15 @@ [ { + "version": "0.38.0", + "changes": [ + { + "note": "Renamed createOrderStateWatcher to createOrderWatcherAsync since it is not async", + "note": + "Renamed ZeroExError to ContractWrappersErrors since they not live in the @0xproject/contract-wrappers subpackage" + } + ] + }, + { "timestamp": 1525477860, "version": "0.37.2", "changes": [ diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index d3cf9ad5f..7940d044f 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -65,15 +65,11 @@ "@0xproject/dev-utils": "^0.4.1", "@0xproject/migrations": "^0.0.5", "@0xproject/monorepo-scripts": "^0.1.19", - "@0xproject/subproviders": "^0.10.1", "@0xproject/tslint-config": "^0.4.17", - "@types/bintrees": "^1.0.2", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "@types/node": "^8.0.53", - "@types/request": "2.47.0", "@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", @@ -86,35 +82,26 @@ "nyc": "^11.0.1", "opn-cli": "^3.1.0", "prettier": "^1.11.1", - "request": "^2.81.0", "shx": "^0.2.2", "sinon": "^4.0.0", "source-map-support": "^0.5.0", "tslint": "5.8.0", "typedoc": "0xProject/typedoc", "typescript": "2.7.1", - "web3-provider-engine": "^14.0.4", "webpack": "^3.1.0" }, "dependencies": { "@0xproject/assert": "^0.2.9", "@0xproject/base-contract": "^0.3.1", - "@0xproject/json-schemas": "^0.7.23", + "@0xproject/contract-wrappers": "^0.0.1", + "@0xproject/order-watcher": "^0.0.1", "@0xproject/order-utils": "^0.0.4", "@0xproject/types": "^0.6.3", "@0xproject/typescript-typings": "^0.3.1", "@0xproject/utils": "^0.6.1", "@0xproject/web3-wrapper": "^0.6.3", - "bintrees": "^1.0.2", - "bn.js": "^4.11.8", - "ethereumjs-abi": "^0.6.4", - "ethereumjs-blockstream": "^2.0.6", - "ethereumjs-util": "^5.1.1", "ethers": "^3.0.15", - "js-sha3": "^0.7.0", - "lodash": "^4.17.4", - "uuid": "^3.1.0", - "web3": "^0.20.0" + "lodash": "^4.17.4" }, "publishConfig": { "access": "public" diff --git a/packages/0x.js/src/0x.ts b/packages/0x.js/src/0x.ts index 78f9f6beb..e670bd0c5 100644 --- a/packages/0x.js/src/0x.ts +++ b/packages/0x.js/src/0x.ts @@ -1,4 +1,13 @@ -import { schemas, SchemaValidator } from '@0xproject/json-schemas'; +import { assert } from '@0xproject/assert'; +import { + ContractWrappers, + EtherTokenWrapper, + ExchangeWrapper, + TokenRegistryWrapper, + TokenTransferProxyWrapper, + TokenWrapper, + ZeroExContractConfig, +} from '@0xproject/contract-wrappers'; import { generatePseudoRandomSalt, getOrderHashHex, @@ -6,27 +15,13 @@ import { isValidSignature, signOrderHashAsync, } from '@0xproject/order-utils'; +import { OrderWatcher, OrderWatcherConfig } from '@0xproject/order-watcher'; import { ECSignature, Order, Provider, SignedOrder, TransactionReceiptWithDecodedLogs } from '@0xproject/types'; -import { AbiDecoder, BigNumber, intervalUtils } from '@0xproject/utils'; +import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { artifacts } from './artifacts'; -import { EtherTokenWrapper } from './contract_wrappers/ether_token_wrapper'; -import { ExchangeWrapper } from './contract_wrappers/exchange_wrapper'; -import { TokenRegistryWrapper } from './contract_wrappers/token_registry_wrapper'; -import { TokenTransferProxyWrapper } from './contract_wrappers/token_transfer_proxy_wrapper'; -import { TokenWrapper } from './contract_wrappers/token_wrapper'; -import { OrderStateWatcher } from './order_watcher/order_state_watcher'; -import { zeroExConfigSchema } from './schemas/zero_ex_config_schema'; -import { zeroExPrivateNetworkConfigSchema } from './schemas/zero_ex_private_network_config_schema'; -import { zeroExPublicNetworkConfigSchema } from './schemas/zero_ex_public_network_config_schema'; -import { OrderStateWatcherConfig, ZeroExConfig, ZeroExError } from './types'; -import { assert } from './utils/assert'; import { constants } from './utils/constants'; -import { decorators } from './utils/decorators'; -import { utils } from './utils/utils'; /** * The ZeroEx class is the single entry-point into the 0x.js library. It contains all of the library's functionality @@ -62,7 +57,7 @@ export class ZeroEx { * tokenTransferProxy smart contract. */ public proxy: TokenTransferProxyWrapper; - private _web3Wrapper: Web3Wrapper; + private _contractWrappers: ContractWrappers; /** * Generates a pseudo-random 256-bit salt. * The salt can be included in a 0x order, ensuring that the order generates a unique orderHash @@ -136,40 +131,15 @@ export class ZeroEx { * @param config The configuration object. Look up the type for the description. * @return An instance of the 0x.js ZeroEx class. */ - constructor(provider: Provider, config: ZeroExConfig) { + constructor(provider: Provider, config: ZeroExContractConfig) { assert.isWeb3Provider('provider', provider); - assert.doesConformToSchema('config', config, zeroExConfigSchema, [ - zeroExPrivateNetworkConfigSchema, - zeroExPublicNetworkConfigSchema, - ]); - const artifactJSONs = _.values(artifacts); - const abiArrays = _.map(artifactJSONs, artifact => artifact.abi); - const defaults = { - gasPrice: config.gasPrice, - }; - this._web3Wrapper = new Web3Wrapper(provider, defaults); - _.forEach(abiArrays, abi => { - this._web3Wrapper.abiDecoder.addABI(abi); - }); - this.proxy = new TokenTransferProxyWrapper( - this._web3Wrapper, - config.networkId, - config.tokenTransferProxyContractAddress, - ); - this.token = new TokenWrapper(this._web3Wrapper, config.networkId, this.proxy); - this.exchange = new ExchangeWrapper( - this._web3Wrapper, - config.networkId, - 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.token); + this._contractWrappers = new ContractWrappers(provider, config); + + this.proxy = this._contractWrappers.proxy; + this.token = this._contractWrappers.token; + this.exchange = this._contractWrappers.exchange; + this.tokenRegistry = this._contractWrappers.tokenRegistry; + this.etherToken = this._contractWrappers.etherToken; } /** * Sets a new web3 provider for 0x.js. Updating the provider will stop all @@ -178,31 +148,23 @@ export class ZeroEx { * @param networkId The id of the network your provider is connected to */ public setProvider(provider: Provider, 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); + this._contractWrappers.setProvider(provider, networkId); } /** * Get the provider instance currently used by 0x.js * @return Web3 provider instance */ public getProvider(): Provider { - return this._web3Wrapper.getProvider(); + return this._contractWrappers.getProvider(); } /** * 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(); + // Hack: Get Web3Wrapper from ZeroExContract + const web3Wrapper = (this._contractWrappers as any)._web3Wrapper; + const availableAddresses = await web3Wrapper.getAvailableAddressesAsync(); return availableAddresses; } /** @@ -223,7 +185,7 @@ export class ZeroEx { shouldAddPersonalMessagePrefix: boolean, ): Promise<ECSignature> { return signOrderHashAsync( - this._web3Wrapper.getProvider(), + this._contractWrappers.getProvider(), orderHash, signerAddress, shouldAddPersonalMessagePrefix, @@ -241,7 +203,9 @@ export class ZeroEx { pollingIntervalMs = 1000, timeoutMs?: number, ): Promise<TransactionReceiptWithDecodedLogs> { - const transactionReceiptWithDecodedLogs = await this._web3Wrapper.awaitTransactionMinedAsync( + // Hack: Get Web3Wrapper from ZeroExContract + const web3Wrapper = (this._contractWrappers as any)._web3Wrapper; + const transactionReceiptWithDecodedLogs = await web3Wrapper.awaitTransactionMinedAsync( txHash, pollingIntervalMs, timeoutMs, @@ -249,22 +213,16 @@ export class ZeroEx { return transactionReceiptWithDecodedLogs; } /** - * Instantiates and returns a new OrderStateWatcher instance. + * Instantiates and returns a new OrderWatcher instance. * Defaults to watching the pending state. * @param config The configuration object. Look up the type for the description. - * @return An instance of the 0x.js OrderStateWatcher class. - */ - public createOrderStateWatcher(config?: OrderStateWatcherConfig) { - return new OrderStateWatcher(this._web3Wrapper, this.token, this.exchange, config); - } - /* - * 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; + * @return An instance of the 0x.js OrderWatcher class. + */ + public async createOrderWatcherAsync(config?: OrderWatcherConfig) { + // Hack: Get Web3Wrapper from ZeroExContract + const web3Wrapper = (this._contractWrappers as any)._web3Wrapper; + const networkId = web3Wrapper.getNetworkIdAsync(); + const provider = this._contractWrappers.getProvider(); + return new OrderWatcher(provider, networkId, config); } } diff --git a/packages/0x.js/src/abstract/balance_and_proxy_allowance_fetcher.ts b/packages/0x.js/src/abstract/balance_and_proxy_allowance_fetcher.ts deleted file mode 100644 index c357f8447..000000000 --- a/packages/0x.js/src/abstract/balance_and_proxy_allowance_fetcher.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { BigNumber } from '@0xproject/utils'; - -export abstract class BalanceAndProxyAllowanceFetcher { - public abstract async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber>; - public abstract async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber>; -} diff --git a/packages/0x.js/src/abstract/order_filled_cancelled_fetcher.ts b/packages/0x.js/src/abstract/order_filled_cancelled_fetcher.ts deleted file mode 100644 index 81ae04b9c..000000000 --- a/packages/0x.js/src/abstract/order_filled_cancelled_fetcher.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { BigNumber } from '@0xproject/utils'; - -export abstract class OrderFilledCancelledFetcher { - public abstract async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber>; - public abstract async getCancelledTakerAmountAsync(orderHash: string): Promise<BigNumber>; -} diff --git a/packages/0x.js/src/artifacts.ts b/packages/0x.js/src/artifacts.ts deleted file mode 100644 index a91d9ae1f..000000000 --- a/packages/0x.js/src/artifacts.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as DummyTokenArtifact from './compact_artifacts/DummyToken.json'; -import * as EtherTokenArtifact from './compact_artifacts/EtherToken.json'; -import * as ExchangeArtifact from './compact_artifacts/Exchange.json'; -import * as TokenArtifact from './compact_artifacts/Token.json'; -import * as TokenRegistryArtifact from './compact_artifacts/TokenRegistry.json'; -import * as TokenTransferProxyArtifact from './compact_artifacts/TokenTransferProxy.json'; -import * as ZRXArtifact from './compact_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, -}; diff --git a/packages/0x.js/src/contract_wrappers/contract_wrapper.ts b/packages/0x.js/src/contract_wrappers/contract_wrapper.ts deleted file mode 100644 index 02be2dab3..000000000 --- a/packages/0x.js/src/contract_wrappers/contract_wrapper.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { BlockParamLiteral, ContractAbi, FilterObject, LogEntry, LogWithDecodedArgs, RawLog } from '@0xproject/types'; -import { AbiDecoder, intervalUtils } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import { Block, BlockAndLogStreamer } from 'ethereumjs-blockstream'; -import * as _ from 'lodash'; -import * as Web3 from 'web3'; - -import { - Artifact, - BlockRange, - ContractEventArgs, - ContractEvents, - EventCallback, - IndexedFilterValues, - InternalZeroExError, - ZeroExError, -} from '../types'; -import { constants } from '../utils/constants'; -import { filterUtils } from '../utils/filter_utils'; - -const CONTRACT_NAME_TO_NOT_FOUND_ERROR: { - [contractName: string]: ZeroExError; -} = { - ZRX: ZeroExError.ZRXContractDoesNotExist, - EtherToken: ZeroExError.EtherTokenContractDoesNotExist, - Token: ZeroExError.TokenContractDoesNotExist, - TokenRegistry: ZeroExError.TokenRegistryContractDoesNotExist, - TokenTransferProxy: ZeroExError.TokenTransferProxyContractDoesNotExist, - Exchange: ZeroExError.ExchangeContractDoesNotExist, -}; - -export class ContractWrapper { - protected _web3Wrapper: Web3Wrapper; - protected _networkId: number; - private _blockAndLogStreamerIfExists?: BlockAndLogStreamer; - private _blockAndLogStreamIntervalIfExists?: NodeJS.Timer; - private _filters: { [filterToken: string]: FilterObject }; - private _filterCallbacks: { - [filterToken: string]: EventCallback<ContractEventArgs>; - }; - private _onLogAddedSubscriptionToken: string | undefined; - private _onLogRemovedSubscriptionToken: string | undefined; - constructor(web3Wrapper: Web3Wrapper, networkId: number) { - this._web3Wrapper = web3Wrapper; - this._networkId = networkId; - 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: 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: 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: LogEntry, - ): LogWithDecodedArgs<ArgsType> | RawLog { - if (_.isUndefined(this._web3Wrapper.abiDecoder)) { - throw new Error(InternalZeroExError.NoAbiDecoder); - } - const logWithDecodedArgs = this._web3Wrapper.abiDecoder.tryToDecodeLogOrNoop(log); - return logWithDecodedArgs; - } - protected async _getContractAbiAndAddressFromArtifactsAsync( - artifact: Artifact, - addressIfExists?: string, - ): Promise<[ContractAbi, string]> { - 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 abiAndAddress: [ContractAbi, string] = [artifact.abi, contractAddress]; - return abiAndAddress; - } - 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: LogEntry): void { - _.forEach(this._filters, (filter: 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._blockAndLogStreamIntervalIfExists = 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._blockAndLogStreamIntervalIfExists as NodeJS.Timer); - 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 deleted file mode 100644 index fd39de34b..000000000 --- a/packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { schemas } from '@0xproject/json-schemas'; -import { LogWithDecodedArgs } from '@0xproject/types'; -import { AbiDecoder, BigNumber } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as _ from 'lodash'; - -import { artifacts } from '../artifacts'; -import { BlockRange, EventCallback, IndexedFilterValues, TransactionOpts, ZeroExError } from '../types'; -import { assert } from '../utils/assert'; - -import { ContractWrapper } from './contract_wrapper'; -import { EtherTokenContract, EtherTokenContractEventArgs, EtherTokenEvents } from './generated/ether_token'; -import { TokenWrapper } from './token_wrapper'; - -/** - * This class includes all the functionality related to interacting with a wrapped Ether ERC20 token contract. - * 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, tokenWrapper: TokenWrapper) { - super(web3Wrapper, networkId); - 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.isETHAddressHex('etherTokenAddress', etherTokenAddress); - assert.isValidBaseUnitAmount('amountInWei', amountInWei); - await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper); - const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase(); - const normalizedDepositorAddress = depositor.toLowerCase(); - - const ethBalanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(normalizedDepositorAddress); - assert.assert(ethBalanceInWei.gte(amountInWei), ZeroExError.InsufficientEthBalanceForDeposit); - - const wethContract = await this._getEtherTokenContractAsync(normalizedEtherTokenAddress); - const txHash = await wethContract.deposit.sendTransactionAsync({ - from: normalizedDepositorAddress, - 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 withdrawal. - * @param txOpts Transaction parameters. - * @return Transaction hash. - */ - public async withdrawAsync( - etherTokenAddress: string, - amountInWei: BigNumber, - withdrawer: string, - txOpts: TransactionOpts = {}, - ): Promise<string> { - assert.isValidBaseUnitAmount('amountInWei', amountInWei); - assert.isETHAddressHex('etherTokenAddress', etherTokenAddress); - await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper); - const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase(); - const normalizedWithdrawerAddress = withdrawer.toLowerCase(); - - const WETHBalanceInBaseUnits = await this._tokenWrapper.getBalanceAsync( - normalizedEtherTokenAddress, - normalizedWithdrawerAddress, - ); - assert.assert(WETHBalanceInBaseUnits.gte(amountInWei), ZeroExError.InsufficientWEthBalanceForWithdrawal); - - const wethContract = await this._getEtherTokenContractAsync(normalizedEtherTokenAddress); - const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, { - from: normalizedWithdrawerAddress, - gas: txOpts.gasLimit, - gasPrice: txOpts.gasPrice, - }); - return txHash; - } - /** - * Gets historical logs without creating a subscription - * @param etherTokenAddress An address of the ether token that emitted 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); - const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase(); - assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents); - assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - const logs = await this._getLogsAsync<ArgsType>( - normalizedEtherTokenAddress, - 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); - const normalizedEtherTokenAddress = etherTokenAddress.toLowerCase(); - assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - assert.isFunction('callback', callback); - const subscriptionToken = this._subscribe<ArgsType>( - normalizedEtherTokenAddress, - 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(); - } - /** - * Retrieves the Ethereum address of the EtherToken contract deployed on the network - * that the user-passed web3 provider is connected to. If it's not Kovan, Ropsten, Rinkeby, Mainnet or TestRPC - * (networkId: 50), it will return undefined (e.g a private network). - * @returns The Ethereum address of the EtherToken contract or undefined. - */ - public getContractAddressIfExists(): string | undefined { - const networkSpecificArtifact = artifacts.EtherTokenArtifact.networks[this._networkId]; - const contractAddressIfExists = _.isUndefined(networkSpecificArtifact) - ? undefined - : networkSpecificArtifact.address; - return contractAddressIfExists; - } - 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 [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( - artifacts.EtherTokenArtifact, - etherTokenAddress, - ); - const contractInstance = new EtherTokenContract( - abi, - address, - this._web3Wrapper.getProvider(), - 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 deleted file mode 100644 index 6ddaaaf4f..000000000 --- a/packages/0x.js/src/contract_wrappers/exchange_wrapper.ts +++ /dev/null @@ -1,964 +0,0 @@ -import { schemas } from '@0xproject/json-schemas'; -import { getOrderHashHex } from '@0xproject/order-utils'; -import { - BlockParamLiteral, - DecodedLogArgs, - ECSignature, - LogEntry, - LogWithDecodedArgs, - Order, - SignedOrder, -} from '@0xproject/types'; -import { AbiDecoder, BigNumber } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as _ from 'lodash'; - -import { artifacts } from '../artifacts'; -import { SimpleBalanceAndProxyAllowanceFetcher } from '../fetchers/simple_balance_and_proxy_allowance_fetcher'; -import { SimpleOrderFilledCancelledFetcher } from '../fetchers/simple_order_filled_cancelled_fetcher'; -import { - BlockRange, - EventCallback, - ExchangeContractErrCodes, - ExchangeContractErrs, - IndexedFilterValues, - MethodOpts, - OrderAddresses, - OrderCancellationRequest, - OrderFillRequest, - OrderState, - OrderTransactionOpts, - OrderValues, - ValidateOrderFillableOpts, -} from '../types'; -import { assert } from '../utils/assert'; -import { decorators } from '../utils/decorators'; -import { ExchangeTransferSimulator } from '../utils/exchange_transfer_simulator'; -import { OrderStateUtils } from '../utils/order_state_utils'; -import { OrderValidationUtils } from '../utils/order_validation_utils'; -import { utils } from '../utils/utils'; - -import { ContractWrapper } from './contract_wrapper'; -import { - ExchangeContract, - ExchangeContractEventArgs, - ExchangeEvents, - LogErrorContractEventArgs, -} from './generated/exchange'; -import { TokenWrapper } from './token_wrapper'; -const SHOULD_VALIDATE_BY_DEFAULT = true; - -interface ExchangeContractErrCodesToMsgs { - [exchangeContractErrCodes: number]: string; -} - -/** - * This class includes all the functionality related to calling methods and subscribing to - * 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, - tokenWrapper: TokenWrapper, - contractAddressIfExists?: string, - zrxContractAddressIfExists?: string, - ) { - super(web3Wrapper, networkId); - 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; - const txData = {}; - let unavailableTakerTokenAmount = await exchangeContract.getUnavailableTakerTokenAmount.callAsync( - orderHash, - txData, - 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; - const txData = {}; - let fillAmountInBaseUnits = await exchangeContract.filled.callAsync(orderHash, txData, 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; - const txData = {}; - let cancelledAmountInBaseUnits = await exchangeContract.cancelled.callAsync(orderHash, txData, 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 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 normalizedTakerAddress = takerAddress.toLowerCase(); - - 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, - normalizedTakerAddress, - zrxTokenAddress, - ); - } - - 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: normalizedTakerAddress, - 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 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 normalizedTakerAddress = takerAddress.toLowerCase(); - - 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), - normalizedTakerAddress, - zrxTokenAddress, - ); - filledTakerTokenAmount = filledTakerTokenAmount.plus(singleFilledTakerTokenAmount); - if (filledTakerTokenAmount.eq(fillTakerTokenAmount)) { - break; - } - } - } - - 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 exchangeInstance = await this._getExchangeContractAsync(); - const txHash = await exchangeInstance.fillOrdersUpTo.sendTransactionAsync( - orderAddressesArray, - orderValuesArray, - fillTakerTokenAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - vArray, - rArray, - sArray, - { - from: normalizedTakerAddress, - 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 - * 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 normalizedTakerAddress = takerAddress.toLowerCase(); - 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, - normalizedTakerAddress, - 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 exchangeInstance = await this._getExchangeContractAsync(); - const txHash = await exchangeInstance.batchFillOrders.sendTransactionAsync( - orderAddressesArray, - orderValuesArray, - fillTakerTokenAmounts, - shouldThrowOnInsufficientBalanceOrAllowance, - vArray, - rArray, - sArray, - { - from: normalizedTakerAddress, - 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 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 normalizedTakerAddress = takerAddress.toLowerCase(); - - 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, - normalizedTakerAddress, - 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: normalizedTakerAddress, - 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 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); - const normalizedTakerAddress = takerAddress.toLowerCase(); - 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, - normalizedTakerAddress, - 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, - ]; - }); - - // 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: normalizedTakerAddress, - 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 normalizedMakerAddress = order.maker.toLowerCase(); - - const exchangeInstance = await this._getExchangeContractAsync(); - - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - const orderHash = 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: normalizedMakerAddress, - 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 normalizedMakerAddress = maker.toLowerCase(); - - const shouldValidate = _.isUndefined(orderTransactionOpts.shouldValidate) - ? SHOULD_VALIDATE_BY_DEFAULT - : orderTransactionOpts.shouldValidate; - if (shouldValidate) { - for (const orderCancellationRequest of orderCancellationRequests) { - const orderHash = 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: normalizedMakerAddress, - 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 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 normalizedTakerAddress = takerAddress.toLowerCase(); - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - normalizedTakerAddress, - 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 = 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 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 normalizedTakerAddress = takerAddress.toLowerCase(); - const zrxTokenAddress = this.getZRXTokenAddress(); - const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper, BlockParamLiteral.Latest); - await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - normalizedTakerAddress, - 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 emitted by Exchange contract on transaction failure. - * @param logs Transaction logs as returned by `zeroEx.awaitTransactionMinedAsync` - */ - public throwLogErrorsAsErrors(logs: Array<LogWithDecodedArgs<DecodedLogArgs> | LogEntry>): void { - const errLog = _.find(logs, { - event: ExchangeEvents.LogError, - }); - if (!_.isUndefined(errLog)) { - const logArgs = (errLog as LogWithDecodedArgs<LogErrorContractEventArgs>).args; - const errCode = logArgs.errorId; - const errMessage = this._exchangeContractErrCodesToMsg[errCode]; - throw new Error(errMessage); - } - } - /** - * Gets the latest OrderState of a signedOrder - * @param signedOrder The signedOrder - * @param stateLayer Optional, desired blockchain state layer (defaults to latest). - * @return OrderState of the signedOrder - */ - public async getOrderStateAsync( - signedOrder: SignedOrder, - stateLayer: BlockParamLiteral = BlockParamLiteral.Latest, - ): Promise<OrderState> { - const simpleBalanceAndProxyAllowanceFetcher = new SimpleBalanceAndProxyAllowanceFetcher( - this._tokenWrapper, - stateLayer, - ); - const simpleOrderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher(this, stateLayer); - const orderStateUtils = new OrderStateUtils( - simpleBalanceAndProxyAllowanceFetcher, - simpleOrderFilledCancelledFetcher, - ); - const orderState = orderStateUtils.getOrderStateAsync(signedOrder); - return orderState; - } - /** - * 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 normalizedSignerAddress = signerAddressHex.toLowerCase(); - - const exchangeInstance = await this._getExchangeContractAsync(); - - const isValidSignature = await exchangeInstance.isValidSignature.callAsync( - normalizedSignerAddress, - 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 [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( - artifacts.ExchangeArtifact, - this._contractAddressIfExists, - ); - const contractInstance = new ExchangeContract( - abi, - address, - this._web3Wrapper.getProvider(), - 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 deleted file mode 100644 index c4a193264..000000000 --- a/packages/0x.js/src/contract_wrappers/token_registry_wrapper.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as _ from 'lodash'; - -import { artifacts } from '../artifacts'; -import { Token, TokenMetadata } from '../types'; -import { assert } from '../utils/assert'; -import { constants } from '../utils/constants'; - -import { ContractWrapper } from './contract_wrapper'; -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], - }; - 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(); - const lowerCaseAddresses = _.map(addresses, address => address.toLowerCase()); - return lowerCaseAddresses; - } - /** - * 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 normalizedAddress = address.toLowerCase(); - - const tokenRegistryContract = await this._getTokenRegistryContractAsync(); - const metadata = await tokenRegistryContract.getTokenMetaData.callAsync(normalizedAddress); - 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 [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( - artifacts.TokenRegistryArtifact, - this._contractAddressIfExists, - ); - const contractInstance = new TokenRegistryContract( - abi, - address, - this._web3Wrapper.getProvider(), - 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 deleted file mode 100644 index be558b5be..000000000 --- a/packages/0x.js/src/contract_wrappers/token_transfer_proxy_wrapper.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as _ from 'lodash'; - -import { artifacts } from '../artifacts'; -import { assert } from '../utils/assert'; - -import { ContractWrapper } from './contract_wrapper'; -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> { - assert.isETHAddressHex('exchangeContractAddress', exchangeContractAddress); - const normalizedExchangeContractAddress = exchangeContractAddress.toLowerCase(); - const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync(); - const isAuthorized = await tokenTransferProxyContractInstance.authorized.callAsync( - normalizedExchangeContractAddress, - ); - 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 [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( - artifacts.TokenTransferProxyArtifact, - this._contractAddressIfExists, - ); - const contractInstance = new TokenTransferProxyContract( - abi, - address, - this._web3Wrapper.getProvider(), - 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 deleted file mode 100644 index 194cfb5aa..000000000 --- a/packages/0x.js/src/contract_wrappers/token_wrapper.ts +++ /dev/null @@ -1,434 +0,0 @@ -import { schemas } from '@0xproject/json-schemas'; -import { LogWithDecodedArgs } from '@0xproject/types'; -import { AbiDecoder, BigNumber } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as _ from 'lodash'; - -import { artifacts } from '../artifacts'; -import { BlockRange, EventCallback, IndexedFilterValues, MethodOpts, TransactionOpts, ZeroExError } from '../types'; -import { assert } from '../utils/assert'; -import { constants } from '../utils/constants'; - -import { ContractWrapper } from './contract_wrapper'; -import { TokenContract, TokenContractEventArgs, TokenEvents } from './generated/token'; -import { TokenTransferProxyWrapper } from './token_transfer_proxy_wrapper'; - -/** - * This class includes all the functionality related to interacting with ERC20 token contracts. - * All ERC20 method calls are supported, along with some convenience methods for getting/setting allowances - * 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, tokenTransferProxyWrapper: TokenTransferProxyWrapper) { - super(web3Wrapper, networkId); - 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 normalizedTokenAddress = tokenAddress.toLowerCase(); - const normalizedOwnerAddress = ownerAddress.toLowerCase(); - - const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); - const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - const txData = {}; - let balance = await tokenContract.balanceOf.callAsync(normalizedOwnerAddress, txData, 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> { - assert.isETHAddressHex('spenderAddress', spenderAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); - await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper); - const normalizedTokenAddress = tokenAddress.toLowerCase(); - const normalizedSpenderAddress = spenderAddress.toLowerCase(); - const normalizedOwnerAddress = ownerAddress.toLowerCase(); - assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - - const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); - const txHash = await tokenContract.approve.sendTransactionAsync(normalizedSpenderAddress, amountInBaseUnits, { - from: normalizedOwnerAddress, - 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> { - assert.isETHAddressHex('ownerAddress', ownerAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); - assert.isETHAddressHex('spenderAddress', spenderAddress); - const normalizedTokenAddress = tokenAddress.toLowerCase(); - const normalizedOwnerAddress = ownerAddress.toLowerCase(); - const normalizedSpenderAddress = spenderAddress.toLowerCase(); - const txHash = await this.setAllowanceAsync( - normalizedTokenAddress, - normalizedOwnerAddress, - normalizedSpenderAddress, - 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); - assert.isETHAddressHex('spenderAddress', spenderAddress); - const normalizedTokenAddress = tokenAddress.toLowerCase(); - const normalizedOwnerAddress = ownerAddress.toLowerCase(); - const normalizedSpenderAddress = spenderAddress.toLowerCase(); - - const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); - const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock; - const txData = {}; - let allowanceInBaseUnits = await tokenContract.allowance.callAsync( - normalizedOwnerAddress, - normalizedSpenderAddress, - txData, - 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 normalizedTokenAddress = tokenAddress.toLowerCase(); - const normalizedOwnerAddress = ownerAddress.toLowerCase(); - - const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress(); - const allowanceInBaseUnits = await this.getAllowanceAsync( - normalizedTokenAddress, - normalizedOwnerAddress, - 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); - const normalizedTokenAddress = tokenAddress.toLowerCase(); - const normalizedOwnerAddress = ownerAddress.toLowerCase(); - assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - - const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress(); - const txHash = await this.setAllowanceAsync( - normalizedTokenAddress, - normalizedOwnerAddress, - 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> { - assert.isETHAddressHex('ownerAddress', ownerAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); - const normalizedTokenAddress = tokenAddress.toLowerCase(); - const normalizedOwnerAddress = ownerAddress.toLowerCase(); - const txHash = await this.setProxyAllowanceAsync( - normalizedTokenAddress, - normalizedOwnerAddress, - 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); - assert.isETHAddressHex('toAddress', toAddress); - await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper); - const normalizedTokenAddress = tokenAddress.toLowerCase(); - const normalizedFromAddress = fromAddress.toLowerCase(); - const normalizedToAddress = toAddress.toLowerCase(); - assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - - const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); - - const fromAddressBalance = await this.getBalanceAsync(normalizedTokenAddress, normalizedFromAddress); - if (fromAddressBalance.lessThan(amountInBaseUnits)) { - throw new Error(ZeroExError.InsufficientBalanceForTransfer); - } - - const txHash = await tokenContract.transfer.sendTransactionAsync(normalizedToAddress, amountInBaseUnits, { - from: normalizedFromAddress, - 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('toAddress', toAddress); - assert.isETHAddressHex('fromAddress', fromAddress); - assert.isETHAddressHex('tokenAddress', tokenAddress); - await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper); - const normalizedToAddress = toAddress.toLowerCase(); - const normalizedFromAddress = fromAddress.toLowerCase(); - const normalizedTokenAddress = tokenAddress.toLowerCase(); - const normalizedSenderAddress = senderAddress.toLowerCase(); - assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits); - - const tokenContract = await this._getTokenContractAsync(normalizedTokenAddress); - - const fromAddressAllowance = await this.getAllowanceAsync( - normalizedTokenAddress, - normalizedFromAddress, - normalizedSenderAddress, - ); - if (fromAddressAllowance.lessThan(amountInBaseUnits)) { - throw new Error(ZeroExError.InsufficientAllowanceForTransfer); - } - - const fromAddressBalance = await this.getBalanceAsync(normalizedTokenAddress, normalizedFromAddress); - if (fromAddressBalance.lessThan(amountInBaseUnits)) { - throw new Error(ZeroExError.InsufficientBalanceForTransfer); - } - - const txHash = await tokenContract.transferFrom.sendTransactionAsync( - normalizedFromAddress, - normalizedToAddress, - amountInBaseUnits, - { - from: normalizedSenderAddress, - 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); - const normalizedTokenAddress = tokenAddress.toLowerCase(); - assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - assert.isFunction('callback', callback); - const subscriptionToken = this._subscribe<ArgsType>( - normalizedTokenAddress, - 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 emitted 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); - const normalizedTokenAddress = tokenAddress.toLowerCase(); - assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); - assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema); - assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - const logs = await this._getLogsAsync<ArgsType>( - normalizedTokenAddress, - eventName, - blockRange, - indexFilterValues, - artifacts.TokenArtifact.abi, - ); - return logs; - } - private _invalidateContractInstances(): void { - this.unsubscribeAll(); - this._tokenContractsByAddress = {}; - } - private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> { - const normalizedTokenAddress = tokenAddress.toLowerCase(); - let tokenContract = this._tokenContractsByAddress[normalizedTokenAddress]; - if (!_.isUndefined(tokenContract)) { - return tokenContract; - } - const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync( - artifacts.TokenArtifact, - normalizedTokenAddress, - ); - const contractInstance = new TokenContract( - abi, - address, - this._web3Wrapper.getProvider(), - this._web3Wrapper.getContractDefaults(), - ); - tokenContract = contractInstance; - this._tokenContractsByAddress[normalizedTokenAddress] = tokenContract; - return tokenContract; - } -} diff --git a/packages/0x.js/src/fetchers/simple_balance_and_proxy_allowance_fetcher.ts b/packages/0x.js/src/fetchers/simple_balance_and_proxy_allowance_fetcher.ts deleted file mode 100644 index 21774d794..000000000 --- a/packages/0x.js/src/fetchers/simple_balance_and_proxy_allowance_fetcher.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { BlockParamLiteral } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; - -import { BalanceAndProxyAllowanceFetcher } from '../abstract/balance_and_proxy_allowance_fetcher'; -import { TokenWrapper } from '../contract_wrappers/token_wrapper'; - -export class SimpleBalanceAndProxyAllowanceFetcher implements BalanceAndProxyAllowanceFetcher { - private _tokenWrapper: TokenWrapper; - private _defaultBlock: BlockParamLiteral; - constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) { - this._tokenWrapper = token; - this._defaultBlock = defaultBlock; - } - public async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> { - const methodOpts = { - defaultBlock: this._defaultBlock, - }; - const balance = this._tokenWrapper.getBalanceAsync(tokenAddress, userAddress, methodOpts); - return balance; - } - public async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> { - const methodOpts = { - defaultBlock: this._defaultBlock, - }; - const proxyAllowance = this._tokenWrapper.getProxyAllowanceAsync(tokenAddress, userAddress, methodOpts); - return proxyAllowance; - } -} diff --git a/packages/0x.js/src/fetchers/simple_order_filled_cancelled_fetcher.ts b/packages/0x.js/src/fetchers/simple_order_filled_cancelled_fetcher.ts deleted file mode 100644 index b7548d54d..000000000 --- a/packages/0x.js/src/fetchers/simple_order_filled_cancelled_fetcher.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { BlockParamLiteral } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; - -import { OrderFilledCancelledFetcher } from '../abstract/order_filled_cancelled_fetcher'; -import { ExchangeWrapper } from '../contract_wrappers/exchange_wrapper'; - -export class SimpleOrderFilledCancelledFetcher implements OrderFilledCancelledFetcher { - private _exchangeWrapper: ExchangeWrapper; - private _defaultBlock: BlockParamLiteral; - constructor(exchange: ExchangeWrapper, defaultBlock: BlockParamLiteral) { - this._exchangeWrapper = exchange; - this._defaultBlock = defaultBlock; - } - public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> { - const methodOpts = { - defaultBlock: this._defaultBlock, - }; - const filledTakerAmount = this._exchangeWrapper.getFilledTakerAmountAsync(orderHash, methodOpts); - return filledTakerAmount; - } - public async getCancelledTakerAmountAsync(orderHash: string): Promise<BigNumber> { - const methodOpts = { - defaultBlock: this._defaultBlock, - }; - const cancelledTakerAmount = this._exchangeWrapper.getCancelledTakerAmountAsync(orderHash, methodOpts); - return cancelledTakerAmount; - } -} diff --git a/packages/0x.js/src/index.ts b/packages/0x.js/src/index.ts index 3b973bd54..e9528d4d1 100644 --- a/packages/0x.js/src/index.ts +++ b/packages/0x.js/src/index.ts @@ -1,61 +1,51 @@ export { ZeroEx } from './0x'; export { - ZeroExError, - EventCallback, - ExchangeContractErrs, - ContractEvent, - Token, - IndexedFilterValues, - BlockRange, - OrderCancellationRequest, - OrderFillRequest, - ContractEventArgs, - ZeroExConfig, - MethodOpts, - OrderTransactionOpts, - TransactionOpts, - LogEvent, - DecodedLogEvent, - EventWatcherCallback, - OnOrderStateChangeCallback, - OrderStateValid, - OrderStateInvalid, - OrderState, -} from './types'; - -export { BlockParamLiteral, FilterObject, BlockParam, ContractEventArg, + ExchangeContractErrs, LogWithDecodedArgs, Order, Provider, SignedOrder, ECSignature, + OrderStateValid, + OrderStateInvalid, + OrderState, + Token, TransactionReceipt, TransactionReceiptWithDecodedLogs, } from '@0xproject/types'; export { + EventCallback, + ContractEvent, + IndexedFilterValues, + BlockRange, + OrderCancellationRequest, + OrderFillRequest, + ContractEventArgs, + MethodOpts, + OrderTransactionOpts, + TransactionOpts, + LogEvent, + DecodedLogEvent, + OnOrderStateChangeCallback, + ContractWrappersError, EtherTokenContractEventArgs, WithdrawalContractEventArgs, DepositContractEventArgs, EtherTokenEvents, -} from './contract_wrappers/generated/ether_token'; - -export { TransferContractEventArgs, ApprovalContractEventArgs, TokenContractEventArgs, TokenEvents, -} from './contract_wrappers/generated/token'; - -export { LogErrorContractEventArgs, LogCancelContractEventArgs, LogFillContractEventArgs, ExchangeContractEventArgs, ExchangeEvents, -} from './contract_wrappers/generated/exchange'; + ZeroExContractConfig, +} from '@0xproject/contract-wrappers'; diff --git a/packages/0x.js/src/order_watcher/event_watcher.ts b/packages/0x.js/src/order_watcher/event_watcher.ts deleted file mode 100644 index de5a99a46..000000000 --- a/packages/0x.js/src/order_watcher/event_watcher.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { BlockParamLiteral, LogEntry } from '@0xproject/types'; -import { intervalUtils } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as _ from 'lodash'; - -import { EventWatcherCallback, ZeroExError } from '../types'; -import { assert } from '../utils/assert'; - -const DEFAULT_EVENT_POLLING_INTERVAL_MS = 200; - -enum LogEventState { - Removed, - Added, -} - -/** - * The EventWatcher watches for blockchain events at the specified block confirmation - * depth. - */ -export class EventWatcher { - private _web3Wrapper: Web3Wrapper; - private _pollingIntervalMs: number; - private _intervalIdIfExists?: NodeJS.Timer; - private _lastEvents: LogEntry[] = []; - private _stateLayer: BlockParamLiteral; - constructor( - web3Wrapper: Web3Wrapper, - pollingIntervalIfExistsMs: undefined | number, - stateLayer: BlockParamLiteral = BlockParamLiteral.Latest, - ) { - this._web3Wrapper = web3Wrapper; - this._stateLayer = stateLayer; - 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<LogEntry[]> { - const eventFilter = { - fromBlock: this._stateLayer, - toBlock: this._stateLayer, - }; - const events = await this._web3Wrapper.getLogsAsync(eventFilter); - return events; - } - private async _emitDifferencesAsync( - logs: 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 deleted file mode 100644 index 27ec7107d..000000000 --- a/packages/0x.js/src/order_watcher/expiration_watcher.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { BigNumber, intervalUtils } from '@0xproject/utils'; -import { RBTree } from 'bintrees'; -import * as _ from 'lodash'; - -import { ZeroExError } from '../types'; -import { utils } from '../utils/utils'; - -const DEFAULT_EXPIRATION_MARGIN_MS = 0; -const DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS = 50; - -/** - * This class includes the functionality to detect expired orders. - * 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 comparator = (lhsOrderHash: string, rhsOrderHash: string) => { - const lhsExpiration = this._expiration[lhsOrderHash].toNumber(); - const rhsExpiration = this._expiration[rhsOrderHash].toNumber(); - if (lhsExpiration !== rhsExpiration) { - return lhsExpiration - rhsExpiration; - } else { - // HACK: If two orders have identical expirations, the order in which they are emitted by the - // ExpirationWatcher does not matter, so we emit them in alphabetical order by orderHash. - return lhsOrderHash.localeCompare(rhsOrderHash); - } - }; - 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 { - if (_.isUndefined(this._expiration[orderHash])) { - return; // noop since order already removed - } - 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 deleted file mode 100644 index a9df8ac9d..000000000 --- a/packages/0x.js/src/order_watcher/order_state_watcher.ts +++ /dev/null @@ -1,381 +0,0 @@ -import { schemas } from '@0xproject/json-schemas'; -import { BlockParamLiteral, LogWithDecodedArgs, SignedOrder } from '@0xproject/types'; -import { AbiDecoder, intervalUtils } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as _ from 'lodash'; - -import { ZeroEx } from '../0x'; -import { ExchangeWrapper } from '../contract_wrappers/exchange_wrapper'; -import { - DepositContractEventArgs, - EtherTokenEvents, - WithdrawalContractEventArgs, -} from '../contract_wrappers/generated/ether_token'; -import { - ExchangeEvents, - LogCancelContractEventArgs, - LogFillContractEventArgs, -} from '../contract_wrappers/generated/exchange'; -import { - ApprovalContractEventArgs, - TokenEvents, - TransferContractEventArgs, -} from '../contract_wrappers/generated/token'; -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 { - ContractEventArgs, - ExchangeContractErrs, - LogEvent, - OnOrderStateChangeCallback, - OrderState, - OrderStateWatcherConfig, - ZeroExError, -} from '../types'; -import { assert } from '../utils/assert'; -import { OrderStateUtils } from '../utils/order_state_utils'; -import { utils } from '../utils/utils'; - -import { EventWatcher } from './event_watcher'; -import { ExpirationWatcher } from './expiration_watcher'; - -interface DependentOrderHashes { - [makerAddress: string]: { - [makerToken: string]: Set<string>; - }; -} - -interface OrderByOrderHash { - [orderHash: string]: SignedOrder; -} - -interface OrderStateByOrderHash { - [orderHash: string]: OrderState; -} - -const DEFAULT_CLEANUP_JOB_INTERVAL_MS = 1000 * 60 * 60; // 1h - -/** - * This class includes all the functionality related to watching a set of orders - * for potential changes in order validity/fillability. The orderWatcher notifies - * the subscriber of these changes so that a final decision can be made on whether - * 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 _expirationWatcher: ExpirationWatcher; - private _orderStateUtils: OrderStateUtils; - private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore; - private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore; - private _cleanupJobInterval: number; - private _cleanupJobIntervalIdIfExists?: NodeJS.Timer; - constructor( - web3Wrapper: Web3Wrapper, - token: TokenWrapper, - exchange: ExchangeWrapper, - config?: OrderStateWatcherConfig, - ) { - this._web3Wrapper = web3Wrapper; - const pollingIntervalIfExistsMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs; - const stateLayer = - _.isUndefined(config) || _.isUndefined(config.stateLayer) ? BlockParamLiteral.Latest : config.stateLayer; - this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs, stateLayer); - this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore(token, stateLayer); - this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange, stateLayer); - 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)._exchangeWrapper as ExchangeWrapper; - const zrxTokenAddress = exchange.getZRXTokenAddress(); - - this._removeFromDependentOrderHashes(signedOrder.maker, zrxTokenAddress, orderHash); - if (zrxTokenAddress !== signedOrder.makerTokenAddress) { - 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._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._web3Wrapper.abiDecoder.tryToDecodeLogOrNoop<ContractEventArgs>(log); - const isLogDecoded = !_.isUndefined(((maybeDecodedLog as any) as LogWithDecodedArgs<ContractEventArgs>).event); - if (!isLogDecoded) { - return; // noop - } - const decodedLog = (maybeDecodedLog as any) 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)._exchangeWrapper 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 deleted file mode 100644 index 184c13aa4..000000000 --- a/packages/0x.js/src/order_watcher/remaining_fillable_calculator.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; - -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; - } -} 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 deleted file mode 100644 index 3ff116db4..000000000 --- a/packages/0x.js/src/stores/balance_proxy_allowance_lazy_store.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { BlockParamLiteral } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import * as _ from 'lodash'; - -import { BalanceAndProxyAllowanceFetcher } from '../abstract/balance_and_proxy_allowance_fetcher'; -import { TokenWrapper } from '../contract_wrappers/token_wrapper'; - -/** - * Copy on read store for balances/proxyAllowances of tokens/accounts - */ -export class BalanceAndProxyAllowanceLazyStore implements BalanceAndProxyAllowanceFetcher { - private _tokenWrapper: TokenWrapper; - private _defaultBlock: BlockParamLiteral; - private _balance: { - [tokenAddress: string]: { - [userAddress: string]: BigNumber; - }; - }; - private _proxyAllowance: { - [tokenAddress: string]: { - [userAddress: string]: BigNumber; - }; - }; - constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) { - this._tokenWrapper = 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._tokenWrapper.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._tokenWrapper.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 deleted file mode 100644 index 61d2db8c2..000000000 --- a/packages/0x.js/src/stores/order_filled_cancelled_lazy_store.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { BlockParamLiteral } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import * as _ from 'lodash'; - -import { OrderFilledCancelledFetcher } from '../abstract/order_filled_cancelled_fetcher'; -import { ExchangeWrapper } from '../contract_wrappers/exchange_wrapper'; - -/** - * Copy on read store for filled/cancelled taker amounts - */ -export class OrderFilledCancelledLazyStore implements OrderFilledCancelledFetcher { - private _exchangeWrapper: ExchangeWrapper; - private _defaultBlock: BlockParamLiteral; - private _filledTakerAmount: { - [orderHash: string]: BigNumber; - }; - private _cancelledTakerAmount: { - [orderHash: string]: BigNumber; - }; - constructor(exchange: ExchangeWrapper, defaultBlock: BlockParamLiteral) { - this._exchangeWrapper = exchange; - this._defaultBlock = defaultBlock; - this._filledTakerAmount = {}; - this._cancelledTakerAmount = {}; - } - public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> { - if (_.isUndefined(this._filledTakerAmount[orderHash])) { - const methodOpts = { - defaultBlock: this._defaultBlock, - }; - const filledTakerAmount = await this._exchangeWrapper.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: this._defaultBlock, - }; - const cancelledTakerAmount = await this._exchangeWrapper.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 ae9f98c5f..516bef8a3 100644 --- a/packages/0x.js/src/types.ts +++ b/packages/0x.js/src/types.ts @@ -1,23 +1,16 @@ -import { BigNumber } from '@0xproject/utils'; - import { BlockParam, BlockParamLiteral, ContractAbi, ContractEventArg, + ExchangeContractErrs, FilterObject, - LogEntryEvent, LogWithDecodedArgs, Order, + OrderState, SignedOrder, } from '@0xproject/types'; -import * as Web3 from 'web3'; - -import { EtherTokenContractEventArgs, EtherTokenEvents } from './contract_wrappers/generated/ether_token'; -import { ExchangeContractEventArgs, ExchangeEvents } from './contract_wrappers/generated/exchange'; -import { TokenContractEventArgs, TokenEvents } from './contract_wrappers/generated/token'; - export enum ZeroExError { ExchangeContractDoesNotExist = 'EXCHANGE_CONTRACT_DOES_NOT_EXIST', ZRXContractDoesNotExist = 'ZRX_CONTRACT_DOES_NOT_EXIST', @@ -45,232 +38,4 @@ export enum InternalZeroExError { WethNotInTokenRegistry = 'WETH_NOT_IN_TOKEN_REGISTRY', } -export type OrderAddresses = [string, string, string, string, string]; - -export type OrderValues = [BigNumber, BigNumber, BigNumber, BigNumber, BigNumber, BigNumber]; - -export type LogEvent = LogEntryEvent; -export interface DecodedLogEvent<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 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 -} - -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', -} - -export interface ContractEvent { - logIndex: number; - transactionIndex: number; - transactionHash: string; - blockHash: string; - blockNumber: number; - address: string; - type: string; - event: string; - args: ContractEventArgs; -} - -export type ContractEventArgs = ExchangeContractEventArgs | TokenContractEventArgs | EtherTokenContractEventArgs; - -// [address, name, symbol, decimals, ipfsHash, swarmHash] -export type TokenMetadata = [string, string, string, number, string, string]; - -export interface Token { - name: string; - address: string; - symbol: string; - decimals: number; -} - -export interface TxOpts { - from: string; - gas?: number; - value?: BigNumber; - gasPrice?: BigNumber; -} - -export interface TokenAddressBySymbol { - [symbol: string]: string; -} - -export type ContractEvents = TokenEvents | ExchangeEvents | EtherTokenEvents; - -export interface IndexedFilterValues { - [index: string]: ContractEventArg; -} - -export interface BlockRange { - fromBlock: BlockParam; - toBlock: BlockParam; -} - -export type DoneCallback = (err?: Error) => void; - -export interface OrderCancellationRequest { - order: Order | SignedOrder; - takerTokenCancelAmount: BigNumber; -} - -export interface OrderFillRequest { - signedOrder: SignedOrder; - takerTokenFillAmount: BigNumber; -} - -export type AsyncMethod = (...args: any[]) => Promise<any>; -export type SyncMethod = (...args: any[]) => any; - -/** - * orderExpirationCheckingIntervalMs: How often to check for expired orders. Default=50. - * eventPollingIntervalMs: How often to poll the Ethereum node for new events. Default=200. - * expirationMarginMs: Amount of time before order expiry that you'd like to be notified - * of an orders expiration. Default=0. - * cleanupJobIntervalMs: How often to run a cleanup job which revalidates all the orders. Default=1hr. - * stateLayer: Optional blockchain state layer OrderWatcher will monitor for new events. Default=latest. - */ -export interface OrderStateWatcherConfig { - orderExpirationCheckingIntervalMs?: number; - eventPollingIntervalMs?: number; - expirationMarginMs?: number; - cleanupJobIntervalMs?: number; - stateLayer: BlockParamLiteral; -} - -/** - * networkId: The id of the underlying ethereum network your provider is connected to. (1-mainnet, 3-ropsten, 4-rinkeby, 42-kovan, 50-testrpc) - * gasPrice: Gas price to use with every transaction - * exchangeContractAddress: The address of an exchange contract to use - * zrxContractAddress: The address of the ZRX contract to use - * tokenRegistryContractAddress: The address of a token registry contract to use - * tokenTransferProxyContractAddress: The address of the token transfer proxy contract to use - * 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; -} - -export type ArtifactContractName = 'ZRX' | 'TokenTransferProxy' | 'TokenRegistry' | 'Token' | 'Exchange' | 'EtherToken'; - -export interface Artifact { - contract_name: ArtifactContractName; - abi: ContractAbi; - networks: { - [networkId: number]: { - address: string; - }; - }; -} - -/** - * expectedFillTakerTokenAmount: If specified, the validation method will ensure that the - * supplied order maker has a sufficient allowance/balance to fill this amount of the order's - * takerTokenAmount. If not specified, the validation method ensures that the maker has a sufficient - * allowance/balance to fill the entire remaining order amount. - */ -export interface ValidateOrderFillableOpts { - expectedFillTakerTokenAmount?: BigNumber; -} - -/** - * defaultBlock: The block up to which to query the blockchain state. Setting this to a historical block number - * let's the user query the blockchain's state at an arbitrary point in time. In order for this to work, the - * backing Ethereum node must keep the entire historical state of the chain (e.g setting `--pruning=archive` - * flag when running Parity). - */ -export interface MethodOpts { - defaultBlock?: BlockParam; -} - -/** - * gasPrice: Gas price in Wei to use for a transaction - * gasLimit: The amount of gas to send with a transaction - */ -export interface TransactionOpts { - gasPrice?: BigNumber; - gasLimit?: number; -} - -/** - * shouldValidate: Flag indicating whether the library should make attempts to validate a transaction before - * broadcasting it. For example, order has a valid signature, maker has sufficient funds, etc. Default=true. - */ -export interface OrderTransactionOpts extends TransactionOpts { - shouldValidate?: boolean; -} - -export enum TradeSide { - Maker = 'maker', - Taker = 'taker', -} - -export enum TransferType { - Trade = 'trade', - Fee = 'fee', -} - -export interface OrderRelevantState { - 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; -} - -export interface OrderStateInvalid { - isValid: false; - orderHash: string; - error: ExchangeContractErrs; -} - -export type OrderState = OrderStateValid | OrderStateInvalid; - -export type OnOrderStateChangeCallback = (err: Error | null, orderState?: OrderState) => void; // tslint:disable:max-file-line-count diff --git a/packages/0x.js/src/utils/assert.ts b/packages/0x.js/src/utils/assert.ts deleted file mode 100644 index 2588a4d09..000000000 --- a/packages/0x.js/src/utils/assert.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { assert as sharedAssert } from '@0xproject/assert'; -// We need those two unused imports because they're actually used by sharedAssert which gets injected here -// tslint:disable-next-line:no-unused-variable -import { Schema } from '@0xproject/json-schemas'; -// tslint:disable-next-line:no-unused-variable -import { ECSignature } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as _ from 'lodash'; - -import { isValidSignature } from '@0xproject/order-utils'; - -export const assert = { - ...sharedAssert, - isValidSignature(orderHash: string, ecSignature: ECSignature, signerAddress: string) { - const isValid = isValidSignature(orderHash, ecSignature, signerAddress); - this.assert(isValid, `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`, - ); - }, -}; diff --git a/packages/0x.js/src/utils/constants.ts b/packages/0x.js/src/utils/constants.ts index 07da6745d..7cd5eb574 100644 --- a/packages/0x.js/src/utils/constants.ts +++ b/packages/0x.js/src/utils/constants.ts @@ -3,9 +3,4 @@ import { BigNumber } from '@0xproject/utils'; export const constants = { NULL_ADDRESS: '0x0000000000000000000000000000000000000000', TESTRPC_NETWORK_ID: 50, - 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 deleted file mode 100644 index f774d734e..000000000 --- a/packages/0x.js/src/utils/decorators.ts +++ /dev/null @@ -1,91 +0,0 @@ -import * as _ from 'lodash'; - -import { AsyncMethod, SyncMethod, ZeroExError } from '../types'; - -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; -}; - -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; -}; - -/** - * 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; - - // 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 asyncErrorHandlingDecorator; -}; - -const syncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => { - 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; - } - }; - - return descriptor; - }; - - return syncErrorHandlingDecorator; -}; - -// _.flow(f, g) = f ∘ g -const zeroExErrorTransformer = _.flow(schemaErrorTransformer, contractCallErrorTransformer); - -export const decorators = { - 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 deleted file mode 100644 index f8301f5c2..000000000 --- a/packages/0x.js/src/utils/exchange_transfer_simulator.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { BlockParamLiteral } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import * as _ from 'lodash'; - -import { TokenWrapper } from '../contract_wrappers/token_wrapper'; -import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allowance_lazy_store'; -import { ExchangeContractErrs, TradeSide, TransferType } from '../types'; -import { constants } from '../utils/constants'; - -enum FailureReason { - 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, - }, - }, -}; - -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> { - // HACK: When simulating an open order (e.g taker is NULL_ADDRESS), we don't want to adjust balances/ - // allowances for the taker. We do however, want to increase the balance of the maker since the maker - // might be relying on those funds to fill subsequent orders or pay the order's fees. - if (from === constants.NULL_ADDRESS && tradeSide === TradeSide.Taker) { - await this._increaseBalanceAsync(tokenAddress, to, amountInBaseUnits); - return; - } - 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 deleted file mode 100644 index c5df7321e..000000000 --- a/packages/0x.js/src/utils/filter_utils.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { - ConstructorAbi, - ContractAbi, - EventAbi, - FallbackAbi, - FilterObject, - LogEntry, - MethodAbi, -} from '@0xproject/types'; -import * as ethUtil from 'ethereumjs-util'; -import * as jsSHA3 from 'js-sha3'; -import * as _ from 'lodash'; -import * as uuid from 'uuid/v4'; - -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: ContractAbi, - blockRange?: BlockRange, - ): FilterObject { - const eventAbi = _.find(abi, { name: eventName }) as 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: FilterObject = { - address, - topics, - }; - if (!_.isUndefined(blockRange)) { - filter = { - ...blockRange, - ...filter, - }; - } - return filter; - }, - getEventSignatureFromAbiByName(eventAbi: EventAbi, eventName: ContractEvents): string { - const types = _.map(eventAbi.inputs, 'type'); - const signature = `${eventAbi.name}(${types.join(',')})`; - return signature; - }, - getTopicsForIndexedArgs(abi: 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: LogEntry, filter: 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 deleted file mode 100644 index b0310d8a8..000000000 --- a/packages/0x.js/src/utils/order_state_utils.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import * as _ from 'lodash'; - -import { ZeroEx } from '../0x'; -import { BalanceAndProxyAllowanceFetcher } from '../abstract/balance_and_proxy_allowance_fetcher'; -import { OrderFilledCancelledFetcher } from '../abstract/order_filled_cancelled_fetcher'; -import { ExchangeWrapper } from '../contract_wrappers/exchange_wrapper'; -import { RemainingFillableCalculator } from '../order_watcher/remaining_fillable_calculator'; -import { ExchangeContractErrs, OrderRelevantState, OrderState, OrderStateInvalid, OrderStateValid } from '../types'; - -const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001; - -export class OrderStateUtils { - private _balanceAndProxyAllowanceFetcher: BalanceAndProxyAllowanceFetcher; - private _orderFilledCancelledFetcher: OrderFilledCancelledFetcher; - 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( - balanceAndProxyAllowanceFetcher: BalanceAndProxyAllowanceFetcher, - orderFilledCancelledFetcher: OrderFilledCancelledFetcher, - ) { - this._balanceAndProxyAllowanceFetcher = balanceAndProxyAllowanceFetcher; - this._orderFilledCancelledFetcher = orderFilledCancelledFetcher; - } - 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._orderFilledCancelledFetcher as any)._exchangeWrapper as ExchangeWrapper; - const zrxTokenAddress = exchange.getZRXTokenAddress(); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - const makerBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync( - signedOrder.makerTokenAddress, - signedOrder.maker, - ); - const makerProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync( - signedOrder.makerTokenAddress, - signedOrder.maker, - ); - const makerFeeBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync( - zrxTokenAddress, - signedOrder.maker, - ); - const makerFeeProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync( - zrxTokenAddress, - signedOrder.maker, - ); - const filledTakerTokenAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash); - const cancelledTakerTokenAmount = await this._orderFilledCancelledFetcher.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; - } -} diff --git a/packages/0x.js/src/utils/order_validation_utils.ts b/packages/0x.js/src/utils/order_validation_utils.ts deleted file mode 100644 index a13c3dc04..000000000 --- a/packages/0x.js/src/utils/order_validation_utils.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { getOrderHashHex, OrderError } from '@0xproject/order-utils'; -import { Order, SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import * as _ from 'lodash'; - -import { ZeroEx } from '../0x'; -import { ExchangeWrapper } from '../contract_wrappers/exchange_wrapper'; -import { ExchangeContractErrs, TradeSide, TransferType, ZeroExError } from '../types'; -import { constants } from '../utils/constants'; -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 = 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; - } - await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync( - exchangeTradeEmulator, - signedOrder, - fillTakerTokenAmount, - signedOrder.taker, - zrxTokenAddress, - ); - } - 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 = getOrderHashHex(signedOrder); - if (!ZeroEx.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker)) { - throw new Error(OrderError.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); - } - } -} diff --git a/packages/0x.js/src/utils/utils.ts b/packages/0x.js/src/utils/utils.ts deleted file mode 100644 index af1125632..000000000 --- a/packages/0x.js/src/utils/utils.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { BigNumber } from '@0xproject/utils'; - -export const utils = { - spawnSwitchErr(name: string, value: any): Error { - return new Error(`Unexpected switch value: ${value} encountered for ${name}`); - }, - 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 6dccdaea7..fe5948e00 100644 --- a/packages/0x.js/test/0x.js_test.ts +++ b/packages/0x.js/test/0x.js_test.ts @@ -41,8 +41,8 @@ describe('ZeroEx library', () => { 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.getProvider(); + // Check that all nested zeroExContract/web3Wrapper instances return the updated provider + const nestedWeb3WrapperProvider = (zeroEx as any)._contractWrappers.getProvider(); expect(nestedWeb3WrapperProvider.zeroExTestId).to.be.a('number'); const exchangeWeb3WrapperProvider = (zeroEx.exchange as any)._web3Wrapper.getProvider(); expect(exchangeWeb3WrapperProvider.zeroExTestId).to.be.a('number'); diff --git a/packages/0x.js/test/assert_test.ts b/packages/0x.js/test/assert_test.ts deleted file mode 100644 index e69de29bb..000000000 --- a/packages/0x.js/test/assert_test.ts +++ /dev/null diff --git a/packages/0x.js/test/ether_token_wrapper_test.ts b/packages/0x.js/test/ether_token_wrapper_test.ts deleted file mode 100644 index 99c42fe0b..000000000 --- a/packages/0x.js/test/ether_token_wrapper_test.ts +++ /dev/null @@ -1,386 +0,0 @@ -import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; -import { BigNumber } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as chai from 'chai'; -import 'mocha'; - -import { - ApprovalContractEventArgs, - BlockParamLiteral, - BlockRange, - DecodedLogEvent, - DepositContractEventArgs, - EtherTokenEvents, - Token, - TransferContractEventArgs, - WithdrawalContractEventArgs, - ZeroEx, - ZeroExError, -} from '../src'; -import { DoneCallback } from '../src/types'; - -import { chaiSetup } from './utils/chai_setup'; -import { constants } from './utils/constants'; -import { reportNodeCallbackErrors } from './utils/report_callback_errors'; -import { TokenUtils } from './utils/token_utils'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -// Since the address depositing/withdrawing ETH/WETH also needs to pay gas costs for the transaction, -// a small amount of ETH will be used to pay this gas cost. We therefore check that the difference between -// the expected balance and actual balance (given the amount of ETH deposited), only deviates by the amount -// required to pay gas costs. -const MAX_REASONABLE_GAS_COST_IN_WEI = 62517; - -describe('EtherTokenWrapper', () => { - 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 () => { - zeroEx = new ZeroEx(provider, zeroExConfig); - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - userAddresses = await zeroEx.getAvailableAddressesAsync(); - addressWithETH = userAddresses[0]; - wethContractAddress = zeroEx.etherToken.getContractAddressIfExists() as string; - depositWeiAmount = Web3Wrapper.toWei(new BigNumber(5)); - decimalPlaces = 7; - addressWithoutFunds = userAddresses[1]; - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('#getContractAddressIfExists', async () => { - it('should return contract address if connected to a known network', () => { - const contractAddressIfExists = zeroEx.etherToken.getContractAddressIfExists(); - expect(contractAddressIfExists).to.not.be.undefined(); - }); - it('should throw if connected to a private network and contract addresses are not specified', () => { - const UNKNOWN_NETWORK_NETWORK_ID = 10; - expect( - () => - new ZeroEx(provider, { - networkId: UNKNOWN_NETWORK_NETWORK_ID, - } as any), - ).to.throw(); - }); - }); - 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 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); - - const extraETHBalance = Web3Wrapper.toWei(new BigNumber(5)); - 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); - - 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 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); - - 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 withdrawal', async () => { - const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH); - expect(preWETHBalance).to.be.bignumber.equal(0); - - 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)(); - zeroEx.setProvider(provider, 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 deleted file mode 100644 index 40ffcc2f6..000000000 --- a/packages/0x.js/test/event_watcher_test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { web3Factory } from '@0xproject/dev-utils'; -import { LogEntry } from '@0xproject/types'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import * as chai from 'chai'; -import * as _ from 'lodash'; -import 'mocha'; -import * as Sinon from 'sinon'; - -import { LogEvent } from '../src'; -import { EventWatcher } from '../src/order_watcher/event_watcher'; -import { DoneCallback } from '../src/types'; - -import { chaiSetup } from './utils/chai_setup'; -import { reportNodeCallbackErrors } from './utils/report_callback_errors'; -import { provider } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; - -describe('EventWatcher', () => { - let stubs: Sinon.SinonStub[] = []; - let eventWatcher: EventWatcher; - let web3Wrapper: Web3Wrapper; - const logA: LogEntry = { - address: '0x71d271f8b14adef568f8f28f1587ce7271ac4ca5', - blockHash: null, - blockNumber: null, - data: '', - logIndex: null, - topics: [], - transactionHash: '0x004881d38cd4a8f72f1a0d68c8b9b8124504706041ff37019c1d1ed6bfda8e17', - transactionIndex: 0, - }; - const logB: LogEntry = { - address: '0x8d12a197cb00d4747a1fe03395095ce2a5cc6819', - blockHash: null, - blockNumber: null, - data: '', - logIndex: null, - topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'], - transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25', - transactionIndex: 0, - }; - const logC: LogEntry = { - address: '0x1d271f8b174adef58f1587ce68f8f27271ac4ca5', - blockHash: null, - blockNumber: null, - data: '', - logIndex: null, - topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'], - transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25', - transactionIndex: 0, - }; - before(async () => { - const pollingIntervalMs = 10; - web3Wrapper = new Web3Wrapper(provider); - 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: 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: LogEntry[] = [logA, logB]; - const changedLogs: 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 deleted file mode 100644 index cb976a0ae..000000000 --- a/packages/0x.js/test/exchange_transfer_simulator_test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils'; -import { BlockParamLiteral } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import * as chai from 'chai'; - -import { ExchangeContractErrs, Token, ZeroEx } from '../src'; -import { TradeSide, TransferType } from '../src/types'; -import { ExchangeTransferSimulator } from '../src/utils/exchange_transfer_simulator'; - -import { chaiSetup } from './utils/chai_setup'; -import { constants } from './utils/constants'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe('ExchangeTransferSimulator', () => { - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - const zeroEx = new ZeroEx(provider, 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 deleted file mode 100644 index 65f4e8251..000000000 --- a/packages/0x.js/test/exchange_wrapper_test.ts +++ /dev/null @@ -1,1195 +0,0 @@ -import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; -import { BlockParamLiteral } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import * as chai from 'chai'; -import * as _ from 'lodash'; -import 'mocha'; - -import { - BlockRange, - DecodedLogEvent, - ExchangeContractErrs, - ExchangeEvents, - LogCancelContractEventArgs, - LogFillContractEventArgs, - OrderCancellationRequest, - OrderFillRequest, - OrderState, - SignedOrder, - Token, - ZeroEx, -} from '../src'; -import { DoneCallback } from '../src/types'; - -import { chaiSetup } from './utils/chai_setup'; -import { constants } from './utils/constants'; -import { FillScenarios } from './utils/fill_scenarios'; -import { reportNodeCallbackErrors } from './utils/report_callback_errors'; -import { TokenUtils } from './utils/token_utils'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -const NON_EXISTENT_ORDER_HASH = '0x79370342234e7acd6bbeac335bd3bb1d368383294b64b8160a00f4060e4d3777'; - -describe('ExchangeWrapper', () => { - 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 () => { - zeroEx = new ZeroEx(provider, 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 and leave the rest of the orders untouched', async () => { - const txHash = await zeroEx.exchange.fillOrdersUpToAsync( - signedOrders, - fillableAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - takerAddress, - ); - await zeroEx.awaitTransactionMinedAsync(txHash); - const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex); - const zeroAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex); - expect(filledAmount).to.be.bignumber.equal(fillableAmount); - expect(zeroAmount).to.be.bignumber.equal(0); - }); - 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); - - zeroEx.setProvider(provider, 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 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); - }); - }); - describe('#getOrderStateAsync', () => { - let maker: string; - let taker: string; - let makerToken: Token; - let takerToken: Token; - let signedOrder: SignedOrder; - let orderState: OrderState; - const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), constants.ZRX_DECIMALS); - before(async () => { - [, maker, taker] = userAddresses; - tokens = await zeroEx.tokenRegistry.getTokensAsync(); - [makerToken, takerToken] = tokenUtils.getDummyTokens(); - }); - it('should report orderStateValid when order is fillable', async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - orderState = await zeroEx.exchange.getOrderStateAsync(signedOrder); - expect(orderState.isValid).to.be.true(); - }); - it('should report orderStateInvalid when maker allowance set to 0', async () => { - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, - fillableAmount, - ); - await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, new BigNumber(0)); - orderState = await zeroEx.exchange.getOrderStateAsync(signedOrder); - expect(orderState.isValid).to.be.false(); - }); - }); -}); // 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 deleted file mode 100644 index 1b022539a..000000000 --- a/packages/0x.js/test/expiration_watcher_test.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils'; -import { BigNumber } from '@0xproject/utils'; -import * as chai from 'chai'; -import * as _ from 'lodash'; -import 'mocha'; -import * as Sinon from 'sinon'; - -import { ZeroEx } from '../src/0x'; -import { ExpirationWatcher } from '../src/order_watcher/expiration_watcher'; -import { DoneCallback, Token } from '../src/types'; -import { utils } from '../src/utils/utils'; - -import { chaiSetup } from './utils/chai_setup'; -import { constants } from './utils/constants'; -import { FillScenarios } from './utils/fill_scenarios'; -import { reportNoErrorCallbackErrors } from './utils/report_callback_errors'; -import { TokenUtils } from './utils/token_utils'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe('ExpirationWatcher', () => { - 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 () => { - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - zeroEx = new ZeroEx(provider, 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); - }); - it('emits events in correct order when expirations are equal', (done: DoneCallback) => { - (async () => { - const order1Lifetime = 60; - const order2Lifetime = 60; - 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(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000)); - expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000)); - const expirationOrder = orderHash1 < orderHash2 ? [orderHash1, orderHash2] : [orderHash2, orderHash1]; - 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 deleted file mode 100644 index 45a292c8b..000000000 --- a/packages/0x.js/test/order_state_watcher_test.ts +++ /dev/null @@ -1,558 +0,0 @@ -import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils'; -import { BigNumber } from '@0xproject/utils'; -import * as chai from 'chai'; -import * as _ from 'lodash'; -import 'mocha'; - -import { - ExchangeContractErrs, - OrderState, - OrderStateInvalid, - OrderStateValid, - SignedOrder, - Token, - ZeroEx, - ZeroExError, -} from '../src'; -import { OrderStateWatcher } from '../src/order_watcher/order_state_watcher'; -import { DoneCallback } from '../src/types'; - -import { chaiSetup } from './utils/chai_setup'; -import { constants } from './utils/constants'; -import { FillScenarios } from './utils/fill_scenarios'; -import { reportNodeCallbackErrors } from './utils/report_callback_errors'; -import { TokenUtils } from './utils/token_utils'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -const TIMEOUT_MS = 150; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe('OrderStateWatcher', () => { - 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; - let orderStateWatcher: OrderStateWatcher; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - const decimals = constants.ZRX_DECIMALS; - const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals); - before(async () => { - zeroEx = new ZeroEx(provider, config); - orderStateWatcher = zeroEx.createOrderStateWatcher(); - 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); - orderStateWatcher.addOrder(signedOrder); - expect((orderStateWatcher as any)._orderByOrderHash).to.include({ - [orderHash]: signedOrder, - }); - let dependentOrderHashes = (orderStateWatcher as any)._dependentOrderHashes; - expect(dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress]).to.have.keys(orderHash); - orderStateWatcher.removeOrder(orderHash); - expect((orderStateWatcher as any)._orderByOrderHash).to.not.include({ - [orderHash]: signedOrder, - }); - dependentOrderHashes = (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('')}`; - orderStateWatcher.removeOrder(nonExistentOrderHash); - }); - }); - describe('#subscribe', async () => { - afterEach(async () => { - orderStateWatcher.unsubscribe(); - }); - it('should fail when trying to subscribe twice', async () => { - orderStateWatcher.subscribe(_.noop); - expect(() => orderStateWatcher.subscribe(_.noop)).to.throw(ZeroExError.SubscriptionAlreadyPresent); - }); - }); - describe('tests with cleanup', async () => { - afterEach(async () => { - orderStateWatcher.unsubscribe(); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - 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); - 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); - }); - 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, - ); - orderStateWatcher.addOrder(signedOrder); - const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { - throw new Error('OrderState callback fired for irrelevant order'); - }); - 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); - 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); - }); - 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); - 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); - }); - 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 makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); - const fillAmountInBaseUnits = new BigNumber(2); - const orderHash = ZeroEx.getOrderHashHex(signedOrder); - 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); - }); - 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)(); - orderStateWatcher.addOrder(signedOrder); - 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); - 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), - ); - }); - 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); - 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, - ); - }); - 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 remainingAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals); - const transferAmount = makerBalance.sub(remainingAmount); - 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, - ); - }); - 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); - 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, - ); - }); - 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 remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals); - const transferTokenAmount = makerFee.sub(remainingTokenAmount); - 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, - ); - }); - 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, - ); - - 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, - ); - }); - 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); - 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); - }); - 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); - 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); - }); - 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); - 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); - }); - 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 deleted file mode 100644 index 0cb95c1b6..000000000 --- a/packages/0x.js/test/order_validation_test.ts +++ /dev/null @@ -1,507 +0,0 @@ -import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils'; -import { OrderError } from '@0xproject/order-utils'; -import { BlockParamLiteral } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import * as chai from 'chai'; -import * as Sinon from 'sinon'; - -import { ExchangeContractErrs, SignedOrder, Token, ZeroEx, ZeroExError } from '../src'; -import { TradeSide, TransferType } from '../src/types'; -import { ExchangeTransferSimulator } from '../src/utils/exchange_transfer_simulator'; -import { OrderValidationUtils } from '../src/utils/order_validation_utils'; - -import { chaiSetup } from './utils/chai_setup'; -import { constants } from './utils/constants'; -import { FillScenarios } from './utils/fill_scenarios'; -import { TokenUtils } from './utils/token_utils'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe('OrderValidation', () => { - 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 () => { - zeroEx = new ZeroEx(provider, 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 maker is buying ZRX and has no ZRX balance', async () => { - const makerFee = new BigNumber(2); - const takerFee = new BigNumber(2); - const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerTokenAddress, - zrxTokenAddress, - makerFee, - takerFee, - makerAddress, - takerAddress, - fillableAmount, - feeRecipient, - ); - const zrxMakerBalance = await zeroEx.token.getBalanceAsync(zrxTokenAddress, makerAddress); - await zeroEx.token.transferAsync(zrxTokenAddress, makerAddress, takerAddress, zrxMakerBalance); - await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder); - }); - it('should succeed if the maker is buying ZRX and has no ZRX balance and there is no specified taker', async () => { - const makerFee = new BigNumber(2); - const takerFee = new BigNumber(2); - const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerTokenAddress, - zrxTokenAddress, - makerFee, - takerFee, - makerAddress, - constants.NULL_ADDRESS, - fillableAmount, - feeRecipient, - ); - const zrxMakerBalance = await zeroEx.token.getBalanceAsync(zrxTokenAddress, makerAddress); - await zeroEx.token.transferAsync(zrxTokenAddress, makerAddress, takerAddress, zrxMakerBalance); - 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(OrderError.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); - }); - }); -}); // tslint:disable-line:max-file-line-count diff --git a/packages/0x.js/test/remaining_fillable_calculator_test.ts b/packages/0x.js/test/remaining_fillable_calculator_test.ts deleted file mode 100644 index d97402ef6..000000000 --- a/packages/0x.js/test/remaining_fillable_calculator_test.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { ECSignature, SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import * as chai from 'chai'; -import 'mocha'; - -import { ZeroEx } from '../src/0x'; -import { RemainingFillableCalculator } from '../src/order_watcher/remaining_fillable_calculator'; - -import { chaiSetup } from './utils/chai_setup'; - -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; - - 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 deleted file mode 100644 index ed4f838c0..000000000 --- a/packages/0x.js/test/subscription_test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils'; -import { BigNumber } from '@0xproject/utils'; -import * as _ from 'lodash'; -import 'mocha'; -import * as Sinon from 'sinon'; - -import { ApprovalContractEventArgs, DecodedLogEvent, Token, TokenEvents, ZeroEx } from '../src'; -import { DoneCallback } from '../src/types'; - -import { chaiSetup } from './utils/chai_setup'; -import { constants } from './utils/constants'; -import { assertNodeCallbackError } from './utils/report_callback_errors'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe('SubscriptionTest', () => { - let zeroEx: ZeroEx; - let userAddresses: string[]; - let tokens: Token[]; - let coinbase: string; - let addressWithoutFunds: string; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - before(async () => { - zeroEx = new ZeroEx(provider, 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 deleted file mode 100644 index 19caa2ed4..000000000 --- a/packages/0x.js/test/token_registry_wrapper_test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils'; -import { schemas, SchemaValidator } from '@0xproject/json-schemas'; -import * as chai from 'chai'; -import * as _ from 'lodash'; -import 'mocha'; - -import { Token, ZeroEx } from '../src'; - -import { chaiSetup } from './utils/chai_setup'; -import { constants } from './utils/constants'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -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 () => { - zeroEx = new ZeroEx(provider, 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(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(); - }); - }); -}); diff --git a/packages/0x.js/test/token_transfer_proxy_wrapper_test.ts b/packages/0x.js/test/token_transfer_proxy_wrapper_test.ts deleted file mode 100644 index 9415d7c08..000000000 --- a/packages/0x.js/test/token_transfer_proxy_wrapper_test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import * as chai from 'chai'; - -import { ZeroEx } from '../src'; - -import { chaiSetup } from './utils/chai_setup'; -import { constants } from './utils/constants'; -import { provider } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; - -describe('TokenTransferProxyWrapper', () => { - let zeroEx: ZeroEx; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - before(async () => { - zeroEx = new ZeroEx(provider, 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 deleted file mode 100644 index 04fd943aa..000000000 --- a/packages/0x.js/test/token_wrapper_test.ts +++ /dev/null @@ -1,528 +0,0 @@ -import { BlockchainLifecycle, devConstants } from '@0xproject/dev-utils'; -import { EmptyWalletSubprovider } from '@0xproject/subproviders'; -import { Provider } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; -import * as chai from 'chai'; -import 'mocha'; -import Web3ProviderEngine = require('web3-provider-engine'); - -import { - ApprovalContractEventArgs, - BlockParamLiteral, - BlockRange, - DecodedLogEvent, - Token, - TokenEvents, - TransferContractEventArgs, - ZeroEx, - ZeroExError, -} from '../src'; -import { DoneCallback } from '../src/types'; - -import { chaiSetup } from './utils/chai_setup'; -import { constants } from './utils/constants'; -import { reportNodeCallbackErrors } from './utils/report_callback_errors'; -import { TokenUtils } from './utils/token_utils'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe('TokenWrapper', () => { - let zeroEx: ZeroEx; - let userAddresses: string[]; - let tokens: Token[]; - let tokenUtils: TokenUtils; - let coinbase: string; - let addressWithoutFunds: string; - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - }; - before(async () => { - zeroEx = new ZeroEx(provider, config); - 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 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 provider without accounts', () => { - let zeroExWithoutAccounts: ZeroEx; - before(async () => { - const hasAddresses = false; - const emptyWalletProvider = addEmptyWalletSubprovider(provider); - zeroExWithoutAccounts = new ZeroEx(emptyWalletProvider, config); - }); - it('should return balance even when called with 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 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 provider without accounts', () => { - let zeroExWithoutAccounts: ZeroEx; - before(async () => { - const hasAddresses = false; - const emptyWalletProvider = addEmptyWalletSubprovider(provider); - zeroExWithoutAccounts = new ZeroEx(emptyWalletProvider, 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)(); - zeroEx.setProvider(provider, 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 - -function addEmptyWalletSubprovider(p: Provider): Provider { - const providerEngine = new Web3ProviderEngine(); - providerEngine.addProvider(new EmptyWalletSubprovider()); - const currentSubproviders = (p as any)._providers; - for (const subprovider of currentSubproviders) { - providerEngine.addProvider(subprovider); - } - providerEngine.start(); - return providerEngine; -} diff --git a/packages/0x.js/test/utils/fill_scenarios.ts b/packages/0x.js/test/utils/fill_scenarios.ts deleted file mode 100644 index 5a82a56d2..000000000 --- a/packages/0x.js/test/utils/fill_scenarios.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { orderFactory } from '@0xproject/order-utils'; -import { BigNumber } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; - -import { SignedOrder, Token, ZeroEx } from '../../src'; -import { artifacts } from '../../src/artifacts'; -import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token'; - -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 defaults = {}; - const dummyToken = new DummyTokenContract( - artifacts.DummyTokenArtifact.abi, - token.address, - web3Wrapper.getProvider(), - web3Wrapper.getContractDefaults(), - ); - 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.getProvider(), - 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/report_callback_errors.ts b/packages/0x.js/test/utils/report_callback_errors.ts deleted file mode 100644 index 27c9745c9..000000000 --- a/packages/0x.js/test/utils/report_callback_errors.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as chai from 'chai'; -import * as _ from 'lodash'; - -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; - }; -}; - -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; - }; -}; - -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; -}; diff --git a/packages/0x.js/test/utils/token_utils.ts b/packages/0x.js/test/utils/token_utils.ts index d3fc22ff4..fe4886ba4 100644 --- a/packages/0x.js/test/utils/token_utils.ts +++ b/packages/0x.js/test/utils/token_utils.ts @@ -1,6 +1,7 @@ +import { Token } from '@0xproject/types'; import * as _ from 'lodash'; -import { InternalZeroExError, Token } from '../../src/types'; +import { InternalZeroExError } from '../../src/types'; const PROTOCOL_TOKEN_SYMBOL = 'ZRX'; const WETH_TOKEN_SYMBOL = 'WETH'; |