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:
2
.github/stale.yml
vendored
2
.github/stale.yml
vendored
@@ -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
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "1.1.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Added Balance Threshold Filter",
|
||||
"pr": 1383
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1544741676,
|
||||
"version": "1.0.2",
|
||||
|
||||
@@ -18,5 +18,5 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contracts": ["DutchAuction", "Forwarder"]
|
||||
"contracts": ["BalanceThresholdFilter", "DutchAuction", "Forwarder"]
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from '../../generated-wrappers/balance_threshold_filter';
|
||||
export * from '../../generated-wrappers/dutch_auction';
|
||||
export * from '../../generated-wrappers/forwarder';
|
||||
|
||||
1644
contracts/extensions/test/extensions/balance_threshold_filter.ts
Normal file
1644
contracts/extensions/test/extensions/balance_threshold_filter.ts
Normal file
File diff suppressed because it is too large
Load Diff
283
contracts/extensions/test/utils/balance_threshold_wrapper.ts
Normal file
283
contracts/extensions/test/utils/balance_threshold_wrapper.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
84
contracts/libs/contracts/libs/LibAddressArray.sol
Normal file
84
contracts/libs/contracts/libs/LibAddressArray.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
152
contracts/libs/contracts/libs/LibExchangeSelectors.sol
Normal file
152
contracts/libs/contracts/libs/LibExchangeSelectors.sol
Normal 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)"));
|
||||
}
|
||||
2
contracts/multisig/src/index.ts
Normal file
2
contracts/multisig/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './artifacts';
|
||||
export * from './wrappers';
|
||||
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "2.2.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Added LibAddressArray",
|
||||
"pr": 1383
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1544741676,
|
||||
"version": "2.1.59",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -104,6 +104,7 @@ export enum ContractName {
|
||||
Authorizable = 'Authorizable',
|
||||
Whitelist = 'Whitelist',
|
||||
Forwarder = 'Forwarder',
|
||||
BalanceThresholdFilter = 'BalanceThresholdFilter',
|
||||
}
|
||||
|
||||
export interface SignedTransaction {
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"id": "/orderWatcherWebSocketUtf8MessageSchema",
|
||||
"properties": {
|
||||
"utf8Data": { "type": "string" }
|
||||
},
|
||||
"required": [
|
||||
"utf8Data"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user