Rebased onto PR #1900

This commit is contained in:
James Towle 2019-07-02 16:50:14 -05:00 committed by Amir Bandeali
parent 073976de10
commit 29eff3b515
2 changed files with 61 additions and 252 deletions

View File

@ -1,5 +1,5 @@
/* /*
Copyright 2018 ZeroEx Intl. Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
@ -190,11 +190,21 @@ contract MixinMatchOrders is
internal internal
returns (LibFillResults.BatchMatchedFillResults memory batchMatchedFillResults) returns (LibFillResults.BatchMatchedFillResults memory batchMatchedFillResults)
{ {
// Ensure that the left and right arrays are compatible and have nonzero lengths // Ensure that the left and right orders have nonzero lengths.
require(leftOrders.length > 0, "Invalid number of left orders"); if (leftOrders.length == 0) {
require(rightOrders.length > 0, "Invalid number of right orders"); _rrevert(BatchMatchOrdersError(BatchMatchOrdersErrorCodes.ZERO_LEFT_ORDERS));
require(leftOrders.length == leftSignatures.length, "Incompatible leftOrders and leftSignatures"); }
require(rightOrders.length == rightSignatures.length, "Incompatible rightOrders and rightSignatures"); if (rightOrders.length == 0) {
_rrevert(BatchMatchOrdersError(BatchMatchOrdersErrorCodes.ZERO_RIGHT_ORDERS));
}
// Ensure that the left and right arrays are compatible.
if (leftOrders.length != leftSignatures.length) {
_rrevert(BatchMatchOrdersError(BatchMatchOrdersErrorCodes.INCOMPATIBLE_LEFT_ORDERS));
}
if (rightOrders.length != rightSignatures.length) {
_rrevert(BatchMatchOrdersError(BatchMatchOrdersErrorCodes.INCOMPATIBLE_RIGHT_ORDERS));
}
// Without simulating all of the order matching, this program cannot know how many // Without simulating all of the order matching, this program cannot know how many
// matches there will be. To ensure that batchMatchedFillResults has enough memory // matches there will be. To ensure that batchMatchedFillResults has enough memory
@ -204,104 +214,85 @@ contract MixinMatchOrders is
batchMatchedFillResults.left = new LibFillResults.FillResults[](maxLength); batchMatchedFillResults.left = new LibFillResults.FillResults[](maxLength);
batchMatchedFillResults.right = new LibFillResults.FillResults[](maxLength); batchMatchedFillResults.right = new LibFillResults.FillResults[](maxLength);
// Initialize initial variables // Set up initial indices.
uint i; uint256 matchCount;
uint256 leftIdx = 0; uint256 leftIdx = 0;
uint256 rightIdx = 0; uint256 rightIdx = 0;
// Keep local variables for orders, order info, and signatures for efficiency.
LibOrder.Order memory leftOrder = leftOrders[0]; LibOrder.Order memory leftOrder = leftOrders[0];
LibOrder.Order memory rightOrder = rightOrders[0]; LibOrder.Order memory rightOrder = rightOrders[0];
bytes memory leftSignature = leftSignatures[0]; LibOrder.OrderInfo memory leftOrderInfo = getOrderInfo(leftOrder);
bytes memory rightSignature = rightSignatures[0]; LibOrder.OrderInfo memory rightOrderInfo = getOrderInfo(rightOrder);
// Loop infinitely (until broken inside of the loop), but keep a counter of how
// many orders have been matched.
for (matchCount = 0;; matchCount++) {
// Match the two orders that are pointed to by the left and right indices
LibFillResults.MatchedFillResults memory matchResults = _matchOrders(
leftOrder,
rightOrder,
leftSignatures[leftIdx],
rightSignatures[rightIdx],
withMaximalFill
);
for (i = 0;; i++) { // Update the orderInfo structs with the updated takerAssetFilledAmount
LibFillResults.MatchedFillResults memory matchResults; leftOrderInfo.orderTakerAssetFilledAmount = _safeAdd(
// Match the two orders that are pointed to by the left and right indices. If specified, use the leftOrderInfo.orderTakerAssetFilledAmount,
// matchOrdersWithMaximalFill function. matchResults.left.takerAssetFilledAmount
if (withMaximalFill) { );
// Match the two orders that are pointed to by the left and right indices rightOrderInfo.orderTakerAssetFilledAmount = _safeAdd(
matchResults = _matchOrders( rightOrderInfo.orderTakerAssetFilledAmount,
leftOrder, matchResults.right.takerAssetFilledAmount
rightOrder, );
leftSignature,
rightSignature,
true
);
} else {
matchResults = _matchOrders(
leftOrder,
rightOrder,
leftSignature,
rightSignature,
false
);
}
// Add the matchResults and the profit made during the match to the // Add the matchResults and the profit made during the match to the
// batchMatchedFillResults for this batch. // batchMatchedFillResults for this batch.
batchMatchedFillResults.left[i] = matchResults.left; batchMatchedFillResults.left[matchCount] = matchResults.left;
batchMatchedFillResults.right[i] = matchResults.right; batchMatchedFillResults.right[matchCount] = matchResults.right;
batchMatchedFillResults.profitInLeftMakerAsset += batchMatchedFillResults.profitInLeftMakerAsset = _safeAdd(
matchResults.left.makerFeePaid; batchMatchedFillResults.profitInLeftMakerAsset,
batchMatchedFillResults.profitInRightMakerAsset += matchResults.leftMakerAssetSpreadAmount
matchResults.right.makerFeePaid; );
batchMatchedFillResults.profitInRightMakerAsset = _safeAdd(
batchMatchedFillResults.profitInRightMakerAsset,
matchResults.rightMakerAssetSpreadAmount
);
// If the leftOrder is filled, update the leftIdx, leftOrder, and leftSignature, // If the leftOrder is filled, update the leftIdx, leftOrder, and leftSignature,
// or break out of the loop if there are no more leftOrders to match. // or break out of the loop if there are no more leftOrders to match.
if (_isFilled(leftOrder)) { if (leftOrderInfo.orderTakerAssetFilledAmount >= leftOrder.takerAssetAmount) {
if (++leftIdx == leftOrders.length) { if (++leftIdx == leftOrders.length) {
break; break;
} else { } else {
leftOrder = leftOrders[leftIdx]; leftOrder = leftOrders[leftIdx];
leftSignature = leftSignatures[leftIdx]; leftOrderInfo = getOrderInfo(leftOrder);
} }
} }
// If the rightOrder is filled, update the rightIdx, rightOrder, and rightSignature, // If the rightOrder is filled, update the rightIdx, rightOrder, and rightSignature,
// or break out of the loop if there are no more rightOrders to match. // or break out of the loop if there are no more rightOrders to match.
if (_isFilled(rightOrder)) { if (rightOrderInfo.orderTakerAssetFilledAmount >= rightOrder.takerAssetAmount) {
if (++rightIdx == rightOrders.length) { if (++rightIdx == rightOrders.length) {
break; break;
} else { } else {
rightOrder = rightOrders[rightIdx]; rightOrder = rightOrders[rightIdx];
rightSignature = rightSignatures[rightIdx]; rightOrderInfo = getOrderInfo(rightOrder);
} }
} }
} }
// Update the lengths of the fill results for batchMatchResults // Update the lengths of the fill results for batchMatchResults
assembly { assembly {
mstore(mload(batchMatchedFillResults), i) mstore(mload(batchMatchedFillResults), matchCount)
mstore(mload(add(batchMatchedFillResults, 32)), i) mstore(mload(add(batchMatchedFillResults, 32)), matchCount)
} }
// Return the fill results from the batch match // Return the fill results from the batch match
return batchMatchedFillResults; return batchMatchedFillResults;
} }
/// @dev Calculates fill amounts for the matched orders.
/// 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.
/// The profit made by the leftOrder order goes to the taker (who matched the two orders).
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
/// @param leftOrderTakerAssetFilledAmount Amount of left order already filled.
/// @param rightOrderTakerAssetFilledAmount Amount of right order already filled.
/// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders.
function calculateMatchedFillResults(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder,
uint256 leftOrderTakerAssetFilledAmount,
uint256 rightOrderTakerAssetFilledAmount
)
public
pure
returns (LibFillResults.MatchedFillResults memory matchedFillResults)
{
return calculateMatchedFillResults(leftOrder, rightOrder, leftOrderTakerAssetFilledAmount, rightOrderTakerAssetFilledAmount, false);
}
/// @dev Calculates fill amounts for the matched orders. /// @dev Calculates fill amounts for the matched orders.
/// 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.
@ -486,188 +477,6 @@ contract MixinMatchOrders is
return matchedFillResults; return matchedFillResults;
} }
/// @dev Match two complementary orders that have a profitable spread.
/// 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.
/// The profit made by the left order goes to the taker (who matched the two orders).
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
/// @param leftSignature Proof that order was created by the left maker.
/// @param rightSignature Proof that order was created by the right maker.
/// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders.
function matchOrders(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder,
bytes memory leftSignature,
bytes memory rightSignature
)
public
nonReentrant
returns (LibFillResults.MatchedFillResults memory matchedFillResults)
{
return _matchOrders(leftOrder, rightOrder, leftSignature, rightSignature, false);
}
/// @dev Match two complementary orders that have a profitable spread.
/// Each order is maximally filled at their respective price point, and
/// the matcher receives a profit denominated in either the left maker asset,
/// right maker asset, or a combination of both.
/// @param leftOrder First order to match.
/// @param rightOrder Second order to match.
/// @param leftSignature Proof that order was created by the left maker.
/// @param rightSignature Proof that order was created by the right maker.
/// @return matchedFillResults Amounts filled by maker and taker of matched orders.
function matchOrdersWithMaximalFill(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder,
bytes memory leftSignature,
bytes memory rightSignature
)
public
nonReentrant
returns (LibFillResults.MatchedFillResults memory matchedFillResults)
{
return _matchOrders(leftOrder, rightOrder, leftSignature, rightSignature, true);
}
function _batchMatchOrders(
LibOrder.Order[] memory leftOrders,
LibOrder.Order[] memory rightOrders,
bytes[] memory leftSignatures,
bytes[] memory rightSignatures,
bool withMaximalFill
)
internal
returns (LibFillResults.BatchMatchedFillResults memory batchMatchedFillResults)
{
// Ensure that the left and right orders have nonzero lengths.
if (leftOrders.length == 0) {
_rrevert(BatchMatchOrdersError(BatchMatchOrdersErrorCodes.ZERO_LEFT_ORDERS));
}
if (rightOrders.length == 0) {
_rrevert(BatchMatchOrdersError(BatchMatchOrdersErrorCodes.ZERO_RIGHT_ORDERS));
}
// Ensure that the left and right arrays are compatible.
if (leftOrders.length != leftSignatures.length) {
_rrevert(BatchMatchOrdersError(BatchMatchOrdersErrorCodes.INCOMPATIBLE_LEFT_ORDERS));
}
if (rightOrders.length != rightSignatures.length) {
_rrevert(BatchMatchOrdersError(BatchMatchOrdersErrorCodes.INCOMPATIBLE_RIGHT_ORDERS));
}
// Without simulating all of the order matching, this program cannot know how many
// matches there will be. To ensure that batchMatchedFillResults has enough memory
// allocated for the left and the right side, we will allocate enough space for the
// maximum amount of matches (the maximum of the left and the right sides).
uint256 maxLength = _max256(leftOrders.length, rightOrders.length);
batchMatchedFillResults.left = new LibFillResults.FillResults[](maxLength);
batchMatchedFillResults.right = new LibFillResults.FillResults[](maxLength);
// Set up initial indices.
uint256 matchCount;
uint256 leftIdx = 0;
uint256 rightIdx = 0;
// Keep local variables for orders, order info, and signatures for efficiency.
LibOrder.Order memory leftOrder = leftOrders[0];
LibOrder.Order memory rightOrder = rightOrders[0];
LibOrder.OrderInfo memory leftOrderInfo = getOrderInfo(leftOrder);
LibOrder.OrderInfo memory rightOrderInfo = getOrderInfo(rightOrder);
// Loop infinitely (until broken inside of the loop), but keep a counter of how
// many orders have been matched.
for (matchCount = 0;; matchCount++) {
// Match the two orders that are pointed to by the left and right indices
LibFillResults.MatchedFillResults memory matchResults = _matchOrders(
leftOrder,
rightOrder,
leftSignatures[leftIdx],
rightSignatures[rightIdx],
withMaximalFill
);
// Update the orderInfo structs with the updated takerAssetFilledAmount
leftOrderInfo.orderTakerAssetFilledAmount = _safeAdd(
leftOrderInfo.orderTakerAssetFilledAmount,
matchResults.left.takerAssetFilledAmount
);
rightOrderInfo.orderTakerAssetFilledAmount = _safeAdd(
rightOrderInfo.orderTakerAssetFilledAmount,
matchResults.right.takerAssetFilledAmount
);
// Add the matchResults and the profit made during the match to the
// batchMatchedFillResults for this batch.
batchMatchedFillResults.left[matchCount] = matchResults.left;
batchMatchedFillResults.right[matchCount] = matchResults.right;
batchMatchedFillResults.profitInLeftMakerAsset = _safeAdd(
batchMatchedFillResults.profitInLeftMakerAsset,
matchResults.leftMakerAssetSpreadAmount
);
batchMatchedFillResults.profitInRightMakerAsset = _safeAdd(
batchMatchedFillResults.profitInRightMakerAsset,
matchResults.rightMakerAssetSpreadAmount
);
// If the leftOrder is filled, update the leftIdx, leftOrder, and leftSignature,
// or break out of the loop if there are no more leftOrders to match.
if (leftOrderInfo.orderTakerAssetFilledAmount >= leftOrder.takerAssetAmount) {
if (++leftIdx == leftOrders.length) {
break;
} else {
leftOrder = leftOrders[leftIdx];
leftOrderInfo = getOrderInfo(leftOrder);
}
}
// If the rightOrder is filled, update the rightIdx, rightOrder, and rightSignature,
// or break out of the loop if there are no more rightOrders to match.
if (rightOrderInfo.orderTakerAssetFilledAmount >= rightOrder.takerAssetAmount) {
if (++rightIdx == rightOrders.length) {
break;
} else {
rightOrder = rightOrders[rightIdx];
rightOrderInfo = getOrderInfo(rightOrder);
}
}
}
// Update the lengths of the fill results for batchMatchResults
assembly {
mstore(mload(batchMatchedFillResults), matchCount)
mstore(mload(add(batchMatchedFillResults, 32)), matchCount)
}
// Return the fill results from the batch match
return batchMatchedFillResults;
}
function _assertValidMatch(
LibOrder.Order memory leftOrder,
LibOrder.Order memory rightOrder
)
internal
view
{
// Make sure there is a profitable spread.
// There is a profitable spread iff the cost per unit bought (OrderA.MakerAmount/OrderA.TakerAmount) for each order is greater
// than the profit per unit sold of the matched order (OrderB.TakerAmount/OrderB.MakerAmount).
// This is satisfied by the equations below:
// <leftOrder.makerAssetAmount> / <leftOrder.takerAssetAmount> >= <rightOrder.takerAssetAmount> / <rightOrder.makerAssetAmount>
// AND
// <rightOrder.makerAssetAmount> / <rightOrder.takerAssetAmount> >= <leftOrder.takerAssetAmount> / <leftOrder.makerAssetAmount>
// These equations can be combined to get the following:
if (_safeMul(leftOrder.makerAssetAmount, rightOrder.makerAssetAmount) <
_safeMul(leftOrder.takerAssetAmount, rightOrder.takerAssetAmount)) {
LibRichErrors._rrevert(LibExchangeRichErrors.NegativeSpreadError(
getOrderHash(leftOrder),
getOrderHash(rightOrder)
));
}
}
/// @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.
@ -719,7 +528,7 @@ contract MixinMatchOrders is
_assertValidMatch(leftOrder, rightOrder); _assertValidMatch(leftOrder, rightOrder);
// Compute proportional fill amounts // Compute proportional fill amounts
matchedFillResults = calculateMatchedFillResults( matchedFillResults = _calculateMatchedFillResults(
leftOrder, leftOrder,
rightOrder, rightOrder,
leftOrderInfo.orderTakerAssetFilledAmount, leftOrderInfo.orderTakerAssetFilledAmount,

View File

@ -2385,7 +2385,7 @@ describe('matchOrders', () => {
expectedTransferAmounts, expectedTransferAmounts,
); );
}); });
it('should correctly match one left order to two right orders, where the last should not be touched ', async () => { it('should correctly match one left order to two right orders, where the last should not be touched', async () => {
const leftOrders = [ const leftOrders = [
await orderFactoryLeft.newSignedOrderAsync({ await orderFactoryLeft.newSignedOrderAsync({
makerAddress: makerAddressLeft, makerAddress: makerAddressLeft,
@ -2419,7 +2419,7 @@ describe('matchOrders', () => {
// Taker // Taker
leftTakerFeeAssetPaidByTakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100% leftTakerFeeAssetPaidByTakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
rightTakerFeeAssetPaidByTakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100% rightTakerFeeAssetPaidByTakerAmount: Web3Wrapper.toBaseUnitAmount(100, 16), // 100%
} },
]; ];
await matchOrderTester.batchMatchOrdersAndAssertEffectsAsync( await matchOrderTester.batchMatchOrdersAndAssertEffectsAsync(
{ {