Merge pull request #1008 from 0xProject/fix/contracts/robustMatching

Robustness in Order Matching
This commit is contained in:
Amir Bandeali
2018-08-24 19:00:53 -07:00
committed by GitHub
4 changed files with 859 additions and 288 deletions

View File

@@ -109,7 +109,7 @@ contract MixinMatchOrders is
rightOrderInfo.orderTakerAssetFilledAmount,
matchedFillResults.right
);
// Settle matched orders. Succeeds or throws.
settleMatchedOrders(
leftOrder,
@@ -165,62 +165,85 @@ contract MixinMatchOrders is
pure
returns (LibFillResults.MatchedFillResults memory matchedFillResults)
{
// We settle orders at the exchange rate of the right order.
// The amount saved by the left maker goes to the taker.
// Either the left or right order will be fully filled; possibly both.
// The left order is fully filled iff the right order can sell more than left can buy.
// That is: the amount required to fill the left order is less than or equal to
// the amount we can spend from the right order:
// <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightMakerToTakerRatio>
// <leftTakerAssetAmountRemaining> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount> / <rightOrder.takerAssetAmount>
// <leftTakerAssetAmountRemaining> * <rightOrder.takerAssetAmount> <= <rightTakerAssetAmountRemaining> * <rightOrder.makerAssetAmount>
// Derive maker asset amounts for left & right orders, given store taker assert amounts
uint256 leftTakerAssetAmountRemaining = safeSub(leftOrder.takerAssetAmount, leftOrderTakerAssetFilledAmount);
uint256 leftMakerAssetAmountRemaining = getPartialAmountFloor(
leftOrder.makerAssetAmount,
leftOrder.takerAssetAmount,
leftTakerAssetAmountRemaining
);
uint256 rightTakerAssetAmountRemaining = safeSub(rightOrder.takerAssetAmount, rightOrderTakerAssetFilledAmount);
uint256 leftTakerAssetFilledAmount;
uint256 rightTakerAssetFilledAmount;
if (
safeMul(leftTakerAssetAmountRemaining, rightOrder.takerAssetAmount) <=
safeMul(rightTakerAssetAmountRemaining, rightOrder.makerAssetAmount)
) {
// Left order will be fully filled: maximally fill left
leftTakerAssetFilledAmount = leftTakerAssetAmountRemaining;
uint256 rightMakerAssetAmountRemaining = getPartialAmountFloor(
rightOrder.makerAssetAmount,
rightOrder.takerAssetAmount,
rightTakerAssetAmountRemaining
);
// The right order receives an amount proportional to how much was spent.
rightTakerAssetFilledAmount = getPartialAmountFloor(
rightOrder.takerAssetAmount,
rightOrder.makerAssetAmount,
leftTakerAssetFilledAmount
// Calculate fill results for maker and taker assets: at least one order will be fully filled.
// The maximum amount the left maker can buy is `leftTakerAssetAmountRemaining`
// The maximum amount the right maker can sell is `rightMakerAssetAmountRemaining`
// We have two distinct cases for calculating the fill results:
// Case 1.
// If the left maker can buy more than the right maker can sell, then only the right order is fully filled.
// If the left maker can buy exactly what the right maker can sell, then both orders are fully filled.
// Case 2.
// If the left maker cannot buy more than the right maker can sell, then only the left order is fully filled.
if (leftTakerAssetAmountRemaining >= rightMakerAssetAmountRemaining) {
// Case 1: Right order is fully filled
matchedFillResults.right.makerAssetFilledAmount = rightMakerAssetAmountRemaining;
matchedFillResults.right.takerAssetFilledAmount = rightTakerAssetAmountRemaining;
matchedFillResults.left.takerAssetFilledAmount = matchedFillResults.right.makerAssetFilledAmount;
// Round down to ensure the maker's exchange rate does not exceed the price specified by the order.
// We favor the maker when the exchange rate must be rounded.
matchedFillResults.left.makerAssetFilledAmount = getPartialAmountFloor(
leftOrder.makerAssetAmount,
leftOrder.takerAssetAmount,
matchedFillResults.left.takerAssetFilledAmount
);
} else {
// Right order will be fully filled: maximally fill right
rightTakerAssetFilledAmount = rightTakerAssetAmountRemaining;
// The left order receives an amount proportional to how much was spent.
leftTakerAssetFilledAmount = getPartialAmountFloor(
rightOrder.makerAssetAmount,
// Case 2: Left order is fully filled
matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining;
matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining;
matchedFillResults.right.makerAssetFilledAmount = matchedFillResults.left.takerAssetFilledAmount;
// Round up to ensure the maker's exchange rate does not exceed the price specified by the order.
// We favor the maker when the exchange rate must be rounded.
matchedFillResults.right.takerAssetFilledAmount = getPartialAmountCeil(
rightOrder.takerAssetAmount,
rightTakerAssetFilledAmount
rightOrder.makerAssetAmount,
matchedFillResults.right.makerAssetFilledAmount
);
}
// Calculate fill results for left order
matchedFillResults.left = calculateFillResults(
leftOrder,
leftTakerAssetFilledAmount
);
// Calculate fill results for right order
matchedFillResults.right = calculateFillResults(
rightOrder,
rightTakerAssetFilledAmount
);
// Calculate amount given to taker
matchedFillResults.leftMakerAssetSpreadAmount = safeSub(
matchedFillResults.left.makerAssetFilledAmount,
matchedFillResults.right.takerAssetFilledAmount
);
// Compute fees for left order
matchedFillResults.left.makerFeePaid = getPartialAmountFloor(
matchedFillResults.left.makerAssetFilledAmount,
leftOrder.makerAssetAmount,
leftOrder.makerFee
);
matchedFillResults.left.takerFeePaid = getPartialAmountFloor(
matchedFillResults.left.takerAssetFilledAmount,
leftOrder.takerAssetAmount,
leftOrder.takerFee
);
// Compute fees for right order
matchedFillResults.right.makerFeePaid = getPartialAmountFloor(
matchedFillResults.right.makerAssetFilledAmount,
rightOrder.makerAssetAmount,
rightOrder.makerFee
);
matchedFillResults.right.takerFeePaid = getPartialAmountFloor(
matchedFillResults.right.takerAssetFilledAmount,
rightOrder.takerAssetAmount,
rightOrder.takerFee
);
// Return fill results
return matchedFillResults;
}

View File

@@ -3,7 +3,6 @@ import { assetDataUtils } from '@0xproject/order-utils';
import { RevertReason } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as chai from 'chai';
import * as _ from 'lodash';
import { DummyERC20TokenContract } from '../../generated_contract_wrappers/dummy_erc20_token';
@@ -14,18 +13,15 @@ import { ExchangeContract } from '../../generated_contract_wrappers/exchange';
import { ReentrantERC20TokenContract } from '../../generated_contract_wrappers/reentrant_erc20_token';
import { artifacts } from '../utils/artifacts';
import { expectTransactionFailedAsync } from '../utils/assertions';
import { chaiSetup } from '../utils/chai_setup';
import { constants } from '../utils/constants';
import { ERC20Wrapper } from '../utils/erc20_wrapper';
import { ERC721Wrapper } from '../utils/erc721_wrapper';
import { ExchangeWrapper } from '../utils/exchange_wrapper';
import { MatchOrderTester } from '../utils/match_order_tester';
import { OrderFactory } from '../utils/order_factory';
import { ERC20BalancesByOwner, ERC721TokenIdsByOwner, OrderInfo, OrderStatus } from '../utils/types';
import { ERC20BalancesByOwner, ERC721TokenIdsByOwner } from '../utils/types';
import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('matchOrders', () => {
@@ -177,6 +173,144 @@ describe('matchOrders', () => {
erc721TokenIdsByOwner = await erc721Wrapper.getBalancesAsync();
});
it('Should give right maker a better price when correct price is not integral', async () => {
// Create orders to match
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAddress: makerAddressLeft,
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2000), 0),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1001), 0),
feeRecipientAddress: feeRecipientAddressLeft,
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAddress: makerAddressRight,
makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress),
takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress),
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 0),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(3000), 0),
feeRecipientAddress: feeRecipientAddressRight,
});
// Note:
// The maker/taker fee percentage paid on the right order differs because
// they received different sale prices. Similarly, the right maker pays a
// slightly higher lower than the right taker.
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2000), 0),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1001), 0),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1001), 0),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(301), 0),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10.01), 16), // 10.01%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1699), 0),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber('10.0333333333333333'), 16), // 10.03%
};
// Match signedOrderLeft with signedOrderRight
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
});
it('Should give left maker a better price when correct price is not integral', async () => {
// Create orders to match
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAddress: makerAddressLeft,
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(12), 0),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(97), 0),
feeRecipientAddress: feeRecipientAddressLeft,
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAddress: makerAddressRight,
makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress),
takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress),
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0),
feeRecipientAddress: feeRecipientAddressRight,
});
// Note:
// The maker/taker fee percentage paid on the left order differs because
// they received different sale prices.
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(11), 0),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.6666666666666666'), 16), // 91.6%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 0),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.7525773195876288'), 16), // 91.75%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
};
// Match signedOrderLeft with signedOrderRight
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
});
it('Should transfer correct amounts when right order fill amount deviates from amount derived by `Exchange.fillOrder`', async () => {
// Create orders to match
const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({
makerAddress: makerAddressLeft,
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 0),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 0),
feeRecipientAddress: feeRecipientAddressLeft,
});
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAddress: makerAddressRight,
makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress),
takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress),
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 0),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 0),
feeRecipientAddress: feeRecipientAddressRight,
});
// TODO: These values will change after implementation of rounding up has been merged
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 0),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 0),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Right Maker
// Note:
// For order [4,2] valid fill amounts through `Exchange.fillOrder` would be [2, 1] or [4, 2]
// In this case we have fill amounts of [3, 1] which is attainable through
// `Exchange.matchOrders` but not `Exchange.fillOrder`
// Note:
// The right maker fee differs from the right taker fee because their exchange rate differs.
// The right maker always receives the better exchange and fee price.
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 0),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 0),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 16), // 75%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), 0),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
};
// Match signedOrderLeft with signedOrderRight
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
});
const reentrancyTest = (functionNames: string[]) => {
_.forEach(functionNames, async (functionName: string, functionId: number) => {
const description = `should not allow matchOrders to reenter the Exchange contract via ${functionName}`;
@@ -217,19 +351,28 @@ describe('matchOrders', () => {
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
});
// Match signedOrderLeft with signedOrderRight
await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
};
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
// Verify left order was fully filled
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
// Verify right order was fully filled
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
});
it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => {
@@ -242,32 +385,29 @@ describe('matchOrders', () => {
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
});
// Store original taker balance
const takerInitialBalances = _.cloneDeep(erc20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress]);
// Match signedOrderLeft with signedOrderRight
let newERC20BalancesByOwner: ERC20BalancesByOwner;
let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
// prettier-ignore
[
newERC20BalancesByOwner,
// tslint:disable-next-line:trailing-comma
newERC721TokenIdsByOwner
] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
};
// Match signedOrderLeft with signedOrderRight
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
);
// Verify left order was fully filled
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
// Verify right order was fully filled
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
// Verify taker did not take a profit
expect(takerInitialBalances).to.be.deep.equal(
newERC20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress],
expectedTransferAmounts,
);
});
@@ -281,20 +421,30 @@ describe('matchOrders', () => {
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18),
});
// Match orders
await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
// Match signedOrderLeft with signedOrderRight
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 16), // 50%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 16), // 50%
};
// Match signedOrderLeft with signedOrderRight
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
// Verify left order was fully filled
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
// Verify right order was partially filled
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FILLABLE);
});
it('should transfer the correct amounts when right order is completely filled and left order is partially filled', async () => {
@@ -307,20 +457,30 @@ describe('matchOrders', () => {
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
});
// Match orders
await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
// Match signedOrderLeft with signedOrderRight
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
};
// Match signedOrderLeft with signedOrderRight
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
// Verify left order was partially filled
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FILLABLE);
// Verify right order was fully filled
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
});
it('should transfer the correct amounts when consecutive calls are used to completely fill the left order', async () => {
@@ -336,24 +496,33 @@ describe('matchOrders', () => {
// Match orders
let newERC20BalancesByOwner: ERC20BalancesByOwner;
let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
};
// prettier-ignore
[
newERC20BalancesByOwner,
// tslint:disable-next-line:trailing-comma
newERC721TokenIdsByOwner
] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
] = await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
// Verify left order was partially filled
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FILLABLE);
// Verify right order was fully filled
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.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"
@@ -365,21 +534,30 @@ describe('matchOrders', () => {
// Match signedOrderLeft with signedOrderRight2
const leftTakerAssetFilledAmount = signedOrderRight.makerAssetAmount;
const rightTakerAssetFilledAmount = new BigNumber(0);
await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
const expectedTransferAmounts2 = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(45), 18),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 18),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90% (10% paid earlier)
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 18),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(45), 18),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90% (10% paid earlier)
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90%
};
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight2,
takerAddress,
newERC20BalancesByOwner,
erc721TokenIdsByOwner,
newERC721TokenIdsByOwner,
expectedTransferAmounts2,
leftTakerAssetFilledAmount,
rightTakerAssetFilledAmount,
);
// Verify left order was fully filled
const leftOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
expect(leftOrderInfo2.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
// Verify second right order was partially filled
const rightOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight2);
expect(rightOrderInfo2.orderStatus as OrderStatus).to.be.equal(OrderStatus.FILLABLE);
});
it('should transfer the correct amounts when consecutive calls are used to completely fill the right order', async () => {
@@ -396,24 +574,34 @@ describe('matchOrders', () => {
// Match orders
let newERC20BalancesByOwner: ERC20BalancesByOwner;
let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 16), // 4%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(6), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 16), // 4%
};
// prettier-ignore
[
newERC20BalancesByOwner,
// tslint:disable-next-line:trailing-comma
newERC721TokenIdsByOwner
] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
] = await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
// Verify left order was partially filled
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
// Verify right order was fully filled
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.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"
@@ -428,21 +616,30 @@ describe('matchOrders', () => {
erc20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress],
);
const rightTakerAssetFilledAmount = signedOrderLeft.makerAssetAmount.minus(takerAmountReceived);
await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
const expectedTransferAmounts2 = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 18),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(48), 18),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(48), 18),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 18),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96%
};
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft2,
signedOrderRight,
takerAddress,
newERC20BalancesByOwner,
erc721TokenIdsByOwner,
newERC721TokenIdsByOwner,
expectedTransferAmounts2,
leftTakerAssetFilledAmount,
rightTakerAssetFilledAmount,
);
// Verify second left order was partially filled
const leftOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft2);
expect(leftOrderInfo2.orderStatus as OrderStatus).to.be.equal(OrderStatus.FILLABLE);
// Verify right order was fully filled
const rightOrderInfo2: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
expect(rightOrderInfo2.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
});
it('should transfer the correct amounts if fee recipient is the same across both matched orders', async () => {
@@ -458,12 +655,27 @@ describe('matchOrders', () => {
feeRecipientAddress,
});
// Match orders
await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
};
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
});
@@ -479,12 +691,27 @@ describe('matchOrders', () => {
});
// Match orders
takerAddress = signedOrderLeft.makerAddress;
await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
};
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
});
@@ -500,12 +727,27 @@ describe('matchOrders', () => {
});
// Match orders
takerAddress = signedOrderRight.makerAddress;
await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
};
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
});
@@ -521,12 +763,27 @@ describe('matchOrders', () => {
});
// Match orders
takerAddress = feeRecipientAddressLeft;
await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
};
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
});
@@ -542,12 +799,27 @@ describe('matchOrders', () => {
});
// Match orders
takerAddress = feeRecipientAddressRight;
await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
};
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
});
@@ -562,12 +834,27 @@ describe('matchOrders', () => {
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
});
// Match orders
await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
};
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
});
@@ -680,19 +967,28 @@ describe('matchOrders', () => {
takerAssetAmount: new BigNumber(1),
});
// Match orders
await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 50%
};
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
// Verify left order was fully filled
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
// Verify right order was fully filled
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
});
it('should transfer correct amounts when right order maker asset is an ERC721 token', async () => {
@@ -706,22 +1002,31 @@ describe('matchOrders', () => {
const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({
makerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer),
makerAssetAmount: new BigNumber(1),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), 18),
});
// Match orders
await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
const expectedTransferAmounts = {
// Left Maker
amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18),
amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0),
feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Right Maker
amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0),
amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), 18),
feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
// Taker
amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18),
feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100%
};
await matchOrderTester.matchOrdersAndAssertEffectsAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
expectedTransferAmounts,
);
// Verify left order was fully filled
const leftOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
expect(leftOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
// Verify right order was fully filled
const rightOrderInfo: OrderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrderRight);
expect(rightOrderInfo.orderStatus as OrderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
});
});
}); // tslint:disable-line:max-file-line-count

