@0x/contracts-exchange: Avoid redundant transfer in matchOrders() when maker/feeRecipient and takerAssetData/makerFeeAssetData are the same.

`@0x/conracts-exchange`: Disable combinatorial tests by default. Can be run by setting env var `TEST_ALL=1`.
This commit is contained in:
Lawrence Forman 2019-05-24 15:47:38 -04:00 committed by Amir Bandeali
parent cd08c3e8fa
commit ee89f74afd
4 changed files with 156 additions and 118 deletions

View File

@ -15,6 +15,7 @@ pragma solidity ^0.5.5;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/ReentrancyGuard.sol"; import "@0x/contracts-utils/contracts/src/ReentrancyGuard.sol";
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol";
@ -35,6 +36,8 @@ contract MixinMatchOrders is
MTransactions, MTransactions,
MExchangeRichErrors MExchangeRichErrors
{ {
using LibBytes for bytes;
/// @dev Match two complementary orders that have a profitable spread. /// @dev Match two complementary orders that have a profitable spread.
/// Each order is filled at their respective price point. However, the calculations are /// Each order is filled at their respective price point. However, the calculations are
/// carried out as though the orders are both being filled at the right order's price point. /// carried out as though the orders are both being filled at the right order's price point.
@ -280,14 +283,28 @@ contract MixinMatchOrders is
) )
private private
{ {
// Order makers and taker address leftFeeRecipientAddress = leftOrder.feeRecipientAddress;
address rightFeeRecipientAddress = rightOrder.feeRecipientAddress;
// Settle left order taker asset and maker fees.
if (
leftOrder.makerAddress == leftFeeRecipientAddress &&
leftOrder.makerFeeAssetData.equals(leftOrder.takerAssetData)
) {
// Maker is fee recipient and the maker fee asset is the same as the taker asset.
// We can transfer the taker asset and maker fees in one go.
_dispatchTransferFrom( _dispatchTransferFrom(
leftOrderHash, rightOrderHash,
leftOrder.makerAssetData, rightOrder.makerAssetData,
leftOrder.makerAddress,
rightOrder.makerAddress, rightOrder.makerAddress,
matchedFillResults.right.takerAssetFilledAmount leftFeeRecipientAddress,
_safeAdd(
matchedFillResults.left.takerAssetFilledAmount,
matchedFillResults.right.makerFeePaid
)
); );
} else {
// Right maker asset -> left maker
_dispatchTransferFrom( _dispatchTransferFrom(
rightOrderHash, rightOrderHash,
rightOrder.makerAssetData, rightOrder.makerAssetData,
@ -295,6 +312,53 @@ contract MixinMatchOrders is
leftOrder.makerAddress, leftOrder.makerAddress,
matchedFillResults.left.takerAssetFilledAmount matchedFillResults.left.takerAssetFilledAmount
); );
// Left maker fee -> left fee recipient
_dispatchTransferFrom(
leftOrderHash,
leftOrder.makerFeeAssetData,
leftOrder.makerAddress,
leftFeeRecipientAddress,
matchedFillResults.left.makerFeePaid
);
}
// Settle right order taker asset and maker fees.
if (
rightOrder.makerAddress == rightFeeRecipientAddress &&
rightOrder.makerFeeAssetData.equals(rightOrder.takerAssetData)
) {
// Maker is fee recipient and the maker fee asset is the same as the taker asset.
// We can transfer the taker asset and maker fees in one go.
_dispatchTransferFrom(
leftOrderHash,
leftOrder.makerAssetData,
leftOrder.makerAddress,
rightOrder.makerAddress,
_safeAdd(
matchedFillResults.right.takerAssetFilledAmount,
matchedFillResults.left.makerFeePaid
)
);
} else {
// Left maker asset -> right maker
_dispatchTransferFrom(
leftOrderHash,
leftOrder.makerAssetData,
leftOrder.makerAddress,
rightOrder.makerAddress,
matchedFillResults.right.takerAssetFilledAmount
);
// Right maker fee -> right fee recipient
_dispatchTransferFrom(
rightOrderHash,
rightOrder.makerFeeAssetData,
rightOrder.makerAddress,
rightFeeRecipientAddress,
matchedFillResults.right.makerFeePaid
);
}
// Settle taker profits.
_dispatchTransferFrom( _dispatchTransferFrom(
leftOrderHash, leftOrderHash,
leftOrder.makerAssetData, leftOrder.makerAssetData,
@ -303,38 +367,16 @@ contract MixinMatchOrders is
matchedFillResults.leftMakerAssetSpreadAmount matchedFillResults.leftMakerAssetSpreadAmount
); );
address leftFeeRecipientAddress = leftOrder.feeRecipientAddress; // Settle taker fees.
address rightFeeRecipientAddress = rightOrder.feeRecipientAddress; if (
leftFeeRecipientAddress == rightFeeRecipientAddress &&
// Maker fees leftOrder.takerFeeAssetData.equals(rightOrder.takerFeeAssetData)
_dispatchTransferFrom( ) {
leftOrderHash,
leftOrder.makerFeeAssetData,
leftOrder.makerAddress,
leftFeeRecipientAddress,
matchedFillResults.left.makerFeePaid
);
_dispatchTransferFrom(
rightOrderHash,
rightOrder.makerFeeAssetData,
rightOrder.makerAddress,
rightFeeRecipientAddress,
matchedFillResults.right.makerFeePaid
);
// Taker fees
bytes memory leftTakerFeeAssetData = leftOrder.takerFeeAssetData;
bytes memory rightTakerFeeAssetData = rightOrder.takerFeeAssetData;
if (leftFeeRecipientAddress == rightFeeRecipientAddress) {
bytes32 leftTakerFeeAssetDataHash = keccak256(leftTakerFeeAssetData);
bytes32 rightTakerFeeAssetDataHash = keccak256(rightTakerFeeAssetData);
if (leftTakerFeeAssetDataHash == rightTakerFeeAssetDataHash) {
// Fee recipients and taker fee assets are identical, so we can // Fee recipients and taker fee assets are identical, so we can
// transfer them in one go. // transfer them in one go.
_dispatchTransferFrom( _dispatchTransferFrom(
leftOrderHash, leftOrderHash,
leftTakerFeeAssetData, leftOrder.takerFeeAssetData,
takerAddress, takerAddress,
leftFeeRecipientAddress, leftFeeRecipientAddress,
_safeAdd( _safeAdd(
@ -342,22 +384,23 @@ contract MixinMatchOrders is
matchedFillResults.right.takerFeePaid matchedFillResults.right.takerFeePaid
) )
); );
return; } else {
} // taker fee -> left fee recipient
}
_dispatchTransferFrom( _dispatchTransferFrom(
leftOrderHash, leftOrderHash,
leftTakerFeeAssetData, leftOrder.takerFeeAssetData,
takerAddress, takerAddress,
leftFeeRecipientAddress, leftFeeRecipientAddress,
matchedFillResults.left.takerFeePaid matchedFillResults.left.takerFeePaid
); );
// taker fee -> right fee recipient
_dispatchTransferFrom( _dispatchTransferFrom(
rightOrderHash, rightOrderHash,
rightTakerFeeAssetData, rightOrder.takerFeeAssetData,
takerAddress, takerAddress,
rightFeeRecipientAddress, rightFeeRecipientAddress,
matchedFillResults.right.takerFeePaid matchedFillResults.right.takerFeePaid
); );
} }
} }
}

