Merge branch 'development' into addUnenforcedConventions

* development: (92 commits)
  Add missing CHANGELOG entry for OrderWatcher WS interface
  Bump up stale to close to 30 days
  Move onMessageAsync outside of tests and add comments
  Fix WS tests to remove race-condition and be more specific about the message expected
  Add temporary console.log to test failing on CI
  Make @0x/contracts-test-utils a dependency instead of a devDependency
  Fix test-publish failure in contracts packages
  Fixed solhint errors
  Added documentation to `LibAddressArray.append` and switched `if` to `require` smt
  Updated changelogs for new contracts
  Added `gas` field so tests pass on Geth;
  Added Changelog for new Extensions
  Updated comment `Execute fillOrder` -> `Execute exchange function`
  Explicit returns
  Prettier / Linter on contracts + TS
  Refactoring balance threshold filter
  Moved exchange calldata functions to separate mixin
  Less Assembly. More Solidity. Less Efficiency. More Readability.
  Run all tests for extensions
  Cleaned up tests for balance threshold filter
  ...
This commit is contained in:
Fabio Berger
2018-12-19 15:17:46 +00:00
36 changed files with 3431 additions and 15 deletions

2
.github/stale.yml vendored
View File

@@ -1,7 +1,7 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 30
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
daysUntilClose: 30
# Issues with these labels will never be considered stale
exemptLabels:
- pinned

View File

@@ -1,4 +1,13 @@
[
{
"version": "1.1.0",
"changes": [
{
"note": "Added Balance Threshold Filter",
"pr": 1383
}
]
},
{
"timestamp": 1544741676,
"version": "1.0.2",

View File

@@ -18,5 +18,5 @@
}
}
},
"contracts": ["DutchAuction", "Forwarder"]
"contracts": ["BalanceThresholdFilter", "DutchAuction", "Forwarder"]
}

View File

@@ -0,0 +1,45 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.24;
import "@0x/contracts-interfaces/contracts/protocol/Exchange/IExchange.sol";
import "./interfaces/IThresholdAsset.sol";
import "./MixinBalanceThresholdFilterCore.sol";
contract BalanceThresholdFilter is
MixinBalanceThresholdFilterCore
{
/// @dev Constructs BalanceThresholdFilter.
/// @param exchange Address of 0x exchange.
/// @param thresholdAsset The asset that must be held by makers/takers.
/// @param balanceThreshold The minimum balance of `thresholdAsset` that must be held by makers/takers.
constructor(
address exchange,
address thresholdAsset,
uint256 balanceThreshold
)
public
{
EXCHANGE = IExchange(exchange);
THRESHOLD_ASSET = IThresholdAsset(thresholdAsset);
BALANCE_THRESHOLD = balanceThreshold;
}
}

View File

@@ -0,0 +1,135 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.24;
import "@0x/contracts-libs/contracts/libs/LibExchangeSelectors.sol";
import "@0x/contracts-libs/contracts/libs/LibOrder.sol";
import "./mixins/MBalanceThresholdFilterCore.sol";
import "./MixinExchangeCalldata.sol";
contract MixinBalanceThresholdFilterCore is
MBalanceThresholdFilterCore,
MixinExchangeCalldata,
LibOrder,
LibExchangeSelectors
{
/// @dev Executes an Exchange transaction iff the maker and taker meet
/// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR
/// the exchange function is a cancellation.
/// Supported Exchange functions:
/// batchFillOrders
/// batchFillOrdersNoThrow
/// batchFillOrKillOrders
/// fillOrder
/// fillOrderNoThrow
/// fillOrKillOrder
/// marketBuyOrders
/// marketBuyOrdersNoThrow
/// marketSellOrders
/// marketSellOrdersNoThrow
/// matchOrders
/// cancelOrder
/// batchCancelOrders
/// cancelOrdersUpTo
/// Trying to call any other exchange function will throw.
/// @param salt Arbitrary number to ensure uniqueness of transaction hash.
/// @param signerAddress Address of transaction signer.
/// @param signedExchangeTransaction AbiV2 encoded calldata.
/// @param signature Proof of signer transaction by signer.
function executeTransaction(
uint256 salt,
address signerAddress,
bytes signedExchangeTransaction,
bytes signature
)
external
{
// Get accounts whose balances must be validated
address[] memory addressesToValidate = getAddressesToValidate(signerAddress);
// Validate account balances
uint256 balanceThreshold = BALANCE_THRESHOLD;
IThresholdAsset thresholdAsset = THRESHOLD_ASSET;
for (uint256 i = 0; i < addressesToValidate.length; ++i) {
uint256 addressBalance = thresholdAsset.balanceOf(addressesToValidate[i]);
require(
addressBalance >= balanceThreshold,
"AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD"
);
}
emit ValidatedAddresses(addressesToValidate);
// All addresses are valid. Execute exchange function.
EXCHANGE.executeTransaction(
salt,
signerAddress,
signedExchangeTransaction,
signature
);
}
/// @dev Constructs an array of addresses to be validated.
/// Addresses depend on which Exchange function is to be called
/// (defined by `signedExchangeTransaction` above).
/// @param signerAddress Address of transaction signer.
/// @return addressesToValidate Array of addresses to validate.
function getAddressesToValidate(address signerAddress)
internal pure
returns (address[] memory addressesToValidate)
{
bytes4 exchangeFunctionSelector = bytes4(exchangeCalldataload(0));
// solhint-disable expression-indent
if (
exchangeFunctionSelector == BATCH_FILL_ORDERS_SELECTOR ||
exchangeFunctionSelector == BATCH_FILL_ORDERS_NO_THROW_SELECTOR ||
exchangeFunctionSelector == BATCH_FILL_OR_KILL_ORDERS_SELECTOR ||
exchangeFunctionSelector == MARKET_BUY_ORDERS_SELECTOR ||
exchangeFunctionSelector == MARKET_BUY_ORDERS_NO_THROW_SELECTOR ||
exchangeFunctionSelector == MARKET_SELL_ORDERS_SELECTOR ||
exchangeFunctionSelector == MARKET_SELL_ORDERS_NO_THROW_SELECTOR
) {
addressesToValidate = loadMakerAddressesFromOrderArray(0);
addressesToValidate = addressesToValidate.append(signerAddress);
} else if (
exchangeFunctionSelector == FILL_ORDER_SELECTOR ||
exchangeFunctionSelector == FILL_ORDER_NO_THROW_SELECTOR ||
exchangeFunctionSelector == FILL_OR_KILL_ORDER_SELECTOR
) {
address makerAddress = loadMakerAddressFromOrder(0);
addressesToValidate = addressesToValidate.append(makerAddress);
addressesToValidate = addressesToValidate.append(signerAddress);
} else if (exchangeFunctionSelector == MATCH_ORDERS_SELECTOR) {
address leftMakerAddress = loadMakerAddressFromOrder(0);
addressesToValidate = addressesToValidate.append(leftMakerAddress);
address rightMakerAddress = loadMakerAddressFromOrder(1);
addressesToValidate = addressesToValidate.append(rightMakerAddress);
addressesToValidate = addressesToValidate.append(signerAddress);
} else if (
exchangeFunctionSelector != CANCEL_ORDER_SELECTOR &&
exchangeFunctionSelector != BATCH_CANCEL_ORDERS_SELECTOR &&
exchangeFunctionSelector != CANCEL_ORDERS_UP_TO_SELECTOR
) {
revert("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR");
}
// solhint-enable expression-indent
return addressesToValidate;
}
}

View File

