diff --git a/contracts/exchange/contracts/src/MixinMatchOrders.sol b/contracts/exchange/contracts/src/MixinMatchOrders.sol index c873948295..647d53d890 100644 --- a/contracts/exchange/contracts/src/MixinMatchOrders.sol +++ b/contracts/exchange/contracts/src/MixinMatchOrders.sol @@ -127,121 +127,24 @@ contract MixinMatchOrders is // Maximally fill the orders and pay out profits to the matcher in one or both of the maker assets. if (shouldMaximallyFillOrders) { - bool doesLeftMakerAssetProfitExist; - bool doesRightMakerAssetProfitExist; - - // Calculate the maximum fill results for the maker and taker assets. At least one of the orders will be fully filled. - // - // The maximum that the left maker can possibly buy is the amount that the right order can sell. - // The maximum that the right maker can possibly buy is the amount that the left order can sell. - // - // If the left order is fully filled, profit will be paid out in the left maker asset. If the right order is fully filled, - // the profit will be out in the right maker asset. - // - // There are three cases to consider: - // Case 1. - // If the left maker can buy more than the right maker can sell, then only the right order is fully filled. - // Case 2. - // If the right maker can buy more than the left maker can sell, then only the right order is fully filled. - // Case 3. - // If the right maker can sell the max of what the left maker can buy and the left maker can sell the max of - // what the right maker can buy, then both orders are fully filled. - if (leftTakerAssetAmountRemaining > rightMakerAssetAmountRemaining) { - // Case 1: Right order is fully filled with the profit paid in the left makerAsset - matchedFillResults.right.makerAssetFilledAmount = rightMakerAssetAmountRemaining; - matchedFillResults.right.takerAssetFilledAmount = rightTakerAssetAmountRemaining; - matchedFillResults.left.takerAssetFilledAmount = rightMakerAssetAmountRemaining; - // Round down to ensure the left maker's exchange rate does not exceed the price specified by the order. - // We favor the left maker when the exchange rate must be rounded and the profit is being paid in the - // left maker asset. - matchedFillResults.left.makerAssetFilledAmount = _safeGetPartialAmountFloor( - leftOrder.makerAssetAmount, - leftOrder.takerAssetAmount, - rightMakerAssetAmountRemaining - ); - // Indicate that the profit should be set to the spread denominated in the left maker asset. - doesLeftMakerAssetProfitExist = true; - } else if (rightTakerAssetAmountRemaining > leftMakerAssetAmountRemaining) { - // Case 2: Left order is fully filled with the profit paid in the right makerAsset. - matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining; - matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining; - // Round down to ensure the right maker's exchange rate does not exceed the price specified by the order. - // We favor the right maker when the exchange rate must be rounded and the profit is being paid in the - // right maker asset. - matchedFillResults.right.makerAssetFilledAmount = _safeGetPartialAmountFloor( - rightOrder.makerAssetAmount, - rightOrder.takerAssetAmount, - leftMakerAssetAmountRemaining - ); - matchedFillResults.right.takerAssetFilledAmount = leftMakerAssetAmountRemaining; - // Indicate that the profit should be set to the spread denominated in the right maker asset. - doesRightMakerAssetProfitExist = true; - } else { - // Case 3: The right and left orders are fully filled - matchedFillResults.right.makerAssetFilledAmount = rightMakerAssetAmountRemaining; - matchedFillResults.right.takerAssetFilledAmount = rightTakerAssetAmountRemaining; - matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining; - matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining; - // Indicate that the profit should be set to the spread denominated in the left and the right maker assets. - doesLeftMakerAssetProfitExist = true; - doesRightMakerAssetProfitExist = true; - } - - // Calculate amount given to taker in the left order's maker asset if the left spread will be part of the profit. - if (doesLeftMakerAssetProfitExist) { - matchedFillResults.profitInLeftMakerAsset = _safeSub( - matchedFillResults.left.makerAssetFilledAmount, - matchedFillResults.right.takerAssetFilledAmount - ); - } - - // Calculate amount given to taker in the right order's maker asset if the right spread will be part of the profit. - if (doesRightMakerAssetProfitExist) { - matchedFillResults.profitInRightMakerAsset = _safeSub( - matchedFillResults.right.makerAssetFilledAmount, - matchedFillResults.left.takerAssetFilledAmount - ); - } + _calculateMatchedFillResultsWithMaximalFill( + matchedFillResults, + leftOrder, + rightOrder, + leftMakerAssetAmountRemaining, + leftTakerAssetAmountRemaining, + rightMakerAssetAmountRemaining, + rightTakerAssetAmountRemaining + ); } else { - // 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 = rightMakerAssetAmountRemaining; - // 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 = _safeGetPartialAmountFloor( - leftOrder.makerAssetAmount, - leftOrder.takerAssetAmount, - rightMakerAssetAmountRemaining // matchedFillResults.left.takerAssetFilledAmount - ); - } else { - // Case 2: Left order is fully filled - matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining; - matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining; - matchedFillResults.right.makerAssetFilledAmount = leftTakerAssetAmountRemaining; - // 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 = _safeGetPartialAmountCeil( - rightOrder.takerAssetAmount, - rightOrder.makerAssetAmount, - leftTakerAssetAmountRemaining // matchedFillResults.right.makerAssetFilledAmount - ); - } - - // Calculate amount given to taker - matchedFillResults.profitInLeftMakerAsset = _safeSub( - matchedFillResults.left.makerAssetFilledAmount, - matchedFillResults.right.takerAssetFilledAmount + _calculateMatchedFillResults( + matchedFillResults, + leftOrder, + rightOrder, + leftMakerAssetAmountRemaining, + leftTakerAssetAmountRemaining, + rightMakerAssetAmountRemaining, + rightTakerAssetAmountRemaining ); } @@ -396,8 +299,9 @@ contract MixinMatchOrders is // 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 left side length added to the right side length). - uint256 maxLength = leftOrders.length + rightOrders.length; + // maximum amount of matches (the left side length added to the right side length + // minus 1). + uint256 maxLength = leftOrders.length + rightOrders.length - 1; batchMatchedFillResults.left = new LibFillResults.FillResults[](maxLength); batchMatchedFillResults.right = new LibFillResults.FillResults[](maxLength); @@ -483,6 +387,180 @@ contract MixinMatchOrders is return batchMatchedFillResults; } + /// @dev Calculates part of the matched fill results for a given situation using the fill strategy that only + /// awards profit denominated in the left maker asset. + /// @param matchedFillResults The MatchedFillResults struct to update with fill result calculations. + /// @param leftOrder The left order in the order matching situation. + /// @param rightOrder The right order in the order matching situation. + /// @param leftMakerAssetAmountRemaining The amount of the left order maker asset that can still be filled. + /// @param leftTakerAssetAmountRemaining The amount of the left order taker asset that can still be filled. + /// @param rightMakerAssetAmountRemaining The amount of the right order maker asset that can still be filled. + /// @param rightTakerAssetAmountRemaining The amount of the right order taker asset that can still be filled. + function _calculateMatchedFillResults( + MatchedFillResults memory matchedFillResults, + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + uint256 leftMakerAssetAmountRemaining, + uint256 leftTakerAssetAmountRemaining, + uint256 rightMakerAssetAmountRemaining, + uint256 rightTakerAssetAmountRemaining + ) + internal + pure + { + // 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 + _calculateCompleteRightFill( + matchedFillResults, + leftOrder, + rightMakerAssetAmountRemaining, + rightTakerAssetAmountRemaining + ); + } else { + // Case 2: Left order is fully filled + matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining; + matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining; + matchedFillResults.right.makerAssetFilledAmount = leftTakerAssetAmountRemaining; + // 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 = _safeGetPartialAmountCeil( + rightOrder.takerAssetAmount, + rightOrder.makerAssetAmount, + leftTakerAssetAmountRemaining // matchedFillResults.right.makerAssetFilledAmount + ); + } + + // Calculate amount given to taker + matchedFillResults.profitInLeftMakerAsset = _safeSub( + matchedFillResults.left.makerAssetFilledAmount, + matchedFillResults.right.takerAssetFilledAmount + ); + } + + /// @dev Calculates part of the matched fill results for a given situation using the maximal fill order matching + /// strategy. + /// @param matchedFillResults The MatchedFillResults struct to update with fill result calculations. + /// @param leftOrder The left order in the order matching situation. + /// @param rightOrder The right order in the order matching situation. + /// @param leftMakerAssetAmountRemaining The amount of the left order maker asset that can still be filled. + /// @param leftTakerAssetAmountRemaining The amount of the left order taker asset that can still be filled. + /// @param rightMakerAssetAmountRemaining The amount of the right order maker asset that can still be filled. + /// @param rightTakerAssetAmountRemaining The amount of the right order taker asset that can still be filled. + function _calculateMatchedFillResultsWithMaximalFill( + MatchedFillResults memory matchedFillResults, + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + uint256 leftMakerAssetAmountRemaining, + uint256 leftTakerAssetAmountRemaining, + uint256 rightMakerAssetAmountRemaining, + uint256 rightTakerAssetAmountRemaining + ) + internal + pure + { + bool doesLeftMakerAssetProfitExist; + bool doesRightMakerAssetProfitExist; + + // Calculate the maximum fill results for the maker and taker assets. At least one of the orders will be fully filled. + // + // The maximum that the left maker can possibly buy is the amount that the right order can sell. + // The maximum that the right maker can possibly buy is the amount that the left order can sell. + // + // If the left order is fully filled, profit will be paid out in the left maker asset. If the right order is fully filled, + // the profit will be out in the right maker asset. + // + // There are three cases to consider: + // Case 1. + // If the left maker can buy more than or equal to the right maker can sell, then only the right order is fully filled. + // Case 2. + // If the right maker can buy more than or equal to the left maker can sell, then only the right order is fully filled. + // Case 3. + // If the right maker can sell the max of what the left maker can buy and the left maker can sell the max of + // what the right maker can buy, then both orders are fully filled. + if (leftTakerAssetAmountRemaining >= rightMakerAssetAmountRemaining) { + // Case 1: Right order is fully filled with the profit paid in the left makerAsset + _calculateCompleteRightFill( + matchedFillResults, + leftOrder, + rightMakerAssetAmountRemaining, + rightTakerAssetAmountRemaining + ); + // Indicate that the profit should be set to the spread denominated in the left maker asset. + doesLeftMakerAssetProfitExist = true; + } else if (rightTakerAssetAmountRemaining >= leftMakerAssetAmountRemaining) { + // Case 2: Left order is fully filled with the profit paid in the right makerAsset. + matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining; + matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining; + // Round down to ensure the right maker's exchange rate does not exceed the price specified by the order. + // We favor the right maker when the exchange rate must be rounded and the profit is being paid in the + // right maker asset. + matchedFillResults.right.makerAssetFilledAmount = _safeGetPartialAmountFloor( + rightOrder.makerAssetAmount, + rightOrder.takerAssetAmount, + leftMakerAssetAmountRemaining + ); + matchedFillResults.right.takerAssetFilledAmount = leftMakerAssetAmountRemaining; + // Indicate that the profit should be set to the spread denominated in the right maker asset. + doesRightMakerAssetProfitExist = true; + } else { + // Case 3: The right and left orders are fully filled + matchedFillResults.right.makerAssetFilledAmount = rightMakerAssetAmountRemaining; + matchedFillResults.right.takerAssetFilledAmount = rightTakerAssetAmountRemaining; + matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining; + matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining; + // Indicate that the profit should be set to the spread denominated in the left and the right maker assets. + doesLeftMakerAssetProfitExist = true; + doesRightMakerAssetProfitExist = true; + } + + // Calculate amount given to taker in the left order's maker asset if the left spread will be part of the profit. + if (doesLeftMakerAssetProfitExist) { + matchedFillResults.profitInLeftMakerAsset = _safeSub( + matchedFillResults.left.makerAssetFilledAmount, + matchedFillResults.right.takerAssetFilledAmount + ); + } + + // Calculate amount given to taker in the right order's maker asset if the right spread will be part of the profit. + if (doesRightMakerAssetProfitExist) { + matchedFillResults.profitInRightMakerAsset = _safeSub( + matchedFillResults.right.makerAssetFilledAmount, + matchedFillResults.left.takerAssetFilledAmount + ); + } + } + + function _calculateCompleteRightFill( + MatchedFillResults memory matchedFillResults, + LibOrder.Order memory leftOrder, + uint256 rightMakerAssetAmountRemaining, + uint256 rightTakerAssetAmountRemaining + ) + internal + pure + { + matchedFillResults.right.makerAssetFilledAmount = rightMakerAssetAmountRemaining; + matchedFillResults.right.takerAssetFilledAmount = rightTakerAssetAmountRemaining; + matchedFillResults.left.takerAssetFilledAmount = rightMakerAssetAmountRemaining; + // Round down to ensure the left maker's exchange rate does not exceed the price specified by the order. + // We favor the left maker when the exchange rate must be rounded and the profit is being paid in the + // left maker asset. + matchedFillResults.left.makerAssetFilledAmount = _safeGetPartialAmountFloor( + leftOrder.makerAssetAmount, + leftOrder.takerAssetAmount, + rightMakerAssetAmountRemaining + ); + } + /// @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.