View File

@@ -4,11 +4,20 @@ import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
import * as _ from 'lodash';
import { TransactionReceiptWithDecodedLogs } from '../../../../node_modules/ethereum-types';
import { chaiSetup } from './chai_setup';
import { ERC20Wrapper } from './erc20_wrapper';
import { ERC721Wrapper } from './erc721_wrapper';
import { ExchangeWrapper } from './exchange_wrapper';
import { ERC20BalancesByOwner, ERC721TokenIdsByOwner, TransferAmountsByMatchOrders as TransferAmounts } from './types';
import {
ERC20BalancesByOwner,
ERC721TokenIdsByOwner,
OrderInfo,
OrderStatus,
TransferAmountsByMatchOrders as TransferAmounts,
TransferAmountsLoggedByMatchOrders as LoggedTransferAmounts,
} from './types';
chaiSetup.configure();
const expect = chai.expect;
@@ -18,43 +27,107 @@ export class MatchOrderTester {
private readonly _erc20Wrapper: ERC20Wrapper;
private readonly _erc721Wrapper: ERC721Wrapper;
private readonly _feeTokenAddress: string;
/// @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,
): boolean {
// ERC20 Balances
const doesErc20BalancesMatch = _.isEqual(expectedNewERC20BalancesByOwner, realERC20BalancesByOwner);
if (!doesErc20BalancesMatch) {
return false;
}
// ERC721 Token Ids
const sortedExpectedNewERC721TokenIdsByOwner = _.mapValues(
expectedNewERC721TokenIdsByOwner,
tokenIdsByOwner => {
_.mapValues(tokenIdsByOwner, tokenIds => {
_.sortBy(tokenIds);
});
},
/// @dev Checks values from the logs produced by Exchange.matchOrders against the expected transfer amounts.
/// Values include the amounts transferred from the left/right makers and taker, along with
/// the fees paid on each matched order. These are also the return values of MatchOrders.
/// @param signedOrderLeft First matched order.
/// @param signedOrderRight Second matched order.
/// @param transactionReceipt Transaction receipt and logs produced by Exchange.matchOrders.
/// @param takerAddress Address of taker (account that called Exchange.matchOrders)
/// @param expectedTransferAmounts Expected amounts transferred as a result of order matching.
private static async _assertLogsAsync(
signedOrderLeft: SignedOrder,
signedOrderRight: SignedOrder,
transactionReceipt: TransactionReceiptWithDecodedLogs,
takerAddress: string,
expectedTransferAmounts: TransferAmounts,
): Promise<void> {
// Should have two fill event logs -- one for each order.
const transactionFillLogs = _.filter(transactionReceipt.logs, ['event', 'Fill']);
expect(transactionFillLogs.length, 'Checking number of logs').to.be.equal(2);
// First log is for left fill
const leftLog = (transactionFillLogs[0] as any).args as LoggedTransferAmounts;
expect(leftLog.makerAddress, 'Checking logged maker address of left order').to.be.equal(
signedOrderLeft.makerAddress,
);
expect(leftLog.takerAddress, 'Checking logged taker address of right order').to.be.equal(takerAddress);
const amountBoughtByLeftMaker = new BigNumber(leftLog.takerAssetFilledAmount);
const amountSoldByLeftMaker = new BigNumber(leftLog.makerAssetFilledAmount);
const feePaidByLeftMaker = new BigNumber(leftLog.makerFeePaid);
const feePaidByTakerLeft = new BigNumber(leftLog.takerFeePaid);
// Second log is for right fill
const rightLog = (transactionFillLogs[1] as any).args as LoggedTransferAmounts;
expect(rightLog.makerAddress, 'Checking logged maker address of right order').to.be.equal(
signedOrderRight.makerAddress,
);
expect(rightLog.takerAddress, 'Checking loggerd taker address of right order').to.be.equal(takerAddress);
const amountBoughtByRightMaker = new BigNumber(rightLog.takerAssetFilledAmount);
const amountSoldByRightMaker = new BigNumber(rightLog.makerAssetFilledAmount);
const feePaidByRightMaker = new BigNumber(rightLog.makerFeePaid);
const feePaidByTakerRight = new BigNumber(rightLog.takerFeePaid);
// Derive amount received by taker
const amountReceivedByTaker = amountSoldByLeftMaker.sub(amountBoughtByRightMaker);
// Assert log values - left order
expect(amountBoughtByLeftMaker, 'Checking logged amount bought by left maker').to.be.bignumber.equal(
expectedTransferAmounts.amountBoughtByLeftMaker,
);
expect(amountSoldByLeftMaker, 'Checking logged amount sold by left maker').to.be.bignumber.equal(
expectedTransferAmounts.amountSoldByLeftMaker,
);
expect(feePaidByLeftMaker, 'Checking logged fee paid by left maker').to.be.bignumber.equal(
expectedTransferAmounts.feePaidByLeftMaker,
);
expect(feePaidByTakerLeft, 'Checking logged fee paid on left order by taker').to.be.bignumber.equal(
expectedTransferAmounts.feePaidByTakerLeft,
);
// Assert log values - right order
expect(amountBoughtByRightMaker, 'Checking logged amount bought by right maker').to.be.bignumber.equal(
expectedTransferAmounts.amountBoughtByRightMaker,
);
expect(amountSoldByRightMaker, 'Checking logged amount sold by right maker').to.be.bignumber.equal(
expectedTransferAmounts.amountSoldByRightMaker,
);
expect(feePaidByRightMaker, 'Checking logged fee paid by right maker').to.be.bignumber.equal(
expectedTransferAmounts.feePaidByRightMaker,
);
expect(feePaidByTakerRight, 'Checking logged fee paid on right order by taker').to.be.bignumber.equal(
expectedTransferAmounts.feePaidByTakerRight,
);
// Assert derived amount received by taker
expect(amountReceivedByTaker, 'Checking logged amount received by taker').to.be.bignumber.equal(
expectedTransferAmounts.amountReceivedByTaker,
);
}
/// @dev Asserts all expected ERC20 and ERC721 account holdings match the real holdings.
/// @param expectedERC20BalancesByOwner Expected ERC20 balances.
/// @param realERC20BalancesByOwner Real ERC20 balances.
/// @param expectedERC721TokenIdsByOwner Expected ERC721 token owners.
/// @param realERC721TokenIdsByOwner Real ERC20 token owners.
private static async _assertAllKnownBalancesAsync(
expectedERC20BalancesByOwner: ERC20BalancesByOwner,
realERC20BalancesByOwner: ERC20BalancesByOwner,
expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
realERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
): Promise<void> {
// ERC20 Balances
const areERC20BalancesEqual = _.isEqual(expectedERC20BalancesByOwner, realERC20BalancesByOwner);
expect(areERC20BalancesEqual, 'Checking all known ERC20 account balances').to.be.true();
// ERC721 Token Ids
const sortedExpectedNewERC721TokenIdsByOwner = _.mapValues(expectedERC721TokenIdsByOwner, tokenIdsByOwner => {
_.mapValues(tokenIdsByOwner, tokenIds => {
_.sortBy(tokenIds);
});
});
const sortedNewERC721TokenIdsByOwner = _.mapValues(realERC721TokenIdsByOwner, tokenIdsByOwner => {
_.mapValues(tokenIdsByOwner, tokenIds => {
_.sortBy(tokenIds);
});
});
const doesErc721TokenIdsMatch = _.isEqual(
const areERC721TokenIdsEqual = _.isEqual(
sortedExpectedNewERC721TokenIdsByOwner,
sortedNewERC721TokenIdsByOwner,
);
return doesErc721TokenIdsMatch;
expect(areERC721TokenIdsEqual, 'Checking all known ERC721 account balances').to.be.true();
}
/// @dev Constructs new MatchOrderTester.
/// @param exchangeWrapper Used to call to the Exchange.
@@ -72,150 +145,199 @@ export class MatchOrderTester {
this._erc721Wrapper = erc721Wrapper;
this._feeTokenAddress = feeTokenAddress;
}
/// @dev Matches two complementary orders and validates results.
/// Validation either succeeds or throws.
/// @dev Matches two complementary orders and asserts results.
/// @param signedOrderLeft First matched order.
/// @param signedOrderRight Second matched order.
/// @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.
/// @param expectedTransferAmounts Expected amounts transferred as a result of order matching.
/// @param initialLeftOrderFilledAmount How much left order has been filled, prior to matching orders.
/// @param initialRightOrderFilledAmount How much the right order has been filled, prior to matching orders.
/// @return New ERC20 balances & ERC721 token owners.
public async matchOrdersAndVerifyBalancesAsync(
public async matchOrdersAndAssertEffectsAsync(
signedOrderLeft: SignedOrder,
signedOrderRight: SignedOrder,
takerAddress: string,
erc20BalancesByOwner: ERC20BalancesByOwner,
erc721TokenIdsByOwner: ERC721TokenIdsByOwner,
initialTakerAssetFilledAmountLeft?: BigNumber,
initialTakerAssetFilledAmountRight?: BigNumber,
expectedTransferAmounts: TransferAmounts,
initialLeftOrderFilledAmount: BigNumber = new BigNumber(0),
initialRightOrderFilledAmount: BigNumber = new BigNumber(0),
): Promise<[ERC20BalancesByOwner, ERC721TokenIdsByOwner]> {
// Verify Left order preconditions
// Assert initial order states
await this._assertInitialOrderStatesAsync(
signedOrderLeft,
signedOrderRight,
initialLeftOrderFilledAmount,
initialRightOrderFilledAmount,
);
// Match left & right orders
const transactionReceipt = await this._exchangeWrapper.matchOrdersAsync(
signedOrderLeft,
signedOrderRight,
takerAddress,
);
const newERC20BalancesByOwner = await this._erc20Wrapper.getBalancesAsync();
const newERC721TokenIdsByOwner = await this._erc721Wrapper.getBalancesAsync();
// Assert logs
await MatchOrderTester._assertLogsAsync(
signedOrderLeft,
signedOrderRight,
transactionReceipt,
takerAddress,
expectedTransferAmounts,
);
// Assert exchange state
await this._assertExchangeStateAsync(
signedOrderLeft,
signedOrderRight,
initialLeftOrderFilledAmount,
initialRightOrderFilledAmount,
expectedTransferAmounts,
);
// Assert balances of makers, taker, and fee recipients
await this._assertBalancesAsync(
signedOrderLeft,
signedOrderRight,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
newERC20BalancesByOwner,
newERC721TokenIdsByOwner,
expectedTransferAmounts,
takerAddress,
);
return [newERC20BalancesByOwner, newERC721TokenIdsByOwner];
}
/// @dev Asserts initial exchange state for the left and right orders.
/// @param signedOrderLeft First matched order.
/// @param signedOrderRight Second matched order.
/// @param expectedOrderFilledAmountLeft How much left order has been filled, prior to matching orders.
/// @param expectedOrderFilledAmountRight How much the right order has been filled, prior to matching orders.
private async _assertInitialOrderStatesAsync(
signedOrderLeft: SignedOrder,
signedOrderRight: SignedOrder,
expectedOrderFilledAmountLeft: BigNumber,
expectedOrderFilledAmountRight: BigNumber,
): Promise<void> {
// Assert left order initial state
const orderTakerAssetFilledAmountLeft = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
orderHashUtils.getOrderHashHex(signedOrderLeft),
);
const expectedOrderFilledAmountLeft = initialTakerAssetFilledAmountLeft
? initialTakerAssetFilledAmountLeft
: new BigNumber(0);
expect(expectedOrderFilledAmountLeft).to.be.bignumber.equal(orderTakerAssetFilledAmountLeft);
// Verify Right order preconditions
expect(orderTakerAssetFilledAmountLeft, 'Checking inital state of left order').to.be.bignumber.equal(
expectedOrderFilledAmountLeft,
);
// Assert right order initial state
const orderTakerAssetFilledAmountRight = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
orderHashUtils.getOrderHashHex(signedOrderRight),
);
const expectedOrderFilledAmountRight = initialTakerAssetFilledAmountRight
? initialTakerAssetFilledAmountRight
: new BigNumber(0);
expect(expectedOrderFilledAmountRight).to.be.bignumber.equal(orderTakerAssetFilledAmountRight);
// 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,
orderTakerAssetFilledAmountLeft,
orderTakerAssetFilledAmountRight,
expect(orderTakerAssetFilledAmountRight, 'Checking inital state of right order').to.be.bignumber.equal(
expectedOrderFilledAmountRight,
);
}
/// @dev Asserts the exchange state against the expected amounts transferred by from matching orders.
/// @param signedOrderLeft First matched order.
/// @param signedOrderRight Second matched order.
/// @param initialLeftOrderFilledAmount How much left order has been filled, prior to matching orders.
/// @param initialRightOrderFilledAmount How much the right order has been filled, prior to matching orders.
/// @return TransferAmounts A struct containing the expected transfer amounts.
private async _assertExchangeStateAsync(
signedOrderLeft: SignedOrder,
signedOrderRight: SignedOrder,
initialLeftOrderFilledAmount: BigNumber,
initialRightOrderFilledAmount: BigNumber,
expectedTransferAmounts: TransferAmounts,
): Promise<void> {
// Assert state for left order: amount bought by left maker
let amountBoughtByLeftMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
orderHashUtils.getOrderHashHex(signedOrderLeft),
);
amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(initialLeftOrderFilledAmount);
expect(amountBoughtByLeftMaker, 'Checking exchange state for left order').to.be.bignumber.equal(
expectedTransferAmounts.amountBoughtByLeftMaker,
);
// Assert state for right order: amount bought by right maker
let amountBoughtByRightMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
orderHashUtils.getOrderHashHex(signedOrderRight),
);
amountBoughtByRightMaker = amountBoughtByRightMaker.minus(initialRightOrderFilledAmount);
expect(amountBoughtByRightMaker, 'Checking exchange state for right order').to.be.bignumber.equal(
expectedTransferAmounts.amountBoughtByRightMaker,
);
// Assert left order status
const maxAmountBoughtByLeftMaker = signedOrderLeft.takerAssetAmount.minus(initialLeftOrderFilledAmount);
const leftOrderInfo: OrderInfo = await this._exchangeWrapper.getOrderInfoAsync(signedOrderLeft);
const leftExpectedStatus = expectedTransferAmounts.amountBoughtByLeftMaker.equals(maxAmountBoughtByLeftMaker)
? OrderStatus.FULLY_FILLED
: OrderStatus.FILLABLE;
expect(leftOrderInfo.orderStatus as OrderStatus, 'Checking exchange status for left order').to.be.equal(
leftExpectedStatus,
);
// Assert right order status
const maxAmountBoughtByRightMaker = signedOrderRight.takerAssetAmount.minus(initialRightOrderFilledAmount);
const rightOrderInfo: OrderInfo = await this._exchangeWrapper.getOrderInfoAsync(signedOrderRight);
const rightExpectedStatus = expectedTransferAmounts.amountBoughtByRightMaker.equals(maxAmountBoughtByRightMaker)
? OrderStatus.FULLY_FILLED
: OrderStatus.FILLABLE;
expect(rightOrderInfo.orderStatus as OrderStatus, 'Checking exchange status for right order').to.be.equal(
rightExpectedStatus,
);
}
/// @dev Asserts account balances after matching orders.
/// @param signedOrderLeft First matched order.
/// @param signedOrderRight Second matched order.
/// @param initialERC20BalancesByOwner ERC20 balances prior to order matching.
/// @param initialERC721TokenIdsByOwner ERC721 token owners prior to order matching.
/// @param finalERC20BalancesByOwner ERC20 balances after order matching.
/// @param finalERC721TokenIdsByOwner ERC721 token owners after order matching.
/// @param expectedTransferAmounts Expected amounts transferred as a result of order matching.
/// @param takerAddress Address of taker (account that called Exchange.matchOrders).
private async _assertBalancesAsync(
signedOrderLeft: SignedOrder,
signedOrderRight: SignedOrder,
initialERC20BalancesByOwner: ERC20BalancesByOwner,
initialERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
finalERC20BalancesByOwner: ERC20BalancesByOwner,
finalERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
expectedTransferAmounts: TransferAmounts,
takerAddress: string,
): Promise<void> {
let expectedERC20BalancesByOwner: ERC20BalancesByOwner;
let expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
[expectedERC20BalancesByOwner, expectedERC721TokenIdsByOwner] = this._calculateExpectedBalances(
signedOrderLeft,
signedOrderRight,
takerAddress,
erc20BalancesByOwner,
erc721TokenIdsByOwner,
initialERC20BalancesByOwner,
initialERC721TokenIdsByOwner,
expectedTransferAmounts,
);
// Assert our expected balances are equal to the actual balances
const didExpectedBalancesMatchRealBalances = MatchOrderTester._compareExpectedAndRealBalances(
// Assert balances of makers, taker, and fee recipients
await this._assertMakerTakerAndFeeRecipientBalancesAsync(
signedOrderLeft,
signedOrderRight,
expectedERC20BalancesByOwner,
newERC20BalancesByOwner,
finalERC20BalancesByOwner,
expectedERC721TokenIdsByOwner,
newERC721TokenIdsByOwner,
finalERC721TokenIdsByOwner,
takerAddress,
);
expect(didExpectedBalancesMatchRealBalances).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 orderTakerAssetFilledAmountLeft How much left order has been filled, prior to matching orders.
/// @param orderTakerAssetFilledAmountRight How much 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,
orderTakerAssetFilledAmountLeft: BigNumber,
orderTakerAssetFilledAmountRight: BigNumber,
): Promise<TransferAmounts> {
let amountBoughtByLeftMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync(
orderHashUtils.getOrderHashHex(signedOrderLeft),
// Assert balances for all known accounts
await MatchOrderTester._assertAllKnownBalancesAsync(
expectedERC20BalancesByOwner,
finalERC20BalancesByOwner,
expectedERC721TokenIdsByOwner,
finalERC721TokenIdsByOwner,
);
amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(orderTakerAssetFilledAmountLeft);
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(
orderHashUtils.getOrderHashHex(signedOrderRight),
);
amountBoughtByRightMaker = amountBoughtByRightMaker.minus(orderTakerAssetFilledAmountRight);
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;
}
/// @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 First matched order.
/// @param signedOrderRight Second matched order.
/// @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.
/// @param expectedTransferAmounts Expected amounts transferred as a result of order matching.
/// @return Expected ERC20 balances & ERC721 token owners after orders have been matched.
private _calculateExpectedBalances(
signedOrderLeft: SignedOrder,
@@ -247,7 +369,7 @@ export class MatchOrderTester {
expectedNewERC20BalancesByOwner[makerAddressRight][
takerAssetAddressRight
] = expectedNewERC20BalancesByOwner[makerAddressRight][takerAssetAddressRight].add(
expectedTransferAmounts.amountReceivedByRightMaker,
expectedTransferAmounts.amountBoughtByRightMaker,
);
// Taker
expectedNewERC20BalancesByOwner[takerAddress][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[
@@ -277,7 +399,7 @@ export class MatchOrderTester {
// Left Maker
expectedNewERC20BalancesByOwner[makerAddressLeft][takerAssetAddressLeft] = expectedNewERC20BalancesByOwner[
makerAddressLeft
][takerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByLeftMaker);
][takerAssetAddressLeft].add(expectedTransferAmounts.amountBoughtByLeftMaker);
// Right Maker
expectedNewERC20BalancesByOwner[makerAddressRight][
makerAssetAddressRight
@@ -307,20 +429,138 @@ export class MatchOrderTester {
// Taker Fees
expectedNewERC20BalancesByOwner[takerAddress][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[
takerAddress
][this._feeTokenAddress].minus(expectedTransferAmounts.totalFeePaidByTaker);
][this._feeTokenAddress].minus(
expectedTransferAmounts.feePaidByTakerLeft.add(expectedTransferAmounts.feePaidByTakerRight),
);
// Left Fee Recipient Fees
expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][
this._feeTokenAddress
] = expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][this._feeTokenAddress].add(
expectedTransferAmounts.feeReceivedLeft,
expectedTransferAmounts.feePaidByLeftMaker.add(expectedTransferAmounts.feePaidByTakerLeft),
);
// Right Fee Recipient Fees
expectedNewERC20BalancesByOwner[feeRecipientAddressRight][
this._feeTokenAddress
] = expectedNewERC20BalancesByOwner[feeRecipientAddressRight][this._feeTokenAddress].add(
expectedTransferAmounts.feeReceivedRight,
expectedTransferAmounts.feePaidByRightMaker.add(expectedTransferAmounts.feePaidByTakerRight),
);
return [expectedNewERC20BalancesByOwner, expectedNewERC721TokenIdsByOwner];
}
}
/// @dev Asserts ERC20 account balances and ERC721 token holdings that result from order matching.
/// Specifically checks balances of makers, taker and fee recipients.
/// @param signedOrderLeft First matched order.
/// @param signedOrderRight Second matched order.
/// @param expectedERC20BalancesByOwner Expected ERC20 balances.
/// @param realERC20BalancesByOwner Real ERC20 balances.
/// @param expectedERC721TokenIdsByOwner Expected ERC721 token owners.
/// @param realERC721TokenIdsByOwner Real ERC20 token owners.
/// @param takerAddress Address of taker (account that called Exchange.matchOrders).
private async _assertMakerTakerAndFeeRecipientBalancesAsync(
signedOrderLeft: SignedOrder,
signedOrderRight: SignedOrder,
expectedERC20BalancesByOwner: ERC20BalancesByOwner,
realERC20BalancesByOwner: ERC20BalancesByOwner,
expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
realERC721TokenIdsByOwner: ERC721TokenIdsByOwner,
takerAddress: string,
): Promise<void> {
// Individual balance comparisons
const makerAssetProxyIdLeft = assetDataUtils.decodeAssetProxyId(signedOrderLeft.makerAssetData);
const makerERC20AssetDataLeft =
makerAssetProxyIdLeft === AssetProxyId.ERC20
? assetDataUtils.decodeERC20AssetData(signedOrderLeft.makerAssetData)
: assetDataUtils.decodeERC721AssetData(signedOrderLeft.makerAssetData);
const makerAssetAddressLeft = makerERC20AssetDataLeft.tokenAddress;
const makerAssetProxyIdRight = assetDataUtils.decodeAssetProxyId(signedOrderRight.makerAssetData);
const makerERC20AssetDataRight =
makerAssetProxyIdRight === AssetProxyId.ERC20
? assetDataUtils.decodeERC20AssetData(signedOrderRight.makerAssetData)
: assetDataUtils.decodeERC721AssetData(signedOrderRight.makerAssetData);
const makerAssetAddressRight = makerERC20AssetDataRight.tokenAddress;
if (makerAssetProxyIdLeft === AssetProxyId.ERC20) {
expect(
realERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft],
'Checking left maker egress ERC20 account balance',
).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft]);
expect(
realERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft],
'Checking right maker ingress ERC20 account balance',
).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft]);
expect(
realERC20BalancesByOwner[takerAddress][makerAssetAddressLeft],
'Checking taker ingress ERC20 account balance',
).to.be.bignumber.equal(expectedERC20BalancesByOwner[takerAddress][makerAssetAddressLeft]);
} else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) {
expect(
realERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft].sort(),
'Checking left maker egress ERC721 account holdings',
).to.be.deep.equal(
expectedERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft].sort(),
);
expect(
realERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft].sort(),
'Checking right maker ERC721 account holdings',
).to.be.deep.equal(
expectedERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft].sort(),
);
expect(
realERC721TokenIdsByOwner[takerAddress][makerAssetAddressLeft].sort(),
'Checking taker ingress ERC721 account holdings',
).to.be.deep.equal(expectedERC721TokenIdsByOwner[takerAddress][makerAssetAddressLeft].sort());
} else {
throw new Error(`Unhandled Asset Proxy ID: ${makerAssetProxyIdLeft}`);
}
if (makerAssetProxyIdRight === AssetProxyId.ERC20) {
expect(
realERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight],
'Checking left maker ingress ERC20 account balance',
).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight]);
expect(
realERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressRight],
'Checking right maker egress ERC20 account balance',
).to.be.bignumber.equal(
expectedERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressRight],
);
} else if (makerAssetProxyIdRight === AssetProxyId.ERC721) {
expect(
realERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight].sort(),
'Checking left maker ingress ERC721 account holdings',
).to.be.deep.equal(
expectedERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight].sort(),
);
expect(
realERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressRight],
'Checking right maker agress ERC721 account holdings',
).to.be.deep.equal(expectedERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressRight]);
} else {
throw new Error(`Unhandled Asset Proxy ID: ${makerAssetProxyIdRight}`);
}
// Paid fees
expect(
realERC20BalancesByOwner[signedOrderLeft.makerAddress][this._feeTokenAddress],
'Checking left maker egress ERC20 account fees',
).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderLeft.makerAddress][this._feeTokenAddress]);
expect(
realERC20BalancesByOwner[signedOrderRight.makerAddress][this._feeTokenAddress],
'Checking right maker egress ERC20 account fees',
).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderRight.makerAddress][this._feeTokenAddress]);
expect(
realERC20BalancesByOwner[takerAddress][this._feeTokenAddress],
'Checking taker egress ERC20 account fees',
).to.be.bignumber.equal(expectedERC20BalancesByOwner[takerAddress][this._feeTokenAddress]);
// Received fees
expect(
realERC20BalancesByOwner[signedOrderLeft.feeRecipientAddress][this._feeTokenAddress],
'Checking left fee recipient ingress ERC20 account fees',
).to.be.bignumber.equal(
expectedERC20BalancesByOwner[signedOrderLeft.feeRecipientAddress][this._feeTokenAddress],
);
expect(
realERC20BalancesByOwner[signedOrderRight.feeRecipientAddress][this._feeTokenAddress],
'Checking right fee receipient ingress ERC20 account fees',
).to.be.bignumber.equal(
expectedERC20BalancesByOwner[signedOrderRight.feeRecipientAddress][this._feeTokenAddress],
);
}
} // tslint:disable-line:max-file-line-count

View File

@@ -117,21 +117,24 @@ 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;
}
export interface TransferAmountsLoggedByMatchOrders {
makerAddress: string;
takerAddress: string;
makerAssetFilledAmount: string;
takerAssetFilledAmount: string;
makerFeePaid: string;
takerFeePaid: string;
}
export interface OrderInfo {