aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contracts')
-rw-r--r--packages/contracts/src/utils/asset_proxy_utils.ts74
-rw-r--r--packages/contracts/src/utils/exchange_wrapper.ts22
-rw-r--r--packages/contracts/src/utils/order_utils.ts9
-rw-r--r--packages/contracts/src/utils/types.ts48
-rw-r--r--packages/contracts/test/exchange/core.ts30
-rw-r--r--packages/contracts/test/exchange/match_orders.ts873
-rw-r--r--packages/contracts/test/exchange/transactions.ts4
-rw-r--r--packages/contracts/test/utils/match_order_tester.ts353
8 files changed, 1390 insertions, 23 deletions
diff --git a/packages/contracts/src/utils/asset_proxy_utils.ts b/packages/contracts/src/utils/asset_proxy_utils.ts
index dc31c3497..9a26a9ca7 100644
--- a/packages/contracts/src/utils/asset_proxy_utils.ts
+++ b/packages/contracts/src/utils/asset_proxy_utils.ts
@@ -8,6 +8,9 @@ export const assetProxyUtils = {
encodeAssetProxyId(assetProxyId: AssetProxyId): Buffer {
return ethUtil.toBuffer(assetProxyId);
},
+ decodeAssetProxyId(encodedAssetProxyId: Buffer): AssetProxyId {
+ return ethUtil.bufferToInt(encodedAssetProxyId);
+ },
encodeAddress(address: string): Buffer {
if (!ethUtil.isValidAddress(address)) {
throw new Error(`Invalid Address: ${address}`);
@@ -15,12 +18,24 @@ export const assetProxyUtils = {
const encodedAddress = ethUtil.toBuffer(address);
return encodedAddress;
},
+ decodeAddress(encodedAddress: Buffer): string {
+ const address = ethUtil.bufferToHex(encodedAddress);
+ if (!ethUtil.isValidAddress(address)) {
+ throw new Error(`Invalid Address: ${address}`);
+ }
+ return address;
+ },
encodeUint256(value: BigNumber): Buffer {
const formattedValue = new BN(value.toString(10));
const encodedValue = ethUtil.toBuffer(formattedValue);
const paddedValue = ethUtil.setLengthLeft(encodedValue, 32);
return paddedValue;
},
+ decodeUint256(encodedValue: Buffer): BigNumber {
+ const formattedValue = ethUtil.bufferToHex(encodedValue);
+ const value = new BigNumber(formattedValue, 16);
+ return value;
+ },
encodeERC20ProxyData(tokenAddress: string): string {
const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC20);
const encodedAddress = assetProxyUtils.encodeAddress(tokenAddress);
@@ -28,6 +43,28 @@ export const assetProxyUtils = {
const encodedMetadataHex = ethUtil.bufferToHex(encodedMetadata);
return encodedMetadataHex;
},
+ decodeERC20ProxyData(proxyData: string): string /* tokenAddress */ {
+ const encodedProxyMetadata = ethUtil.toBuffer(proxyData);
+ if (encodedProxyMetadata.byteLength !== 21) {
+ throw new Error(
+ `Could not decode ERC20 Proxy Data. Expected length of encoded data to be 21. Got ${
+ encodedProxyMetadata.byteLength
+ }`,
+ );
+ }
+ const encodedAssetProxyId = encodedProxyMetadata.slice(0, 1);
+ const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId);
+ if (assetProxyId !== AssetProxyId.ERC20) {
+ throw new Error(
+ `Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be ERC20 (${
+ AssetProxyId.ERC20
+ }), but got ${assetProxyId}`,
+ );
+ }
+ const encodedTokenAddress = encodedProxyMetadata.slice(1, 21);
+ const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress);
+ return tokenAddress;
+ },
encodeERC721ProxyData(tokenAddress: string, tokenId: BigNumber): string {
const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC721);
const encodedAddress = assetProxyUtils.encodeAddress(tokenAddress);
@@ -36,4 +73,41 @@ export const assetProxyUtils = {
const encodedMetadataHex = ethUtil.bufferToHex(encodedMetadata);
return encodedMetadataHex;
},
+ decodeERC721ProxyData(proxyData: string): [string /* tokenAddress */, BigNumber /* tokenId */] {
+ const encodedProxyMetadata = ethUtil.toBuffer(proxyData);
+ if (encodedProxyMetadata.byteLength !== 53) {
+ throw new Error(
+ `Could not decode ERC20 Proxy Data. Expected length of encoded data to be 53. Got ${
+ encodedProxyMetadata.byteLength
+ }`,
+ );
+ }
+ const encodedAssetProxyId = encodedProxyMetadata.slice(0, 1);
+ const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId);
+ if (assetProxyId !== AssetProxyId.ERC721) {
+ throw new Error(
+ `Could not decode ERC721 Proxy Data. Expected Asset Proxy Id to be ERC721 (${
+ AssetProxyId.ERC721
+ }), but got ${assetProxyId}`,
+ );
+ }
+ const encodedTokenAddress = encodedProxyMetadata.slice(1, 21);
+ const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress);
+ const encodedTokenId = encodedProxyMetadata.slice(21, 53);
+ const tokenId = assetProxyUtils.decodeUint256(encodedTokenId);
+ return [tokenAddress, tokenId];
+ },
+ decodeProxyDataId(proxyData: string): AssetProxyId {
+ const encodedProxyMetadata = ethUtil.toBuffer(proxyData);
+ if (encodedProxyMetadata.byteLength < 1) {
+ throw new Error(
+ `Could not decode Proxy Data. Expected length of encoded data to be at least 1. Got ${
+ encodedProxyMetadata.byteLength
+ }`,
+ );
+ }
+ const encodedAssetProxyId = encodedProxyMetadata.slice(0, 1);
+ const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId);
+ return assetProxyId;
+ },
};
diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts
index 27fdd698f..6d36198f2 100644
--- a/packages/contracts/src/utils/exchange_wrapper.ts
+++ b/packages/contracts/src/utils/exchange_wrapper.ts
@@ -231,4 +231,26 @@ export class ExchangeWrapper {
tx.logs = _.map(tx.logs, log => this._logDecoder.decodeLogOrThrow(log));
return tx;
}
+ public async getOrderInfoAsync(
+ signedOrder: SignedOrder,
+ ): Promise<[number /* orderStatus */, string /* orderHash */, BigNumber /* orderTakerAssetAmountFilled */]> {
+ const orderInfo: [number, string, BigNumber] = await this._exchange.getOrderInfo.callAsync(signedOrder);
+ return orderInfo;
+ }
+ public async matchOrdersAsync(
+ signedOrderLeft: SignedOrder,
+ signedOrderRight: SignedOrder,
+ from: string,
+ ): Promise<TransactionReceiptWithDecodedLogs> {
+ const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight);
+ const txHash = await this._exchange.matchOrders.sendTransactionAsync(
+ params.left,
+ params.right,
+ params.leftSignature,
+ params.rightSignature,
+ { from },
+ );
+ const tx = await this._getTxWithDecodedExchangeLogsAsync(txHash);
+ return tx;
+ }
}
diff --git a/packages/contracts/src/utils/order_utils.ts b/packages/contracts/src/utils/order_utils.ts
index 10bbf4f7c..7a482ad9e 100644
--- a/packages/contracts/src/utils/order_utils.ts
+++ b/packages/contracts/src/utils/order_utils.ts
@@ -80,4 +80,13 @@ export const orderUtils = {
const orderHashHex = `0x${orderHashBuff.toString('hex')}`;
return orderHashHex;
},
+ createMatchOrders(signedOrderLeft: SignedOrder, signedOrderRight: SignedOrder) {
+ const fill = {
+ left: orderUtils.getOrderStruct(signedOrderLeft),
+ right: orderUtils.getOrderStruct(signedOrderRight),
+ leftSignature: signedOrderLeft.signature,
+ rightSignature: signedOrderRight.signature,
+ };
+ return fill;
+ },
};
diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts
index 234b14ef9..ce7fbcbe1 100644
--- a/packages/contracts/src/utils/types.ts
+++ b/packages/contracts/src/utils/types.ts
@@ -74,12 +74,27 @@ export interface Token {
swarmHash: string;
}
-export enum ExchangeContractErrs {
- ERROR_ORDER_EXPIRED,
- ERROR_ORDER_FULLY_FILLED,
- ERROR_ORDER_CANCELLED,
- ERROR_ROUNDING_ERROR_TOO_LARGE,
- ERROR_INSUFFICIENT_BALANCE_OR_ALLOWANCE,
+export enum ExchangeStatus {
+ /// Default Status ///
+ INVALID, // General invalid status
+
+ /// General Exchange Statuses ///
+ SUCCESS, // Indicates a successful operation
+ ROUNDING_ERROR_TOO_LARGE, // Rounding error too large
+ INSUFFICIENT_BALANCE_OR_ALLOWANCE, // Insufficient balance or allowance for token transfer
+ TAKER_ASSET_FILL_AMOUNT_TOO_LOW, // takerAssetFillAmount is <= 0
+ INVALID_SIGNATURE, // Invalid signature
+ INVALID_SENDER, // Invalid sender
+ INVALID_TAKER, // Invalid taker
+ INVALID_MAKER, // Invalid maker
+
+ /// Order State Statuses ///
+ ORDER_INVALID_MAKER_ASSET_AMOUNT, // Order does not have a valid maker asset amount
+ ORDER_INVALID_TAKER_ASSET_AMOUNT, // Order does not have a valid taker asset amount
+ ORDER_FILLABLE, // Order is fillable
+ ORDER_EXPIRED, // Order has already expired
+ ORDER_FULLY_FILLED, // Order is fully filled
+ ORDER_CANCELLED, // Order has been cancelled
}
export enum ContractName {
@@ -143,3 +158,24 @@ export interface SignedTransaction {
data: string;
signature: string;
}
+
+export interface TransferAmountsByMatchOrders {
+ // Left Maker
+ amountBoughtByLeftMaker: BigNumber;
+ amountSoldByLeftMaker: BigNumber;
+ amountReceivedByLeftMaker: BigNumber;
+ feePaidByLeftMaker: BigNumber;
+ // Right Maker
+ amountBoughtByRightMaker: BigNumber;
+ amountSoldByRightMaker: BigNumber;
+ amountReceivedByRightMaker: BigNumber;
+ feePaidByRightMaker: BigNumber;
+ // Taker
+ amountReceivedByTaker: BigNumber;
+ feePaidByTakerLeft: BigNumber;
+ feePaidByTakerRight: BigNumber;
+ totalFeePaidByTaker: BigNumber;
+ // Fee Recipients
+ feeReceivedLeft: BigNumber;
+ feeReceivedRight: BigNumber;
+}
diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts
index 6c190d4da..e9c51ea18 100644
--- a/packages/contracts/test/exchange/core.ts
+++ b/packages/contracts/test/exchange/core.ts
@@ -12,7 +12,7 @@ import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c
import {
CancelContractEventArgs,
ExchangeContract,
- ExchangeErrorContractEventArgs,
+ ExchangeStatusContractEventArgs,
FillContractEventArgs,
} from '../../src/contract_wrappers/generated/exchange';
import { artifacts } from '../../src/utils/artifacts';
@@ -25,8 +25,8 @@ import { ERC721Wrapper } from '../../src/utils/erc721_wrapper';
import { ExchangeWrapper } from '../../src/utils/exchange_wrapper';
import { OrderFactory } from '../../src/utils/order_factory';
import { orderUtils } from '../../src/utils/order_utils';
-import { AssetProxyId, ERC20BalancesByOwner, ExchangeContractErrs, SignedOrder } from '../../src/utils/types';
-import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper';
+import { AssetProxyId, ContractName, ERC20BalancesByOwner, ExchangeStatus, SignedOrder } from '../../src/utils/types';
+import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
@@ -556,9 +556,9 @@ describe('Exchange core', () => {
const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
expect(res.logs).to.have.length(1);
- const log = res.logs[0] as LogWithDecodedArgs<ExchangeErrorContractEventArgs>;
- const errCode = log.args.errorId;
- expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED);
+ const log = res.logs[0] as LogWithDecodedArgs<ExchangeStatusContractEventArgs>;
+ const statusCode = log.args.statusId;
+ expect(statusCode).to.be.equal(ExchangeStatus.ORDER_EXPIRED);
});
it('should log an error event if no value is filled', async () => {
@@ -567,9 +567,9 @@ describe('Exchange core', () => {
const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
expect(res.logs).to.have.length(1);
- const log = res.logs[0] as LogWithDecodedArgs<ExchangeErrorContractEventArgs>;
- const errCode = log.args.errorId;
- expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_FULLY_FILLED);
+ const log = res.logs[0] as LogWithDecodedArgs<ExchangeStatusContractEventArgs>;
+ const statusCode = log.args.statusId;
+ expect(statusCode).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
});
});
@@ -635,9 +635,9 @@ describe('Exchange core', () => {
const res = await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress);
expect(res.logs).to.have.length(1);
- const log = res.logs[0] as LogWithDecodedArgs<ExchangeErrorContractEventArgs>;
- const errCode = log.args.errorId;
- expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_CANCELLED);
+ const log = res.logs[0] as LogWithDecodedArgs<ExchangeStatusContractEventArgs>;
+ const statusCode = log.args.statusId;
+ expect(statusCode).to.be.equal(ExchangeStatus.ORDER_CANCELLED);
});
it('should log error if order is expired', async () => {
@@ -647,9 +647,9 @@ describe('Exchange core', () => {
const res = await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress);
expect(res.logs).to.have.length(1);
- const log = res.logs[0] as LogWithDecodedArgs<ExchangeErrorContractEventArgs>;
- const errCode = log.args.errorId;
- expect(errCode).to.be.equal(ExchangeContractErrs.ERROR_ORDER_EXPIRED);
+ const log = res.logs[0] as LogWithDecodedArgs<ExchangeStatusContractEventArgs>;
+ const statusCode = log.args.statusId;
+ expect(statusCode).to.be.equal(ExchangeStatus.ORDER_EXPIRED);
});
});
diff --git a/packages/contracts/test/exchange/match_orders.ts b/packages/contracts/test/exchange/match_orders.ts
new file mode 100644
index 000000000..a114d92ac
--- /dev/null
+++ b/packages/contracts/test/exchange/match_orders.ts
@@ -0,0 +1,873 @@
+import { LogWithDecodedArgs, ZeroEx } from '0x.js';
+import { BlockchainLifecycle } from '@0xproject/dev-utils';
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import ethUtil = require('ethereumjs-util');
+import * as _ from 'lodash';
+
+import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token';
+import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token';
+import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy';
+import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy';
+import {
+ CancelContractEventArgs,
+ ExchangeContract,
+ ExchangeStatusContractEventArgs,
+ FillContractEventArgs,
+} from '../../src/contract_wrappers/generated/exchange';
+import { assetProxyUtils } from '../../src/utils/asset_proxy_utils';
+import { constants } from '../../src/utils/constants';
+import { crypto } from '../../src/utils/crypto';
+import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
+import { ERC721Wrapper } from '../../src/utils/erc721_wrapper';
+import { ExchangeWrapper } from '../../src/utils/exchange_wrapper';
+import { OrderFactory } from '../../src/utils/order_factory';
+import { orderUtils } from '../../src/utils/order_utils';
+import {
+ AssetProxyId,
+ ContractName,
+ ERC20BalancesByOwner,
+ ERC721TokenIdsByOwner,
+ ExchangeStatus,
+ SignedOrder,
+} from '../../src/utils/types';
+import { chaiSetup } from '../utils/chai_setup';
+import { deployer } from '../utils/deployer';
+import { provider, web3Wrapper } from '../utils/web3_wrapper';
+
+import { MatchOrderTester } from '../utils/match_order_tester';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+describe('matchOrdersAndVerifyBalancesAsync', () => {
+ 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 erc721Token: DummyERC721TokenContract;
+ let exchange: ExchangeContract;
+ let erc20Proxy: ERC20ProxyContract;
+ let erc721Proxy: ERC721ProxyContract;
+
+ let erc20BalancesByOwner: ERC20BalancesByOwner;
+ let erc721TokenIdsByOwner: ERC721TokenIdsByOwner;
+ let exchangeWrapper: ExchangeWrapper;
+ let erc20Wrapper: ERC20Wrapper;
+ let erc721Wrapper: ERC721Wrapper;
+ let orderFactoryLeft: OrderFactory;
+ let orderFactoryRight: OrderFactory;
+
+ let erc721LeftMakerAssetIds: BigNumber[];
+ let erc721RightMakerAssetIds: BigNumber[];
+ let erc721TakerAssetIds: BigNumber[];
+
+ let defaultERC20MakerAssetAddress: string;
+ let defaultERC20TakerAssetAddress: string;
+ let defaultERC721AssetAddress: string;
+
+ let matchOrderTester: MatchOrderTester;
+
+ let zeroEx: ZeroEx;
+
+ before(async () => {
+ // Create accounts
+ const accounts = await web3Wrapper.getAvailableAddressesAsync();
+ const usedAddresses = ([
+ owner,
+ makerAddressLeft,
+ makerAddressRight,
+ takerAddress,
+ feeRecipientAddressLeft,
+ feeRecipientAddressRight,
+ ] = accounts);
+ // Create wrappers
+ erc20Wrapper = new ERC20Wrapper(deployer, provider, usedAddresses, owner);
+ erc721Wrapper = new ERC721Wrapper(deployer, provider, usedAddresses, owner);
+ // Deploy ERC20 token & ERC20 proxy
+ [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync();
+ erc20Proxy = await erc20Wrapper.deployProxyAsync();
+ await erc20Wrapper.setBalancesAndAllowancesAsync();
+ // Deploy ERC721 token and proxy
+ [erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
+ erc721Proxy = await erc721Wrapper.deployProxyAsync();
+ await erc721Wrapper.setBalancesAndAllowancesAsync();
+ const erc721Balances = await erc721Wrapper.getBalancesAsync();
+ erc721LeftMakerAssetIds = erc721Balances[makerAddressLeft][erc721Token.address];
+ erc721RightMakerAssetIds = erc721Balances[makerAddressRight][erc721Token.address];
+ erc721TakerAssetIds = erc721Balances[takerAddress][erc721Token.address];
+ // Depoy exchange
+ const exchangeInstance = await deployer.deployAsync(ContractName.Exchange, [
+ assetProxyUtils.encodeERC20ProxyData(zrxToken.address),
+ ]);
+ exchange = new ExchangeContract(exchangeInstance.abi, exchangeInstance.address, provider);
+ zeroEx = new ZeroEx(provider, {
+ exchangeContractAddress: exchange.address,
+ networkId: constants.TESTRPC_NETWORK_ID,
+ });
+ exchangeWrapper = new ExchangeWrapper(exchange, zeroEx);
+ await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner);
+ await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC721, erc721Proxy.address, owner);
+ // Authorize ERC20 and ERC721 trades by exchange
+ await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, {
+ from: owner,
+ });
+ await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, {
+ from: owner,
+ });
+ // Set default addresses
+ defaultERC20MakerAssetAddress = erc20TokenA.address;
+ defaultERC20TakerAssetAddress = erc20TokenB.address;
+ defaultERC721AssetAddress = erc721Token.address;
+ // Create default order parameters
+ const defaultOrderParams = {
+ ...constants.STATIC_ORDER_PARAMS,
+ exchangeAddress: exchange.address,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ };
+ const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)];
+ orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParams);
+ const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)];
+ orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParams);
+ // Set match order tester
+ matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper);
+ });
+ beforeEach(async () => {
+ await blockchainLifecycle.startAsync();
+ });
+ afterEach(async () => {
+ await blockchainLifecycle.revertAsync();
+ });
+ describe('matchOrders', () => {
+ beforeEach(async () => {
+ erc20BalancesByOwner = await erc20Wrapper.getBalancesAsync();
+ erc721TokenIdsByOwner = await erc721Wrapper.getBalancesAsync();
+ });
+
+ it('should transfer the correct amounts when orders completely fill each other', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match signedOrderLeft with signedOrderRight
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was fully filled
+ const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify right order was fully filled
+ const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(
+ signedOrderRight,
+ );
+ expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ });
+
+ 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 = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Store original taker balance
+ const takerInitialBalances = _.cloneDeep(erc20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress]);
+ // Match signedOrderLeft with signedOrderRight
+ let newERC20BalancesByOwner: ERC20BalancesByOwner;
+ let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
+ [
+ newERC20BalancesByOwner,
+ newERC721TokenIdsByOwner,
+ ] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was fully filled
+ const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify right order was fully filled
+ const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(
+ signedOrderRight,
+ );
+ expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify taker did not take a profit
+ expect(takerInitialBalances).to.be.deep.equal(
+ newERC20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress],
+ );
+ });
+
+ it('should transfer the correct amounts when left order is completely filled and right order is partially filled', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(20), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(4), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was fully filled
+ const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify right order was partially filled
+ const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(
+ signedOrderRight,
+ );
+ expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
+ });
+
+ it('should transfer the correct amounts when right order is completely filled and left order is partially filled', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(50), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was partially filled
+ const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
+ // Verify right order was fully filled
+ const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(
+ signedOrderRight,
+ );
+ expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ });
+
+ it('should transfer the correct amounts when consecutive calls are used to completely fill the left order', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(50), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ let newERC20BalancesByOwner: ERC20BalancesByOwner;
+ let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
+ [
+ newERC20BalancesByOwner,
+ newERC721TokenIdsByOwner,
+ ] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was partially filled
+ const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
+ // Verify right order was fully filled
+ const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(
+ signedOrderRight,
+ );
+ expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Construct second right order
+ // Note: This order needs makerAssetAmount=90/takerAssetAmount=[anything <= 45] to fully fill the right order.
+ // However, we use 100/50 to ensure a partial fill as we want to go down the "left fill"
+ // branch in the contract twice for this test.
+ const signedOrderRight2 = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(50), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match signedOrderLeft with signedOrderRight2
+ const leftTakerAssetFilledAmount = signedOrderRight.makerAssetAmount;
+ const rightTakerAssetFilledAmount = new BigNumber(0);
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight2,
+ zrxToken.address,
+ takerAddress,
+ newERC20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ leftTakerAssetFilledAmount,
+ rightTakerAssetFilledAmount,
+ );
+ // Verify left order was fully filled
+ const leftOrderInfo2: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(
+ signedOrderLeft,
+ );
+ expect(leftOrderInfo2[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify second right order was partially filled
+ const rightOrderInfo2: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(
+ signedOrderRight2,
+ );
+ expect(rightOrderInfo2[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
+ });
+
+ it('should transfer the correct amounts when consecutive calls are used to completely fill the right order', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(50), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ let newERC20BalancesByOwner: ERC20BalancesByOwner;
+ let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
+ [
+ newERC20BalancesByOwner,
+ newERC721TokenIdsByOwner,
+ ] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was partially filled
+ const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify right order was fully filled
+ const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(
+ signedOrderRight,
+ );
+ expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
+ // Create second left order
+ // Note: This order needs makerAssetAmount=96/takerAssetAmount=48 to fully fill the right order.
+ // However, we use 100/50 to ensure a partial fill as we want to go down the "right fill"
+ // branch in the contract twice for this test.
+ const signedOrderLeft2 = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(50), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ // Match signedOrderLeft2 with signedOrderRight
+ const leftTakerAssetFilledAmount = new BigNumber(0);
+ const takerAmountReceived = newERC20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress].minus(
+ erc20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress],
+ );
+ const rightTakerAssetFilledAmount = signedOrderLeft.makerAssetAmount.minus(takerAmountReceived);
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft2,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ newERC20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ leftTakerAssetFilledAmount,
+ rightTakerAssetFilledAmount,
+ );
+ // Verify second left order was partially filled
+ const leftOrderInfo2: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(
+ signedOrderLeft2,
+ );
+ expect(leftOrderInfo2[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FILLABLE);
+ // Verify right order was fully filled
+ const rightOrderInfo2: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(
+ signedOrderRight,
+ );
+ expect(rightOrderInfo2[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ });
+
+ it('should transfer the correct amounts if fee recipient is the same across both matched orders', async () => {
+ const feeRecipientAddress = feeRecipientAddressLeft;
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress,
+ });
+ // Match orders
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ });
+
+ it('should transfer the correct amounts if taker is also the left order maker', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ takerAddress = signedOrderLeft.makerAddress;
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ });
+
+ it('should transfer the correct amounts if taker is also the right order maker', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ takerAddress = signedOrderRight.makerAddress;
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ });
+
+ it('should transfer the correct amounts if taker is also the left fee recipient', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ takerAddress = feeRecipientAddressLeft;
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ });
+
+ it('should transfer the correct amounts if taker is also the right fee recipient', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ takerAddress = feeRecipientAddressRight;
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ });
+
+ it('should transfer the correct amounts if left maker is the left fee recipient and right maker is the right fee recipient', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: makerAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: makerAddressRight,
+ });
+ // Match orders
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ });
+
+ it('Should not transfer any amounts if left order is not fillable', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Cancel left order
+ await exchangeWrapper.cancelOrderAsync(signedOrderLeft, signedOrderLeft.makerAddress);
+ // Match orders
+ await exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress);
+ // Verify balances did not change
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20BalancesByOwner);
+ });
+
+ it('Should not transfer any amounts if right order is not fillable', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Cancel right order
+ await exchangeWrapper.cancelOrderAsync(signedOrderRight, signedOrderRight.makerAddress);
+ // Match orders
+ await exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress);
+ // Verify balances did not change
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+ expect(newBalances).to.be.deep.equal(erc20BalancesByOwner);
+ });
+
+ it('should throw if there is not a positive spread', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(100), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(1), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(200), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ return expect(
+ matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw if the left maker asset is not equal to the right taker asset ', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ return expect(
+ matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should throw if the right maker asset is not equal to the left taker asset', async () => {
+ // Create orders to match
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(5), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(2), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ return expect(
+ matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ ),
+ ).to.be.rejectedWith(constants.REVERT);
+ });
+
+ it('should transfer correct amounts when left order maker asset is an ERC721 token', async () => {
+ // Create orders to match
+ const erc721TokenToTransfer = erc721LeftMakerAssetIds[0];
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ makerAssetAmount: new BigNumber(1),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: new BigNumber(1),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was fully filled
+ const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify right order was fully filled
+ const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(
+ signedOrderRight,
+ );
+ expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ });
+
+ it('should transfer correct amounts when right order maker asset is an ERC721 token', async () => {
+ // Create orders to match
+ const erc721TokenToTransfer = erc721RightMakerAssetIds[0];
+ const signedOrderLeft = orderFactoryLeft.newSignedOrder({
+ makerAddress: makerAddressLeft,
+ makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ takerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer),
+ makerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ takerAssetAmount: new BigNumber(1),
+ feeRecipientAddress: feeRecipientAddressLeft,
+ });
+ const signedOrderRight = orderFactoryRight.newSignedOrder({
+ makerAddress: makerAddressRight,
+ makerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer),
+ takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress),
+ makerAssetAmount: new BigNumber(1),
+ takerAssetAmount: ZeroEx.toBaseUnitAmount(new BigNumber(10), 18),
+ feeRecipientAddress: feeRecipientAddressRight,
+ });
+ // Match orders
+ await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ zrxToken.address,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ );
+ // Verify left order was fully filled
+ const leftOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
+ expect(leftOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ // Verify right order was fully filled
+ const rightOrderInfo: [number, string, BigNumber] = await exchangeWrapper.getOrderInfoAsync(
+ signedOrderRight,
+ );
+ expect(rightOrderInfo[0] as ExchangeStatus).to.be.equal(ExchangeStatus.ORDER_FULLY_FILLED);
+ });
+ });
+}); // tslint:disable-line:max-file-line-count
diff --git a/packages/contracts/test/exchange/transactions.ts b/packages/contracts/test/exchange/transactions.ts
index a71b50a61..482475554 100644
--- a/packages/contracts/test/exchange/transactions.ts
+++ b/packages/contracts/test/exchange/transactions.ts
@@ -21,7 +21,7 @@ import { TransactionFactory } from '../../src/utils/transaction_factory';
import {
AssetProxyId,
ERC20BalancesByOwner,
- ExchangeContractErrs,
+ ExchangeStatus,
OrderStruct,
SignatureType,
SignedOrder,
@@ -197,7 +197,7 @@ describe('Exchange transactions', () => {
it('should cancel the order when signed by maker and called by sender', async () => {
await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress);
- const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress);
+ const res = await exchangeWrapper.fillOrderAsync(signedOrder, senderAddress);
const newBalances = await erc20Wrapper.getBalancesAsync();
expect(newBalances).to.deep.equal(erc20Balances);
});
diff --git a/packages/contracts/test/utils/match_order_tester.ts b/packages/contracts/test/utils/match_order_tester.ts
new file mode 100644
index 000000000..91c2ab0a3
--- /dev/null
+++ b/packages/contracts/test/utils/match_order_tester.ts
@@ -0,0 +1,353 @@
+import { LogWithDecodedArgs, ZeroEx } from '0x.js';
+import { BlockchainLifecycle } from '@0xproject/dev-utils';
+import { BigNumber } from '@0xproject/utils';
+import * as chai from 'chai';
+import ethUtil = require('ethereumjs-util');
+import * as _ from 'lodash';
+
+import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token';
+import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token';
+import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy';
+import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy';
+import {
+ CancelContractEventArgs,
+ ExchangeContract,
+ FillContractEventArgs,
+} from '../../src/contract_wrappers/generated/exchange';
+import { assetProxyUtils } from '../../src/utils/asset_proxy_utils';
+import { constants } from '../../src/utils/constants';
+import { crypto } from '../../src/utils/crypto';
+import { ERC20Wrapper } from '../../src/utils/erc20_wrapper';
+import { ERC721Wrapper } from '../../src/utils/erc721_wrapper';
+import { ExchangeWrapper } from '../../src/utils/exchange_wrapper';
+import { OrderFactory } from '../../src/utils/order_factory';
+import { orderUtils } from '../../src/utils/order_utils';
+import {
+ AssetProxyId,
+ ContractName,
+ ERC20BalancesByOwner,
+ ERC721TokenIdsByOwner,
+ ExchangeStatus,
+ SignedOrder,
+ TransferAmountsByMatchOrders as TransferAmounts,
+} from '../../src/utils/types';
+import { chaiSetup } from '../utils/chai_setup';
+import { deployer } from '../utils/deployer';
+import { provider, web3Wrapper } from '../utils/web3_wrapper';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
+
+export class MatchOrderTester {
+ private _exchangeWrapper: ExchangeWrapper;
+ private _erc20Wrapper: ERC20Wrapper;
+ private _erc721Wrapper: ERC721Wrapper;
+
+ /// @dev Calculates the expected balances of order makers, fee recipients, and the taker,
+ /// as a result of matching two orders.
+ /// @param signedOrderLeft First matched order.
+ /// @param signedOrderRight Second matched order.
+ /// @param feeTokenAddress Address of ERC20 fee token.
+ /// @param takerAddress Address of taker (the address who matched the two orders)
+ /// @param erc20BalancesByOwner Current ERC20 balances.
+ /// @param erc721TokenIdsByOwner Current ERC721 token owners.
+ /// @param expectedTransferAmounts A struct containing the expected transfer amounts.
+ /// @return Expected ERC20 balances & ERC721 token owners after orders have been matched.
+ private static _calculateExpectedBalances(
+ signedOrderLeft: SignedOrder,
+ signedOrderRight: SignedOrder,
+ feeTokenAddress: string,
+ takerAddress: string,
+ erc20BalancesByOwner: ERC20BalancesByOwner,
+ erc721TokenIdsByOwner: ERC721TokenIdsByOwner,
+ expectedTransferAmounts: TransferAmounts,
+ ): [ERC20BalancesByOwner, ERC721TokenIdsByOwner] {
+ const makerAddressLeft = signedOrderLeft.makerAddress;
+ const makerAddressRight = signedOrderRight.makerAddress;
+ const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress;
+ const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress;
+ // Operations are performed on copies of the balances
+ const expectedNewERC20BalancesByOwner = _.cloneDeep(erc20BalancesByOwner);
+ const expectedNewERC721TokenIdsByOwner = _.cloneDeep(erc721TokenIdsByOwner);
+ // Left Maker Asset (Right Taker Asset)
+ const makerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.makerAssetData);
+ if (makerAssetProxyIdLeft === AssetProxyId.ERC20) {
+ // Decode asset data
+ const makerAssetAddressLeft = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.makerAssetData);
+ const takerAssetAddressRight = makerAssetAddressLeft;
+ // Left Maker
+ expectedNewERC20BalancesByOwner[makerAddressLeft][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[
+ makerAddressLeft
+ ][makerAssetAddressLeft].minus(expectedTransferAmounts.amountSoldByLeftMaker);
+ // Right Maker
+ expectedNewERC20BalancesByOwner[makerAddressRight][
+ takerAssetAddressRight
+ ] = expectedNewERC20BalancesByOwner[makerAddressRight][takerAssetAddressRight].add(
+ expectedTransferAmounts.amountReceivedByRightMaker,
+ );
+ // Taker
+ expectedNewERC20BalancesByOwner[takerAddress][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[
+ takerAddress
+ ][makerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByTaker);
+ } else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) {
+ // Decode asset data
+ let makerAssetAddressLeft;
+ let makerAssetIdLeft;
+ [makerAssetAddressLeft, makerAssetIdLeft] = assetProxyUtils.decodeERC721ProxyData(
+ signedOrderLeft.makerAssetData,
+ );
+ const takerAssetAddressRight = makerAssetAddressLeft;
+ const takerAssetIdRight = makerAssetIdLeft;
+ // Left Maker
+ _.remove(expectedNewERC721TokenIdsByOwner[makerAddressLeft][makerAssetAddressLeft], makerAssetIdLeft);
+ // Right Maker
+ expectedNewERC721TokenIdsByOwner[makerAddressRight][takerAssetAddressRight].push(takerAssetIdRight);
+ // Taker: Since there is only 1 asset transferred, the taker does not receive any of the left maker asset.
+ }
+ // Left Taker Asset (Right Maker Asset)
+ // Note: This exchange is only between the order makers: the Taker does not receive any of the left taker asset.
+ const takerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.takerAssetData);
+ if (takerAssetProxyIdLeft === AssetProxyId.ERC20) {
+ // Decode asset data
+ const takerAssetAddressLeft = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.takerAssetData);
+ const makerAssetAddressRight = takerAssetAddressLeft;
+ // Left Maker
+ expectedNewERC20BalancesByOwner[makerAddressLeft][takerAssetAddressLeft] = expectedNewERC20BalancesByOwner[
+ makerAddressLeft
+ ][takerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByLeftMaker);
+ // Right Maker
+ expectedNewERC20BalancesByOwner[makerAddressRight][
+ makerAssetAddressRight
+ ] = expectedNewERC20BalancesByOwner[makerAddressRight][makerAssetAddressRight].minus(
+ expectedTransferAmounts.amountSoldByRightMaker,
+ );
+ } else if (takerAssetProxyIdLeft === AssetProxyId.ERC721) {
+ // Decode asset data
+ let makerAssetAddressRight;
+ let makerAssetIdRight;
+ [makerAssetAddressRight, makerAssetIdRight] = assetProxyUtils.decodeERC721ProxyData(
+ signedOrderRight.makerAssetData,
+ );
+ const takerAssetAddressLeft = makerAssetAddressRight;
+ const takerAssetIdLeft = makerAssetIdRight;
+ // Right Maker
+ _.remove(expectedNewERC721TokenIdsByOwner[makerAddressRight][makerAssetAddressRight], makerAssetIdRight);
+ // Left Maker
+ expectedNewERC721TokenIdsByOwner[makerAddressLeft][takerAssetAddressLeft].push(takerAssetIdLeft);
+ }
+ // Left Maker Fees
+ expectedNewERC20BalancesByOwner[makerAddressLeft][feeTokenAddress] = expectedNewERC20BalancesByOwner[
+ makerAddressLeft
+ ][feeTokenAddress].minus(expectedTransferAmounts.feePaidByLeftMaker);
+ // Right Maker Fees
+ expectedNewERC20BalancesByOwner[makerAddressRight][feeTokenAddress] = expectedNewERC20BalancesByOwner[
+ makerAddressRight
+ ][feeTokenAddress].minus(expectedTransferAmounts.feePaidByRightMaker);
+ // Taker Fees
+ expectedNewERC20BalancesByOwner[takerAddress][feeTokenAddress] = expectedNewERC20BalancesByOwner[takerAddress][
+ feeTokenAddress
+ ].minus(expectedTransferAmounts.totalFeePaidByTaker);
+ // Left Fee Recipient Fees
+ expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][feeTokenAddress] = expectedNewERC20BalancesByOwner[
+ feeRecipientAddressLeft
+ ][feeTokenAddress].add(expectedTransferAmounts.feeReceivedLeft);
+ // Right Fee Recipient Fees
+ expectedNewERC20BalancesByOwner[feeRecipientAddressRight][feeTokenAddress] = expectedNewERC20BalancesByOwner[
+ feeRecipientAddressRight
+ ][feeTokenAddress].add(expectedTransferAmounts.feeReceivedRight);
+
+ return [expectedNewERC20BalancesByOwner, expectedNewERC721TokenIdsByOwner];
+ }
+
+ /// @dev Compares a pair of ERC20 balances and a pair of ERC721 token owners.
+ /// @param expectedNewERC20BalancesByOwner Expected ERC20 balances.
+ /// @param realERC20BalancesByOwner Actual ERC20 balances.
+ /// @param expectedNewERC721TokenIdsByOwner Expected ERC721 token owners.
+ /// @param realERC721TokenIdsByOwner Actual ERC20 token owners.
+ /// @return True only if ERC20 balances match and ERC721 token owners match.
+ private static _compareExpectedAndRealBalances(
+ expectedNewERC20BalancesByOwner: ERC20BalancesByOwner,
+ realERC20BalancesByOwner: ERC20BalancesByOwner,
+ expectedNewERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
+ realERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
+ ) {
+ // ERC20 Balances
+ const erc20BalancesMatch = _.isEqual(expectedNewERC20BalancesByOwner, realERC20BalancesByOwner);
+ if (!erc20BalancesMatch) {
+ return false;
+ }
+ // ERC721 Token Ids
+ const sortedExpectedNewERC721TokenIdsByOwner = _.mapValues(
+ expectedNewERC721TokenIdsByOwner,
+ tokenIdsByOwner => {
+ _.mapValues(tokenIdsByOwner, tokenIds => {
+ _.sortBy(tokenIds);
+ });
+ },
+ );
+ const sortedNewERC721TokenIdsByOwner = _.mapValues(realERC721TokenIdsByOwner, tokenIdsByOwner => {
+ _.mapValues(tokenIdsByOwner, tokenIds => {
+ _.sortBy(tokenIds);
+ });
+ });
+ const erc721TokenIdsMatch = _.isEqual(sortedExpectedNewERC721TokenIdsByOwner, sortedNewERC721TokenIdsByOwner);
+ return erc721TokenIdsMatch;
+ }
+
+ /// @dev Constructs new MatchOrderTester.
+ /// @param exchangeWrapper Used to call to the Exchange.
+ /// @param erc20Wrapper Used to fetch ERC20 balances.
+ /// @param erc721Wrapper Used to fetch ERC721 token owners.
+ constructor(exchangeWrapper: ExchangeWrapper, erc20Wrapper: ERC20Wrapper, erc721Wrapper: ERC721Wrapper) {
+ this._exchangeWrapper = exchangeWrapper;
+ this._erc20Wrapper = erc20Wrapper;
+ this._erc721Wrapper = erc721Wrapper;
+ }
+
+ /// @dev Matches two complementary orders and validates results.
+ /// Validation either succeeds or throws.
+ /// @param signedOrderLeft First matched order.
+ /// @param signedOrderRight Second matched order.
+ /// @param feeTokenAddress Address of ERC20 fee token.
+ /// @param takerAddress Address of taker (the address who matched the two orders)
+ /// @param erc20BalancesByOwner Current ERC20 balances.
+ /// @param erc721TokenIdsByOwner Current ERC721 token owners.
+ /// @param initialTakerAssetFilledAmountLeft Current amount the left order has been filled.
+ /// @param initialTakerAssetFilledAmountRight Current amount the right order has been filled.
+ /// @return New ERC20 balances & ERC721 token owners.
+ public async matchOrdersAndVerifyBalancesAsync(
+ signedOrderLeft: SignedOrder,
+ signedOrderRight: SignedOrder,
+ feeTokenAddress: string,
+ takerAddress: string,
+ erc20BalancesByOwner: ERC20BalancesByOwner,
+ erc721TokenIdsByOwner: ERC721TokenIdsByOwner,
+ initialTakerAssetFilledAmountLeft?: BigNumber,
+ initialTakerAssetFilledAmountRight?: BigNumber,
+ ): Promise<[ERC20BalancesByOwner, ERC721TokenIdsByOwner]> {
+ // Test setup & verify preconditions
+ const makerAddressLeft = signedOrderLeft.makerAddress;
+ const makerAddressRight = signedOrderRight.makerAddress;
+ const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress;
+ const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress;
+ // Verify Left order preconditions
+ const takerAssetFilledAmountBeforeLeft = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrderLeft),
+ );
+ const expectedLeftOrderFillAmoutBeforeMatch = initialTakerAssetFilledAmountLeft
+ ? initialTakerAssetFilledAmountLeft
+ : new BigNumber(0);
+ expect(takerAssetFilledAmountBeforeLeft).to.be.bignumber.equal(expectedLeftOrderFillAmoutBeforeMatch);
+ // Verify Right order preconditions
+ const takerAssetFilledAmountBeforeRight = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrderRight),
+ );
+ const expectedRightOrderFillAmoutBeforeMatch = initialTakerAssetFilledAmountRight
+ ? initialTakerAssetFilledAmountRight
+ : new BigNumber(0);
+ expect(takerAssetFilledAmountBeforeRight).to.be.bignumber.equal(expectedRightOrderFillAmoutBeforeMatch);
+ // Match left & right orders
+ await this._exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress);
+ const newERC20BalancesByOwner = await this._erc20Wrapper.getBalancesAsync();
+ const newERC721TokenIdsByOwner = await this._erc721Wrapper.getBalancesAsync();
+ // Calculate expected balance changes
+ const expectedTransferAmounts = await this._calculateExpectedTransferAmountsAsync(
+ signedOrderLeft,
+ signedOrderRight,
+ expectedLeftOrderFillAmoutBeforeMatch,
+ expectedRightOrderFillAmoutBeforeMatch,
+ );
+ let expectedERC20BalancesByOwner: ERC20BalancesByOwner;
+ let expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
+ [expectedERC20BalancesByOwner, expectedERC721TokenIdsByOwner] = MatchOrderTester._calculateExpectedBalances(
+ signedOrderLeft,
+ signedOrderRight,
+ feeTokenAddress,
+ takerAddress,
+ erc20BalancesByOwner,
+ erc721TokenIdsByOwner,
+ expectedTransferAmounts,
+ );
+ // Assert our expected balances are equal to the actual balances
+ const expectedBalancesMatchRealBalances = MatchOrderTester._compareExpectedAndRealBalances(
+ expectedERC20BalancesByOwner,
+ newERC20BalancesByOwner,
+ expectedERC721TokenIdsByOwner,
+ newERC721TokenIdsByOwner,
+ );
+ expect(expectedBalancesMatchRealBalances).to.be.true();
+ return [newERC20BalancesByOwner, newERC721TokenIdsByOwner];
+ }
+
+ /// @dev Calculates expected transfer amounts between order makers, fee recipients, and
+ /// the taker when two orders are matched.
+ /// @param signedOrderLeft First matched order.
+ /// @param signedOrderRight Second matched order.
+ /// @param expectedLeftOrderFillAmoutBeforeMatch How much we expect the left order has been filled, prior to matching orders.
+ /// @param expectedRightOrderFillAmoutBeforeMatch How much we expect the right order has been filled, prior to matching orders.
+ /// @return TransferAmounts A struct containing the expected transfer amounts.
+ private async _calculateExpectedTransferAmountsAsync(
+ signedOrderLeft: SignedOrder,
+ signedOrderRight: SignedOrder,
+ expectedLeftOrderFillAmoutBeforeMatch: BigNumber,
+ expectedRightOrderFillAmoutBeforeMatch: BigNumber,
+ ): Promise<TransferAmounts> {
+ let amountBoughtByLeftMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrderLeft),
+ );
+ amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(expectedLeftOrderFillAmoutBeforeMatch);
+ const amountSoldByLeftMaker = amountBoughtByLeftMaker
+ .times(signedOrderLeft.makerAssetAmount)
+ .dividedToIntegerBy(signedOrderLeft.takerAssetAmount);
+ const amountReceivedByRightMaker = amountBoughtByLeftMaker
+ .times(signedOrderRight.takerAssetAmount)
+ .dividedToIntegerBy(signedOrderRight.makerAssetAmount);
+ const amountReceivedByTaker = amountSoldByLeftMaker.minus(amountReceivedByRightMaker);
+ let amountBoughtByRightMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
+ orderUtils.getOrderHashHex(signedOrderRight),
+ );
+ amountBoughtByRightMaker = amountBoughtByRightMaker.minus(expectedRightOrderFillAmoutBeforeMatch);
+ const amountSoldByRightMaker = amountBoughtByRightMaker
+ .times(signedOrderRight.makerAssetAmount)
+ .dividedToIntegerBy(signedOrderRight.takerAssetAmount);
+ const amountReceivedByLeftMaker = amountSoldByRightMaker;
+ const feePaidByLeftMaker = signedOrderLeft.makerFee
+ .times(amountSoldByLeftMaker)
+ .dividedToIntegerBy(signedOrderLeft.makerAssetAmount);
+ const feePaidByRightMaker = signedOrderRight.makerFee
+ .times(amountSoldByRightMaker)
+ .dividedToIntegerBy(signedOrderRight.makerAssetAmount);
+ const feePaidByTakerLeft = signedOrderLeft.takerFee
+ .times(amountSoldByLeftMaker)
+ .dividedToIntegerBy(signedOrderLeft.makerAssetAmount);
+ const feePaidByTakerRight = signedOrderRight.takerFee
+ .times(amountSoldByRightMaker)
+ .dividedToIntegerBy(signedOrderRight.makerAssetAmount);
+ const totalFeePaidByTaker = feePaidByTakerLeft.add(feePaidByTakerRight);
+ const feeReceivedLeft = feePaidByLeftMaker.add(feePaidByTakerLeft);
+ const feeReceivedRight = feePaidByRightMaker.add(feePaidByTakerRight);
+ // Return values
+ const expectedTransferAmounts = {
+ // Left Maker
+ amountBoughtByLeftMaker,
+ amountSoldByLeftMaker,
+ amountReceivedByLeftMaker,
+ feePaidByLeftMaker,
+ // Right Maker
+ amountBoughtByRightMaker,
+ amountSoldByRightMaker,
+ amountReceivedByRightMaker,
+ feePaidByRightMaker,
+ // Taker
+ amountReceivedByTaker,
+ feePaidByTakerLeft,
+ feePaidByTakerRight,
+ totalFeePaidByTaker,
+ // Fee Recipients
+ feeReceivedLeft,
+ feeReceivedRight,
+ };
+ return expectedTransferAmounts;
+ }
+}