Atomic Order Matching - Tests

This commit is contained in:
Greg Hysen
2018-05-10 14:22:49 -07:00
parent a4c821eb60
commit 9b1015bbce
10 changed files with 1412 additions and 33 deletions

View File

@@ -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;
},
};

View File

@@ -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;
}
}

View File

@@ -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;
},
};

View File

@@ -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;
}

View File

@@ -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);
});
});

View File

@@ -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

View File

@@ -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);
});

View File

@@ -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;
}
}

View File

@@ -1,3 +1,4 @@
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
import chaiAsPromised = require('chai-as-promised');
import ChaiBigNumber = require('chai-bignumber');
@@ -9,5 +10,12 @@ export const chaiSetup = {
chai.use(ChaiBigNumber());
chai.use(dirtyChai);
chai.use(chaiAsPromised);
// Node uses '.inspect()' instead of '.toString()' for log messages
// HACK: Typescript won't allow me to mess with BigNumber.prototype
// directly, so I create an instance and then get the prototype.
Object.getPrototypeOf(new BigNumber(0)).inspect = function() {
return this.toString();
};
},
};

View File

@@ -1879,10 +1879,6 @@ buffer-from@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-0.1.2.tgz#15f4b9bcef012044df31142c14333caf6e0260d0"
buffer-from@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531"
buffer-indexof@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c"
@@ -2513,7 +2509,15 @@ concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
concat-stream@^1.4.10, concat-stream@^1.5.0, concat-stream@^1.5.1:
concat-stream@^1.4.10, concat-stream@^1.5.0:
version "1.6.1"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.1.tgz#261b8f518301f1d834e36342b9fea095d2620a26"
dependencies:
inherits "^2.0.3"
readable-stream "^2.2.2"
typedarray "^0.0.6"
concat-stream@^1.5.1:
version "1.6.2"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
dependencies:
@@ -10139,14 +10143,14 @@ semver-sort@0.0.4:
semver "^5.0.3"
semver-regex "^1.0.0"
"semver@2 || 3 || 4 || 5", semver@5.5.0, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
semver@5.4.1, semver@~5.4.1:
"semver@2 || 3 || 4 || 5", semver@5.4.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@~5.4.1:
version "5.4.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
semver@5.5.0, semver@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
semver@^4.1.0:
version "4.3.6"
resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da"