aboutsummaryrefslogtreecommitdiffstats
path: root/contracts/extensions
diff options
context:
space:
mode:
authorGreg Hysen <greg.hysen@gmail.com>2018-12-12 06:47:52 +0800
committerGreg Hysen <greg.hysen@gmail.com>2018-12-19 05:36:05 +0800
commit4417c76b130655da3b14209148191d43037e8f8a (patch)
tree1cb3cf7ea3104d2dd68d9f5f48c1183e037ad244 /contracts/extensions
parent8799f9bb90c6bcfffc74550d74ed87422467f9e1 (diff)
downloaddexon-sol-tools-4417c76b130655da3b14209148191d43037e8f8a.tar
dexon-sol-tools-4417c76b130655da3b14209148191d43037e8f8a.tar.gz
dexon-sol-tools-4417c76b130655da3b14209148191d43037e8f8a.tar.bz2
dexon-sol-tools-4417c76b130655da3b14209148191d43037e8f8a.tar.lz
dexon-sol-tools-4417c76b130655da3b14209148191d43037e8f8a.tar.xz
dexon-sol-tools-4417c76b130655da3b14209148191d43037e8f8a.tar.zst
dexon-sol-tools-4417c76b130655da3b14209148191d43037e8f8a.zip
Ported Balance Threshold Filter to new contract directory structure
Diffstat (limited to 'contracts/extensions')
-rw-r--r--contracts/extensions/compiler.json2
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol44
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol319
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol30
-rw-r--r--contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol81
-rw-r--r--contracts/extensions/package.json2
-rw-r--r--contracts/extensions/src/artifacts/index.ts2
-rw-r--r--contracts/extensions/src/wrappers/index.ts1
-rw-r--r--contracts/extensions/test/extensions/balance_threshold_filter.ts1530
-rw-r--r--contracts/extensions/test/utils/balance_threshold_wrapper.ts246
-rw-r--r--contracts/extensions/tsconfig.json2
11 files changed, 2256 insertions, 3 deletions
diff --git a/contracts/extensions/compiler.json b/contracts/extensions/compiler.json
index 69d607b3e..e6ed0c215 100644
--- a/contracts/extensions/compiler.json
+++ b/contracts/extensions/compiler.json
@@ -18,5 +18,5 @@
}
}
},
- "contracts": ["DutchAuction", "Forwarder"]
+ "contracts": ["BalanceThresholdFilter", "DutchAuction", "Forwarder"]
}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol b/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol
new file mode 100644
index 000000000..a68f6805d
--- /dev/null
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/BalanceThresholdFilter.sol
@@ -0,0 +1,44 @@
+/*
+
+ 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;
+pragma experimental ABIEncoderV2;
+
+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 thresholdBalance The minimum balance of `thresholdAsset` that must be held by makers/takers.
+ constructor(
+ address exchange,
+ address thresholdAsset,
+ uint256 thresholdBalance
+ )
+ public
+ {
+ EXCHANGE = IExchange(exchange);
+ THRESHOLD_ASSET = IThresholdAsset(thresholdAsset);
+ THRESHOLD_BALANCE = thresholdBalance;
+ }
+}
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol
new file mode 100644
index 000000000..0ad8ccddf
--- /dev/null
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol
@@ -0,0 +1,319 @@
+/*
+
+ 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;
+pragma experimental ABIEncoderV2;
+
+import "./mixins/MBalanceThresholdFilterCore.sol";
+
+
+contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore {
+
+ /// @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
+ {
+ // Validate addresses.
+ validateBalanceThresholdsOrRevert();
+
+ // All addresses are valid. Execute fillOrder.
+ EXCHANGE.executeTransaction(
+ salt,
+ signerAddress,
+ signedExchangeTransaction,
+ signature
+ );
+ }
+
+ /// @dev Validates addresses meet the balance threshold specified by `BALANCE_THRESHOLD`
+ /// for the asset `THRESHOLD_ASSET`. If one address does not meet the thresold
+ /// then this function will revert. Which addresses are validated depends on
+ /// which Exchange function is to be called (defined by `signedExchangeTransaction` above).
+ /// No parameters are taken as this function reads arguments directly from calldata, to save gas.
+ /// If all addresses are valid then this function emits a ValidatedAddresses event, listing all
+ /// of the addresses whose balance thresholds it checked.
+ function validateBalanceThresholdsOrRevert()
+ internal
+ {
+ // Addresses that are validated below.
+ address[] memory validatedAddresses;
+
+ ///// Do not add variables after this point. /////
+ ///// The assembly block may overwrite their values. /////
+
+ // Validate addresses
+ assembly {
+ /// @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(offset) -> value {
+ // 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)
+ }
+
+ /// @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(offset) -> value {
+ value := exchangeCalldataload(add(offset, 0x4))
+ }
+
+ /// @dev A running list is maintained of addresses to validate.
+ /// This function records an address in this array.
+ /// @param addressToValidate - Address to record for validation.
+ function recordAddressToValidate(addressToValidate) {
+ // Compute `addressesToValidate` memory offset
+ let addressesToValidate_ := mload(0x40)
+ let nAddressesToValidate_ := mload(addressesToValidate_)
+
+ // Increment length
+ nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01)
+ mstore(addressesToValidate_, nAddressesToValidate_)
+
+ // Append address to validate
+ let offset := mul(nAddressesToValidate_, 0x20)
+ mstore(add(addressesToValidate_, offset), addressToValidate)
+ }
+
+ /// @dev Extracts the maker address from an order stored in the Exchange calldata
+ /// (which is embedded in `signedExchangeTransaction`), and records it in
+ /// the running list of addresses to validate.
+ /// @param orderParamIndex - Index of the order in the Exchange function's signature
+ function recordMakerAddressFromOrder(orderParamIndex) {
+ let orderPtr := loadExchangeData(mul(orderParamIndex, 0x20))
+ let makerAddress := loadExchangeData(orderPtr)
+ recordAddressToValidate(makerAddress)
+ }
+
+ /// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata
+ /// (which is embedded in `signedExchangeTransaction`), and records them in
+ /// the running list of addresses to validate.
+ /// @param orderArrayParamIndex - Index of the order array in the Exchange function's signature
+ function recordMakerAddressesFromOrderArray(orderArrayParamIndex) {
+ let orderArrayPtr := loadExchangeData(mul(orderArrayParamIndex, 0x20))
+ let orderArrayLength := loadExchangeData(orderArrayPtr)
+ let orderArrayElementPtr := add(orderArrayPtr, 0x20)
+ let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20))
+ for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} {
+ let orderPtr := loadExchangeData(orderPtrOffset)
+ let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr))
+ recordAddressToValidate(makerAddress)
+ }
+ }
+
+ /// @dev Records address of signer in the running list of addresses to validate.
+ /// @note: We cannot access `signerAddress` directly from within the asm function,
+ /// so it is loaded from the calldata.
+ function recordSignerAddress() {
+ // Load the signer address from calldata
+ // 0x04 for selector
+ // 0x20 to access `signerAddress`, which is the second parameter.
+ let signerAddress_ := calldataload(0x24)
+ recordAddressToValidate(signerAddress_)
+ }
+
+ /// @dev Records addresses to be validated when Exchange transaction is a batch fill variant.
+ /// This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow
+ /// Reference signature<T>: <batchFillVariant>(Order[],uint256[],bytes[])
+ function recordAddressesForBatchFillVariant() {
+ // Record maker addresses from order array (parameter index 0)
+ // The signer is the taker for these orders and must also be validated.
+ recordMakerAddressesFromOrderArray(0)
+ recordSignerAddress()
+ }
+
+ /// @dev Records addresses to be validated when Exchange transaction is a fill order variant.
+ /// This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow
+ /// Reference signature<T>: <fillOrderVariant>(Order,uint256,bytes)
+ function recordAddressesForFillOrderVariant() {
+ // Record maker address from the order (param index 0)
+ // The signer is the taker for this order and must also be validated.
+ recordMakerAddressFromOrder(0)
+ recordSignerAddress()
+ }
+
+ /// @dev Records addresses to be validated when Exchange transaction is a market fill variant.
+ /// This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow
+ /// Reference signature<T>: <marketFillInvariant>(Order[],uint256,bytes[])
+ function recordAddressesForMarketFillVariant() {
+ // Record maker addresses from order array (parameter index 0)
+ // The signer is the taker for these orders and must also be validated.
+ recordMakerAddressesFromOrderArray(0)
+ recordSignerAddress()
+ }
+
+ /// @dev Records addresses to be validated when Exchange transaction is matchOrders.
+ /// Reference signature: matchOrders(Order,Order)
+ function recordAddressesForMatchOrders() {
+ // Record maker address from both orders (param indices 0 & 1).
+ // The signer is the taker and must also be validated.
+ recordMakerAddressFromOrder(0)
+ recordMakerAddressFromOrder(1)
+ recordSignerAddress()
+ }
+
+ ///// Record Addresses to Validate /////
+
+ // Addresses needing validation depends on which Exchange function is being called.
+ // Step 1/2 Read the exchange function selector.
+ let exchangeFunctionSelector := and(
+ exchangeCalldataload(0x0),
+ 0xffffffff00000000000000000000000000000000000000000000000000000000
+ )
+
+ // Step 2/2 Extract addresses to validate based on this selector.
+ // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors
+ switch exchangeFunctionSelector
+ case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders
+ case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow
+ case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders
+ case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder
+ case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow
+ case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder
+ case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders
+ case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow
+ case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders
+ case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow
+ case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders
+ case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder
+ case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders
+ case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo
+ default {
+ // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")`
+ mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
+ mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000)
+ mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f)
+ mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000)
+ mstore(0x80, 0x00000000)
+ // Revert length calculation:
+ // 4 -- error selector
+ // 32 -- offset to string
+ // 32 -- string length field
+ // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word.
+ revert(0, 132)
+ }
+
+ ///// Validate Recorded Addresses /////
+
+ // Load from memory the addresses to validate
+ let addressesToValidate := mload(0x40)
+ let addressesToValidateLength := mload(addressesToValidate)
+ let addressesToValidateElementPtr := add(addressesToValidate, 0x20)
+ let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20))
+
+ // Set free memory pointer to after `addressesToValidate` array.
+ // This is to avoid corruption when making calls in the loop below.
+ let freeMemPtr := addressesToValidateElementEndPtr
+ mstore(0x40, freeMemPtr)
+
+ // Validate addresses
+ let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot)
+ let thresholdBalance := sload(THRESHOLD_BALANCE_slot)
+ for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} {
+ // Construct calldata for `THRESHOLD_ASSET.balanceOf`
+ mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000)
+ mstore(add(freeMemPtr, 0x04), mload(addressToValidate))
+
+ // call `THRESHOLD_ASSET.balanceOf`
+ let success := call(
+ gas, // forward all gas
+ thresholdAssetAddress, // call address of asset proxy
+ 0, // don't send any ETH
+ freeMemPtr, // pointer to start of input
+ 0x24, // length of input (one padded address)
+ freeMemPtr, // write output to next free memory offset
+ 0x20 // reserve space for return balance (0x20 bytes)
+ )
+ if eq(success, 0) {
+ // @TODO Revert with `Error("BALANCE_QUERY_FAILED")`
+ mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
+ mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000)
+ mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000)
+ mstore(0x60, 0x00000000)
+ // Revert length calculation:
+ // 4 -- error selector
+ // 32 -- offset to string
+ // 32 -- string length field
+ // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word.
+ revert(0, 100)
+ }
+
+ // Revert if balance not held
+ let addressBalance := mload(freeMemPtr)
+ if lt(addressBalance, thresholdBalance) {
+ // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")`
+ mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
+ mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000)
+ mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f)
+ mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000)
+ mstore(0x80, 0x00000000)
+ // Revert length calculation:
+ // 4 -- error selector
+ // 32 -- offset to string
+ // 32 -- string length field
+ // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word.
+ revert(0, 132)
+ }
+ }
+
+ // Record validated addresses
+ validatedAddresses := addressesToValidate
+ }
+
+ ///// If we hit this point then all addresses are valid /////
+ emit ValidatedAddresses(validatedAddresses);
+ }
+} \ No newline at end of file
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol
new file mode 100644
index 000000000..61acaba0a
--- /dev/null
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/interfaces/IThresholdAsset.sol
@@ -0,0 +1,30 @@
+/*
+
+ 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);
+
+} \ No newline at end of file
diff --git a/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol
new file mode 100644
index 000000000..ff0042f58
--- /dev/null
+++ b/contracts/extensions/contracts/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol
@@ -0,0 +1,81 @@
+/*
+
+ 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;
+pragma experimental ABIEncoderV2;
+
+import "@0x/contracts-interfaces/contracts/protocol/Exchange/IExchange.sol";
+import "../interfaces/IThresholdAsset.sol";
+
+
+contract MBalanceThresholdFilterCore {
+
+ // Points to 0x exchange contract
+ 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 THRESHOLD_BALANCE;
+
+ // Addresses that hold at least `THRESHOLD_BALANCE` of `THRESHOLD_ASSET`
+ event ValidatedAddresses (
+ address[] addresses
+ );
+
+ /// @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;
+
+ /// @dev Validates addresses meet the balance threshold specified by `BALANCE_THRESHOLD`
+ /// for the asset `THRESHOLD_ASSET`. If one address does not meet the thresold
+ /// then this function will revert. Which addresses are validated depends on
+ /// which Exchange function is to be called (defined by `signedExchangeTransaction` above).
+ /// No parameters are taken as this function reads arguments directly from calldata, to save gas.
+ /// If all addresses are valid then this function emits a ValidatedAddresses event, listing all
+ /// of the addresses whose balance thresholds it checked.
+ function validateBalanceThresholdsOrRevert() internal;
+} \ No newline at end of file
diff --git a/contracts/extensions/package.json b/contracts/extensions/package.json
index 938e1138c..2d9ed4dcd 100644
--- a/contracts/extensions/package.json
+++ b/contracts/extensions/package.json
@@ -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",
diff --git a/contracts/extensions/src/artifacts/index.ts b/contracts/extensions/src/artifacts/index.ts
index 7588178f0..ebf0b8050 100644
--- a/contracts/extensions/src/artifacts/index.ts
+++ b/contracts/extensions/src/artifacts/index.ts
@@ -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,
};
diff --git a/contracts/extensions/src/wrappers/index.ts b/contracts/extensions/src/wrappers/index.ts
index 90880e37f..8a8122caa 100644
--- a/contracts/extensions/src/wrappers/index.ts
+++ b/contracts/extensions/src/wrappers/index.ts
@@ -1,2 +1,3 @@
+export * from '../../generated-wrappers/balance_threshold_filter';
export * from '../../generated-wrappers/dutch_auction';
export * from '../../generated-wrappers/forwarder';
diff --git a/contracts/extensions/test/extensions/balance_threshold_filter.ts b/contracts/extensions/test/extensions/balance_threshold_filter.ts
new file mode 100644
index 000000000..34339564c
--- /dev/null
+++ b/contracts/extensions/test/extensions/balance_threshold_filter.ts
@@ -0,0 +1,1530 @@
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { assetDataUtils } from '@0x/order-utils';
+import { RevertReason, SignedOrder, Order } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import * as chai from 'chai';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
+
+import {
+ artifacts as protocolArtifacts,
+ ERC20Wrapper,
+ ERC721Wrapper,
+ ExchangeContract,
+ ExchangeWrapper,
+ TestExchangeInternalsContract,
+} from '@0x/contracts-protocol';
+import {
+ chaiSetup,
+ constants,
+ ContractName,
+ ERC20BalancesByOwner,
+ expectTransactionFailedAsync,
+ expectTransactionFailedWithoutReasonAsync,
+ getLatestBlockTimestampAsync,
+ LogDecoder,
+ OrderFactory,
+ OrderStatus,
+ orderUtils,
+ provider,
+ SignedTransaction,
+ txDefaults,
+ TransactionFactory,
+ web3Wrapper,
+} from '@0x/contracts-test-utils';
+import {
+ artifacts as tokensArtifacts,
+ DummyERC20TokenContract,
+ DummyERC721TokenContract,
+ WETH9Contract,
+} from '@0x/contracts-tokens';
+
+import { artifacts } from '../../src/artifacts';
+import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter';
+import { BalanceThresholdWrapper } from '../utils/balance_threshold_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+const DECIMALS_DEFAULT = 18;
+
+interface ValidatedAddressesLog {
+ args: {addresses: string[]}
+}
+
+describe.only(ContractName.BalanceThresholdFilter, () => {
+ let compliantMakerAddress: string;
+ let compliantMakerAddress2: string;
+ let owner: string;
+ let compliantTakerAddress: string;
+ let feeRecipientAddress: string;
+ let nonCompliantAddress: string;
+ let defaultMakerAssetAddress: string;
+ let defaultTakerAssetAddress: string;
+ let zrxAssetData: string;
+ let zrxToken: DummyERC20TokenContract;
+ let exchangeInstance: ExchangeContract;
+ let exchangeWrapper: ExchangeWrapper;
+
+ let orderFactory: OrderFactory;
+ let orderFactory2: OrderFactory;
+ let nonCompliantOrderFactory: OrderFactory;
+ let erc20Wrapper: ERC20Wrapper;
+ let erc20Balances: ERC20BalancesByOwner;
+ let erc20TakerBalanceThresholdWrapper: BalanceThresholdWrapper;
+ let erc721TakerBalanceThresholdWrapper: BalanceThresholdWrapper;
+ let erc721MakerBalanceThresholdWrapper: BalanceThresholdWrapper;
+ let erc721NonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper;
+
+ let takerTransactionFactory: TransactionFactory;
+ let makerTransactionFactory: TransactionFactory;
+ let compliantSignedOrder: SignedOrder;
+ let compliantSignedOrder2: SignedOrder;
+ let compliantSignedFillOrderTx: SignedTransaction;
+
+ let logDecoder: LogDecoder;
+ let exchangeInternals: TestExchangeInternalsContract;
+
+ let defaultOrderParams: Partial<Order>;
+
+ const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT);
+ const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT);
+ const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT);
+
+ let erc721CompliantForwarderInstance: BalanceThresholdFilterContract;
+ let erc20CompliantForwarderInstance: BalanceThresholdFilterContract;
+
+ const assertValidatedAddressesLog = async (txReceipt: TransactionReceiptWithDecodedLogs, expectedValidatedAddresses: string[]) => {
+ expect(txReceipt.logs.length).to.be.gte(1);
+ const validatedAddressesLog = (txReceipt.logs[0] as any) as ValidatedAddressesLog;
+ const validatedAddresses = validatedAddressesLog.args.addresses;
+ // @HACK-hysz: Nested addresses are not translated to lower-case but this will change once
+ // the new ABI Encoder/Decoder is used by the contract templates.
+ let validatedAddressesNormalized: string[] = [];
+ _.each(validatedAddresses, (address) => {
+ const normalizedAddress = _.toLower(address);
+ validatedAddressesNormalized.push(normalizedAddress);
+ });
+ expect(validatedAddressesNormalized).to.be.deep.equal(expectedValidatedAddresses);
+ };
+
+ before(async () => {
+ // Create accounts
+ await blockchainLifecycle.startAsync();
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ const usedAddresses = ([
+ owner,
+ compliantMakerAddress,
+ compliantMakerAddress2,
+ compliantTakerAddress,
+ feeRecipientAddress,
+ nonCompliantAddress,
+ ] = accounts);
+ // Create wrappers
+ erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
+ let compliantAddresses = _.cloneDeepWith(usedAddresses);
+ _.remove(compliantAddresses, (address: string) => {
+ return address === nonCompliantAddress;
+ });
+ const erc721Wrapper = new ERC721Wrapper(provider, compliantAddresses, owner);
+ // Deploy ERC20 tokens
+ const numDummyErc20ToDeploy = 4;
+ let erc20TokenA: DummyERC20TokenContract;
+ let erc20TokenB: DummyERC20TokenContract;
+ let erc20BalanceThresholdAsset: DummyERC20TokenContract;
+ [erc20TokenA, erc20TokenB, zrxToken, erc20BalanceThresholdAsset] = await erc20Wrapper.deployDummyTokensAsync(
+ numDummyErc20ToDeploy,
+ constants.DUMMY_TOKEN_DECIMALS,
+ );
+ defaultMakerAssetAddress = erc20TokenA.address;
+ defaultTakerAssetAddress = erc20TokenB.address;
+ zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
+ // Create proxies
+ const erc20Proxy = await erc20Wrapper.deployProxyAsync();
+ await erc20Wrapper.setBalancesAndAllowancesAsync();
+ // Deploy Exchange congtract
+ exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync(
+ protocolArtifacts.Exchange,
+ provider,
+ txDefaults,
+ zrxAssetData,
+ );
+ exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider);
+ // Register proxies
+ await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner);
+ await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, {
+ from: owner,
+ });
+ // Deploy Compliant Forwarder
+ const erc721alanceThreshold = new BigNumber(1);
+ await erc721Wrapper.deployProxyAsync();
+ const [erc721BalanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync();
+ await erc721Wrapper.setBalancesAndAllowancesAsync();
+ erc721CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync(
+ artifacts.BalanceThresholdFilter,
+ provider,
+ txDefaults,
+ exchangeInstance.address,
+ erc721BalanceThresholdAsset.address,
+ erc721alanceThreshold
+ );
+ const erc20BalanceThreshold = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 10);
+ erc20CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync(
+ artifacts.BalanceThresholdFilter,
+ provider,
+ txDefaults,
+ exchangeInstance.address,
+ erc20BalanceThresholdAsset.address,
+ erc20BalanceThreshold
+ );
+
+ // Default order parameters
+ defaultOrderParams = {
+ exchangeAddress: exchangeInstance.address,
+ feeRecipientAddress,
+ makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
+ takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress),
+ makerAssetAmount,
+ takerAssetAmount,
+ makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT),
+ takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT),
+ senderAddress: erc721CompliantForwarderInstance.address,
+ };
+ const defaultOrderParams1 = {
+ makerAddress: compliantMakerAddress,
+ ...
+ defaultOrderParams,
+ }
+ const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)];
+ takerTransactionFactory = new TransactionFactory(makerPrivateKey, exchangeInstance.address);
+ orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams1);
+ const defaultOrderParams2 = {
+ makerAddress: compliantMakerAddress2,
+ ...
+ defaultOrderParams,
+ }
+ const secondMakerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress2)];
+ orderFactory2 = new OrderFactory(secondMakerPrivateKey, defaultOrderParams2);
+
+ const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)];
+ const defaultNonCompliantOrderParams = {
+ makerAddress: nonCompliantAddress,
+ ...
+ defaultOrderParams,
+ };
+ nonCompliantOrderFactory = new OrderFactory(nonCompliantPrivateKey, defaultNonCompliantOrderParams);
+ // Create Valid/Invalid orders
+ const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)];
+ takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address);
+ compliantSignedOrder = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721CompliantForwarderInstance.address,
+ });
+ const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(
+ compliantSignedOrder,
+ );
+ const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData(
+ compliantSignedOrderWithoutExchangeAddress,
+ takerAssetFillAmount,
+ compliantSignedOrder.signature,
+ );
+ compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction(
+ compliantSignedOrderWithoutExchangeAddressData,
+ );
+
+ logDecoder = new LogDecoder(web3Wrapper, {... artifacts, ... protocolArtifacts, ... tokensArtifacts});
+ erc20TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc20CompliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider);
+ erc721TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider);
+ erc721MakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(makerPrivateKey, exchangeInstance.address), provider);
+ erc721NonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider);
+
+ // Instantiate internal exchange contract
+ exchangeInternals = await TestExchangeInternalsContract.deployFrom0xArtifactAsync(
+ protocolArtifacts.TestExchangeInternals,
+ provider,
+ txDefaults,
+ );
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ describe('General Sanity Checks', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ compliantSignedOrder = await orderFactory.newSignedOrderAsync();
+ compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both maker/taker when both maker and taker exceed the balance threshold of an ERC20 token', async () => {
+ const compliantSignedOrderERC20Sender = await orderFactory.newSignedOrderAsync({
+ ...
+ defaultOrderParams,
+ makerAddress: compliantMakerAddress,
+ senderAddress: erc20TakerBalanceThresholdWrapper.getBalanceThresholdAddress(),
+ });
+ // Execute a valid fill
+ const txReceipt = await erc20TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrderERC20Sender, compliantTakerAddress, {takerAssetFillAmount});
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(compliantSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount);
+ const makerFeePaid = compliantSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount);
+ const takerFeePaid = compliantSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount);
+ expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount),
+ );
+ expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
+ );
+ });
+ it('should revert if the signed transaction is not intended for supported', async () => {
+ // Create signed order without the fillOrder function selector
+ const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data);
+ const selectorLengthInBytes = 4;
+ const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes);
+ const badSelector = '0x00000000';
+ const badSelectorBuf = ethUtil.toBuffer(badSelector);
+ const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]);
+ const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector);
+ // Call compliant forwarder
+ return expectTransactionFailedWithoutReasonAsync(erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync(
+ compliantSignedFillOrderTx.salt,
+ compliantSignedFillOrderTx.signerAddress,
+ txDataBufWithBadSelectorHex,
+ compliantSignedFillOrderTx.signature,
+ ));
+ });
+ it('should revert if senderAddress is not set to the compliant forwarding contract', async () => {
+ // Create signed order with incorrect senderAddress
+ const notBalanceThresholdFilterAddress = zrxToken.address;
+ const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: notBalanceThresholdFilterAddress,
+ });
+ const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(
+ signedOrderWithBadSenderAddress,
+ );
+ const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData(
+ signedOrderWithoutExchangeAddress,
+ takerAssetFillAmount,
+ compliantSignedOrder.signature,
+ );
+ const signedFillOrderTx = takerTransactionFactory.newSignedTransaction(
+ signedOrderWithoutExchangeAddressData,
+ );
+ // Call compliant forwarder
+ return expectTransactionFailedWithoutReasonAsync(erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync(
+ signedFillOrderTx.salt,
+ signedFillOrderTx.signerAddress,
+ signedFillOrderTx.data,
+ signedFillOrderTx.signature,
+ ));
+ });
+ // @TODO - greater than 1 balance
+ });
+
+ describe('batchFillOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ compliantSignedOrder = await orderFactory.newSignedOrderAsync();
+ compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const orders = [compliantSignedOrder, compliantSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts});
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2);
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(compliantSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount);
+ const makerFeePaid = compliantSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount);
+ const takerFeePaid = compliantSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount)
+ .times(2);
+ // Maker #1
+ expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ // Maker #2
+ expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid),
+ );
+ // Taker
+ expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+
+ expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)),
+ );
+ expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-compliant maker address
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: nonCompliantAddress
+ });
+ const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync(
+ orders,
+ compliantTakerAddress,
+ {takerAssetFillAmounts}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [compliantSignedOrder, compliantSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ return expectTransactionFailedAsync(
+ erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersAsync(
+ orders,
+ nonCompliantAddress,
+ {takerAssetFillAmounts}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ });
+
+ describe('batchFillOrdersNoThrow', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ compliantSignedOrder = await orderFactory.newSignedOrderAsync();
+ compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const orders = [compliantSignedOrder, compliantSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts});
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2);
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(compliantSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount);
+ const makerFeePaid = compliantSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount);
+ const takerFeePaid = compliantSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount)
+ .times(2);
+ // Maker #1
+ expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ // Maker #2
+ expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid),
+ );
+ // Taker
+ expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+
+ expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)),
+ );
+ expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-compliant maker address
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: nonCompliantAddress
+ });
+ const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(
+ orders,
+ compliantTakerAddress,
+ {takerAssetFillAmounts}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [compliantSignedOrder, compliantSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ return expectTransactionFailedAsync(
+ erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(
+ orders,
+ nonCompliantAddress,
+ {takerAssetFillAmounts}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ });
+
+ describe('batchFillOrKillOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ compliantSignedOrder = await orderFactory.newSignedOrderAsync();
+ compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const orders = [compliantSignedOrder, compliantSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts});
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2);
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(compliantSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount);
+ const makerFeePaid = compliantSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount);
+ const takerFeePaid = compliantSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount)
+ .times(2);
+ // Maker #1
+ expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ // Maker #2
+ expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid),
+ );
+ // Taker
+ expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+
+ expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)),
+ );
+ expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-compliant maker address
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: nonCompliantAddress
+ });
+ const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(
+ orders,
+ compliantTakerAddress,
+ {takerAssetFillAmounts}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [compliantSignedOrder, compliantSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ return expectTransactionFailedAsync(
+ erc721NonCompliantBalanceThresholdWrapper.batchFillOrKillOrdersAsync(
+ orders,
+ nonCompliantAddress,
+ {takerAssetFillAmounts}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ it('should revert if one takerAssetFillAmount is not fully filled', async () => {
+ const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2);
+ const orders = [compliantSignedOrder, compliantSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount];
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(
+ orders,
+ compliantTakerAddress,
+ {takerAssetFillAmounts}
+ ),
+ RevertReason.FailedExecution
+ );
+ });
+ });
+
+ describe('fillOrder', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ compliantSignedOrder = await orderFactory.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount});
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(compliantSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount);
+ const makerFeePaid = compliantSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount);
+ const takerFeePaid = compliantSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount);
+ expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount),
+ );
+ expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
+ );
+ });
+ it('should revert if maker does not meet the balance threshold', async () => {
+ // Create signed order with non-compliant maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721CompliantForwarderInstance.address,
+ makerAddress: nonCompliantAddress
+ });
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.fillOrderAsync(
+ signedOrderWithBadMakerAddress,
+ compliantTakerAddress,
+ {takerAssetFillAmount}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ return expectTransactionFailedAsync(
+ erc721NonCompliantBalanceThresholdWrapper.fillOrderAsync(
+ compliantSignedOrder,
+ nonCompliantAddress,
+ {takerAssetFillAmount}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ });
+
+ describe('fillOrderNoThrow', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ compliantSignedOrder = await orderFactory.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount});
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(compliantSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount);
+ const makerFeePaid = compliantSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount);
+ const takerFeePaid = compliantSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount);
+ expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount),
+ );
+ expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
+ );
+ });
+ it('should revert if maker does not meet the balance threshold', async () => {
+ // Create signed order with non-compliant maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721CompliantForwarderInstance.address,
+ makerAddress: nonCompliantAddress
+ });
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync(
+ signedOrderWithBadMakerAddress,
+ compliantTakerAddress,
+ {takerAssetFillAmount}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ return expectTransactionFailedAsync(
+ erc721NonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync(
+ compliantSignedOrder,
+ nonCompliantAddress,
+ {takerAssetFillAmount}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ });
+
+ describe('fillOrKillOrder', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ compliantSignedOrder = await orderFactory.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount;
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_});
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount = takerAssetFillAmount_
+ .times(compliantSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount);
+ const makerFeePaid = compliantSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount);
+ const takerFeePaid = compliantSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount);
+ expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_),
+ );
+ expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_),
+ );
+ expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount),
+ );
+ expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)),
+ );
+ });
+ it('should revert if maker does not meet the balance threshold', async () => {
+ // Create signed order with non-compliant maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721CompliantForwarderInstance.address,
+ makerAddress: nonCompliantAddress
+ });
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(
+ signedOrderWithBadMakerAddress,
+ compliantTakerAddress,
+ {takerAssetFillAmount}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ return expectTransactionFailedAsync(
+ erc721NonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync(
+ compliantSignedOrder,
+ nonCompliantAddress,
+ {takerAssetFillAmount}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ it('should revert if takerAssetFillAmount is not fully filled', async () => {
+ const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2);
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(
+ compliantSignedOrder,
+ compliantTakerAddress,
+ {takerAssetFillAmount: tooBigTakerAssetFillAmount}
+ ),
+ RevertReason.FailedExecution
+ );
+ });
+ });
+
+ describe('marketSellOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ compliantSignedOrder = await orderFactory.newSignedOrderAsync();
+ compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const orders = [compliantSignedOrder, compliantSignedOrder2];
+ const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount);
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount});
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount2 = takerAssetFillAmount
+ .times(compliantSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount);
+ const makerFeePaid2 = compliantSignedOrder2.makerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount);
+ const takerFeePaid2 = compliantSignedOrder2.takerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount);
+ const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2);
+ const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2);
+ // Maker #1
+ expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount),
+ );
+ expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount),
+ );
+ expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee),
+ );
+ // Maker #2
+ expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2),
+ );
+ expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2),
+ );
+ // Taker
+ expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+ expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount),
+ );
+ expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-compliant maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: nonCompliantAddress
+ });
+ const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync(
+ orders,
+ compliantTakerAddress,
+ {takerAssetFillAmount}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [compliantSignedOrder, compliantSignedOrder2];
+ return expectTransactionFailedAsync(
+ erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersAsync(
+ orders,
+ nonCompliantAddress,
+ {takerAssetFillAmount}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ });
+
+ describe('marketSellOrdersNoThrow', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ compliantSignedOrder = await orderFactory.newSignedOrderAsync();
+ compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const orders = [compliantSignedOrder, compliantSignedOrder2];
+ const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount);
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount});
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount2 = takerAssetFillAmount
+ .times(compliantSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount);
+ const makerFeePaid2 = compliantSignedOrder2.makerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount);
+ const takerFeePaid2 = compliantSignedOrder2.takerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount);
+ const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2);
+ const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2);
+ // Maker #1
+ expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount),
+ );
+ expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount),
+ );
+ expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee),
+ );
+ // Maker #2
+ expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2),
+ );
+ expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2),
+ );
+ // Taker
+ expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+ expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount),
+ );
+ expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-compliant maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: nonCompliantAddress
+ });
+ const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(
+ orders,
+ compliantTakerAddress,
+ {takerAssetFillAmount}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [compliantSignedOrder, compliantSignedOrder2];
+ return expectTransactionFailedAsync(
+ erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(
+ orders,
+ nonCompliantAddress,
+ {takerAssetFillAmount}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ });
+
+ describe('marketBuyOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ compliantSignedOrder = await orderFactory.newSignedOrderAsync();
+ compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const orders = [compliantSignedOrder, compliantSignedOrder2];
+ const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount);
+ const makerAssetFillAmount2 = takerAssetFillAmount
+ .times(compliantSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount);
+ const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2);
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount});
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerFeePaid2 = compliantSignedOrder2.makerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount);
+ const takerFeePaid2 = compliantSignedOrder2.takerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount);
+ const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2);
+ // Maker #1
+ expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount),
+ );
+ expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount),
+ );
+ expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee),
+ );
+ // Maker #2
+ expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2),
+ );
+ expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2),
+ );
+ // Taker
+ expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+ expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount),
+ );
+ expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-compliant maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: nonCompliantAddress
+ });
+ const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ const dummyMakerAssetFillAmount = new BigNumber(0);
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync(
+ orders,
+ compliantTakerAddress,
+ {makerAssetFillAmount: dummyMakerAssetFillAmount}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [compliantSignedOrder, compliantSignedOrder2];
+ const dummyMakerAssetFillAmount = new BigNumber(0);
+ return expectTransactionFailedAsync(
+ erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersAsync(
+ orders,
+ nonCompliantAddress,
+ {makerAssetFillAmount: dummyMakerAssetFillAmount}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ });
+
+ describe('marketBuyOrdersNoThrowAsync', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ compliantSignedOrder = await orderFactory.newSignedOrderAsync();
+ compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => {
+ // Execute a valid fill
+ const orders = [compliantSignedOrder, compliantSignedOrder2];
+ const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount);
+ const makerAssetFillAmount2 = takerAssetFillAmount
+ .times(compliantSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount);
+ const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2);
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount});
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerFeePaid2 = compliantSignedOrder2.makerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount);
+ const takerFeePaid2 = compliantSignedOrder2.takerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount);
+ const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2);
+ // Maker #1
+ expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount),
+ );
+ expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount),
+ );
+ expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee),
+ );
+ // Maker #2
+ expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2),
+ );
+ expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2),
+ );
+ // Taker
+ expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+ expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount),
+ );
+ expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-compliant maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: nonCompliantAddress
+ });
+ const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ const dummyMakerAssetFillAmount = new BigNumber(0);
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(
+ orders,
+ compliantTakerAddress,
+ {makerAssetFillAmount: dummyMakerAssetFillAmount}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [compliantSignedOrder, compliantSignedOrder2];
+ const dummyMakerAssetFillAmount = new BigNumber(0);
+ return expectTransactionFailedAsync(
+ erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(
+ orders,
+ nonCompliantAddress,
+ {makerAssetFillAmount: dummyMakerAssetFillAmount}
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ });
+
+ describe('matchOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ compliantSignedOrder = await orderFactory.newSignedOrderAsync();
+ compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('Should transfer correct amounts when both makers and taker meet the balance threshold', async () => {
+ // Test values/results taken from Match Orders test:
+ // 'Should transfer correct amounts when right order is fully filled and values pass isRoundingErrorFloor but fail isRoundingErrorCeil'
+ // Create orders to match
+ const signedOrderLeft = await orderFactory.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(17), 0),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(98), 0),
+ makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18),
+ takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18),
+ feeRecipientAddress: feeRecipientAddress,
+ });
+ const signedOrderRight = await orderFactory2.newSignedOrderAsync({
+ makerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress),
+ takerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0),
+ makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18),
+ takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18),
+ feeRecipientAddress: feeRecipientAddress,
+ });
+ // Compute expected transfer amounts
+ const expectedTransferAmounts = {
+ // Left Maker
+ amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0),
+ amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0),
+ feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), // 76.47%
+ // Right Maker
+ amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0),
+ amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0),
+ feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
+ // Taker
+ amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 0),
+ feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53%
+ feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
+ };
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress);
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [signedOrderLeft.makerAddress, signedOrderRight.makerAddress, compliantTakerAddress];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(
+ newBalances[signedOrderLeft.makerAddress][defaultMakerAssetAddress],
+ 'Checking left maker egress ERC20 account balance',
+ ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][defaultMakerAssetAddress].sub(expectedTransferAmounts.amountSoldByLeftMaker));
+ expect(
+ newBalances[signedOrderRight.makerAddress][defaultTakerAssetAddress],
+ 'Checking right maker ingress ERC20 account balance',
+ ).to.be.bignumber.equal(erc20Balances[signedOrderRight.makerAddress][defaultTakerAssetAddress].sub(expectedTransferAmounts.amountSoldByRightMaker));
+ expect(
+ newBalances[compliantTakerAddress][defaultMakerAssetAddress],
+ 'Checking taker ingress ERC20 account balance',
+ ).to.be.bignumber.equal(erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(expectedTransferAmounts.amountReceivedByTaker));
+ expect(
+ newBalances[signedOrderLeft.makerAddress][defaultTakerAssetAddress],
+ 'Checking left maker ingress ERC20 account balance',
+ ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][defaultTakerAssetAddress].add(expectedTransferAmounts.amountBoughtByLeftMaker));
+ expect(
+ newBalances[signedOrderRight.makerAddress][defaultMakerAssetAddress],
+ 'Checking right maker egress ERC20 account balance',
+ ).to.be.bignumber.equal(
+ erc20Balances[signedOrderRight.makerAddress][defaultMakerAssetAddress].add(expectedTransferAmounts.amountBoughtByRightMaker),
+ );
+ // Paid fees
+ expect(
+ newBalances[signedOrderLeft.makerAddress][zrxToken.address],
+ 'Checking left maker egress ERC20 account fees',
+ ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByLeftMaker));
+ expect(
+ newBalances[signedOrderRight.makerAddress][zrxToken.address],
+ 'Checking right maker egress ERC20 account fees',
+ ).to.be.bignumber.equal(erc20Balances[signedOrderRight.makerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByRightMaker));
+ expect(
+ newBalances[compliantTakerAddress][zrxToken.address],
+ 'Checking taker egress ERC20 account fees',
+ ).to.be.bignumber.equal(erc20Balances[compliantTakerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByTakerLeft).sub(expectedTransferAmounts.feePaidByTakerRight));
+ // Received fees
+ expect(
+ newBalances[signedOrderLeft.feeRecipientAddress][zrxToken.address],
+ 'Checking left fee recipient ingress ERC20 account fees',
+ ).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address].add(expectedTransferAmounts.feePaidByLeftMaker).add(expectedTransferAmounts.feePaidByRightMaker).add(expectedTransferAmounts.feePaidByTakerLeft).add(expectedTransferAmounts.feePaidByTakerRight),
+ );
+ });
+ it('should revert if left maker does not meet the balance threshold', async () => {
+ // Create signed order with non-compliant maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721CompliantForwarderInstance.address,
+ makerAddress: nonCompliantAddress
+ });
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.matchOrdersAsync(
+ compliantSignedOrder,
+ signedOrderWithBadMakerAddress,
+ compliantTakerAddress,
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ it('should revert if right maker does not meet the balance threshold', async () => {
+ // Create signed order with non-compliant maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721CompliantForwarderInstance.address,
+ makerAddress: nonCompliantAddress
+ });
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.matchOrdersAsync(
+ signedOrderWithBadMakerAddress,
+ compliantSignedOrder,
+ compliantTakerAddress,
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ return expectTransactionFailedAsync(
+ erc721NonCompliantBalanceThresholdWrapper.matchOrdersAsync(
+ compliantSignedOrder,
+ compliantSignedOrder,
+ nonCompliantAddress,
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold
+ );
+ });
+ });
+
+ describe('cancelOrder', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ compliantSignedOrder = await orderFactory.newSignedOrderAsync();
+ compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('Should successfully cancel order if maker meets balance threshold', async () => {
+ // Verify order is not cancelled
+ const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder)
+ expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ // Cancel
+ const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrderAsync(compliantSignedOrder, compliantSignedOrder.makerAddress);
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder)
+ expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED);
+ });
+ it('Should successfully cancel order if maker does not meet balance threshold', async () => {
+ // Create order where maker does not meet balance threshold
+ const signedOrderWithBadMakerAddress = await nonCompliantOrderFactory.newSignedOrderAsync({});
+ // Verify order is not cancelled
+ const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress)
+ expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ // Cancel
+ const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrderAsync(signedOrderWithBadMakerAddress, signedOrderWithBadMakerAddress.makerAddress);
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress)
+ expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED);
+ });
+ });
+
+ describe('batchCancelOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ });
+ it('Should successfully batch cancel orders if maker meets balance threshold', async () => {
+ // Create orders to cancel
+ const compliantSignedOrders = [
+ await orderFactory.newSignedOrderAsync(),
+ await orderFactory.newSignedOrderAsync(),
+ await orderFactory.newSignedOrderAsync(),
+ ];
+ // Verify orders are not cancelled
+ await _.each(compliantSignedOrders, async (compliantSignedOrder) => {
+ const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder)
+ return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ });
+ // Cancel
+ const txReceipt = await erc721MakerBalanceThresholdWrapper.batchCancelOrdersAsync(compliantSignedOrders, compliantSignedOrder.makerAddress);
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ await _.each(compliantSignedOrders, async (compliantSignedOrder) => {
+ const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder)
+ return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED);
+ });
+ });
+ it('Should successfully batch cancel order if maker does not meet balance threshold', async () => {
+ // Create orders to cancel
+ const nonCompliantSignedOrders = [
+ await nonCompliantOrderFactory.newSignedOrderAsync(),
+ await nonCompliantOrderFactory.newSignedOrderAsync(),
+ await nonCompliantOrderFactory.newSignedOrderAsync(),
+ ];
+ // Verify orders are not cancelled
+ await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => {
+ const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder)
+ return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ });
+ // Cancel
+ const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync(nonCompliantSignedOrders, nonCompliantAddress);
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => {
+ const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder)
+ return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED);
+ });
+ });
+ });
+
+ describe('cancelOrdersUpTo', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ });
+ it('Should successfully batch cancel orders if maker meets balance threshold', async () => {
+ // Create orders to cancel
+ const compliantSignedOrders = [
+ await orderFactory.newSignedOrderAsync({salt: new BigNumber(0)}),
+ await orderFactory.newSignedOrderAsync({salt: new BigNumber(1)}),
+ await orderFactory.newSignedOrderAsync({salt: new BigNumber(2)}),
+ ];
+ // Verify orders are not cancelled
+ await _.each(compliantSignedOrders, async (compliantSignedOrder) => {
+ const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder)
+ return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ });
+ // Cancel
+ const cancelOrdersUpToThisSalt = new BigNumber(1);
+ const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, compliantSignedOrder.makerAddress);
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ await _.each(compliantSignedOrders, async (compliantSignedOrder, salt: number) => {
+ const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder)
+ const saltAsBigNumber = new BigNumber(salt);
+ if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) {
+ return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED);
+ } else {
+ return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ }
+ });
+ });
+ it('Should successfully batch cancel order if maker does not meet balance threshold', async () => {
+ // Create orders to cancel
+ const nonCompliantSignedOrders = [
+ await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(0)}),
+ await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(1)}),
+ await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(2)}),
+ ];
+ // Verify orders are not cancelled
+ await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => {
+ const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder)
+ return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ });
+ // Cancel
+ const cancelOrdersUpToThisSalt = new BigNumber(1);
+ const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, nonCompliantAddress);
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder, salt: number) => {
+ const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder)
+ const saltAsBigNumber = new BigNumber(salt);
+ if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) {
+ return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED);
+ } else {
+ return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ }
+ });
+ });
+ });
+});
+// tslint:disable:max-file-line-count
+// tslint:enable:no-unnecessary-type-assertion
diff --git a/contracts/extensions/test/utils/balance_threshold_wrapper.ts b/contracts/extensions/test/utils/balance_threshold_wrapper.ts
new file mode 100644
index 000000000..63128686c
--- /dev/null
+++ b/contracts/extensions/test/utils/balance_threshold_wrapper.ts
@@ -0,0 +1,246 @@
+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 { ExchangeContract } from '@0x/contracts-protocol';
+import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter';
+
+import { formatters, LogDecoder, orderUtils, OrderInfo, TransactionFactory } from '@0x/contracts-test-utils';
+import { artifacts } from '../../src/artifacts';
+import {artifacts as protocolArtifacts} from '@0x/contracts-protocol';
+import { artifacts as tokensArtifacts } from '@0x/contracts-tokens';
+
+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._executeTransaction(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._executeTransaction(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._executeTransaction(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._executeTransaction(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._executeTransaction(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._executeTransaction(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._executeTransaction(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._executeTransaction(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._executeTransaction(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._executeTransaction(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._executeTransaction(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._executeTransaction(data, from);
+ return txReceipt;
+ }
+ public async cancelOrdersUpToAsync(salt: BigNumber, from: string): Promise<TransactionReceiptWithDecodedLogs> {
+ const data = this._exchange.cancelOrdersUpTo.getABIEncodedTransactionData(salt);
+ const txReceipt = this._executeTransaction(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)) as OrderInfo;
+ 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._executeTransaction(data, from);
+ return txReceipt;
+ }
+ public getBalanceThresholdAddress(): string {
+ return this._balanceThresholdFilter.address;
+ }
+ public getExchangeAddress(): string {
+ return this._exchange.address;
+ }
+ // Exchange functions
+ //abiEncodeFillOrder
+ //getFillOrderResultsAsync
+ //
+ private async _executeTransaction(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;
+ }
+}
diff --git a/contracts/extensions/tsconfig.json b/contracts/extensions/tsconfig.json
index a4ce1e002..5d3ebfe74 100644
--- a/contracts/extensions/tsconfig.json
+++ b/contracts/extensions/tsconfig.json
@@ -6,6 +6,6 @@
"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"]
}