aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md9
-rw-r--r--package-lock.json2
-rw-r--r--package.json4
-rw-r--r--src/artifacts/EtherToken.json51
-rw-r--r--src/artifacts/Exchange.json135
-rw-r--r--src/artifacts/TokenRegistry.json171
-rw-r--r--src/contract_wrappers/exchange_wrapper.ts150
-rw-r--r--src/types.ts18
-rw-r--r--src/utils/order_validation_utils.ts42
-rw-r--r--test/artifacts_test.ts17
-rw-r--r--test/exchange_wrapper_test.ts216
-rw-r--r--test/order_validation_test.ts40
-rw-r--r--test/utils/constants.ts3
-rw-r--r--test/utils/fill_scenarios.ts4
14 files changed, 792 insertions, 70 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 61031c089..ac2588278 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# CHANGELOG
+v0.18.1 - _September 28, 2017_
+ * Added Ropsten testnet support (#173)
+
+v0.18.0 - _September 26, 2017_
+ * Added `zeroEx.exchange.validateOrderFillableOrThrowAsync` to simplify orderbook pruning (#170)
+
+v0.17.0 - _September 26, 2017_
+ * Made `zeroEx.exchange.getZRXTokenAddressAsync` public (#171)
+
v0.16.0 - _September 20, 2017_
------------------------
* Added the ability to specify custom contract addresses to be used with 0x.js (#165)
diff --git a/package-lock.json b/package-lock.json
index 6d529d46e..600573a10 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "0x.js",
- "version": "0.16.0",
+ "version": "0.18.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index 41ff5eefe..a87b3d8bb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "0x.js",
- "version": "0.16.0",
+ "version": "0.18.0",
"description": "A javascript library for interacting with the 0x protocol",
"keywords": [
"0x.js",
@@ -64,7 +64,7 @@
"chai-bignumber": "^2.0.1",
"chai-typescript-typings": "^0.0.0",
"copyfiles": "^1.2.0",
- "coveralls": "^2.13.1",
+ "coveralls": "^3.0.0",
"dirty-chai": "^2.0.1",
"ethereumjs-testrpc": "4.0.1",
"json-loader": "^0.5.4",
diff --git a/src/artifacts/EtherToken.json b/src/artifacts/EtherToken.json
index 54b5a032e..91b23bc94 100644
--- a/src/artifacts/EtherToken.json
+++ b/src/artifacts/EtherToken.json
@@ -286,6 +286,57 @@
"updated_at": 1502488087000,
"address": "0x2956356cd2a2bf3202f771f50d3d14a367b48070"
},
+ "3": {
+ "links": {},
+ "events": {
+ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "_from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Transfer",
+ "type": "event"
+ },
+ "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925": {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "_owner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "name": "_spender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Approval",
+ "type": "event"
+ }
+ },
+ "updated_at": 1506602007000,
+ "address": "0xc00fd9820cd2898cc4c054b7bf142de637ad129a"
+ },
"42": {
"links": {},
"events": {
diff --git a/src/artifacts/Exchange.json b/src/artifacts/Exchange.json
index 2445f13ad..734c8f9c7 100644
--- a/src/artifacts/Exchange.json
+++ b/src/artifacts/Exchange.json
@@ -725,6 +725,139 @@
"updated_at": 1502480340000,
"address": "0x12459c951127e0c374ff9105dda097662a027093"
},
+ "3": {
+ "links": {},
+ "events": {
+ "0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3": {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "maker",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "taker",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "name": "feeRecipient",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "makerToken",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "takerToken",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "filledMakerTokenAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "name": "filledTakerTokenAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "name": "paidMakerFee",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "name": "paidTakerFee",
+ "type": "uint256"
+ },
+ {
+ "indexed": true,
+ "name": "tokens",
+ "type": "bytes32"
+ },
+ {
+ "indexed": false,
+ "name": "orderHash",
+ "type": "bytes32"
+ }
+ ],
+ "name": "LogFill",
+ "type": "event"
+ },
+ "0x67d66f160bc93d925d05dae1794c90d2d6d6688b29b84ff069398a9b04587131": {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "maker",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "name": "feeRecipient",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "makerToken",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "takerToken",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "cancelledMakerTokenAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": false,
+ "name": "cancelledTakerTokenAmount",
+ "type": "uint256"
+ },
+ {
+ "indexed": true,
+ "name": "tokens",
+ "type": "bytes32"
+ },
+ {
+ "indexed": false,
+ "name": "orderHash",
+ "type": "bytes32"
+ }
+ ],
+ "name": "LogCancel",
+ "type": "event"
+ },
+ "0x36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e90": {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "errorId",
+ "type": "uint8"
+ },
+ {
+ "indexed": true,
+ "name": "orderHash",
+ "type": "bytes32"
+ }
+ ],
+ "name": "LogError",
+ "type": "event"
+ }
+ },
+ "updated_at": 1506602007000,
+ "address": "0x479cc461fecd078f766ecc58533d6f69580cf3ac"
+ },
"42": {
"links": {},
"events": {
@@ -994,4 +1127,4 @@
},
"schema_version": "0.0.5",
"updated_at": 1503318938231
-} \ No newline at end of file
+}
diff --git a/src/artifacts/TokenRegistry.json b/src/artifacts/TokenRegistry.json
index e244a496a..5a0564a69 100644
--- a/src/artifacts/TokenRegistry.json
+++ b/src/artifacts/TokenRegistry.json
@@ -698,6 +698,175 @@
"updated_at": 1502488442000,
"address": "0x926a74c5c36adf004c87399e65f75628b0f98d2c"
},
+ "3": {
+ "links": {},
+ "events": {
+ "0xd8d928b0b50ca11d9dc273236b46f3526515b03602f71f3a6af4f45bd9fa9144": {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "name",
+ "type": "string"
+ },
+ {
+ "indexed": false,
+ "name": "symbol",
+ "type": "string"
+ },
+ {
+ "indexed": false,
+ "name": "decimals",
+ "type": "uint8"
+ },
+ {
+ "indexed": false,
+ "name": "ipfsHash",
+ "type": "bytes"
+ },
+ {
+ "indexed": false,
+ "name": "swarmHash",
+ "type": "bytes"
+ }
+ ],
+ "name": "LogAddToken",
+ "type": "event"
+ },
+ "0x32c54f1e2ea75844ded7517e7dbcd3895da7cd0c28f9ab9f9cf6ecf5f83762c6": {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "name",
+ "type": "string"
+ },
+ {
+ "indexed": false,
+ "name": "symbol",
+ "type": "string"
+ },
+ {
+ "indexed": false,
+ "name": "decimals",
+ "type": "uint8"
+ },
+ {
+ "indexed": false,
+ "name": "ipfsHash",
+ "type": "bytes"
+ },
+ {
+ "indexed": false,
+ "name": "swarmHash",
+ "type": "bytes"
+ }
+ ],
+ "name": "LogRemoveToken",
+ "type": "event"
+ },
+ "0x4a6dbfc867b179991dec22ff19960f0a94d8d9d891fc556f547764670340e8ae": {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "oldName",
+ "type": "string"
+ },
+ {
+ "indexed": false,
+ "name": "newName",
+ "type": "string"
+ }
+ ],
+ "name": "LogTokenNameChange",
+ "type": "event"
+ },
+ "0x53d878a6530e56c9bc96548fa0a8cae4f1d1f49c86b0e934c086b992ebb6998f": {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "oldSymbol",
+ "type": "string"
+ },
+ {
+ "indexed": false,
+ "name": "newSymbol",
+ "type": "string"
+ }
+ ],
+ "name": "LogTokenSymbolChange",
+ "type": "event"
+ },
+ "0x5b19f79ac4e8cfa820815502e11615f1a449e28155dc289ec5cac1a11f908694": {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "oldIpfsHash",
+ "type": "bytes"
+ },
+ {
+ "indexed": false,
+ "name": "newIpfsHash",
+ "type": "bytes"
+ }
+ ],
+ "name": "LogTokenIpfsHashChange",
+ "type": "event"
+ },
+ "0xc3168fdc13112e44a031057dbf6c609b33353addb4d8037d24543e22cbfe2acd": {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "token",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "oldSwarmHash",
+ "type": "bytes"
+ },
+ {
+ "indexed": false,
+ "name": "newSwarmHash",
+ "type": "bytes"
+ }
+ ],
+ "name": "LogTokenSwarmHashChange",
+ "type": "event"
+ }
+ },
+ "updated_at": 1506602007000,
+ "address": "0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed"
+ },
"42": {
"links": {},
"events": {
@@ -1039,4 +1208,4 @@
},
"schema_version": "0.0.5",
"updated_at": 1503318938228
-} \ No newline at end of file
+}
diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts
index 54d7f62d5..d02a6e642 100644
--- a/src/contract_wrappers/exchange_wrapper.ts
+++ b/src/contract_wrappers/exchange_wrapper.ts
@@ -28,6 +28,8 @@ import {
LogCancelContractEventArgs,
LogWithDecodedArgs,
MethodOpts,
+ ValidateOrderFillableOpts,
+ OrderTransactionOpts,
} from '../types';
import {assert} from '../utils/assert';
import {utils} from '../utils/utils';
@@ -39,6 +41,8 @@ import {TokenWrapper} from './token_wrapper';
import {decorators} from '../utils/decorators';
import {artifacts} from '../artifacts';
+const SHOULD_VALIDATE_BY_DEFAULT = true;
+
/**
* This class includes all the functionality related to calling methods and subscribing to
* events of the 0x Exchange smart contract.
@@ -153,19 +157,26 @@ export class ExchangeWrapper extends ContractWrapper {
* @param takerAddress The user Ethereum address who would like to fill this order.
* Must be available via the supplied Web3.Provider
* passed to 0x.js.
- * @return Transaction hash.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
- takerAddress: string): Promise<string> {
+ takerAddress: string,
+ orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
const exchangeInstance = await this._getExchangeContractAsync();
- await this.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress);
+ const shouldValidate = _.isUndefined(orderTransactionOpts) ?
+ SHOULD_VALIDATE_BY_DEFAULT :
+ orderTransactionOpts.shouldValidate;
+ if (shouldValidate) {
+ await this.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress);
+ }
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder);
@@ -210,12 +221,14 @@ export class ExchangeWrapper extends ContractWrapper {
* @param takerAddress The user Ethereum address who would like to fill these
* orders. Must be available via the supplied Web3.Provider
* passed to 0x.js.
- * @return Transaction hash.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async fillOrdersUpToAsync(signedOrders: SignedOrder[], fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
- takerAddress: string): Promise<string> {
+ takerAddress: string,
+ orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
const takerTokenAddresses = _.map(signedOrders, signedOrder => signedOrder.takerTokenAddress);
assert.hasAtMostOneUniqueValue(takerTokenAddresses,
@@ -226,10 +239,15 @@ export class ExchangeWrapper extends ContractWrapper {
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
- for (const signedOrder of signedOrders) {
- await this.validateFillOrderThrowIfInvalidAsync(
- signedOrder, fillTakerTokenAmount, takerAddress);
+
+ const shouldValidate = _.isUndefined(orderTransactionOpts) ?
+ SHOULD_VALIDATE_BY_DEFAULT :
+ orderTransactionOpts.shouldValidate;
+ if (shouldValidate) {
+ await Promise.all(signedOrders.map(signedOrder => this.validateFillOrderThrowIfInvalidAsync(
+ signedOrder, fillTakerTokenAmount, takerAddress)));
}
+
if (_.isEmpty(signedOrders)) {
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
}
@@ -291,12 +309,14 @@ export class ExchangeWrapper extends ContractWrapper {
* @param takerAddress The user Ethereum address who would like to fill
* these orders. Must be available via the supplied
* Web3.Provider passed to 0x.js.
- * @return Transaction hash.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async batchFillOrdersAsync(orderFillRequests: OrderFillRequest[],
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
- takerAddress: string): Promise<string> {
+ takerAddress: string,
+ orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema);
const exchangeContractAddresses = _.map(
orderFillRequests,
@@ -306,9 +326,13 @@ export class ExchangeWrapper extends ContractWrapper {
ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress);
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
- for (const orderFillRequest of orderFillRequests) {
- await this.validateFillOrderThrowIfInvalidAsync(
- orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount, takerAddress);
+ const shouldValidate = _.isUndefined(orderTransactionOpts) ?
+ SHOULD_VALIDATE_BY_DEFAULT :
+ orderTransactionOpts.shouldValidate;
+ if (shouldValidate) {
+ await Promise.all(orderFillRequests.map(orderFillRequest => this.validateFillOrderThrowIfInvalidAsync(
+ orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount, takerAddress)),
+ );
}
if (_.isEmpty(orderFillRequests)) {
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
@@ -364,18 +388,25 @@ export class ExchangeWrapper extends ContractWrapper {
* @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill.
* @param takerAddress The user Ethereum address who would like to fill this order.
* Must be available via the supplied Web3.Provider passed to 0x.js.
- * @return Transaction hash.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async fillOrKillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber.BigNumber,
- takerAddress: string): Promise<string> {
+ takerAddress: string,
+ orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
const exchangeInstance = await this._getExchangeContractAsync();
- await this.validateFillOrKillOrderThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress);
+ const shouldValidate = _.isUndefined(orderTransactionOpts) ?
+ SHOULD_VALIDATE_BY_DEFAULT :
+ orderTransactionOpts.shouldValidate;
+ if (shouldValidate) {
+ await this.validateFillOrKillOrderThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress);
+ }
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder);
@@ -410,11 +441,13 @@ export class ExchangeWrapper extends ContractWrapper {
* @param orderFillOrKillRequests An array of objects that conform to the OrderFillOrKillRequest interface.
* @param takerAddress The user Ethereum address who would like to fill there orders.
* Must be available via the supplied Web3.Provider passed to 0x.js.
- * @return Transaction hash.
+ * @param orderTransactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async batchFillOrKillAsync(orderFillOrKillRequests: OrderFillOrKillRequest[],
- takerAddress: string): Promise<string> {
+ takerAddress: string,
+ orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
assert.doesConformToSchema('orderFillOrKillRequests', orderFillOrKillRequests,
schemas.orderFillOrKillRequestsSchema);
const exchangeContractAddresses = _.map(
@@ -428,9 +461,14 @@ export class ExchangeWrapper extends ContractWrapper {
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
}
const exchangeInstance = await this._getExchangeContractAsync();
- for (const request of orderFillOrKillRequests) {
- await this.validateFillOrKillOrderThrowIfInvalidAsync(
- request.signedOrder, request.fillTakerAmount, takerAddress);
+
+ const shouldValidate = _.isUndefined(orderTransactionOpts) ?
+ SHOULD_VALIDATE_BY_DEFAULT :
+ orderTransactionOpts.shouldValidate;
+ if (shouldValidate) {
+ await Promise.all(orderFillOrKillRequests.map(request => this.validateFillOrKillOrderThrowIfInvalidAsync(
+ request.signedOrder, request.fillTakerAmount, takerAddress)),
+ );
}
const orderAddressesValuesAndTakerTokenFillAmounts = _.map(orderFillOrKillRequests, request => {
@@ -477,17 +515,25 @@ export class ExchangeWrapper extends ContractWrapper {
* @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.
- * @return Transaction hash.
+ * @param transactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
*/
@decorators.contractCallErrorHandler
- public async cancelOrderAsync(
- order: Order|SignedOrder, cancelTakerTokenAmount: BigNumber.BigNumber): Promise<string> {
+ public async cancelOrderAsync(order: Order|SignedOrder,
+ cancelTakerTokenAmount: BigNumber.BigNumber,
+ orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
assert.doesConformToSchema('order', order, schemas.orderSchema);
assert.isBigNumber('takerTokenCancelAmount', cancelTakerTokenAmount);
await assert.isSenderAddressAsync('order.maker', order.maker, this._web3Wrapper);
const exchangeInstance = await this._getExchangeContractAsync();
- await this.validateCancelOrderThrowIfInvalidAsync(order, cancelTakerTokenAmount);
+
+ const shouldValidate = _.isUndefined(orderTransactionOpts) ?
+ SHOULD_VALIDATE_BY_DEFAULT :
+ orderTransactionOpts.shouldValidate;
+ if (shouldValidate) {
+ await this.validateCancelOrderThrowIfInvalidAsync(order, cancelTakerTokenAmount);
+ }
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order);
const gas = await exchangeInstance.cancelOrder.estimateGasAsync(
@@ -514,10 +560,12 @@ export class ExchangeWrapper extends ContractWrapper {
* All orders must be from the same maker.
* @param orderCancellationRequests An array of objects that conform to the OrderCancellationRequest
* interface.
- * @return Transaction hash.
+ * @param transactionOpts Optional arguments this method accepts.
+ * @return Transaction hash.
*/
@decorators.contractCallErrorHandler
- public async batchCancelOrdersAsync(orderCancellationRequests: OrderCancellationRequest[]): Promise<string> {
+ public async batchCancelOrdersAsync(orderCancellationRequests: OrderCancellationRequest[],
+ orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
assert.doesConformToSchema('orderCancellationRequests', orderCancellationRequests,
schemas.orderCancellationRequestsSchema);
const exchangeContractAddresses = _.map(
@@ -530,10 +578,13 @@ export class ExchangeWrapper extends ContractWrapper {
assert.hasAtMostOneUniqueValue(makers, ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed);
const maker = makers[0];
await assert.isSenderAddressAsync('maker', maker, this._web3Wrapper);
- for (const cancellationRequest of orderCancellationRequests) {
- await this.validateCancelOrderThrowIfInvalidAsync(
- cancellationRequest.order, cancellationRequest.takerTokenCancelAmount,
- );
+ const shouldValidate = _.isUndefined(orderTransactionOpts) ?
+ SHOULD_VALIDATE_BY_DEFAULT :
+ orderTransactionOpts.shouldValidate;
+ if (shouldValidate) {
+ await Promise.all(orderCancellationRequests.map(cancellationRequest =>
+ this.validateCancelOrderThrowIfInvalidAsync(
+ cancellationRequest.order, cancellationRequest.takerTokenCancelAmount)));
}
if (_.isEmpty(orderCancellationRequests)) {
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
@@ -624,6 +675,25 @@ export class ExchangeWrapper extends ContractWrapper {
return exchangeAddress;
}
/**
+ * 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 = await this.getZRXTokenAddressAsync();
+ const expectedFillTakerTokenAmount = !_.isUndefined(opts) ? opts.expectedFillTakerTokenAmount : undefined;
+ await this._orderValidationUtils.validateOrderFillableOrThrowAsync(
+ 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.
@@ -637,7 +707,7 @@ export class ExchangeWrapper extends ContractWrapper {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
- const zrxTokenAddress = await this._getZRXTokenAddressAsync();
+ const zrxTokenAddress = await this.getZRXTokenAddressAsync();
await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
}
@@ -670,7 +740,7 @@ export class ExchangeWrapper extends ContractWrapper {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
- const zrxTokenAddress = await this._getZRXTokenAddressAsync();
+ const zrxTokenAddress = await this.getZRXTokenAddressAsync();
await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync(
signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
}
@@ -708,6 +778,15 @@ export class ExchangeWrapper extends ContractWrapper {
throw new Error(errMessage);
}
}
+ /**
+ * Returns the ZRX token address used by the exchange contract.
+ * @return Address of ZRX token
+ */
+ public async getZRXTokenAddressAsync(): Promise<string> {
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const ZRXtokenAddress = await exchangeInstance.ZRX_TOKEN_CONTRACT.callAsync();
+ return ZRXtokenAddress;
+ }
private async _invalidateContractInstancesAsync(): Promise<void> {
await this.stopWatchingAllEventsAsync();
delete this._exchangeContractIfExists;
@@ -745,11 +824,6 @@ export class ExchangeWrapper extends ContractWrapper {
this._exchangeContractIfExists = contractInstance as ExchangeContract;
return this._exchangeContractIfExists;
}
- private async _getZRXTokenAddressAsync(): Promise<string> {
- const exchangeInstance = await this._getExchangeContractAsync();
- const ZRXtokenAddress = await exchangeInstance.ZRX_TOKEN_CONTRACT.callAsync();
- return ZRXtokenAddress;
- }
private async _getTokenTransferProxyAddressAsync(): Promise<string> {
const exchangeInstance = await this._getExchangeContractAsync();
const tokenTransferProxyAddress = await exchangeInstance.TOKEN_TRANSFER_PROXY_CONTRACT.callAsync();
diff --git a/src/types.ts b/src/types.ts
index 29fb40e73..02230b0ab 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -434,6 +434,16 @@ export interface Artifact {
}
/*
+ * 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.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`
@@ -442,3 +452,11 @@ export interface Artifact {
export interface MethodOpts {
defaultBlock?: Web3.BlockParam;
}
+
+/*
+ * 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.
+ */
+export interface OrderTransactionOpts {
+ shouldValidate: boolean;
+}
diff --git a/src/utils/order_validation_utils.ts b/src/utils/order_validation_utils.ts
index 815cd0115..6f7522c41 100644
--- a/src/utils/order_validation_utils.ts
+++ b/src/utils/order_validation_utils.ts
@@ -1,3 +1,4 @@
+import * as _ from 'lodash';
import {ExchangeContractErrs, SignedOrder, Order, ZeroExError} from '../types';
import {ZeroEx} from '../0x';
import {TokenWrapper} from '../contract_wrappers/token_wrapper';
@@ -12,6 +13,23 @@ export class OrderValidationUtils {
this.tokenWrapper = tokenWrapper;
this.exchangeWrapper = exchangeWrapper;
}
+ public async validateOrderFillableOrThrowAsync(
+ signedOrder: SignedOrder, zrxTokenAddress: string, expectedFillTakerTokenAmount?: BigNumber.BigNumber,
+ ): Promise<void> {
+ const orderHash = utils.getOrderHashHex(signedOrder);
+ const unavailableTakerTokenAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
+ this.validateRemainingFillAmountNotZeroOrThrow(
+ signedOrder.takerTokenAmount, unavailableTakerTokenAmount,
+ );
+ this.validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
+ let fillTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
+ if (!_.isUndefined(expectedFillTakerTokenAmount)) {
+ fillTakerTokenAmount = expectedFillTakerTokenAmount;
+ }
+ await this.validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
+ signedOrder, fillTakerTokenAmount, zrxTokenAddress,
+ );
+ }
public async validateFillOrderThrowIfInvalidAsync(signedOrder: SignedOrder,
fillTakerTokenAmount: BigNumber.BigNumber,
takerAddress: string,
@@ -24,16 +42,13 @@ export class OrderValidationUtils {
throw new Error(ZeroExError.InvalidSignature);
}
const unavailableTakerTokenAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
- if (signedOrder.makerTokenAmount.eq(unavailableTakerTokenAmount)) {
- throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
- }
+ this.validateRemainingFillAmountNotZeroOrThrow(
+ signedOrder.takerTokenAmount, unavailableTakerTokenAmount,
+ );
if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== takerAddress) {
throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
}
- const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
- if (signedOrder.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
- throw new Error(ExchangeContractErrs.OrderFillExpired);
- }
+ this.validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
await this.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress,
);
@@ -147,4 +162,17 @@ export class OrderValidationUtils {
}
}
}
+ private validateRemainingFillAmountNotZeroOrThrow(
+ takerTokenAmount: BigNumber.BigNumber, unavailableTakerTokenAmount: BigNumber.BigNumber,
+ ) {
+ if (takerTokenAmount.eq(unavailableTakerTokenAmount)) {
+ throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
+ }
+ }
+ private validateOrderNotExpiredOrThrow(expirationUnixTimestampSec: BigNumber.BigNumber) {
+ const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
+ if (expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
+ throw new Error(ExchangeContractErrs.OrderFillExpired);
+ }
+ }
}
diff --git a/test/artifacts_test.ts b/test/artifacts_test.ts
index bd34a6b91..b2866a1d6 100644
--- a/test/artifacts_test.ts
+++ b/test/artifacts_test.ts
@@ -29,4 +29,21 @@ describe('Artifacts', () => {
await zeroEx.exchange.getContractAddressAsync();
}).timeout(TIMEOUT);
});
+ describe('contracts are deployed on ropsten', () => {
+ const ropstenRpcUrl = constants.ROPSTEN_RPC_URL;
+ const packageJSONContent = fs.readFileSync('package.json', 'utf-8');
+ const packageJSON = JSON.parse(packageJSONContent);
+ const mnemonic = packageJSON.config.mnemonic;
+ const web3Provider = new HDWalletProvider(mnemonic, ropstenRpcUrl);
+ const zeroEx = new ZeroEx(web3Provider);
+ it('token registry contract is deployed', async () => {
+ await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync();
+ }).timeout(TIMEOUT);
+ it('proxy contract is deployed', async () => {
+ await (zeroEx.token as any)._getTokenTransferProxyAddressAsync();
+ }).timeout(TIMEOUT);
+ it('exchange contract is deployed', async () => {
+ await zeroEx.exchange.getContractAddressAsync();
+ }).timeout(TIMEOUT);
+ });
});
diff --git a/test/exchange_wrapper_test.ts b/test/exchange_wrapper_test.ts
index f72e7f64f..45a2d3907 100644
--- a/test/exchange_wrapper_test.ts
+++ b/test/exchange_wrapper_test.ts
@@ -18,6 +18,7 @@ import {
OrderCancellationRequest,
OrderFillRequest,
LogFillContractEventArgs,
+ OrderFillOrKillRequest,
} from '../src';
import {DoneCallback} from '../src/types';
import {FillScenarios} from './utils/fill_scenarios';
@@ -93,14 +94,47 @@ describe('ExchangeWrapper', () => {
];
await zeroEx.exchange.batchFillOrKillAsync(orderFillOrKillRequests, takerAddress);
});
+ describe('order transaction options', () => {
+ let signedOrder: SignedOrder;
+ let orderFillOrKillRequests: OrderFillOrKillRequest[];
+ const fillableAmount = new BigNumber(5);
+ beforeEach(async () => {
+ signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ );
+ orderFillOrKillRequests = [
+ {
+ signedOrder,
+ fillTakerAmount: new BigNumber(0),
+ },
+ ];
+ });
+ it('should validate when orderTransactionOptions are not present', async () => {
+ return expect(zeroEx.exchange.batchFillOrKillAsync(orderFillOrKillRequests, takerAddress))
+ .to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
+ });
+ it('should validate when orderTransactionOptions specify to validate', async () => {
+ return expect(zeroEx.exchange.batchFillOrKillAsync(orderFillOrKillRequests, takerAddress, {
+ shouldValidate: true,
+ })).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
+ });
+ it('should not validate when orderTransactionOptions specify not to validate', async () => {
+ return expect(zeroEx.exchange.batchFillOrKillAsync(orderFillOrKillRequests, 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 () => {
- const fillableAmount = new BigNumber(5);
- 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))
@@ -120,10 +154,6 @@ describe('ExchangeWrapper', () => {
.to.be.bignumber.equal(fillableAmount.minus(fillTakerAmount));
});
it('should partially fill a valid order', async () => {
- const fillableAmount = new BigNumber(5);
- const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
- makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
- );
const partialFillAmount = new BigNumber(3);
await zeroEx.exchange.fillOrKillOrderAsync(signedOrder, partialFillAmount, takerAddress);
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
@@ -136,6 +166,23 @@ describe('ExchangeWrapper', () => {
.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)', () => {
@@ -212,6 +259,32 @@ describe('ExchangeWrapper', () => {
.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('#batchFillOrdersAsync', () => {
let signedOrder: SignedOrder;
@@ -228,18 +301,20 @@ describe('ExchangeWrapper', () => {
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder);
- orderFillBatch = [
- {
- signedOrder,
- takerTokenFillAmount: fillTakerAmount,
- },
- {
- signedOrder: anotherSignedOrder,
- takerTokenFillAmount: fillTakerAmount,
- },
- ];
});
describe('successful batch fills', () => {
+ beforeEach(() => {
+ orderFillBatch = [
+ {
+ signedOrder,
+ takerTokenFillAmount: fillTakerAmount,
+ },
+ {
+ signedOrder: anotherSignedOrder,
+ takerTokenFillAmount: fillTakerAmount,
+ },
+ ];
+ });
it('should throw if a batch is empty', async () => {
return expect(zeroEx.exchange.batchFillOrdersAsync(
[], shouldThrowOnInsufficientBalanceOrAllowance, takerAddress),
@@ -255,6 +330,38 @@ describe('ExchangeWrapper', () => {
expect(anotherFilledAmount).to.be.bignumber.equal(fillTakerAmount);
});
});
+ 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('#fillOrdersUpTo', () => {
let signedOrder: SignedOrder;
@@ -292,6 +399,26 @@ describe('ExchangeWrapper', () => {
expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount);
});
});
+ 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)', () => {
@@ -323,6 +450,23 @@ describe('ExchangeWrapper', () => {
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;
@@ -369,6 +513,35 @@ describe('ExchangeWrapper', () => {
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', () => {
@@ -591,4 +764,11 @@ describe('ExchangeWrapper', () => {
expect(orderHash).to.equal(orderHashFromContract);
});
});
+ describe('#getZRXTokenAddressAsync', () => {
+ it('gets the same token as is in token registry', async () => {
+ const zrxAddress = await zeroEx.exchange.getZRXTokenAddressAsync();
+ const zrxToken = tokenUtils.getProtocolTokenOrThrow();
+ expect(zrxAddress).to.equal(zrxToken.address);
+ });
+ });
});
diff --git a/test/order_validation_test.ts b/test/order_validation_test.ts
index 9a621555c..f625433eb 100644
--- a/test/order_validation_test.ts
+++ b/test/order_validation_test.ts
@@ -54,6 +54,46 @@ describe('OrderValidation', () => {
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
+ describe('validateOrderFillableOrThrowAsync', () => {
+ it('should succeed if the order is fillable', async () => {
+ const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ );
+ await zeroEx.exchange.validateOrderFillableOrThrowAsync(
+ signedOrder,
+ );
+ });
+ it('should succeed if the order is asymmetric and fillable', async () => {
+ const makerFillableAmount = fillableAmount;
+ const takerFillableAmount = fillableAmount.minus(4);
+ const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
+ makerFillableAmount, takerFillableAmount,
+ );
+ await zeroEx.exchange.validateOrderFillableOrThrowAsync(
+ signedOrder,
+ );
+ });
+ it('should throw when the order is fully filled or cancelled', async () => {
+ const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ );
+ await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
+ return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(
+ signedOrder,
+ )).to.be.rejectedWith(ExchangeContractErrs.OrderRemainingFillAmountZero);
+ });
+ it('should throw when order is expired', async () => {
+ const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
+ const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
+ fillableAmount, expirationInPast,
+ );
+ return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(
+ signedOrder,
+ )).to.be.rejectedWith(ExchangeContractErrs.OrderFillExpired);
+ });
+ });
describe('validateFillOrderAndThrowIfInvalidAsync', () => {
it('should throw when the fill amount is zero', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
diff --git a/test/utils/constants.ts b/test/utils/constants.ts
index b677d7361..c7d3aebca 100644
--- a/test/utils/constants.ts
+++ b/test/utils/constants.ts
@@ -3,5 +3,6 @@ export const constants = {
RPC_HOST: 'localhost',
RPC_PORT: 8545,
TESTRPC_NETWORK_ID: 50,
- KOVAN_RPC_URL: 'https://kovan.0xproject.com',
+ KOVAN_RPC_URL: 'https://kovan.infura.io',
+ ROPSTEN_RPC_URL: 'https://ropsten.infura.io',
};
diff --git a/test/utils/fill_scenarios.ts b/test/utils/fill_scenarios.ts
index 563415a48..e305759f6 100644
--- a/test/utils/fill_scenarios.ts
+++ b/test/utils/fill_scenarios.ts
@@ -62,7 +62,9 @@ export class FillScenarios {
fillableAmount, fillableAmount,
);
const shouldThrowOnInsufficientBalanceOrAllowance = false;
- await this.zeroEx.exchange.fillOrderAsync(signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
+ await this.zeroEx.exchange.fillOrderAsync(
+ signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
+ );
return signedOrder;
}
private async createAsymmetricFillableSignedOrderWithFeesAsync(