@@ -0,0 +1,103 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.24;
import "./mixins/MExchangeCalldata.sol";
import "@0x/contracts-libs/contracts/libs/LibAddressArray.sol";
contract MixinExchangeCalldata is
MExchangeCalldata
{
using LibAddressArray for address[];
/// @dev Emulates the `calldataload` opcode on the embedded Exchange calldata,
/// which is accessed through `signedExchangeTransaction`.
/// @param offset Offset into the Exchange calldata.
/// @return value Corresponding 32 byte value stored at `offset`.
function exchangeCalldataload(uint256 offset)
internal pure
returns (bytes32 value)
{
assembly {
// Pointer to exchange transaction
// 0x04 for calldata selector
// 0x40 to access `signedExchangeTransaction`, which is the third parameter
let exchangeTxPtr := calldataload(0x44)
// Offset into Exchange calldata
// We compute this by adding 0x24 to the `exchangeTxPtr` computed above.
// 0x04 for calldata selector
// 0x20 for length field of `signedExchangeTransaction`
let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset))
value := calldataload(exchangeCalldataOffset)
}
return value;
}
/// @dev Convenience function that skips the 4 byte selector when loading
/// from the embedded Exchange calldata.
/// @param offset Offset into the Exchange calldata (minus the 4 byte selector)
/// @return value Corresponding 32 byte value stored at `offset` + 4.
function loadExchangeData(uint256 offset)
internal pure
returns (bytes32 value)
{
value = exchangeCalldataload(offset + 4);
return value;
}
/// @dev Extracts the maker address from an order stored in the Exchange calldata
/// (which is embedded in `signedExchangeTransaction`).
/// @param orderParamIndex Index of the order in the Exchange function's signature.
/// @return makerAddress The extracted maker address.
function loadMakerAddressFromOrder(uint256 orderParamIndex)
internal pure
returns (address makerAddress)
{
uint256 orderOffsetInBytes = orderParamIndex * 32;
uint256 orderPtr = uint256(loadExchangeData(orderOffsetInBytes));
makerAddress = address(loadExchangeData(orderPtr));
return makerAddress;
}
/// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata
/// (which is embedded in `signedExchangeTransaction`).
/// @param orderArrayParamIndex Index of the order array in the Exchange function's signature
/// @return makerAddresses The extracted maker addresses.
function loadMakerAddressesFromOrderArray(uint256 orderArrayParamIndex)
internal pure
returns (address[] makerAddresses)
{
uint256 orderArrayOffsetInBytes = orderArrayParamIndex * 32;
uint256 orderArrayPtr = uint256(loadExchangeData(orderArrayOffsetInBytes));
uint256 orderArrayLength = uint256(loadExchangeData(orderArrayPtr));
uint256 orderArrayLengthInBytes = orderArrayLength * 32;
uint256 orderArrayElementPtr = orderArrayPtr + 32;
uint256 orderArrayElementEndPtr = orderArrayElementPtr + orderArrayLengthInBytes;
for (uint orderPtrOffset = orderArrayElementPtr; orderPtrOffset < orderArrayElementEndPtr; orderPtrOffset += 32) {
uint256 orderPtr = uint256(loadExchangeData(orderPtrOffset));
address makerAddress = address(loadExchangeData(orderPtr + orderArrayElementPtr));
makerAddresses = makerAddresses.append(makerAddress);
}
return makerAddresses;
}
}

View File

@@ -0,0 +1,55 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.24;
contract IBalanceThresholdFilterCore {
/// @dev Executes an Exchange transaction iff the maker and taker meet
/// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR
/// the exchange function is a cancellation.
/// Supported Exchange functions:
/// - batchFillOrders
/// - batchFillOrdersNoThrow
/// - batchFillOrKillOrders
/// - fillOrder
/// - fillOrderNoThrow
/// - fillOrKillOrder
/// - marketBuyOrders
/// - marketBuyOrdersNoThrow
/// - marketSellOrders
/// - marketSellOrdersNoThrow
/// - matchOrders
/// - cancelOrder
/// - batchCancelOrders
/// - cancelOrdersUpTo
/// Trying to call any other exchange function will throw.
/// @param salt Arbitrary number to ensure uniqueness of transaction hash.
/// @param signerAddress Address of transaction signer.
/// @param signedExchangeTransaction AbiV2 encoded calldata.
/// @param signature Proof of signer transaction by signer.
function executeTransaction(
uint256 salt,
address signerAddress,
bytes signedExchangeTransaction,
bytes signature
)
external;
}

View File

@@ -0,0 +1,31 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.24;
contract IThresholdAsset {
/// @param _owner The address from which the balance will be retrieved
/// @return Balance of owner
function balanceOf(address _owner)
external
view
returns (uint256);
}

View File

@@ -0,0 +1,54 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.24;
import "@0x/contracts-interfaces/contracts/protocol/Exchange/IExchange.sol";
import "../interfaces/IThresholdAsset.sol";
import "../interfaces/IBalanceThresholdFilterCore.sol";
contract MBalanceThresholdFilterCore is
IBalanceThresholdFilterCore
{
// Points to 0x exchange contract
// solhint-disable var-name-mixedcase
IExchange internal EXCHANGE;
// The asset that must be held by makers/takers
IThresholdAsset internal THRESHOLD_ASSET;
// The minimum balance of `THRESHOLD_ASSET` that must be held by makers/takers
uint256 internal BALANCE_THRESHOLD;
// solhint-enable var-name-mixedcase
// Addresses that hold at least `BALANCE_THRESHOLD` of `THRESHOLD_ASSET`
event ValidatedAddresses (
address[] addresses
);
/// @dev Constructs an array of addresses to be validated.
/// Addresses depend on which Exchange function is to be called
/// (defined by `signedExchangeTransaction` above).
/// @param signerAddress Address of transaction signer.
/// @return addressesToValidate Array of addresses to validate.
function getAddressesToValidate(address signerAddress)
internal pure
returns (address[] memory addressesToValidate);
}

View File

@@ -0,0 +1,56 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.24;
contract MExchangeCalldata {
/// @dev Emulates the `calldataload` opcode on the embedded Exchange calldata,
/// which is accessed through `signedExchangeTransaction`.
/// @param offset Offset into the Exchange calldata.
/// @return value Corresponding 32 byte value stored at `offset`.
function exchangeCalldataload(uint256 offset)
internal pure
returns (bytes32 value);
/// @dev Convenience function that skips the 4 byte selector when loading
/// from the embedded Exchange calldata.
/// @param offset Offset into the Exchange calldata (minus the 4 byte selector)
/// @return value Corresponding 32 byte value stored at `offset` + 4.
function loadExchangeData(uint256 offset)
internal pure
returns (bytes32 value);
/// @dev Extracts the maker address from an order stored in the Exchange calldata
/// (which is embedded in `signedExchangeTransaction`).
/// @param orderParamIndex Index of the order in the Exchange function's signature.
/// @return makerAddress The extracted maker address.
function loadMakerAddressFromOrder(uint256 orderParamIndex)
internal pure
returns (address makerAddress);
/// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata
/// (which is embedded in `signedExchangeTransaction`).
/// @param orderArrayParamIndex Index of the order array in the Exchange function's signature
/// @return makerAddresses The extracted maker addresses.
function loadMakerAddressesFromOrderArray(uint256 orderArrayParamIndex)
internal pure
returns (address[] makerAddresses);
}

View File

