aboutsummaryrefslogtreecommitdiffstats
path: root/contracts/extensions/test
diff options
context:
space:
mode:
authorBrandon Millman <brandon.millman@gmail.com>2018-12-21 07:21:28 +0800
committerBrandon Millman <brandon.millman@gmail.com>2018-12-21 07:21:28 +0800
commit56af9b2aab26fd6a774d0b345ce8e1441bb1a9e0 (patch)
tree54ed033d1d080bcf6212ce697dffa6f427b1b020 /contracts/extensions/test
parentb399aa25aa9386d388d31edb463e803c7c31a2db (diff)
parent0a84ee748823e5099b0767eedc5de95c71cb8f4e (diff)
downloaddexon-sol-tools-56af9b2aab26fd6a774d0b345ce8e1441bb1a9e0.tar
dexon-sol-tools-56af9b2aab26fd6a774d0b345ce8e1441bb1a9e0.tar.gz
dexon-sol-tools-56af9b2aab26fd6a774d0b345ce8e1441bb1a9e0.tar.bz2
dexon-sol-tools-56af9b2aab26fd6a774d0b345ce8e1441bb1a9e0.tar.lz
dexon-sol-tools-56af9b2aab26fd6a774d0b345ce8e1441bb1a9e0.tar.xz
dexon-sol-tools-56af9b2aab26fd6a774d0b345ce8e1441bb1a9e0.tar.zst
dexon-sol-tools-56af9b2aab26fd6a774d0b345ce8e1441bb1a9e0.zip
Merge branch 'development' into fix/instant/signature-denied
* development: (914 commits) Unfix compiler version except for top level contracts Move OrderValidator to extensions Update CHANGELOG Remove assembly version of matchOrders Add getOrderInfo check before calling fillOrder Update comments and hard code function selector constants Fix build after rebase update comments Fix build and add back tests Update dependency paths Add OrderMatcher tests feat: Add OrderMatcher contract that takes spread in multiple assets by calling `matchOrders` followed by `fillOrder` Update CHANGELOG Use more efficient equality checks Add note about input validation Use more efficient check for overflow Check if amount == 0 before doing division Reapply prettier New relayers feat(sra_client.py): Test deployed pkg via tox ...
Diffstat (limited to 'contracts/extensions/test')
-rw-r--r--contracts/extensions/test/extensions/balance_threshold_filter.ts1644
-rw-r--r--contracts/extensions/test/extensions/forwarder.ts3
-rw-r--r--contracts/extensions/test/extensions/order_matcher.ts818
-rw-r--r--contracts/extensions/test/extensions/order_validator.ts609
-rw-r--r--contracts/extensions/test/utils/balance_threshold_wrapper.ts283
5 files changed, 3355 insertions, 2 deletions
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..07199d60b
--- /dev/null
+++ b/contracts/extensions/test/extensions/balance_threshold_filter.ts
@@ -0,0 +1,1644 @@
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { assetDataUtils } from '@0x/order-utils';
+import { Order, RevertReason, SignedOrder } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import * as chai from 'chai';
+import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import {
+ artifacts as protocolArtifacts,
+ ERC20Wrapper,
+ ERC721Wrapper,
+ ExchangeContract,
+ ExchangeWrapper,
+} from '@0x/contracts-protocol';
+import {
+ chaiSetup,
+ constants,
+ ContractName,
+ ERC20BalancesByOwner,
+ expectTransactionFailedAsync,
+ OrderFactory,
+ OrderStatus,
+ provider,
+ TransactionFactory,
+ txDefaults,
+ web3Wrapper,
+} from '@0x/contracts-test-utils';
+import { DummyERC20TokenContract } from '@0x/contracts-tokens';
+
+import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter';
+import { artifacts } from '../../src/artifacts';
+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(ContractName.BalanceThresholdFilter, () => {
+ 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 validMakerAddress: string;
+ let validMakerAddress2: string;
+ let owner: string;
+ let validTakerAddress: string;
+ let feeRecipientAddress: string;
+ let invalidAddress: 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 invalidOrderFactory: OrderFactory;
+ let erc20Wrapper: ERC20Wrapper;
+ let erc20Balances: ERC20BalancesByOwner;
+ let erc20TakerBalanceThresholdWrapper: BalanceThresholdWrapper;
+ let erc721TakerBalanceThresholdWrapper: BalanceThresholdWrapper;
+ let erc721MakerBalanceThresholdWrapper: BalanceThresholdWrapper;
+ let erc721NonValidBalanceThresholdWrapper: BalanceThresholdWrapper;
+
+ let defaultOrderParams: Partial<Order>;
+ let validSignedOrder: SignedOrder;
+ let validSignedOrder2: SignedOrder;
+
+ let erc721BalanceThresholdFilterInstance: BalanceThresholdFilterContract;
+ let erc20BalanceThresholdFilterInstance: 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.
+ const 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,
+ validMakerAddress,
+ validMakerAddress2,
+ validTakerAddress,
+ feeRecipientAddress,
+ invalidAddress,
+ ] = accounts);
+ const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(validTakerAddress)];
+ const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(validMakerAddress)];
+ const secondMakerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(validMakerAddress2)];
+ const invalidAddressPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(invalidAddress)];
+ // Create wrappers
+ erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
+ const validAddresses = _.cloneDeepWith(usedAddresses);
+ _.remove(validAddresses, (address: string) => {
+ return address === invalidAddress;
+ });
+ const erc721Wrapper = new ERC721Wrapper(provider, validAddresses, 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 contract
+ 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 Balance Threshold Filters
+ // One uses an ERC721 token as its balance threshold asset; the other uses an ERC20
+ const erc721alanceThreshold = new BigNumber(1);
+ await erc721Wrapper.deployProxyAsync();
+ const [erc721BalanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync();
+ await erc721Wrapper.setBalancesAndAllowancesAsync();
+ erc721BalanceThresholdFilterInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync(
+ artifacts.BalanceThresholdFilter,
+ provider,
+ txDefaults,
+ exchangeInstance.address,
+ erc721BalanceThresholdAsset.address,
+ erc721alanceThreshold,
+ );
+ const erc20BalanceThreshold = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 10);
+ erc20BalanceThresholdFilterInstance = 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: erc721BalanceThresholdFilterInstance.address,
+ };
+ // Create two order factories with valid makers (who meet the threshold balance), and
+ // one factory for an invalid address (that does not meet the threshold balance)
+ // Valid order factory #1
+ const defaultOrderParams1 = {
+ makerAddress: validMakerAddress,
+ ...defaultOrderParams,
+ };
+ orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams1);
+ // Valid order factory #2
+ const defaultOrderParams2 = {
+ makerAddress: validMakerAddress2,
+ ...defaultOrderParams,
+ };
+ orderFactory2 = new OrderFactory(secondMakerPrivateKey, defaultOrderParams2);
+ // Invalid order factory
+ const defaultNonValidOrderParams = {
+ makerAddress: invalidAddress,
+ ...defaultOrderParams,
+ };
+ invalidOrderFactory = new OrderFactory(invalidAddressPrivateKey, defaultNonValidOrderParams);
+ // Create Balance Thresold Wrappers
+ erc20TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(
+ erc20BalanceThresholdFilterInstance,
+ exchangeInstance,
+ new TransactionFactory(takerPrivateKey, exchangeInstance.address),
+ provider,
+ );
+ erc721TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(
+ erc721BalanceThresholdFilterInstance,
+ exchangeInstance,
+ new TransactionFactory(takerPrivateKey, exchangeInstance.address),
+ provider,
+ );
+ erc721MakerBalanceThresholdWrapper = new BalanceThresholdWrapper(
+ erc721BalanceThresholdFilterInstance,
+ exchangeInstance,
+ new TransactionFactory(makerPrivateKey, exchangeInstance.address),
+ provider,
+ );
+ erc721NonValidBalanceThresholdWrapper = new BalanceThresholdWrapper(
+ erc721BalanceThresholdFilterInstance,
+ exchangeInstance,
+ new TransactionFactory(invalidAddressPrivateKey, exchangeInstance.address),
+ provider,
+ );
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ describe('General Sanity Checks', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = 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 validSignedOrderERC20Sender = await orderFactory.newSignedOrderAsync({
+ ...defaultOrderParams,
+ makerAddress: validMakerAddress,
+ senderAddress: erc20TakerBalanceThresholdWrapper.getBalanceThresholdAddress(),
+ });
+ // Execute a valid fill
+ const txReceipt = await erc20TakerBalanceThresholdWrapper.fillOrderAsync(
+ validSignedOrderERC20Sender,
+ validTakerAddress,
+ { takerAssetFillAmount },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [validSignedOrder.makerAddress, validTakerAddress];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid = validSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][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 Exchange transaction function is not supported', async () => {
+ // Create signed order without the fillOrder function selector
+ const salt = new BigNumber(0);
+ const badSelectorHex = '0x00000000';
+ const signatureHex = '0x';
+ // Call valid forwarder
+ return expectTransactionFailedAsync(
+ erc721BalanceThresholdFilterInstance.executeTransaction.sendTransactionAsync(
+ salt,
+ validTakerAddress,
+ badSelectorHex,
+ signatureHex,
+ ),
+ RevertReason.InvalidOrBlockedExchangeSelector,
+ );
+ });
+ it('should revert if senderAddress is not set to the valid forwarding contract', async () => {
+ // Create signed order with incorrect senderAddress
+ const notBalanceThresholdFilterAddress = zrxToken.address;
+ const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: notBalanceThresholdFilterAddress,
+ });
+ // Call valid forwarder
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.fillOrderAsync(signedOrderWithBadSenderAddress, validTakerAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.FailedExecution,
+ );
+ });
+ });
+
+ describe('batchFillOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = 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 = [validSignedOrder, validSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync(orders, validTakerAddress, {
+ takerAssetFillAmounts,
+ });
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ validSignedOrder.makerAddress,
+ validSignedOrder2.makerAddress,
+ validTakerAddress,
+ ];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2);
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid = validSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount)
+ .times(2);
+ // Maker #1
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ // Maker #2
+ expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid),
+ );
+ // Taker
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][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-valid maker address
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: invalidAddress,
+ });
+ const orders = [validSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync(orders, validTakerAddress, {
+ takerAssetFillAmounts,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [validSignedOrder, validSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.batchFillOrdersAsync(orders, invalidAddress, {
+ takerAssetFillAmounts,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('batchFillOrdersNoThrow', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = 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 = [validSignedOrder, validSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(
+ orders,
+ validTakerAddress,
+ {
+ takerAssetFillAmounts,
+ // HACK(albrow): We need to hardcode the gas estimate here because
+ // the Geth gas estimator doesn't work with the way we use
+ // delegatecall and swallow errors.
+ gas: 600000,
+ },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ validSignedOrder.makerAddress,
+ validSignedOrder2.makerAddress,
+ validTakerAddress,
+ ];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2);
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid = validSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount)
+ .times(2);
+ // Maker #1
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ // Maker #2
+ expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid),
+ );
+ // Taker
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][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-valid maker address
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: invalidAddress,
+ });
+ const orders = [validSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, validTakerAddress, {
+ takerAssetFillAmounts,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [validSignedOrder, validSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, invalidAddress, {
+ takerAssetFillAmounts,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('batchFillOrKillOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = 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 = [validSignedOrder, validSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(
+ orders,
+ validTakerAddress,
+ { takerAssetFillAmounts },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ validSignedOrder.makerAddress,
+ validSignedOrder2.makerAddress,
+ validTakerAddress,
+ ];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2);
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid = validSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount)
+ .times(2);
+ // Maker #1
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ // Maker #2
+ expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid),
+ );
+ // Taker
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][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-valid maker address
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: invalidAddress,
+ });
+ const orders = [validSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, validTakerAddress, {
+ takerAssetFillAmounts,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [validSignedOrder, validSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount];
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, invalidAddress, {
+ takerAssetFillAmounts,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if one takerAssetFillAmount is not fully filled', async () => {
+ const tooBigTakerAssetFillAmount = validSignedOrder.takerAssetAmount.times(2);
+ const orders = [validSignedOrder, validSignedOrder2];
+ const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount];
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, validTakerAddress, {
+ takerAssetFillAmounts,
+ }),
+ RevertReason.FailedExecution,
+ );
+ });
+ });
+
+ describe('fillOrder', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = 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(
+ validSignedOrder,
+ validTakerAddress,
+ { takerAssetFillAmount },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [validSignedOrder.makerAddress, validTakerAddress];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid = validSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][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-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721BalanceThresholdFilterInstance.address,
+ makerAddress: invalidAddress,
+ });
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.fillOrderAsync(signedOrderWithBadMakerAddress, validTakerAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.fillOrderAsync(validSignedOrder, invalidAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('fillOrderNoThrow', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = 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(
+ validSignedOrder,
+ validTakerAddress,
+ {
+ takerAssetFillAmount,
+ // HACK(albrow): We need to hardcode the gas estimate here because
+ // the Geth gas estimator doesn't work with the way we use
+ // delegatecall and swallow errors.
+ gas: 600000,
+ },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [validSignedOrder.makerAddress, validTakerAddress];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid = validSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][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-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721BalanceThresholdFilterInstance.address,
+ makerAddress: invalidAddress,
+ });
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync(
+ signedOrderWithBadMakerAddress,
+ validTakerAddress,
+ { takerAssetFillAmount },
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.fillOrderNoThrowAsync(validSignedOrder, invalidAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('fillOrKillOrder', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = 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_ = validSignedOrder.takerAssetAmount;
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(
+ validSignedOrder,
+ validTakerAddress,
+ { takerAssetFillAmount: takerAssetFillAmount_ },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [validSignedOrder.makerAddress, validTakerAddress];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount = takerAssetFillAmount_
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid = validSignedOrder.makerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee
+ .times(makerAssetFillAmount)
+ .dividedToIntegerBy(validSignedOrder.makerAssetAmount);
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(makerFeePaid),
+ );
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][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-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721BalanceThresholdFilterInstance.address,
+ makerAddress: invalidAddress,
+ });
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(
+ signedOrderWithBadMakerAddress,
+ validTakerAddress,
+ { takerAssetFillAmount },
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.fillOrKillOrderAsync(validSignedOrder, invalidAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if takerAssetFillAmount is not fully filled', async () => {
+ const tooBigTakerAssetFillAmount = validSignedOrder.takerAssetAmount.times(2);
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(validSignedOrder, validTakerAddress, {
+ takerAssetFillAmount: tooBigTakerAssetFillAmount,
+ }),
+ RevertReason.FailedExecution,
+ );
+ });
+ });
+
+ describe('marketSellOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = 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 = [validSignedOrder, validSignedOrder2];
+ const cumulativeTakerAssetFillAmount = validSignedOrder.takerAssetAmount.plus(takerAssetFillAmount);
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync(
+ orders,
+ validTakerAddress,
+ { takerAssetFillAmount: cumulativeTakerAssetFillAmount },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ validSignedOrder.makerAddress,
+ validSignedOrder2.makerAddress,
+ validTakerAddress,
+ ];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount2 = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid2 = validSignedOrder2.makerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid2 = validSignedOrder2.takerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee.plus(takerFeePaid2);
+ const cumulativeMakerAssetFillAmount = validSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2);
+ // Maker #1
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(validSignedOrder.makerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(validSignedOrder.takerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee),
+ );
+ // Maker #2
+ expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2),
+ );
+ expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid2),
+ );
+ // Taker
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address]
+ .add(validSignedOrder.makerFee)
+ .add(makerFeePaid2)
+ .add(takerFeePaid),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: invalidAddress,
+ });
+ const orders = [validSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync(orders, validTakerAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [validSignedOrder, validSignedOrder2];
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.marketSellOrdersAsync(orders, invalidAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('marketSellOrdersNoThrow', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = 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 = [validSignedOrder, validSignedOrder2];
+ const cumulativeTakerAssetFillAmount = validSignedOrder.takerAssetAmount.plus(takerAssetFillAmount);
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(
+ orders,
+ validTakerAddress,
+ {
+ takerAssetFillAmount: cumulativeTakerAssetFillAmount,
+ // HACK(albrow): We need to hardcode the gas estimate here because
+ // the Geth gas estimator doesn't work with the way we use
+ // delegatecall and swallow errors.
+ gas: 600000,
+ },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ validSignedOrder.makerAddress,
+ validSignedOrder2.makerAddress,
+ validTakerAddress,
+ ];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerAssetFillAmount2 = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const makerFeePaid2 = validSignedOrder2.makerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid2 = validSignedOrder2.takerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee.plus(takerFeePaid2);
+ const cumulativeMakerAssetFillAmount = validSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2);
+ // Maker #1
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(validSignedOrder.makerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(validSignedOrder.takerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee),
+ );
+ // Maker #2
+ expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2),
+ );
+ expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid2),
+ );
+ // Taker
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address]
+ .add(validSignedOrder.makerFee)
+ .add(makerFeePaid2)
+ .add(takerFeePaid),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: invalidAddress,
+ });
+ const orders = [validSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, validTakerAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [validSignedOrder, validSignedOrder2];
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, invalidAddress, {
+ takerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('marketBuyOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = 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 = [validSignedOrder, validSignedOrder2];
+ const cumulativeTakerAssetFillAmount = validSignedOrder.takerAssetAmount.plus(takerAssetFillAmount);
+ const makerAssetFillAmount2 = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const cumulativeMakerAssetFillAmount = validSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2);
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, validTakerAddress, {
+ makerAssetFillAmount: cumulativeMakerAssetFillAmount,
+ });
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ validSignedOrder.makerAddress,
+ validSignedOrder2.makerAddress,
+ validTakerAddress,
+ ];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerFeePaid2 = validSignedOrder2.makerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid2 = validSignedOrder2.takerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee.plus(takerFeePaid2);
+ // Maker #1
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(validSignedOrder.makerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(validSignedOrder.takerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee),
+ );
+ // Maker #2
+ expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2),
+ );
+ expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid2),
+ );
+ // Taker
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address]
+ .add(validSignedOrder.makerFee)
+ .add(makerFeePaid2)
+ .add(takerFeePaid),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: invalidAddress,
+ });
+ const orders = [validSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ const dummyMakerAssetFillAmount = new BigNumber(0);
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, validTakerAddress, {
+ makerAssetFillAmount: dummyMakerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [validSignedOrder, validSignedOrder2];
+ const dummyMakerAssetFillAmount = new BigNumber(0);
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.marketBuyOrdersAsync(orders, invalidAddress, {
+ makerAssetFillAmount: dummyMakerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('marketBuyOrdersNoThrowAsync', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = 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 = [validSignedOrder, validSignedOrder2];
+ const cumulativeTakerAssetFillAmount = validSignedOrder.takerAssetAmount.plus(takerAssetFillAmount);
+ const makerAssetFillAmount2 = takerAssetFillAmount
+ .times(validSignedOrder.makerAssetAmount)
+ .dividedToIntegerBy(validSignedOrder.takerAssetAmount);
+ const cumulativeMakerAssetFillAmount = validSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2);
+ const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(
+ orders,
+ validTakerAddress,
+ {
+ makerAssetFillAmount: cumulativeMakerAssetFillAmount,
+ // HACK(albrow): We need to hardcode the gas estimate here because
+ // the Geth gas estimator doesn't work with the way we use
+ // delegatecall and swallow errors.
+ gas: 600000,
+ },
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ validSignedOrder.makerAddress,
+ validSignedOrder2.makerAddress,
+ validTakerAddress,
+ ];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check balances
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ const makerFeePaid2 = validSignedOrder2.makerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid2 = validSignedOrder2.takerFee
+ .times(makerAssetFillAmount2)
+ .dividedToIntegerBy(validSignedOrder2.makerAssetAmount);
+ const takerFeePaid = validSignedOrder.takerFee.plus(takerFeePaid2);
+ // Maker #1
+ expect(newBalances[validMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultMakerAssetAddress].minus(validSignedOrder.makerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][defaultTakerAssetAddress].add(validSignedOrder.takerAssetAmount),
+ );
+ expect(newBalances[validMakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress][zrxToken.address].minus(validSignedOrder.makerFee),
+ );
+ // Maker #2
+ expect(newBalances[validMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2),
+ );
+ expect(newBalances[validMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount),
+ );
+ expect(newBalances[validMakerAddress2][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validMakerAddress2][zrxToken.address].minus(makerFeePaid2),
+ );
+ // Taker
+ expect(newBalances[validTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount),
+ );
+ expect(newBalances[validTakerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][zrxToken.address].minus(takerFeePaid),
+ );
+ // Fee recipient
+ expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[feeRecipientAddress][zrxToken.address]
+ .add(validSignedOrder.makerFee)
+ .add(makerFeePaid2)
+ .add(takerFeePaid),
+ );
+ });
+ it('should revert if one maker does not meet the balance threshold', async () => {
+ // Create order set with one non-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ makerAddress: invalidAddress,
+ });
+ const orders = [validSignedOrder, signedOrderWithBadMakerAddress];
+ // Execute transaction
+ const dummyMakerAssetFillAmount = new BigNumber(0);
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, validTakerAddress, {
+ makerAssetFillAmount: dummyMakerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ const orders = [validSignedOrder, validSignedOrder2];
+ const dummyMakerAssetFillAmount = new BigNumber(0);
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, invalidAddress, {
+ makerAssetFillAmount: dummyMakerAssetFillAmount,
+ }),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('matchOrders', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = 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,
+ });
+ 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,
+ });
+ // 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,
+ validTakerAddress,
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses = [
+ signedOrderLeft.makerAddress,
+ signedOrderRight.makerAddress,
+ validTakerAddress,
+ ];
+ await 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[validTakerAddress][defaultMakerAssetAddress],
+ 'Checking taker ingress ERC20 account balance',
+ ).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][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[validTakerAddress][zrxToken.address],
+ 'Checking taker egress ERC20 account fees',
+ ).to.be.bignumber.equal(
+ erc20Balances[validTakerAddress][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-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721BalanceThresholdFilterInstance.address,
+ makerAddress: invalidAddress,
+ });
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.matchOrdersAsync(
+ validSignedOrder,
+ signedOrderWithBadMakerAddress,
+ validTakerAddress,
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if right maker does not meet the balance threshold', async () => {
+ // Create signed order with non-valid maker address
+ const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({
+ senderAddress: erc721BalanceThresholdFilterInstance.address,
+ makerAddress: invalidAddress,
+ });
+ // Execute transaction
+ return expectTransactionFailedAsync(
+ erc721TakerBalanceThresholdWrapper.matchOrdersAsync(
+ signedOrderWithBadMakerAddress,
+ validSignedOrder,
+ validTakerAddress,
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ it('should revert if taker does not meet the balance threshold', async () => {
+ return expectTransactionFailedAsync(
+ erc721NonValidBalanceThresholdWrapper.matchOrdersAsync(
+ validSignedOrder,
+ validSignedOrder,
+ invalidAddress,
+ ),
+ RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold,
+ );
+ });
+ });
+
+ describe('cancelOrder', () => {
+ beforeEach(async () => {
+ erc20Balances = await erc20Wrapper.getBalancesAsync();
+ validSignedOrder = await orderFactory.newSignedOrderAsync();
+ validSignedOrder2 = await orderFactory2.newSignedOrderAsync();
+ });
+ it('Should successfully cancel order if maker meets balance threshold', async () => {
+ // Verify order is not cancelled
+ const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(
+ validSignedOrder,
+ );
+ expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ // Cancel
+ const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrderAsync(
+ validSignedOrder,
+ validSignedOrder.makerAddress,
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(
+ validSignedOrder,
+ );
+ 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 invalidOrderFactory.newSignedOrderAsync({});
+ // Verify order is not cancelled
+ const orderInfoBeforeCancelling = await erc721NonValidBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrderWithBadMakerAddress,
+ );
+ expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ // Cancel
+ const txReceipt = await erc721NonValidBalanceThresholdWrapper.cancelOrderAsync(
+ signedOrderWithBadMakerAddress,
+ signedOrderWithBadMakerAddress.makerAddress,
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ await 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 validSignedOrders = [
+ await orderFactory.newSignedOrderAsync(),
+ await orderFactory.newSignedOrderAsync(),
+ await orderFactory.newSignedOrderAsync(),
+ ];
+ // Verify orders are not cancelled
+ _.each(validSignedOrders, async signedOrder => {
+ const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ });
+ // Cancel
+ const txReceipt = await erc721MakerBalanceThresholdWrapper.batchCancelOrdersAsync(
+ validSignedOrders,
+ validSignedOrders[0].makerAddress,
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ _.each(validSignedOrders, async signedOrder => {
+ const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ 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 invalidSignedOrders = [
+ await invalidOrderFactory.newSignedOrderAsync(),
+ await invalidOrderFactory.newSignedOrderAsync(),
+ await invalidOrderFactory.newSignedOrderAsync(),
+ ];
+ // Verify orders are not cancelled
+ _.each(invalidSignedOrders, async signedOrder => {
+ const orderInfoBeforeCancelling = await erc721NonValidBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ });
+ // Cancel
+ const txReceipt = await erc721NonValidBalanceThresholdWrapper.batchCancelOrdersAsync(
+ invalidSignedOrders,
+ invalidAddress,
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ _.each(invalidSignedOrders, async signedOrder => {
+ const orderInfoAfterCancelling = await erc721NonValidBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ 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 validSignedOrders = [
+ 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
+ _.each(validSignedOrders, async signedOrder => {
+ const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ });
+ // Cancel
+ const cancelOrdersUpToThisSalt = new BigNumber(1);
+ const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrdersUpToAsync(
+ cancelOrdersUpToThisSalt,
+ validSignedOrders[0].makerAddress,
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ _.each(validSignedOrders, async (signedOrder, salt: number) => {
+ const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ 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 invalidSignedOrders = [
+ await invalidOrderFactory.newSignedOrderAsync({ salt: new BigNumber(0) }),
+ await invalidOrderFactory.newSignedOrderAsync({ salt: new BigNumber(1) }),
+ await invalidOrderFactory.newSignedOrderAsync({ salt: new BigNumber(2) }),
+ ];
+ // Verify orders are not cancelled
+ _.each(invalidSignedOrders, async signedOrder => {
+ const orderInfoBeforeCancelling = await erc721NonValidBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ });
+ // Cancel
+ const cancelOrdersUpToThisSalt = new BigNumber(1);
+ const txReceipt = await erc721NonValidBalanceThresholdWrapper.cancelOrdersUpToAsync(
+ cancelOrdersUpToThisSalt,
+ invalidAddress,
+ );
+ // Assert validated addresses
+ const expectedValidatedAddresseses: string[] = [];
+ await assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses);
+ // Check that order was cancelled
+ _.each(invalidSignedOrders, async (signedOrder, salt: number) => {
+ const orderInfoAfterCancelling = await erc721NonValidBalanceThresholdWrapper.getOrderInfoAsync(
+ signedOrder,
+ );
+ 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/extensions/forwarder.ts b/contracts/extensions/test/extensions/forwarder.ts
index 4027f493d..69939ed04 100644
--- a/contracts/extensions/test/extensions/forwarder.ts
+++ b/contracts/extensions/test/extensions/forwarder.ts
@@ -48,7 +48,6 @@ describe(ContractName.Forwarder, () => {
let owner: string;
let takerAddress: string;
let feeRecipientAddress: string;
- let otherAddress: string;
let defaultMakerAssetAddress: string;
let zrxAssetData: string;
let wethAssetData: string;
@@ -78,7 +77,7 @@ describe(ContractName.Forwarder, () => {
before(async () => {
await blockchainLifecycle.startAsync();
const accounts = await web3Wrapper.getAvailableAddressesAsync();
- const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress, otherAddress] = accounts);
+ const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts);
const txHash = await web3Wrapper.sendTransactionAsync({ from: accounts[0], to: accounts[0], value: 0 });
const transaction = await web3Wrapper.getTransactionByHashAsync(txHash);
diff --git a/contracts/extensions/test/extensions/order_matcher.ts b/contracts/extensions/test/extensions/order_matcher.ts
new file mode 100644
index 000000000..acb46ced4
--- /dev/null
+++ b/contracts/extensions/test/extensions/order_matcher.ts
@@ -0,0 +1,818 @@
+import {
+ artifacts as protocolArtifacts,
+ ERC20ProxyContract,
+ ERC20Wrapper,
+ ERC721ProxyContract,
+ ExchangeContract,
+ ExchangeFillEventArgs,
+ ExchangeWrapper,
+} from '@0x/contracts-protocol';
+import {
+ chaiSetup,
+ constants,
+ ERC20BalancesByOwner,
+ expectContractCreationFailedAsync,
+ expectTransactionFailedAsync,
+ LogDecoder,
+ OrderFactory,
+ provider,
+ sendTransactionResult,
+ txDefaults,
+ web3Wrapper,
+} from '@0x/contracts-test-utils';
+import { artifacts as tokenArtifacts, DummyERC20TokenContract, DummyERC721TokenContract } from '@0x/contracts-tokens';
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { assetDataUtils } from '@0x/order-utils';
+import { RevertReason } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import * as chai from 'chai';
+import { LogWithDecodedArgs } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { OrderMatcherContract } from '../../generated-wrappers/order_matcher';
+import { artifacts } from '../../src/artifacts';
+
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+chaiSetup.configure();
+const expect = chai.expect;
+// tslint:disable:no-unnecessary-type-assertion
+describe('OrderMatcher', () => {
+ let makerAddressLeft: string;
+ let makerAddressRight: string;
+ let owner: string;
+ let takerAddress: string;
+ let feeRecipientAddressLeft: string;
+ let feeRecipientAddressRight: string;
+
+ let erc20TokenA: DummyERC20TokenContract;
+ let erc20TokenB: DummyERC20TokenContract;
+ let zrxToken: DummyERC20TokenContract;
+ let exchange: ExchangeContract;
+ let erc20Proxy: ERC20ProxyContract;
+ let erc721Proxy: ERC721ProxyContract;
+ let orderMatcher: OrderMatcherContract;
+
+ let erc20BalancesByOwner: ERC20BalancesByOwner;
+ let exchangeWrapper: ExchangeWrapper;
+ let erc20Wrapper: ERC20Wrapper;
+ let orderFactoryLeft: OrderFactory;
+ let orderFactoryRight: OrderFactory;
+
+ let leftMakerAssetData: string;
+ let leftTakerAssetData: string;
+ let defaultERC20MakerAssetAddress: string;
+ let defaultERC20TakerAssetAddress: string;
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ before(async () => {
+ // Create accounts
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ // Hack(albrow): Both Prettier and TSLint insert a trailing comma below
+ // but that is invalid syntax as of TypeScript version >= 2.8. We don't
+ // have the right fine-grained configuration options in TSLint,
+ // Prettier, or TypeScript, to reconcile this, so we will just have to
+ // wait for them to sort it out. We disable TSLint and Prettier for
+ // this part of the code for now. This occurs several times in this
+ // file. See https://github.com/prettier/prettier/issues/4624.
+ // prettier-ignore
+ const usedAddresses = ([
+ owner,
+ makerAddressLeft,
+ makerAddressRight,
+ takerAddress,
+ feeRecipientAddressLeft,
+ // tslint:disable-next-line:trailing-comma
+ feeRecipientAddressRight
+ ] = _.slice(accounts, 0, 6));
+ // Create wrappers
+ erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
+ // Deploy ERC20 token & ERC20 proxy
+ const numDummyErc20ToDeploy = 3;
+ [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
+ numDummyErc20ToDeploy,
+ constants.DUMMY_TOKEN_DECIMALS,
+ );
+ erc20Proxy = await erc20Wrapper.deployProxyAsync();
+ await erc20Wrapper.setBalancesAndAllowancesAsync();
+ // Deploy ERC721 proxy
+ erc721Proxy = await ERC721ProxyContract.deployFrom0xArtifactAsync(
+ protocolArtifacts.ERC721Proxy,
+ provider,
+ txDefaults,
+ );
+ // Depoy exchange
+ exchange = await ExchangeContract.deployFrom0xArtifactAsync(
+ protocolArtifacts.Exchange,
+ provider,
+ txDefaults,
+ assetDataUtils.encodeERC20AssetData(zrxToken.address),
+ );
+ exchangeWrapper = new ExchangeWrapper(exchange, provider);
+ await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner);
+ await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner);
+ // Authorize ERC20 trades by exchange
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Deploy OrderMatcher
+ orderMatcher = await OrderMatcherContract.deployFrom0xArtifactAsync(
+ artifacts.OrderMatcher,
+ provider,
+ txDefaults,
+ exchange.address,
+ );
+ // Set default addresses
+ defaultERC20MakerAssetAddress = erc20TokenA.address;
+ defaultERC20TakerAssetAddress = erc20TokenB.address;
+ leftMakerAssetData = assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress);
+ leftTakerAssetData = assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress);
+ // Set OrderMatcher balances and allowances
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20TokenA.setBalance.sendTransactionAsync(orderMatcher.address, constants.INITIAL_ERC20_BALANCE, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20TokenB.setBalance.sendTransactionAsync(orderMatcher.address, constants.INITIAL_ERC20_BALANCE, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.approveAssetProxy.sendTransactionAsync(
+ leftMakerAssetData,
+ constants.INITIAL_ERC20_ALLOWANCE,
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.approveAssetProxy.sendTransactionAsync(
+ leftTakerAssetData,
+ constants.INITIAL_ERC20_ALLOWANCE,
+ ),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ // Create default order parameters
+ const defaultOrderParamsLeft = {
+ ...constants.STATIC_ORDER_PARAMS,
+ makerAddress: makerAddressLeft,
+ exchangeAddress: exchange.address,
+ makerAssetData: leftMakerAssetData,
+ takerAssetData: leftTakerAssetData,
+ feeRecipientAddress: feeRecipientAddressLeft,
+ makerFee: constants.ZERO_AMOUNT,
+ takerFee: constants.ZERO_AMOUNT,
+ };
+ const defaultOrderParamsRight = {
+ ...constants.STATIC_ORDER_PARAMS,
+ makerAddress: makerAddressRight,
+ exchangeAddress: exchange.address,
+ makerAssetData: leftTakerAssetData,
+ takerAssetData: leftMakerAssetData,
+ feeRecipientAddress: feeRecipientAddressRight,
+ makerFee: constants.ZERO_AMOUNT,
+ takerFee: constants.ZERO_AMOUNT,
+ };
+ const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)];
+ orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParamsLeft);
+ const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)];
+ orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParamsRight);
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('constructor', () => {
+ it('should revert if assetProxy is unregistered', async () => {
+ const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync(
+ protocolArtifacts.Exchange,
+ provider,
+ txDefaults,
+ constants.NULL_BYTES,
+ );
+ return expectContractCreationFailedAsync(
+ (OrderMatcherContract.deployFrom0xArtifactAsync(
+ artifacts.OrderMatcher,
+ provider,
+ txDefaults,
+ exchangeInstance.address,
+ ) as any) as sendTransactionResult,
+ RevertReason.UnregisteredAssetProxy,
+ );
+ });
+ });
+ describe('matchOrders', () => {
+ beforeEach(async () => {
+ erc20BalancesByOwner = await erc20Wrapper.getBalancesAsync();
+ });
+ it('should revert if not called by owner', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
+ });
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await expectTransactionFailedAsync(
+ web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: takerAddress,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ RevertReason.OnlyContractOwner,
+ );
+ });
+ it('should transfer the correct amounts when orders completely fill each other', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
+ });
+ // Match signedOrderLeft with signedOrderRight
+ const expectedTransferAmounts = {
+ // Left Maker
+ amountSoldByLeftMaker: signedOrderLeft.makerAssetAmount,
+ amountBoughtByLeftMaker: signedOrderLeft.takerAssetAmount,
+ // Right Maker
+ amountSoldByRightMaker: signedOrderRight.makerAssetAmount,
+ amountBoughtByRightMaker: signedOrderRight.takerAssetAmount,
+ // Taker
+ leftMakerAssetSpreadAmount: signedOrderLeft.makerAssetAmount.minus(signedOrderRight.takerAssetAmount),
+ };
+ const initialLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const newLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const newErc20Balances = await erc20Wrapper.getBalancesAsync();
+ expect(newErc20Balances[makerAddressLeft][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20MakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20TakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByRightMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressLeft][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20TakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20MakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByRightMaker,
+ ),
+ );
+ expect(newLeftMakerAssetTakerBalance).to.be.bignumber.equal(
+ initialLeftMakerAssetTakerBalance.plus(expectedTransferAmounts.leftMakerAssetSpreadAmount),
+ );
+ });
+ it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ });
+ // Match signedOrderLeft with signedOrderRight
+ const expectedTransferAmounts = {
+ // Left Maker
+ amountSoldByLeftMaker: signedOrderLeft.makerAssetAmount,
+ amountBoughtByLeftMaker: signedOrderLeft.takerAssetAmount,
+ // Right Maker
+ amountSoldByRightMaker: signedOrderRight.makerAssetAmount,
+ amountBoughtByRightMaker: signedOrderRight.takerAssetAmount,
+ };
+ const initialLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const newLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const newErc20Balances = await erc20Wrapper.getBalancesAsync();
+ expect(newErc20Balances[makerAddressLeft][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20MakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20TakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByRightMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressLeft][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20TakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20MakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByRightMaker,
+ ),
+ );
+ expect(newLeftMakerAssetTakerBalance).to.be.bignumber.equal(initialLeftMakerAssetTakerBalance);
+ });
+ it('should transfer the correct amounts when left order is completely filled and right order would be partially filled', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18),
+ });
+ // Match signedOrderLeft with signedOrderRight
+ const expectedTransferAmounts = {
+ // Left Maker
+ amountSoldByLeftMaker: signedOrderLeft.makerAssetAmount,
+ amountBoughtByLeftMaker: signedOrderLeft.takerAssetAmount,
+ // Right Maker
+ amountSoldByRightMaker: signedOrderRight.makerAssetAmount,
+ amountBoughtByRightMaker: signedOrderRight.takerAssetAmount,
+ // Taker
+ leftMakerAssetSpreadAmount: signedOrderLeft.makerAssetAmount.minus(signedOrderRight.takerAssetAmount),
+ leftTakerAssetSpreadAmount: signedOrderRight.makerAssetAmount.minus(signedOrderLeft.takerAssetAmount),
+ };
+ const initialLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const initialLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf.callAsync(orderMatcher.address);
+ // Match signedOrderLeft with signedOrderRight
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const newLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const newLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf.callAsync(orderMatcher.address);
+ const newErc20Balances = await erc20Wrapper.getBalancesAsync();
+ expect(newErc20Balances[makerAddressLeft][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20MakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20TakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByRightMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressLeft][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20TakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20MakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByRightMaker,
+ ),
+ );
+ expect(newLeftMakerAssetTakerBalance).to.be.bignumber.equal(
+ initialLeftMakerAssetTakerBalance.plus(expectedTransferAmounts.leftMakerAssetSpreadAmount),
+ );
+ expect(newLeftTakerAssetTakerBalance).to.be.bignumber.equal(
+ initialLeftTakerAssetTakerBalance.plus(expectedTransferAmounts.leftTakerAssetSpreadAmount),
+ );
+ });
+ it('should not call fillOrder when rightOrder is completely filled after matchOrders call and orders were never partially filled', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
+ });
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ const logDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...tokenArtifacts, ...protocolArtifacts });
+ const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
+ await web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ );
+ const fillLogs = _.filter(
+ txReceipt.logs,
+ log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
+ );
+ // Only 2 Fill logs should exist for `matchOrders` call. `fillOrder` should not have been called and should not have emitted a Fill event.
+ expect(fillLogs.length).to.be.equal(2);
+ });
+ it('should not call fillOrder when rightOrder is completely filled after matchOrders call and orders were initially partially filled', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
+ });
+ await exchangeWrapper.fillOrderAsync(signedOrderLeft, takerAddress, {
+ takerAssetFillAmount: signedOrderLeft.takerAssetAmount.dividedToIntegerBy(5),
+ });
+ await exchangeWrapper.fillOrderAsync(signedOrderRight, takerAddress, {
+ takerAssetFillAmount: signedOrderRight.takerAssetAmount.dividedToIntegerBy(5),
+ });
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ const logDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...tokenArtifacts, ...protocolArtifacts });
+ const txReceipt = await logDecoder.getTxWithDecodedLogsAsync(
+ await web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ );
+ const fillLogs = _.filter(
+ txReceipt.logs,
+ log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
+ );
+ // Only 2 Fill logs should exist for `matchOrders` call. `fillOrder` should not have been called and should not have emitted a Fill event.
+ expect(fillLogs.length).to.be.equal(2);
+ });
+ it('should only take a spread in rightMakerAsset if entire leftMakerAssetSpread amount can be used to fill rightOrder after matchOrders call', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.9), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(990), 18),
+ });
+ const initialLeftMakerAssetSpreadAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1.09), 18);
+ const leftTakerAssetSpreadAmount = initialLeftMakerAssetSpreadAmount
+ .times(signedOrderRight.makerAssetAmount)
+ .dividedToIntegerBy(signedOrderRight.takerAssetAmount);
+ // Match signedOrderLeft with signedOrderRight
+ const expectedTransferAmounts = {
+ // Left Maker
+ amountSoldByLeftMaker: signedOrderLeft.makerAssetAmount,
+ amountBoughtByLeftMaker: signedOrderLeft.takerAssetAmount,
+ // Right Maker
+ amountSoldByRightMaker: signedOrderLeft.takerAssetAmount.plus(leftTakerAssetSpreadAmount),
+ amountBoughtByRightMaker: signedOrderLeft.makerAssetAmount,
+ // Taker
+ leftMakerAssetSpreadAmount: constants.ZERO_AMOUNT,
+ leftTakerAssetSpreadAmount,
+ };
+ const initialLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const initialLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf.callAsync(orderMatcher.address);
+ // Match signedOrderLeft with signedOrderRight
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const newLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const newLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf.callAsync(orderMatcher.address);
+ const newErc20Balances = await erc20Wrapper.getBalancesAsync();
+ expect(newErc20Balances[makerAddressLeft][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20MakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20TakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByRightMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressLeft][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20TakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20MakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByRightMaker,
+ ),
+ );
+ expect(newLeftMakerAssetTakerBalance).to.be.bignumber.equal(
+ initialLeftMakerAssetTakerBalance.plus(expectedTransferAmounts.leftMakerAssetSpreadAmount),
+ );
+ expect(newLeftTakerAssetTakerBalance).to.be.bignumber.equal(
+ initialLeftTakerAssetTakerBalance.plus(expectedTransferAmounts.leftTakerAssetSpreadAmount),
+ );
+ });
+ it("should succeed if rightOrder's makerAssetData and takerAssetData are not provided", async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18),
+ });
+ // Match signedOrderLeft with signedOrderRight
+ const expectedTransferAmounts = {
+ // Left Maker
+ amountSoldByLeftMaker: signedOrderLeft.makerAssetAmount,
+ amountBoughtByLeftMaker: signedOrderLeft.takerAssetAmount,
+ // Right Maker
+ amountSoldByRightMaker: signedOrderRight.makerAssetAmount,
+ amountBoughtByRightMaker: signedOrderRight.takerAssetAmount,
+ // Taker
+ leftMakerAssetSpreadAmount: signedOrderLeft.makerAssetAmount.minus(signedOrderRight.takerAssetAmount),
+ leftTakerAssetSpreadAmount: signedOrderRight.makerAssetAmount.minus(signedOrderLeft.takerAssetAmount),
+ };
+ const initialLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const initialLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf.callAsync(orderMatcher.address);
+ // Match signedOrderLeft with signedOrderRight
+ signedOrderRight.makerAssetData = constants.NULL_BYTES;
+ signedOrderRight.takerAssetData = constants.NULL_BYTES;
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const newLeftMakerAssetTakerBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ const newLeftTakerAssetTakerBalance = await erc20TokenB.balanceOf.callAsync(orderMatcher.address);
+ const newErc20Balances = await erc20Wrapper.getBalancesAsync();
+ expect(newErc20Balances[makerAddressLeft][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20MakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20TakerAssetAddress].minus(
+ expectedTransferAmounts.amountSoldByRightMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressLeft][defaultERC20TakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressLeft][defaultERC20TakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByLeftMaker,
+ ),
+ );
+ expect(newErc20Balances[makerAddressRight][defaultERC20MakerAssetAddress]).to.be.bignumber.equal(
+ erc20BalancesByOwner[makerAddressRight][defaultERC20MakerAssetAddress].plus(
+ expectedTransferAmounts.amountBoughtByRightMaker,
+ ),
+ );
+ expect(newLeftMakerAssetTakerBalance).to.be.bignumber.equal(
+ initialLeftMakerAssetTakerBalance.plus(expectedTransferAmounts.leftMakerAssetSpreadAmount),
+ );
+ expect(newLeftTakerAssetTakerBalance).to.be.bignumber.equal(
+ initialLeftTakerAssetTakerBalance.plus(expectedTransferAmounts.leftTakerAssetSpreadAmount),
+ );
+ });
+ it('should revert with the correct reason if matchOrders call reverts', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ });
+ signedOrderRight.signature = `0xff${signedOrderRight.signature.slice(4)}`;
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await expectTransactionFailedAsync(
+ web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ RevertReason.InvalidOrderSignature,
+ );
+ });
+ it('should revert with the correct reason if fillOrder call reverts', async () => {
+ // Create orders to match
+ const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
+ });
+ const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
+ makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 18),
+ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18),
+ });
+ // Matcher will not have enough allowance to fill rightOrder
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.approveAssetProxy.sendTransactionAsync(leftMakerAssetData, constants.ZERO_AMOUNT, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const data = exchange.matchOrders.getABIEncodedTransactionData(
+ signedOrderLeft,
+ signedOrderRight,
+ signedOrderLeft.signature,
+ signedOrderRight.signature,
+ );
+ await expectTransactionFailedAsync(
+ web3Wrapper.sendTransactionAsync({
+ data,
+ to: orderMatcher.address,
+ from: owner,
+ gas: constants.MAX_MATCH_ORDERS_GAS,
+ }),
+ RevertReason.TransferFailed,
+ );
+ });
+ });
+ describe('withdrawAsset', () => {
+ it('should allow owner to withdraw ERC20 tokens', async () => {
+ const erc20AWithdrawAmount = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ expect(erc20AWithdrawAmount).to.be.bignumber.gt(constants.ZERO_AMOUNT);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.withdrawAsset.sendTransactionAsync(leftMakerAssetData, erc20AWithdrawAmount, {
+ from: owner,
+ }),
+ );
+ const newBalance = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ expect(newBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should allow owner to withdraw ERC721 tokens', async () => {
+ const erc721Token = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
+ tokenArtifacts.DummyERC721Token,
+ provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ );
+ const tokenId = new BigNumber(1);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(orderMatcher.address, tokenId, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const assetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId);
+ const withdrawAmount = new BigNumber(1);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.withdrawAsset.sendTransactionAsync(assetData, withdrawAmount, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const erc721Owner = await erc721Token.ownerOf.callAsync(tokenId);
+ expect(erc721Owner).to.be.equal(owner);
+ });
+ it('should revert if not called by owner', async () => {
+ const erc20AWithdrawAmount = await erc20TokenA.balanceOf.callAsync(orderMatcher.address);
+ expect(erc20AWithdrawAmount).to.be.bignumber.gt(constants.ZERO_AMOUNT);
+ await expectTransactionFailedAsync(
+ orderMatcher.withdrawAsset.sendTransactionAsync(leftMakerAssetData, erc20AWithdrawAmount, {
+ from: takerAddress,
+ }),
+ RevertReason.OnlyContractOwner,
+ );
+ });
+ });
+ describe('approveAssetProxy', () => {
+ it('should be able to set an allowance for ERC20 tokens', async () => {
+ const allowance = new BigNumber(55465465426546);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.approveAssetProxy.sendTransactionAsync(leftMakerAssetData, allowance, {
+ from: owner,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const newAllowance = await erc20TokenA.allowance.callAsync(orderMatcher.address, erc20Proxy.address);
+ expect(newAllowance).to.be.bignumber.equal(allowance);
+ });
+ it('should be able to approve an ERC721 token by passing in allowance = 1', async () => {
+ const erc721Token = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
+ tokenArtifacts.DummyERC721Token,
+ provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ );
+ const assetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, constants.ZERO_AMOUNT);
+ const allowance = new BigNumber(1);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.approveAssetProxy.sendTransactionAsync(assetData, allowance, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isApproved = await erc721Token.isApprovedForAll.callAsync(orderMatcher.address, erc721Proxy.address);
+ expect(isApproved).to.be.equal(true);
+ });
+ it('should be able to approve an ERC721 token by passing in allowance > 1', async () => {
+ const erc721Token = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
+ tokenArtifacts.DummyERC721Token,
+ provider,
+ txDefaults,
+ constants.DUMMY_TOKEN_NAME,
+ constants.DUMMY_TOKEN_SYMBOL,
+ );
+ const assetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, constants.ZERO_AMOUNT);
+ const allowance = new BigNumber(2);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await orderMatcher.approveAssetProxy.sendTransactionAsync(assetData, allowance, { from: owner }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isApproved = await erc721Token.isApprovedForAll.callAsync(orderMatcher.address, erc721Proxy.address);
+ expect(isApproved).to.be.equal(true);
+ });
+ it('should revert if not called by owner', async () => {
+ const approval = new BigNumber(1);
+ await expectTransactionFailedAsync(
+ orderMatcher.approveAssetProxy.sendTransactionAsync(leftMakerAssetData, approval, {
+ from: takerAddress,
+ }),
+ RevertReason.OnlyContractOwner,
+ );
+ });
+ });
+});
+// tslint:disable:max-file-line-count
+// tslint:enable:no-unnecessary-type-assertion
diff --git a/contracts/extensions/test/extensions/order_validator.ts b/contracts/extensions/test/extensions/order_validator.ts
new file mode 100644
index 000000000..82a6b937f
--- /dev/null
+++ b/contracts/extensions/test/extensions/order_validator.ts
@@ -0,0 +1,609 @@
+import {
+ artifacts as protocolArtifacts,
+ ERC20ProxyContract,
+ ERC20Wrapper,
+ ERC721ProxyContract,
+ ERC721Wrapper,
+ ExchangeContract,
+ ExchangeWrapper,
+} from '@0x/contracts-protocol';
+import {
+ chaiSetup,
+ constants,
+ OrderFactory,
+ OrderStatus,
+ provider,
+ txDefaults,
+ web3Wrapper,
+} from '@0x/contracts-test-utils';
+import { DummyERC20TokenContract, DummyERC721TokenContract } from '@0x/contracts-tokens';
+import { BlockchainLifecycle } from '@0x/dev-utils';
+import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
+import { SignedOrder } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import * as chai from 'chai';
+import * as _ from 'lodash';
+
+import { OrderValidatorContract } from '../../generated-wrappers/order_validator';
+import { artifacts } from '../../src/artifacts/index';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('OrderValidator', () => {
+ let makerAddress: string;
+ let owner: string;
+ let takerAddress: string;
+ let erc20AssetData: string;
+ let erc721AssetData: string;
+
+ let erc20Token: DummyERC20TokenContract;
+ let zrxToken: DummyERC20TokenContract;
+ let erc721Token: DummyERC721TokenContract;
+ let exchange: ExchangeContract;
+ let orderValidator: OrderValidatorContract;
+ let erc20Proxy: ERC20ProxyContract;
+ let erc721Proxy: ERC721ProxyContract;
+
+ let signedOrder: SignedOrder;
+ let signedOrder2: SignedOrder;
+ let orderFactory: OrderFactory;
+
+ const tokenId = new BigNumber(123456789);
+ const tokenId2 = new BigNumber(987654321);
+ const ERC721_BALANCE = new BigNumber(1);
+ const ERC721_ALLOWANCE = new BigNumber(1);
+
+ before(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ after(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ before(async () => {
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ const usedAddresses = ([owner, makerAddress, takerAddress] = _.slice(accounts, 0, 3));
+
+ const erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
+ const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
+
+ const numDummyErc20ToDeploy = 2;
+ [erc20Token, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
+ numDummyErc20ToDeploy,
+ constants.DUMMY_TOKEN_DECIMALS,
+ );
+ erc20Proxy = await erc20Wrapper.deployProxyAsync();
+
+ [erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
+ erc721Proxy = await erc721Wrapper.deployProxyAsync();
+
+ const zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
+ exchange = await ExchangeContract.deployFrom0xArtifactAsync(
+ protocolArtifacts.Exchange,
+ provider,
+ txDefaults,
+ zrxAssetData,
+ );
+ const exchangeWrapper = new ExchangeWrapper(exchange, provider);
+ await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner);
+ await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner);
+
+ orderValidator = await OrderValidatorContract.deployFrom0xArtifactAsync(
+ artifacts.OrderValidator,
+ provider,
+ txDefaults,
+ exchange.address,
+ zrxAssetData,
+ );
+
+ erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20Token.address);
+ erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId);
+ const defaultOrderParams = {
+ ...constants.STATIC_ORDER_PARAMS,
+ exchangeAddress: exchange.address,
+ makerAddress,
+ feeRecipientAddress: constants.NULL_ADDRESS,
+ makerAssetData: erc20AssetData,
+ takerAssetData: erc721AssetData,
+ };
+ const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
+ orderFactory = new OrderFactory(privateKey, defaultOrderParams);
+ });
+
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+
+ describe('getBalanceAndAllowance', () => {
+ describe('getERC721TokenOwner', async () => {
+ it('should return the null address when tokenId is not owned', async () => {
+ const tokenOwner = await orderValidator.getERC721TokenOwner.callAsync(makerAddress, tokenId);
+ expect(tokenOwner).to.be.equal(constants.NULL_ADDRESS);
+ });
+ it('should return the owner address when tokenId is owned', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const tokenOwner = await orderValidator.getERC721TokenOwner.callAsync(erc721Token.address, tokenId);
+ expect(tokenOwner).to.be.equal(makerAddress);
+ });
+ });
+ describe('ERC20 assetData', () => {
+ it('should return the correct balances and allowances when both values are 0', async () => {
+ const [newBalance, newAllowance] = await orderValidator.getBalanceAndAllowance.callAsync(
+ makerAddress,
+ erc20AssetData,
+ );
+ expect(constants.ZERO_AMOUNT).to.be.bignumber.equal(newBalance);
+ expect(constants.ZERO_AMOUNT).to.be.bignumber.equal(newAllowance);
+ });
+ it('should return the correct balance and allowance when both values are non-zero', async () => {
+ const balance = new BigNumber(123);
+ const allowance = new BigNumber(456);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.setBalance.sendTransactionAsync(makerAddress, balance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, allowance, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const [newBalance, newAllowance] = await orderValidator.getBalanceAndAllowance.callAsync(
+ makerAddress,
+ erc20AssetData,
+ );
+ expect(balance).to.be.bignumber.equal(newBalance);
+ expect(allowance).to.be.bignumber.equal(newAllowance);
+ });
+ });
+ describe('ERC721 assetData', () => {
+ it('should return a balance of 0 when the tokenId is not owned by target', async () => {
+ const [newBalance] = await orderValidator.getBalanceAndAllowance.callAsync(
+ makerAddress,
+ erc721AssetData,
+ );
+ expect(newBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should return an allowance of 0 when no approval is set', async () => {
+ const [, newAllowance] = await orderValidator.getBalanceAndAllowance.callAsync(
+ makerAddress,
+ erc721AssetData,
+ );
+ expect(newAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should return a balance of 1 when the tokenId is owned by target', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const [newBalance] = await orderValidator.getBalanceAndAllowance.callAsync(
+ makerAddress,
+ erc721AssetData,
+ );
+ expect(newBalance).to.be.bignumber.equal(ERC721_BALANCE);
+ });
+ it('should return an allowance of 1 when ERC721Proxy is approved for all', async () => {
+ const isApproved = true;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const [, newAllowance] = await orderValidator.getBalanceAndAllowance.callAsync(
+ makerAddress,
+ erc721AssetData,
+ );
+ expect(newAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ });
+ it('should return an allowance of 0 when ERC721Proxy is approved for specific tokenId', async () => {
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.approve.sendTransactionAsync(erc721Proxy.address, tokenId, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const [, newAllowance] = await orderValidator.getBalanceAndAllowance.callAsync(
+ makerAddress,
+ erc721AssetData,
+ );
+ expect(newAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ });
+ });
+ describe('getBalancesAndAllowances', () => {
+ it('should return the correct balances and allowances when all values are 0', async () => {
+ const [
+ [erc20Balance, erc721Balance],
+ [erc20Allowance, erc721Allowance],
+ ] = await orderValidator.getBalancesAndAllowances.callAsync(makerAddress, [
+ erc20AssetData,
+ erc721AssetData,
+ ]);
+ expect(erc20Balance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(erc721Balance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(erc20Allowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(erc721Allowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should return the correct balances and allowances when balances and allowances are non-zero', async () => {
+ const balance = new BigNumber(123);
+ const allowance = new BigNumber(456);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.setBalance.sendTransactionAsync(makerAddress, balance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, allowance, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isApproved = true;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const [
+ [erc20Balance, erc721Balance],
+ [erc20Allowance, erc721Allowance],
+ ] = await orderValidator.getBalancesAndAllowances.callAsync(makerAddress, [
+ erc20AssetData,
+ erc721AssetData,
+ ]);
+ expect(erc20Balance).to.be.bignumber.equal(balance);
+ expect(erc721Balance).to.be.bignumber.equal(ERC721_BALANCE);
+ expect(erc20Allowance).to.be.bignumber.equal(allowance);
+ expect(erc721Allowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ });
+ });
+ describe('getTraderInfo', () => {
+ beforeEach(async () => {
+ signedOrder = await orderFactory.newSignedOrderAsync();
+ });
+ it('should return the correct info when no balances or allowances are set', async () => {
+ const traderInfo = await orderValidator.getTraderInfo.callAsync(signedOrder, takerAddress);
+ expect(traderInfo.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should return the correct info when balances and allowances are set', async () => {
+ const makerBalance = new BigNumber(123);
+ const makerAllowance = new BigNumber(456);
+ const makerZrxBalance = new BigNumber(789);
+ const takerZrxAllowance = new BigNumber(987);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.setBalance.sendTransactionAsync(makerAddress, makerBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, makerAllowance, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.setBalance.sendTransactionAsync(makerAddress, makerZrxBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, takerZrxAllowance, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isApproved = true;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const traderInfo = await orderValidator.getTraderInfo.callAsync(signedOrder, takerAddress);
+ expect(traderInfo.makerBalance).to.be.bignumber.equal(makerBalance);
+ expect(traderInfo.makerAllowance).to.be.bignumber.equal(makerAllowance);
+ expect(traderInfo.takerBalance).to.be.bignumber.equal(ERC721_BALANCE);
+ expect(traderInfo.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance);
+ expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance);
+ });
+ });
+ describe('getTradersInfo', () => {
+ beforeEach(async () => {
+ signedOrder = await orderFactory.newSignedOrderAsync();
+ signedOrder2 = await orderFactory.newSignedOrderAsync({
+ takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId2),
+ });
+ });
+ it('should return the correct info when no balances or allowances have been set', async () => {
+ const orders = [signedOrder, signedOrder2];
+ const takers = [takerAddress, takerAddress];
+ const [traderInfo1, traderInfo2] = await orderValidator.getTradersInfo.callAsync(orders, takers);
+ expect(traderInfo1.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should return the correct info when balances and allowances are set', async () => {
+ const makerBalance = new BigNumber(123);
+ const makerAllowance = new BigNumber(456);
+ const makerZrxBalance = new BigNumber(789);
+ const takerZrxAllowance = new BigNumber(987);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.setBalance.sendTransactionAsync(makerAddress, makerBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, makerAllowance, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.setBalance.sendTransactionAsync(makerAddress, makerZrxBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, takerZrxAllowance, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isApproved = true;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId2),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const orders = [signedOrder, signedOrder2];
+ const takers = [takerAddress, takerAddress];
+ const [traderInfo1, traderInfo2] = await orderValidator.getTradersInfo.callAsync(orders, takers);
+
+ expect(traderInfo1.makerBalance).to.be.bignumber.equal(makerBalance);
+ expect(traderInfo1.makerAllowance).to.be.bignumber.equal(makerAllowance);
+ expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance);
+ expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance);
+ expect(traderInfo2.makerBalance).to.be.bignumber.equal(makerBalance);
+ expect(traderInfo2.makerAllowance).to.be.bignumber.equal(makerAllowance);
+ expect(traderInfo2.takerBalance).to.be.bignumber.equal(ERC721_BALANCE);
+ expect(traderInfo2.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance);
+ expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance);
+ });
+ });
+ describe('getOrderAndTraderInfo', () => {
+ beforeEach(async () => {
+ signedOrder = await orderFactory.newSignedOrderAsync();
+ });
+ it('should return the correct info when no balances or allowances are set', async () => {
+ const [orderInfo, traderInfo] = await orderValidator.getOrderAndTraderInfo.callAsync(
+ signedOrder,
+ takerAddress,
+ );
+ const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder);
+ expect(orderInfo.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ expect(orderInfo.orderHash).to.be.equal(expectedOrderHash);
+ expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should return the correct info when balances and allowances are set', async () => {
+ const makerBalance = new BigNumber(123);
+ const makerAllowance = new BigNumber(456);
+ const makerZrxBalance = new BigNumber(789);
+ const takerZrxAllowance = new BigNumber(987);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.setBalance.sendTransactionAsync(makerAddress, makerBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, makerAllowance, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.setBalance.sendTransactionAsync(makerAddress, makerZrxBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, takerZrxAllowance, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isApproved = true;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const [orderInfo, traderInfo] = await orderValidator.getOrderAndTraderInfo.callAsync(
+ signedOrder,
+ takerAddress,
+ );
+ const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder);
+ expect(orderInfo.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ expect(orderInfo.orderHash).to.be.equal(expectedOrderHash);
+ expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.makerBalance).to.be.bignumber.equal(makerBalance);
+ expect(traderInfo.makerAllowance).to.be.bignumber.equal(makerAllowance);
+ expect(traderInfo.takerBalance).to.be.bignumber.equal(ERC721_BALANCE);
+ expect(traderInfo.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance);
+ expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance);
+ });
+ });
+ describe('getOrdersAndTradersInfo', () => {
+ beforeEach(async () => {
+ signedOrder = await orderFactory.newSignedOrderAsync();
+ signedOrder2 = await orderFactory.newSignedOrderAsync({
+ takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId2),
+ });
+ });
+ it('should return the correct info when no balances or allowances have been set', async () => {
+ const orders = [signedOrder, signedOrder2];
+ const takers = [takerAddress, takerAddress];
+ const [
+ [orderInfo1, orderInfo2],
+ [traderInfo1, traderInfo2],
+ ] = await orderValidator.getOrdersAndTradersInfo.callAsync(orders, takers);
+ const expectedOrderHash1 = orderHashUtils.getOrderHashHex(signedOrder);
+ const expectedOrderHash2 = orderHashUtils.getOrderHashHex(signedOrder2);
+ expect(orderInfo1.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ expect(orderInfo1.orderHash).to.be.equal(expectedOrderHash1);
+ expect(orderInfo1.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(orderInfo2.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ expect(orderInfo2.orderHash).to.be.equal(expectedOrderHash2);
+ expect(orderInfo2.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ });
+ it('should return the correct info when balances and allowances are set', async () => {
+ const makerBalance = new BigNumber(123);
+ const makerAllowance = new BigNumber(456);
+ const makerZrxBalance = new BigNumber(789);
+ const takerZrxAllowance = new BigNumber(987);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.setBalance.sendTransactionAsync(makerAddress, makerBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, makerAllowance, {
+ from: makerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.setBalance.sendTransactionAsync(makerAddress, makerZrxBalance),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, takerZrxAllowance, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const isApproved = true;
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, {
+ from: takerAddress,
+ }),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId2),
+ constants.AWAIT_TRANSACTION_MINED_MS,
+ );
+ const orders = [signedOrder, signedOrder2];
+ const takers = [takerAddress, takerAddress];
+ const [
+ [orderInfo1, orderInfo2],
+ [traderInfo1, traderInfo2],
+ ] = await orderValidator.getOrdersAndTradersInfo.callAsync(orders, takers);
+ const expectedOrderHash1 = orderHashUtils.getOrderHashHex(signedOrder);
+ const expectedOrderHash2 = orderHashUtils.getOrderHashHex(signedOrder2);
+ expect(orderInfo1.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ expect(orderInfo1.orderHash).to.be.equal(expectedOrderHash1);
+ expect(orderInfo1.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(orderInfo2.orderStatus).to.be.equal(OrderStatus.FILLABLE);
+ expect(orderInfo2.orderHash).to.be.equal(expectedOrderHash2);
+ expect(orderInfo2.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.makerBalance).to.be.bignumber.equal(makerBalance);
+ expect(traderInfo1.makerAllowance).to.be.bignumber.equal(makerAllowance);
+ expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance);
+ expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance);
+ expect(traderInfo2.makerBalance).to.be.bignumber.equal(makerBalance);
+ expect(traderInfo2.makerAllowance).to.be.bignumber.equal(makerAllowance);
+ expect(traderInfo2.takerBalance).to.be.bignumber.equal(ERC721_BALANCE);
+ expect(traderInfo2.takerAllowance).to.be.bignumber.equal(ERC721_ALLOWANCE);
+ expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance);
+ expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance);
+ });
+ });
+});
+// tslint:disable:max-file-line-count
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..28a4ef011
--- /dev/null
+++ b/contracts/extensions/test/utils/balance_threshold_wrapper.ts
@@ -0,0 +1,283 @@
+import { artifacts as protocolArtifacts, ExchangeContract } from '@0x/contracts-protocol';
+import {
+ FillResults,
+ formatters,
+ LogDecoder,
+ OrderInfo,
+ orderUtils,
+ TransactionFactory,
+} from '@0x/contracts-test-utils';
+import { artifacts as tokensArtifacts } from '@0x/contracts-tokens';
+import { SignedOrder } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter';
+import { artifacts } from '../../src/artifacts';
+
+export class BalanceThresholdWrapper {
+ private readonly _balanceThresholdFilter: BalanceThresholdFilterContract;
+ private readonly _signerTransactionFactory: TransactionFactory;
+ private readonly _exchange: ExchangeContract;
+ private readonly _web3Wrapper: Web3Wrapper;
+ private readonly _logDecoder: LogDecoder;
+ constructor(
+ balanceThresholdFilter: BalanceThresholdFilterContract,
+ exchangeContract: ExchangeContract,
+ signerTransactionFactory: TransactionFactory,
+ provider: Provider,
+ ) {
+ this._balanceThresholdFilter = balanceThresholdFilter;
+ this._exchange = exchangeContract;
+ this._signerTransactionFactory = signerTransactionFactory;
+ this._web3Wrapper = new Web3Wrapper(provider);
+ this._logDecoder = new LogDecoder(this._web3Wrapper, {
+ ...artifacts,
+ ...tokensArtifacts,
+ ...protocolArtifacts,
+ });
+ }
+ public async fillOrderAsync(
+ signedOrder: SignedOrder,
+ from: string,
+ opts: { takerAssetFillAmount?: BigNumber } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
+ const data = this._exchange.fillOrder.getABIEncodedTransactionData(
+ params.order,
+ params.takerAssetFillAmount,
+ params.signature,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async fillOrKillOrderAsync(
+ signedOrder: SignedOrder,
+ from: string,
+ opts: { takerAssetFillAmount?: BigNumber } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
+ const data = this._exchange.fillOrKillOrder.getABIEncodedTransactionData(
+ params.order,
+ params.takerAssetFillAmount,
+ params.signature,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async fillOrderNoThrowAsync(
+ signedOrder: SignedOrder,
+ from: string,
+ opts: { takerAssetFillAmount?: BigNumber; gas?: number } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
+ const data = this._exchange.fillOrderNoThrow.getABIEncodedTransactionData(
+ params.order,
+ params.takerAssetFillAmount,
+ params.signature,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
+ return txReceipt;
+ }
+ public async batchFillOrdersAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { takerAssetFillAmounts?: BigNumber[] } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
+ const data = this._exchange.batchFillOrders.getABIEncodedTransactionData(
+ params.orders,
+ params.takerAssetFillAmounts,
+ params.signatures,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async batchFillOrKillOrdersAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { takerAssetFillAmounts?: BigNumber[] } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
+ const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData(
+ params.orders,
+ params.takerAssetFillAmounts,
+ params.signatures,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async batchFillOrdersNoThrowAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number } = {},
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts);
+ const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData(
+ params.orders,
+ params.takerAssetFillAmounts,
+ params.signatures,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
+ return txReceipt;
+ }
+ public async marketSellOrdersAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { takerAssetFillAmount: BigNumber },
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount);
+ const data = this._exchange.marketSellOrders.getABIEncodedTransactionData(
+ params.orders,
+ params.takerAssetFillAmount,
+ params.signatures,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async marketSellOrdersNoThrowAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { takerAssetFillAmount: BigNumber; gas?: number },
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount);
+ const data = this._exchange.marketSellOrdersNoThrow.getABIEncodedTransactionData(
+ params.orders,
+ params.takerAssetFillAmount,
+ params.signatures,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
+ return txReceipt;
+ }
+ public async marketBuyOrdersAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { makerAssetFillAmount: BigNumber },
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount);
+ const data = this._exchange.marketBuyOrders.getABIEncodedTransactionData(
+ params.orders,
+ params.makerAssetFillAmount,
+ params.signatures,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async marketBuyOrdersNoThrowAsync(
+ orders: SignedOrder[],
+ from: string,
+ opts: { makerAssetFillAmount: BigNumber; gas?: number },
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount);
+ const data = this._exchange.marketBuyOrdersNoThrow.getABIEncodedTransactionData(
+ params.orders,
+ params.makerAssetFillAmount,
+ params.signatures,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from, opts.gas);
+ return txReceipt;
+ }
+ public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createCancel(signedOrder);
+ const data = this._exchange.cancelOrder.getABIEncodedTransactionData(params.order);
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async batchCancelOrdersAsync(
+ orders: SignedOrder[],
+ from: string,
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = formatters.createBatchCancel(orders);
+ const data = this._exchange.batchCancelOrders.getABIEncodedTransactionData(params.orders);
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async cancelOrdersUpToAsync(salt: BigNumber, from: string): Promise<TransactionReceiptWithDecodedLogs> {
+ const data = this._exchange.cancelOrdersUpTo.getABIEncodedTransactionData(salt);
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise<BigNumber> {
+ const filledAmount = await this._exchange.filled.callAsync(orderHashHex);
+ return filledAmount;
+ }
+ public async isCancelledAsync(orderHashHex: string): Promise<boolean> {
+ const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex);
+ return isCancelled;
+ }
+ public async getOrderEpochAsync(makerAddress: string, senderAddress: string): Promise<BigNumber> {
+ const orderEpoch = await this._exchange.orderEpoch.callAsync(makerAddress, senderAddress);
+ return orderEpoch;
+ }
+ public async getOrderInfoAsync(signedOrder: SignedOrder): Promise<OrderInfo> {
+ const orderInfo = await this._exchange.getOrderInfo.callAsync(signedOrder);
+ return orderInfo;
+ }
+ public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise<OrderInfo[]> {
+ const ordersInfo = (await this._exchange.getOrdersInfo.callAsync(signedOrders)) as OrderInfo[];
+ return ordersInfo;
+ }
+ public async matchOrdersAsync(
+ signedOrderLeft: SignedOrder,
+ signedOrderRight: SignedOrder,
+ from: string,
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight);
+ const data = await this._exchange.matchOrders.getABIEncodedTransactionData(
+ params.left,
+ params.right,
+ params.leftSignature,
+ params.rightSignature,
+ );
+ const txReceipt = this._executeTransactionAsync(data, from);
+ return txReceipt;
+ }
+ public async getFillOrderResultsAsync(
+ signedOrder: SignedOrder,
+ from: string,
+ opts: { takerAssetFillAmount?: BigNumber } = {},
+ ): Promise<FillResults> {
+ const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
+ const fillResults = await this._exchange.fillOrder.callAsync(
+ params.order,
+ params.takerAssetFillAmount,
+ params.signature,
+ { from },
+ );
+ return fillResults;
+ }
+ public abiEncodeFillOrder(signedOrder: SignedOrder, opts: { takerAssetFillAmount?: BigNumber } = {}): string {
+ const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount);
+ const data = this._exchange.fillOrder.getABIEncodedTransactionData(
+ params.order,
+ params.takerAssetFillAmount,
+ params.signature,
+ );
+ return data;
+ }
+ public getBalanceThresholdAddress(): string {
+ return this._balanceThresholdFilter.address;
+ }
+ public getExchangeAddress(): string {
+ return this._exchange.address;
+ }
+ private async _executeTransactionAsync(
+ abiEncodedExchangeTxData: string,
+ from: string,
+ gas?: number,
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const signedExchangeTx = this._signerTransactionFactory.newSignedTransaction(abiEncodedExchangeTxData);
+ const txOpts = _.isUndefined(gas) ? { from } : { from, gas };
+ const txHash = await this._balanceThresholdFilter.executeTransaction.sendTransactionAsync(
+ signedExchangeTx.salt,
+ signedExchangeTx.signerAddress,
+ signedExchangeTx.data,
+ signedExchangeTx.signature,
+ txOpts,
+ );
+ const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
+ return txReceipt;
+ }
+}