View File

@ -1,6 +1,8 @@
import { chaiSetup, txDefaults, web3Wrapper } from '@0x/contracts-test-utils'; import { chaiSetup, txDefaults, web3Wrapper } from '@0x/contracts-test-utils';
import { BlockchainLifecycle } from '@0x/dev-utils'; import { BlockchainLifecycle } from '@0x/dev-utils';
import { logUtils } from '@0x/utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { env } from 'process';
import { import {
AllowanceAmountScenario, AllowanceAmountScenario,
@ -22,6 +24,12 @@ import {
chaiSetup.configure(); chaiSetup.configure();
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
let describeOptional = describe;
if (env.TEST_ALL === undefined || env.TEST_ALL === '0') {
describeOptional = describe.skip;
logUtils.warn(`Combinatorial tests are disabled. Run with "TEST_ALL=1" env variable to enable.`);
}
const defaultFillScenario = { const defaultFillScenario = {
orderScenario: { orderScenario: {
takerScenario: TakerScenario.Unspecified, takerScenario: TakerScenario.Unspecified,
@ -51,7 +59,7 @@ const defaultFillScenario = {
}, },
}; };
describe.only('FillOrder Tests', () => { describe('FillOrder Tests', () => {
let fillOrderCombinatorialUtils: FillOrderCombinatorialUtils; let fillOrderCombinatorialUtils: FillOrderCombinatorialUtils;
before(async () => { before(async () => {
@ -631,7 +639,7 @@ describe.only('FillOrder Tests', () => {
} }
}); });
describe('Combinatorially generated fills orders', () => { describeOptional('Combinatorially generated fills orders', () => {
const allFillScenarios = FillOrderCombinatorialUtils.generateFillOrderCombinations(); const allFillScenarios = FillOrderCombinatorialUtils.generateFillOrderCombinations();
for (const fillScenario of allFillScenarios) { for (const fillScenario of allFillScenarios) {
const description = `Combinatorial OrderFill: ${JSON.stringify(fillScenario)}`; const description = `Combinatorial OrderFill: ${JSON.stringify(fillScenario)}`;

View File

@ -38,6 +38,11 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
chaiSetup.configure(); chaiSetup.configure();
const expect = chai.expect; const expect = chai.expect;
// Reduce the number of tokens to deploy to speed up tests, since we don't need
// so many.
constants.NUM_DUMMY_ERC721_TO_DEPLOY = 1;
constants.NUM_DUMMY_ERC1155_CONTRACTS_TO_DEPLOY = 1;
describe('matchOrders', () => { describe('matchOrders', () => {
let chainId: number; let chainId: number;
let makerAddressLeft: string; let makerAddressLeft: string;

View File

@ -259,6 +259,22 @@ function simulateMatchOrders(
fills: simulateFillEvents(orders, takerAddress, transferAmounts), fills: simulateFillEvents(orders, takerAddress, transferAmounts),
balances: _.cloneDeep(tokenBalances), balances: _.cloneDeep(tokenBalances),
}; };
// Right maker asset -> left maker
transferAsset(
orders.rightOrder.makerAddress,
orders.leftOrder.makerAddress,
transferAmounts.rightMakerAssetBoughtByLeftMakerAmount,
orders.rightOrder.makerAssetData,
matchResults,
);
// Left maker fees
transferAsset(
orders.leftOrder.makerAddress,
orders.leftOrder.feeRecipientAddress,
transferAmounts.leftMakerFeeAssetPaidByLeftMakerAmount,
orders.leftOrder.makerFeeAssetData,
matchResults,
);
// Left maker asset -> right maker // Left maker asset -> right maker
transferAsset( transferAsset(
orders.leftOrder.makerAddress, orders.leftOrder.makerAddress,
@ -267,12 +283,12 @@ function simulateMatchOrders(
orders.leftOrder.makerAssetData, orders.leftOrder.makerAssetData,
matchResults, matchResults,
); );
// Right maker asset -> left maker // Right maker fees
transferAsset( transferAsset(
orders.rightOrder.makerAddress, orders.rightOrder.makerAddress,
orders.leftOrder.makerAddress, orders.rightOrder.feeRecipientAddress,
transferAmounts.rightMakerAssetBoughtByLeftMakerAmount, transferAmounts.rightMakerFeeAssetPaidByRightMakerAmount,
orders.rightOrder.makerAssetData, orders.rightOrder.makerFeeAssetData,
matchResults, matchResults,
); );
// Left taker profit // Left taker profit
@ -291,39 +307,6 @@ function simulateMatchOrders(
orders.rightOrder.makerAssetData, orders.rightOrder.makerAssetData,
matchResults, matchResults,
); );
// Left maker fees
transferAsset(
orders.leftOrder.makerAddress,
orders.leftOrder.feeRecipientAddress,
transferAmounts.leftMakerFeeAssetPaidByLeftMakerAmount,
orders.leftOrder.makerFeeAssetData,
matchResults,
);
// Right maker fees
transferAsset(
orders.rightOrder.makerAddress,
orders.rightOrder.feeRecipientAddress,
transferAmounts.rightMakerFeeAssetPaidByRightMakerAmount,
orders.rightOrder.makerFeeAssetData,
matchResults,
);
// Taker fees.
if (
orders.leftOrder.feeRecipientAddress === orders.rightOrder.feeRecipientAddress &&
orders.leftOrder.takerFeeAssetData === orders.rightOrder.takerFeeAssetData
) {
// Same asset data and recipients, so combine into a single transfer.
const totalTakerFeeAssetPaidByTakerAmount = transferAmounts.leftTakerFeeAssetPaidByTakerAmount.plus(
transferAmounts.rightTakerFeeAssetPaidByTakerAmount,
);
transferAsset(
takerAddress,
orders.leftOrder.feeRecipientAddress,
totalTakerFeeAssetPaidByTakerAmount,
orders.leftOrder.takerFeeAssetData,
matchResults,
);
} else {
// Left taker fees // Left taker fees
transferAsset( transferAsset(
takerAddress, takerAddress,
@ -340,7 +323,6 @@ function simulateMatchOrders(
orders.rightOrder.takerFeeAssetData, orders.rightOrder.takerFeeAssetData,
matchResults, matchResults,
); );
}
return matchResults; return matchResults;
} }