@@ -31,7 +31,7 @@
"lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
},
"config": {
"abis": "generated-artifacts/@(DutchAuction|Forwarder).json"
"abis": "generated-artifacts/@(BalanceThresholdFilter|DutchAuction|Forwarder).json"
},
"repository": {
"type": "git",

View File

@@ -1,9 +1,11 @@
import { ContractArtifact } from 'ethereum-types';
import * as BalanceThresholdFilter from '../../generated-artifacts/BalanceThresholdFilter.json';
import * as DutchAuction from '../../generated-artifacts/DutchAuction.json';
import * as Forwarder from '../../generated-artifacts/Forwarder.json';
export const artifacts = {
BalanceThresholdFilter: BalanceThresholdFilter as ContractArtifact,
DutchAuction: DutchAuction as ContractArtifact,
Forwarder: Forwarder as ContractArtifact,
};

View File

@@ -1,2 +1,3 @@
export * from '../../generated-wrappers/balance_threshold_filter';
export * from '../../generated-wrappers/dutch_auction';
export * from '../../generated-wrappers/forwarder';

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,283 @@
import { artifacts as protocolArtifacts, ExchangeContract } from '@0x/contracts-protocol';
import {
FillResults,
formatters,
LogDecoder,
OrderInfo,
orderUtils,
TransactionFactory,
} from '@0x/contracts-test-utils';
import { artifacts as tokensArtifacts } from '@0x/contracts-tokens';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import * as _ from 'lodash';
import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter';
import { artifacts } from '../../src/artifacts';
export class BalanceThresholdWrapper {
private readonly _balanceThresholdFilter: BalanceThresholdFilterContract;
private readonly _signerTransactionFactory: TransactionFactory;
private readonly _exchange: ExchangeContract;
private readonly _web3Wrapper: Web3Wrapper;
private readonly _logDecoder: LogDecoder;
constructor(
balanceThresholdFilter: BalanceThresholdFilterContract,
exchangeContract: ExchangeContract,
signerTransactionFactory: TransactionFactory,
provider: Provider,
) {
this._balanceThresholdFilter = balanceThresholdFilter;
this._exchange = exchangeContract;
this._signerTransactionFactory = signerTransactionFactory;
this._web3Wrapper = new Web3Wrapper(provider);
this._logDecoder = new LogDecoder(this._web3Wrapper, {
...artifacts,
...tokensArtifacts,
...protocolArtifacts,
});
}
public async fillOrderAsync(
signedOrder: SignedOrder,
from: string,
opts: { takerAssetFillAmount?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
const data = this._exchange.fillOrder.getABIEncodedTransactionData(
params.order,
params.takerAssetFillAmount,
params.signature,
);
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async fillOrKillOrderAsync(
signedOrder: SignedOrder,
from: string,
opts: { takerAssetFillAmount?: BigNumber } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
const data = this._exchange.fillOrKillOrder.getABIEncodedTransactionData(
params.order,
params.takerAssetFillAmount,
params.signature,
);
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async fillOrderNoThrowAsync(
signedOrder: SignedOrder,
from: string,
opts: { takerAssetFillAmount?: BigNumber; gas?: number } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
const data = this._exchange.fillOrderNoThrow.getABIEncodedTransactionData(
params.order,
params.takerAssetFillAmount,
params.signature,
);
const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
return txReceipt;
}
public async batchFillOrdersAsync(
orders: SignedOrder[],
from: string,
opts: { takerAssetFillAmounts?: BigNumber[] } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
const data = this._exchange.batchFillOrders.getABIEncodedTransactionData(
params.orders,
params.takerAssetFillAmounts,
params.signatures,
);
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async batchFillOrKillOrdersAsync(
orders: SignedOrder[],
from: string,
opts: { takerAssetFillAmounts?: BigNumber[] } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData(
params.orders,
params.takerAssetFillAmounts,
params.signatures,
);
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async batchFillOrdersNoThrowAsync(
orders: SignedOrder[],
from: string,
opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number } = {},
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData(
params.orders,
params.takerAssetFillAmounts,
params.signatures,
);
const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
return txReceipt;
}
public async marketSellOrdersAsync(
orders: SignedOrder[],
from: string,
opts: { takerAssetFillAmount: BigNumber },
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount);
const data = this._exchange.marketSellOrders.getABIEncodedTransactionData(
params.orders,
params.takerAssetFillAmount,
params.signatures,
);
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async marketSellOrdersNoThrowAsync(
orders: SignedOrder[],
from: string,
opts: { takerAssetFillAmount: BigNumber; gas?: number },
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount);
const data = this._exchange.marketSellOrdersNoThrow.getABIEncodedTransactionData(
params.orders,
params.takerAssetFillAmount,
params.signatures,
);
const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
return txReceipt;
}
public async marketBuyOrdersAsync(
orders: SignedOrder[],
from: string,
opts: { makerAssetFillAmount: BigNumber },
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount);
const data = this._exchange.marketBuyOrders.getABIEncodedTransactionData(
params.orders,
params.makerAssetFillAmount,
params.signatures,
);
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async marketBuyOrdersNoThrowAsync(
orders: SignedOrder[],
from: string,
opts: { makerAssetFillAmount: BigNumber; gas?: number },
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount);
const data = this._exchange.marketBuyOrdersNoThrow.getABIEncodedTransactionData(
params.orders,
params.makerAssetFillAmount,
params.signatures,
);
const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
return txReceipt;
}
public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createCancel(signedOrder);
const data = this._exchange.cancelOrder.getABIEncodedTransactionData(params.order);
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async batchCancelOrdersAsync(
orders: SignedOrder[],
from: string,
): Promise<TransactionReceiptWithDecodedLogs> {
const params = formatters.createBatchCancel(orders);
const data = this._exchange.batchCancelOrders.getABIEncodedTransactionData(params.orders);
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async cancelOrdersUpToAsync(salt: BigNumber, from: string): Promise<TransactionReceiptWithDecodedLogs> {
const data = this._exchange.cancelOrdersUpTo.getABIEncodedTransactionData(salt);
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise<BigNumber> {
const filledAmount = await this._exchange.filled.callAsync(orderHashHex);
return filledAmount;
}
public async isCancelledAsync(orderHashHex: string): Promise<boolean> {
const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex);
return isCancelled;
}
public async getOrderEpochAsync(makerAddress: string, senderAddress: string): Promise<BigNumber> {
const orderEpoch = await this._exchange.orderEpoch.callAsync(makerAddress, senderAddress);
return orderEpoch;
}
public async getOrderInfoAsync(signedOrder: SignedOrder): Promise<OrderInfo> {
const orderInfo = await this._exchange.getOrderInfo.callAsync(signedOrder);
return orderInfo;
}
public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise<OrderInfo[]> {
const ordersInfo = (await this._exchange.getOrdersInfo.callAsync(signedOrders)) as OrderInfo[];
return ordersInfo;
}
public async matchOrdersAsync(
signedOrderLeft: SignedOrder,
signedOrderRight: SignedOrder,
from: string,
): Promise<TransactionReceiptWithDecodedLogs> {
const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight);
const data = await this._exchange.matchOrders.getABIEncodedTransactionData(
params.left,
params.right,
params.leftSignature,
params.rightSignature,
);
const txReceipt = this._executeTransactionAsync(data, from);
return txReceipt;
}
public async getFillOrderResultsAsync(
signedOrder: SignedOrder,
from: string,
opts: { takerAssetFillAmount?: BigNumber } = {},
): Promise<FillResults> {
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
const fillResults = await this._exchange.fillOrder.callAsync(
params.order,
params.takerAssetFillAmount,
params.signature,
{ from },
);
return fillResults;
}
public abiEncodeFillOrder(signedOrder: SignedOrder, opts: { takerAssetFillAmount?: BigNumber } = {}): string {
const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
const data = this._exchange.fillOrder.getABIEncodedTransactionData(
params.order,
params.takerAssetFillAmount,
params.signature,
);
return data;
}
public getBalanceThresholdAddress(): string {
return this._balanceThresholdFilter.address;
}
public getExchangeAddress(): string {
return this._exchange.address;
}
private async _executeTransactionAsync(
abiEncodedExchangeTxData: string,
from: string,
gas?: number,
): Promise<TransactionReceiptWithDecodedLogs> {
const signedExchangeTx = this._signerTransactionFactory.newSignedTransaction(abiEncodedExchangeTxData);
const txOpts = _.isUndefined(gas) ? { from } : { from, gas };
const txHash = await this._balanceThresholdFilter.executeTransaction.sendTransactionAsync(
signedExchangeTx.salt,
signedExchangeTx.signerAddress,
signedExchangeTx.data,
signedExchangeTx.signature,
txOpts,
);
const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
return txReceipt;
}
}

View File

@@ -6,6 +6,10 @@
"resolveJsonModule": true
},
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"files": ["./generated-artifacts/DutchAuction.json", "./generated-artifacts/Forwarder.json"],
"files": [
"./generated-artifacts/BalanceThresholdFilter.json",
"./generated-artifacts/DutchAuction.json",
"./generated-artifacts/Forwarder.json"
],
"exclude": ["./deploy/solc/solc_bin"]
}

View File

@@ -0,0 +1,84 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.24;
import "@0x/contracts-utils/contracts/utils/LibBytes/LibBytes.sol";
library LibAddressArray {
/// @dev Append a new address to an array of addresses.
/// The `addressArray` may need to be reallocated to make space
/// for the new address. Because of this we return the resulting
/// memory location of `addressArray`.
/// @param addressToAppend Address to append.
/// @return Array of addresses: [... addressArray, addressToAppend]
function append(address[] memory addressArray, address addressToAppend)
internal pure
returns (address[])
{
// Get stats on address array and free memory
uint256 freeMemPtr = 0;
uint256 addressArrayBeginPtr = 0;
uint256 addressArrayEndPtr = 0;
uint256 addressArrayLength = addressArray.length;
uint256 addressArrayMemSizeInBytes = 32 + (32 * addressArrayLength);
assembly {
freeMemPtr := mload(0x40)
addressArrayBeginPtr := addressArray
addressArrayEndPtr := add(addressArray, addressArrayMemSizeInBytes)
}
// Cases for `freeMemPtr`:
// `freeMemPtr` == `addressArrayEndPtr`: Nothing occupies memory after `addressArray`
// `freeMemPtr` > `addressArrayEndPtr`: Some value occupies memory after `addressArray`
// `freeMemPtr` < `addressArrayEndPtr`: Memory has not been managed properly.
require(
freeMemPtr >= addressArrayEndPtr,
"INVALID_FREE_MEMORY_PTR"
);
// If free memory begins at the end of `addressArray`
// then we can append `addressToAppend` directly.
// Otherwise, we must copy the array to free memory
// before appending new values to it.
if (freeMemPtr > addressArrayEndPtr) {
LibBytes.memCopy(freeMemPtr, addressArrayBeginPtr, addressArrayMemSizeInBytes);
assembly {
addressArray := freeMemPtr
addressArrayBeginPtr := addressArray
}
}
// Append `addressToAppend`
addressArrayLength += 1;
addressArrayMemSizeInBytes += 32;
addressArrayEndPtr = addressArrayBeginPtr + addressArrayMemSizeInBytes;
freeMemPtr = addressArrayEndPtr;
assembly {
// Store new array length
mstore(addressArray, addressArrayLength)
// Update `freeMemPtr`
mstore(0x40, freeMemPtr)
}
addressArray[addressArrayLength - 1] = addressToAppend;
return addressArray;
}
}

View File

@@ -0,0 +1,152 @@
/*
Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity 0.4.24;
contract LibExchangeSelectors {
// solhint-disable max-line-length
// allowedValidators
bytes4 constant public ALLOWED_VALIDATORS_SELECTOR = 0x7b8e3514;
bytes4 constant public ALLOWED_VALIDATORS_SELECTOR_GENERATOR = bytes4(keccak256("allowedValidators(address,address)"));
// assetProxies
bytes4 constant public ASSET_PROXIES_SELECTOR = 0x3fd3c997;
bytes4 constant public ASSET_PROXIES_SELECTOR_GENERATOR = bytes4(keccak256("assetProxies(bytes4)"));
// batchCancelOrders
bytes4 constant public BATCH_CANCEL_ORDERS_SELECTOR = 0x4ac14782;
bytes4 constant public BATCH_CANCEL_ORDERS_SELECTOR_GENERATOR = bytes4(keccak256("batchCancelOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])"));
// batchFillOrKillOrders
bytes4 constant public BATCH_FILL_OR_KILL_ORDERS_SELECTOR = 0x4d0ae546;
bytes4 constant public BATCH_FILL_OR_KILL_ORDERS_SELECTOR_GENERATOR = bytes4(keccak256("batchFillOrKillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])"));
// batchFillOrders
bytes4 constant public BATCH_FILL_ORDERS_SELECTOR = 0x297bb70b;
bytes4 constant public BATCH_FILL_ORDERS_SELECTOR_GENERATOR = bytes4(keccak256("batchFillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])"));
// batchFillOrdersNoThrow
bytes4 constant public BATCH_FILL_ORDERS_NO_THROW_SELECTOR = 0x50dde190;
bytes4 constant public BATCH_FILL_ORDERS_NO_THROW_SELECTOR_GENERATOR = bytes4(keccak256("batchFillOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])"));
// cancelOrder
bytes4 constant public CANCEL_ORDER_SELECTOR = 0xd46b02c3;
bytes4 constant public CANCEL_ORDER_SELECTOR_GENERATOR = bytes4(keccak256("cancelOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))"));
// cancelOrdersUpTo
bytes4 constant public CANCEL_ORDERS_UP_TO_SELECTOR = 0x4f9559b1;
bytes4 constant public CANCEL_ORDERS_UP_TO_SELECTOR_GENERATOR = bytes4(keccak256("cancelOrdersUpTo(uint256)"));
// cancelled
bytes4 constant public CANCELLED_SELECTOR = 0x2ac12622;
bytes4 constant public CANCELLED_SELECTOR_GENERATOR = bytes4(keccak256("cancelled(bytes32)"));
// currentContextAddress
bytes4 constant public CURRENT_CONTEXT_ADDRESS_SELECTOR = 0xeea086ba;
bytes4 constant public CURRENT_CONTEXT_ADDRESS_SELECTOR_GENERATOR = bytes4(keccak256("currentContextAddress()"));
// executeTransaction
bytes4 constant public EXECUTE_TRANSACTION_SELECTOR = 0xbfc8bfce;
bytes4 constant public EXECUTE_TRANSACTION_SELECTOR_GENERATOR = bytes4(keccak256("executeTransaction(uint256,address,bytes,bytes)"));
// fillOrKillOrder
bytes4 constant public FILL_OR_KILL_ORDER_SELECTOR = 0x64a3bc15;
bytes4 constant public FILL_OR_KILL_ORDER_SELECTOR_GENERATOR = bytes4(keccak256("fillOrKillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)"));
// fillOrder
bytes4 constant public FILL_ORDER_SELECTOR = 0xb4be83d5;
bytes4 constant public FILL_ORDER_SELECTOR_GENERATOR = bytes4(keccak256("fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)"));
// fillOrderNoThrow
bytes4 constant public FILL_ORDER_NO_THROW_SELECTOR = 0x3e228bae;
bytes4 constant public FILL_ORDER_NO_THROW_SELECTOR_GENERATOR = bytes4(keccak256("fillOrderNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)"));
// filled
bytes4 constant public FILLED_SELECTOR = 0x288cdc91;
bytes4 constant public FILLED_SELECTOR_GENERATOR = bytes4(keccak256("filled(bytes32)"));
// getAssetProxy
bytes4 constant public GET_ASSET_PROXY_SELECTOR = 0x60704108;
bytes4 constant public GET_ASSET_PROXY_SELECTOR_GENERATOR = bytes4(keccak256("getAssetProxy(bytes4)"));
// getOrderInfo
bytes4 constant public GET_ORDER_INFO_SELECTOR = 0xc75e0a81;
bytes4 constant public GET_ORDER_INFO_SELECTOR_GENERATOR = bytes4(keccak256("getOrderInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))"));
// getOrdersInfo
bytes4 constant public GET_ORDERS_INFO_SELECTOR = 0x7e9d74dc;
bytes4 constant public GET_ORDERS_INFO_SELECTOR_GENERATOR = bytes4(keccak256("getOrdersInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])"));
// isValidSignature
bytes4 constant public IS_VALID_SIGNATURE_SELECTOR = 0x93634702;
bytes4 constant public IS_VALID_SIGNATURE_SELECTOR_GENERATOR = bytes4(keccak256("isValidSignature(bytes32,address,bytes)"));
// marketBuyOrders
bytes4 constant public MARKET_BUY_ORDERS_SELECTOR = 0xe5fa431b;
bytes4 constant public MARKET_BUY_ORDERS_SELECTOR_GENERATOR = bytes4(keccak256("marketBuyOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])"));
// marketBuyOrdersNoThrow
bytes4 constant public MARKET_BUY_ORDERS_NO_THROW_SELECTOR = 0xa3e20380;
bytes4 constant public MARKET_BUY_ORDERS_NO_THROW_SELECTOR_GENERATOR = bytes4(keccak256("marketBuyOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])"));
// marketSellOrders
bytes4 constant public MARKET_SELL_ORDERS_SELECTOR = 0x7e1d9808;
bytes4 constant public MARKET_SELL_ORDERS_SELECTOR_GENERATOR = bytes4(keccak256("marketSellOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])"));
// marketSellOrdersNoThrow
bytes4 constant public MARKET_SELL_ORDERS_NO_THROW_SELECTOR = 0xdd1c7d18;
bytes4 constant public MARKET_SELL_ORDERS_NO_THROW_SELECTOR_GENERATOR = bytes4(keccak256("marketSellOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])"));
// matchOrders
bytes4 constant public MATCH_ORDERS_SELECTOR = 0x3c28d861;
bytes4 constant public MATCH_ORDERS_SELECTOR_GENERATOR = bytes4(keccak256("matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)"));
// orderEpoch
bytes4 constant public ORDER_EPOCH_SELECTOR = 0xd9bfa73e;
bytes4 constant public ORDER_EPOCH_SELECTOR_GENERATOR = bytes4(keccak256("orderEpoch(address,address)"));
// owner
bytes4 constant public OWNER_SELECTOR = 0x8da5cb5b;
bytes4 constant public OWNER_SELECTOR_GENERATOR = bytes4(keccak256("owner()"));
// preSign
bytes4 constant public PRE_SIGN_SELECTOR = 0x3683ef8e;
bytes4 constant public PRE_SIGN_SELECTOR_GENERATOR = bytes4(keccak256("preSign(bytes32,address,bytes)"));
// preSigned
bytes4 constant public PRE_SIGNED_SELECTOR = 0x82c174d0;
bytes4 constant public PRE_SIGNED_SELECTOR_GENERATOR = bytes4(keccak256("preSigned(bytes32,address)"));
// registerAssetProxy
bytes4 constant public REGISTER_ASSET_PROXY_SELECTOR = 0xc585bb93;
bytes4 constant public REGISTER_ASSET_PROXY_SELECTOR_GENERATOR = bytes4(keccak256("registerAssetProxy(address)"));
// setSignatureValidatorApproval
bytes4 constant public SET_SIGNATURE_VALIDATOR_APPROVAL_SELECTOR = 0x77fcce68;
bytes4 constant public SET_SIGNATURE_VALIDATOR_APPROVAL_SELECTOR_GENERATOR = bytes4(keccak256("setSignatureValidatorApproval(address,bool)"));
// transactions
bytes4 constant public TRANSACTIONS_SELECTOR = 0x642f2eaf;
bytes4 constant public TRANSACTIONS_SELECTOR_GENERATOR = bytes4(keccak256("transactions(bytes32)"));
// transferOwnership
bytes4 constant public TRANSFER_OWNERSHIP_SELECTOR = 0xf2fde38b;
bytes4 constant public TRANSFER_OWNERSHIP_SELECTOR_GENERATOR = bytes4(keccak256("transferOwnership(address)"));
}

View File

@@ -0,0 +1,2 @@
export * from './artifacts';
export * from './wrappers';

View File

@@ -1,4 +1,13 @@
[
{
"version": "2.2.0",
"changes": [
{
"note": "Added LibAddressArray",
"pr": 1383
}
]
},
{
"timestamp": 1544741676,
"version": "2.1.59",

View File

@@ -44,7 +44,6 @@
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
"devDependencies": {
"@0x/abi-gen": "^1.0.19",
"@0x/contracts-test-utils": "^1.0.2",
"@0x/dev-utils": "^1.0.21",
"@0x/sol-compiler": "^1.1.16",
"@0x/sol-cov": "^2.1.16",
@@ -75,6 +74,7 @@
"@0x/contracts-interfaces": "^1.0.2",
"@0x/contracts-libs": "^1.0.2",
"@0x/contracts-multisig": "^1.0.2",
"@0x/contracts-test-utils": "^1.0.2",
"@0x/contracts-tokens": "^1.0.2",
"@0x/contracts-utils": "^1.0.2",
"@0x/order-utils": "^3.0.7",

View File

@@ -104,6 +104,7 @@ export enum ContractName {
Authorizable = 'Authorizable',
Whitelist = 'Whitelist',
Forwarder = 'Forwarder',
BalanceThresholdFilter = 'BalanceThresholdFilter',
}
export interface SignedTransaction {

View File

@@ -0,0 +1,52 @@
{
"id": "/orderWatcherWebSocketRequestSchema",
"type": "object",
"definitions": {
"signedOrderParam": {
"type": "object",
"properties": {
"signedOrder": { "$ref": "/signedOrderSchema" }
},
"required": ["signedOrder"]
},
"orderHashParam": {
"type": "object",
"properties": {
"orderHash": { "$ref": "/hexSchema" }
},
"required": ["orderHash"]
}
},
"oneOf": [
{
"type": "object",
"properties": {
"id": { "type": "number" },
"jsonrpc": { "type": "string" },
"method": { "enum": ["ADD_ORDER"] },
"params": { "$ref": "#/definitions/signedOrderParam" }
},
"required": ["id", "jsonrpc", "method", "params"]
},
{
"type": "object",
"properties": {
"id": { "type": "number" },
"jsonrpc": { "type": "string" },
"method": { "enum": ["REMOVE_ORDER"] },
"params": { "$ref": "#/definitions/orderHashParam" }
},
"required": ["id", "jsonrpc", "method", "params"]
},
{
"type": "object",
"properties": {
"id": { "type": "number" },
"jsonrpc": { "type": "string" },
"method": { "enum": ["GET_STATS"] },
"params": {}
},
"required": ["id", "jsonrpc", "method"]
}
]
}

View File

@@ -0,0 +1,10 @@
{
"id": "/orderWatcherWebSocketUtf8MessageSchema",
"properties": {
"utf8Data": { "type": "string" }
},
"required": [
"utf8Data"
],
"type": "object"
}

View File

@@ -16,6 +16,8 @@ import * as orderFillOrKillRequestsSchema from '../schemas/order_fill_or_kill_re
import * as orderFillRequestsSchema from '../schemas/order_fill_requests_schema.json';
import * as orderHashSchema from '../schemas/order_hash_schema.json';
import * as orderSchema from '../schemas/order_schema.json';
import * as orderWatcherWebSocketRequestSchema from '../schemas/order_watcher_web_socket_request_schema.json';
import * as orderWatcherWebSocketUtf8MessageSchema from '../schemas/order_watcher_web_socket_utf8_message_schema.json';
import * as orderBookRequestSchema from '../schemas/orderbook_request_schema.json';
import * as ordersRequestOptsSchema from '../schemas/orders_request_opts_schema.json';
import * as ordersSchema from '../schemas/orders_schema.json';
@@ -66,6 +68,8 @@ export const schemas = {
jsNumber,
requestOptsSchema,
pagedRequestOptsSchema,
orderWatcherWebSocketRequestSchema,
orderWatcherWebSocketUtf8MessageSchema,
ordersRequestOptsSchema,
orderBookRequestSchema,
orderConfigRequestSchema,

View File

@@ -23,6 +23,8 @@
"./schemas/order_schema.json",
"./schemas/signed_order_schema.json",
"./schemas/orders_schema.json",
"./schemas/order_watcher_web_socket_request_schema.json",
"./schemas/order_watcher_web_socket_utf8_message_schema.json",
"./schemas/paginated_collection_schema.json",
"./schemas/relayer_api_asset_data_pairs_response_schema.json",
"./schemas/relayer_api_asset_data_pairs_schema.json",

View File

@@ -1,4 +1,14 @@
[
{
"version": "2.3.0",
"changes": [
{
"note":
"Added a WebSocket interface to OrderWatcher so that it can be used by a client written in any language",
"pr": 1427
}
]
},
{
"version": "2.2.8",
"changes": [

View File

@@ -4,6 +4,9 @@ An order watcher daemon that watches for order validity.
#### Read the wiki [article](https://0xproject.com/wiki#0x-OrderWatcher).
OrderWatcher also comes with a WebSocket server to provide language-agnostic access
to order watching functionality. We used the [WebSocket Client and Server Implementation for Node](https://www.npmjs.com/package/websocket). The server sends and receives messages that conform to the [JSON RPC specifications](https://www.jsonrpc.org/specification).
## Installation
**Install**
@@ -26,6 +29,91 @@ If your project is in [TypeScript](https://www.typescriptlang.org/), add the fol
}
```
## Using the WebSocket Server
**Setup**
**Environmental Variables**
Several environmental variables can be set to configure the server:
* `ORDER_WATCHER_HTTP_PORT` specifies the port that the http server will listen on
and accept connections from. When this is not set, we default to 8080.
**Requests**
The server accepts three types of requests: `ADD_ORDER`, `REMOVE_ORDER` and `GET_STATS`. These mirror what the underlying OrderWatcher does. You can read more in the [wiki](https://0xproject.com/wiki#0x-OrderWatcher). Unlike the OrderWatcher, it does not expose any `subscribe` or `unsubscribe` functionality because the WebSocket server keeps a single subscription open for all clients.
The first step for making a request is establishing a connection with the server. In Javascript:
```
var W3CWebSocket = require('websocket').w3cwebsocket;
wsClient = new W3CWebSocket('ws://127.0.0.1:8080');
```
In Python, you could use the [websocket-client library](http://pypi.python.org/pypi/websocket-client/) and run:
```
from websocket import create_connection
wsClient = create_connection("ws://127.0.0.1:8080")
```
With the connection established, you prepare the payload for your request. The payload is a json object with a format established by the [JSON RPC specification](https://www.jsonrpc.org/specification):
* `id`: All requests require you to specify a numerical `id`. When the server responds to the request, the response will have the same `id` as the one supplied with your request.
* `jsonrpc`: This is always the string `'2.0'`.
* `method`: This specifies the OrderWatcher method you want to call. I.e., `'ADD_ORDER'`, `'REMOVE_ORDER'` or `'GET_STATS'`.
* `params`: These contain the parameters needed by OrderWatcher to execute the method you called. For `ADD_ORDER`, provide `{ signedOrder: <your signedOrder> }`. For `REMOVE_ORDER`, provide `{ orderHash: <your orderHash> }`. For `GET_STATS`, no parameters are needed, so you may leave this empty.
Next, convert the payload to a string and send it through the connection.
In Javascript:
```
const addOrderPayload = {
id: 1,
jsonrpc: '2.0',
method: 'ADD_ORDER',
params: { signedOrder: <your signedOrder> },
};
wsClient.send(JSON.stringify(addOrderPayload));
```
In Python:
```
import json
remove_order_payload = {
'id': 1,
'jsonrpc': '2.0',
'method': 'REMOVE_ORDER',
'params': {'orderHash': '0x6edc16bf37fde79f5012088c33784c730e2f103d9ab1caf73060c386ad107b7e'},
}
wsClient.send(json.dumps(remove_order_payload));
```
**Response**
The server responds to all requests in a similar format. In the data field, you'll find another object containing the following fields:
* `id`: The id corresponding to the request that the server is responding to. `UPDATE` responses are not based on any requests so the `id` field is omitted`.
* `jsonrpc`: Always `'2.0'`.
* `method`: The method the server is responding to. Eg. `ADD_ORDER`. When order states change the server may also initiate a response. In this case, method will be listed as `UPDATE`.
* `result`: This field varies based on the method. `UPDATE` responses contain the new order state. `GET_STATS` responses contain the current order count. When there are errors, this field is omitted.
* `error`: When there is an error executing a request, the [JSON RPC](https://www.jsonrpc.org/specification) error object is listed here. When the server responds successfully, this field is omitted.
In Javascript, the responses can be parsed using the `onmessage` callback:
```
wsClient.onmessage = (msg) => {
const responseData = JSON.parse(msg.data);
const method = responseData.method
};
```
In Python, `recv` is a lightweight way to receive a response:
```
result = wsClient.recv()
method = result.method
```
## Contributing
We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository.

View File

@@ -74,7 +74,8 @@
"ethereum-types": "^1.1.4",
"ethereumjs-blockstream": "6.0.0",
"ethers": "~4.0.4",
"lodash": "^4.17.5"
"lodash": "^4.17.5",
"websocket": "^1.0.25"
},
"publishConfig": {
"access": "public"

View File

@@ -1,4 +1,5 @@
export { OrderWatcher } from './order_watcher/order_watcher';
export { OrderWatcherWebSocketServer } from './order_watcher/order_watcher_web_socket_server';
export { ExpirationWatcher } from './order_watcher/expiration_watcher';
export {

View File

@@ -0,0 +1,200 @@
import { ContractAddresses } from '@0x/contract-addresses';
import { schemas } from '@0x/json-schemas';
import { OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types';
import { BigNumber, logUtils } from '@0x/utils';
import { Provider } from 'ethereum-types';
import * as http from 'http';
import * as WebSocket from 'websocket';
import { GetStatsResult, OrderWatcherConfig, OrderWatcherMethod, WebSocketRequest, WebSocketResponse } from '../types';
import { assert } from '../utils/assert';
import { OrderWatcher } from './order_watcher';
const DEFAULT_HTTP_PORT = 8080;
const JSON_RPC_VERSION = '2.0';
// Wraps the OrderWatcher functionality in a WebSocket server. Motivations:
// 1) Users can watch orders via non-typescript programs.
// 2) Better encapsulation so that users can work
export class OrderWatcherWebSocketServer {
private readonly _orderWatcher: OrderWatcher;
private readonly _httpServer: http.Server;
private readonly _connectionStore: Set<WebSocket.connection>;
private readonly _wsServer: WebSocket.server;
private readonly _isVerbose: boolean;
/**
* Recover types lost when the payload is stringified.
*/
private static _parseSignedOrder(rawRequest: any): SignedOrder {
const bigNumberFields = [
'salt',
'makerFee',
'takerFee',
'makerAssetAmount',
'takerAssetAmount',
'expirationTimeSeconds',
];
for (const field of bigNumberFields) {
rawRequest[field] = new BigNumber(rawRequest[field]);
}
return rawRequest;
}
/**
* Instantiate a new WebSocket server which provides OrderWatcher functionality
* @param provider Web3 provider to use for JSON RPC calls.
* @param networkId NetworkId to watch orders on.
* @param contractAddresses Optional contract addresses. Defaults to known
* addresses based on networkId.
* @param orderWatcherConfig OrderWatcher configurations. isVerbose sets the verbosity for the WebSocket server aswell.
* @param isVerbose Whether to enable verbose logging. Defaults to true.
*/
constructor(
provider: Provider,
networkId: number,
contractAddresses?: ContractAddresses,
orderWatcherConfig?: Partial<OrderWatcherConfig>,
) {
this._isVerbose =
orderWatcherConfig !== undefined && orderWatcherConfig.isVerbose !== undefined
? orderWatcherConfig.isVerbose
: true;
this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, orderWatcherConfig);
this._connectionStore = new Set();
this._httpServer = http.createServer();
this._wsServer = new WebSocket.server({
httpServer: this._httpServer,
// Avoid setting autoAcceptConnections to true as it defeats all
// standard cross-origin protection facilities built into the protocol
// and the browser.
// Source: https://www.npmjs.com/package/websocket#server-example
// Also ensures that a request event is emitted by
// the server whenever a new WebSocket request is made.
autoAcceptConnections: false,
});
this._wsServer.on('request', async (request: any) => {
// Designed for usage pattern where client and server are run on the same
// machine by the same user. As such, no security checks are in place.
const connection: WebSocket.connection = request.accept(null, request.origin);
this._log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`);
connection.on('message', this._onMessageCallbackAsync.bind(this, connection));
connection.on('close', this._onCloseCallback.bind(this, connection));
this._connectionStore.add(connection);
});
}
/**
* Activates the WebSocket server by subscribing to the OrderWatcher and
* starting the WebSocket's HTTP server
*/
public start(): void {
// Have the WebSocket server subscribe to the OrderWatcher to receive updates.
// These updates are then broadcast to clients in the _connectionStore.
this._orderWatcher.subscribe(this._broadcastCallback.bind(this));
const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT;
this._httpServer.listen(port, () => {
this._log(`${new Date()} [Server] Listening on port ${port}`);
});
}
/**
* Deactivates the WebSocket server by stopping the HTTP server from accepting
* new connections and unsubscribing from the OrderWatcher
*/
public stop(): void {
this._httpServer.close();
this._orderWatcher.unsubscribe();
}
private _log(...args: any[]): void {
if (this._isVerbose) {
logUtils.log(...args);
}
}
private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise<void> {
let response: WebSocketResponse;
let id: number | null = null;
try {
assert.doesConformToSchema('message', message, schemas.orderWatcherWebSocketUtf8MessageSchema);
const request: WebSocketRequest = JSON.parse(message.utf8Data);
id = request.id;
assert.doesConformToSchema('request', request, schemas.orderWatcherWebSocketRequestSchema);
assert.isString(request.jsonrpc, JSON_RPC_VERSION);
response = {
id,
jsonrpc: JSON_RPC_VERSION,
method: request.method,
result: await this._routeRequestAsync(request),
};
} catch (err) {
response = {
id,
jsonrpc: JSON_RPC_VERSION,
method: null,
error: err.toString(),
};
}
this._log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`);
connection.sendUTF(JSON.stringify(response));
}
private _onCloseCallback(connection: WebSocket.connection): void {
this._connectionStore.delete(connection);
this._log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`);
}
private async _routeRequestAsync(request: WebSocketRequest): Promise<GetStatsResult | undefined> {
this._log(`${new Date()} [Server] Request received: ${request.method}`);
switch (request.method) {
case OrderWatcherMethod.AddOrder: {
const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder(
request.params.signedOrder,
);
await this._orderWatcher.addOrderAsync(signedOrder);
break;
}
case OrderWatcherMethod.RemoveOrder: {
this._orderWatcher.removeOrder(request.params.orderHash || 'undefined');
break;
}
case OrderWatcherMethod.GetStats: {
return this._orderWatcher.getStats();
}
default:
// Should never reach here. Should be caught by JSON schema check.
throw new Error(`Unexpected default case hit for request.method`);
}
return undefined;
}
/**
* Broadcasts OrderState changes to ALL connected clients. At the moment,
* we do not support clients subscribing to only a subset of orders. As such,
* Client B will be notified of changes to an order that Client A added.
*/
private _broadcastCallback(err: Error | null, orderState?: OrderStateValid | OrderStateInvalid | undefined): void {
const method = OrderWatcherMethod.Update;
const response =
err === null
? {
jsonrpc: JSON_RPC_VERSION,
method,
result: orderState,
}
: {
jsonrpc: JSON_RPC_VERSION,
method,
error: {
code: -32000,
message: err.message,
},
};
this._connectionStore.forEach((connection: WebSocket.connection) => {
connection.sendUTF(JSON.stringify(response));
});
}
}

View File

@@ -1,4 +1,4 @@
import { OrderState } from '@0x/types';
import { OrderState, SignedOrder } from '@0x/types';
import { LogEntryEvent } from 'ethereum-types';
export enum OrderWatcherError {
@@ -31,3 +31,67 @@ export enum InternalOrderWatcherError {
ZrxNotInTokenRegistry = 'ZRX_NOT_IN_TOKEN_REGISTRY',
WethNotInTokenRegistry = 'WETH_NOT_IN_TOKEN_REGISTRY',
}
export enum OrderWatcherMethod {
// Methods initiated by the user.
GetStats = 'GET_STATS',
AddOrder = 'ADD_ORDER',
RemoveOrder = 'REMOVE_ORDER',
// These are spontaneous; they are primarily orderstate changes.
Update = 'UPDATE',
// `subscribe` and `unsubscribe` are methods of OrderWatcher, but we don't
// need to expose them to the WebSocket server user because the user implicitly
// subscribes and unsubscribes by connecting and disconnecting from the server.
}
// Users have to create a json object of this format and attach it to
// the data field of their WebSocket message to interact with the server.
export type WebSocketRequest = AddOrderRequest | RemoveOrderRequest | GetStatsRequest;
export interface AddOrderRequest {
id: number;
jsonrpc: string;
method: OrderWatcherMethod.AddOrder;
params: { signedOrder: SignedOrder };
}
export interface RemoveOrderRequest {
id: number;
jsonrpc: string;
method: OrderWatcherMethod.RemoveOrder;
params: { orderHash: string };
}
export interface GetStatsRequest {
id: number;
jsonrpc: string;
method: OrderWatcherMethod.GetStats;
}
// Users should expect a json object of this format in the data field
// of the WebSocket messages that the server sends out.
export type WebSocketResponse = SuccessfulWebSocketResponse | ErrorWebSocketResponse;
export interface SuccessfulWebSocketResponse {
id: number;
jsonrpc: string;
method: OrderWatcherMethod;
result: OrderState | GetStatsResult | undefined; // result is undefined for ADD_ORDER and REMOVE_ORDER
}
export interface ErrorWebSocketResponse {
id: number | null;
jsonrpc: string;
method: null;
error: JSONRPCError;
}
export interface JSONRPCError {
code: number;
message: string;
data?: string | object;
}
export interface GetStatsResult {
orderCount: number;
}

View File

@@ -0,0 +1,308 @@
import { ContractWrappers } from '@0x/contract-wrappers';
import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils';
import { BlockchainLifecycle } from '@0x/dev-utils';
import { FillScenarios } from '@0x/fill-scenarios';
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
import { ExchangeContractErrs, OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types';
import { BigNumber, logUtils } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as chai from 'chai';
import 'mocha';
import * as WebSocket from 'websocket';
import { OrderWatcherWebSocketServer } from '../src/order_watcher/order_watcher_web_socket_server';
import { AddOrderRequest, OrderWatcherMethod, RemoveOrderRequest } from '../src/types';
import { chaiSetup } from './utils/chai_setup';
import { constants } from './utils/constants';
import { migrateOnceAsync } from './utils/migrate';
import { provider, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
interface WsMessage {
data: string;
}
describe.only('OrderWatcherWebSocketServer', async () => {
let contractWrappers: ContractWrappers;
let wsServer: OrderWatcherWebSocketServer;
let wsClient: WebSocket.w3cwebsocket;
let wsClientTwo: WebSocket.w3cwebsocket;
let fillScenarios: FillScenarios;
let userAddresses: string[];
let makerAssetData: string;
let takerAssetData: string;
let makerTokenAddress: string;
let takerTokenAddress: string;
let makerAddress: string;
let takerAddress: string;
let zrxTokenAddress: string;
let signedOrder: SignedOrder;
let orderHash: string;
let addOrderPayload: AddOrderRequest;
let removeOrderPayload: RemoveOrderRequest;
const decimals = constants.ZRX_DECIMALS;
const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals);
before(async () => {
// Set up constants
const contractAddresses = await migrateOnceAsync();
await blockchainLifecycle.startAsync();
const networkId = constants.TESTRPC_NETWORK_ID;
const config = {
networkId,
contractAddresses,
};
contractWrappers = new ContractWrappers(provider, config);
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
zrxTokenAddress = contractAddresses.zrxToken;
[makerAddress, takerAddress] = userAddresses;
[makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
[makerAssetData, takerAssetData] = [
assetDataUtils.encodeERC20AssetData(makerTokenAddress),
assetDataUtils.encodeERC20AssetData(takerTokenAddress),
];
fillScenarios = new FillScenarios(
provider,
userAddresses,
zrxTokenAddress,
contractAddresses.exchange,
contractAddresses.erc20Proxy,
contractAddresses.erc721Proxy,
);
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerAssetData,
takerAssetData,
makerAddress,
takerAddress,
fillableAmount,
);
orderHash = orderHashUtils.getOrderHashHex(signedOrder);
addOrderPayload = {
id: 1,
jsonrpc: '2.0',
method: OrderWatcherMethod.AddOrder,
params: { signedOrder },
};
removeOrderPayload = {
id: 1,
jsonrpc: '2.0',
method: OrderWatcherMethod.RemoveOrder,
params: { orderHash },
};
// Prepare OrderWatcher WebSocket server
const orderWatcherConfig = {
isVerbose: true,
};
wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig);
});
after(async () => {
await blockchainLifecycle.revertAsync();
});
beforeEach(async () => {
wsServer.start();
await blockchainLifecycle.startAsync();
wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/');
logUtils.log(`${new Date()} [Client] Connected.`);
});
afterEach(async () => {
wsClient.close();
await blockchainLifecycle.revertAsync();
wsServer.stop();
logUtils.log(`${new Date()} [Client] Closed.`);
});
it('responds to getStats requests correctly', (done: any) => {
const payload = {
id: 1,
jsonrpc: '2.0',
method: 'GET_STATS',
};
wsClient.onopen = () => wsClient.send(JSON.stringify(payload));
wsClient.onmessage = (msg: any) => {
const responseData = JSON.parse(msg.data);
expect(responseData.id).to.be.eq(1);
expect(responseData.jsonrpc).to.be.eq('2.0');
expect(responseData.method).to.be.eq('GET_STATS');
expect(responseData.result.orderCount).to.be.eq(0);
done();
};
});
it('throws an error when an invalid method is attempted', async () => {
const invalidMethodPayload = {
id: 1,
jsonrpc: '2.0',
method: 'BAD_METHOD',
};
wsClient.onopen = () => wsClient.send(JSON.stringify(invalidMethodPayload));
const errorMsg = await onMessageAsync(wsClient, null);
const errorData = JSON.parse(errorMsg.data);
// tslint:disable-next-line:no-unused-expression
expect(errorData.id).to.be.null;
// tslint:disable-next-line:no-unused-expression
expect(errorData.method).to.be.null;
expect(errorData.jsonrpc).to.be.eq('2.0');
expect(errorData.error).to.match(/^Error: Expected request to conform to schema/);
});
it('throws an error when jsonrpc field missing from request', async () => {
const noJsonRpcPayload = {
id: 1,
method: 'GET_STATS',
};
wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload));
const errorMsg = await onMessageAsync(wsClient, null);
const errorData = JSON.parse(errorMsg.data);
// tslint:disable-next-line:no-unused-expression
expect(errorData.method).to.be.null;
expect(errorData.jsonrpc).to.be.eq('2.0');
expect(errorData.error).to.match(/^Error: Expected request to conform to schema/);
});
it('throws an error when we try to add an order without a signedOrder', async () => {
const noSignedOrderAddOrderPayload = {
id: 1,
jsonrpc: '2.0',
method: 'ADD_ORDER',
orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355',
};
wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload));
const errorMsg = await onMessageAsync(wsClient, null);
const errorData = JSON.parse(errorMsg.data);
// tslint:disable-next-line:no-unused-expression
expect(errorData.id).to.be.null;
// tslint:disable-next-line:no-unused-expression
expect(errorData.method).to.be.null;
expect(errorData.jsonrpc).to.be.eq('2.0');
expect(errorData.error).to.match(/^Error: Expected request to conform to schema/);
});
it('throws an error when we try to add a bad signedOrder', async () => {
const invalidAddOrderPayload = {
id: 1,
jsonrpc: '2.0',
method: 'ADD_ORDER',
signedOrder: {
makerAddress: '0x0',
},
};
wsClient.onopen = () => wsClient.send(JSON.stringify(invalidAddOrderPayload));
const errorMsg = await onMessageAsync(wsClient, null);
const errorData = JSON.parse(errorMsg.data);
// tslint:disable-next-line:no-unused-expression
expect(errorData.id).to.be.null;
// tslint:disable-next-line:no-unused-expression
expect(errorData.method).to.be.null;
expect(errorData.error).to.match(/^Error: Expected request to conform to schema/);
});
it('executes addOrder and removeOrder requests correctly', async () => {
wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload));
const addOrderMsg = await onMessageAsync(wsClient, OrderWatcherMethod.AddOrder);
const addOrderData = JSON.parse(addOrderMsg.data);
expect(addOrderData.method).to.be.eq('ADD_ORDER');
expect((wsServer as any)._orderWatcher._orderByOrderHash).to.deep.include({
[orderHash]: signedOrder,
});
const clientOnMessagePromise = onMessageAsync(wsClient, OrderWatcherMethod.RemoveOrder);
wsClient.send(JSON.stringify(removeOrderPayload));
const removeOrderMsg = await clientOnMessagePromise;
const removeOrderData = JSON.parse(removeOrderMsg.data);
expect(removeOrderData.method).to.be.eq('REMOVE_ORDER');
expect((wsServer as any)._orderWatcher._orderByOrderHash).to.not.deep.include({
[orderHash]: signedOrder,
});
});
it('broadcasts orderStateInvalid message when makerAddress allowance set to 0 for watched order', async () => {
// Add the regular order
wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload));
// We register the onMessage callback before calling `setProxyAllowanceAsync` which we
// expect will cause a message to be emitted. We do now "await" here, since we want to
// check for messages _after_ calling `setProxyAllowanceAsync`
const clientOnMessagePromise = onMessageAsync(wsClient, OrderWatcherMethod.Update);
// Set the allowance to 0
await contractWrappers.erc20Token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, new BigNumber(0));
// We now await the `onMessage` promise to check for the message
const orderWatcherUpdateMsg = await clientOnMessagePromise;
const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data);
expect(orderWatcherUpdateData.method).to.be.eq('UPDATE');
const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid;
expect(invalidOrderState.isValid).to.be.false();
expect(invalidOrderState.orderHash).to.be.eq(orderHash);
expect(invalidOrderState.error).to.be.eq(ExchangeContractErrs.InsufficientMakerAllowance);
});
it('broadcasts to multiple clients when an order backing ZRX allowance changes', async () => {
// Prepare order
const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals);
const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals);
const nonZeroMakerFeeSignedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
makerAssetData,
takerAssetData,
makerFee,
takerFee,
makerAddress,
takerAddress,
fillableAmount,
takerAddress,
);
const nonZeroMakerFeeOrderPayload = {
id: 1,
jsonrpc: '2.0',
method: 'ADD_ORDER',
signedOrder: nonZeroMakerFeeSignedOrder,
};
// Set up a second client and have it add the order
wsClientTwo = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/');
logUtils.log(`${new Date()} [Client] Connected.`);
wsClientTwo.onopen = () => wsClientTwo.send(JSON.stringify(nonZeroMakerFeeOrderPayload));
// Setup the onMessage callbacks, but don't await them yet
const clientOneOnMessagePromise = onMessageAsync(wsClient, OrderWatcherMethod.Update);
const clientTwoOnMessagePromise = onMessageAsync(wsClientTwo, OrderWatcherMethod.Update);
// Change the allowance
await contractWrappers.erc20Token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress, new BigNumber(0));
// Check that both clients receive the emitted event by awaiting the onMessageAsync promises
let updateMsg = await clientOneOnMessagePromise;
let updateData = JSON.parse(updateMsg.data);
let orderState = updateData.result as OrderStateValid;
expect(orderState.isValid).to.be.true();
expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0');
updateMsg = await clientTwoOnMessagePromise;
updateData = JSON.parse(updateMsg.data);
orderState = updateData.result as OrderStateValid;
expect(orderState.isValid).to.be.true();
expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0');
wsClientTwo.close();
logUtils.log(`${new Date()} [Client] Closed.`);
});
});
// HACK: createFillableSignedOrderAsync is Promise-based, which forces us
// to use Promises instead of the done() callbacks for tests.
// onmessage callback must thus be wrapped as a Promise.
async function onMessageAsync(client: WebSocket.w3cwebsocket, method: string | null): Promise<WsMessage> {
return new Promise<WsMessage>(resolve => {
client.onmessage = (msg: WsMessage) => {
const data = JSON.parse(msg.data);
if (data.method === method) {
resolve(msg);
}
};
});
}

View File

@@ -243,6 +243,10 @@ export enum RevertReason {
AuctionNotStarted = 'AUCTION_NOT_STARTED',
AuctionInvalidBeginTime = 'INVALID_BEGIN_TIME',
InvalidAssetData = 'INVALID_ASSET_DATA',
// Balance Threshold Filter
InvalidOrBlockedExchangeSelector = 'INVALID_OR_BLOCKED_EXCHANGE_SELECTOR',
BalanceQueryFailed = 'BALANCE_QUERY_FAILED',
AtLeastOneAddressDoesNotMeetBalanceThreshold = 'AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD',
}
export enum StatusCodes {

View File

@@ -24,7 +24,7 @@ export const SidebarHeader: React.StatelessComponent<SidebarHeaderProps> = ({
return (
<Container>
<Container className="flex justify-bottom">
<Container className="left pl1" width="150px">
<Container className="col col-7 pl1">
<Text
fontColor={colors.lightLinkBlue}
fontSize={screenWidth === ScreenWidths.Sm ? '20px' : '22px'}
@@ -37,12 +37,14 @@ export const SidebarHeader: React.StatelessComponent<SidebarHeaderProps> = ({
{!_.isUndefined(docsVersion) &&
!_.isUndefined(availableDocVersions) &&
!_.isUndefined(onVersionSelected) && (
<div className="right" style={{ alignSelf: 'flex-end', paddingBottom: 4 }}>
<VersionDropDown
selectedVersion={docsVersion}
versions={availableDocVersions}
onVersionSelected={onVersionSelected}
/>
<div className="col col-5 pl1" style={{ alignSelf: 'flex-end', paddingBottom: 4 }}>
<Container className="right">
<VersionDropDown
selectedVersion={docsVersion}
versions={availableDocVersions}
onVersionSelected={onVersionSelected}
/>
</Container>
</div>
)}
</Container>

View File

@@ -24,7 +24,7 @@ const docsInfoConfig: DocsInfoConfig = {
id: DocPackages.OrderWatcher,
packageName: '@0x/order-watcher',
type: SupportedDocJson.TypeDoc,
displayName: 'OrderWatcher',
displayName: 'Order Watcher',
packageUrl: 'https://github.com/0xProject/0x-monorepo',
markdownMenu: {
'getting-started': [markdownSections.introduction, markdownSections.installation],